server.py 4.92 KB
Newer Older
Daniel Stan's avatar
Daniel Stan committed
1 2 3 4 5 6 7 8 9
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""cranspasswords-server.py: Serveur pour cranspasswords"""

import glob
import os
import pwd
import sys
import json
10
import smtplib
Daniel Stan's avatar
Daniel Stan committed
11
import datetime
12 13
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
Daniel Stan's avatar
Daniel Stan committed
14

Daniel Stan's avatar
Daniel Stan committed
15 16
from serverconfig import CRANSP_MAIL, DEST_MAIL, KEYS, ROLES, STORE

Daniel Stan's avatar
Daniel Stan committed
17 18 19 20
MYUID = pwd.getpwuid(os.getuid())[0]
if MYUID == 'root':
    MYUID = os.environ['SUDO_USER']

21 22 23 24
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
25
    for role in roles:
26 27
        if mode == 'w': role+='-w'
        if ROLES.has_key(role) and MYUID in ROLES[role]:
Daniel Stan's avatar
Daniel Stan committed
28 29 30
            return True
    return False

Daniel Stan's avatar
Daniel Stan committed
31
def getpath(filename,backup=False):
Daniel Stan's avatar
Daniel Stan committed
32
    """Récupère le chemin du fichier `filename'"""
Daniel Stan's avatar
Daniel Stan committed
33
    return os.path.join(STORE, '%s.%s' % (filename,'bak' if backup else 'json'))
Daniel Stan's avatar
Daniel Stan committed
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68

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:
69 70 71 72
        obj = json.loads(open(filepath).read())
        if not validate(obj['roles']):
	        return False
        return obj
Daniel Stan's avatar
Daniel Stan committed
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
    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:
92 93
        old = getfile(filename)
        oldroles = old['roles']
Daniel Stan's avatar
Daniel Stan committed
94
    except TypeError:
Daniel Stan's avatar
Daniel Stan committed
95
        old = "[Création du fichier]"
Daniel Stan's avatar
Daniel Stan committed
96 97
        pass
    else:
98
        if not validate(oldroles,'w'):
Daniel Stan's avatar
Daniel Stan committed
99 100
            return False
    
101 102
    notification("Modification de %s" % filename,\
    "Le fichier %s a été modifié par %s." %\
Daniel Stan's avatar
Daniel Stan committed
103
        (filename,MYUID),filename,old)
104 105


Daniel Stan's avatar
Daniel Stan committed
106 107 108 109 110 111
    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:
112 113
        old = getfile(filename)
        roles = old['roles']
Daniel Stan's avatar
Daniel Stan committed
114 115 116
    except TypeError:
        return True
    else:
117
        if validate(roles,'w'):
118 119
            notification("Suppression de %s" % filename,\
                "Le fichier %s a été supprimé par %s." %\
Daniel Stan's avatar
Daniel Stan committed
120
                (filename,MYUID),filename,old)
Daniel Stan's avatar
Daniel Stan committed
121 122 123 124 125
            os.remove(getpath(filename))
        else:
            return False
    return True

126
def notification(subject,corps,fname,old):
Daniel Stan's avatar
Daniel Stan committed
127 128 129 130 131 132 133
    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
134 135 136 137 138 139 140 141 142 143
    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
144
    info = MIMEText(corps + 
145
        "\nLa version précédente a été sauvegardée." +
Daniel Stan's avatar
Daniel Stan committed
146
        #"\nCi-joint l'ancien fichier." +
147 148 149 150
        "\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
151
    #msg.attach(str(old))
152 153 154
    conn.sendmail(frommail,tomail,msg.as_string())
    conn.quit()

Daniel Stan's avatar
Daniel Stan committed
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
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)