services.py 18.3 KB
Newer Older
1 2 3
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import ldap
4
import socket
5
import time
6 7
import lc_ldap
import attributs
8
import objets
9
import variables
10 11 12
import sys
if not '/usr/scripts' in sys.path:
    sys.path.append('/usr/scripts')
13
from gestion.gen_confs.dhcpd_new import dydhcp
14
import gestion.config as config
15 16 17

# liste des attributs dont dépend un service
services_to_attrs = {}
18 19 20 21
services_to_attrs['filtrage_machines'] = [ attributs.ipHostNumber, attributs.ip6HostNumber, attributs.macAddress ]
services_to_attrs['surveillance_machines'] = services_to_attrs['filtrage_machines']
services_to_attrs['filtrage_exemptions'] = [ attributs.exempt ]
services_to_attrs['surveillance_exemptions'] = services_to_attrs['filtrage_exemptions']
22
services_to_attrs['macip'] = [ attributs.ipHostNumber, attributs.ip6HostNumber, attributs.macAddress, attributs.paiement, attributs.carteEtudiant, attributs.finConnexion ]
23
services_to_attrs['dns'] = [ attributs.ipHostNumber, attributs.ip6HostNumber, attributs.sshFingerprint, attributs.host, attributs.hostAlias, attributs.dnsIpv6 , attributs.hostCert, attributs.portTCPin, attributs.portUDPin ]
24 25 26 27 28
services_to_attrs['blacklist'] = [ attributs.blacklist, attributs.chbre, attributs.mailInvalide ] + services_to_attrs['macip']
services_to_attrs['ports'] = [ attributs.portUDPout, attributs.portUDPin, attributs.portTCPout, attributs.portTCPin ]
services_to_attrs['droits'] = [ attributs.droits ]
services_to_attrs['mail_ajout_droits'] = [ attributs.droits ]
services_to_attrs['dhcp'] = services_to_attrs['macip']
29 30
services_to_attrs['mail_bienvenue'] = [ attributs.mail, attributs.canonicalAlias ]
services_to_attrs['mail_modif'] = [ attributs.droits, attributs.exempt ] + services_to_attrs['ports'] + services_to_attrs['macip']
31

32 33 34 35 36 37
services_to_objects={}
services_to_objects['delete']={}
services_to_objects['create']={}
services_to_objects['create']['home'] = [objets.adherent, objets.club]
services_to_objects['delete']['del_user'] = [objets.adherent, objets.club]

38 39 40 41 42
NOW = time.time()

def update_now():
    global NOW
    NOM = time.time()
43

44 45 46
def services_to_args_mail_modif(x):
    if isinstance(x, attributs.droits):
        return [ "uid=%s" % x.parent['uid'][0] ]
47
    elif isinstance(x, attributs.Attr) and x.__class__ in [ attributs.exempt ] + services_to_attrs['ports'] and isinstance(x.parent, objets.machine):
48
        return [ "mid=%s" % x.parent['mid'][0] ]
49
    elif isinstance(x.parent, objets.machine) and isinstance(x.parent.proprio(mode='ro'), objets.AssociationCrans):
50 51 52 53 54 55 56 57 58
        return [ "mid=%s" % x.parent['mid'][0] ]
    else:
        return []

def services_to_args_macip(x):
    if isinstance(x.parent, objets.machine):
        if isinstance(x, attributs.ipHostNumber):
            return [str(x)]
        else:
59
            return [ str(ip) for ip in x.parent.get('ipHostNumber',[]) ]
60 61 62 63 64 65 66
    elif isinstance(x.parent, objets.proprio):
        return [ str(ip) for m in x.parent.machines() for ip in m.get('ipHostNumber',[])]
    else:
        return []

def services_to_args_port(x):
    if isinstance(x, attributs.ipHostNumber) or isinstance(x, attributs.ip6HostNumber):
67
        return [str(x)]
68
    elif isinstance(x.parent, objets.machine):
69
        return [ str(ip) for ip in x.parent.get('ipHostNumber',[]) ]
70 71 72 73 74 75 76 77 78 79
    else:
        return []

