diff --git a/cranspasswords-server.py b/cranspasswords-server.py index 7c98e9f9a88fe1b67df6a6c3903cecb455b41692..569e8169f375d26df2218bad1a8fa3366bf3b2c5 100755 --- a/cranspasswords-server.py +++ b/cranspasswords-server.py @@ -2,8 +2,8 @@ # -*- encoding: utf-8 -*- """cranspasswords-server.py: Serveur pour cranspasswords""" -MYDIR = '/home/dstan/crans/cranspasswords/' -STORE = '/home/dstan/crans/passwords/v2/' +MYDIR = '/home/dstan/cranspasswords/' +STORE = MYDIR+'test/' import glob import os @@ -31,7 +31,7 @@ KEYS = { "becue": ("becue@crans.org", "194974E2"), "dstan": ("daniel.stan@crans.org", "6E1C820B"), "cauderlier": ("cauderlier@crans.org",None), #Méchant pas beau - "maioli": ("maioli@crans.org","9E5026E8") + "maioli": ("maioli@crans.org",None) #Bis (maybe 9E5026E8) } RTC=[ @@ -54,15 +54,8 @@ NOUNOUS=RTC+[ ] ROLES = { - "bureau": [ - "aza-vallina", - ], - "ca": [ - "aza-vallina", - "blockelet", - "durand-gasselin", - "lagorce", - ], + "bureau": [], + "ca": [], "rtc": RTC, "nounous": NOUNOUS, "nounous-w": NOUNOUS #Or maybe RTC ? diff --git a/cranspasswords.py b/cranspasswords.py index a32336ea43f8e53130bd03656017bb0303d67bab..ce217e5c187a9a641d4ee52660da8f72718abee3 100755 --- a/cranspasswords.py +++ b/cranspasswords.py @@ -8,6 +8,7 @@ import json import tempfile import os import atexit +import argparse ###### ## GPG Definitions @@ -20,8 +21,11 @@ GPG_ARGS = { 'receive-keys': ['--recv-keys'], } -DEBUG=False -CLIPBOARD=False # Par défaut, place-t-on le mdp dans le presse-papier ? +DEBUG = False +VERB = False +CLIPBOARD = False # Par défaut, place-t-on le mdp dans le presse-papier ? +FORCED = False #Mode interactif qui demande confirmation +NROLES = None # Droits à définir sur le fichier en édition def gpg(command, args = None): """Lance gpg pour la commande donnée avec les arguments @@ -30,7 +34,7 @@ def gpg(command, args = None): full_command.extend(GPG_ARGS[command]) if args: full_command.extend(args) - if DEBUG: + if VERB: stderr=sys.stderr else: stderr=subprocess.PIPE @@ -41,22 +45,23 @@ def gpg(command, args = None): stdout = subprocess.PIPE, stderr = stderr, close_fds = True) - if not DEBUG: + if not VERB: proc.stderr.close() return proc.stdin, proc.stdout ###### ## Remote commands -SSH = '/usr/bin/ssh' -SSH_HOST = 'localhost' -REMOTE_COMMAND = ['/home/dstan/crans/cranspasswords/cranspasswords-server.py'] +SERVER_CMD_DEBUG = ['/usr/bin/ssh', 'localhost', \ + '/home/dstan/crans/cranspasswords/cranspasswords-server.py'] +SERVER_CMD = ['/usr/bin/ssh', 'vo',\ + '/home/dstan/cranspasswords/cranspasswords-server'] +USER = 'dstan' def ssh(command, arg = None): """Lance ssh avec les arguments donnés. Renvoie son entrée standard et sa sortie standard.""" - full_command = [SSH, SSH_HOST] - full_command.extend(REMOTE_COMMAND) + full_command = list(SERVER_CMD) full_command.append(command) if arg: full_command.append(arg) @@ -101,6 +106,11 @@ def rm_file(filename): """Supprime le fichier sur le serveur distant""" return remote_command("rmfile", filename) +def get_my_roles(): + """Retoure la liste des rôles perso""" + allr = all_roles() + return filter(lambda role: USER in allr[role],allr.keys()) + ###### ## Local commands @@ -120,13 +130,12 @@ def check_keys(): for mail, key in keys.values(): if key: _, stdout = gpg("fingerprint", [key]) - if DEBUG: print "Checking %s" % mail + if VERB: print "Checking %s" % mail if str("<%s>" % mail.lower()) not in stdout.read().lower(): - if DEBUG: print "-->Fail on %s" % mail + if VERB: print "-->Fail on %s" % mail break else: return True - return False def encrypt(roles, contents): @@ -151,7 +160,7 @@ def encrypt(roles, contents): stdin.close() out = stdout.read() if out == '': - if DEBUG: print "Échec de chiffrement" + if VERB: print "Échec de chiffrement" return None else: return out @@ -167,6 +176,8 @@ def put_password(name, roles, contents): """Dépose le mot de passe après l'avoir chiffré pour les destinataires donnés""" enc_pwd = encrypt(roles, contents) + if NROLES != None: + roles = NROLES if enc_pwd <> None: return put_file(name, roles, enc_pwd) else: @@ -178,26 +189,6 @@ def get_password(name): return decrypt(remotefile['contents']) ## Interface -def usage(): - print """Cranspasswords 2 Usage: - cranspasswords [options] [<filename>] - cranspasswords <filename> Télécharge le fichier - cranspasswords Mode interactif - -Options: - --view Télécharge le fichier -# --upload Upload un nouveau fichier depuis stdin - --edit Lance $EDITOR sur le fichier -# --roles=<role1>,<role2>… Définit les rôles -# --roles+=<role> Ajoute un rôle -# --roles-=<role> Supprime un rôle -# --edit-roles Lance $EDITOR sur les rôles -# --rm Supprime le fichier - --update-keys Mets à jour les clés - --check-keys Vérifie les clés - -l, --list Liste les fichiers disponibles - --list-roles Liste des rôles disponibles - -c, --clipboard mot de passe en presse papier""" def editor(texte): """ Lance $EDITOR sur texte""" @@ -214,8 +205,12 @@ def editor(texte): def show_files(): print """Liste des fichiers disponibles""" - for fname in all_files(): - print " * " + fname + my_roles = get_my_roles() + for (fname,froles) in all_files().iteritems(): + access = set(my_roles).intersection(froles) != set([]) + print " %s %s (%s)" % ((access and '+' or '-'),fname,", ".join(froles)) + print """--Mes roles: %s""" % \ + ", ".join(my_roles) def show_roles(): print """Liste des roles disponibles""" @@ -254,7 +249,6 @@ def show_file(fname): print texte print "-----" print "Visible par: %s" % ','.join(value['roles']) - # Todo: some clipboard facility def edit_file(fname): value = get_file(fname) @@ -272,31 +266,129 @@ def edit_file(fname): print "Modifications enregistrées" else: print "Erreur lors de l'enregistrement (avez-vous les droits suffisants ?)" - + +def confirm(text): + if FORCED: return True + while True: + out = raw_input(text + ' (O/N)').lower() + if out == 'o': + return True + elif out == 'n': + return False + +def remove_file(fname): + if not confirm('Êtes-vous sûr de vouloir supprimer %s ?' % fname): + return + if rm_file(fname): + print "Suppression achevée" + else: + print "Erreur de suppression (avez-vous les droits ?)" + + +def my_check_keys(): + check_keys() and "Base de clés ok" or "Erreurs dans la base" + +def my_update_keys(): + print update_keys() + +def update_role(roles=None): + """ Reencode les fichiers, si roles est fourni, + contient une liste de rôles""" + my_roles = get_my_roles() + if roles == None: + # On ne conserve que les rôles qui finissent par -w + roles = [ r[:-2] for r in filter(lambda r: r.endswith('-w'),my_roles)] + if type(roles) != list: + roles = [roles] + + for (fname,froles) in all_files().iteritems(): + if set(roles).intersection(froles) == set([]): + continue + #if VERB: + print "Reencodage de %s" % fname + put_password(fname,froles,get_password(fname)) + +def parse_roles(strroles): + if strroles == None: return None + roles = all_roles() + my_roles = filter(lambda r: USER in roles[r],roles.keys()) + my_roles_w = [ r[:-2] for r in filter(lambda r: r.endswith('-w'),my_roles) ] + ret = set() + writable = False + for role in strroles.split(','): + if role not in roles.keys(): + print("Le rôle %s n'existe pas !" % role) + return False + if role.endswith('-w'): + print("Le rôle %s ne devrait pas être utilisé ! (utilisez %s)" + % (role,role[:-2])) + return False + writable = writable or role in my_roles_w + ret.add(role) + + if not FORCED and not writable: + if not confirm("Vous vous apprêtez à perdre vos droits d'écritures (role ne contient pas %s) sur ce fichier, continuer ?" % ", ".join(my_roles_w)): + return False + return list(ret) if __name__ == "__main__": - argv = sys.argv[1:] - if '-c' in argv or '--clipboard' in argv: - CLIPBOARD=True - action = show_file - if '--edit' in argv: - action = edit_file - if '-v' in argv: #Verbose ! - DEBUG = True - for arg in argv: - if arg in ['--list','-l']: - show_files() - elif not arg.startswith('-'): - action(arg) - elif arg == '--check-keys': - print check_keys() and "Base de clés ok" or "Erreurs dans la base" - elif arg == '--update-keys': - print update_keys() - elif arg == '--list-roles': - show_roles() - elif arg in ['-c','--clipboard','--view','--edit','-v']: - pass + parser = argparse.ArgumentParser(description="trousseau crans") + parser.add_argument('--test',action='store_true',default=False, + help='Utilisation du serveur de test') + parser.add_argument('-v','--verbose',action='store_true',default=False, + help="Mode verbeux") + parser.add_argument('-c','--clipboard',action='store_true',default=False, + help="Stocker le mot de passe dans le presse papier") + parser.add_argument('-f','--force',action='store_true',default=False, + help="Forcer l'action") + + # Actions possibles + action_grp = parser.add_mutually_exclusive_group(required=False) + action_grp.add_argument('--edit',action='store_const',dest='action', + default=show_file,const=edit_file, + help="Editer") + action_grp.add_argument('--view',action='store_const',dest='action', + default=show_file,const=show_file, + help="Voir") + action_grp.add_argument('--remove',action='store_const',dest='action', + default=show_file,const=remove_file, + help="Effacer") + 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('--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 des gens") + action_grp.add_argument('--recrypt-role',action='store_const',dest='action', + default=show_file,const=update_role, + help="Met à jour (reencode les roles)") + + parser.add_argument('--roles',nargs='?',default=None, + help="liste des roles à affecter au fichier") + parser.add_argument('fname',nargs='?',default=None, + help="Nom du fichier à afficher") + + parsed = parser.parse_args(sys.argv[1:]) + DEBUG = parsed.test + if DEBUG: + SERVER_CMD = SERVER_CMD_DEBUG + VERB = parsed.verbose + CLIPBOARD = parsed.clipboard + FORCED = parsed.force + NROLES = parse_roles(parsed.roles) + + if NROLES != False: + if parsed.action.func_code.co_argcount == 0: + parsed.action() + elif parsed.fname == None: + print("Vous devez fournir un nom de fichier avec cette commande") + parser.print_help() else: - usage() - break + parsed.action(parsed.fname)