Projet

Général

Profil

login.py

/usr/lib/python2.7/dist-packages/scribe/login.py - Joël Cuissinat, 07/06/2017 17:15

Télécharger (15,6 ko)

 
1
# -*- coding: utf-8 -*-
2
###########################################################################
3
# EOLE - 2011
4
# Copyright Pole de Competence Eole  (Ministere Education - Academie Dijon)
5
# Licence CeCill  cf /root/LicenceEole.txt
6
# eole@ac-dijon.fr
7
#
8
# modifié par Cadoles (http://www.cadoles.com/)
9
# contact@cadoles.com
10
#
11
###########################################################################
12

    
13
import time, sys
14
import traceback
15
from shutil import rmtree
16
from os import system, makedirs, symlink, chown, fchmod
17
from os.path import join, islink, isfile, isdir, getmtime, exists
18
from datetime import datetime
19
import ldap
20
import MySQLdb
21

    
22
from scribe.ldapconf import ROOT_DN, ldap_server, ldap_passwd, SUFFIX, \
23
        HOME_PATH, USER_FILTER, GROUP_FILTER, SHARE_FILTER
24

    
25
sys.path.append('/usr/share/eole/controlevnc/')
26
from config import mysql_host, mysql_password
27

    
28
PATH_SCRIPTS = '/home/netlogon/scripts'
29
SCRIPTS_EXT = '.txt'
30

    
31
class Ldap():
32
    def __init__(self):
33
        self.ldap_conn = ldap.open(ldap_server)
34
        self.ldap_conn.simple_bind_s(ROOT_DN, ldap_passwd)
35

    
36
    def ldap_search(self, ldap_filter, attrib=None, one=False):
37
        result = self.ldap_conn.search_s(SUFFIX, ldap.SCOPE_SUBTREE,
38
                                    ldap_filter, attrib)
39
        if len(result) > 0 and len(result[0]) == 2:
40
            if one:
41
                return result[0][1]
42
            else:
43
                return result
44
        else:
45
            return {}
46
    def get_user_attributs(self, user):
47
        return self.ldap_search("(&%s(uid=%s))" % (USER_FILTER, user),
48
                ['objectClass', 'homeDirectory', 'sambaHomeDrive', 'uidNumber',
49
                'gidNumber', 'displayName', 'sambaSID'], True)
50

    
51
    def get_groups(self, user):
52
        return self.ldap_search("(&%s(memberUid=%s))" % (GROUP_FILTER, user),
53
                attrib=['cn', 'gidNumber'])
54

    
55
    def get_group_name(self, gidnumber):
56
        return self.ldap_search("(&%s(gidNumber=%s))" % (GROUP_FILTER,
57
                gidnumber), ['displayName'], True)['displayName'][0]
58

    
59
    def get_shares_filtred(self, group_filter):
60
        return self.ldap_search('(&%s(|%s))' % (SHARE_FILTER, group_filter),
61
                    ['sambaShareName', 'sambaFilePath', 'sambaShareURI',
62
                    'sambaShareDrive'])
63

    
64
    def __del__(self):
65
        self.ldap_conn.unbind()
66
        del(self)
67

    
68
def logon(user, ostype, machine, adresse_ip, pid):
69

    
70
    ldap_conn = Ldap()
71
    user_attrib = ldap_conn.get_user_attributs(user)
72
    userclass = get_userclass_name(user_attrib.get('objectClass', None))
73

    
74

    
75
    if not userclass:
76
        raise Exception("utilisateur %s inconnu" % user)
77

    
78
    if userclass not in ['administratif', 'eleve', 'enseignant']:
79
        raise Exception("l'utilisateur %s n'est pas autorisé à se connecter" % user)
80

    
81
    # si le fichier existe déjà et qu'il a moins de 1min on loggue juste la
82
    # connexion
83
    netlogon = '/home/netlogon/%s%s.txt' % (user, ostype)
84
    if isfile(netlogon):