def services_to_args_dns(x):
    if isinstance(x.parent, objets.machine):
        return [ str(x.parent['mid'][0]) ]
    elif isinstance(x.parent, objets.baseCert):
        return [ str(x.parent.machine()['mid'][0]) ]
    else:
        return []
80 81 82 83 84 85 86 87 88

# Trouver comment faire le cas où on ajoute une redirection mail (il faut alors retourner un quadruplet "homedir,uidNumber,uid,mail")
def services_to_args_home(x):
    if isinstance(x, attributs.Attr):
        proprio=x.parent
    elif isinstance(x, objets.proprio):
        proprio=x
    else:
        return []
89
    if u'cransAccount' in [ str(o) for o in proprio['objectClass']]:
90 91 92 93 94 95 96 97 98 99 100
        return [ "%s,%s,%s" % (proprio['homeDirectory'][0],proprio['uidNumber'][0],proprio['uid'][0]) ]
    else:
        return []

def services_to_args_del_user(x):
    if isinstance(x, attributs.Attr):
        proprio=x.parent
    elif isinstance(x, objets.proprio):
        proprio=x
    else:
        return []
101
    if u'cransAccount' in [ str(o) for o in proprio['objectClass']]:
102 103 104 105 106 107 108
        return [ "%s,%s" % (proprio['uid'][0], proprio['homeDirectory'][0]) ]
    else:
        return []


def services_to_args_blacklist(x):
    if isinstance(x.parent, objets.machine):
109
        return [ str(ip) for m in x.parent.proprio(mode='ro').machines() for ip in m['ipHostNumber'] ]
110 111 112 113 114 115 116 117 118 119 120 121
    elif isinstance(x.parent, objets.proprio):
        return [ str(ip) for m in x.parent.machines() for ip in m['ipHostNumber'] ]
    else:
        return []

def services_to_args_mail_bienvenue(x):
    if isinstance(x, attributs.Attr) and isinstance(x.parent, objets.adherent):
        adh=x.parent
    elif isinstance(x, objets.adherent):
        adh=x
    else:
        return []
122
    if u'cransAccount' in [ str(o) for o in adh['objectClass']]:
123 124 125 126 127 128 129
        return [ "%s" % adh['canonicalAlias'][0] ]
    else:
        return [ "%s" % adh['mail'][0] ]

def services_to_time_blacklist(x):
    if isinstance(x, attributs.blacklist):
        # Un set à au plus deux valeur : la date de début et la date de fin de la blacklist
130
        return set([(0 if x['debut'] == '-' else x['debut']), (0 if x['fin'] == '-' else x['fin'])])
131 132 133 134 135 136 137 138 139 140
    else:
        return [0]

def services_to_time_mail_bienvenue(x):
    if isinstance(x, attributs.Attr) and isinstance(x.parent, objets.adherent):
        adh=x.parent
    elif isinstance(x, objets.adherent):
        adh=x
    else:
        return []
141
    if u'cransAccount' in [ str(o) for o in adh['objectClass']]:
142
        return [ NOW + 660 ]
143
    else:
144
        return [ NOW + 660 ]
145

146 147
# génération des arguments du service à redémarrer (par defaut [])
services_to_args={}
148
services_to_args['macip']=services_to_args_macip
149
## Inutile pour blackliste pour le moment
150 151
#services_to_args['blacklist']=services_to_args_blacklist
services_to_args['port']=services_to_args_port
152
services_to_args['dns']=services_to_args_dns
153
services_to_args['home']=services_to_args_home
154
services_to_args['mail_ajout_droits'] = lambda x: "%s:%s" % (x.parent['uid'][0], x)
155 156 157
services_to_args['del_user']=services_to_args_del_user
services_to_args['mail_bienvenue']=services_to_args_mail_bienvenue
services_to_args['mail_modif']=services_to_args_mail_modif
158

159
# Quand redémarrer le service (par defaut [0]), doit returner un iterable avec potentiellement plusieurs dates
160
services_to_time={}
161 162
services_to_time['blacklist']=services_to_time_blacklist
services_to_time['mail_bienvenue']=services_to_time_mail_bienvenue
163 164 165 166 167 168

