scripts/gestion/ldap_crans.py
glondu 5b075f28bf Historique plus souple.
Dans base_classes_crans, modifs devient un dictionnaire.
Franois, c'est  toi.

darcs-hash:20060120023038-68412-8a5b577ca9877e7c46c53b00a0b4a2773d6d2b58.gz
2006-01-20 03:30:38 +01:00

2695 lines
99 KiB
Python
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#! /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
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 !\n','jaune'))
sleep(2)
ldap_password = ''
ldap_auth_dn = ''
date_format='%d/%m/%Y %H:%M'
hostname = gethostname().split(".")[0]
# Uri pour lecture écriture
if hostname == 'egon' and not '/usr/scripts/gestion/' in sys.argv[0]:
# pour les tests, les séminaires... sur egon
uri = 'ldapi://%2fvar%2frun%2fldapi/'
ldap_auth_dn='cn=admin,dc=crans,dc=org'
ldap_password='75bdb64f32'
else:
uri = 'ldap://ldap.adm.crans.org/'
# Uri pour les instances de crans_ldap faisant de la lecture seule
if os.path.exists('/var/run/ldapi') :
ro_uri = 'ldapi://%2fvar%2frun%2fldapi/'
else :
ro_uri = uri
smtpserv = "rouge.crans.org"
random.seed() # On initialise le générateur aléatoire
##################################################################################
### Items de la blackliste
blacklist_items = { u'bloq' : u'Bloquage total de tous les services' ,
u'virus' : u'Bloquage sur squid',
u'upload' : u'Bloquage total de l\'accès à l\'extérieur',
u'autodisc' : u'Autodisconnect pour upload',
u'warez' : u'Bloquage sur squid',
u'p2p' : u'Bloquage total de l\'accès à l\'extérieur'}
##################################################################################
### Droits possibles
droits_possibles = [ u'Nounou', u'Apprenti', u'Modérateur', u'Câbleur', u'Déconnecteur', u'WebRadio' , u'Imprimeur', u'MultiMachines', u'Contrôleur' ]
##################################################################################
### 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 à partir 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):
""" Supression des accents de la chaîne fournie """
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 une adresse 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] in [250, 252]:
return True
else:
return False
def preattr(val):
"""
val est :
* un entier
* une chaîne
* une liste avec un seul entier ou une seule cha-Aîne-b
Retourne [ len(str(val).strip), str(val).strip en utf-8 ]
"""
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 fournie (cha-Aîne-b
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.strip().replace("-",":")
if mac.count(":") == 5:
# On a une adresse de la forme 0:01:02:18:d1:90
# On va compléter s'il manque des 0
mac = ":".join(map(lambda x: x.replace(' ', '0'),
map(lambda x: "%02s" % x, mac.split(":"))))
mac= mac.replace(':','').lower()
if len(mac)!=12:
raise ValueError(u'Longueur de l\'adresse mac incorrecte.')
for c in mac[:]:
if c not in string.hexdigits:
raise ValueError(u"Caractère interdit '%s' dans l\'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 service:
""" Définit un service à redémarrer """
def __init__(self,nom,args=[],start=[]):
"""
Nom du service
Liste des arguments
Liste d'horaires de démarrages
"""
self.nom=nom
self.args=args
self.start=map(int,start)
def __str__(self):
return "%s(%s) à partir du %s" % (self.nom, \
','.join(self.args), \
' et '.join(map(lambda t:time.strftime(date_format,time.gmtime(t)), self.start)))
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 transformation 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', 'uid', 'info', 'solde' , 'controle' , 'contourneGreylist' ], \
'machine': [ 'mid' , 'ipsec', 'historique', 'blacklist' , 'puissance', 'canal', 'portTCPin', 'portTCPout', 'portUDPin', 'portUDPout', 'prise' , 'info', 'exempt' ] ,
'club': [ 'cid' , 'responsable', 'paiement', 'historique', 'blacklist', 'mailAlias', 'info', 'controle' ] }
# Profondeur des différentes recherches (scope)
scope = { 'adherent': 1 , 'machine': 2 , 'club': 1 }
def __init__(self,readonly=False):
self.connect(readonly)
def __del__(self):
# Destruction des locks résiduels
if hasattr(self,'_locks'):
for lock in self._locks:
self.remove_lock(lock)
def connect(self,readonly=False):
""" Initialisation la connexion vers le serveur LDAP """
if readonly :
self.conn = ldap.initialize(ro_uri)
return
self.conn = ldap.initialize(uri)
while True :
try:
self.conn.bind_s(ldap_auth_dn,ldap_password,ldap.AUTH_SIMPLE)
break
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 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.
La méthode prend en compte les locks.
arg doit être une expression de recherche ldap
Si il y a existence, on retourne la liste des 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éjà 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'attribut 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')
if not valeur:
# On le lock pas un truc vide
return True
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 = '%s-1' % hostname
if l != lockid:
# C'est locké par un autre process que le notre
# il tourne encore ?
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
raise EnvironmentError(u'Objet (%s=%s) locké, patienter.' % (item, valeur), l)
else:
if not hasattr(self,'_locks'):
self._locks = [lock_dn]
else:
self._locks.append(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)
try:
self._locks.remove(lockdn)
except:
# Pas grave si ca foire, le lock n'y est plus
pass
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=[],start=0):
"""
start indique la date (en secondes depuis epoch) à partir du
moment où cette action sera effectué
Si new = None retourne la liste des services à redémarrer
Si new est fourni mais ne commence pas par '-', on ajoute
le service à la liste avec les arguments args (args doit être
une liste).
Si new commence par '-', on supprime le service si son start
est dans le futur
Si new commence par '--', on supprime le service de condition.
"""
if new: new = preattr(new)[1]
# Quels services sont déjà à redémarrer ?
serv = {} # { service: [ arguments ] }
serv_dates = {} # { service: [ dates de restart ] }
services = []
for s in self.conn.search_s(self.base_services,1,'objectClass=service'):
s=s[1]
serv[s['cn'][0]] = s.get('args',[])
serv_dates[s['cn'][0]] = s.get('start',[])
services.append(service(s['cn'][0],s.get('args',[]),s.get('start',[])))
# Retourne la liste des services à redémarrer
if not new: return services
# Effacement d'un service
if new[0] == '-':
if new[1] == '-':
# Double -- on enlève quelque soit la date
remove_dn='cn=%s,%s' % ( new[2:], self.base_services )
else:
# On enlève uniquement si la date est passée
remove_dn='cn=%s,%s' % ( new[1:], self.base_services )
if not serv.has_key(new[1:]):
# Existe pas => rien à faire
return
keep_date=[]
for date in serv_dates[new[1:]]:
if time.time() < int(date):
keep_date.append(date)
if keep_date:
self.conn.modify_s(remove_dn,ldap.modlist.modifyModlist({'start': serv_dates[new[1:]]}, { 'start': keep_date }))
remove_dn=None
if remove_dn:
# Suppression
try: self.conn.delete_s(remove_dn)
except: pass
# Si n'existe pas => Erreur mais le résultat est là.
return
serv_dn = 'cn=%s,%s' % ( new, self.base_services )
# Petite fonction à appliquer aux arguments
if type(args) == str: args = [ args ]
args=map(lambda x:preattr(x)[1] ,args)
if type(start) == int: start = [ start ]
start=map(lambda x:preattr(x)[1] ,start)
if new in serv.keys():
modlist = []
new_args = []
# Nouveaux arguments ?
for arg in args:
if arg not in serv[new]:
new_args.append(arg)
if new_args:
modlist += ldap.modlist.modifyModlist({ 'args': serv[new]}, { 'args': serv[new] + new_args })
new_date = []
# Nouvelle date ?
for date in start:
if date not in serv_dates[new]:
new_date.append(date)
if new_date:
modlist += ldap.modlist.modifyModlist({'start': serv_dates[new]}, { 'start': serv_dates[new] + new_date })
if modlist:
try:
self.conn.modify_s(serv_dn,modlist)
except ldap.TYPE_OR_VALUE_EXISTS:
# Pas grave
pass
# else rien à faire
else:
# Entrée non présente -> ajout
modlist = ldap.modlist.addModlist({ 'objectClass': 'service' ,
'cn': new ,
'args': args ,
'start': start } )
try:
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: pass
elif champ == 'paiement' and expr == 'ok':
# Paiement donnant droit à une connexion maintenant ?
# il doit avoir payé pour
# l'année en cours ou pour l'année précédente
# si on est en septembre
#
# Dans tous les cas, pour un adhérent, le paiement est considéré non ok
# s'il n'a pas fourni sa carte d'etudiant alors que l'on est desormais
# en periode de bloquage definifif (cf config.py).
if localtime()[1] == 9:
# Pour septembre paiement année précédente ok
el = "(|(paiement=%s)(paiement=%s))" % (int(ann_scol), int(ann_scol)-1)
else:
el = "(paiement=%s)" % (int(ann_scol),)
# Doit-on bloquer en cas de manque de la carte d'etudiant ?
if config.bl_carte_et_definitif:
el = "(&(|(carteEtudiant=%s)(objectClass=club))%s)" % (int(ann_scol), el)
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[i]!='(&' ) 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 len(conds) == 1:
# Filtre sur un seul champ
# => on retourne tout
for i in filtres:
if not r[i]: continue
for res in r[i]:
result[i].append(globals()[i](res,mode,self.conn))
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
__machines = ()
def all_machines(self, graphic=False):
"""Renvoie toutes les machines autorisées.
Cela affiche des trucs et des bidules si graphic est à True."""
if graphic: from affich_tools import anim, cprint, OK
if not self.__machines:
# Récolte des données
if graphic: cprint('Lecture base LDAP','gras')
# Machines de l'assoce
self.__machines = crans(self.conn).machines()
# Machines des invités
self.__machines += invite(self.conn).machines()
# Machines des adhérents et clubs de l'année en cours
base = self.search('paiement=ok')
base = base['adherent'] + base['club']
if graphic: a = anim('\ttri machines',len(base))
for adh in base:
if graphic: a.cycle()
# Adhérent ayant payé l'année en cours
if 'bloq' in adh.blacklist_actif():
# Adhérent ignoré
continue
self.__machines += adh.machines()
if graphic: a.reinit()
if graphic: print OK
return self.__machines
#############################################################################
class base_classes_crans(crans_ldap):
""" Méthodes de base des classes machines, et base_proprietaire """
def __eq__(self, autre):
""" Test d'égalité de deux instances de club/adhérent/machine,
retourne True s'il s'agit du même club/adhérent/machine, False sinon """
return self.__class__==autre.__class__ and self.id()==autre.id()
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
"""
return self.blacklist_all()[0].keys()
def blacklist_all(self):
"""
Vérifie si l'instance courante est blacklistée ou a été blacklistée.
Retourne les sanctions en cours sous forme de dictionnaire avec comme clef
la sanction et comme valeur une liste de couple de dates correspondant aux
différentes périodes de sanctions.
ex: { 'upload' : (('17/11/2004 00:00','20/11/2004 00:00'), ('...', '...')) }
"""
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 = {}
inactifs = {}
for sanction in bl_liste:
s = sanction.split(',')[2]
if is_actif(sanction):
if not s in actifs:
actifs[s] = []
actifs[s].append((sanction.split(',')[0],
sanction.split(',')[1]))
else:
if not s in inactifs:
inactifs[s] = []
inactifs[s].append((sanction.split(',')[0],
sanction.split(',')[1]))
return (actifs, inactifs)
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)
debut=0
else:
try: debut=int(time.mktime(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)
fin=0
elif new[1]!='-':
try: fin=int(time.mktime(time.strptime(new[1],date_format)))
except: raise ValueError(u'Date de fin blacklist invalide')
else:
fin = -1
if debut == fin:
raise ValueError(u'Dates de début et fin identiques')
# On prend en compte le fuseau horaire et on dépasse la fin
# de sanction d'1min pour être sur quelle périmé.
fin=fin+60-time.timezone
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.setdefault('blacklist_' + new[2], None)
if not hasattr(self,"_blacklist_restart"):
self._blacklist_restart={}
if not self._blacklist_restart.has_key(new[2]):
self._blacklist_restart[new[2]] = [ debut, fin ]
else:
if debut not in self._blacklist_restart[new[2]]:
self._blacklist_restart[new[2]].append(debut)
if fin!=-1 and fin not in self._blacklist_restart[new[2]]:
self._blacklist_restart[new[2]].append(fin)
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:
### ON NE TOUCHE PAS A SELF.MODIFS, IL EST UTILISÉ PLUS LOIN !!!!!!!
# Dictionnaire local des modifs
modif = {}
# Cas spécial
if "solde" in self.modifs:
diff = float(self._init_data.get('solde',[0])[0]) - float(self._data.get('solde',[0])[0])
if diff > 0:
modif['solde'] = "debit %s Euros" % str(diff)
else:
modif['solde'] = "credit %s Euros" % str(-diff)
# Formate les entrées de l'historique de la forme champ (ancien -> nouveau)
# On suppose que le champ apparaît forcément dans l'enregistrement
for champ in ['chbre', 'nom', 'prenom', 'mail',
'puissance', 'canal', 'prise', 'responsable',
'contourneGreylist']:
if champ in self.modifs:
if champ not in self._init_data.keys():
valeur_initiale = 'N/A'
else:
valeur_initiale = self._init_data[champ][0]
if not self._data.get(champ,[]):
valeur_finale = 'N/A'
else:
valeur_finale = self._data[champ][0]
modif[champ] = '%s (%s -> %s)' % (champ,
valeur_initiale,
valeur_finale)
# Formate les entrées de l'historique de la forme champ+diff-diff
for champ in ['droits', 'controle', 'paiement', 'carteEtudiant',
'mailAlias', 'hostAlias', 'exempt']:
if champ in self.modifs:
if champ == 'controle':
# Ce n'est pas pareil que self._init_data.get('controle', [''])
# qui peut renvoyer une liste vide (petite erreur de choix
# dans la première implémentation de controle)
ancien = (self._init_data.get('controle') or [''])[0]
nouveau = (self._data.get('controle') or [''])[0]
else:
# Là, on bosse directement sur les listes renvoyées par get
ancien = self._init_data.get(champ, [])
nouveau = self._data.get(champ, [])
# On établit le diff
diff = ''.join([ '+%s' % decode(d) for d in nouveau if d not in ancien ])
diff += ''.join([ '-%s' % decode(d) for d in ancien if d not in nouveau ])
modif[champ] = champ + diff
# On recolle tous les morceaux
liste_historique = []
for champ in self.modifs.keys():
ligne = modif.get(champ, champ)
if self.modifs[champ] != None:
ligne += ' [%s]' % self.modifs[champ]
liste_historique.append(ligne)
modif = ', '.join(liste_historique)
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'] + [ preattr("%s : %s" % ( hist, modif ))[1] ]
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
# Quasiement tout est traité dans les classes filles.
if hasattr(self,"_blacklist_restart"):
for n,t in self._blacklist_restart.items():
self.services_to_restart("blacklist_%s"%n,[],t)
# Reinitialisation
self._init_data = self._data.copy()
def _delete(self,dn,comment=''):
""" Sauvegarde puis destruction du dn (et des sous-dn) fourni """
# Commentaires
comment = preattr(comment)[1]
self.modifs.setdefault('destruction (%s)' % comment, None)
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
cPickle.dump(self,fd,2)
fd.close()
index = u"%s, %s : %s %s # %s\n" % (time.strftime(date_format), script_utilisateur, t, self.Nom() , decode(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, comment=None):
""" Met à jour les données de data et modifie modifs si besoin """
if (not self._data.has_key(champ) and val != []) \
or (self._data.has_key(champ) and self._data[champ]!=val):
self._data[champ]=val
if self.modifs.get(champ) == None or comment == None:
self.modifs[champ] = comment
else:
# Ici, self.modifs[champ] et comment devraient être tous deux
# des chaînes de caractères
self.modifs[champ] += ', ' + comment
#############################################################################
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 = {}
if data:
self.dn=data[0]
if mode == 'w':
try:
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 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 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.lock('mailAlias',new)
self._set('mailAlias',liste)
return liste
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 machines_fixes(self):
""" Retourne les machines fixes appartenant à l'instance """
return filter(lambda x: not x.ipsec(), self.machines())
def machines_wifi(self):
""" Retourne les machines wifi appartenant à l'instance """
return filter(lambda x: x.ipsec(), self.machines())
def solde(self, operation=None, comment=None):
""" Retourne ou modifie le solde d'un propriétaire
operation doit être un nombre positif ou négatif
(string ou int ou float)
comment est un commentaire à rajouter dans l'historique
"""
solde = float(self._data.get('solde',[0])[0])
if operation==None:
return solde
# On effectue une opération
try:
new = solde + float(str(operation).replace(',','.'))
except ValueError:
raise ValueError(u"Il faut donner un nombre en argument.")
# découvert accepté
if new < config.impression.decouvert:
raise ValueError(u"Solde minimal atteind, opération non effectuée.")
self._set('solde',[str(new)], comment)
return new
def controle(self,new=None):
"""
Controle du tresorier
New est de la forme [+-][pck]
(p pour le paiement, c pour la carte, k pour la caution)
Retourne une chaine contenant une combinaison de p, c, k.
"""
actuel = self._data.get('controle',[''])
if not actuel:
actuel = ''
else:
actuel = actuel[0]
if new==None:
return actuel
if not sre.match(r'^[+-][pck]$', new):
raise ValueError('modification de controle incorrecte')
for c in 'pck':
if new == '+%s' % c and c not in actuel:
actuel += c
if new == '-%s' % c:
actuel = actuel.replace(c, '')
if actuel == '':
# La base LDAP n'accepte pas les chaînes vides.
# On supprime directement le champ controle
if self._data.has_key('controle'):
if self._data.pop('controle') != []:
# Il y avait vraiment qqch avant
if 'controle' not in self.modifs:
self.modifs.setdefault('controle', None)
else:
self._set('controle',[actuel])
return actuel
def contourneGreylist(self,contourneGreylist=None):
""" Retourne ou change la greylist pour le compte
True : contourne le GreyListing
False :ne contourne pas le greylisting """
# Pour postfix il faut retourner :
# OK : contourne la greyliste
# cf. man 5 access
# si l'adhérent n'a pas de compte, on lève une exeption
if not self.compte():
raise NotImplementedError, u"L'adhérent na pas de compte"
# tente de modifier la valeur
if contourneGreylist==None:
pass
elif contourneGreylist == True:
self._set('contourneGreylist',['OK'])
elif contourneGreylist == False:
self._set('contourneGreylist',[])
else:
raise ValueError, u"contourneGreylist prend un booléen comme argument"
# renvoie la valeur trouvée dans la base
if self._data.get('contourneGreylist',[]) == []:
return False
else:
return True
def home(self):
""" Retourne le home de l'adhérent """
if not self.compte():
raise NotImplementedError, u"L'adhérent na pas de compte"
return self._data['homeDirectory'][0]
def uidNumber(self):
""" Retourne l'uidNumber de l'adhérent """
if not self.compte():
raise NotImplementedError, u"L'adhérent na pas de compte"
return self._data['uidNumber'][0]
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 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 =''
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
if 'chbre' in self.modifs and '????' in [ self._init_data.get("chbre",[''])[0] , self._init_data.get("chbre",[''])[0] ]:
self.services_to_restart('bl_chbre_invalide')
# Enregistrement
self._save()
# Message de sortie
if nouveau:
ret += coul(u"%s inscrit avec succès." % self.Nom(), 'vert')
if self.idn !='cid' and self.etudes(1) != "Pers":
# 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')
# Changements administratifs
test_carte = 'carteEtudiant' in self.modifs
if test_carte and self.machines():
self.services_to_restart('bl_carte_etudiant')
if 'paiement' in self.modifs or (config.bl_carte_et_definitif and test_carte):
for m in self.machines():
self.services_to_restart('macip',[m.ip()] )
self.services_to_restart('dns')
if m.ipsec():
self.services_to_restart('conf_wifi_ng')
self.services_to_restart('nectaris-dhcp')
else:
self.services_to_restart('switch',[self.chbre()])
self.services_to_restart('rouge-dhcp')
# 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>') )
m.save()
except Exception, 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 vert ?
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(u"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')
# Modif des droits ?
if 'droits' in self.modifs:
self.services_to_restart('droits')
self.services_to_restart('mail_modif',['uid=%s' % self._data['uid'][0]])
# Remise à zero
self.modifs = {}
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
touched = False
if action>0 and action not in trans:
trans.append(action)
touched = True
elif action<0 and -action in trans:
trans.remove(-action)
touched = True
if touched and champ not in self.modifs:
self.modifs.setdefault(champ, None)
trans.sort()
self._data[champ] = map(str, trans)
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.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 [u'',u'',u'',u'']
else:
addr = self._data.get('postalAddress', ['','','',''])[:4]
if len(addr) < 4:
addr = addr + ['']*(4-len(addr))
return map(decode,addr)
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")
# Il ne doit pas y avoir de compte
self.supprimer_compte()
self._set('mail',[new])
return new
def email(self, new=None):
""" Retourne l'adresse mail, ajoute le @crans.org si nécessaire """
# pour la compatibilité entre les fonctions
if new:
self.mail(new)
# ajout du @crans.org si nécessaire
mail = self.mail()
if not '@' in mail:
mail += '@crans.org'
return mail
def supprimer_compte(self):
u"""
Supprime le compte sur zamok. Penser à définir l'adresse mail après.
"""
self._set('mail', [''])
self._data['objectClass'] = [ 'adherent' ]
for c in [ 'uid', 'cn', 'shadowLastChange', 'shadowMax', 'shadowWarning', 'loginShell', 'userPassword', 'uidNumber', 'gidNumber', 'homeDirectory', 'gecos', 'droits','mailAlias', 'cannonicalAlias' ]:
try: self._data.pop(c)
except: pass
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 checkPassword(self, password):
"""Vérifie le mot de passe de l'adhérent"""
anon = ldap.initialize(uri)
try:
r = anon.simple_bind(self.dn, password)
anon.result(r)
except ldap.INVALID_CREDENTIALS:
# A priori, le mot de passe est pas bon, il se peut aussi
# que l'utilisateur n'existe pas
return False
return True
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)
if os.path.exists("/var/mail/"+login):
raise ValueError(u'Création du compte impossible : /var/mail/%s existant'%login,1)
# Lock du mail
self.lock('mail',login)
self._data['mail']= [ login ]
if not 'compte' in self.modifs:
self.modifs.setdefault('compte', None)
# 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:
pool_uid = range(1001,9999)
random.shuffle(pool_uid)
while len(pool_uid) > 0:
uidNumber = pool_uid.pop() # On choisit une IP
if not self.exist('(uidNumber=%s)'):
# On a trouvé un uid libre
pool_uid.append(uidNumber)
break
if not len(pool_uid):
raise ValueError(u'Plus d\'uid disponibles !')
try:
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(strip_accents(self.Nom()))[1] + ',,,' ]
return decode(login)
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.lock('cannonicalAlias',a)
# Attribution
self._set('cannonicalAlias',[a])
return a
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):
""" Défini ou retourne le nom du club """
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 etudes(*args):
""" Etudes bidon pour club """
return u'N/A'
def nom(self):
""" Retourne le nom du club, utilisé lors de la destruction """
return strip_accents(self.Nom())
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(),''))
def compte(self,login=None):
""" Créé un compte au club sur vert"""
if login==None:
return self._data.get('uid',[''])[0]
# Génération du login : club-<login fourni>
login = login.lower()
if not sre.match('^club-', login):
login = 'club-' + login
for l in login:
if l not in string.letters + '-':
raise ValueError('Caractère %s interdit dans le login.' % l)
login = preattr(login)[1]
if 'posixAccount' in self._data['objectClass']:
if login != self._data['uid']:
# A déja un compte
raise ValueError(u"Le club à déjà un compte. Login : %s" % self._data['uid'][0])
else:
return login
if mailexist(login) and not os.system('/usr/sbin/list_lists | grep -qi %s' % login):
# la 2ème vérif est pour vérifier que ce n'est pas la ML du club
raise ValueError(u"Login existant ou correspondant à un alias mail.",1)
home = '/home/' + login.replace('-','/',1)
if os.path.exists(home):
raise ValueError(u'Création du compte impossible : home existant',1)
if os.path.exists("/var/mail/"+login):
raise ValueError(u'Création du compte impossible : /var/mail/%s existant'%login,1)
# Lock du mail
self.lock('mail',login)
if not 'compte' in self.modifs:
self.modifs.setdefault('compte', None)
self._data['objectClass'] = [ 'club', 'posixAccount', 'shadowAccount' ]
self._data['uid'] = [ login ]
self._data['cn'] = [ preattr(self.Nom())[1] ]
self._data['loginShell' ] = [ config.club_login_shell ]
# Détermination de l'uid
uidNumber = 1000
while self.exist('(uidNumber=%s)' % uidNumber):
uidNumber += 1
try:
self.lock('uidNumber',str(uidNumber))
except:
# Quelqu'un nous a piqué l'uid que l'on venait de choisir !
return self.compte(login)
self._data['uidNumber']= [ str(uidNumber) ]
self._data['gidNumber']=[ str(config.club_gid) ]
self._data['homeDirectory']=[ preattr(home)[1] ]
return decode(login)
def email(self):
""" Retourne l'adresse mail du responsable """
return self.responsable().email()
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 = {}
t = parent_or_tuple.__class__
if t == tuple:
# Initialisation avec données fournies
self.dn = parent_or_tuple[0]
if typ == 'w':
try:
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 in [ adherent, club, crans, invite ] 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'] = [ '2047' ]
self._data['puissance'] = [ '60' ]
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)
# La MAC serait-elle une MAC à la con ?
if mac == "00:04:4b:80:80:03":
raise ValueError(u"Il s'agit de l'unique adresse MAC achetée par nVidia pour ses cartes réseau. Il faut changer cette adresse.",2)
elif mac[0:11] == "44:45:53:54":
raise ValueError(u"Il s'agit de l'adresse MAC d'une interface PPP.", 2)
# Le test final : vendeur connu
prefix=mac[:8].upper() + ' '
vendor = ''
try:
for line in open('/usr/scripts/gestion/ethercodes.dat').readlines():
if line.startswith(prefix):
vendor = line.replace(prefix,'').replace('( )','').strip()
break
except IOError:
# Le fichier existe pas, on sors
raise RuntimeError(u"Fichier de fabiquants de MAC non trouvé !")
if not vendor:
raise ValueError(u"Le constructeur correspondant à cette adresse MAC ne peut être trouvé.\nL'adresse MAC correspond peut-être à un pont réseau, désactivez ce pont réseau.\nContactez nounou si la MAC est bien celle d'une carte.",2)
# Lock de la mac
self.lock('macAddress',mac)
self._set('macAddress',[mac])
return mac
def mac2(self):
"""Retourne l'adresse MAC + 2"""
mac = self.mac().split(":")
mac[-1] = "%0.2x" % (int(mac[-1], 16) + 2)
return ":".join(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.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 prise(self,new=None):
""" Retourne ou défini la prise associée à la machine
La définition n'est possible que si la machine est
une machine de l'assoce.
Si la prise est inconne retourne N/A
"""
if new == None:
if self.proprietaire().__class__ == crans:
return decode(self._data.get('prise',['N/A'])[0])
else:
chbre = self.proprietaire().chbre()
if chbre and chbre[0].lower() in annuaires.chbre_prises.keys():
try:
return annuaires.chbre_prises[chbre[0].lower()][chbre[1:]]
except:
return 'N/A'
# Attribution de la prise
new=preattr(new)[1]
if new == 'N/A' :
if self._data.has_key('prise') :
self._set('prise',[])
self._data.pop('prise')
return
if not sre.match('^[a-cg-jmp][0-6][0-5][0-9]$',new.lower()):
raise ValueError('Prise incorrecte')
self._set('prise',[new.upper()])
return new
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':
if self.proprietaire().__class__ == invite:
net = config.NETs['wifi-invite']
elif self.proprietaire().etudes(0) == "ENS" and self.proprietaire().etudes(1) == "Pers":
# Personnel ENS
net = config.NETs['wifi-ens']
else:
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
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
pool_ip.append(ip)
break
if not len(pool_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)
# Reformatage
ip = iptools.DecToQuad(iptools.QuadToDec(ip))
# L'ip est-elle déja allouée ?
if self.exist('ipHostNumber=%s' % ip):
raise ValueError(u'IP déjà prise.')
# Lock ip
self.lock('ipHostNumber',ip)
self._set('ipHostNumber',[ip])
return ip
def exempt(self,new=None):
"""
Liste des réseaux vers lesquels on ne compte pas l'upload
Cette liste est transférée dans la base postgres de komaz
Pour ajouter un réseau new doit être la chaîne
représentant le réseau à ajouter
Pour modifier new doit être une liste de la forme :
[ index du nouveau réseau , nouveau réseau ]
l'index est celui obtenu dans la liste retournée par exempt()
"""
if not self._data.has_key('exempt'):
self._data['exempt']=[]
liste = list(self._data['exempt'])
if new==None: return map(decode,liste)
if type(new)==list:
# Modif
index = new[0]
l, new = preattr(new[1])
if not new:
# Supression réseau
liste.pop(index)
else:
# Modif remarque
liste[index]=new
elif type(new)==str:
# Réseau supplémentaire
l, new = preattr(new)
if not new:
# On ajoute pas de réseau vide
return liste
# Ajout à la liste
liste = liste + [ new ]
else:
raise TypeError
self._set('exempt',liste)
return liste
def proprietaire(self):
"""
retroune le propriétaire de la machine (classe adherent, club ou crans ou invite)
"""
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)
elif 'invite' in res[0][0]:
self.__proprietaire = invite()
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(22):
clef += random.choice(filter(lambda x: x != 'l' and x != 'o', string.lowercase) +
filter(lambda x: x != '1' and x != '0', string.digits))
self._set('ipsec',[clef])
return clef
def canal(self,new=None,raw=False):
""" Attribution ou visualisation du canal d'une borne wifi """
if self.__typ != 'borne': return None
if not new:
canaux = self._data.get('canal',[''])[0]
if raw:
return canaux
else:
canaux = int(canaux)
if canaux < 14:
# Compatibilité ascendante
return str(canaux)
lcanal1 = []
for i in range(1,14):
found = False
if canaux & (1 << (i - 1)):
lcanal2 = []
for c in lcanal1:
if c[1] == i - 1:
lcanal2.append((c[0], i))
found = True
else:
lcanal2.append(c)
if not found:
lcanal2.append((i,i))
lcanal1 = lcanal2
lcanal3 = []
for c in lcanal1:
if c[0] == c[1]:
lcanal3.append("%d" % c[0])
else:
lcanal3.append("%d-%d" % (c[0],c[1]))
return ",".join(lcanal3)
try:
new = int(new)
if new < 0 or new > 13: raise
except:
# Nouveau système, il doit s'agir d'une liste de canaux
try:
lcanal3 = str(new).split(",")
lcanal = []
for c in lcanal3:
c2 = c.split("-")
if len(c2) == 1:
lcanal.append(int(c2[0]))
else:
for i in range(int(c2[0]), int(c2[1]) + 1):
lcanal.append(i)
new = 0
for c in lcanal:
if c not in range(0,14):
raise
new = new + (1 << (c - 1))
except:
raise ValueError(u'Canal invalide : doit être entre 0 et 13 ou une liste de canaux')
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 < -99 or new > 99: raise
except:
raise ValueError(u'Puissance invalide : doit être entre -99 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().__class__ == crans and not ( isadm or user_tests.getuser() == 'www-data' ):
raise EnvironmentError(u'Il faut être administrateur pour effectuer cette opération.')
if not self._init_data:
# Nouvelle machine => configuration prise
self.services_to_restart('switch',[self.proprietaire().chbre()])
ret =''
# Besoin de redémarrer les firewalls ?
if 'ipHostNumber' in self.modifs or 'macAddress' in self.modifs:
reconf_ip = self._init_data.get('ipHostNumber',[])
reconf_ip += self._data.get('ipHostNumber',[])
else:
reconf_ip = []
# On vire les doublons dans reconf_ip
reconf_ip = list(dict(zip(reconf_ip,[None]*len(reconf_ip))))
# Enregistrement
self._save()
# Clef IPsec
if 'ipsec' in self.modifs:
ret += coul(u'Clef IPsec de la machine : %s\n' % self.ipsec(),'cyan')
self.services_to_restart('conf_wifi_ng')
# Reconfiguration firewalls et dhcps
if reconf_ip:
self.services_to_restart('macip',reconf_ip )
if self.__typ == 'wifi' :
self.services_to_restart('nectaris-dhcp')
else:
self.services_to_restart('rouge-dhcp')
if 'ports' in self.modifs:
self.services_to_restart('komaz-ports', [ self.ip() ] )
self.services_to_restart('mail_modif',['ip=%s' % self.ip()])
# Reconfiguration DNS ?
if 'host' in self.modifs or 'ipHostNumber' in self.modifs or 'hostAlias' in self.modifs:
self.services_to_restart('dns')
# Reconfiguration bornes wifi ?
if 'canal' in self.modifs or 'puissance' in self.modifs:
self.services_to_restart('conf_wifi_ng')
# Reconfiguration clients wifi ?
if self.__typ in ('wifi','borne') and ( 'ipHostNumber' in self.modifs or 'host' in self.modifs or 'macAddress' in self.modifs ):
self.services_to_restart('conf_wifi_ng')
# 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() ] )
# Regénération de l'autostatus et mail de changmement ?
if self.proprietaire().__class__ == crans:
self.services_to_restart('autostatus')
self.services_to_restart('mail_modif',['ip=%s' % self.ip()])
# Synchronisation avec la base pgsql pour les exemptions
if 'exempt' in self.modifs or ('ipHostNumber' in self.modifs and self.exempt()):
self.services_to_restart('surveillance_exemptions')
# Synchronisation avec la base pgsql pour la liste des machines
if 'ipHostNumber' in self.modifs:
self.services_to_restart('surveillance_machines')
# 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().__class__ == crans and not isadm:
raise EnvironmentError(u'Il faut être administrateur pour effectuer cette opération.')
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)
# Services à redémarrer
if self.__typ == 'wifi' :
self.services_to_restart('conf_wifi_ng')
self.services_to_restart('nectaris-dhcp')
else:
self.services_to_restart('rouge-dhcp')
if self.exempt():
self.services_to_restart('surveillance_exemptions')
self.services_to_restart('surveillance_machines')
self.services_to_restart('dns')
self.services_to_restart('macip',[self.ip()] )
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.setdefault('ports', None)
elif self._data.has_key(champ):
self._data.pop(champ)
if 'ports' not in self.modifs:
self.modifs.setdefault('ports', None)
class _fake_proprio(crans_ldap):
""" Définitions de base d'un propriétaire virtuel """
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 blacklist(s):
return []
def paiement(s):
return [ ann_scol ]
def carteEtudiant(s):
return [ ann_scol ]
def blacklist_actif(s):
return []
def mail(self,new=None):
return 'roots@crans.org'
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
class crans(_fake_proprio):
""" Classe définissant l'assoce (pour affichage de ses machines) """
def __init__(s,conn=None):
s.conn = conn
if not s.conn: s.connect()
s.dn = s.base_dn
def Nom(s):
return u"Crans"
def chbre(s):
return u"CRA"
class invite(_fake_proprio):
""" Propriétaire des machines invité """
def __init__(s,conn=None):
s.conn = conn
if not s.conn: s.connect()
s.dn = "ou=invites," + s.base_dn
def Nom(s):
return u"Invité"
def chbre(s):
return u"N/A"
if __name__ == '__main__':
import sys
usage = """Usage %s [ACTION]
--lock : donne la liste des locks actifs
--purgelock : supprime tous les locks de la base LDAP
--menage : supprime les machines des anciens adhérents"""%sys.argv[0]
if len(sys.argv)!=2:
print usage
sys.exit(1)
elif '--lock' in sys.argv:
print "Liste des locks"
db = crans_ldap()
for lock in db.list_locks():
print "%s\t %s" % (lock[1]["lockid"][0],lock[0].split(',')[0])
elif '--purgelock' in sys.argv:
print "Suppression de tous les locks"
db = crans_ldap()
db.remove_lock('*')
elif '--menage' in sys.argv:
print "Ménage des machines des adhérents partis..."
db = crans_ldap()
machines=db.search('paiement!=%s&host=*.crans.org' % ann_scol ,'w')['machine']
print "Destruction de %i machines" % len(machines)
for m in machines:
print 'Destruction de %s' % m.nom()
m.delete('Ménage')
else:
print usage
sys.exit(1)