diff --git a/client.py b/client.py index b4a6d075abb5a284da18b0697882e71eac57c7a3..400f7dbe4ac1aaecdd455ac192a6b50de608cc5f 100755 --- a/client.py +++ b/client.py @@ -32,16 +32,18 @@ from paramiko.ssh_exception import SSHException # On n'a pas encore accès à la config donc on devine le nom bootstrap_cmd_name = os.path.split(sys.argv[0])[1] default_config_path = os.path.expanduser("~/.config/" + bootstrap_cmd_name) -config_path = os.getenv("CRANSPASSWORDS_CLIENT_CONFIG_DIR", default_config_path) +config_path = os.getenv( + "CRANSPASSWORDS_CLIENT_CONFIG_DIR", default_config_path) config = ConfigParser() if not config.read(config_path + "/clientconfig.ini"): # If config could not be imported, display an error if required ducktape_display_error = sys.stderr.isatty() and \ - not any([opt in sys.argv for opt in ["-q", "--quiet"]]) and \ - __name__ == '__main__' + not any([opt in sys.argv for opt in ["-q", "--quiet"]]) and \ + __name__ == '__main__' if ducktape_display_error: # Do not use logger as it has not been initialized yet - print("%s/clientconfig.ini could not be read. Please read README." % config_path) + print("%s/clientconfig.ini could not be read. Please read README." % + config_path) exit(1) # Logger local @@ -49,34 +51,35 @@ log = logging.getLogger(bootstrap_cmd_name) #: Pattern utilisé pour détecter la ligne contenant le mot de passe dans les fichiers pass_regexp = re.compile('[\t ]*pass(?:word)?[\t ]*:[\t ]*(.*)\r?\n?$', - flags=re.IGNORECASE) + flags=re.IGNORECASE) -## GPG Definitions +# GPG Definitions #: Path du binaire gpg GPG = 'gpg' #: Paramètres à fournir à gpg en fonction de l'action désirée GPG_ARGS = { - 'decrypt' : ['-d'], - 'encrypt' : ['--armor', '-es'], - 'receive-keys' : ['--recv-keys'], - 'list-keys' : ['--list-keys', '--with-colons', '--fixed-list-mode', - '--with-fingerprint', '--with-fingerprint'], # Ce n'est pas une erreur. Il faut 2 --with-fingerprint pour avoir les fingerprints des subkeys. - } + 'decrypt': ['-d'], + 'encrypt': ['--armor', '-es'], + 'receive-keys': ['--recv-keys'], + 'list-keys': ['--list-keys', '--with-colons', '--fixed-list-mode', + '--with-fingerprint', '--with-fingerprint'], # Ce n'est pas une erreur. Il faut 2 --with-fingerprint pour avoir les fingerprints des subkeys. +} #: Mapping (lettre de trustlevel) -> (signification, faut-il faire confiance à la clé) GPG_TRUSTLEVELS = { - "-" : ("inconnue (pas de valeur assignée)", False), - "o" : ("inconnue (nouvelle clé)", False), - "i" : ("invalide (self-signature manquante ?)", False), - "n" : ("nulle (il ne faut pas faire confiance à cette clé)", False), - "m" : ("marginale (pas assez de lien de confiance vers cette clé)", False), - "f" : ("entière (clé dans le réseau de confiance)", True), - "u" : ("ultime (c'est probablement ta clé)", True), - "r" : ("révoquée", False), - "e" : ("expirée", False), - "q" : ("non définie", False), - } + "-": ("inconnue (pas de valeur assignée)", False), + "o": ("inconnue (nouvelle clé)", False), + "i": ("invalide (self-signature manquante ?)", False), + "n": ("nulle (il ne faut pas faire confiance à cette clé)", False), + "m": ("marginale (pas assez de lien de confiance vers cette clé)", False), + "f": ("entière (clé dans le réseau de confiance)", True), + "u": ("ultime (c'est probablement ta clé)", True), + "r": ("révoquée", False), + "e": ("expirée", False), + "q": ("non définie", False), +} + def gpg(options, command, args=None): """Lance gpg pour la commande donnée avec les arguments @@ -94,76 +97,84 @@ def gpg(options, command, args=None): # Run gpg log.info("Running `%s`" % " ".join(full_command)) proc = subprocess.Popen(full_command, - stdin = subprocess.PIPE, - stdout = subprocess.PIPE, - stderr = stderr, - close_fds = True) + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=stderr, + close_fds=True) if not options.verbose: proc.stderr.close() return proc.stdin, proc.stdout + def _parse_timestamp(string, canbenone=False): """Interprète ``string`` comme un timestamp depuis l'Epoch.""" if string == u'' and canbenone: return None return datetime.datetime(*time.localtime(int(string))[:7]) + def _parse_pub(data): """Interprète une ligne ``pub:``""" d = { - u'trustletter' : data[1], - u'length' : int(data[2]), - u'algorithm' : int(data[3]), - u'longid' : data[4], - u'signdate' : _parse_timestamp(data[5]), - u'expiredate' : _parse_timestamp(data[6], canbenone=True), - u'ownertrustletter' : data[8], - u'capabilities' : data[11], - } + u'trustletter': data[1], + u'length': int(data[2]), + u'algorithm': int(data[3]), + u'longid': data[4], + u'signdate': _parse_timestamp(data[5]), + u'expiredate': _parse_timestamp(data[6], canbenone=True), + u'ownertrustletter': data[8], + u'capabilities': data[11], + } return d + def _parse_uid(data): """Interprète une ligne ``uid:``""" d = { - u'trustletter' : data[1], - u'signdate' : _parse_timestamp(data[5], canbenone=True), - u'hash' : data[7], - u'uid' : data[9], - } + u'trustletter': data[1], + u'signdate': _parse_timestamp(data[5], canbenone=True), + u'hash': data[7], + u'uid': data[9], + } return d + def _parse_fpr(data): """Interprète une ligne ``fpr:``""" d = { - u'fpr' : data[9], - } + u'fpr': data[9], + } return d + def _parse_sub(data): """Interprète une ligne ``sub:``""" d = { - u'trustletter' : data[1], - u'length' : int(data[2]), - u'algorithm' : int(data[3]), - u'longid' : data[4], - u'signdate' : _parse_timestamp(data[5]), - u'expiredate' : _parse_timestamp(data[6], canbenone=True), - u'capabilities' : data[11], - } + u'trustletter': data[1], + u'length': int(data[2]), + u'algorithm': int(data[3]), + u'longid': data[4], + u'signdate': _parse_timestamp(data[5]), + u'expiredate': _parse_timestamp(data[6], canbenone=True), + u'capabilities': data[11], + } return d + #: Functions to parse the recognized fields GPG_PARSERS = { - u'pub' : _parse_pub, - u'uid' : _parse_uid, - u'fpr' : _parse_fpr, - u'sub' : _parse_sub, - } + u'pub': _parse_pub, + u'uid': _parse_uid, + u'fpr': _parse_fpr, + u'sub': _parse_sub, +} + def parse_keys(gpgout, debug=False): """Parse l'output d'un listing de clés gpg.""" ring = {} - init_value = "initialize" # Valeur utilisée pour dire "cet objet n'a pas encore été rencontré pendant le parsing" + # Valeur utilisée pour dire "cet objet n'a pas encore été rencontré pendant le parsing" + init_value = "initialize" current_pub = init_value current_sub = init_value for line in iter(gpgout.readline, ''): @@ -175,7 +186,8 @@ def parse_keys(gpgout, debug=False): line = line.decode("iso8859-1") except UnicodeDecodeError: line = line.decode("iso8859-1", "ignore") - log.warning("gpg is telling shit, it is neither ISO-8859-1 nor UTF-8. Dropping!") + log.warning( + "gpg is telling shit, it is neither ISO-8859-1 nor UTF-8. Dropping!") line = line.split(":") field = line[0] if field in GPG_PARSERS.keys(): @@ -229,8 +241,10 @@ def parse_keys(gpgout, debug=False): ring[current_pub["fpr"]] = current_pub return ring + class simple_memoize(object): """ Memoization/Lazy """ + def __init__(self, f): self.f = f self.val = None @@ -251,7 +265,7 @@ class simple_memoize(object): ###### -## Remote commands +# Remote commands @simple_memoize def create_ssh_client(): @@ -268,11 +282,13 @@ def create_ssh_client(): try: client.connect(str(options.serverdata['host'])) except SSHException: - log.error("Host key is unknown or you are using a outdated python-paramiko (ssh-ed25519 was implemented in 2017)") + log.error( + "Host key is unknown or you are using a outdated python-paramiko (ssh-ed25519 was implemented in 2017)") raise return client + def remote_command(options, command, arg=None, stdin_contents=None): """ Execute remote command and return output @@ -322,21 +338,25 @@ def all_keys(options): """Récupère les clés du serveur distant""" return remote_command(options, "listkeys") + @simple_memoize def all_roles(options): """Récupère les roles du serveur distant""" return remote_command(options, "listroles") + @simple_memoize def all_files(options): """Récupère les fichiers du serveur distant""" return remote_command(options, "listfiles") + @simple_memoize def restore_all_files(options): """Récupère les fichiers du serveur distant""" return remote_command(options, "restorefiles") + @simple_memoize def get_file(options, filename): """ @@ -344,23 +364,28 @@ def get_file(options, filename): """ return remote_command(options, "getfile", filename) + def put_files(options, files): """Dépose les fichiers sur le serveur distant""" return remote_command(options, "putfiles", stdin_contents=files) + def rm_file(filename): """Supprime le fichier sur le serveur distant""" return remote_command(options, "rmfile", filename) + @simple_memoize def get_my_roles(options): """Retourne la liste des rôles de l'utilisateur, et également la liste des rôles dont il possède le role-w.""" allroles = all_roles(options) distant_username = allroles.pop("whoami") - my_roles = [r for (r, users) in allroles.items() if distant_username in users] + my_roles = [r for (r, users) in allroles.items() + if distant_username in users] my_roles_w = [r[:-2] for r in my_roles if r.endswith("-w")] return (my_roles, my_roles_w) + def gen_password(): """Génère un mot de passe aléatoire""" random.seed(datetime.datetime.now().microsecond) @@ -369,16 +394,19 @@ def gen_password(): return u''.join([random.choice(chars) for _ in range(length)]) ###### -## Local commands +# Local commands + def update_keys(options): """Met à jour les clés existantes""" keys = all_keys(options) - _, stdout = gpg(options, "receive-keys", [key for _, key in keys.values() if key]) + _, stdout = gpg(options, "receive-keys", + [key for _, key in keys.values() if key]) return stdout.read().decode("utf-8") + def _check_encryptable(key): """Vérifie qu'on peut chiffrer un message pour ``key``. C'est-à -dire, que la clé est de confiance (et non expirée). @@ -401,6 +429,7 @@ def _check_encryptable(key): else: return "Aucune sous clé de chiffrement n'est de confiance et non expirée." + def check_keys(options, recipients=None, quiet=False): """Vérifie les clés, c'est-à -dire, si le mail est présent dans les identités du fingerprint, et que la clé est de confiance (et non expirée/révoquée). @@ -417,7 +446,7 @@ def check_keys(options, recipients=None, quiet=False): speak = options.verbose and not options.quiet else: speak = False - keys = {u : val for (u, val) in keys.iteritems() if u in recipients} + keys = {u: val for (u, val) in keys.iteritems() if u in recipients} if speak: print("M : le mail correspond à un uid du fingerprint\nC : confiance OK (inclut la vérification de non expiration).\n") _, gpgout = gpg(options, 'list-keys') @@ -450,11 +479,11 @@ def check_keys(options, recipients=None, quiet=False): # On cherche à savoir si on droppe ce recipient message = "Abandonner le chiffrement pour cette clé ? (Si vous la conservez, il est posible que gpg crashe)" if confirm(options, message, ('drop', fpr, mail)): - drop = True # si on a répondu oui à "abandonner ?", on droppe + drop = True # si on a répondu oui à "abandonner ?", on droppe elif options.drop_invalid and options.force: - drop = True # ou bien si --drop-invalid avec --force nous autorisent à dropper silencieusement + drop = True # ou bien si --drop-invalid avec --force nous autorisent à dropper silencieusement else: - drop = False # Là , on droppe pas + drop = False # Là , on droppe pas if not drop: trusted_recipients.append(recipient) else: @@ -466,6 +495,7 @@ def check_keys(options, recipients=None, quiet=False): else: return trusted_recipients + def get_recipients_of_roles(options, roles): """Renvoie les destinataires d'une liste de rôles""" recipients = set() @@ -477,11 +507,13 @@ def get_recipients_of_roles(options, roles): recipients.add(recipient) return recipients + def get_dest_of_roles(options, roles): """Renvoie la liste des "username : mail (fingerprint)" """ allkeys = all_keys(options) return ["%s : %s (%s)" % (rec, allkeys[rec][0], allkeys[rec][1]) - for rec in get_recipients_of_roles(options, roles) if allkeys[rec][1]] + for rec in get_recipients_of_roles(options, roles) if allkeys[rec][1]] + def encrypt(options, roles, contents): """Chiffre le contenu pour les roles donnés""" @@ -505,6 +537,7 @@ def encrypt(options, roles, contents): else: return [True, out] + def decrypt(options, contents): """Déchiffre le contenu""" stdin, stdout = gpg(options, "decrypt") @@ -512,27 +545,31 @@ def decrypt(options, contents): stdin.close() return stdout.read().decode("utf-8") + def put_password(options, roles, contents): """Dépose le mot de passe après l'avoir chiffré pour les destinataires dans ``roles``.""" success, enc_pwd_or_error = encrypt(options, roles, contents) if success: enc_pwd = enc_pwd_or_error - return put_files(options, [{'filename' : options.fname, 'roles' : roles, 'contents' : enc_pwd}])[0] + return put_files(options, [{'filename': options.fname, 'roles': roles, 'contents': enc_pwd}])[0] else: error = enc_pwd_or_error return [False, error] ###### -## Interface +# Interface + NEED_FILENAME = [] + def need_filename(f): """Décorateur qui ajoutera la fonction à la liste des fonctions qui attendent un filename.""" NEED_FILENAME.append(f) return f + def editor(texte, annotations=""): """ Lance $EDITOR sur texte. Renvoie le nouveau texte si des modifications ont été apportées, ou None @@ -556,9 +593,11 @@ def editor(texte, annotations=""): f.seek(0) ntexte = f.read().decode("utf-8", errors='ignore') f.close() - ntexte = u'\n'.join(filter(lambda l: not l.startswith('#'), ntexte.split('\n'))) + ntexte = u'\n'.join( + filter(lambda l: not l.startswith('#'), ntexte.split('\n'))) return ntexte + def show_files(options): """Affiche la liste des fichiers disponibles sur le serveur distant""" my_roles, _ = get_my_roles(options) @@ -572,6 +611,7 @@ def show_files(options): print((" %s %s (%s)" % ((access and '+' or '-'), fname, ", ".join(froles)))) print(("""--Mes roles: %s""" % (", ".join(my_roles),))) + def restore_files(options): """Restore les fichiers corrompues sur le serveur distant""" print("Fichier corrompus :") @@ -579,32 +619,34 @@ def restore_files(options): keys = files.keys() keys.sort() for fname in keys: - print(" %s (%s)" % ( fname, files[fname])) + print(" %s (%s)" % (fname, files[fname])) def show_roles(options): """Affiche la liste des roles existants""" print("Liste des roles disponibles") - allroles = all_roles(options) + allroles = all_roles(options) for (role, usernames) in allroles.iteritems(): if role == "whoami": continue if not role.endswith('-w'): print(" * %s : %s" % (role, ", ".join(usernames))) + def show_servers(options): """Affiche la liste des serveurs disponibles""" print("Liste des serveurs disponibles") for server in config.keys(): print(" * " + server) + def saveclipboard(restore=False, old_clipboard=None): """Enregistre le contenu du presse-papier. Le rétablit si ``restore=True``""" if restore and old_clipboard == None: return act = '-in' if restore else '-out' proc = subprocess.Popen(['xclip', act, '-selection', 'clipboard'], - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=sys.stderr) + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=sys.stderr) if not restore: old_clipboard = proc.stdout.read() else: @@ -614,27 +656,29 @@ def saveclipboard(restore=False, old_clipboard=None): proc.stdout.close() return old_clipboard + def clipboard(texte): """Place ``texte`` dans le presse-papier en mémorisant l'ancien contenu.""" old_clipboard = saveclipboard() - proc =subprocess.Popen(['xclip', '-selection', 'clipboard'],\ - stdin=subprocess.PIPE, stdout=sys.stdout, stderr=sys.stderr) + proc = subprocess.Popen(['xclip', '-selection', 'clipboard'], + stdin=subprocess.PIPE, stdout=sys.stdout, stderr=sys.stderr) proc.stdin.write(texte.encode("utf-8")) proc.stdin.close() return old_clipboard + @need_filename def show_file(options): """Affiche le contenu d'un fichier""" fname = options.fname gotit, value = get_file(options, fname) if not gotit: - log.warn(value) # value contient le message d'erreur + log.warn(value) # value contient le message d'erreur return passfile = value (sin, sout) = gpg(options, 'decrypt') content = passfile['contents'] - + # Kludge (broken db ?) if type(content) == list: log.warn("Eau dans le gaz") @@ -671,14 +715,16 @@ def show_file(options): if is_key: filtered = "La clé a été mise dans l'agent ssh" - shown = "Fichier %s:\n\n%s-----\nVisible par: %s\n" % (fname, filtered, ','.join(passfile['roles'])) + shown = "Fichier %s:\n\n%s-----\nVisible par: %s\n" % ( + fname, filtered, ','.join(passfile['roles'])) if is_key: with tempfile.NamedTemporaryFile(suffix='') as key_file: # Génère la clé publique correspondante key_file.write(texte.encode('utf-8')) key_file.flush() - pub = subprocess.check_output(['ssh-keygen', '-y', '-f', key_file.name]) + pub = subprocess.check_output( + ['ssh-keygen', '-y', '-f', key_file.name]) # Charge en mémoire subprocess.check_call(['ssh-add', key_file.name]) @@ -704,6 +750,7 @@ def show_file(options): if old_clipboard is not None: saveclipboard(restore=True, old_clipboard=old_clipboard) + @need_filename def edit_file(options): """Modifie/Crée un fichier""" @@ -729,7 +776,7 @@ Enregistrez le fichier vide pour annuler.\n""" if new_roles is None: new_roles = parse_roles(options, cast=True) - passfile = {'roles' : new_roles} + passfile = {'roles': new_roles} elif not gotit: log.warn(value) return @@ -764,15 +811,16 @@ Enregistrez le fichier vide pour annuler.\n""" annotations += """Ce fichier sera chiffré pour les rôles suivants :\n%s\n C'est-à -dire pour les utilisateurs suivants :\n%s""" % ( - ', '.join(new_roles), - '\n'.join(' %s' % rec for rec in get_dest_of_roles(options, new_roles)) - ) + ', '.join(new_roles), + '\n'.join(' %s' % + rec for rec in get_dest_of_roles(options, new_roles)) + ) ntexte = editor(texte, annotations) if ((not nfile and ntexte in [u'', texte] # pas nouveau, vidé ou pas modifié - and set(new_roles) == set(passfile['roles'])) # et on n'a même pas touché à ses rôles, - or (nfile and ntexte == u'')): # ou alors on a créé un fichier vide. + and set(new_roles) == set(passfile['roles'])) # et on n'a même pas touché à ses rôles, + or (nfile and ntexte == u'')): # ou alors on a créé un fichier vide. message = "Pas de modification à enregistrer.\n" message += "Si ce n'est pas un nouveau fichier, il a été vidé ou n'a pas été modifié (même pas ses rôles).\n" message += "Si c'est un nouveau fichier, vous avez tenté de le créer vide." @@ -783,7 +831,10 @@ C'est-à -dire pour les utilisateurs suivants :\n%s""" % ( success, message = put_password(options, new_roles, ntexte) print(message) + _remember_dict = {} + + def confirm(options, text, remember_key=None): """Demande confirmation, sauf si on est mode ``--force``. Si ``remember_key`` est fourni, il doit correspondre à un objet hashable @@ -807,6 +858,7 @@ def confirm(options, text, remember_key=None): _remember_dict[remember_key] = res return res + @need_filename def remove_file(options): """Supprime un fichier""" @@ -822,15 +874,17 @@ def my_check_keys(options): print("Vérification que les clés sont valides (uid correspondant au login) et de confiance.") print(check_keys(options) and "Base de clés ok" or "Erreurs dans la base") + def my_update_keys(options): """Met à jour les clés existantes et affiche le résultat""" print(update_keys(options)) + def recrypt_files(options, strict=False): """Rechiffre les fichiers. Ici, la signification de ``options.roles`` est : - strict => on chiffre les fichiers dont *tous* les rôles sont dans la liste - non strict => on ne veut rechiffrer que les fichiers qui ont au moins un de ces roles. + strict => on chiffre les fichiers dont *tous* les rôles sont dans la liste + non strict => on ne veut rechiffrer que les fichiers qui ont au moins un de ces roles. """ rechiffre_roles = options.roles _, my_roles_w = get_my_roles(options) @@ -851,7 +905,7 @@ def recrypt_files(options, strict=False): return bool(set(fileroles).intersection(rechiffre_roles)) askfiles = [filename for (filename, fileroles) in allfiles.iteritems() - if is_wanted(fileroles) ] + if is_wanted(fileroles)] files = [get_file(options, f) for f in askfiles] # Au cas où on aurait échoué à récupérer ne serait-ce qu'un de ces fichiers, @@ -867,13 +921,14 @@ def recrypt_files(options, strict=False): if not confirm(options, message + "\nConfirmer"): exit(2) # On rechiffre - to_put = [{'filename' : f['filename'], - 'roles' : f['roles'], - 'contents' : encrypt(options, f['roles'], decrypt(options, f['contents']))[-1]} + to_put = [{'filename': f['filename'], + 'roles': f['roles'], + 'contents': encrypt(options, f['roles'], decrypt(options, f['contents']))[-1]} for [success, f] in files] if to_put: if not options.quiet: - print("Rechiffrement de %s" % (", ".join([f['filename'] for f in to_put]))) + print("Rechiffrement de %s" % + (", ".join([f['filename'] for f in to_put]))) results = put_files(options, to_put) # On affiche les messages de retour if not options.quiet: @@ -883,6 +938,7 @@ def recrypt_files(options, strict=False): if not options.quiet: print("Aucun fichier n'a besoin d'être rechiffré") + def parse_roles(options, cast=False): """Interprête la liste de rôles fournie par l'utilisateur. Si il n'en a pas fourni, c'est-à -dire que roles @@ -911,11 +967,13 @@ def parse_roles(options, cast=False): log.warn("role %s do not exists" % role) exit(1) if role.endswith('-w'): - log.warn("role %s should not be used, rather use %s" % (role, role[:-2])) + log.warn("role %s should not be used, rather use %s" % + (role, role[:-2])) exit(1) ret.add(role) return list(ret) + def insult_on_nofilename(options, parser): """Insulte (si non quiet) et quitte si aucun nom de fichier n'a été fourni en commandline.""" if options.fname == None: @@ -924,6 +982,7 @@ def insult_on_nofilename(options, parser): parser.print_help() exit(1) + if __name__ == "__main__": # Gestion des arguments parser = argparse.ArgumentParser( @@ -941,58 +1000,132 @@ if __name__ == "__main__": default=False, help="silent mode: hide errors, overrides verbosity" ) - parser.add_argument('-s', '--server', default='DEFAULT', - help="Utilisation d'un serveur alternatif (test, backup, etc)") - parser.add_argument('--drop-invalid', action='store_true', default=False, + parser.add_argument( + '-s', '--server', + default='DEFAULT', + help="Utilisation d'un serveur alternatif (test, backup, etc)" + ) + parser.add_argument( + '--drop-invalid', + action='store_true', + default=False, dest='drop_invalid', - help="Combiné avec --force, droppe les clés en lesquelles on n'a pas confiance sans demander confirmation.") - parser.add_argument('-c', '--clipboard', action='store_true', default=None, + help="Combiné avec --force, droppe les clés en lesquelles on n'a pas confiance sans demander confirmation." + ) + parser.add_argument( + '-c', '--clipboard', + action='store_true', + default=None, help="Stocker le mot de passe dans le presse papier") - parser.add_argument('--no-clip', '--noclip', '--noclipboard', action='store_false', default=None, + parser.add_argument( + '--no-clip', '--noclip', '--noclipboard', + action='store_false', + default=None, dest='clipboard', - help="Ne PAS stocker le mot de passe dans le presse papier") - parser.add_argument('-f', '--force', action='store_true', default=False, - help="Ne pas demander confirmation") + help="Ne PAS stocker le mot de passe dans le presse papier" + ) + parser.add_argument( + '-f', '--force', + action='store_true', + default=False, + help="Ne pas demander confirmation" + ) # Actions possibles action_grp = parser.add_mutually_exclusive_group(required=False) - action_grp.add_argument('-e', '--edit', action='store_const', dest='action', - default=show_file, const=edit_file, - help="Editer (ou créer)") - action_grp.add_argument('--view', action='store_const', dest='action', - default=show_file, const=show_file, - help="Voir le fichier") - action_grp.add_argument('--remove', action='store_const', dest='action', - default=show_file, const=remove_file, - help="Effacer le fichier") - action_grp.add_argument('-l', '--list', action='store_const', dest='action', - default=show_file, const=show_files, - help="Lister les fichiers") - action_grp.add_argument('-r', '--restore', action='store_const', dest='action', - default=show_file, const=restore_files, - help="Restorer les fichiers corrompues") - action_grp.add_argument('--check-keys', action='store_const', dest='action', - default=show_file, const=my_check_keys, - help="Vérifier les clés") - action_grp.add_argument('--update-keys', action='store_const', dest='action', - default=show_file, const=my_update_keys, - help="Mettre à jour les clés") - action_grp.add_argument('--list-roles', action='store_const', dest='action', - default=show_file, const=show_roles, - help="Lister les rôles existants") - action_grp.add_argument('--list-servers', action='store_const', dest='action', - default=show_file, const=show_servers, + action_grp.add_argument( + '-e', '--edit', + action='store_const', + dest='action', + default=show_file, + const=edit_file, + help="Editer (ou créer)" + ) + action_grp.add_argument( + '--view', + action='store_const', + dest='action', + default=show_file, + const=show_file, + help="Voir le fichier" + ) + action_grp.add_argument( + '--remove', + action='store_const', + dest='action', + default=show_file, + const=remove_file, + help="Effacer le fichier" + ) + action_grp.add_argument( + '-l', '--list', + action='store_const', + dest='action', + default=show_file, + const=show_files, + help="Lister les fichiers" + ) + action_grp.add_argument( + '-r', '--restore', + action='store_const', dest='action', + default=show_file, + const=restore_files, + help="Restorer les fichiers corrompues" + ) + action_grp.add_argument( + '--check-keys', + + action='store_const', + dest='action', + default=show_file, + const=my_check_keys, + help="Vérifier les clés" + ) + action_grp.add_argument( + '--update-keys', + action='store_const', dest='action', + default=show_file, + const=my_update_keys, + help="Mettre à jour les clés" + ) + action_grp.add_argument( + '--list-roles', + action='store_const', + dest='action', + default=show_file, + const=show_roles, + help="Lister les rôles existants" + ) + action_grp.add_argument( + '--list-servers', + action='store_const', + dest='action', + default=show_file, + const=show_servers, help="Lister les serveurs") - action_grp.add_argument('--recrypt-files', action='store_const', dest='action', - default=show_file, const=recrypt_files, + action_grp.add_argument( + '--recrypt-files', + action='store_const', + dest='action', + default=show_file, + const=recrypt_files, help="""Rechiffrer les mots de passe. (Avec les mêmes rôles que ceux qu'ils avant. - Cela sert à mettre à jour les recipients pour qui un password est chiffré)""") - action_grp.add_argument('--strict-recrypt-files', action='store_const', dest='action', - default=show_file, const=lambda x:recrypt_files(x, strict=True), - help="""Rechiffrer les mots de passe (mode strict, voir --roles)""") + Cela sert à mettre à jour les recipients pour qui un password est chiffré)""" + ) + action_grp.add_argument( + '--strict-recrypt-files', + action='store_const', + dest='action', + default=show_file, const=lambda x: recrypt_files( + x, strict=True), + help="""Rechiffrer les mots de passe (mode strict, voir --roles)""" + ) - parser.add_argument('--roles', nargs='?', default=None, + parser.add_argument( + '--roles', + nargs='?', + default=None, help="""Liste de roles (séparés par des virgules). Par défaut, tous les rôles en écriture (sauf pour l'édition, d'un fichier existant). Avec --edit: le fichier sera chiffré pour exactement ces roles @@ -1002,8 +1135,12 @@ if __name__ == "__main__": * strict: tout fichier dont *tous* les rôles sont dans la liste """) - parser.add_argument('fname', nargs='?', default=None, - help="Nom du fichier à afficher") + parser.add_argument( + 'fname', + nargs='?', + default=None, + help="Nom du fichier à afficher" + ) # On parse les options fournies en commandline options = parser.parse_args() @@ -1020,15 +1157,16 @@ if __name__ == "__main__": format='\033[90m%(asctime)s\033[1;0m %(name)s %(levelname)s %(message)s' ) - ## On calcule les options qui dépendent des autres. - ## C'est un peu un hack, peut-être que la méthode propre serait de surcharger argparse.ArgumentParser - ## et argparse.Namespace, mais j'ai pas réussi à comprendre commenr m'en sortir. + # On calcule les options qui dépendent des autres. + # C'est un peu un hack, peut-être que la méthode propre serait de surcharger argparse.ArgumentParser + # et argparse.Namespace, mais j'ai pas réussi à comprendre commenr m'en sortir. # ** Presse papier ** # Si l'utilisateur n'a rien dit (ni option --clipboard ni --noclipboard), # on active le clipboard par défaut, à la condition # que xclip existe et qu'il a un serveur graphique auquel parler. if options.clipboard is None: - options.clipboard = bool(os.getenv('DISPLAY')) and os.path.exists('/usr/bin/xclip') + options.clipboard = bool( + os.getenv('DISPLAY')) and os.path.exists('/usr/bin/xclip') # On récupère les données du serveur à partir du nom fourni options.serverdata = config[options.server] # On parse les roles fournis, et il doivent exister, ne pas être -w…