attrs_to_services = {}
for (key, values) in services_to_attrs.items():
    for value in values:
        attrs_to_services[value] = attrs_to_services.get(value, []) + [key]

169 170 171 172 173 174 175
objects_to_services={}
for status in ['delete', 'create']:
    objects_to_services[status]={}
    for (key, values) in services_to_objects[status].items():
        for value in values:
            objects_to_services[status][value]=objects_to_services[status].get(value, []) + [key]

176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
# Identique à celle dans ldap_crans.py
def preattr(val):
    """
    val est :
        * un entier
        * une chaîne
        * une liste avec un seul entier ou une seule chaîne

    Retourne [ len(str(val).strip), str(val).strip en utf-8 ]
    """

    if isinstance(val, list) and len(val) == 1:
        return preattr(val[0])

    elif isinstance(val, str) or isinstance(val, int):
        val = str(val).strip()
        # On passe tout en utf-8 pour ne pas avoir de problèmes
        # d'accents dans la base
        return [len(val), val]
    elif isinstance(val, unicode):
        val = val.strip()
        return [len(val), val.encode('utf-8')]
    else:
        raise TypeError(type(val))

# Presque identique à celle dans ldap_crans.py
def service_to_restart(conn, new=None, args=[], start=0):
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
    """
    start indique la date (en secondes depuis epoch) à partir du
    moment où cette action sera effectuée.

    Si new = None retourne la liste des services à redémarrer.

    Si new est fourni, mais ne commence pas par '-', on ajoute
    le service à la liste avec les arguments args (args doit être
    une liste).

    Si new commence par '-', on supprime le service si son start
    est dans le futur.

    Si new commence par '--', on supprime le service sans condition.
    """
    if new: new = preattr(new)[1]

    # Quels services sont déjà à redémarrer ?
    serv = {}       # { service: [ arguments ] }
    serv_dates = {} # { service: [ dates de restart ] }
    services = []
224
    for s in conn.search_s(variables.services_dn, 1, 'objectClass=service'):
225 226 227 228 229 230 231 232 233 234 235 236
        s = s[1]
        serv[s['cn'][0]] = s.get('args', [])
        serv_dates[s['cn'][0]] = s.get('start', [])
        services.append(Service(s['cn'][0], s.get('args', []), s.get('start', [])))

    # Retourne la liste des services à redémarrer
    if not new: return services

    # Effacement d'un service
    if new[0] == '-':
        if new[1] == '-':
            # Double -- on enlève quelque soit la date
237
            remove_dn = 'cn=%s,%s' % (new[2:], variables.services_dn)
238
        else:
239
            # On enlève uniquement si la date est passée
240
            remove_dn = 'cn=%s,%s' % (new[1:], variables.services_dn)
241 242 243 244 245
            if not serv.has_key(new[1:]):
                # Existe pas => rien à faire
                return
            keep_date = []
            for date in serv_dates[new[1:]]:
246
                if NOW < int(date):
247 248 249 250 251 252 253 254 255 256 257 258 259
                    keep_date.append(date)
            if keep_date:
                mods = [{'start': serv_dates[new[1:]]}, { 'start': keep_date }]
                conn.modify_s(remove_dn, ldap.modlist.modifyModlist(*mods))
                remove_dn = None

        if remove_dn:
            # Suppression
            try: conn.delete_s(remove_dn)
            except: pass
            # Si n'existe pas => Erreur mais le résultat est là.
        return

260
    serv_dn = 'cn=%s,%s' % (new, variables.services_dn)
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293

    # Conversion avant stockage dans la base
    if isinstance(args, basestring):
        args = [args]
    args = map(lambda x:preattr(x)[1], args)
    try:
        start = [int(start)]
    except TypeError:
        pass
    start = map(lambda x:preattr(x)[1], start)

    if new in serv.keys():
        modlist = []

        new_args = []
        # Nouveaux arguments ?
        for arg in args:
            if arg not in serv[new]:
                new_args.append(arg)
        if new_args:
            modlist += ldap.modlist.modifyModlist({'args': serv[new]},
                                                  {'args': serv[new] + new_args})

        new_date = []
        # Nouvelle date ?
        for date in start:
            if date not in serv_dates[new]:
                new_date.append(date)
        if new_date:
            modlist += ldap.modlist.modifyModlist({'start': serv_dates[new]},
                                                  {'start': serv_dates[new] + new_date})

        if modlist:
