#!/usr/bin/env python # -*- coding: utf-8 -*- import ldap import socket import time import lc_ldap import attributs import objets import variables import sys if not '/usr/scripts' in sys.path: sys.path.append('/usr/scripts') from gestion.gen_confs.dhcpd_new import dydhcp import gestion.config as config # liste des attributs dont dépend un service services_to_attrs = {} 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'] services_to_attrs['macip'] = [ attributs.ipHostNumber, attributs.ip6HostNumber, attributs.macAddress, attributs.paiement, attributs.carteEtudiant, attributs.finConnexion ] services_to_attrs['dns'] = [ attributs.ipHostNumber, attributs.ip6HostNumber, attributs.sshFingerprint, attributs.host, attributs.hostAlias, attributs.dnsIpv6 , attributs.hostCert, attributs.portTCPin, attributs.portUDPin ] 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'] 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'] 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] NOW = time.time() def update_now(): global NOW NOM = time.time() def services_to_args_mail_modif(x): if isinstance(x, attributs.droits): return [ "uid=%s" % x.parent['uid'][0] ] elif isinstance(x, attributs.Attr) and x.__class__ in [ attributs.exempt ] + services_to_attrs['ports'] and isinstance(x.parent, objets.machine): return [ "mid=%s" % x.parent['mid'][0] ] elif isinstance(x.parent, objets.machine) and isinstance(x.parent.proprio(mode='ro'), objets.AssociationCrans): 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: return [ str(ip) for ip in x.parent.get('ipHostNumber',[]) ] 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): return [str(x)] elif isinstance(x.parent, objets.machine): return [ str(ip) for ip in x.parent.get('ipHostNumber',[]) ] 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 [] # 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 [] if u'cransAccount' in [ str(o) for o in proprio['objectClass']]: 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 [] if u'cransAccount' in [ str(o) for o in proprio['objectClass']]: return [ "%s,%s" % (proprio['uid'][0], proprio['homeDirectory'][0]) ] else: return [] def services_to_args_blacklist(x): if isinstance(x.parent, objets.machine): return [ str(ip) for m in x.parent.proprio(mode='ro').machines() for ip in m['ipHostNumber'] ] 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 [] if u'cransAccount' in [ str(o) for o in adh['objectClass']]: 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 return set([(0 if x['debut'] == '-' else x['debut']), (0 if x['fin'] == '-' else x['fin'])]) 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 [] if u'cransAccount' in [ str(o) for o in adh['objectClass']]: return [ NOW + 660 ] else: return [ NOW + 660 ] # génération des arguments du service à redémarrer (par defaut []) services_to_args={} services_to_args['macip']=services_to_args_macip ## Inutile pour blackliste pour le moment #services_to_args['blacklist']=services_to_args_blacklist services_to_args['port']=services_to_args_port services_to_args['dns']=services_to_args_dns services_to_args['home']=services_to_args_home services_to_args['mail_ajout_droits'] = lambda x: "%s:%s" % (x.parent['uid'][0], x) 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 # Quand redémarrer le service (par defaut [0]), doit returner un iterable avec potentiellement plusieurs dates services_to_time={} services_to_time['blacklist']=services_to_time_blacklist services_to_time['mail_bienvenue']=services_to_time_mail_bienvenue 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] 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] # 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): """ 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 = [] for s in conn.search_s(variables.services_dn, 1, 'objectClass=service'): 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 remove_dn = 'cn=%s,%s' % (new[2:], variables.services_dn) else: # On enlève uniquement si la date est passée remove_dn = 'cn=%s,%s' % (new[1:], variables.services_dn) if not serv.has_key(new[1:]): # Existe pas => rien à faire return keep_date = [] for date in serv_dates[new[1:]]: if NOW < int(date): 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 serv_dn = 'cn=%s,%s' % (new, variables.services_dn) # 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: try: conn.modify_s(serv_dn, modlist) except ldap.TYPE_OR_VALUE_EXISTS: # Pas grave pass # 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) except (ldap.ALREADY_EXISTS, ldap.TYPE_OR_VALUE_EXISTS): # Existe déja => rien à faire pass def services_to_restart(conn, old_attrs={}, new_attrs={}, created_object=[], deleted_object=[]): """Détermine quels sont les services à reconfigurer""" update_now() 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) if 'cransAccount' in deleted_objectClass: service_to_restart(conn, "del_user", ["%s,%s" % (old_attrs['uid'][0], old_attrs['homeDirectory'][0])], 0) # On tranforme les dico *_attrs du type string -> attr en *_attrs_c du type class -> attr 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) # Les attributs crée 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]] # Les attributs supprimé 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]] # 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 updated_attr = [ (old_attrs_c[name], new_attrs_c[name], [ 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]]], ) 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]] # Génération du dico "nom de service à redémarrer" -> "attribut dont dépende le service qui ont été modifier" 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] for obj in created_object: for service in objects_to_services['create'].get(obj.__class__, []): services_to_restart[service] = services_to_restart.get(service, []) + [obj] for obj in deleted_object: for service in objects_to_services['delete'].get(obj.__class__, []): services_to_restart[service] = services_to_restart.get(service, []) + [obj] for service in services_to_restart.keys(): # ok, la variable s'appel attr, mais ça paut aussi être un objet for attr in services_to_restart[service]: arg = set() # Il y a une fonction pour générer des arguements, ils sont donc nécessaire 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)) if not arg: # services_to_args retour une liste vide d'arguments, on passe à l'attribut suivant continue # Cas du dhcp if attr.__class__ in services_to_attrs['dhcp']: for server in config.dhcp_servers: 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: dhcp.del_host(str(old_attrs['ipHostNumber'][0]), str(old_attrs['macAddress'][0])) 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 # Il y a une fonction pour dire quand il faut redémarrer le service if service in services_to_time.keys(): for start in services_to_time[service](attr): service_to_restart(conn, service, list(arg), start) # Sinon, on le redémarre tout de suite else: service_to_restart(conn, service, list(arg), 0) else: service_to_restart(conn, service, [], 0) # Identique à la classe dans ldap_crans.py class Service: """ Définit un service à redémarrer """ __slots__ = ("nom", "args", "start") 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() dates = u' et '.join(map(lambda t: t < NOW and \ 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")