Newer
Older
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""cranspasswords: gestion des mots de passe du Cr@ns"""
import sys
import subprocess
import json
######
## GPG Definitions
GPG = '/usr/bin/gpg'
GPG_ARGS = {
'decrypt': ['-d'],
'encrypt': ['--armor', '-es'],
'fingerprint': ['--fingerprint'],
'receive-keys': ['--recv-keys'],
}
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
donnés. Renvoie son entrée standard et sa sortie standard."""
full_command = [GPG]
full_command.extend(GPG_ARGS[command])
if args:
full_command.extend(args)
stderr=sys.stderr
else:
stderr=subprocess.PIPE
full_command.extend(['--debug-level=1'])
#print full_command
proc = subprocess.Popen(full_command,
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
return proc.stdin, proc.stdout
######
## Remote commands
def ssh(command, arg = None):
"""Lance ssh avec les arguments donnés. Renvoie son entrée
standard et sa sortie standard."""
full_command = list(SERVER['server_cmd'])
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
full_command.append(command)
if arg:
full_command.append(arg)
proc = subprocess.Popen(full_command,
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = sys.stderr,
close_fds = True)
return proc.stdin, proc.stdout
def remote_command(command, arg = None, stdin_contents = None):
"""Exécute la commande distante, et retourne la sortie de cette
commande"""
sshin, sshout = ssh(command, arg)
if stdin_contents:
sshin.write(json.dumps(stdin_contents))
sshin.close()
return json.loads(sshout.read())
def all_keys():
"""Récupère les clés du serveur distant"""
return remote_command("listkeys")
def all_roles():
"""Récupère les roles du serveur distant"""
return remote_command("listroles")
def all_files():
"""Récupère les fichiers du serveur distant"""
return remote_command("listfiles")
def get_file(filename):
"""Récupère le contenu du fichier distant"""
return remote_command("getfile", filename)
def put_file(filename, roles, contents):
"""Dépose le fichier sur le serveur distant"""
return remote_command("putfile", filename, {'roles': roles,
'contents': contents})
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: SERVER['user'] in allr[role],allr.keys())
######
## Local commands
def update_keys():
"""Met à jour les clés existantes"""
keys = all_keys()
_, stdout = gpg("receive-keys", [key for _, key in keys.values() if key])
return stdout.read()
def check_keys():
"""Vérifie les clés existantes"""
keys = all_keys()
for mail, key in keys.values():
if key:
_, stdout = gpg("fingerprint", [key])
if str("<%s>" % mail.lower()) not in stdout.read().lower():
break
else:
return True
return False
def encrypt(roles, contents):
"""Chiffre le contenu pour les roles donnés"""
recipients = set()
allroles = all_roles()
allkeys = all_keys()
email_recipients = []
for role in roles:
for recipient in allroles[role]:
recipients.add(recipient)
for recipient in recipients:
email, key = allkeys[recipient]
if key:
email_recipients.append("-r")
email_recipients.append(email)
stdin, stdout = gpg("encrypt", email_recipients)
stdin.write(contents)
stdin.close()
def decrypt(contents):
"""Déchiffre le contenu"""
stdin, stdout = gpg("decrypt")
stdin.write(contents)
stdin.close()
return stdout.read()
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 enc_pwd <> None:
return put_file(name, roles, enc_pwd)
else:
return False
def get_password(name):
"""Récupère le mot de passe donné par name"""
remotefile = get_file(name)
return decrypt(remotefile['contents'])
## Interface
def editor(texte):
""" Lance $EDITOR sur texte"""
f = tempfile.NamedTemporaryFile()
atexit.register(f.close)
f.write(texte)
f.flush()
proc = subprocess.Popen(os.getenv('EDITOR') + ' ' + f.name,shell=True)
os.waitpid(proc.pid,0)
f.seek(0)
ntexte = f.read()
f.close()
return texte <> ntexte and ntexte or None
def show_files():
proc = subprocess.Popen("less",stdin=subprocess.PIPE,shell=True)
out = proc.stdin
out.write("""Liste des fichiers disponibles\n""" )
my_roles = get_my_roles()
for (fname,froles) in all_files().iteritems():
access = set(my_roles).intersection(froles) != set([])
out.write(" %s %s (%s)\n" % ((access and '+' or '-'),fname,", ".join(froles)))
out.write("""--Mes roles: %s\n""" % \
", ".join(my_roles))
out.close()
os.waitpid(proc.pid,0)
def show_roles():
print """Liste des roles disponibles"""
for role in all_roles().keys():
if role.endswith('-w'): continue
print " * " + role
def clipboard(texte):
proc =subprocess.Popen(['xclip','-selection','clipboard'],\
stdin=subprocess.PIPE,stdout=sys.stdout,stderr=sys.stderr)
proc.stdin.write(texte)
proc.stdin.close()
return "[Le mot de passe a été mis dans le presse papier]"
def show_file(fname):
value = get_file(fname)
if value == False:
print "Fichier introuvable"; return
proc = subprocess.Popen("less",stdin=subprocess.PIPE,shell=True)
out = proc.stdin
out.write("Fichier %s:\n\n" % fname)
(sin,sout) = gpg('decrypt')
sin.write(value['contents'])
sin.close()
if CLIPBOARD: # Ça ne va pas plaire à tout le monde
for line in lines:
if line.startswith('pass:'):
out.write(clipboard(line[5:].strip(' \t\r\n')) + '\n')
else:
out.write(line+'\n')
else: # Si pas de presse papier, on fait passer ça dans un less
out.write(sout.read())
out.write("-----\n")
out.write("Visible par: %s\n" % ','.join(value['roles']))
out.close()
os.waitpid(proc.pid,0)
def edit_file(fname):
value = get_file(fname)
nfile = True
print "Fichier introuvable"
if not confirm("Créer fichier ?"):
return
texte = ""
roles = get_my_roles()
# Par défaut les roles d'un fichier sont ceux en écriture de son
# créateur
roles = [ r[:-2] for r in roles if r.endswith('-w') ]
if roles == []:
print "Vous ne possédez aucun rôle en écriture ! Abandon."
return
value = {'roles':roles}
else:
(sin,sout) = gpg('decrypt')
sin.write(value['contents'])
sin.close()
texte = sout.read()
if ntexte == None and not nfile and NROLES == None:
print "Pas de modifications effectuées"
else:
if put_password(fname,value['roles'],ntexte):
print "Modifications enregistrées"
else:
print "Erreur lors de l'enregistrement (avez-vous les droits suffisants ?)"
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
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 my_roles if r.endswith('-w')]
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: SERVER['user'] in roles[r],roles.keys())
my_roles_w = [ r[:-2] for r in my_roles if r.endswith('-w') ]
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)
parser = argparse.ArgumentParser(description="trousseau crans")
parser.add_argument('--server',default='default',
help='Utilisation d\'un serveur alternatif (test, etc)')
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,
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
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:])
VERB = parsed.verbose
CLIPBOARD = parsed.clipboard
FORCED = parsed.force
NROLES = parse_roles(parsed.roles)
SERVER = config.servers[parsed.server]
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()