294
            try:
295 296 297
                conn.modify_s(serv_dn, modlist)
            except ldap.TYPE_OR_VALUE_EXISTS:
                # Pas grave
298
                pass
299 300 301 302 303 304 305 306 307
        # else rien à faire
    else:
        # Entrée non présente -> ajout
        modlist = ldap.modlist.addModlist({ 'objectClass': 'service',
                                            'cn':  new,
                                            'args': args,
                                            'start': start })
        try:
            conn.add_s(serv_dn, modlist)
308
        except (ldap.ALREADY_EXISTS, ldap.TYPE_OR_VALUE_EXISTS):
309 310
            # Existe déja => rien à faire
            pass
311

312
def services_to_restart(conn, old_attrs={}, new_attrs={}, created_object=[], deleted_object=[]):
313
    """Détermine quels sont les services à reconfigurer"""
314

315 316
    update_now()

317 318 319 320 321 322 323 324
    added_objectClass = [obc for obc in new_attrs.get("objectClass", []) if not obc in old_attrs.get("objectClass", [])]
    deleted_objectClass = [obc for obc in old_attrs.get("objectClass", []) if not obc in new_attrs.get("objectClass", [])]

    # Je met la reconfiguration du home / del_user à la main pour la création/suppression d'un compte crans
    # parce que que je vois pas vraiement comment faire autrement
    if 'cransAccount' in added_objectClass:
        arg = services_to_args['home'](added_objectClass[0])
        service_to_restart(conn, "home", list(arg), 0)
325

326 327 328
    if 'cransAccount' in deleted_objectClass:
        service_to_restart(conn, "del_user", ["%s,%s" % (old_attrs['uid'][0], old_attrs['homeDirectory'][0])], 0)

329
    # On tranforme les dico *_attrs du type string -> attr en *_attrs_c du type class -> attr
330 331
    old_attrs_c = dict((attributs.AttributeFactory.get(key), value) for key,value in old_attrs.items() if attributs.AttributeFactory.get(key, fallback=None) != None and value)
    new_attrs_c = dict((attributs.AttributeFactory.get(key), value) for key,value in new_attrs.items() if attributs.AttributeFactory.get(key, fallback=None) != None and value)
332

333
    # Les attributs crée
334
    created_attr = [ attr for name in new_attrs_c.keys() if not name in old_attrs_c.keys() for attr in new_attrs_c[name]]
335
    # Les attributs supprimé
336
    deleted_attr = [ attr for name in old_attrs_c.keys() if not name in new_attrs_c.keys() for attr in old_attrs_c[name]]
337
    # Les attributs mis à jour : liste de (ancien, nouveau, ancien\nouveau, nouveau\ancien) pour les types d'attibut a la fois dans old_attrs et new_attrs
338
    updated_attr = [ (old_attrs_c[name], new_attrs_c[name],
339 340
      [ i for i in old_attrs_c[name] if i.value not in [ j.value for j in new_attrs_c[name]]],
      [ i for i in new_attrs_c[name] if i.value not in [ j.value for j in old_attrs_c[name]]],
341 342 343 344
                      ) for name in set(new_attrs_c.keys()).intersection(old_attrs_c.keys()) if old_attrs_c[name][-1].value != new_attrs_c[name][-1].value ]
    updated_attr_new = [ i for j in updated_attr for i in j[3]]
    updated_attr_old = [ i for j in updated_attr for i in j[2]]

345 346

    # Génération du dico "nom de service à redémarrer" -> "attribut dont dépende le service qui ont été modifier"
347 348 349 350 351
    services_to_restart = {}
    for attr in [ attr for l in [created_attr, deleted_attr, updated_attr_new ] for attr in l]:
        for service in attrs_to_services.get(attr.__class__, []):
            services_to_restart[service] = services_to_restart.get(service, []) + [attr]

