cranspasswords-server.py 6.41 KB
Newer Older
Daniel STAN's avatar
init  
Daniel STAN committed
1
2
3
4
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""cranspasswords-server.py: Serveur pour cranspasswords"""

root's avatar
root committed
5
6
MYDIR = '/root/cranspasswords/'
STORE = MYDIR+'db/'
Daniel STAN's avatar
Daniel STAN committed
7

Daniel STAN's avatar
init  
Daniel STAN committed
8
9
10
11
12
import glob
import os
import pwd
import sys
import json
13
import smtplib
Daniel STAN's avatar
Daniel STAN committed
14
import datetime
15
16
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
Daniel STAN's avatar
init  
Daniel STAN committed
17
18
19
20
21

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

22
CRANSP_MAIL = "root@crans.org"
root's avatar
root committed
23
DEST_MAIL = "root@crans.org"
24

Daniel STAN's avatar
init  
Daniel STAN committed
25
26
27
28
29
30
31
32
33
34
35
36
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
37
38
39
    "iffrig": ("iffrig@crans.org","5BEC9A2F"),
    "becue": ("becue@crans.org", "194974E2"),
    "dstan": ("daniel.stan@crans.org", "6E1C820B"),
Daniel STAN's avatar
Daniel STAN committed
40
    "samir": ("samir@crans.org", "41C2B76B"),
41
    "boilard": ("boilard@crans.org", "C39EB6F4"),
Daniel STAN's avatar
Daniel STAN committed
42
    "cauderlier": ("cauderlier@crans.org",None),    #Méchant pas beau
root's avatar
root committed
43
44
    "maioli": ("maioli@crans.org",None),             #Bis (maybe 9E5026E8)
    "legallic": ("legallic@crans.org", "3784CFC3"),
Daniel STAN's avatar
init  
Daniel STAN committed
45
46
    }

Daniel STAN's avatar
Daniel STAN committed
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
RTC=[
    "iffrig"
    ]
NOUNOUS=RTC+[
    "blockelet",
    "becue",
    "dstan",
    "chambart",
    "dimino",
    "durand-gasselin",
    "glondu",
    "huber",
    "lagorce",
    "parret-freaud",
    "cauderlier",
Daniel STAN's avatar
Daniel STAN committed
62
    "maioli",
63
    "samir",
root's avatar
root committed
64
65
    "boilard",
    "legallic",
Daniel STAN's avatar
Daniel STAN committed
66
67
    ]

root's avatar
root committed
68
CA=[]
69

Daniel STAN's avatar
init  
Daniel STAN committed
70
ROLES = {
71
72
    "ca": CA,
    "ca-w": CA,
Daniel STAN's avatar
Daniel STAN committed
73
    "nounous": NOUNOUS,
74
    "nounous-w": NOUNOUS,
Daniel STAN's avatar
init  
Daniel STAN committed
75
76
    }

Daniel STAN's avatar
Daniel STAN committed
77
78
79
80
81

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
init  
Daniel STAN committed
82
    for role in roles:
Daniel STAN's avatar
Daniel STAN committed
83
84
        if mode == 'w': role+='-w'
        if ROLES.has_key(role) and MYUID in ROLES[role]:
Daniel STAN's avatar
init  
Daniel STAN committed
85
86
87
            return True
    return False

Daniel STAN's avatar
Daniel STAN committed
88
def getpath(filename,backup=False):
Daniel STAN's avatar
init  
Daniel STAN committed
89
    """Récupère le chemin du fichier `filename'"""
Daniel STAN's avatar
Daniel STAN committed
90
    return os.path.join(STORE, '%s.%s' % (filename,'bak' if backup else 'json'))
Daniel STAN's avatar
init  
Daniel STAN committed
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125

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:
root's avatar
root committed
126
127
128
129
        obj = json.loads(open(filepath).read())
        if not validate(obj['roles']):
	        return False
        return obj
Daniel STAN's avatar
init  
Daniel STAN committed
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
    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:
149
150
        old = getfile(filename)
        oldroles = old['roles']
Daniel STAN's avatar
init  
Daniel STAN committed
151
    except TypeError:
Daniel STAN's avatar
Daniel STAN committed
152
        old = "[Création du fichier]"
Daniel STAN's avatar
init  
Daniel STAN committed
153
154
        pass
    else:
Daniel STAN's avatar
Daniel STAN committed
155
        if not validate(oldroles,'w'):
Daniel STAN's avatar
init  
Daniel STAN committed
156
157
            return False
    
158
159
    notification("Modification de %s" % filename,\
    "Le fichier %s a été modifié par %s." %\
Daniel STAN's avatar
Daniel STAN committed
160
        (filename,MYUID),filename,old)
161
162


Daniel STAN's avatar
init  
Daniel STAN committed
163
164
165
166
167
168
    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:
169
170
        old = getfile(filename)
        roles = old['roles']
Daniel STAN's avatar
init  
Daniel STAN committed
171
172
173
    except TypeError:
        return True
    else:
Daniel STAN's avatar
Daniel STAN committed
174
        if validate(roles,'w'):
175
176
            notification("Suppression de %s" % filename,\
                "Le fichier %s a été supprimé par %s." %\
Daniel STAN's avatar
Daniel STAN committed
177
                (filename,MYUID),filename,old)
Daniel STAN's avatar
init  
Daniel STAN committed
178
179
180
181
182
            os.remove(getpath(filename))
        else:
            return False
    return True

183
def notification(subject,corps,fname,old):
Daniel STAN's avatar
Daniel STAN committed
184
185
186
187
188
189
190
    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
191
192
193
194
195
196
197
198
199
200
    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
201
    info = MIMEText(corps + 
202
        "\nLa version précédente a été sauvegardée." +
Daniel STAN's avatar
Daniel STAN committed
203
        #"\nCi-joint l'ancien fichier." +
204
205
206
207
        "\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
208
    #msg.attach(str(old))
209
210
211
    conn.sendmail(frommail,tomail,msg.as_string())
    conn.quit()

Daniel STAN's avatar
init  
Daniel STAN committed
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
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)