#! /usr/bin/env python # -*- coding: iso-8859-15 -*- """ Définitions des classes de base du système de gestion des machines et adhérents du crans Copyright (C) Frédéric Pauget Licence : GPLv2 """ from socket import gethostname date_format='%d/%m/%Y %H:%M' hostname = gethostname().split(".")[0] if hostname == "zamok": anon_bind = 1 # Anonyme pour lecture seule uri = 'ldapi://%2fvar%2frun%2fldapi/' rw_uri = uri elif hostname == "sila" : anon_bind = 1 # Anonyme pour lecture seule uri = 'ldapi://%2fvar%2frun%2fldapi/' rw_uri = 'ldaps://zamok.crans.org:636/' else : anon_bind = 0 # Doit s'authentifier pour toute action uri = 'ldaps://sila.crans.org:636/' rw_uri = 'ldaps://zamok.crans.org:636/' smtpserv = "localhost" import smtplib, sre, os, random, string, time, sys import ldap, ldap.modlist import config, annuaires, iptools, chgpass, user_tests, cPickle from chgpass import chgpass from affich_tools import coul, prompt from time import sleep,localtime try : from secrets import ldap_password, ldap_auth_dn except : sys.stdout.write(coul('Warning : impossible de lire le fichier de secret !','jaune')) sleep(2) ldap_password = '' ldap_auth_dn = '' random.seed() # On initialise le générateur aléatoire ################################################################################## ### Différent services redémarrables #dns, dhcp, firewall, bornes_wifi(nom_borne), conf_wifi, bl_carte_etudiant, switch(chbre) ################################################################################## ### Items de la blackliste blacklist_items = { u'bloq' : u'Bloquage total de tout services' , u'virus' : u'Bloquage sur squid', u'upload' : u'Bloquage total accès extérieur', u'warez' : u'Bloquage sur squid' } ################################################################################## ### Droits possibles droits_possibles = [ u'Nounou', u'Apprenti', u'Modérateur', u'Câbleur', u'Déconnecteur',u'CVSWeb' ] ################################################################################## ### Variables internes diverses isadm = user_tests.isadm() isdeconnecteur = user_tests.isdeconnecteur() ann_scol = config.ann_scol script_utilisateur = user_tests.getuser() ################################################################################## ### Fonctions utiles def decode(s) : """ Retourne un unicode à paritr de s s doit être en utf-8 """ return s.decode('utf-8','ignore') # On ignore les erreurs accents = "êëèéÉÈÀÙâäàûüôöÖÔîïÎÏ'çÇÿßæÆøØ" # Si modif ici modifier aussi la fonction def strip_accents(a) : a = a.replace(u'ê','e').replace(u'ë','e').replace(u'è','e').replace(u'é','e').replace(u'É','e').replace(u'È','e') a = a.replace(u'â','a').replace(u'ä','a').replace(u'à','a').replace(u'À','a') a = a.replace(u'û','u').replace(u'ü','u').replace(u'ù','u').replace(u'Ù','u') a = a.replace(u'ô','o').replace(u'ö','o').replace(u'Ö','o').replace(u'Ô','o') a = a.replace(u'î','i').replace(u'ï','i').replace(u'Ï','i').replace(u'Î','i') a = a.replace(' ','_').replace(u"'",'').replace(u'ç','c').replace(u'Ç','c') a = a.replace(u'ÿ','y').replace(u'ß','ss').replace(u'æ','ae').replace(u'Æ','ae').replace(u'ø','o').replace(u'Ø','o') return a def mailexist(mail) : """ Vérifie si un mail existe ou non grace à la commande vrfy du serveur mail """ try : s=smtplib.SMTP(smtpserv) r = s.vrfy(mail) s.close() except : raise ValueError(u'Serveur de mail injoignable') if r[0] == 250 : return True else : return False def preattr(val) : """ val est : * un entier * une chaîne * une liste avec un seul entier ou chaine Retourne [ len(str(val).strip), str(val).strip ] """ t = type(val) if t==list and len(val)==1 : return preattr(val[0]) elif t==str or t==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) , unicode(val,'iso-8859-1').encode('utf-8') ] elif t==unicode : val = val.strip() return [ len(val) , val.encode('utf-8') ] else : raise TypeError def is_actif(sanction) : """ Retourne True ou False suivant si la sanction founie (chaine venant de blacklist) est active ou non """ bl = sanction.split(',') try : now = time.time() debut = time.mktime( time.strptime(bl[0],date_format) ) if bl[1]=='-' : fin = now + 1 else : fin = time.mktime( time.strptime(bl[1],date_format) ) return debut < now and fin > now except : return False def format_mac(mac) : """ Formatage des adresses mac Transforme une adresse pour obtenir la forme xx:xx:xx:xx:xx:xx Le séparateur original peut être :, - ou rien Retourne la mac formatée. """ l, mac = preattr(mac) mac= mac.replace(':','').replace('-','').replace(' ','').lower() if len(mac)!=12 : raise ValueError(u'Longueur adresse mac incorrecte.') for c in mac[:] : if c not in string.hexdigits : raise ValueError(u"Caractère interdit '%s' dans adresse mac." % c) if mac=='000000000000' : raise ValueError(u"MAC nulle interdite\nIl doit être possible de modifier l'adresse de la carte.") # Formatage mac="%s:%s:%s:%s:%s:%s" % ( mac[:2],mac[2:4],mac[4:6], mac[6:8], mac[8:10], mac[10:] ) return mac ################################################################################## ### Définition des classes class crans_ldap : """ Classe de connexion à la base LDAP du crans. """ conn=None base_dn='ou=data,dc=crans,dc=org' base_lock = 'ou=lock,dc=crans,dc=org' base_services = 'ou=services,dc=crans,dc=org' ### Configuration de la recheche # Dictionnaire de tranformation des champs trans = { 'prénom' : 'prenom' , 'chambre' : 'chbre', 'login' : 'mail' , 'hostname' : 'host', 'mac' : 'macAddress', 'ip' : 'ipHostNumber' , 'telephone' : 'tel' } # Champs de recherche pour la recherche automatique auto_search_champs = { 'adherent' : [ 'nom', 'prenom', 'tel', 'mail', 'chbre', 'mailAlias', 'cannonicalAlias' ], \ 'machine' : [ 'macAddress', 'host', 'ipHostNumber', 'hostAlias'] , 'club' : [ 'nom', 'chbre' ] } # Champs de recherche pour la recherche manuelle (en plus de la recherche auto) non_auto_search_champs = { 'adherent' : [ 'etudes', 'paiement', 'carteEtudiant', 'aid' , 'postalAddress', 'historique' ,'blacklist', 'droits', 'uidNumber' ], \ 'machine' : [ 'mid' , 'ipsec', 'historique', 'blacklist' , 'puissance', 'canal', 'portTCPin', 'portTCPout', 'portUDPin', 'portUDPout' ] , 'club' : [ 'cid' , 'responsable', 'paiement', 'historique', 'blacklist'] } # Scope des différentes recherches scope = { 'adherent' : 1 , 'machine' : 2 , 'club' : 1 } def __init__(self) : self.connect() def connect(self): """ Initialisation des connexion vers le serveur LDAP """ def bind(conn,anon_bind=0) : """ Authentification auprès du serveur ldap """ nbessais = 0 ok = False while not ok: try: if anon_bind : conn.bind_s('','',ldap.AUTH_SIMPLE) else : conn.bind_s(ldap_auth_dn,ldap_password,ldap.AUTH_SIMPLE) ok = True except ldap.SERVER_DOWN : nbessais += 1 if nbessais > 2: sys.stderr.write("ERREUR : serveur LDAP injoignable\n") sys.exit(1) else: sleep(0.3) def select_conn(methode_base, methode_alternative) : """ Retoune une fonction qui : 1) bind sur la connection self.conn si necessaire 2) fait ce que ferai methode_base 3) si échoue bind sur la connexion self.rw_conn 4) fait ce que ferai methode_alternative """ def new_methode(*args) : try : if not self.__conn_binded : bind(self.conn,anon_bind) self.__conn_binded = True return methode_base(*args) except ldap.STRONG_AUTH_REQUIRED : # On a pas les droits necessaires ici if not self.__rw_conn_binded : bind(self.rw_conn) self.__rw_conn_binded = True return methode_alternative(*args) return new_methode # Les objets ldap necessaires self.conn = ldap.initialize(uri) self.__conn_binded = False self.rw_conn = ldap.initialize(rw_uri) self.__rw_conn_binded = False # Modification des méthodes utilisées self.conn.search_s = select_conn(self.conn.search_s,self.rw_conn.search_s) self.conn.add_s = select_conn(self.conn.add_s,self.rw_conn.add_s) self.conn.modify_s = select_conn(self.conn.modify_s,self.rw_conn.modify_s) self.conn.delete_s = select_conn(self.conn.delete_s,self.rw_conn.delete_s) def exist(self,arg) : """ Vérifie l'existence d'une entrée dans la base et que cette entrée n'appartient pas à l'objet en cours, prend en compte les locks arg doit être une expression de recherche ldap Si existence, retourne la liste de dn correspondants Sinon retourne une liste vide Exemple : exist('chbre=Z345') vérifie si il y a un adhérent en Z345 """ r=[] # Premier test : dans les objets déja inscrits ret = self.conn.search_s(self.base_dn,2,arg) for res in ret : # C'est peut être l'objet courant try : # Si ce n'est pas une classe fille avec l'attribu dn => erreur if res[0] == self.dn : continue except : pass r.append(res[0]) # Deuxième test : lock ? ret = self.conn.search_s(self.base_lock,1,arg) lockid = '%s-%s' % (hostname, os.getpid() ) for res in ret : # Lock encore actif ? l = res[1]['lockid'][0] if l == lockid : continue # C'est locké par un autre process que le notre # il tourne encore ? try : if l.split('-')[0] == hostname and os.system('ps %s > /dev/null 2>&1' % l.split('-')[1] ) : # Il ne tourne plus self.remove_lock(res[0]) # delock continue except : pass r.append(res[0]) return r def lock(self,item,valeur) : """ Lock un item avec la valeur valeur, les items possibles peuvent être : aid $ chbre $ mail $ mailAlias $ cannonicalAlias $ mid $ macAddress $ host $ hostAlias $ ipHostNumber retourne le dn du lock """ valeur = valeur.encode('utf-8') lock_dn = '%s=%s,%s' % ( item, valeur, self.base_lock ) lockid = '%s-%s' % (hostname, os.getpid() ) modlist = ldap.modlist.addModlist({ 'objectClass' : 'lock' , 'lockid' : lockid , item : valeur } ) try : self.conn.add_s(lock_dn,modlist) except ldap.ALREADY_EXISTS : # Pas de chance, le lock est déja pris try : res = self.conn.search_s(lock_dn,2,'objectClass=lock')[0] l = res[1]['lockid'][0] except : l = '' if l != lockid : # C'est locké par un autre process que le notre # il tourne encore ? try : if l.split('-')[0] == hostname and os.system('ps %s > /dev/null 2>&1' % l.split('-')[1] ) : # Il ne tourne plus self.remove_lock(res[0]) # delock return self.lock(item,valeur) # relock except : pass raise EnvironmentError(u'Objet (%s=%s) locké, patienter.' % (item, valeur), l) return lock_dn def remove_lock(self,lockdn) : """ Destruction d'un lock Destruction de tous les locks si lockdn=*""" # Mettre des verifs ? if lockdn!='*' : self.conn.delete_s(lockdn) else : locks = self.list_locks() for l in locks : self.conn.delete_s(l[0]) def list_locks(self) : """ Liste les locks """ return self.conn.search_s(self.base_lock,1,'objectClass=lock') def services_to_restart(self,new=None,args=[]) : """ Si new = None retourne la liste des services à redémarrer Si new est founi et ne comence pas par - ajoute le service à la liste avec les arguments args (args doit être une liste). Si new est founi et ne comence par - supprime le service de la liste """ if new and new[0] == '-' : serv_dn = 'cn=%s,%s' % ( new[1:], self.base_services ) try : self.conn.delete_s(serv_dn) except : pass # Si n'existe pas => Erreur mais le résultat est la. return # Quels services sont déjà à redémarrer ? serv = {} # { service : [ arguments ] } for s in self.conn.search_s(self.base_services,1,'objectClass=service') : s=s[1] serv[s['cn'][0]] = s.get('args',[]) if not new : return serv serv_dn = 'cn=%s,%s' % ( new, self.base_services ) # Petite fonction à appliquer aux arguments def tr(arg) : return preattr(arg)[1] args=map(tr,args) if new in serv.keys() : new_args = [] 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 }) try : self.conn.modify_s(serv_dn,modlist) except ldap.TYPE_OR_VALUE_EXISTS : # Pas grave pass # else rien à faire else : modlist = ldap.modlist.addModlist({ 'objectClass' : 'service' , 'cn' : new , 'args' : args } ) try : self.conn.add_s(serv_dn,modlist) except ldap.ALREADY_EXISTS : # Existe déja => rien à faire pass def search(self,expression,mode='') : """ Recherche dans la base LDAP, expression est une chaîne : une expression : champ1=expr1 champ2=expr2 champ3!=expr3.... soit un seul terme, dans ce cas cherche sur les champs de auto_search_champs Si mode ='w' les instances crées seront en mode d'écriture """ if type(expression)==str : # Transformation de l'expression en utf-8 expression = unicode(expression,'iso-8859-15').encode('utf-8') elif type(expression)==unicode : expression = expression.encode('utf-8') else : raise TypeError(u'Chaine attendue') if not expression : return [] # Il faut un filtre par type d'objet de la base filtres = self.auto_search_champs.keys() result={'adherent' : [], 'machine' : [], 'club' : []} # Fonction utile def build_filtre(champ,expr,neg=0) : """ Retourne une chaine pour recherche dans la base LDAP du style (champ=expr) en adaptant les valeurs de expr au champ si neg = 1 : retourne le négatif : (!(champ=expr))""" el = '' if champ in [ 'host', 'hostAlias' ] : if expr[-1] == '*' : el = '(%s=%s)' % (champ, expr) elif expr.find('.')==-1 : el = '(%s=%s.*)' % ( champ, expr) else : el = '(%s=%s*)' % ( champ, expr) elif champ == 'macAddress' : # Formatage adresse mac try : el = '(macAddress=%s)' % format_mac(expr) except : return '' elif champ == 'paiement' and expr=='ok' : # Paiement donnant droit à une connexion maintenant ? # Deux cas : # - classique : résident sur le campus, il doit avoir payé pour # l'année en cours ou pour l'année précédente # si on est en septembre # - wifi : résident extérieur, mêmes conditions ou alors il a # adhéré depuis moins d'une semaine (gratuité) # Cas wifi elwifi = "(&(chbre=EXT)(|%s))" % ("(historique=%s * : inscription)"*4) % tuple(map(lambda i: time.strftime(date_format.split(" ")[0],time.localtime(time.time()-60*60*24*i)),[0,1,2,3])) if localtime()[1] == 9 : # Pour septembre paiement année précédente ok el = "(|(paiement=%s)(paiement=%s)%s)" % (int(ann_scol), int(ann_scol)-1, elwifi) else : el = "(|(paiement=%s)%s)" % (int(ann_scol), elwifi) else : # Cas général el = '(%s=%s)' % (champ, expr) if neg : el = '(!%s)' % el return el if expression.find('=')!=-1 : #### Recherche avec conditions explicites ## Construction des filtres # initialisation filtre={} filtre_cond={} # Stokage si filtre avec condition (ne comporte pas que des *) filtre_tout=1 # Passse à 0 si au moins une condition for i in filtres : filtre[i]='(&' filtre_cond[i] = 0 conds = expression.split('&') # Test de l'expression de recherche et classement par filtres for cond in conds : neg = 0 try : champ, expr = cond.strip().split('=') if champ[-1] == '!' : # Négation pour se champ champ = champ[:-1] neg = 1 except : raise ValueError(u'Syntaxe de recherche invalide.') # transformation de certains champs champ = self.trans.get(champ,champ) if expr=='' : expr='*' ok = 0 # Construction du filtre for i in filtres : if champ in self.auto_search_champs[i] + self.non_auto_search_champs[i] : filtre[i] += build_filtre(champ,expr,neg) ok = 1 if expr!='*' or neg : filtre_cond[i] = 1 filtre_tout=0 if not ok : raise ValueError(u'Champ de recherche inconnu (%s)'% champ ) ## Recherche avec chacun des filtres r={} # contiendra les réponses par filtre for i in filtres : if (filtre_tout and filtre!='(&' ) or filtre_cond[i] : # Filtre valide #filtre[i] += ')' filtre[i] += '(objectClass=%s))' % i r[i] = self.conn.search_s(self.base_dn,self.scope[i],filtre[i]) else : r[i] = None ## On a alors une liste de résultats ## r = { categorie1 : [ (result1), (result2), ...] , ... } # Traitement if not r['machine'] and not r['adherent'] and not r['club'] : # Pas de réponses return result elif not r['adherent'] and not r['club'] : # Il n'y avait seulement un filtre machine # => on retourne uniquement les machines trouvées for m in r['machine'] : result['machine'].append(machine(m,mode,self.conn) ) elif not r['machine'] : # Il n'y avait pas de filtre machine # => on retourne uniquement les adhérents if r['adherent'] : for a in r['adherent'] : result['adherent'].append(adherent(a,mode,self.conn) ) if r['club'] : for a in r['club'] : result['club'].append(club(a,mode,self.conn) ) else : # Il faut croiser les résultats machine et propriétaire # Traitement des machines mach_adh = [] # liste de dn d'adhérents et de clubs for res in r['machine'] : dn = string.join(res[0].split(',')[-4:],',') if dn[:3] != 'aid' and dn[:3] != 'cid' : continue if dn not in mach_adh : mach_adh.append(dn) # Croisement bons_dn = [] # liste des dn d'adhérents qui correspondent aux critères if r['adherent'] : for a in r['adherent'] : if a[0] in mach_adh and not a[0] in bons_dn : bons_dn.append(a[0]) result['adherent'].append(adherent(a,mode,self.conn) ) if r['club'] : for a in r['club'] : if a[0] in mach_adh and not a[0] in bons_dn : bons_dn.append(a[0]) result['club'].append(club(a,mode,self.conn) ) # Maintenant c'est au tour des bonnes machines bons_dn2 = [] for a in r['machine'] : dn = string.join(a[0].split(',')[-4:],',') if dn in bons_dn and not a[0] in bons_dn2 : bons_dn2.append(dn) result['machine'].append(machine(a,mode,self.conn) ) else : ### Recherche d'une chaine sur tous les champs conv = { 'machine' : machine , 'club' : club, 'adherent' : adherent } for i in filtres : cl = conv[i] # Construction du filtre filtre = '(&(|' for champ in self.auto_search_champs[i] : filtre += build_filtre(champ,expression) filtre+=')(objectClass=%s))' %i # Recherche for r in self.conn.search_s(self.base_dn,self.scope[i],filtre) : result[i].append( cl(r,mode,self.conn) ) return result ############################################################################# class base_classes_crans(crans_ldap) : """ Méthodes de base des classes machines, et base_proprietaire """ def __del__(self) : # Destruction des locks résiduels for lock in self._locks : try : self.remove_lock(lock) except : pass def id(self): """ Retourne la valeur de l'attribu caractéristique de la classe (aid,mid,cid)""" try : s = self.dn.split(',')[0].split('=') if s[0] == self.idn : return s[1] except : return '' def blacklist_actif(self) : """ Vérifie si l'instance courante est blacklistée. Retourne les sanctions en cours (liste) Retourne une liste vide si aucune sanction en cours """ bl_liste = self._data.get('blacklist',[]) if 'machine' in self._data['objectClass'] : # Il faut aussi regarder la blackliste du propriétaire p = self.proprietaire() bl_liste += p.blacklist() actifs = [] for sanction in bl_liste : s = sanction.split(',')[2] if not s in actifs and is_actif(sanction) : actifs.append(s) return actifs def blacklist(self,new=None) : """ Blacklistage de la ou de toutes la machines du propriétaire new est une liste de 4 termes : [ debut_sanction , fin_sanction, sanction, commentaire ] début et fin doivent être sous la forme donnée par date_format pour un début ou fin immédiate mettre now pour une fin indéterminée mettre '-' pour modifier une entrée donner un tuple de deux termes : ( index dans blacklist à modifier , nouvelle liste ) l'index est celui obtenu dans la liste retournée par blacklist() """ if not self._data.has_key('blacklist') : self._data['blacklist']=[] liste = list(self._data['blacklist']) if new==None : return map(decode,liste) if type(new)==tuple : # Modif index = new[0] new = new[1] if new=='' : liste.pop(index) self._set('blacklist',liste) return liste else : index = -1 if type(new)!=list or len(new)!=4 : raise TypeError # Verif que les dates sont OK if new[0] == 'now' : new[0] = time.strftime(date_format) else : try : time.strptime(new[0],date_format) except : raise ValueError(u'Date de début blacklist invalide') if new[1] == 'now' : new[1] = time.strftime(date_format) elif new[1]!='-' : try : time.strptime(new[1],date_format) except : raise ValueError(u'Date de fin blacklist invalide') new = ','.join(new) new = preattr(new)[1] if index!=-1 : liste[index] = new else : liste = liste + [ new ] self._set('blacklist',liste) return liste def restore(self) : """ Restore les données à l'état initial """ self._data = self._init_data.copy() self.modifs=[] def historique(self) : """ Retourne l'historique de l'objet """ return map(decode,self._data.get('historique',[])) def info(self,new=None) : """ Pour ajouter une remarque new doit être la chaîne représentant la remarque à ajouter Pour modifier new doit être une liste de la forme : [ index de la remarque à modifier , nouvelle remarque ] l'index est celui obtenu dans la liste retournée par info() """ if not self._data.has_key('info') : self._data['info']=[] liste = list(self._data['info']) if new==None : return map(decode,liste) if type(new)==list : # Modif index = new[0] l, new = preattr(new[1]) if not new : # Supression remarque liste.pop(index) else : # Modif remarque liste[index]=new elif type(new)==str : # Remarque supplémentaire l, new = preattr(new) if not new : # On ajoute pas de remarque vide return liste # Ajout à la liste liste = liste + [ new ] else : raise TypeError self._set('info',liste) return liste def _save(self) : """ Sauvegarde dans la base LDAP """ if not self.modifs : # Rien à faire return [] if not self.dn : # Enregistrement à placer en tête de base self.dn = self.base_dn # Construction de l'historique if not self._init_data : modif='inscription' else : modif=', '.join(self.modifs) timestamp = localtime() hist = "%s, %s" % ( time.strftime(date_format, timestamp), script_utilisateur ) # On loggue try: fd = file('%s/%s_%s_%s' % ("%s/logs" % config.cimetiere, str(self.__class__).split('.')[-1], time.strftime('%Y-%m-%d-%H:%M', timestamp), self.nom()),'wb') fd.write("%s\n" % self._data) fd.close() except: pass # Suffit-t-il d'ajouter un item au dernier élément de l'historique ? try: dern = self._data['historique'][-1].split(' : ',2) if dern[0] == hist : # Même date et même cableur if modif not in dern[1].split(', ') : # Qqch de plus de modifié self._data['historique'][-1] = self._data['historique'][-1] + ', ' +modif else : # Nouvelle entrée # NE PAS UTILISER L'OPERATEUR += ICI sinon self._init_data aussi modififié self._data['historique'] = self._data['historique'] + [ "%s : %s" % ( hist, modif ) ] except: # Nouvelle inscription self._data['historique'] = [ "%s : %s" % ( hist, modif ) ] if not self._init_data : ### Nouvel enregistrement # Génération du dn res = self.conn.search_s(self.base_dn,2,'objectClass=%s' % self._data['objectClass'][0],['']) vidn=1 vidns=[] # Liste des dn pris for r in res : # r=( dn, {} ) r = r[0].split(',')[0] if r[:4] != '%s=' % self.idn : continue vidns.append(int(r[4:])) # On prend le premier libre while vidn in vidns : vidn += 1 self.dn='%s=%s,%s' % (self.idn, vidn, self.dn) self._data[self.idn]= [ '%d' % vidn ] # Ecriture modlist = ldap.modlist.addModlist(self._data) self.conn.add_s(self.dn,modlist) else : ### Modification entrée if not self._modifiable : raise RuntimeError(u'Objet non modifiable') modlist = ldap.modlist.modifyModlist(self._init_data,self._data) try : self.conn.modify_s(self.dn,modlist) except ldap.TYPE_OR_VALUE_EXISTS , c : champ = c.args[0]['info'].split(':')[0] raise RuntimeError(u'Entrée en double dans le champ %s' % champ) ### Génération de la liste de services à redémarrer # Correspondance modif de la base -> service ayant besoin d'être redémarré # pour paiement et carte d'étudiant : traitement dans la classe adhérent # car il faut vérifier l'existance de machines annuaire_modif_service = { 'host' : [ 'dhcp', 'dns' ], 'ipHostNumber' : [ 'dhcp', 'dns', 'firewall' ], 'macAddress' : [ 'dhcp', 'dns', 'firewall' ], 'ipsec' : [ 'conf_wifi' ], 'hostAlias' : [ 'dns' ] , 'droits' : [ 'droits' ] , 'ports' : [ 'firewall-komaz' ] , 'blacklist' : [ 'blacklistes' ] , } serv = [] for m in self.modifs : for s in annuaire_modif_service.get(m,[]) : if s not in serv : serv.append(s) # Reinitialisation self._init_data = self._data.copy() for s in serv : self.services_to_restart(s) def _delete(self,dn,comment='') : """ Sauvegarde puis destruction du dn (et des sous-dn) fourni """ # Commentaires self.modifs.append('destruction (%s)' % comment) self._save() # Sauvegarde t = str(self.__class__).split('.')[-1] fd = open('%s/%s/%s_%s' % (config.cimetiere, t, time.strftime('%Y-%m-%d-%H:%M'), self.nom()),'wb') self.conn = None # Fermeture des connexions à la base sinon cPickle ne marchera pas self.rw_conn = None cPickle.dump(self,fd,2) fd.close() index = "%s, %s : %s %s # %s\n" % (time.strftime(date_format), script_utilisateur, t, self.Nom() , comment) self.connect() # Reconnexion à la base # Destruction data = self.conn.search_s(dn,2) data.reverse() # Necessaire pour détruire d'abord les sous-dn for r in data : self.conn.delete_s(r[0]) try : log = open(config.cimetiere + '/index','a') log.write(index) log.close() except : pass def _set(self,champ,val) : """ Met à jour les données de data et modifie modifs si besoin """ if not self._data.has_key(champ) \ or self._data.has_key(champ) and self._data[champ]!=val : self._data[champ]=val if champ not in self.modifs : self.modifs.append(champ) ############################################################################# class base_proprietaire(base_classes_crans) : """ Méthodes de bases pour les classes adherent et club """ def __init__(self,data=(),mode='',conn=None) : """ Si data est fourni initialise l'adhérent avec les valeurs données Format de data : tuple comme retourné par une recherche dans la base ldap: ( dn, { donnée }) Si mode='w' : le propriétaire pourra être modifié Attention, si mode ='w' mais si l'objet est déja locké il n'y a pas d'erreur vérifier l'obtetion du lock grace à la valeur de _modifiable (si =w c'est bon) Il est inutile de préciser le mode pour un nouveau proprietaire conn est une instance de la classe de connexion à la base LDAP """ self.conn = conn if not self.conn : self.connect() if type(data) != tuple : raise TypeError self.modifs=[] self._locks = [] if data : self.dn=data[0] if mode == 'w' : try : self._locks.append(self.lock(self.idn, self.id())) self._modifiable = 'w' except EnvironmentError , c: self._modifiable = 0 else : self._modifiable = 0 # Utile pour construire l'instruction LDAP de modif self._init_data = data[1].copy() self._data = data[1] else : # Propriétaire vide self.dn='' # Génération du reste au moment de l'écriture self._data={ 'objectClass' : [ self.objectClass ] } self._init_data={} self._modifiable = 'w' def machines(self) : """ Retourne les machines (instances) appartenant à la classe """ if self.id() : res = [] try : for r in self.conn.search_s('%s=%s,%s' % ( self.idn,self.id() , self.base_dn ),1,'objectClass=machine') : res.append(machine(r, self._modifiable,self.conn) ) return res except : return [] else : return [] def paiement(self,action=None) : """ Action est un entier représentant une année si positif ajoute l'année à la liste si négatif le supprime """ return self._an('paiement',action) def prise(self) : """ Retourne la prise associée ou au club Si chbre est dans un bat sans correspondance chbre <-> prise retourne '' Si prise non trouvée retourne inconnue """ if self.chbre()[0].lower() in annuaires.chbre_prises.keys() : try : return annuaires.chbre_prises[self.chbre()[0].lower()][self.chbre()[1:]] except : return 'inconnue' else : return '' def delete(self,comment='') : """Destruction du proprietaire""" for m in self.machines() : # Destruction machines m.delete(comment) self._delete(self.dn,comment) try : if self.compte() : args = self._data['uid'][0] + ',' args+= self._data['homeDirectory'][0] self.services_to_restart('del_user',[ args ] ) except : # Si ne peux avoir de compte pass def save(self) : """ Enregistre l'adhérent ou le club courant dans la base LDAP Ajoute le mail de bienvenue à la liste des services à redémarrer Retourne une chaîne indiquant les opération effectuées. """ # Note : un peu trop de fonctions pour un club mais ce n'est pas génant ret ='' serv = [] if self._init_data : nouveau =0 # Reconfiguration switch si changement de chambre et si machine fixe if 'chbre' in self.modifs : for m in self.machines() : if not m.ipsec() : self.services_to_restart('switch',[self._data['chbre'][0]]) self.services_to_restart('switch',[self._init_data.get('chbre','')[0]]) break else : nouveau = 1 # Enregistrement self._save() # Message de sortie if nouveau : ret += coul(u"%s inscrit avec succès." % self.Nom(), 'vert') if self.idn !='cid' : # Mail de bienvenue self.services_to_restart('mail_bienvenue',[self.mail().encode('iso-8859-15')]) else : ret += coul(u"Modification %s effectuée avec succès." % self.Nom(), 'vert') # Faut-il redémarrer plus de services que ceux traités dans _save ? if 'carteEtudiant+%s' % ann_scol in self.modifs \ or 'carteEtudiant-%s' % ann_scol in self.modifs \ and self.machines() and 'bl_carte_etudiant' not in serv : self.services_to_restart('bl_carte_etudiant') if 'paiement+%s' % ann_scol in self.modifs \ or 'paiement-%s' % ann_scol in self.modifs : for m in self.machines() : if m.ipsec() and not 'conf_wifi' in serv : self.services_to_restart('conf_wifi') elif not self.chbre() in serv : self.services_to_restart('switch',[self.chbre()]) if self.machines() : for s in ['dhcp', 'dns', 'firewall' ] : if s not in serv : serv.append(s) # Vérification si changement de bât, ce qui obligerai un changement d'IP if 'chbre' in self.modifs and self.chbre()!='????' : # Verif si machines avec bonnes ip err = 0 for m in self.machines() : if m.ipsec() : # Machine Wifi continue # Machine fixe ip = m.ip() try : # Tentative de changement d'IP de la machine m.ip(ip) except ValueError: # IP invalide, on la change ret += "\nChangement d'IP machine %s : " % m.nom() try : ret += "%s -> %s" % ( ip, m.ip('') ) r = m.save() except c : ret += coul(u'ERREUR : %s' % c.args[0], rouge) err = 1 if err : ret += '\nEssayer de corriger les erreurs machines en éditant celles-ci.\n' # Faut-il créer un compte sur zamok ? if 'compte' in self.modifs : ret += u'\nUn compte a été créé :\n login : %s\n' % self.compte() args = self._data['homeDirectory'][0] + ',' args+= self._data['uidNumber'][0] + ',' args+= self._data['uid'][0] self.services_to_restart('home',[ args ]) r = prompt("Attribuer tout de suite un mot de passe ? [O/N]","O") if r=='O' or r=='o' : chgpass(self.dn) else : ret += coul(u' Il faudra penser à attribuer un mot de passe\n','jaune') # Remise à zero self.modifs=[] for s in serv : self.services_to_restart(s) return ret def _an(self,champ,action) : """ Champ est un champ contenant une liste d'entiers Action est un entier représentant une année si positif ajoute l'année à la liste si négatif le supprime """ if not self._data.has_key(champ) : trans=[] else : # On va travailler sur une liste d'entiers trans = map(int,self._data[champ]) if action==None : return trans if type(action)!=int : raise TypeError if action>0 and action not in trans : trans.append(action) act = '%s+%d' % (champ,action) if act not in self.modifs : self.modifs.append(act) elif action<0 and -action in trans : trans.remove(-action) act = '%s%s' % (champ,action) if act not in self.modifs : self.modifs.append(act) trans.sort() new=[] for an in trans : new.append('%d' % an ) self._data[champ] = new return self._data[champ] ############################################################################# class adherent(base_proprietaire) : """ Classe de définition d'un adhérent """ objectClass = 'adherent' idn = 'aid' ### Méthodes Nom utilisée lors de l'affichage des propriétés ### (commune avec les classes crans et club) def Nom(self) : """ Retourne prenom nom """ return "%s %s" % ( self.prenom() , self.nom() ) def nom(self,new=None) : return self.__nom_prenom('nom',new) def prenom(self,new=None) : return self.__nom_prenom('prenom',new) def __nom_prenom(self,champ,new) : if new==None : return decode(self._data.get(champ,[''])[0]) l, new = preattr(new) new = new.capitalize() for c in new[:] : if c not in (string.letters + '- ' + preattr(accents)[1] ) : raise ValueError(u"Seuls les caractères alphabétiques, l'espace et le - sont permis dans %s." % champ.replace(u'e',u'é') ) if l<2 : raise ValueError(u"%s trop court." % champ.capitalize().replace(u'e',u'é')) if new[0] not in string.letters : raise ValueError(u"Le premier caractère du %s doit être une lettre" % champ.replace(u'e',u'é') ) self._set(champ,[new]) return new def tel(self,new=None) : if new==None : return self._data.get('tel',[''])[0] if new != 'inconnu' : l, new = preattr(new) if not new.isdigit() or l<6 or l>15 : raise ValueError(u"Numéro de téléphone incorrect (il doit comporter uniquement des chiffres).") self._set('tel',[new]) return new def chbre(self,new=None) : """ Défini la chambre d'un adhérent, EXT pour personne extérieure au campus """ if new==None : return decode(self._data.get('chbre',[''])[0]) l, new = preattr(new) if l==0 : raise ValueError(u"Chambre incorrecte.") if new.upper() == 'EXT' : # N'est pas ou plus sur le campus # Machine fixe ? # for m in self.machines() : # if not m.ipsec() : # raise ValueError(u'Un adhérent en dehors du campus ne doit pas avoir de machine fixe.') self._set('chbre',['EXT']) return 'EXT' elif new.upper() == '????' : # On ne sait pas ou est l'adhérent self._set('chbre',['????']) return '????' new = new.capitalize() bat = new[0].lower() if bat in annuaires.chbre_prises.keys() : # On a la liste des chambres chbres = annuaires.chbre_prises[bat].keys() if new[1:] not in chbres or len(new)<4 or not new[1:4].isdigit() : chbres.sort() aide = u"Chambre inconnue dans le batiment, les chambres valides sont :" a=0 for c in chbres : if len(c)>=3 and not c[:3].isdigit() : # C'est un local club continue if int(a/14)>int((a-1)/14) : aide += '\n ' if c[0]!='X' : aide += c.ljust(5) a=a+1 aide += u'\n' aide += u" " + annuaires.aide.get(bat,'') raise ValueError(aide) else : raise ValueError(u'Bâtiment inconnu.') # La chambre est valide, est-elle déja occupée ? test = self.exist('chbre=%s' % new) if test : search = test[0].split(',')[0] if search.split('=')[0]!='aid' : raise ValueError(u'Chambre déjà occupée.') adh = self.search(search,self._modifiable)['adherent'] if len(adh) != 1 : raise ValueError(u'Chambre déjà occupée.') else : raise ValueError(u'Chambre déjà occupée.',adh[0]) # Lock de la chambre self._locks.append(self.lock('chbre',new)) self._set('chbre',[new]) return new def adresse(self,new=None): """ Défini l'adresse pour les personnes extérieures (dont la chambre = EXT) L'adresse est une liste de 4 éléments : numero, rue, code postal, ville """ if new==None : if self.chbre() != 'EXT' : # Personne sur le campus return '' else : return map(decode,self._data.get('postalAddress', ['','','',''])[:4]) if type(new)!=list and len(new)!=4 : raise TypeError l_min = [ 2, 0, 5, 2 ] for i in range(0,4) : l, new[i] = preattr(new[i]) if l < l_min[i] : raise ValueError(u"Adresse incorrecte.") # Correction si necessaire if not new[1] : new[1] = ' ' self._set('postalAddress',new) return new def mail(self,new=None) : if new==None : return decode(self._data.get('mail',[''])[0]) l, new = preattr(new) new = new.lower() #Emplacement de l'@ a=new.find('@') #Emplacement du . final b=new.rfind('.') # Les tests : # exactement un @ # 2 ou 3 caractères après le . final # @ pas en premier ni juste avant le dernier . if new.count('@')!=1 \ or not ( l-b==4 or l-b==3) \ or a<1 or b-a<2 : raise ValueError(u"Adresse mail incorrecte.") # Pas de caractèrs bizarres for l in new[:]: if not l in (string.lowercase + string.digits + '-_.@') : raise ValueError(u"Caractère interdits dans l'adresse mail (%s)." % l) # Pour les vicieux if sre.match('.*crans.(org|ens-cachan.fr)$',new) : raise ValueError(u"Adresse mail @crans interdite ici") self._set('mail',[new]) # Il ne doit pas y avoir de compte try: self._data['objectClass'] = [ 'adherent' ] self._data.pop('uid') self._data.pop('cn') self._data.pop('shadowLastChange') self._data.pop('shadowMax') self._data.pop('shadowWarning') self._data.pop('loginShell') self._data.pop('userPassword') self._data.pop('uidNumber') self._data.pop('gidNumber') self._data.pop('homeDirectory') self._data.pop('gecos') self._data.pop('droits') except : return new def etudes(self,index_or_new) : """ Retourne l'un des 3 champs études (selon index_or_new si entier) """ if type(index_or_new)==int : if self._data.has_key('etudes'): return decode(self._data['etudes'][index_or_new]) else: return '' if type(index_or_new)!=list : raise TypeError if not self._data.has_key('etudes') : self._data['etudes']=['','',''] # Pas grand chose à faire à part vérifier que ce sont bien des chaines if len(index_or_new)!=3 : raise ValueError(u"Format études non valides.") new = ['','',''] for i in range(0,3) : n = preattr(index_or_new[i])[1] if n in new or n=='' : raise ValueError(u"Etudes non valides.") new[i] = n self._set('etudes',new) return new def carteEtudiant(self,action=None) : """ Action est un entier représentant une année si positif ajoute l'année à la liste si négatif le supprime """ return self._an('carteEtudiant',action) def compte(self,login=None,uidNumber=0,hash_pass='',shell=config.login_shell) : """ Création d'un compte à un adhérent (la création se fait après l'écriture dans la base par la méthode save) Si login = None, retourne le compte de l'adhérent """ if not login : if self.mail().find('@') != -1 : return '' else : return self.mail() # Supression des accents et espaces login = strip_accents(login) l, login = preattr(login) login = login.lower() if 'posixAccount' in self._data['objectClass'] : if login != self._data['uid'] : # A déja un compte raise ValueError(u"L'adhérent à déjà un compte. Login : %s" % self._data['uid'][0]) else : return login for c in login[:]: if not c in (string.letters + '-') : raise ValueError(u"Seuls les caractères alphabétiques non accentués et le - sont autorisés dans le login.") if l<2 : raise ValueError(u"Login trop court.") if l>config.maxlen_login : raise ValueError(u"Login trop long.") if login[0]=='-' : raise ValueError(u"- interdit en première position.") if mailexist(login) : raise ValueError(u"Login existant ou correspondant à un alias mail.",1) home = '/home/' + login if os.path.exists(home) : raise ValueError(u'Création du compte impossible : home existant',1) # Lock du mail self._locks.append(self.lock('mail',login)) self._data['mail']= [ login ] if not 'compte' in self.modifs : self.modifs.append('compte') # Création de l'alias cannonique if self.nom() and self.prenom() : a = '%s.%s' % (self.prenom().capitalize(), self.nom().capitalize()) self.cannonical_alias(a) self._data['objectClass'] = [ 'adherent', 'posixAccount', 'shadowAccount' ] self._data['uid'] = [ login ] self._data['cn'] = [ preattr(self.Nom())[1] ] self._data['shadowLastChange'] = [ '12632' ] self._data['shadowMax'] = [ '99999'] self._data['shadowWarning'] = [ '7' ] self._data['loginShell' ] = [ shell ] if hash_pass : self._data['userPassword'] = [ hash_pass ] if uidNumber : if self.exist('(uidNumber=%s)' % uidNumber) : raise ValueError(u'uidNumber pris') else : uidNumber = 1000 while self.exist('(uidNumber=%s)' % uidNumber) : uidNumber += 1 try : self._locks.append(self.lock('uidNumber',str(uidNumber))) except : # Quelqu'un nous a piqué l'uid que l'on venait de choisir ! return self.compte(login,uidNumber,hash_pass,shell) self._data['uidNumber']= [ str(uidNumber) ] self._data['gidNumber']=[ str(config.gid) ] self._data['homeDirectory']=[ preattr(home)[1] ] self._data['gecos'] = [ preattr(self.Nom())[1] + ',,,' ] return decode(login) def chsh(self,new=None) : """ Retourne ou change le shell de l'adhérent """ if new == None : try : return decode(self._data.get('loginShell',[''])[0]) except : return '' else : new = preattr(new)[1] self._set('loginShell',[new]) return new def cannonical_alias(self,new=None) : """ Retourne ou défini l'alias canonique""" if new == None : try : return decode(self._data['cannonicalAlias'][0]) except : return '' else : a = strip_accents(new) a = preattr(a)[1] if not mailexist(a) : # Attribution de l'alias, sinon on passe # Lock de cannonicalAlias self._locks.append(self.lock('cannonicalAlias',a)) # Attribution self._set('cannonicalAlias',[a]) return a def alias(self,new=None) : """ Création ou visualisation des alias mail Même sytème d'argument que la méthode info. """ if not self._data.has_key('mailAlias') : self._data['mailAlias']=[] liste = list(self._data['mailAlias']) if new==None : return map(decode,liste) if type(new)==list : # Modif index = new[0] new = new[1] if new=='' : # Supression alias liste.pop(index) self._set('mailAlias',liste) return liste else : new = new.replace('@crans.org','') index=-1 # Tests l, new = preattr(new) new = new.lower() if l<2 : raise ValueError(u"Alias trop court.") for c in new[:]: if not c in (string.letters + string.digits + '-_.') : raise ValueError(u"Alias : seuls les caractères alphanumériques, le -, le _ et le . sont autorisés." ) if new[0] not in string.letters : raise ValueError(u"Le premier caractère de l'alias doit être alphabétique.") if mailexist(new) : raise ValueError(u"Alias existant ou correspondand à un compte.") if index!=-1 : liste[index]=new else : liste = liste + [ new ] # Lock de mailAlias self._locks.append(self.lock('mailAlias',new)) self._set('mailAlias',liste) return liste def droits(self,droits=None) : """ droits est la liste des droits à donner à l'utilisateur """ if droits==None : return map(decode,self._data.get('droits',[])) if not isadm : raise EnvironmentError(u'Il faut être administrateur pour effectuer cette opération.') if type(droits)!=list : raise TypeError(u'Une liste est attendue') new = [] for droit in droits : if droit == '' : continue droit = unicode(droit.strip(),'iso-8859-15') if droit not in droits_possibles : raise ValueError(u'Droit %s incorrect' % droit) new.append(droit.encode('utf-8')) if new != self._data.get('droits',[]) : self._set('droits',new) return new class club(base_proprietaire) : """ Classe de définition d'un club """ idn = 'cid' objectClass = 'club' def Nom(self,new=None) : if new==None : return decode(self._data.get('nom',[''])[0]) l, new = preattr(new) new = new.capitalize() if l<2 : raise ValueError(u"Nom trop court.") self._set('nom',[new]) return new def carteEtudiant(self,pd=None) : return [ ann_scol ] def responsable(self,adher=None) : """ Responsable du club, adher doit être une instance de la classe adhérent """ if adher==None : aid = decode(self._data.get('responsable',[''])[0]) if aid : return self.search('aid=%s' % aid)['adherent'][0] else : return '' if adher.__class__ != adherent : raise ValueError if not adher.id() : raise AttributeError(u'Adhérent invalide') self._set('responsable',[adher.id()]) return adher def chbre(self,new=None) : """ Défini le local du club new doit être une des clefs de l'annuaire locaux_clubs""" if new==None : return decode(self._data.get('chbre',[''])[0]) annu = annuaires.locaux_clubs() if new not in annu.keys() : raise ValueError(u'Local invalide',annu) self._set('chbre',[new]) return new def local(self) : """ Retourne le local à partir de la chambre enregistrée et de la conversion avec l'annuaire locaux_clubs """ annu = annuaires.locaux_clubs() return decode(annu.get(self.chbre(),'')) class machine(base_classes_crans) : """ Classe de définition d'une machine """ idn = 'mid' def __init__(self,parent_or_tuple,typ='fixe',conn=None) : """ parent_or_tuple est : *soit une instance d'une classe pouvant posséder une machine (adherent, club ou crans), la nouvelle machine lui sera alors associé. *soit directement le tuple définissant une machine (tel que retourné par les fonctions de recherche ldap) typ permet de définir le type de machine : wifi ou fixe, pris en compte uniquement pour une nouvelle machine (parent donné) Pour édition d'une machine, typ devra être égal à 'w' Attention, si typ ='w' mais si l'objet est déja locké il n'y a pas d'erreur vérifier l'obtetion du lock grace à la valeur de _modifiable (si =w c'est bon) conn est une instance de la classe de connexion à la base LDAP """ self.conn = conn if not self.conn : self.connect() self.modifs=[] self._locks=[] t = parent_or_tuple.__class__ if t == tuple : # Initialisation avec données fournies self.dn = parent_or_tuple[0] if typ == 'w' : try : self._locks.append(self.lock(self.idn, self.id())) self._modifiable = 'w' except EnvironmentError , c: self._modifiable = 0 else : self._modifiable = 0 self._init_data = parent_or_tuple[1].copy() # Utile pour construire l'instruction LDAP de modif self._data = parent_or_tuple[1] # Type de machine if self._init_data.has_key('ipsec') : self.__typ = 'wifi' elif self._init_data.has_key('puissance') : self.__typ = 'borne' else : self.__typ = 'fixe' # Propriéraire inconnu mais ce n'est pas grave self.__proprietaire = None elif t == adherent or t == club or t == crans and typ in [ 'fixe' , 'wifi' , 'borne' ] : # Machine vide self.__proprietaire = parent_or_tuple self.dn = parent_or_tuple.dn self._data={ 'objectClass' : [ 'machine' ] } if typ == 'borne' : # Valeurs par défaut self._data['canal'] = '11' self._data['puissance'] = '33' self._init_data={} self.__typ = typ self._modifiable = 'w' chbre = self.__proprietaire.chbre() # if chbre == 'EXT' and typ == 'fixe' : # raise ValueError(u'Il faut une chambre pour pouvoir posséder une machine fixe') if chbre == '????' : raise ValueError(u'ERREUR : la chambre du propriétaire est inconnue') if typ == 'wifi' : # Génération de la clef IPsec self.ipsec(1) else : raise TypeError(u'Arguments invalides') def Nom(self) : """ Retourne le nom de la machine """ return self.nom() def mac(self,mac=None,multi_ok=0) : """ Défini ou retourne l'adresse mac de la machine Adresse valide si : 12 caractères hexa avec - ou : comme séparateurs non nulle Stoque l'adresse sous la forme xx:xx:xx:xx:xx:xx Si multi_ok = 1 permet d'avoir plusieur fois la même mac sur le réseau """ if mac==None : return decode(self._data.get('macAddress',[''])[0]) mac = format_mac(mac) # La mac serait-elle déjà connue ? if not multi_ok and self.exist('macAddress=%s' % mac) : raise ValueError(u'Mac déja utilisée sur le réseau.',1) # Lock de la mac self._locks.append(self.lock('macAddress',mac)) self._set('macAddress',[mac]) return mac def __host_alias(self,champ,new) : """ Vérification de la validité d'un nom de machine """ # Supression des accents new = strip_accents(unicode(new,'iso-8859-15')) l, new = preattr(new) new = new.lower() l = len(new.split('.')[0]) if l<2 : raise ValueError(u"%s trop court." % champ.capitalize()) if self.proprietaire().__class__ != crans : new = new.split('.',1)[0] for c in new[:]: if not c in (string.letters + string.digits + '-') : raise ValueError(u"Seuls les caractères alphabétiques minuscules et les - sont autorisés pour %s" % champ) if l>17 : raise ValueError(u"%s trop long." % champ.capitalize()) if not new[0].isalnum() : raise ValueError(u"Le premier caractère du champ %s doit être alphanumérique" % champ) # Ajout du domaine si necessaire if new.find('.')==-1 : try : new += '.' + config.domains[self.__typ] except : raise RuntimeError(u"%s : domaine non trouvé" % champ.capitalize() ) # Pas déja pris ? if self.exist('(|(host=%s)(hostAlias=%s))' % (new,new)) : raise ValueError(u"%s : nom déjà pris" % champ.capitalize()) # Lock host self._locks.append(self.lock('host',new)) return new def nom(self,new=None) : """ Défini ou retourne le nom de machine. Un nom de machine valide ne comporte que des caractères alphabétiques minuscules et le - """ if new==None : return decode(self._data.get('host',[''])[0]) new = self.__host_alias('nom de machine',new) self._set('host',[new]) return new.split('.')[0] def alias(self,new=None) : """ Création ou visualisation des alias d'une machine. Même sytème d'argument que la méthode info. """ if not self._data.has_key('hostAlias') : self._data['hostAlias']=[] liste = list(self._data['hostAlias']) if new==None : return map(decode,liste) if type(new)==list : # Modif index = new[0] new = new[1] if new=='' : liste.pop(index) self._set('hostAlias',liste) return liste else : index=-1 # Test si valide new = self.__host_alias('alias',new) if index!=-1 : liste[index] = new else : liste = liste + [ new ] self._set('hostAlias',liste) return liste def ip(self,ip=None) : """ Défini ou retourne l'IP de la machine. Les IP sont stoquées sous forme xxx.xxx.xxx.xxx et doivent être fournies ainsi. Si l'IP n'est pas définie retourne . Si ip= attribue la permière IP libre du sous réseau. """ if ip==None : if self._data.has_key('ipHostNumber') : return decode(self._data['ipHostNumber'][0]) elif self.proprietaire().__class__ == crans and self.__typ != 'borne': return '' else : return '' l, ip = preattr(ip) # Dans quel réseau la machine doit-elle être placée ? if self.__typ == 'wifi' : net = config.NETs['wifi'] elif self.__typ == 'borne' : net = config.NETs['bornes'] elif self.proprietaire().__class__ == crans : net = [ '0.0.0.0/0' ] else : try : net = config.NETs[self.proprietaire().chbre()[0].lower()] except : raise RuntimeError(u'Impossible de trouver le réseau où placer la machine.') if ip=='' : pool_ip = [] # Pool d'IP à tester for ne in net : ip = ne.split('/')[0] ip = ip.split('.') n=[] for i in ip : n.append(int(i)) while 1 : if n[3] < 255 : n[3] += 1 else : n[2] += 1 n[3] = 0 if n[2]==255 : break ip = "%d.%d.%d.%d" % tuple(n) if not iptools.AddrInNet(ip,ne): # On est allé trop loin break pool_ip.append(ip) # On va prendre choisir une IP au hasard dans le pool des IP dispo ip = '' random.shuffle(pool_ip) while len(pool_ip) > 0: ip = pool_ip.pop() # On choisit une IP if not self.exist('ipHostNumber=%s' % ip) : # On a trouvé la première ip libre break if ip =='' : raise RuntimeError(u'Plus d\'IP libres dans %s.' % string.join(net,' et ') ) else : # L'ip est elle dans le bon sous réseau ? # (accessoirement teste si l'IP est valide et ne correspond pas # à l'adresse de broadcast ou de réseau) if not iptools.AddrInNet(ip,net) : raise ValueError(u'IP invalide ou en dehors du sous réseau alloué.',1) # L'ip est-elle déja allouée ? if self.exist('ipHostNumber=%s' % ip) : raise ValueError(u'IP déjà prise.') # Lock ip self._locks.append(self.lock('ipHostNumber',ip)) self._set('ipHostNumber',[ip]) return ip def proprietaire(self) : """ retroune le propriétaire de la machine (classe adherent, club ou crans) """ if not self.__proprietaire : res = self.conn.search_s(','.join(self.dn.split(',')[1:]),0) if 'adherent' in res[0][1]['objectClass'] : self.__proprietaire = adherent(res[0],self._modifiable,self.conn) elif 'club' in res[0][1]['objectClass'] : self.__proprietaire = club(res[0],self._modifiable,self.conn) else : self.__proprietaire = crans() return self.__proprietaire def ipsec(self,clef=0) : """ Génération (clef=1) ou affichage de la clef IPsec de la machine Si clef!=1 : prend la clef fournie. """ if self.__typ != 'wifi' : return None if not clef : return decode(self._data.get('ipsec',[''])[0]) if clef == 1 : # Génération clef = '' for i in range(12) : clef += random.choice(string.lowercase + string.digits) self._set('ipsec',[clef]) return clef def canal(self,new=None) : """ Attribution ou visualisation du canal d'une borne wifi """ if self.__typ != 'borne' : return None if not new : return self._data.get('canal',[''])[0] try : new = int(new) if new < 0 or new > 14 : raise except : raise ValueError(u'Canal invalide : doit être entre 0 et 14') self._set('canal',[str(new)]) return new def puissance(self,new=None) : """ Attribution ou visualisation de la puissance d'une borne wifi """ if self.__typ != 'borne' : return None if not new : return self._data.get('puissance',[''])[0] try : new = int(new) if new < 0 or new > 99 : raise except : raise ValueError(u'Puissance invalide : doit être entre 0 et 99') self._set('puissance',[str(new)]) return new def save(self) : """ Enregistre la machine courante dans la base LDAP Retourne une chaîne indiquant les opération effectuées. """ if self.proprietaire() == crans and not isadm : raise EnvironmentError(u'Il faut être administrateur pour effectuer cette opération.') ret ='' # Enregistrement self._save() # Clef IPsec if 'ipsec' in self.modifs : ret += coul(u'Clef IPsec de la machine : %s\n' % self.ipsec(),'cyan') elif 'macAddress' in self.modifs and self.__typ == 'fixe' : # Reconfiguration switch necessaire self.services_to_restart('switch',[self.proprietaire().chbre()]) if 'canal' in self.modifs or 'puissance' in self.modifs : self.services_to_restart('bornes_wifi',[self.nom()]) if self.__typ == 'wifi' and ( 'ipHostNumber' in self.modifs or 'host' in self.modifs ) : # Reconfiguration clients wifi necessaire self.services_to_restart('conf_wifi') # Regénération blackliste nécessaire ? bl = self.blacklist_actif() if bl and ( 'ipHostNumber' in self.modifs or 'host' in self.modifs ): for s in bl : self.services_to_restart(s,[ self.ip() ] ) # Remise à zéro self.modifs=[] # Message de sortie ret += coul(u"Machine %s enregistrée avec succès." % self._data['host'][0],'vert') return ret def delete(self,comment='') : """ Destruction de la machines """ if self.proprietaire() == crans and not isadm : raise EnvironmentError(u'Il faut être administrateur pour effectuer cette opération.') if self.__typ == 'wifi' : self.services_to_restart('conf_wifi') elif self.__typ == 'fixe' : self.services_to_restart('switch',[ self.proprietaire().chbre() ]) self.proprio = self.__proprietaire.Nom() # On met dans un coin le nom du proprio self.__proprietaire = None # On oublie le propriétaire self._delete(self.dn,comment) self.services_to_restart('dhcp') self.services_to_restart('dns') self.services_to_restart('firewall',[ self.nom() ]) def portTCPin(self,ports=None) : """ Ports TCP ouverts depuis l'extérieur pour la machine """ return self.__port(ports,'portTCPin') def portTCPout(self,ports=None) : """ Ports TCP ouverts vers l'extérieur pour la machine """ return self.__port(ports,'portTCPout') def portUDPin(self,ports=None) : """ Ports UDP ouverts vers l'extérieur pour la machine """ return self.__port(ports,'portUDPin') def portUDPout(self,ports=None) : """ Ports UDP ouverts vers l'extérieur pour la machine """ return self.__port(ports,'portUDPout') def __port(self,ports,champ): if ports == None : return self._data.get(champ,[''])[0] ports = preattr(ports)[1] if ports and self._data.get(champ)!=ports : self._data[champ] = [ ports ] if 'ports' not in self.modifs : self.modifs.append('ports') elif self._data.has_key(champ) : self._data.pop(champ) if 'ports' not in self.modifs : self.modifs.append('ports') class crans(crans_ldap) : """ Classe définissant l'assoce (pour affichage de ses machines) """ idn = '' def __init__(s,conn=None): s.conn = conn if not s.conn : s.connect() s.dn = s.base_dn def id(s) : return '' def Nom(s) : return u"Crans" def chbre(s) : return u"CRA" def blacklist(s) : return [] def paiement(s) : return [ ann_scol ] def carteEtudiant(s) : return [ ann_scol ] def blacklist_actif(s) : return [] def machines(s) : res = s.conn.search_s(s.dn,1,'objectClass=machine') m = [] for r in res : m.append(machine(r,'',s.conn)) return m if __name__ == '__main__' : import sys if 'lock' in sys.argv : db = crans_ldap() print db.list_locks() if 'purgelock' in sys.argv : db = crans_ldap() db.remove_lock('*')