352 353
    for obj in created_object:
        for service in objects_to_services['create'].get(obj.__class__, []):
354
            services_to_restart[service] = services_to_restart.get(service, []) + [obj]
355 356 357

    for obj in deleted_object:
        for service in objects_to_services['delete'].get(obj.__class__, []):
358
            services_to_restart[service] = services_to_restart.get(service, []) + [obj]
359

360
    for service in services_to_restart.keys():
361
        # ok, la variable s'appel attr, mais ça paut aussi être un objet
362 363
        for attr in services_to_restart[service]:
            arg = set()
364
            # Il y a une fonction pour générer des arguements, ils sont donc nécessaire
365 366 367 368 369 370
            if service in services_to_args.keys():
                arg = arg.union(services_to_args[service](attr))
                if attr in updated_attr_new:
                    for old_attr in updated_attr_old:
                        if attr.__class__ == old_attr.__class__:
                            arg = arg.union(services_to_args[service](old_attr))
371 372
                if not arg: # services_to_args retour une liste vide d'arguments, on passe à l'attribut suivant
                    continue
373 374 375

                # Cas du dhcp
                if attr.__class__ in services_to_attrs['dhcp']:
376
                    for server in config.dhcp_servers:
377 378 379 380 381 382 383 384
                        try:
                            dhcp=dydhcp(server)
                            if old_attrs.get('ipHostNumber', []) and old_attrs.get('macAddress', []):
                                if new_attrs.get('ipHostNumber', []) and new_attrs.get('macAddress', []):
                                    if str(old_attrs['ipHostNumber'][0]) != str(new_attrs['ipHostNumber'][0]) or str(old_attrs['macAddress'][0]) != str(new_attrs['macAddress'][0]):
                                        dhcp.del_host(str(old_attrs['ipHostNumber'][0]), str(old_attrs['macAddress'][0]))
                                        dhcp.add_host(str(new_attrs['ipHostNumber'][0]), str(new_attrs['macAddress'][0]), str(new_attrs['host'][0]))
                                else:
385
                                    dhcp.del_host(str(old_attrs['ipHostNumber'][0]), str(old_attrs['macAddress'][0]))
386 387 388 389
                            elif new_attrs.get('ipHostNumber', []) and new_attrs.get('macAddress', []):
                                dhcp.add_host(str(new_attrs['ipHostNumber'][0]), str(new_attrs['macAddress'][0]), str(new_attrs['host'][0]))
                        except socket.error:
                            pass
390

391
            # Il y a une fonction pour dire quand il faut redémarrer le service
392
            if service in services_to_time.keys():
393 394
                for start in services_to_time[service](attr):
                    service_to_restart(conn, service, list(arg), start)
395
            # Sinon, on le redémarre tout de suite
396 397 398 399
            else:
                service_to_restart(conn, service, list(arg), 0)
        else:
            service_to_restart(conn, service, [], 0)
400 401 402 403 404


# Identique à la classe dans ldap_crans.py
class Service:
    """ Définit un service à redémarrer """
405
    __slots__ = ("nom", "args", "start")
406 407 408 409 410 411 412 413 414 415 416 417 418 419
    def __init__(self, nom, args=[], start=[]):
        """
        Nom du service
        Liste des arguments
        Liste d'horaires de démarrages
        """

        self.nom = nom
        self.args = args
        self.start = map(int, start)

    def __unicode__(self):
        starting = self.start
        starting.sort()
420
        dates = u' et '.join(map(lambda t: t < NOW and \
421 422 423 424 425 426 427 428 429 430 431
                                u"maintenant" or time.strftime(date_format,
                                                              time.localtime(t)),
                                self.start))
        dates = u" à partir d%s %s" % (dates.startswith(u"maintenant") and u"e" or u"u",
                                      dates)
        return (u"%s(%s)%s" % (self.nom,
                              u','.join([i.decode("UTF-8") for i in self.args]),
                              dates)).replace(u" et maintenant", u"")

    def __str__(self):
        return self.__unicode__().encode("utf-8", "ignore")