85
        diff = time.time() - getmtime(netlogon)
86
    else:
87
        diff = 600
88
    prim_group = None
89
    gidnumber = user_attrib['gidNumber'][0].strip()
90
    display_name = user_attrib['displayName'][0].strip()
91
    sid = user_attrib['sambaSID'][0].strip()
92
    groups = []
93
    if diff > 2:
94
        """ construction des scripts de connexion windows
95
        le fichier est du format .bat pour Win95 et .txt pour le reste
96
        """
97
        homedir = user_attrib['homeDirectory'][0].strip()
98
        homedrive = user_attrib['sambaHomeDrive'][0].strip()
99
        uid = int(user_attrib['uidNumber'][0].strip())
100

    
101
        group_filter = ''
102
        for dn, grp in ldap_conn.get_groups(user):
103
            name = grp['cn'][0]
104
            if grp['gidNumber'][0] == gidnumber:
105
                prim_group = name
106
            groups.append(name)
107
            group_filter += '(sambaShareGroup=%s)' % name
108

    
109
        shares = []
110
        for share in ldap_conn.get_shares_filtred(group_filter):
111
            if share[1].has_key('sambaShareDrive'):
112
                drive = share[1]['sambaShareDrive'][0]
113
            else:
114
                drive = ''
115
            shares.append({'name': share[1]['sambaShareName'][0],
116
                            'path': share[1]['sambaFilePath'][0],
117
                            'uri': share[1]['sambaShareURI'][0],
118
                            'drive': drive})
119

    
120
        # génération du fichier client
121
        gen_fich(user, groups, shares, machine, ostype, homedrive, userclass,
122
                netlogon)
123
        # mise à jour de /home/u/user/.ftp/
124
        gen_ftpdir(uid, homedir, shares)
125
        # mise à jour de /home/u/user/groupes/
126
        gen_groupedir(uid, homedir, shares)
127
        # répertoire devoirs
128
        gen_devdir(user, uid, homedir, userclass)
129
        # met à jour la base de donnée
130
        log_connexion_db(user, sid, display_name, prim_group, groups, machine,
131
                ostype, adresse_ip)
132

    
133
    if not prim_group:
134
        prim_group = ldap_conn.get_group_name(gidnumber)
135

    
136
    del(ldap_conn)
137
    # enregistrement de la connexion
138
    # après puisque si l'utilisateur n'est pas censé se connecter
139
    # le script n'arrive pas jusqu'à là
140
    log_connexion(user, prim_group, machine, ostype, adresse_ip, pid)
141

    
142
def get_userclass_name(objectClass):
143
    """
144
        Renvoie le nom du module gérant les objets de classe : objectClass
145
    """
146
    if objectClass == None:
147
        return None
148
    users_objectClass = {
149
            'Eleves': 'eleve',
150
            'administrateur': 'enseignant',
151
            'responsable': 'responsable',
152
            'administratif': 'administratif',
153
            'autre': 'autre',
154
            }
155
    for objectclass, module in users_objectClass.items():
156
        if objectclass in objectClass:
157
            return module
158
    return None
159

    
160
def gen_devdir(login, uid, homedir, userclass):
161
    """
162
        Partie commune pour _gen_devoirdir
163
    """
164
    if userclass not in ['eleve', 'enseignant']:
165
        return
166
    perso = join(homedir, 'perso')
167
    # le partage "devoirs" /home/l/login/devoirs
168
    dev_part = join(homedir, 'devoirs')
169
    # le dossier U:\devoirs /home/l/login/perso/devoirs
170
    dev_perso = join(perso, 'devoirs')
171
    cmd = ""
172
    for rep in [dev_part, dev_perso]:
173
        if not isdir(rep):
174
            makedirs(rep, 0750)
175
            cmd += '/bin/chown -PR %s %s;' % (login, rep)
176
            cmd += '/usr/bin/setfacl -PRm u:%s:rwx %s;'%(uid, rep)
