scripts/gestion/ldap_crans.py
pauget 3ca42aa431 Ne pas tout mettre dans les services redmarrer sinon ca bug.
darcs-hash:20041017113904-41617-cc38e7afc549b920734a38818a38310f17ce6c87.gz
2004-10-17 13:39:04 +02:00

2065 lines
61 KiB
Python
Executable file

#! /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_c = ','.join(new)
new_c = preattr(new_c)[1]
if index!=-1 :
liste[index] = new_c
else :
liste = liste + [ new_c ]
if self._data['blacklist'] != liste :
self._data['blacklist']=liste
self.modifs.append('blacklist_' + new[2])
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 :
if "chbre" in self.modifs:
modif=filter(lambda x: x!="chbre", self.modifs)
modif.append("chbre %s -> %s" % (self._init_data["chbre"][0],
self._data["chbre"][0]))
else:
modif = self.modifs
modif=', '.join(modif)
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' ] ,
}
serv = []
for m in self.modifs :
if sre.match('blacklist_*',m) :
serv.append(m)
else :
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('<automatique>') )
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 <automatique>.
Si ip=<automatique> 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 '<automatique>'
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=='<automatique>' :
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('*')