
environ 24h et pas 25h. Le champ start est enregistre en GMT et pas en local. time.timezone vaut -3600 darcs-hash:20060314210354-72cb0-0a692250426622bdc7bbd0413911abefdac39550.gz
2889 lines
106 KiB
Python
Executable file
2889 lines
106 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
|
||
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
|
||
|
||
date_format='%d/%m/%Y %H:%M'
|
||
hostname = gethostname().split(".")[0]
|
||
smtpserv = "rouge.crans.org"
|
||
random.seed() # On initialise le générateur aléatoire
|
||
|
||
|
||
##################################################################################
|
||
### paramètres de connaction à la base LDAP
|
||
|
||
if __name__=='ldap_crans_test' or os.environ.get('crans_ldap','')=='test':
|
||
# Utilisation de la base de données de test (tests, séminaire...)
|
||
# Il faut au choix :
|
||
# - faire un import crans_ldap_test
|
||
# ou - crans_ldap=test /le/script
|
||
uri = ro_uri = 'ldap://egon.adm.crans.org/'
|
||
ldap_auth_dn='cn=admin,dc=crans,dc=org'
|
||
ldap_password='75bdb64f32'
|
||
|
||
elif user_tests.getuser()=='freerad':
|
||
# Freeradius n'a pas accès au secret, donc accès que en local
|
||
uri = ''
|
||
ro_uri = 'ldapi://%2fvar%2frun%2fldapi/'
|
||
ldap_auth_dn = ldap_password = ''
|
||
|
||
else:
|
||
# pour les autres on utilise ldap.adm.crans.org en rw
|
||
uri = 'ldap://ldap.adm.crans.org/'
|
||
|
||
# avec le secret
|
||
try:
|
||
from secrets import ldap_password, ldap_auth_dn
|
||
except:
|
||
sys.stdout.write(coul('Warning : impossible de lire le fichier de secret !\n','jaune'))
|
||
sys.exit(1)
|
||
|
||
# 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
|
||
|
||
##################################################################################
|
||
### 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'warez' : u'Bloquage sur squid',
|
||
u'p2p' : u'Bloquage total de l\'accès à l\'extérieur',
|
||
u'autodisc_upload' : u'Autodisconnect pour upload',
|
||
u'autodisc_p2p' : u'Autodisconnect pour P2P'}
|
||
|
||
##################################################################################
|
||
### 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
|
||
"""
|
||
|
||
if type(s) is unicode:
|
||
# Si s est déjà un unicode, on ne décode pas
|
||
return s
|
||
else:
|
||
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îne
|
||
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):
|
||
starting = self.start
|
||
starting.sort()
|
||
dates = ' et '.join(map(lambda t: t<time.time() and \
|
||
"maintenant" or time.strftime(date_format,
|
||
time.gmtime(t)),
|
||
self.start))
|
||
dates = " à partir d%s %s" % (dates.startswith("maintenant") and "e" or "u",
|
||
dates)
|
||
return ("%s(%s)%s" % (self.nom,
|
||
','.join(self.args),
|
||
dates)).replace(" et maintenant","")
|
||
|
||
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',
|
||
'position': 'positionBorne'}
|
||
|
||
# Les différentes classes LDAP de machines
|
||
ldap_machines_classes = ['machineFixe', 'machineWifi', 'machineCrans', 'borneWifi']
|
||
|
||
# Champs de recherche pour la recherche automatique
|
||
auto_search_machines_champs = \
|
||
['macAddress', 'host', 'ipHostNumber', 'hostAlias']
|
||
|
||
auto_search_champs = { \
|
||
'adherent': \
|
||
['nom', 'prenom', 'tel', 'mail', 'chbre', 'mailAlias', 'cannonicalAlias' ],
|
||
'club': ['nom', 'chbre'],
|
||
'machineFixe': auto_search_machines_champs,
|
||
'machineWifi': auto_search_machines_champs,
|
||
'machineCrans': auto_search_machines_champs,
|
||
'borneWifi': auto_search_machines_champs }
|
||
|
||
# Champs de recherche pour la recherche manuelle (en plus de la recherche auto)
|
||
non_auto_search_machines_champs = \
|
||
['mid', 'historique', 'blacklist', 'info', 'exempt', 'mblacklist',
|
||
'portTCPin', 'portTCPout', 'portUDPin', 'portUDPout']
|
||
|
||
non_auto_search_champs = { \
|
||
'adherent': \
|
||
['etudes', 'paiement', 'carteEtudiant', 'aid', 'postalAddress',
|
||
'historique', 'blacklist', 'droits', 'uidNumber', 'uid', 'info',
|
||
'solde', 'controle', 'contourneGreylist', 'rewriteMailHeaders',
|
||
'ablacklist'], \
|
||
'club': \
|
||
['cid', 'responsable', 'paiement', 'historique', 'blacklist',
|
||
'mailAlias', 'info', 'controle'], \
|
||
'machineFixe': non_auto_search_machines_champs,
|
||
'machineCrans': non_auto_search_machines_champs + ['prise'],
|
||
'borneWifi': non_auto_search_machines_champs + \
|
||
['prise', 'puissance', 'canal', 'hotspot', 'positionBorne'],
|
||
'machineWifi': non_auto_search_machines_champs + ['ipsec'] }
|
||
|
||
# Profondeur des différentes recherches (scope)
|
||
scope = {'adherent': 1, 'club': 1,
|
||
'machineFixe': 2, 'machineWifi': 2,
|
||
'machineCrans': 2, 'borneWifi': 2 }
|
||
|
||
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)
|
||
nbessais = 0
|
||
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)+time.timezone:
|
||
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 make(self, entry, mode=''):
|
||
"""
|
||
Crée le bon objet à partir de entry.
|
||
mode a la même signification que dans search.
|
||
"""
|
||
# On récupère la bonne classe
|
||
nom_classe = (entry[1].get('objectClass') or ['none'])[0]
|
||
nom_classe = nom_classe[0].upper() + nom_classe[1:]
|
||
try:
|
||
# Hack temporaire, à enlever quand on aura tout renommé
|
||
if nom_classe in ['Adherent', 'Club']:
|
||
nom_classe = nom_classe.lower()
|
||
classe = globals()[nom_classe]
|
||
# On crée l'objet
|
||
return classe(entry, mode, self.conn)
|
||
except:
|
||
raise ValueError(u"Impossible de créer l'objet %s" % nom_classe)
|
||
|
||
|
||
def search(self, expression, mode=''):
|
||
"""
|
||
Recherche dans la base LDAP, expression est une chaîne :
|
||
* soit 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'Chaîne attendue')
|
||
|
||
if not expression:
|
||
return []
|
||
|
||
# On échappe les caractères spéciaux
|
||
expression = expression.replace('\\', '\\\\').replace('(', '\\(').replace(')', '\\)')
|
||
|
||
# Il faut un filtre par type d'objet de la base
|
||
filtres = self.auto_search_champs.keys()
|
||
result = {}
|
||
for i in filtres:
|
||
result[i] = []
|
||
|
||
# Fonction utile
|
||
def build_filtre(champ, expr, neg=False):
|
||
"""
|
||
Retourne une chaine pour recherche dans la base LDAP
|
||
du style (champ=expr) en adaptant les valeurs de expr au champ.
|
||
Si neg = True, retourne le négatif : (!(champ=expr))
|
||
"""
|
||
el = ''
|
||
if champ in ['host', 'hostAlias']:
|
||
if expr[-1] == '*':
|
||
el = '(%s=%s)' % (champ, expr)
|
||
elif '.' not in expr:
|
||
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
|
||
# définifif (cf config.py).
|
||
if localtime()[1] == 9:
|
||
# Pour septembre paiement année précédente ok
|
||
el = "(|(paiement=%d)(paiement=%d))" % (ann_scol, ann_scol-1)
|
||
else:
|
||
el = "(paiement=%s)" % ann_scol
|
||
# Doit-on bloquer en cas de manque de la carte d'etudiant ?
|
||
if config.bl_carte_et_definitif:
|
||
el = "(&(|(carteEtudiant=%d)(objectClass=club))%s)" % (ann_scol, el)
|
||
elif champ[1:] == 'blacklist':
|
||
el = '(blacklist=%s)' % expr
|
||
else:
|
||
# Cas général
|
||
el = '(%s=%s)' % (champ, expr)
|
||
if neg: el = '(!%s)' % el
|
||
return el
|
||
|
||
if '=' in expression:
|
||
#### Recherche avec conditions explicites
|
||
## Construction des filtres
|
||
|
||
# Initialisation
|
||
filtre = {}
|
||
filtre_seul = {}
|
||
# filtre_seul[i] est True si la requête porte sur un champ
|
||
# qui n'est pas dans toutes les machines, mais dans i
|
||
for i in filtres:
|
||
filtre[i] = ''
|
||
filtre_seul[i] = False
|
||
|
||
conds = expression.split('&')
|
||
|
||
# Test de l'expression de recherche et classement par filtres
|
||
for cond in conds:
|
||
neg = False
|
||
try:
|
||
champ, expr = cond.strip().split('=')
|
||
if champ[-1] == '!':
|
||
# Négation pour ce champ
|
||
champ = champ[:-1]
|
||
neg = True
|
||
except:
|
||
raise ValueError(u'Syntaxe de recherche invalide (%s)' % cond)
|
||
|
||
# Transformation de certains champs
|
||
champ = self.trans.get(champ, champ)
|
||
|
||
if expr == '': expr = '*'
|
||
|
||
ok = False
|
||
|
||
# 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 = True
|
||
if champ not in self.auto_search_machines_champs \
|
||
and champ not in self.non_auto_search_machines_champs:
|
||
filtre_seul[i] = True
|
||
|
||
if not ok:
|
||
raise ValueError(u'Champ de recherche inconnu (%s)' % champ)
|
||
|
||
## Recherche avec chacun des filtres
|
||
r = {} # contiendra les réponses par filtre
|
||
|
||
# On efface les bons filtres si la requête porte sur des
|
||
# champs spéciaux
|
||
ok = False
|
||
for i in self.ldap_machines_classes:
|
||
ok = ok or filtre_seul[i]
|
||
if ok:
|
||
# Il y a au moins un champ spécial
|
||
for i in self.ldap_machines_classes:
|
||
if not filtre_seul[i]:
|
||
filtre[i] = ''
|
||
|
||
for i in filtres:
|
||
if filtre[i] != '':
|
||
# Filtre valide
|
||
filtre[i] = '(&(objectClass=%s)%s)' % (i, filtre[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 (r['machineFixe'] != None or r['machineWifi'] != None) \
|
||
and (r['adherent'] != None or r['club'] != None) \
|
||
and len(conds) > 1:
|
||
|
||
# On renvoie toutes les machineCrans et borneWifi
|
||
for i in 'machineCrans', 'borneWifi':
|
||
if r[i] == None:
|
||
continue
|
||
for res in r[i]:
|
||
result[i].append(self.make(res, mode))
|
||
|
||
# On croise maintenant 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['machineFixe'] + r['machineWifi']:
|
||
dn = ','.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
|
||
for i in 'adherent', 'club':
|
||
if r[i] == None:
|
||
continue
|
||
for a in r[i]:
|
||
if a[0] in mach_adh and not a[0] in bons_dn:
|
||
bons_dn.append(a[0])
|
||
result[i].append(self.make(a, mode))
|
||
|
||
# Maintenant c'est au tour des bonnes machines
|
||
bons_dn2 = []
|
||
for i in 'machineFixe', 'machineWifi':
|
||
if r[i] == None:
|
||
continue
|
||
for a in r[i]:
|
||
dn = string.join(a[0].split(',')[-4:], ',')
|
||
if dn in bons_dn and not a[0] in bons_dn2:
|
||
bons_dn2.append(dn)
|
||
result[i].append(self.make(a, mode))
|
||
|
||
else:
|
||
# On retourne tout
|
||
for i in filtres:
|
||
if r[i] == None:
|
||
continue
|
||
for res in r[i]:
|
||
result[i].append(self.make(res, mode))
|
||
|
||
else:
|
||
### Recherche d'une chaine sur tous les champs
|
||
for i in filtres:
|
||
# Construction du filtre
|
||
filtre = ''
|
||
for champ in self.auto_search_champs[i]:
|
||
filtre += build_filtre(champ, expression)
|
||
filtre = '(&(|%s)(objectClass=%s))' % (filtre, i)
|
||
|
||
# Recherche
|
||
for r in self.conn.search_s(self.base_dn, self.scope[i], filtre):
|
||
result[i].append(self.make(r, mode))
|
||
|
||
# Backward-compatibilité
|
||
result['machine'] = []
|
||
for i in self.ldap_machines_classes:
|
||
result['machine'] += result[i]
|
||
|
||
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 __str__(self):
|
||
""" Chainde identifiant l'objet de la forme 'uid=1245' """
|
||
return '%s=%s' % (self.idn, self.id())
|
||
|
||
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', 'tel',
|
||
'puissance', 'canal', 'prise', 'responsable',
|
||
'contourneGreylist', 'macAddress', 'ipHostNumber', 'host']:
|
||
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, self.filtre_idn)
|
||
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 : %s'%str(self))
|
||
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 """
|
||
res = []
|
||
for r in self.conn.search_s('%s=%s,%s' % (self.idn, self.id(), self.base_dn), 1, Machine.filtre_idn):
|
||
res.append(self.make(r, self._modifiable))
|
||
return res
|
||
|
||
def machines_fixes(self):
|
||
""" Retourne les machines fixes appartenant à l'instance """
|
||
res = []
|
||
for r in self.conn.search_s('%s=%s,%s' % (self.idn, self.id(), self.base_dn), 1, 'objectClass=machineFixe'):
|
||
res.append(self.make(r, self._modifiable))
|
||
return res
|
||
|
||
def machines_wifi(self):
|
||
""" Retourne les machines wifi appartenant à l'instance """
|
||
res = []
|
||
for r in self.conn.search_s('%s=%s,%s' % (self.idn, self.id(), self.base_dn), 1, 'objectClass=machineWifi'):
|
||
res.append(self.make(r, self._modifiable))
|
||
return res
|
||
|
||
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 == True:
|
||
self._set('contourneGreylist',['OK'])
|
||
elif contourneGreylist == False:
|
||
self._set('contourneGreylist',[])
|
||
elif contourneGreylist != None:
|
||
raise ValueError, u"contourneGreylist prend un booléen comme argument"
|
||
|
||
# renvoie la valeur trouvée dans la base
|
||
return bool(self._data.get('contourneGreylist',[]))
|
||
|
||
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:
|
||
if self.machines_fixes():
|
||
self.services_to_restart('switch', [self._data['chbre'][0]])
|
||
self.services_to_restart('switch', [self._init_data.get('chbre', '')[0]])
|
||
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 isinstance(m, MachineWifi):
|
||
self.services_to_restart('conf_wifi_ng')
|
||
self.services_to_restart('ragnarok-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 isinstance(m, MachineWifi):
|
||
# 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'
|
||
filtre_idn = '(objectClass=adherent)'
|
||
|
||
### 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])
|
||
if self._data.has_key('gecos'):
|
||
gecos = '%s %s' % tuple(map(lambda x: strip_accents(x.capitalize()), (self.prenom(), self.nom())))
|
||
self._data['gecos'] = [ preattr(gecos)[1] + ',,,' ]
|
||
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 isinstance(m, MachineWifi):
|
||
# 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] ]
|
||
gecos = '%s %s' % tuple(map(lambda x: strip_accents(x.capitalize()), (self.prenom(), self.nom())))
|
||
self._data['gecos'] = [ preattr(gecos)[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
|
||
|
||
def rewriteMailHeaders(self,rewrite=None):
|
||
""" Réécriture des entêtes mail avec l'alias canonique
|
||
True : réécrit les en-têtes
|
||
False : ne réécrit pas les en-têtes """
|
||
|
||
# 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 rewrite == True:
|
||
self._set('rewriteMailHeaders',['oui'])
|
||
elif rewrite == False:
|
||
self._set('rewriteMailHeaders',['non'])
|
||
elif rewrite!=None:
|
||
raise ValueError, u"rewriteMailHeaders prend un booléen comme argument"
|
||
|
||
# renvoie la valeur trouvée dans la base
|
||
return self._data.get('rewriteMailHeaders',['oui']) == ['oui']
|
||
|
||
class club(base_proprietaire):
|
||
""" Classe de définition d'un club """
|
||
idn = 'cid'
|
||
filtre_idn = '(objectClass=club)'
|
||
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
|
||
if not sre.match('^[a-z0-9]*[a-z]+[a-z0-9-]*$', login):
|
||
raise ValueError(u'Login incorrect')
|
||
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'
|
||
filtre_idn = '(|(objectClass=machineFixe)(objectClass=machineWifi)'
|
||
filtre_idn += '(objectClass=machineCrans)(objectClass=borneWifi))'
|
||
|
||
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ée.
|
||
* 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'obtention du lock grâce à 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
|
||
# Utile pour construire l'instruction LDAP de modif
|
||
self._init_data = parent_or_tuple[1].copy()
|
||
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': [ self.objectClass ] }
|
||
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(True)
|
||
|
||
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 __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(self.conn)
|
||
else:
|
||
self.__proprietaire = crans(self.conn)
|
||
|
||
return self.__proprietaire
|
||
|
||
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('ragnarok-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('ragnarok-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 MachineFixe(Machine):
|
||
""" Classe de définition d'une machine fixe """
|
||
objectClass = "machineFixe"
|
||
|
||
def __init__(self, parent_or_tuple, typ='fixe', conn=None):
|
||
Machine.__init__(self, parent_or_tuple, typ, conn)
|
||
|
||
|
||
class MachineWifi(Machine):
|
||
""" Classe de définition d'une machine wifi """
|
||
objectClass = "machineWifi"
|
||
|
||
def __init__(self, parent_or_tuple, typ='wifi', conn=None):
|
||
Machine.__init__(self, parent_or_tuple, typ, conn)
|
||
|
||
def ipsec(self, clef=None):
|
||
"""
|
||
Affichage (clef=None), génération (clef=True) ou définition de la clef IPsec
|
||
de la machine. Si clef différent de True et None: prend la clef fournie.
|
||
"""
|
||
if clef == None:
|
||
return decode(self._data.get('ipsec', [''])[0])
|
||
|
||
if clef == True:
|
||
# 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
|
||
|
||
|
||
class MachineCrans(Machine):
|
||
""" Classe de définition d'une machine du Crans """
|
||
objectClass = "machineCrans"
|
||
|
||
def __init__(self, parent_or_tuple, typ='fixe', conn=None):
|
||
Machine.__init__(self, parent_or_tuple, typ, conn)
|
||
|
||
|
||
class BorneWifi(Machine):
|
||
""" Classe de définition d'une borne wifi """
|
||
objectClass = "borneWifi"
|
||
|
||
def __init__(self, parent_or_tuple, typ='borne', conn=None):
|
||
Machine.__init__(self, parent_or_tuple, typ, conn)
|
||
if not isinstance(parent_or_tuple, tuple):
|
||
# Initialisaton par défaut spécifique
|
||
self._data['canal'] = ['2047']
|
||
self._data['puissance'] = ['60']
|
||
self._data['hotspot'] = ['FALSE']
|
||
|
||
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 hotspot(self, new=None):
|
||
""" Cette borne est-elle partagée avec l'ENS ? """
|
||
if new == None:
|
||
# Le schéma nous assure que ce champ existe toujours
|
||
return self._data['hotspot'][0] == "TRUE"
|
||
else:
|
||
if new:
|
||
self._set('hotspot', ['TRUE'])
|
||
else:
|
||
self._set('hotspot', ['FALSE'])
|
||
|
||
def canal(self, new=None, raw=False):
|
||
""" Attribution ou visualisation du canal d'une borne wifi """
|
||
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 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 position(self, new=False):
|
||
"""
|
||
Attribution ou visualisation de la position d'une borne wifi.
|
||
Renvoie un couple de coordonnées (en strings) si elles existent,
|
||
None sinon. new doit être un couple de coordonnées (en strings),
|
||
None (pour enlever les coordonnées) ou False (retourne les valeurs
|
||
actuelles).
|
||
On utilise des strings plutôt que des floats à cause de la précision.
|
||
"""
|
||
if new == False:
|
||
valeur = (self._data.get('positionBorne') or [''])[0]
|
||
if valeur:
|
||
return tuple(valeur.split(' '))
|
||
else:
|
||
return None
|
||
|
||
elif new == None:
|
||
self._set('positionBorne', [])
|
||
return None
|
||
|
||
else:
|
||
self._set('positionBorne', ' '.join(new))
|
||
return new
|
||
|
||
|
||
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:
|
||
if r[1].has_key('puissance'):
|
||
m.append(BorneWifi(r, '', s.conn))
|
||
else:
|
||
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)
|