177
            cmd += '/usr/bin/setfacl -dPRm u:%s:rwx %s;'%(uid, rep)
178

    
179
    if userclass == 'enseignant':
180
        # dossier contenant les devoirs et les données à distribuer du prof
181
        # U:\devoirs\.distribues ou \\scribe\devoirs\.distribues
182
        dev_dist_dir = join(HOME_PATH, 'workgroups/devoirs', login)
183
        if not isdir(dev_dist_dir):
184
            makedirs(dev_dist_dir, 0755)
185
            cmd += '/usr/bin/setfacl -PRbk %s;' % dev_dist_dir
186
            cmd += '/usr/bin/setfacl -PRm u:%s:rwx %s;' % (uid, dev_dist_dir)
187
            cmd += '/usr/bin/setfacl -dPRm u:%s:rwx %s;' % (uid, dev_dist_dir)
188
        for rep in [dev_part, dev_perso]:
189
            link = join(rep, '.distribues') # le cacher avec un "."
190
            if not islink(link):
191
                symlink(dev_dist_dir, link)
192
    if cmd != '':
193
        system(cmd)
194

    
195
def gen_ftpdir(uid, homedir, shares):
196
    """
197
        Gestion du répertoire ".ftp"
198
    """
199
    ftpdir = join(homedir, '.ftp')
200
    ftpperso = join(ftpdir, 'perso')
201
    homedir = join(HOME_PATH, homedir[6:])
202
    homeperso = join(homedir, 'perso')
203
    def create_ftpsymlink():
204
        if not islink(ftpperso):
205
            symlink(homeperso, ftpperso)
206
        for share in shares:
207
            if share['name'] not in ['icones$', 'groupes']:
208
                if HOME_PATH != '/home':
209
                    share['path'] = share['path'].replace('/home', HOME_PATH)
210
                if not exists(join(ftpdir, share['name'])):
211
                    symlink(share['path'], join(ftpdir, share['name']))
212
    if not isdir(ftpdir):
213
        makedirs(ftpdir, 0500)
214
        chown(ftpdir, uid, -1)
215
    elif not islink(ftpperso) and exists(ftpperso):
216
        rmtree(ftpperso)
217
    create_ftpsymlink()
218

    
219
def gen_groupedir(uid, homedir, shares):
220
    """
221
        Gestion du répertoire "groupes"
222
    """
223
    groupedir = join(homedir, 'groupes')
224
    if isdir(groupedir):
225
        rmtree(groupedir)
226
    makedirs(groupedir, 0500)
227
    chown(groupedir, uid, -1)
228
    #les ACLs ne sont pas mise
229
    #system('setfacl -bk %s' % groupedir)
230
    for share in shares:
231
        # sinon partage avec lettre de lecteur
232
        if share['drive'] == "":
233
            # lien dans le répertoire "groupes"
234
            symlink(share['path'], join(groupedir, share['name']))
235

    
236
def log_connexion_db(user, sid, display_name, primgrp, groups, netbios,
237
        ostype, ip):
238
    db = MySQLdb.connect(host=mysql_host, user='controlevnc',
239
            passwd=mysql_password, db='controlevnc')
240
    try:
241
        c = db.cursor()
242
        # contrôle et nettoyage
243
        c.execute("SELECT ip FROM log WHERE ip=%s", ip)
244
        rows = c.fetchall()
245
        if len(rows) > 1: # plusieurs fois la même IP dans la base
246
            # ça ne devrait pas arriver => RàZ
247
            c.execute("DELETE FROM `log` WHERE ip=%s", ip)
248
        c.execute("SELECT ip FROM log WHERE netbios=%s", netbios)
249
        rows = c.fetchall()
250
        if len(rows) > 1: # plusieurs fois le même NETBIOS dans la base
251
            # ça ne devrait pas arriver => RàZ
252
            c.execute("DELETE FROM `log` WHERE netbios=%s", netbios)
253
        # log db
254
        c.execute("SELECT ip FROM log WHERE ip=%s", ip)
255
        if c.fetchone(): # l'IP existe dans la BDD
256
            c.execute("SELECT ip FROM log WHERE netbios=%s", netbios)
257
            row = c.fetchone() # row[0] = IP correspondant à <netbios>
258
            if row and row[0] != ip: # <netbios> mauvaise IP => effacement
259
                c.execute("DELETE FROM `log` WHERE netbios=%s AND ip=%s", (netbios, row[0]))
260
            # maj de la ligne contenant la bonne IP
261
            c.execute("UPDATE log SET netbios=%s, user=%s, sid=%s, display_name=%s, prim_group=%s, os=%s WHERE ip=%s", (netbios, user, sid, display_name, primgrp, ostype, ip))
262
        else: # l'IP n'existe pas dans la BDD
263
            c.execute("SELECT ip FROM log WHERE netbios=%s", netbios)
264
            if c.fetchone(): # <netbios> mauvaise IP => mise à jour
265
                c.execute("UPDATE log SET ip=%s, user=%s, sid=%s, display_name=%s, prim_group=%s, os=%s WHERE netbios=%s", (ip, user, sid, display_name, primgrp, ostype, netbios))
266
            else: # <netbios> n'existe pas => insertion
267
                c.execute("INSERT INTO log (netbios, ip, user, sid, display_name, prim_group, os) VALUES (%s, %s, %s, %s, %s, %s, %s)", (netbios, ip, user, sid, display_name, primgrp, ostype))
268
        c.execute("DELETE FROM `group` WHERE ip=%s", ip)
269
        # group db
270
        for group in groups:
271
            c.execute("INSERT INTO `group` (ip, groupname) VALUES (%s, %s)",
272
                    (ip, group))
273
        db.commit()
274
    except Exception, e:
275
        db.rollback()
276
        raise Exception('erreur de la base de donnée : %s %s' % (str(e), traceback.format_exc()))
277
    finally:
278
        db.close()
279

    
280
def log_connexion(user, primgrp, machine, ostype, adresse_ip, pid):
281
    """
282
    Enregistrement de la connexion
283
    """
284
    try:
285
        sdate = datetime.now().strftime("%a %d %b %Y %H:%M").capitalize()
286
        cmd = 'echo CONNECTION %s %s %s %s %s %s %s' % (sdate, user,
287
                primgrp, machine, ostype, adresse_ip, pid)
288
        system('%s >> /var/log/samba/%s.log' % (cmd, machine))
289
        system('%s >> /var/log/samba/connexions.log' % (cmd))
290
    except:
291
        pass
292

    
293
#############################################################
294
## Génération des fichiers de logon
295
#############################################################
296

    
297
def gen_fich(user, groups, shares, machine, ostype, homedrive, userclass,
298
        netlogon):
299
    """
300
    Génération du fichier lu par le client
301
    """
302
    script = ""
303
    # gestion des groupes et des partages
304
    for share in shares:
305
        # sinon créé un lien symbolique
306
        if share['drive'] != "":
307
            # partage avec lettre de lecteur
308
            script += gen_lecteur_bloc(share['drive'], share['uri'])
309

    
310
    # gestion des scripts additionnels
311
    debut, fin = get_scripts(user, machine, ostype, groups)
312
    # écriture du fichier
313
    write_fich(netlogon, debut + script + fin)
314

    
315
def gen_letter_share(lettre, share):
316
    """lettre : supprime les ":", juste la lettre
317
    share : le partage sans \ à la fin
318
    """
319
    while share[-1] == '\\':
320
        share = share[:-1]
321
    return lettre[:1], share
322

    
323
def gen_lecteur_bloc(lettre, share):
324
    """Génère un bloc de ligne pour le montage
325
    d'un lecteur réseau "share" sur la lettre "lettre"
326
    ou sur * sinon (sauf pour win95)
327
    """
328
    lettre, share = gen_letter_share(lettre, share)
329
    return 'lecteur,%s:,%s\r\n' % (lettre, share)
