Commit 3589ef41 authored by Vincent Le gallic's avatar Vincent Le gallic

getfile*s*; putfile*s* et utilisation pour --recrypt-files

On peut récupérer/envoyer plusieurs fichiers à la fois.

A priori, le serveur n'est plus rétro-compatible avec les clients non à jour.

Conflicts:
	client.py
	server.py
parent 0f415a0d
...@@ -21,6 +21,7 @@ import re ...@@ -21,6 +21,7 @@ import re
import random import random
import string import string
import datetime import datetime
import gnupg
try: try:
import gnupg #disponible seulement sous wheezy import gnupg #disponible seulement sous wheezy
except ImportError: except ImportError:
...@@ -68,7 +69,7 @@ CLIPBOARD = bool(os.getenv('DISPLAY')) and os.path.exists('/usr/bin/xclip') ...@@ -68,7 +69,7 @@ CLIPBOARD = bool(os.getenv('DISPLAY')) and os.path.exists('/usr/bin/xclip')
#: Mode «ne pas demander confirmation» #: Mode «ne pas demander confirmation»
FORCED = False FORCED = False
#: Droits à définir sur le fichier en édition #: Droits à définir sur le fichier en édition
NROLES = None NEWROLES = None
#: Serveur à interroger (peuplée à l'exécution) #: Serveur à interroger (peuplée à l'exécution)
SERVER = None SERVER = None
...@@ -128,10 +129,11 @@ def remote_command(command, arg = None, stdin_contents = None): ...@@ -128,10 +129,11 @@ def remote_command(command, arg = None, stdin_contents = None):
commande""" commande"""
sshin, sshout = ssh(command, arg) sshin, sshout = ssh(command, arg)
if stdin_contents: if not stdin_contents is None:
sshin.write(json.dumps(stdin_contents)) sshin.write(json.dumps(stdin_contents))
sshin.close() sshin.close()
return json.loads(sshout.read()) raw_out = sshout.read()
return json.loads(raw_out)
@simple_memoize @simple_memoize
def all_keys(): def all_keys():
...@@ -148,14 +150,13 @@ def all_files(): ...@@ -148,14 +150,13 @@ def all_files():
"""Récupère les fichiers du serveur distant""" """Récupère les fichiers du serveur distant"""
return remote_command("listfiles") return remote_command("listfiles")
def get_file(filename): def get_files(filenames):
"""Récupère le contenu du fichier distant""" """Récupère le contenu des fichiers distants"""
return remote_command("getfile", filename) return remote_command("getfiles", stdin_contents=filenames)
def put_file(filename, roles, contents): def put_files(files):
"""Dépose le fichier sur le serveur distant""" """Dépose les fichiers sur le serveur distant"""
return remote_command("putfile", filename, {'roles': roles, return remote_command("putfiles", stdin_contents=files)
'contents' : contents})
def rm_file(filename): def rm_file(filename):
"""Supprime le fichier sur le serveur distant""" """Supprime le fichier sur le serveur distant"""
...@@ -238,8 +239,7 @@ def get_dest_of_roles(roles): ...@@ -238,8 +239,7 @@ def get_dest_of_roles(roles):
for rec in get_recipients_of_roles(roles) if allkeys[rec][1]] for rec in get_recipients_of_roles(roles) if allkeys[rec][1]]
def encrypt(roles, contents): def encrypt(roles, contents):
"""Chiffre le contenu pour les roles donnés""" """Chiffre ``contents`` pour les ``roles`` donnés"""
allkeys = all_keys() allkeys = all_keys()
recipients = get_recipients_of_roles(roles) recipients = get_recipients_of_roles(roles)
...@@ -270,20 +270,20 @@ def put_password(name, roles, contents): ...@@ -270,20 +270,20 @@ def put_password(name, roles, contents):
"""Dépose le mot de passe après l'avoir chiffré pour les """Dépose le mot de passe après l'avoir chiffré pour les
destinataires donnés""" destinataires donnés"""
success, enc_pwd_or_error = encrypt(roles, contents) success, enc_pwd_or_error = encrypt(roles, contents)
if NROLES != None: if NEWROLES != None:
roles = NROLES roles = NEWROLES
if VERB: if VERB:
print(u"Pas de nouveaux rôles".encode("utf-8")) print(u"Pas de nouveaux rôles".encode("utf-8"))
if success: if success:
enc_pwd = enc_pwd_or_error enc_pwd = enc_pwd_or_error
return put_file(name, roles, enc_pwd) return put_files([{'filename' : name, 'roles' : roles, 'contents' : enc_pwd}])[0]
else: else:
error = enc_pwd_or_error error = enc_pwd_or_error
return [False, error] return [False, error]
def get_password(name): def get_password(name):
"""Récupère le mot de passe donné par name""" """Récupère le mot de passe donné par name"""
gotit, remotefile = get_file(name) gotit, remotefile = get_files([name])[0]
if gotit: if gotit:
remotefile = decrypt(remotefile['contents']) remotefile = decrypt(remotefile['contents'])
return [gotit, remotefile] return [gotit, remotefile]
...@@ -367,7 +367,7 @@ def clipboard(texte): ...@@ -367,7 +367,7 @@ def clipboard(texte):
def show_file(fname): def show_file(fname):
"""Affiche le contenu d'un fichier""" """Affiche le contenu d'un fichier"""
gotit, value = get_file(fname) gotit, value = get_files([fname])[0]
if not gotit: if not gotit:
print(value.encode("utf-8")) # value contient le message d'erreur print(value.encode("utf-8")) # value contient le message d'erreur
return return
...@@ -385,7 +385,7 @@ def show_file(fname): ...@@ -385,7 +385,7 @@ def show_file(fname):
line = clipboard(catchPass.group(1)) line = clipboard(catchPass.group(1))
ntexte += line + '\n' ntexte += line + '\n'
showbin = "cat" if hidden else "less" showbin = "cat" if hidden else "less"
proc = subprocess.Popen(showbin, stdin=subprocess.PIPE, shell=True) proc = subprocess.Popen([showbin], stdin=subprocess.PIPE)
out = proc.stdin out = proc.stdin
raw = u"Fichier %s:\n\n%s-----\nVisible par: %s\n" % (fname, ntexte, ','.join(value['roles'])) raw = u"Fichier %s:\n\n%s-----\nVisible par: %s\n" % (fname, ntexte, ','.join(value['roles']))
out.write(raw.encode("utf-8")) out.write(raw.encode("utf-8"))
...@@ -395,7 +395,7 @@ def show_file(fname): ...@@ -395,7 +395,7 @@ def show_file(fname):
def edit_file(fname): def edit_file(fname):
"""Modifie/Crée un fichier""" """Modifie/Crée un fichier"""
gotit, value = get_file(fname) gotit, value = get_files([fname])[0]
nfile = False nfile = False
annotations = u"" annotations = u""
if not gotit and not "pas les droits" in value: if not gotit and not "pas les droits" in value:
...@@ -423,7 +423,8 @@ Enregistrez le fichier vide pour annuler.\n""" ...@@ -423,7 +423,8 @@ Enregistrez le fichier vide pour annuler.\n"""
sin.write(value['contents'].encode("utf-8")) sin.write(value['contents'].encode("utf-8"))
sin.close() sin.close()
texte = sout.read().decode("utf-8") texte = sout.read().decode("utf-8")
value['roles'] = NROLES or value['roles'] # On récupère les nouveaux roles si ils ont été précisés, sinon on garde les mêmes
value['roles'] = NEWROLES or value['roles']
annotations += u"""Ce fichier sera chiffré pour les rôles suivants :\n%s\n annotations += u"""Ce fichier sera chiffré pour les rôles suivants :\n%s\n
C'est-à-dire pour les utilisateurs suivants :\n%s""" % ( C'est-à-dire pour les utilisateurs suivants :\n%s""" % (
...@@ -433,7 +434,7 @@ C'est-à-dire pour les utilisateurs suivants :\n%s""" % ( ...@@ -433,7 +434,7 @@ C'est-à-dire pour les utilisateurs suivants :\n%s""" % (
ntexte = editor(texte, annotations) ntexte = editor(texte, annotations)
if ((not nfile and ntexte in [u'', texte] and NROLES == None) or # Fichier existant vidé ou inchangé if ((not nfile and ntexte in [u'', texte] and NEWROLES == None) or # Fichier existant vidé ou inchangé
(nfile and ntexte == u'')): # Nouveau fichier créé vide (nfile and ntexte == u'')): # Nouveau fichier créé vide
print(u"Pas de modification effectuée".encode("utf-8")) print(u"Pas de modification effectuée".encode("utf-8"))
else: else:
...@@ -470,26 +471,50 @@ def my_update_keys(): ...@@ -470,26 +471,50 @@ def my_update_keys():
def recrypt_files(): def recrypt_files():
"""Rechiffre les fichiers""" """Rechiffre les fichiers"""
roles = None # Ici, la signification de NEWROLES est : on ne veut rechiffrer que les fichiers qui ont au moins un de ces roles
rechiffre_roles = NEWROLES
my_roles = get_my_roles() my_roles = get_my_roles()
if roles == None: my_roles_w = [r for r in my_roles if r.endswith("-w")]
# On ne conserve que les rôles qui finissent par -w if rechiffre_roles == None:
roles = [ r[:-2] for r in my_roles if r.endswith('-w')] # Sans précisions, on prend tous les roles qu'on peut
if type(roles) != list: rechiffre_roles = my_roles
roles = [roles] # On ne conserve que les rôles en écriture
rechiffre_roles = [ r[:-2] for r in rechiffre_roles if r.endswith('-w')]
for (fname, froles) in all_files().iteritems():
if set(roles).intersection(froles) == set([]): # La liste des fichiers
continue allfiles = all_files()
print((u"Rechiffrement de %s" % fname).encode("utf-8")) # On ne demande que les fichiers dans lesquels on peut écrire
_, password = get_password(fname) # et qui ont au moins un role dans ``roles``
put_password(fname, froles, password) askfiles = [filename for (filename, fileroles) in allfiles.iteritems()
if set(fileroles).intersection(roles) != set()
and set(fileroles).intersection(my_roles_w) != set()]
files = get_files(askfiles)
# Au cas où on aurait échoué à récupérer ne serait-ce qu'un de ces fichiers,
# on affiche le message d'erreur correspondant et on abandonne.
for (success, message) in files:
if not success:
print(message.encode("utf-8"))
return
# On rechiffre
to_put = [{'filename' : f['filename'],
'roles' : f['roles'],
'contents' : encrypt(f['roles'], decrypt(f['contents']))}
for f in files]
if to_put:
print((u"Rechiffrement de %s" % (", ".join([f['filename'] for f in to_put]))).encode("utf-8"))
results = put_files(to_put)
# On affiche les messages de retour
for i in range(len(results)):
print (u"%s : %s" % (to_put[i]['filename'], results[i][1]))
else:
print(u"Aucun fichier n'a besoin d'être rechiffré".encode("utf-8"))
def parse_roles(strroles): def parse_roles(strroles):
"""Interprête une liste de rôles fournie par l'utilisateur""" """Interprête une liste de rôles fournie par l'utilisateur.
Renvoie ``False`` si au moins un de ces rôles pose problème."""
if strroles == None: return None if strroles == None: return None
roles = all_roles() roles = all_roles()
my_roles = filter(lambda r: SERVER['user'] in roles[r],roles.keys()) 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') ] my_roles_w = [ r[:-2] for r in my_roles if r.endswith('-w') ]
ret = set() ret = set()
writable = False writable = False
...@@ -555,10 +580,14 @@ if __name__ == "__main__": ...@@ -555,10 +580,14 @@ if __name__ == "__main__":
help="Lister les serveurs") help="Lister les serveurs")
action_grp.add_argument('--recrypt-files', action='store_const', dest='action', action_grp.add_argument('--recrypt-files', action='store_const', dest='action',
default=show_file, const=recrypt_files, default=show_file, const=recrypt_files,
help="Rechiffrer les mots de passe") help="Rechiffrer les mots de passe. (Avec les mêmes rôles qu'avant, sert à rajouter un lecteur)")
parser.add_argument('--roles', nargs='?', default=None, parser.add_argument('--roles', nargs='?', default=None,
help="liste des roles à affecter au fichier") help="""Liste de roles (séparés par des virgules).
Avec --edit, le fichier sera chiffré pour exactement ces roles
(par défaut, tous vos rôles en écriture seront utilisés).
Avec --recrypt-files, tous les fichiers ayant au moins un de ces roles (et pour lesquels vous avez le droit d'écriture) seront rechiffrés
(par défaut, tous les fichiers pour lesquels vous avez les droits en écriture sont rechiffrés).""")
parser.add_argument('fname', nargs='?', default=None, parser.add_argument('fname', nargs='?', default=None,
help="Nom du fichier à afficher") help="Nom du fichier à afficher")
...@@ -569,9 +598,9 @@ if __name__ == "__main__": ...@@ -569,9 +598,9 @@ if __name__ == "__main__":
if parsed.clipboard != None: if parsed.clipboard != None:
CLIPBOARD = parsed.clipboard CLIPBOARD = parsed.clipboard
FORCED = parsed.force FORCED = parsed.force
NROLES = parse_roles(parsed.roles) NEWROLES = parse_roles(parsed.roles)
if NROLES != False: if NEWROLES != False:
if parsed.action.func_code.co_argcount == 0: if parsed.action.func_code.co_argcount == 0:
parsed.action() parsed.action()
elif parsed.fname == None: elif parsed.fname == None:
......
...@@ -73,22 +73,20 @@ def getfile(filename): ...@@ -73,22 +73,20 @@ def getfile(filename):
obj = json.loads(open(filepath).read()) obj = json.loads(open(filepath).read())
if not validate(obj['roles']): if not validate(obj['roles']):
return [False, u"Vous n'avez pas les droits de lecture sur le fichier %s." % filename] return [False, u"Vous n'avez pas les droits de lecture sur le fichier %s." % filename]
obj["filename"] = filename
return [True, obj] return [True, obj]
except IOError: except IOError:
return [False, u"Le fichier %s n'existe pas." % filename] return [False, u"Le fichier %s n'existe pas." % filename]
def putfile(filename): def getfiles():
"""Écrit le fichier ``filename`` avec les données reçues sur stdin.""" """Récupère plusieurs fichiers, lit la liste des filenames demandés sur stdin"""
filepath = getpath(filename)
stdin = sys.stdin.read() stdin = sys.stdin.read()
parsed_stdin = json.loads(stdin) filenames = json.loads(stdin)
try: return [getfile(f) for f in filenames]
roles = parsed_stdin['roles']
contents = parsed_stdin['contents'] def _putfile(filename, roles, contents):
except KeyError: """Écrit ``contents`` avec les roles ``roles`` dans le fichier ``filename``"""
return [False, u"Entrée invalide"]
gotit, old = getfile(filename) gotit, old = getfile(filename)
if not gotit: if not gotit:
old = u"[Création du fichier]" old = u"[Création du fichier]"
...@@ -102,9 +100,38 @@ def putfile(filename): ...@@ -102,9 +100,38 @@ def putfile(filename):
backup(corps, filename, old) backup(corps, filename, old)
notification(u"Modification de %s" % filename, corps, filename, old) notification(u"Modification de %s" % filename, corps, filename, old)
filepath = getpath(filename)
writefile(filepath, json.dumps({'roles': roles, 'contents': contents})) writefile(filepath, json.dumps({'roles': roles, 'contents': contents}))
return [True, u"Modification effectuée."] return [True, u"Modification effectuée."]
def putfile(filename):
"""Écrit le fichier ``filename`` avec les données reçues sur stdin."""
stdin = sys.stdin.read()
parsed_stdin = json.loads(stdin)
try:
roles = parsed_stdin['roles']
contents = parsed_stdin['contents']
except KeyError:
return [False, u"Entrée invalide"]
return _putfile(filename, roles, contents)
def putfiles():
"""Écrit plusieurs fichiers. Lit les filenames sur l'entrée standard avec le reste."""
stdin = sys.stdin.read()
parsed_stdin = json.loads(stdin)
results = []
for fichier in parsed_stdin:
try:
filename = fichier['filename']
roles = fichier['roles']
contents = fichier['contents']
except KeyError:
results.append([False, u"Entrée invalide"])
else:
results.append(_putfile(filename, roles, contents))
return results
def rmfile(filename): def rmfile(filename):
"""Supprime le fichier filename après avoir vérifié les droits sur le fichier""" """Supprime le fichier filename après avoir vérifié les droits sur le fichier"""
gotit, old = getfile(filename) gotit, old = getfile(filename)
...@@ -120,6 +147,7 @@ def rmfile(filename): ...@@ -120,6 +147,7 @@ def rmfile(filename):
return u"Vous n'avez pas les droits d'écriture sur le fichier %s." % filename return u"Vous n'avez pas les droits d'écriture sur le fichier %s." % filename
return u"Suppression effectuée" return u"Suppression effectuée"
def backup(corps, fname, old): def backup(corps, fname, old):
"""Backupe l'ancienne version du fichier""" """Backupe l'ancienne version du fichier"""
back = open(getpath(fname, backup=True), 'a') back = open(getpath(fname, backup=True), 'a')
...@@ -168,8 +196,13 @@ if __name__ == "__main__": ...@@ -168,8 +196,13 @@ if __name__ == "__main__":
answer = listkeys() answer = listkeys()
elif command == "listfiles": elif command == "listfiles":
answer = listfiles() answer = listfiles()
elif command == "getfiles":
answer = getfiles()
elif command == "putfiles":
answer = putfiles()
else: else:
if not filename: if not filename:
print("filename nécessaire pour cette opération", file=sys.stderr)
sys.exit(1) sys.exit(1)
if command == "getfile": if command == "getfile":
answer = getfile(filename) answer = getfile(filename)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment