#!/usr/bin/env python # -*- encoding: utf-8 -*- """Serveur pour cranspasswords""" from __future__ import print_function import glob import os import pwd import sys import json import smtplib import datetime from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart # Même problème que pour le client, il faut bootstraper le nom de la commande # Pour accéder à la config cmd_name = os.path.split(sys.argv[0])[1].replace("-server", "") sys.path.append("/etc/%s/" % (cmd_name,)) import serverconfig MYUID = pwd.getpwuid(os.getuid())[0] if MYUID == 'root': MYUID = os.environ['SUDO_USER'] def validate(roles, mode='r'): """Vérifie que l'appelant appartient bien aux roles précisés Si mode mode='w', recherche un rôle en écriture """ for role in roles: if mode == 'w': role += '-w' if serverconfig.ROLES.has_key(role) and MYUID in serverconfig.ROLES[role]: return True return False def getpath(filename, backup=False): """Récupère le chemin du fichier ``filename``""" return os.path.join(serverconfig.STORE, '%s.%s' % (filename, 'bak' if backup else 'json')) def writefile(filename, contents): """Écrit le fichier avec les bons droits UNIX""" os.umask(0077) f = open(filename, 'w') f.write(contents.encode("utf-8")) f.close() def listroles(): """Liste des roles existant et de leurs membres""" return serverconfig.ROLES def listkeys(): """Liste les usernames et les (mail, fingerprint) correspondants""" return serverconfig.KEYS def listfiles(): """Liste les fichiers dans l'espace de stockage, et les roles qui peuvent y accéder""" os.chdir(serverconfig.STORE) filenames = glob.glob('*.json') files = {} for filename in filenames: file_dict = json.loads(open(filename).read()) files[filename[:-5]] = file_dict["roles"] return files def getfile(filename): """Récupère le fichier ``filename``""" filepath = getpath(filename) try: obj = json.loads(open(filepath).read()) if not validate(obj['roles']): return False return obj except IOError: return False def putfile(filename): """Écrit le fichier ``filename`` avec les données reçues sur stdin.""" filepath = getpath(filename) stdin = sys.stdin.read() parsed_stdin = json.loads(stdin) try: roles = parsed_stdin['roles'] contents = parsed_stdin['contents'] except KeyError: return False try: old = getfile(filename) oldroles = old['roles'] except TypeError: old = u"[Création du fichier]" pass else: if not validate(oldroles,'w'): return False corps = u"Le fichier %s a été modifié par %s." % (filename, MYUID) backup(corps, filename, old) notification(u"Modification de %s" % filename, corps, filename, old) writefile(filepath, json.dumps({'roles': roles, 'contents': contents})) return True def rmfile(filename): """Supprime le fichier filename après avoir vérifié les droits sur le fichier""" try: old = getfile(filename) roles = old['roles'] except TypeError: return True else: if validate(roles,'w'): corps = u"Le fichier %s a été supprimé par %s." % (filename, MYUID) backup(corps, filename, old) notification(u"Suppression de %s" % filename, corps, filename, old) os.remove(getpath(filename)) else: return False return True def backup(corps, fname, old): """Backupe l'ancienne version du fichier""" back = open(getpath(fname, backup=True), 'a') back.write(json.dumps(old)) back.write('\n') back.write((u'* %s: %s\n' % (str(datetime.datetime.now()), corps)).encode("utf-8")) back.close() def notification(subject, corps, fname, old): """Envoie par mail une notification de changement de fichier""" conn = smtplib.SMTP('localhost') frommail = serverconfig.CRANSP_MAIL tomail = serverconfig.DEST_MAIL msg = MIMEMultipart(_charset="utf-8") msg['Subject'] = subject msg['X-Mailer'] = serverconfig.cmd_name.decode() msg['From'] = serverconfig.CRANSP_MAIL msg['To'] = serverconfig.DEST_MAIL msg.preamble = u"%s report" % (serverconfig.cmd_name.decode(),) info = MIMEText(corps + u"\nLa version précédente a été sauvegardée." + u"\n\n-- \nCranspasswords.py", _charset="utf-8") msg.attach(info) conn.sendmail(frommail, tomail, msg.as_string()) conn.quit() WRITE_COMMANDS = ["putfile", "rmfile"] if __name__ == "__main__": argv = sys.argv[1:] if len(argv) not in [1, 2]: sys.exit(1) command = argv[0] if serverconfig.READONLY and command in WRITE_COMMANDS: raise IOError("Ce serveur est read-only.") filename = None try: filename = argv[1] except IndexError: pass if command == "listroles": print(json.dumps(listroles())) elif command == "listkeys": print(json.dumps(listkeys())) elif command == "listfiles": print(json.dumps(listfiles())) else: if not filename: sys.exit(1) if command == "getfile": print(json.dumps(getfile(filename))) elif command == "putfile": print(json.dumps(putfile(filename))) elif command == "rmfile": print(json.dumps(rmfile(filename))) else: sys.exit(1)