#!/usr/bin/env python3 """A tool for finding the passwords required by the plugin""" import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk import argparse import sys supported_services = ["org.freedesktop.NetworkManager.wireguard"] # for parsing the data-keys DATA_KEY_TAG = "DATA_KEY=" DATA_VAL_TAG = "DATA_VAL=" SECRET_KEY_TAG = "SECRET_KEY=" SECRET_VAL_TAG = "SECRET_VAL=" # strings used in hints VPN_MESSAGE = "x-vpn-message:" VPN_PASS = "password" VPN_CERTPASS = "cert-pass" VPN_PROXY_PASS = "http-proxy-password" # NMSettingSecretFlags NM_SETTING_SECRET_FLAG_NONE = 0 NM_SETTING_SECRET_FLAG_AGENT_OWNED = 1 NM_SETTING_SECRET_FLAG_NOT_SAVED = 2 NM_SETTING_SECRET_FLAG_NOT_REQUIRED = 4 class PasswordDialog(Gtk.Dialog): """The dialog used for communication with the User (for password entry)""" def __init__(self, parent, prompt, password, need_pw, certpass, need_cp, proxy_pass, need_pp): Gtk.Dialog.__init__(self, "Authenticate VPN", parent, 0, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK)) self.password = password if password is not None else "" self.certpass = certpass if certpass is not None else "" self.proxypass = proxy_pass if proxy_pass is not None else "" self.set_default_size(150, 100) label = Gtk.Label(prompt) area = Gtk.Box() area.set_spacing(10) box = self.get_content_area() box.set_spacing(10) box.add(label) # create the labels / text-fields for the passwords pw_box = Gtk.Box() self.pw = Gtk.Label("Password") self.pw_field = Gtk.Entry() self.pw_field.set_text(self.password) pw_box.set_homogeneous(True) pw_box.add(self.pw) pw_box.add(self.pw_field) cp_box = Gtk.Box() self.cp = Gtk.Label("Certificate Password") self.cp_field = Gtk.Entry() self.cp_field.set_text(self.certpass) cp_box.set_homogeneous(True) cp_box.add(self.cp) cp_box.add(self.cp_field) pp_box = Gtk.Box() self.pp = Gtk.Label("Proxy Password") self.pp_field = Gtk.Entry() self.pp_field.set_text(self.proxypass) pp_box.set_homogeneous(True) pp_box.add(self.pp) pp_box.add(self.pp_field) if need_pw: box.add(pw_box) if need_cp: box.add(cp_box) if need_pp: box.add(pp_box) area.add(box) self.show_all() def get_password(self): """Return the password entered by the user (only valid after .run())""" return self.pw_field.get_text() def get_certpass(self): """Return the cert-pass entered by the user (only valid after .run())""" return self.cp_field.get_text() def get_proxypass(self): """Return the http-proxy-password entered by the user (only valid after .run())""" return self.pp_field.get_text() def read_details(input_file): """Read the DATA and SECRET things passed to the stdin and create dictionaries from them""" data = {} secrets = {} data_key = None secret_key = None for line in input_file: line = line.strip() if line == "DONE": break else: if line.startswith(DATA_KEY_TAG): data_key = line[len(DATA_KEY_TAG):] elif line.startswith(DATA_VAL_TAG): data_val = line[len(DATA_VAL_TAG):] data[data_key] = data_val elif line.startswith(SECRET_KEY_TAG): secret_key = line[len(SECRET_KEY_TAG):] elif line.startswith(SECRET_VAL_TAG): secret_val = line[len(SECRET_VAL_TAG):] secrets[secret_key] = secret_val return data, secrets def check_passwords_required(hints, name): """Check which passwords we need for the plugin""" prompt = "You need to authenticate to access the VPN '%s'" % name need_password, need_certpass, need_proxy_pass = False, False, False for hint in hints: if hint.startswith(VPN_MESSAGE): prompt = hint[len(VPN_MESSAGE):] elif hint == VPN_PASS: need_password = True elif hint == VPN_CERTPASS: need_certpass = True elif hint == VPN_PROXY_PASS: need_proxy_pass = True # TODO implement other logic from `get_passwords_required()` as well return prompt, need_password, need_certpass, need_proxy_pass def keyring_lookup(uuid, key): """Look up the information stored in the keyring for uuid and key""" # TODO return "asdf" def nm_vpn_service_plugin_get_secret_flags(data_dict, secret_name, out_flags): """Check if the """ lookup_item = "%s-flags" % secret_name if lookup_item not in data_dict: # if the constructed lookup item is not in the dictionary... return False, out_flags else: try: entry = data_dict[lookup_item] flag = int(entry) # check if the flag is one of the NMSecretSettingFlags if flag not in [0,1,2,4] or str(flag) != entry: return False, out_flags return True, flag except: return False, out_flags def find_existing_passwords(data_dict, secrets_dict, vpn_uuid, need_pass, need_certpass, need_proxypass): """Check if any of the required passwords can be found in the secrets_dict or the keyring""" pw_flags = NM_SETTING_SECRET_FLAG_NONE cp_flags = NM_SETTING_SECRET_FLAG_NONE pp_flags = NM_SETTING_SECRET_FLAG_NONE stored_pw = None stored_cp = None stored_pp = None # password # check if the DATA dictionary contains any flags regarding the password # and if so, check if it is anything else than NM_SETTING_SECRET_FLAG_NOT_SAVED tmp, pw_flags = nm_vpn_service_plugin_get_secret_flags(data_dict, VPN_PASS, pw_flags) if pw_flags != NM_SETTING_SECRET_FLAG_NOT_SAVED: # check out the SECRETS dictionary # or, if that fails # check out the keyring (TODO) if VPN_PASS in secrets_dict: stored_pw = secrets_dict[VPN_PASS] else: pass # stored_pw = keyring_lookup(vpn_uuid, VPN_PASS) # certpass tmp, cp_flags = nm_vpn_service_plugin_get_secret_flags(data_dict, VPN_CERTPASS, cp_flags) if cp_flags != NM_SETTING_SECRET_FLAG_NOT_SAVED: if VPN_CERTPASS in secrets_dict: stored_cp = secrets_dict[VPN_CERTPASS] else: pass # stored_cp = keyring_lookup(vpn_uuid, VPN_CERTPASS) # proxy tmp, pp_flags = nm_vpn_service_plugin_get_secret_flags(data_dict, VPN_PROXY_PASS, pp_flags) if pp_flags != NM_SETTING_SECRET_FLAG_NOT_SAVED: if VPN_PROXY_PASS in secrets_dict: stored_pp = secrets_dict[VPN_PROXY_PASS] else: pass # stored_pp = keyring_lookup(vpn_uuid, VPN_PROXY_PASS) return stored_pw, stored_cp, stored_pp def wait_for_quit(): """Wait until we read QUIT from stdin""" for line in sys.stdin: line = line.strip() if line == "QUIT": break descr = "" epilog = "" parser = argparse.ArgumentParser(description=descr, epilog=epilog) parser.add_argument("-r", "--reprompt", type=str, help="Reprompt for password") parser.add_argument("-u", "--uuid", type=str, help="UUID of VPN connection") parser.add_argument("-n", "--name", type=str, help="Name of VPN connection") parser.add_argument("-s", "--service", type=str, help="VPN service type") parser.add_argument("-i", "--allow-interaction", action="store_true", default=False, help="Allow user interaction") parser.add_argument("--external-ui-mode", action="store_true", default=False, help="External UI mode") parser.add_argument("-t", "--hint", action="append", help="Hints from the VPN plugin") args = parser.parse_args() canceled = False hints = args.hint ext_ui = args.external_ui_mode retry = args.reprompt interactive = args.allow_interaction uuid = args.uuid name = args.name service = args.service if hints is None: hints = [] if uuid is None or name is None or service is None: print("UUID (-u), Name (-n) and Service (-s) have to be provided", file=sys.stderr) exit(1) if service not in supported_services: print("The service '%s' is not supported" % service, file=sys.stderr) print("Supported services: %s" % supported_services, file=sys.stderr) exit(1) if ext_ui: print("Sorry, but external UI is not supported", file=sys.stderr) # read data and secrets from stdin until "DONE", in the format # # DATA_KEY=key # DATA_VAL=value # SECRET_KEY=key # SECRET_VAL=value # DONE data, secrets = read_details(sys.stdin) if not data and not secrets: # sys.exit(1) pass print(data) print(secrets) # check, which passwords are required prompt, need_password, need_certpass, need_proxy_pass = check_passwords_required(hints, name) if need_password or need_certpass or need_proxy_pass: # find whatever we need in the secrets map or the keyring (TODO) pw, cp, pp = find_existing_passwords(data, secrets, uuid, need_password, need_certpass, need_proxy_pass) required_secrets = False # check if there is anything left we need if need_password and pw is None: required_secrets = True if need_certpass and cp is None: required_secrets = True if need_proxy_pass and pp is None: required_secrets = True # if there is, we have to ask the user if interactive and (required_secrets or retry): dialog = PasswordDialog(None, prompt, pw, need_password, cp, need_certpass, pp, need_proxy_pass) resp = dialog.run() if resp == Gtk.ResponseType.OK: # fetch what we need from the dialog if need_password: pw = dialog.get_password() if need_certpass: cp = dialog.get_certpass() if need_proxy_pass: pp = dialog.get_proxypass() else: canceled = True # if the dialog wasn't cancelled or the session wasn't interactive at all, # print to stdout what we found out if not canceled: # print whatever we found out if pw is not None: print("%s\n%s" % (VPN_PASS, pw)) if cp is not None: print("%s\n%s" % (VPN_CERTPASS, cp)) if pp is not None: print("%s\n%s" % (VPN_PROXY_PASS, pp)) print("\n\n", end="") # wait for QUIT from the stdin wait_for_quit() exit(1 if canceled else 0)