330

    
331
def gen_cmd_line(line, hide=False, nowait=False):
332
    """génère une ligne DOS (avec le bon retour chariot) (pas win95)
333
    """
334
    chaine = 'cmd,%s' % line
335
    if hide:
336
        chaine += ',HIDDEN'
337
    if nowait:
338
        chaine += ',NOWAIT'
339
    return chaine + '\r\n'
340

    
341
def get_scripts(user, machine, ostype, user_groups):
342
    """Gestion des scripts externes
343
    """
344
    # motif à rechercher dans les fichiers externes
345
    # (debut_script %motif% fin_script)
346
    motif_include = [ "%%NetUse%%", "%NetUse%" ]
347
    # chemin par défaut des scripts externes
348
    buffer_debut,  buffer_fin = '', ''
349
    # on traite ceux qui sont présents
350
    for chemin in get_scripts_list(user, machine, ostype, user_groups):
351
        if isfile(chemin):
352
            debut = 1
353
            for line in file(chemin,"r"):
354
                line = line.strip()
355
                if line in motif_include:
356
                    debut = 0
357
                else:
358
                    if debut == 1:
359
                        buffer_debut += parse_line(ostype, line)
360
                    else:
361
                        buffer_fin += parse_line(ostype, line)
362
    return (buffer_debut, buffer_fin)
363

    
364
def get_scripts_list(user, machine, ostype, user_groups):
365
    """On créé la liste des fichiers possibles
366
    +--PATH_SCRIPTS/users/<user>.txt
367
    +--PATH_SCRIPTS/groups/<group>.txt
368
    +--PATH_SCRIPTS/machines/<machine>.txt
369
    +--PATH_SCRIPTS/os/<os>.txt
370
    +--PATH_SCRIPTS/os/<os>/<group>.txt
371
    +--PATH_SCRIPTS/os/<os>/<user>.txt
372
    """
373
    chemins = [join(PATH_SCRIPTS, 'users', '%s%s' % (user, SCRIPTS_EXT))]
374
    chemins.append(join(PATH_SCRIPTS, 'machines', '%s%s' %
375
                (machine, SCRIPTS_EXT)))
376
    for group in user_groups:
377
        chemins.append(join(PATH_SCRIPTS, 'groups', '%s%s' %
378
                    (group, SCRIPTS_EXT)))
379
        chemins.append(join(PATH_SCRIPTS, 'os', '%s' % ostype, '%s%s' %
380
                    (group, SCRIPTS_EXT)))
381
    chemins.append(join(PATH_SCRIPTS, 'os', '%s%s' % (ostype, SCRIPTS_EXT)))
382
    chemins.append(join(PATH_SCRIPTS, 'os', ostype, '%s%s' %
383
                (user, SCRIPTS_EXT)))
384
    return chemins
385

    
386
def parse_line(ostype, line):
387
    items = [ i.strip() for i in line.split(',') ]
388
    script_type = items[0].upper()
389
    options = [opt.upper() for opt in items[2:]]
390
    if script_type == 'CMD':
391
        # exécuter une commande
392
        # cmd,"C:\Windows\notepad.exe",NOWAIT,HIDDEN
393
        #  0     1                       2      3
394
        hide = 'HIDDEN' in options
395
        nowait = 'NOWAIT' in options
396
        return gen_cmd_line(items[1], hide, nowait)
397
    elif script_type == 'LECTEUR':
398
        # monter un partage
399
        lettre, partage = items[1], items[2]
400
        return gen_lecteur_bloc(lettre, partage)
401
    return ''
402

    
403
def write_fich(netlogon, lines):
404
    """Ecrit le fichier
405
    """
406
    fic = file(netlogon, 'w')
407
    fchmod(fic.fileno(), 0644)
408
    #convert to DOS format
409
    #lines = lines.replace('\n', '\r\n')
410
    lines = lines.decode('utf8').encode('cp437')
411
    fic.write(lines)
412
    fic.close()