Skip to content
Snippets Groups Projects
cranspasswords-server.py 6.41 KiB
Newer Older
Daniel STAN's avatar
Daniel STAN committed
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""cranspasswords-server.py: Serveur pour cranspasswords"""

MYDIR = '/root/cranspasswords/'
STORE = MYDIR+'db/'
Daniel STAN's avatar
Daniel STAN committed
import glob
import os
import pwd
import sys
import json
import smtplib
Daniel STAN's avatar
Daniel STAN committed
import datetime
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
Daniel STAN's avatar
Daniel STAN committed

MYUID = pwd.getpwuid(os.getuid())[0]
if MYUID == 'root':
    MYUID = os.environ['SUDO_USER']

CRANSP_MAIL = "root@crans.org"
DEST_MAIL = "root@crans.org"
Daniel STAN's avatar
Daniel STAN committed
KEYS = {
    "aza-vallina": ("Damien.Aza-Vallina@crans.org", None),
    "dandrimont": ("nicolas.dandrimont@crans.org", "66475AAF"),
    "blockelet": ("blockelet@crans.org", "AF087A52"),
    "chambart": ("pierre.chambart@crans.org", "F2530FCE"),
    "dimino": ("jdimino@dptinfo.ens-cachan.fr", "2127F85A"),
    "durand-gasselin": ("adg@crans.org", "8E96ACDA"),
    "glondu": ("Stephane.Glondu@crans.org", "49881AD3"),
    "huber": ("olivier.huber@crans.org", "E0DCF376"),
    "lagorce": ("xavier.lagorce@crans.org", "0BF3708E"),
    "parret-freaud": ("parret-freaud@crans.org", "7D980513"),
    "tvincent": ("vincent.thomas@crans.org", "C5C4ACC0"),
Daniel STAN's avatar
Daniel STAN committed
    "iffrig": ("iffrig@crans.org","5BEC9A2F"),
    "becue": ("becue@crans.org", "194974E2"),
    "dstan": ("daniel.stan@crans.org", "6E1C820B"),
Daniel STAN's avatar
Daniel STAN committed
    "samir": ("samir@crans.org", "41C2B76B"),
    "boilard": ("boilard@crans.org", "C39EB6F4"),
Daniel STAN's avatar
Daniel STAN committed
    "cauderlier": ("cauderlier@crans.org",None),    #Méchant pas beau
root's avatar
root committed
    "maioli": ("maioli@crans.org",None),             #Bis (maybe 9E5026E8)
    "legallic": ("legallic@crans.org", "3784CFC3"),
Daniel STAN's avatar
Daniel STAN committed
    }

Daniel STAN's avatar
Daniel STAN committed
RTC=[
    "iffrig"
    ]
NOUNOUS=RTC+[
    "blockelet",
    "becue",
    "dstan",
    "chambart",
    "dimino",
    "durand-gasselin",
    "glondu",
    "huber",
    "lagorce",
    "parret-freaud",
    "cauderlier",
Daniel STAN's avatar
Daniel STAN committed
    "maioli",
root's avatar
root committed
    "boilard",
    "legallic",
root's avatar
root committed
CA=[]
Daniel STAN's avatar
Daniel STAN committed
ROLES = {
    "ca": CA,
    "ca-w": CA,
Daniel STAN's avatar
Daniel STAN committed
    "nounous": NOUNOUS,
    "nounous-w": NOUNOUS,
Daniel STAN's avatar
Daniel STAN committed
    }

Daniel STAN's avatar
Daniel STAN committed

def validate(roles,mode='r'):
    """Valide que l'appelant appartient bien aux roles précisés
    Si mode mode='w', recherche un rôle en écriture
    """
Daniel STAN's avatar
Daniel STAN committed
    for role in roles:
Daniel STAN's avatar
Daniel STAN committed
        if mode == 'w': role+='-w'
        if ROLES.has_key(role) and MYUID in ROLES[role]:
Daniel STAN's avatar
Daniel STAN committed
            return True
    return False

Daniel STAN's avatar
Daniel STAN committed
def getpath(filename,backup=False):
Daniel STAN's avatar
Daniel STAN committed
    """Récupère le chemin du fichier `filename'"""
Daniel STAN's avatar
Daniel STAN committed
    return os.path.join(STORE, '%s.%s' % (filename,'bak' if backup else 'json'))
Daniel STAN's avatar
Daniel STAN committed

def writefile(filename, contents):
    """Écrit le fichier de manière sécure"""
    os.umask(0077)
    f = open(filename, 'w')
    f.write(contents)
    f.close()

def listroles():
    """Liste des roles existant et de leurs membres"""
    return ROLES

def listkeys():
    """Liste les uid et les clés correspondantes"""
    return KEYS

def listfiles():
    """Liste les fichiers dans l'espace de stockage, et les roles qui peuvent y accéder"""
    os.chdir(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
Daniel STAN's avatar
Daniel STAN committed
    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']
Daniel STAN's avatar
Daniel STAN committed
    except TypeError:
Daniel STAN's avatar
Daniel STAN committed
        old = "[Création du fichier]"
Daniel STAN's avatar
Daniel STAN committed
        pass
    else:
Daniel STAN's avatar
Daniel STAN committed
        if not validate(oldroles,'w'):
Daniel STAN's avatar
Daniel STAN committed
            return False
    
    notification("Modification de %s" % filename,\
    "Le fichier %s a été modifié par %s." %\
Daniel STAN's avatar
Daniel STAN committed
        (filename,MYUID),filename,old)
Daniel STAN's avatar
Daniel STAN committed
    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']
Daniel STAN's avatar
Daniel STAN committed
    except TypeError:
        return True
    else:
Daniel STAN's avatar
Daniel STAN committed
        if validate(roles,'w'):
            notification("Suppression de %s" % filename,\
                "Le fichier %s a été supprimé par %s." %\
Daniel STAN's avatar
Daniel STAN committed
                (filename,MYUID),filename,old)
Daniel STAN's avatar
Daniel STAN committed
            os.remove(getpath(filename))
        else:
            return False
    return True

def notification(subject,corps,fname,old):
Daniel STAN's avatar
Daniel STAN committed
    back = open(getpath(fname,True),'a')
    back.write(json.dumps(old))
    back.write('\n')
    back.write('* %s: %s\n' % (str(datetime.datetime.now()),corps)) 
    back.close()

    # Puis envoi du message
    conn = smtplib.SMTP('localhost')
    frommail = CRANSP_MAIL
    tomail = DEST_MAIL
    msg = MIMEMultipart(_charset="utf-8")
    msg['Subject'] = subject
    # me == the sender's email address
    # family = the list of all recipients' email addresses
    msg['From'] = "cranspasswords <%s>" % CRANSP_MAIL
    msg['To'] = DEST_MAIL
    msg.preamble = "cranspasswords report"
Daniel STAN's avatar
Daniel STAN committed
    info = MIMEText(corps + 
        "\nLa version précédente a été sauvegardée." +
Daniel STAN's avatar
Daniel STAN committed
        #"\nCi-joint l'ancien fichier." +
        "\n\n-- \nCranspasswords.py",_charset="utf-8")
    msg.attach(info)
    #old = MIMEText(old)
    #old.add_header('Content-Disposition', 'attachment', filename=fname) 
Daniel STAN's avatar
Daniel STAN committed
    #msg.attach(str(old))
    conn.sendmail(frommail,tomail,msg.as_string())
    conn.quit()

Daniel STAN's avatar
Daniel STAN committed
if __name__ == "__main__":
    argv = sys.argv[1:]
    if len(argv) not in [1, 2]:
        sys.exit(1)
    command = argv[0]
    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)