diff --git a/gestion/affich_tools.py b/gestion/affich_tools.py new file mode 100755 index 00000000..1df2f234 --- /dev/null +++ b/gestion/affich_tools.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python +# -*- coding: iso-8859-15 -*- + +""" Collection de fonction/classe pour avoir un bel affichage + +Copyright (C) Frédéric Pauget +Licence : GPLv2 +""" + +import sys, sre, os, tempfile + +def dialog(backtitle,arg) : + """ Affiche la boite de dialogue défine avec les arguments fournis + (cf man dialog) + si tout se déroule bien retourne : + [ 0, [ reponse(s) ] ] + si annulatin retourne : + [ 1, [] ] + si appui sur ESC demande confirmation de l'abandon et exécute sys.exit(0) + si erreur dans les arguments raise RuntimeError + """ + f = tempfile.NamedTemporaryFile() + cmd = u'/usr/bin/dialog --backtitle "%s" --cancel-label "Retour" %s 2>%s' % (backtitle,arg,f.name) + res = os.system(cmd.encode('iso-8859-15','ignore')) + + if res == 256 : + # Annuler + f.close() + return [ 1, [] ] + + # Lecture du fichier de résultat et effacement + try: + result=f.readlines() + f.close() + except : + result = [ "n'importe quoi", ''] + res = 65280 + + # Traitement + if res==65280 and result: + # Erreur dans les arguments + raise RuntimeError( arg, result[1].strip() ) + elif res==65280 : + # Appui sur ESC + arg1 = u'--title "Annulation" --yesno "Quitter ?\nLes dernières modifications seront perdues." 6 48' + print backtitle + cmd = u'/usr/bin/dialog --backtitle "%s" %s' % (backtitle,arg1) + res = os.system(cmd.encode('iso-8859-15','ignore') ) + if res==0 : sys.exit(0) + else : return dialog(backtitle,arg) + elif not result : result=[''] + + return [ 0, result ] + +def coul(txt,col): + """ + Retourne la chaine donnée encadrée des séquences qui + vont bien pour obtenir la couleur souhaitée + Les couleur sont celles de codecol + Il est possible de changer la couleur de fond grace aux couleur f_ + """ + codecol={'rouge' : 31 , 'vert' : 32 , 'jaune' : 33 , 'bleu': 34 , 'violet' : 35 , 'cyan' : 36 , 'gras' : 50} + try : + if col[:2]=='f_' : add=10; col=col[2:] + else : add=0 + txt = "\033[1;%sm%s\033[1;0m" % (codecol[col]+add,txt) + finally : + return txt + +OK = coul('OK','vert') +WARNING = coul('WARNING','jaune') +ERREUR = coul('ERREUR','rouge') + +def cprint(txt,col): + print coul(txt,col) + +def tableau(largeurs,data) : + """ + retourne une chaine formatée repésentant un tableau + largeur est la liste des largeurs des colones + data est une liste de tuples : + [ ( données entète), (données ligne1), .... ] + """ + sep_col = u'|' + + # Ligne de séparation entète corps + s=u'\n' + for l in largeurs : + s+= sep_col + u'-'*l + s += sep_col + u'\n' + + nb_cols = len(largeurs) + + # Remplissage tableau + f=u'' + for ligne in data : + for i in range(0, nb_cols) : + f+= sep_col + # Centrage + l = len(sre.sub('\x1b\[1;([0-9]|[0-9][0-9])m','',ligne[i])) # Longeur sans les chaines de formatage + if l >= largeurs[i] : + f += ligne[i] + else : + n = largeurs[i] - l + f += u' '*(n/2) + ligne[i] + u' '*(n/2 + n%2) + f+= sep_col + u'\n' + + # Final + f = f.replace(u'\n',s,1) # Insertion du séparateur entète - corps + return f[:-1] # Supression du \n final + +def prompt(prompt, defaut=''): + """ Pose la question prompt, retourne la réponse """ + sys.stdout.write(coul(prompt,'gras')) + if defaut : + sys.stdout.write(" ["+defaut+"]") + sys.stdout.write(" ") + v=sys.stdin.readline().strip() + if not v : v = defaut + return v + +class anim : + """ Permet de créer une animation : + truc................./ + truc.................- + truc.................\ + truc.................| + ou une barre de progression si le nombre total d'itéraitions est founi. + """ + def __init__(self,truc,iter=0) : + """ Affichage de : + truc.................""" + self.txt = truc + '.'*(40-len(truc)) + self.c = 1 + self.iter = iter + sys.stdout.write(self.txt) + sys.stdout.flush() + + def reinit(self) : + """ Efface la ligne courrante et + affiche : truc................. """ + sys.stdout.write('\r' + self.txt) + if self.iter : + sys.stdout.write(' '*28) + sys.stdout.write('\r' + self.txt) + sys.stdout.flush() + + def cycle(self) : + """ Efface la ligne courrante et + affiche : truc..................? + ? caratère variant à chaque appel """ + sys.stdout.write('\r' + self.txt) + if self.iter!=0 : + sys.stdout.write('[') + av = float(self.c) / float(self.iter) + n = int(20 * av) + sys.stdout.write('='*n) + sys.stdout.write('>') + sys.stdout.write(' '*(20 - n)) + sys.stdout.write('] %3i%%' % int(100 * av) ) + else : + sys.stdout.write('/-\|'[self.c%4]) + sys.stdout.flush() + self.c += 1 diff --git a/gestion/annuaires.py b/gestion/annuaires.py new file mode 100755 index 00000000..a6f095e8 --- /dev/null +++ b/gestion/annuaires.py @@ -0,0 +1,508 @@ +#!/usr/bin/python +# -*- coding: iso8859-15 -*- + +aide={ +'g' : "appart du RDC=G901" , +'i' : "Chambres 209g + 309g + 105g + 403g + 110d + 312 + 007g +007d + locaux clubs sur prise 150.", +'h' : "Chambres 7 et 8 et les locaux clubs sur la prise 046" +} + +# Toute chambre ne commencant pas par 3 chiffres sera considéré comme un local club +# En conséquence les locaux club ne devront pas commencer par 3 chiffres. + +#Pour le G : +# le signe - indique un cable 10 Mbps +# et XXX = prise vide +#Les chambres G121, G241 et G201 ont chacune 2 cables en 100 Mbps qui +#sont branches sur les switchs. + +# Correspondance chbre -> prise +chbre_prises={ 'a' : +{'101d':'101d' , '101g':'101g' , '102':'102' , '103':'103' , + '104':'104' , '105':'105' , '106':'106' , '107':'107' , + '108':'108' , '109':'109' , '110d':'110d' , '110g':'110g' , + '111d':'111d' , '111g':'111g' , '112d':'112d' , '112g':'112g' , + '113d':'113d' , '113g':'113g' , '114':'114' , '115':'115' , + + '201d':'201d' , '201g':'201g' , '202':'202' , '203':'203' , + '204':'204' , '205':'205' , '206':'206' , '207':'207' , + '208':'208' , '209':'209' , '210d':'210d' , '210g':'210g' , + '211d':'211d' , '211g':'211g' , '212d':'212d' , '212g':'212g' , + '213d':'213d' , '213g':'213g' , '214':'214' , '215':'215' , + + '301d':'301d' , '301g':'301g' , '302':'302' , '303':'303' , + '304':'304' , '305':'305' , '306':'306' , '307':'307' , + '308':'308' , '309':'309' , '310d':'310d' , '310g':'310g' , + '311d':'311d' , '311g':'311g' , '312d':'312d' , '312g':'312g' , + '313d':'313d' , '313g':'313g' , '314':'314' , '315':'315' , + + '401d':'401d' , '401g':'401g' , '402':'402' , '403':'403' , + '404':'404' , '405':'405' , '406':'406' , '407':'407' , + '408':'408' , '409':'409' , '410d':'410d' , '410g':'410g' , + '411d':'411d' , '411g':'411g' , '412d':'412d' , '412g':'412g' , + '413d':'413d' , '413g':'413g' , '414':'414' , '415':'415' , + + '501d':'501d' , '501g':'501g' , '502d':'502d' , '502g':'502g' , + '503d':'503d' , '503g':'503g' , '504d':'504d' , '504g':'504g' , + '505d':'505d' , '505g':'505g' , '506d':'506d' , '506g':'506g' , + '507d':'507d' , '507g':'507g' , '507d':'507d' , '508g':'508g' , + + '601d':'601d' , '601g':'601g' , '602d':'602d' , '602g':'602g' , + '603d':'603d' , '603g':'603g' , '604d':'604d' , '604g':'604g' , + '605d':'605d' , '605g':'605g' , '606d':'606d' , '606g':'606g' , + '607d':'607d' , '607g':'607g' , '607d':'607d' , '608g':'608g' } , + +'b' : +{'cl0':'047' , 'cl1':'045' , + '105':'001' , '106':'002' , '107':'003' , '108':'004' , +'120g':'005' , '120d':'006' , '121g':'007' , '121d':'008' , + '205':'009' , '206':'010' , '207':'011' , '208':'012' , + '109':'013' , '110':'014' , '111':'015' , '112':'016' , +'122g':'017' , '122d':'018' , '123':'019' , '124':'020' , + '209':'021' , '210':'022' , '211':'023' , '212':'024' , + 'cl6':'025' , 'cl5':'026' , 'cl4':'027' , 'cl3':'028' , +'101g':'029' , '101d':'030' , '102g':'031' , '102d':'032' , + '113':'033' , '114':'034' , '115':'035' , '116':'036' , +'103g':'037' , '103d':'038' , '104g':'039' , '104d':'040' , + '117':'041' , '118':'042' , '119g':'043' , '119d':'044' , + +'220g':'101' , '220d':'102' , '221g':'103' , '221d':'104' , + '305':'105' , '306':'106' , '307':'107' , '308':'108' , +'320g':'109' , '320d':'110' , '321g':'111' , '321d':'112' , +'222g':'113' , '222d':'114' , '223':'115' , '224':'116' , + '309':'117' , '310':'118' , '311':'119' , '312':'120' , +'322g':'121' , '322d':'122' , '323':'123' , '324':'124' , +'201g':'125' , '201d':'126' , '202g':'127' , '202d':'128' , + '213':'129' , '214':'130' , '215':'131' , '216':'132' , +'301g':'133' , '301d':'134' , '302g':'135' , '302d':'136' , +'203g':'137' , '203d':'138' , '204g':'139' , '204d':'140' , + '217':'141' , '218':'142' , '219g':'143' , '219d':'144' , +'303g':'145' , '303d':'146' , '304g':'147' , '304d':'148' , + + '405':'201' , '406':'202' , '407':'203' , '408':'204' , +'420g':'205' , '420d':'206' , '421g':'207' , '421d':'208' , +'505g':'209' , '505d':'210' , '506g':'211' , '506d':'212' , + '411':'213' , '412':'214' , '410':'215' , '409':'216' , +'422g':'217' , '422d':'218' , '423':'219' , '424':'220' , +'507g':'221' , '507d':'222' , '508g':'223' , '508d':'224' , + '313':'225' , '314':'226' , '316':'227' , '315':'228' , +'401g':'229' , '401d':'230' , '402g':'231' , '402d':'232' , + '413':'233' , '414':'234' , '415':'235' , '416':'236' , + '317':'237' , '318':'238' , '319g':'239' , '319d':'240' , +'403g':'241' , '403d':'242' , '404g':'243' , '404d':'244' , + '417':'245' , '418':'246' , '419g':'247' , '419d':'248' , + +'511g':'301' , '511d':'302' , '512g':'303' , '512d':'304' , +'603g':'305' , '603d':'306' , '604g':'307' , '604d':'308' , +'609g':'309' , '609d':'310' , '610g':'311' , '610d':'312' , +'513g':'313' , '513d':'314' , '514g':'315' , '514d':'316' , +'605g':'317' , '605d':'318' , '606g':'319' , '606d':'320' , +'611g':'321' , '611d':'322' , '612g':'323' , '612d':'324' , +'501g':'325' , '501d':'326' , '502g':'327' , '502d':'328' , +'607g':'329' , '607d':'330' , '608g':'331' , '608d':'332' , +'613g':'333' , '613d':'334' , '614g':'335' , '614d':'336' , +'503g':'337' , '503d':'338' , '504g':'339' , '504d':'340' , +'509g':'341' , '509d':'342' , '510g':'343' , '510d':'344' , +'601g':'345' , '601d':'346' , '602g':'347' , '602d':'348' } , + +'c' : +{'312d':'001', '313g':'002' , '313d':'003' , '314':'004' , + '315':'005' , '401g':'006' , '401d':'007' , '402g':'008' , +'402d':'009' , '403g':'010' , '406g':'011' , '406d':'012' , +'407g':'013' , '407d':'014' , '408g':'015' , '408d':'016' , +'409g':'017' , '409d':'018' , '410g':'019' , '410d':'020' , +'411g':'021' , '411d':'022' , '414':'023' , '415':'024' , +'501g':'025' , '501d':'026' , '502':'027' , '503':'028' , +'601g':'029' , '601d':'030' , '602':'031' , '603':'032' , +'403d':'033' , '404g':'034' , '404d':'035' , '412g':'036' , +'412d':'037' , '413g':'038' , '413d':'039' , '405g':'040' , +'405d':'041' , '311d':'042' , '312g':'043' , '311g':'044' , +'310d':'045' , '310g':'046' , '309d':'047' , '309g':'048' , + +'103d':'101' , '111d':'102' , '103g':'103' , '111g':'104' , +'102d':'105' , '110d':'106' , '102g':'107' , '110g':'108' , +'101d':'109' , '109d':'110' , '101g':'111' , '109g':'112' , +'205d':'113' , '213d':'114' , '307g':'115' , '205g':'116' , +'213g':'117' , '306d':'118' , '204d':'119' , '212d':'120' , +'306g':'121' , '204g':'122' , '212g':'123' , '305d':'124' , +'203d':'125' , '211d':'126' , '305g':'127' , '203g':'128' , +'211g':'129' , '304d':'130' , '104g':'131' , '112g':'132' , +'206g':'133' , '214':'134' , '307d':'135' , '104d':'136' , +'112d':'137' , '105g':'138' , '113g':'139' , '105d':'140' , +'113d':'141' , '106g':'142' , '114g':'143' , '106d':'144' , +'114d':'145' , '107g':'146' , '201g':'147' , '107d':'148' , + +'304g':'201' , '303d':'202' , '303g':'203' , '210d':'204' , +'210g':'205' , '209d':'206' , '302d':'207' , '209g':'208' , +'208d':'209' , '302g':'210' , '208g':'211' , '301d':'212' , +'207d':'213' , '301g':'214' , '207g':'215' , '308d':'216' , +'206d':'217' , '215':'218' , '308g':'219' , '108d':'220' , +'202d':'221' , '108g':'222' , '202g':'223' , '201d':'224' } , + +'g' : +{'257':'001' , '180':'002-', '183':'003' , '135':'004' , + '298':'005' , '174':'006' , 'XXX':'007-', '138':'008' , + '136':'009' , '127':'010' , '176':'011' , '139':'012-' , + '132':'013' , '172':'014' , '137':'015' , '186':'016' , + '125':'017-', '181':'018' , '128':'019' , '178':'020' , + '175':'021' , '130':'022' , '177':'023' , '185':'024' , + '182':'025' , '131':'026' , '179':'027' , '299':'028' , + '259':'029-', '310':'030' , '300':'031' , '251':'032-' , + '295':'033' , 'XXX':'034-', '306':'035-', '058':'036' , + '304':'037' , '254':'038' , '261':'039' , '002':'040' , + '308':'041-', '252':'042' , '255':'043' , '256':'044' , + '253':'045' , '297':'046' , '260':'047' , '015':'048' , + + '247':'101-', '301':'102-', '249':'103' , '049':'104' , + '014':'105' , '054':'106' , '061':'107-', '010':'108' , + '005':'109' , '013':'110' , '006':'111' , '008':'112-' , + '009':'113-', 'XXX':'114-', '001':'115-', '052':'116-' , + '012':'117-', '053':'118' , '004':'119' , '066':'120' , + '076':'121' , '118':'122' , '119':'123' , '072':'124' , + '065':'125' , '073':'126' , '114':'127' , '112':'128' , + '123':'129' , '120':'130' , '068':'131' , '113':'132' , + 'XXX':'133' , '055':'134' , '117':'135-', '074':'136' , + '063':'137' , '077':'138' , '071':'139' , '111':'140' , + '121':'141' , '901':'142' , '122':'143' , '050':'144' , + '064':'145' , '067':'146' , '115':'147' , '124':'148' , + + '059':'201' , '258':'202' , '307':'203' , 'XXX':'204' , + '187':'205' , '246':'206-', '190':'207' , '193':'208' , + '195':'209' , '309':'210' , '189':'211' , '242':'212' , + '238':'213' , 'XXX':'214' , '241':'215' , '244':'216-' , + '198':'217-', '199':'218-', '192':'219' , '239':'220' , + '245':'221' , '303':'222' , '305':'223' , '191':'224' , + 'XXX':'225' , 'XXX':'226' , 'XXX':'227' , 'XXX':'228' , + 'XXX':'229' , 'XXX':'230' , 'XXX':'231' , 'XXX':'232' , + 'XXX':'233' , 'XXX':'234' , 'XXX':'235' , 'XXX':'236' , + 'XXX':'237' , 'XXX':'238' , 'XXX':'239' , 'XXX':'240' , + 'XXX':'241' , 'XXX':'242' , 'XXX':'243' , 'XXX':'244' , + 'XXX':'245' , 'XXX':'246' , 'XXX':'247' , 'XXX':'248' , + + '288':'401' , '204':'402' , '206':'403' , '207':'404-' , + '208':'405' , '210':'406' , '212':'407' , '213':'408-' , + '216':'409' , '217':'410' , '218':'411' , '220':'412' , + '222':'413' , '223':'414' , '224':'415' , '226':'416-' , + '231':'417' , '233':'418' , '234':'419' , '235':'420' , + '236':'421' , '237':'422-', '263':'423' , '264':'424' , + '265':'425' , '266':'426' , '267':'427' , '269':'428' , + '270':'429' , '271':'430' , '272':'431' , '273':'432' , + '274':'433' , '275':'434' , '277':'435' , '279':'436' , + '281':'437' , '282':'438' , '285':'439' , '286':'440' , + '287':'441' , '149':'442' , '289':'443' , '290':'444' , + '291':'445' , '292':'446' , '293':'447' , '221':'448' , + + '140':'501' , '141':'502' , '142':'503' , '143':'504' , + 'XXX':'505' , '147':'506-', '165':'507' , 'XXX':'508' , + '150':'509' , '151':'510' , '152':'511' , '153':'512' , + '154':'513' , '155':'514-', '156':'515' , '157':'516-' , + '158':'517' , '144':'518' , '160':'519' , '161':'520' , + '162':'521-', '163':'522' , '164':'523' , 'XXX':'524' , + '166':'525' , '167':'526-', '168':'527-', '169':'528' , + '280':'529' , '171':'530' , '101':'531' , '173':'532' , + '294':'533' , '026':'534' , '225':'535' , '031':'536' , + '146':'537' , '148':'538' , 'XXX':'539' , 'XXX':'540' , + 'XXX':'541' , 'XXX':'541' , 'XXX':'543' , 'XXX':'544' , + 'XXX':'545' , '096':'546-' , '903':'547' , '036':'548' , + + '106':'601-', '229':'602' , 'Med':'603' , '089':'604-' , + '092':'605' , '016':'606' , '017':'607' , '018':'608' , + '019':'609' , '200':'610' , '024':'611' , '025':'612' , + '028':'613' , '030':'614' , '034':'615' , '035':'616-' , + '037':'617' , '038':'618' , '039':'619' , '045':'620-' , + '041':'621' , '042':'622' , '043':'623' , '044':'624' , + 'XXX':'625' , '046':'626' , '078':'627-', '079':'628' , + '080':'629' , '081':'630' , '082':'631' , '083':'632-' , + '084':'633' , '085':'634-', '086':'635' , '088':'636-' , + '090':'637' , '091':'638' , '095':'639' , '097':'640-' , + '103':'641' , '102':'642' , '107':'643-', '109':'644' , + '227':'645' , '201':'646' , '202':'647' , '203':'648' } , + +'h' : +{'007g':'046' , '007d':'046' , '008g':'046' , '008d':'046' , + 'cl2':'046' , 'cl3':'046' , 'cl4':'046' , 'cl1':'046' , + '004g':'046' , + + '301':'101' , '302':'103' , '303':'105' , '304':'107' , + '305':'109' , '306':'111' , '307':'113' , '308':'115' , +'309g':'117' , '309d':'119' , '310g':'121' , '310d':'123' , +'311g':'125' , '311d':'127' , '312':'129' , '313':'131' , + '314':'133' , '315':'135' , '316':'137' , '317':'139' , + '318':'141' , '319':'143' , + +'401g':'102' , '401d':'104' , '402g':'106' , '402d':'108' , +'403g':'110' , '403d':'112' , '404g':'114' , '404d':'116' , +'405g':'118' , '405d':'120' , '406g':'122' , '406d':'124' , +'407g':'126' , '407d':'128' , '408g':'130' , '408d':'132' , +'409g':'134' , '409d':'136' , '410g':'138' , '410d':'140' , +'411g':'142' , '411d':'144' , + +'101g':'001' , '101d':'003' , '102g':'005' , '102d':'007' , +'103g':'009' , '103d':'011' , '104g':'013' , '104d':'015' , +'105g':'017' , '105d':'019' , '106g':'021' , '106d':'023' , +'107g':'025' , '107d':'027' , '108g':'029' , '108d':'031' , +'109g':'033' , '109d':'035' , '110g':'037' , '110d':'039' , + +'001g':'041' , '001d':'043' , '003g':'045' , '003d':'047' , +'004d':'048' , '005g':'145' , '005d':'147' , '006g':'146' , +'006d':'148' , + + '201':'002' , '202':'004' , '203':'006' , '204':'008' , + '205':'010' , '206':'012' , '207':'014' , '208':'016' , +'209g':'018' , '209d':'020' , '210g':'022' , '210d':'024' , +'211g':'026' , '211d':'028' , '212':'030' , '213':'032' , + '214':'034' , '215':'036' , '216':'038' , '217':'040' , + '218':'042' , '219':'044' } , + + 'i' : +{'110d':'150' , '403g':'150' , '105g':'150' , '209g':'150' , + '309g':'150' , '312':'150' , 'cl2':'150' , 'cl3':'150' , + 'cl4':'150' , 'cl1':'150' , '007g':'150' , '007d':'150' , + +'009d':'102' , '009g':'101' , '008d':'103' , '005d':'104' , +'008g':'105' , '005g':'106' , '006d':'107' , '004d':'108' , +'006g':'109' , '004g':'110' , '001g':'147' , '001d':'148' , +'002g':'145' , '002d':'146' , '003g':'143' , '003d':'144' , + +'107g':'114' , '103g':'111' , '106d':'112' , '102d':'128' , +'106g':'129' , '102g':'131' , '105d':'130' , '101d':'132' , +'101g':'133' , '103d':'134' , '107d':'135' , '104g':'136' , +'108g':'137' , '104d':'138' , '108d':'139' , '109g':'140' , +'109d':'141' , '110g':'142' , + + '217':'044' , '216':'041' , '213':'047' , '206':'125' , + '215':'043' , '208':'046' , '214':'045' , '207':'048' , + '212':'123' , '205':'124' , '211d':'126' , '204':'127' , +'211g':'121' , '203':'122' , '210d':'119' , '202':'120' , +'210g':'117' , '201':'118' , '209d':'115' , '219':'116' , + '218':'113' , + + '315':'034' , '308':'035' , '314':'036' , '307':'037' , + '313':'038' , '306':'040' , '305':'039' , '311d':'042' , + '304':'009' , '311g':'010' , '303':'008' , '302':'006' , +'310d':'004' , '310g':'002' , '301':'007' , '309d':'005' , + '319':'003' , '318':'001' , '317':'032' , '316':'033' , + +'405g':'021' , '411g':'022' , '405d':'019' , '411d':'020' , +'401g':'018' , '406g':'017' , '406d':'015' , '401d':'016' , +'407g':'013' , '402g':'014' , '402d':'012' , '407d':'011' , +'410d':'023' , '410g':'024' , '409d':'025' , '404d':'026' , +'404g':'028' , '409g':'027' , '408d':'029' , '403d':'030' , +'408g':'031' } , + + 'j' : +{'002d':'002d' , '002g':'002g' , '003d':'003d' , '003g':'003g' , + '004d':'004d' , '004g':'004g' , '005d':'005d' , '005g':'005g' , + + '101d':'101d' , '101g':'101g' , '102d':'102d' , '102g':'102g' , + '103d':'103d' , '103g':'103g' , '104d':'104d' , '104g':'104g' , + '105d':'105d' , '105g':'105g' , '106d':'106d' , '106g':'106g' , + '107d':'107d' , '107g':'107g' , '108d':'108d' , '108g':'108g' , + '109d':'109d' , '109g':'109g' , '110d':'110d' , '110g':'110g' , + '111d':'111d' , '111g':'111g' , '112d':'112d' , '112g':'112g' , + + '201':'201' , '202':'202' , '203':'203' , '204':'204' , + '205':'205' , '206':'206' , '207':'207' , '208':'208' , + '209':'209' , '210':'210' , '211d':'211d' , '211g':'211g' , + '212d':'212d' , '212g':'212g' , '213d':'213d' , '213g':'213g' , + '214':'214' , '215':'215' , '216':'216' , '217':'217' , + '218':'218' , '219':'219' , '220':'220' , '221':'221' , + '222':'222' , '223':'223' , + + '301':'301' , '302':'302' , '303':'303' , '304':'304' , + '305':'305' , '306':'306' , '307':'307' , '308':'308' , + '309':'309' , '310':'310' , '311d':'311d' , '311g':'311g' , + '312d':'312d' , '312g':'312g' , '313d':'313d' , '313g':'313g' , + '314':'314' , '315':'315' , '316':'316' , '317':'317' , + '318':'318' , '319':'319' , '320':'320' , '321':'321' , + '322':'322' , '323':'323' , + + '401d':'401d' , '401g':'401g' , '402d':'402d' , '402g':'402g' , + '403d':'403d' , '403g':'403g' , '404d':'404d' , '404g':'404g' , + '405d':'405d' , '405g':'405g' , '406d':'406d' , '406g':'406g' , + '407d':'407d' , '407g':'407g' , '408d':'408d' , '408g':'408g' , + '409d':'409d' , '409g':'409g' , '410d':'410d' , '410g':'410g' , + '411d':'411d' , '411g':'411g' , '412d':'412d' , '412g':'412g' , + '413d':'413d' , '413g':'413g' } , + + 'm' : +{'001':'001' , '002':'002' , '003':'003' , '004':'004' , + '005':'005' , '006':'006' , + + '101':'101' , '102':'102' , '103':'103' , '104':'104' , + '105':'105' , '106':'106' , '106b':'106b' , '107':'107' , + '108':'108' , '109':'109' , '110':'110' , '111':'111' , + '112':'112' , '113':'113' , '114':'114' , '115':'115' , + '116':'116' , '117':'117' , '118':'118' , '119':'119' , + '120':'120' , '121':'121' , '122':'122' , '123':'123' , + '124':'124' , '125':'125' , '126':'126' , '127':'127' , + '128':'128' , '129':'129' , '130':'130' , '131':'131' , + '132':'132' , '133':'133' , '134':'134' , '135':'135' , + '136':'136' , '137':'137' , '138':'138' , '138b':'138b' , + '139':'101' , '140':'140' , '141':'141' , '142':'142' , + '143':'101' , '144':'144' , '145':'145' , '146':'146' , + '147':'101' , '148':'148' , '149':'149' , '150':'150' , + '151':'101' , '152':'152' , '153':'153' , '154':'154' , + '155':'101' , '156':'156' , '157':'157' , '158':'158' , + '159':'101' , '160':'160' , '161':'161' , '162':'162' , + '163':'101' , '164':'164' , '165':'165' , '166':'166' , + '167':'101' , '168':'168' , + + '201':'201' , '202':'202' , '203':'203' , '204':'204' , + '205':'205' , '206':'206' , '206b':'206b' , '207':'207' , + '208':'208' , '209':'209' , '210':'210' , '211':'211' , + '212':'212' , '213':'213' , '214':'214' , '215':'215' , + '216':'216' , '217':'217' , '218':'218' , '219':'219' , + '220':'220' , '221':'221' , '222':'222' , '223':'223' , + '224':'224' , '225':'225' , '226':'226' , '227':'227' , + '228':'228' , '229':'229' , '230':'230' , '231':'231' , + '232':'232' , '233':'233' , '234':'234' , '235':'235' , + '236':'236' , '237':'237' , '238':'238' , '238b':'238b' , + '239':'201' , '240':'240' , '241':'241' , '242':'242' , + '243':'201' , '244':'244' , '245':'245' , '246':'246' , + '247':'201' , '248':'248' , '249':'249' , '250':'250' , + '251':'201' , '252':'252' , '253':'253' , '254':'254' , + '255':'201' , '256':'256' , '257':'257' , '258':'258' , + '259':'201' , '260':'260' , '261':'261' , '262':'262' , + '263':'201' , '264':'264' , '265':'265' , '266':'266' , + '267':'201' , '268':'268' , + + '301':'301' , '302':'302' , '303':'303' , '304':'304' , + '305':'305' , '306':'306' , '306b':'306b' , '307':'307' , + '308':'308' , '309':'309' , '310':'310' , '311':'311' , + '312':'312' , '313':'313' , '314':'314' , '315':'315' , + '316':'316' , '317':'317' , '318':'318' , '319':'319' , + '320':'320' , '321':'321' , '322':'322' , '323':'323' , + '324':'324' , '325':'325' , '326':'326' , '327':'327' , + '328':'328' , '329':'329' , '330':'330' , '331':'331' , + '332':'332' , '333':'333' , '334':'334' , '335':'335' , + '336':'336' , '337':'337' , '338':'338' , '338b':'338b' , + '339':'301' , '340':'340' , '341':'341' , '342':'342' , + '343':'301' , '344':'344' , '345':'345' , '346':'346' , + '347':'301' , '348':'348' , '349':'349' , '350':'350' , + '351':'301' , '352':'352' , '353':'353' , '354':'354' , + '355':'301' , '356':'356' , '357':'357' , '358':'358' , + '359':'301' , '360':'360' , '361':'361' , '362':'362' , + '363':'301' , '364':'364' , '365':'365' , '366':'366' , + '367':'301' , '368':'368' , + + '401':'401' , '402':'402' , '403':'403' , '404':'404' , + '405':'405' , '406':'406' , '406b':'406b' , '407':'407' , + '408':'408' , '409':'409' , '410':'410' , '411':'411' , + '412':'412' , '413':'413' , '414':'414' , '415':'415' , + '416':'416' , '417':'417' , '418':'418' , '419':'419' , + '420':'420' , '421':'421' , '422':'422' , '423':'423' , + '424':'424' , '425':'425' , '426':'426' , '427':'427' , + '428':'428' , '429':'429' , '430':'430' , '431':'431' , + '432':'432' , '433':'433' , '434':'434' , '435':'435' , + '436':'436' , '437':'437' , '438':'438' , '438b':'438b' , + '439':'401' , '440':'440' , '441':'441' , '442':'442' , + '443':'401' , '444':'444' , '445':'445' , '446':'446' , + '447':'401' , '448':'448' , '449':'449' , '450':'450' , + '451':'401' , '452':'452' , '453':'453' , '454':'454' , + '455':'401' , '456':'456' , '457':'457' , '458':'458' , + '459':'401' , '460':'460' , '461':'461' , '462':'462' , + '463':'401' , '464':'464' , '465':'465' , '466':'466' , + '467':'401' , '468':'468' , + + '501':'501' , '502':'502' , '502b':'502b' , '503':'503' , + '504':'504' , '505':'505' , '506':'506' , '507':'507' , + '508':'508' , '509':'509' , '510':'510' , '511':'511' , + '512':'512' , '513':'513' , '514':'514' , '515':'515' , + '516':'516' , '517':'517' , '518':'518' , '519':'519' , + '520':'520' , '521':'521' , '522':'522' , '523':'523' , + '524':'524' , '525':'525' , '526':'526' , '527':'527' , + '528':'528' } , + + 'p' : +{'101':'101' , '102':'102' , '103':'103' , '104':'104' , + '105':'105' , '106':'106' , '111':'111' , '112':'112' , + '113':'113' , '114':'114' , '115':'115' , '116':'116' , + '117':'117' , '118':'118' , '119':'119' , '120':'120' , + '121':'121' , '122':'122' , '123':'123' , '124':'124' , + '125':'125' , '126':'126' , '127':'127' , '128':'128' , + '129':'129' , '130':'130' , '131':'131' , + + '201':'201' , '202':'202' , '203':'203' , '204':'204' , + '205':'205' , '206':'206' , '211':'211' , '212':'212' , + '213':'213' , '214':'214' , '215':'215' , '216':'216' , + '217':'217' , '218':'218' , '219':'219' , '220':'220' , + '221':'221' , '222':'222' , '223':'223' , '224':'224' , + '225':'225' , '226':'226' , '227':'227' , '228':'228' , + '229':'229' , '230':'230' , '231':'231' , + + '301':'301' , '302':'302' , '303':'303' , '304':'304' , + '305':'305' , '306':'306' , '311':'311' , '312':'312' , + '313':'313' , '314':'314' , '315':'315' , '316':'316' , + '317':'317' , '318':'318' , '319':'319' , '320':'320' , + '321':'321' , '322':'322' , '323':'323' , '324':'324' , + '325':'325' , '326':'326' , '327':'327' , '328':'328' , + '329':'329' , '330':'330' , '331':'331' , + + '401':'401' , '402':'402' , '403':'403' , '404':'404' , + '405':'405' , '406':'406' , '411':'411' , '412':'412' , + '413':'413' , '414':'414' , '415':'415' , '416':'416' , + '417':'417' , '418':'418' , '419':'419' , '420':'420' , + '421':'421' , '422':'422' , '423':'423' , '424':'424' , + '425':'425' , '426':'426' , '427':'427' , '428':'428' , + '429':'429' , '430':'430' , '431':'431' } +} + +# Prises d'uplink, de machines du crans ou de bornes wifi +uplink_prises={ 'i' : +{ 49 : 'uplink->backbone' , 50 : 'uplink->bati1', + 149 : 'uplink->bati' } , +'h' : +{ 49 : 'uplink->backbone' , 50 : 'uplink->bath1', + 149 : 'uplink->bath' , 150 : 'pegase' } , +'g' : +{ 49 : 'uplink->backbone', 50 : 'uplink->batg1' , + 149 : 'uplink->batg' , 150 : 'uplink->batg2' , + 249 : 'uplink->batg1' , 250 : 'uplink->batg4' , + 449 : 'uplink->batg2' , 450 : 'uplink->batg5' , + 549 : 'uplink->batg4' , 550 : 'uplink->batg6' , + 649 : 'uplink->batg5' , 547 : 'wifi_lodur' } , +'b' : +{ 49 : 'uplink->backbone', 50 : 'uplink->batb1', + 149 : 'uplink->batb', 150 : 'uplink->batb2', + 249 : 'uplink->batb1', 250 : 'uplink->batb3', + 349 : 'uplink->batb2' }, +'c' : +{ 49 : 'uplink->backbone', 50 : 'uplink->batc1', + 149 : 'uplink->batc' , 150 : 'uplink->batc2', + 225 : 'uplink->batc1', 226 : 'locaux_clubs' } +} + +# Dictionnaire inverse +def reverse(bat) : + """ Retourne un dictionnaire : { prise : [ chambre(s) ] } """ + reverse={} + for chbre, prise in chbre_prises[bat].items() : + if reverse.has_key(prise) : + reverse[prise] += [ chbre ] + else : + reverse[prise] = [ chbre ] + return reverse + +# Locaux clubs : lecture dans chbre_prises et ajout des locaux dans les bats non +# manageables +def locaux_clubs() : + """ Retourne le dicctionaire des locaux club : { bat :[ locaux ] } """ + # Corespondance chbre -> nom du local club + locaux_clubs = { 'Bcl0' : 'Kfet' , + 'Bcl1' : 'Krobot', + 'Gcl0' : 'Med' , + 'Pcl0' : 'Bds' } + # Ajout des locaux d'étage A, B et C + for b in 'ABC' : + for i in range(2,7) : + locaux_clubs['%scl%i' % ( b, i)] = '%i@%s' % (i, b) + # Ajout de ceux des H, I et J + for b in 'HIJ' : + for i in range(1,5) : + locaux_clubs['%scl%i' % ( b, i)] = '%i@%s' % (i, b) + # Supression du 2@B et 4@J + locaux_clubs.pop('Bcl2') + locaux_clubs.pop('Jcl4') + + return locaux_clubs diff --git a/gestion/chgpass.py b/gestion/chgpass.py new file mode 100755 index 00000000..69b4d125 --- /dev/null +++ b/gestion/chgpass.py @@ -0,0 +1,41 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-15 -*- + +import getpass, commands, os + +import ldap_secret + +def chgpass(dn) : + print "Le mot de passe tapé ne sera pas écris à l'écran." + print "Taper Ctrl-C pour abandonner" + + try : + + while 1 : + mdp = getpass.getpass('Nouveau mot de passe :') + mdp1 = getpass.getpass('Retaper mot de passe :') + + if mdp != mdp1 : + print 'Les deux mots de passe entrés sont différents, réesayer' + continue + + # Test du mdp + test = commands.getoutput('echo "%s" | /usr/sbin/crack_testlib' % mdp) + + if test.split(':')[1] == ' ok' : + break + else : + print test.split(':')[1] + + # Changement mdp + if os.system('/usr/bin/ldappasswd -x -D "%s" -w %s "%s" -s %s > /dev/null' % (ldap_secret.auth_dn, ldap_secret.password, dn, mdp) ): + print 'Erreur lors du changement de mot de passe' + else : + print 'Changement effectué avec succès' + + except KeyboardInterrupt : + pass + +if __name__ == '__main__' : + print 'TODO : interface de sélection' + chgpass('aid=10,dc=crans,dc=org') diff --git a/gestion/config.py b/gestion/config.py new file mode 100644 index 00000000..bb34e479 --- /dev/null +++ b/gestion/config.py @@ -0,0 +1,101 @@ +# -*- python -*- +# -*- coding: iso-8859-15 -*- + +############################################ +## Définition du comportement des scripts ## +############################################ + +#Précablage possible ? +precab=1 + +#Bloquage si carte d'étudiant manquante pour l'année en cours +carte_et=1 + +##Création de comptes +# Gid des comptes créés +gid=100 +# Shell +login_shell='/bin/zsh' +# Longueur maximale d'un login +maxlen_login=15 + +# Année scolaire en cours +from time import localtime +dat = localtime() +if dat[1]<9 : ann_scol = dat[0]-1 +else : ann_scol = dat[0] + +## Quelques fichiers +# Log des destructions de machines ou d'adhérents +delete_log = '/tmp/delete.log' + +############################# +## Paramètres des machines ## +############################# + +## >>>>>>>>>>>>>>> La modification des paramètres suivants doit se +## >> ATTENTION >> faire avec précaution, il faut mettre la base à +## >>>>>>>>>>>>>>> jour en parralèle de ces modifs. + +# Sous réseaux alloués à chaque type de machine ou bâtiment +NETs = { 'wifi' : [ '138.231.149.0/24', '138.231.150.0/23' ], + 'b' : [ '138.231.137.0/24' ], + 'm' : [ '138.231.138.0/24' ], + 'c' : [ '138.231.139.0/24' ], + 'p' : [ '138.231.140.0/25' ], + 'a' : [ '138.231.140.128/25'], + 'g' : [ '138.231.141.0/24' ], + 'i' : [ '138.231.142.0/25' ], + 'j' : [ '138.231.142.128/25'], + 'h' : [ '138.231.143.0/24' ], + 'all' : [ '138.231.136.0/21', '138.231.148.0/22' ] } + +# Domaines dans lesquels les machines sont placées suivant leur type +domains = { 'wifi' : 'wifi.crans.org' , + 'fixe' : 'crans.org' , + 'borne': 'wifi.crans.org' } + +####################### +## Mail de bienvenue ## +####################### +#From est batx@crans.org ou respbat si adhérent extérieur +txt_mail_bienvenue = """From: Crans <%(From)s> +To: %(To)s +Subject: Bienvenue au Cr@ns ! + +Si tu lis ce mail, c'est que ton inscription à l'association est effective ! + +Rappel : Le site web de l'association est http://www.crans.org. + +Par ailleurs, toutes les informations concernant l'association sont +disponibles sur le WIKI à l'adresse http://wiki.crans.org + +Notamment, il est important de prendre le temps de lire la page : + http://wiki.crans.org/moin.cgi/CransPratique +Elle regroupe toutes les informations nécessaires à l'utilisation des +ressources de l'association. + +Sans lire attentivement ce document, l'accès au Web peut ne pas fonctionner. + +----- +L'accés aux news et au wiki sont limités à un usage interne au CRANS. +Pour y avoir accés depuis l'extérieur il faut utiliser un mot de passe: + - Pour les news : + * Utilisateur : Vivelapa + * Mot de passe : ranoia! + - Pour le wiki : + * Utilisateur : ViveLe + * Mot de passe : Wiki! +----- + +Sur ce, bienvenue au Cr@ns ! +-- +Les membres actifs.""" + +## Ports ouvers par défaut pour tous les adhérents +# A Priori stocké dans la base ldap quand Fred se sera bougé le cul +port_default = { 'tcp_input' : ['22','1024:'], + 'tcp_output': [':79','81:134','136','140:444','446:'], + 'udp_input' : [''], + 'udp_output': [':136','140:'] } + diff --git a/gestion/gen_confs/__init__.py b/gestion/gen_confs/__init__.py new file mode 100755 index 00000000..9ff1ee85 --- /dev/null +++ b/gestion/gen_confs/__init__.py @@ -0,0 +1,140 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-15 -*- + +""" Package pour la génération des fichiers de conf + +Copyright (C) Frédéric Pauget +Licence : GPLv2 +""" + +import sys, os, signal +sys.path.append('/usr/scripts/gestion') + +import time, commands +from affich_tools import * +from lock import * + +import config + +from tempfile import NamedTemporaryFile + +# Notes snmp +oid = '.1.3.6.1.4.1.2021.255' +try : + if sys.argv[1] == '-s' and sys.argv[2] == oid : + service = sys.argv[4].strip().strip('"') +except : + None +# Fin notes snmp + +class gen_config : + """ Base pour toutes les classes de génération de fichiers de conf """ + base = None + ann_scol = config.ann_scol + debug = 0 + _locked = 0 + __restore={} # pour restorer la config d'origine en cas d'erreur de génération + + def lock(self) : + """ Lock le service courant """ + if not self._locked : + make_lock(str(self.__class__),'') + self._locked = 1 + + def unlock(self) : + """ Supression du lock """ + if self._locked : remove_lock(str(self.__class__)) + + def __del__(self) : + # Au cas où... + self.unlock() + + def _restore(self) : + """ Affichage d'une erreur et du traceback si debug + Puis restauration des fichers """ + print ERREUR + if self.debug : + import traceback + traceback.print_exc() + # Restauration + for nom, f in self.__restore.items() : + os.system('cp -f %s %s' % ( f.name, nom ) ) + + def _open_conf(self,nom,comment=None) : + """ Créé un fichier + si comment est fourni, insère une entète qui utilisera le caractère + de commentaire fourni + + copie l'ancien fichier dans un fichier temporaire pour permettre + la restauration en cas d'échec de la configuration + + Retourne le descripteur du fichier """ + + f = NamedTemporaryFile() + os.system('cp %s %s 2> /dev/null' % ( nom, f.name ) ) + self.__restore[nom] = f + + fd = open(nom, 'w') + + if comment : + e = """*********************************************************** + Ce fichier est généré par les scripts de %s + Les données proviennent de la base LDAP et de la conf + présente au début du script. + + Génération : %s + Fichier : %s + + NE PAS EDITER + +***********************************************************""" % \ +(__name__, nom, time.strftime('%A %d %B %Y %H:%M') ) + + e = comment + e.replace('\n', '\n%s' % comment) + '\n' + fd.write(e) + + return fd + + def gen_conf(self) : + """ Génération des fichiers de conf, retourne 1 si erreur """ + self.lock() + self.anim = anim('\tgénération fichiers') + try : + warn = self._gen() + if warn : + self.anim.reinit() + print WARNING + if self.debug : sys.stderr.write(warn.encode("ISO-8859-15")) + else : + self.anim.reinit() + print OK + self.unlock() + except : + self.anim.reinit() + self._restore() + self.unlock() + return 1 + + def restart(self) : + """ Redémarrage du service concerné """ + self.lock() + self.anim = anim('\trestart') + status, output = commands.getstatusoutput(self.restart_cmd) + if status : + self.anim.reinit() + print ERREUR + if self.debug : + sys.stderr.write(output+'\n') + self.unlock() + return 1 + else : + print OK + self.unlock() + + def reconfigure(self) : + """ Génère les fichiers puis redémarre le service + si la génération c'est bien passée """ + cprint('Reconfiguration %s :' % self.__str__() , 'gras') + if not self.gen_conf() : + return self.restart() + else : return 1 diff --git a/gestion/gen_confs/bind.py b/gestion/gen_confs/bind.py new file mode 100755 index 00000000..03080192 --- /dev/null +++ b/gestion/gen_confs/bind.py @@ -0,0 +1,186 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-15 -*- + +""" Génération de la configuration pour bind9 + +Copyright (C) Frédéric Pauget +Licence : GPLv2 +""" + +# TODO : + # verif si SOA zamok.crans.org bon partout + # verif si liste NS identiques partout ok + # traitement warnings + +import time, sre +from gen_confs import gen_config + +class dns(gen_config) : + """ + Génération des fichiers de configuration de bind9 : + * fichier DNS_CONF qui contient les définitions de zone conformément +à zone_template. Ce fichier doit être inclus à partir de la config statique +de bind + * les fichiers de zones, ce sont eux qui contiennent les données du +dns, ils ont appellés par le fichier DNS_CONF et sont générés dans DNS_DIR +Leur entète est générée à partir de zone_entete. + + Les fichiers générés placent bind comme autoritaire sur les noms de +zones_direct et les adresses de zones_reverse. Les données proviennent de +la base LDAP + """ + ######################################PARTIE DE CONFIGURATION + + ### Fichiers à écrire + # Répertoire d'écriture des fichiers de zone + DNS_DIR='/etc/bind/generated/' # Avec un / à la fin + # Fichier de définition des zones + DNS_CONF=DNS_DIR + 'zones_crans' + + ### Sur quelles zones on a autorité ? + # Résolution directe + zones_direct = [ 'crans.org' , 'crans.ens-cachan.fr', 'wifi.crans.org' ] + # Résolution inverse (regexp pour définir les débuts des IP correspondantes) + zones_reverse = sre.compile('^138\.231\.1(3[6-9]|4[0-9]|5[01])') # 138.231.136.0 à 138.231.151.255 + + ### Liste DNS + # Le premier est doit être le maitre + DNSs = [ 'zamok.crans.org' , 'sila.crans.org' , 'freebox.crans.org' ] + + ### Serveurs de mail + # format : [ priorité serveur , .... ] + MXs = ['10 zamok.crans.org', '20 freebox.crans.org' ] + + ### Entète des fichiers de zone + zone_entete=""" +$ORIGIN %(zone)s. +$TTL 86400 +@\tIN\tSOA zamok.crans.org. root.crans.org. ( + %(serial)i ; numero de serie + 21600 ; refresh (s) + 3600 ; retry (s) + 1209600 ; expire (s) + 86400 ; TTL (s) + ) +""" + + # Syntaxe utilisée dans le fichier DNS_CONF pour définir une zone + zone_template=""" +zone "%(NOM_zone)s" { + type master; + file "%(FICHIER_zone)s"; +}; +""" + + ### Verbosité + # Si =1 ralera (chaine warnings) si machines hors zone trouvée + # Si =0 ralera seulement contre les machines ne pouvant être classées + verbose = 1 + + restart_cmd = '/etc/init.d/bind9 reload' + ######################################FIN PARTIE DE CONFIGURATION + + def __str__(self) : + return "DNS" + + def _gen(self) : + ### Génération du numéro de série + # Le + 1000.... s'explique pas l'idée précédente et peu pratique d'avoir + # le numéro de série du type AAAAMMJJNN (année, mois, jour, incrément par jour) + serial = time.time() + 1000000000 + + ### DNS + DNS='; DNS de la zone par ordre de priorité\n' + for d in self.DNSs : + DNS += '@\tIN\tNS %s.\n' % d + DNS += '\n' + + ### Serveurs de mail + MX='; Serveurs de mails\n' + for m in self.MXs : + MX += '%(zone)s.\t' # Sera remplacé par le nom de zone plus tard + MX += 'IN\tMX\t%s.\n' % m + MX += '\n' + + ### Tri des machines + direct = {} # format : { zone : [ lignes correspondantes] } + reverse = {} + warnings = '' + + self.anim.iter=len(self.machines) + for machine in self.machines : + self.anim.cycle() + + # Calculs préliminaires + try : + nom , zone = machine.nom().split('.',1) + zone = zone.encode('iso-8859-1') + except : + warnings += u'Machine ignorée (mid=%s) : format nom incorrect (%s)\n' % ( machine.id().encode('iso-8859-1'), machine.nom().encode('iso-8859-1') ) + continue + + # Le direct + if zone in self.zones_direct : + ligne = "%s\tIN\tA\t%s\n" % ( nom, machine.ip() ) + try : direct[zone] += ligne + except : direct[zone] = ligne + elif self.verbose : + warnings += u'Résolution directe ignorée (mid=%s) : zone non autoritaire (%s)\n' % ( machine.id().encode('iso-8859-1'), zone.encode('iso-8859-1') ) + + # Le direct avec alias + for alias in machine.alias() : + # Bon format ? + alias_l = alias.split('.') + ok = 0 + for i in range(len(alias_l)) : + zone_essai = '.'.join(alias_l[i:]) + if zone_essai in self.zones_direct : + # On est autoritaire sur cette zone + # On place donc l'alias dans le fichier de cette zone + zone = zone_essai + nom = '.'.join(alias_l[:i]) + ok = 1 + break + if not ok : + warnings += u'Alias ignoré (mid=%s) : %s\n' % ( machine.id().encode('iso-8859-1'), alias.encode('iso-8859-1') ) + continue + zone = zone.encode('iso-8859-1') + ligne = "%s\tIN\tCNAME\t%s.\n" % ( nom, machine.nom() ) + try : direct[zone] += ligne + except : direct[zone] = ligne + # Le reverse + if self.zones_reverse.match(machine.ip()) : + base_ip = machine.ip().split('.') + base_ip.reverse() + zone = "%s.%s.%s.in-addr.arpa" % tuple(base_ip[1:]) + zone = zone.encode('iso-8859-1') + ligne = '%s\tIN\tPTR\t%s.\n' % (base_ip[0],machine.nom()) + try : reverse[zone] += ligne + except : reverse[zone] = ligne + elif self.verbose : + warnings += u'Résolution inverse ignorée (mid=%s) : ip sur zone non autoritaire (%s)\n' % ( machine.id().encode('iso-8859-1'), machine.ip().encode('iso-8859-1') ) + + ### Ajouts pour les fichiers de résolution directs + for zone in direct.keys() : + # MXs + direct[zone] = MX % { 'zone' : zone } + direct[zone] + + ### Ecriture des fichiers de zone et préparation du fichier de définition + f = '' + for zone, lignes in direct.items() + reverse.items() : + file = self.DNS_DIR + 'db.' + zone + fd = self._open_conf(file,';') + fd.write(self.zone_entete % \ + { 'zone' : zone, 'serveur_autoritaire' : self.DNSs[0] , 'serial' : serial } ) + fd.write('\n') + fd.write(DNS) + fd.write(lignes) + fd.close() + f += self.zone_template % { 'NOM_zone' : zone, 'FICHIER_zone' : file } + + ### Ecriture fichier de définition + fd = self._open_conf(self.DNS_CONF,'//') + fd.write(f) + fd.close() + + return warnings diff --git a/gestion/gen_confs/blacklist_upload.py b/gestion/gen_confs/blacklist_upload.py new file mode 100755 index 00000000..b948f309 --- /dev/null +++ b/gestion/gen_confs/blacklist_upload.py @@ -0,0 +1,42 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-15 -*- + +from firewall import bl_upload_fw +from squid import bl_upload_squid + +class bl_upload : + """ Classe d'interface avec les classes spécifiques des + opération de configuration pour upload """ + debug = 0 + description = u'Bloquage de toute communiquation vers l\'extérieur.' + + def __str__(self) : + return "blackliste upload" + + def __init__(self) : + self.fw = bl_upload_fw() + + self.squid = bl_upload_squid() + + def __set(self) : + """ Attribution des proprietes des differentes classes """ + self.fw.base = self.base + self.fw.debug = self.debug + + self.squid.base = self.base + self.squid.debug = self.debug + + def reconfigure(self) : + self.__set() + self.fw.reconfigure() + self.squid.reconfigure() + + def restart(self) : + self.__set() + self.fw.restart() + self.squid.restart() + + def gen_conf(self) : + self.__set() + self.fw.gen_conf() + self.squid.gen_conf() diff --git a/gestion/gen_confs/dhcpd.py b/gestion/gen_confs/dhcpd.py new file mode 100755 index 00000000..3b267288 --- /dev/null +++ b/gestion/gen_confs/dhcpd.py @@ -0,0 +1,115 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-15 -*- + +""" Génération de la configuration pour le dhcp + +Copyright (C) Frédéric Pauget +Licence : GPLv2 +""" + +from iptools import AddrInNet, param +from gen_confs import gen_config + +class dhcp(gen_config) : + """ Génération du fichier de configuration pour dhcpd (DHCPD_CONF) + Le fichier comporte une partie par réseau servi, chaque réseau + servi doit être une clef du dictionnaire reseaux, la valeur correspondante + est une chaine décrivant les options spécifiques à ce réseau. + Les options communes sont celles de base_dhcp. + + Chaque machines possède ensuite une entrée de la forme de host_template + """ + ######################################PARTIE DE CONFIGURATION + + # Fichier à écire + DHCPD_CONF='/etc/dhcpd.conf' + + # Réseaux servis avec leurs options spécifiques + reseaux = { '138.231.136.0/21' : +"""option routers 138.231.136.4; + option domain-name-servers 138.231.136.6, 138.231.136.10, 138.231.136.9; + option domain-name "crans.org"; + option option-119 "crans.org wifi.crans.org";""", + + '138.231.148.0/22' : +"""option routers 138.231.148.1; + option domain-name-servers 138.231.148.1; + option domain-name "wifi.crans.org"; + option option-119 "wifi.crans.org crans.org";""" + } + + # Options communes à toutes les réseaux servis + base_dhcp=""" +subnet %(network)s netmask %(netmask)s { + default-lease-time 86400; + option subnet-mask %(netmask)s; + option broadcast-address %(broadcast)s; + %(OPTIONS_RESEAU)s + option time-servers 138.231.136.6; + option ntp-servers 138.231.136.6; + option smtp-server 138.231.136.6; + option netbios-name-servers 138.231.136.6; + option netbios-dd-server 138.231.136.6; + option netbios-node-type 8; + option ip-forwarding off; + option option-252 "http://www.crans.org/proxy.pac" ; + deny unknown-clients; + not authoritative; + %(HOSTs)s +} +""" + + host_template=""" + host %(nom)s { + hardware ethernet %(mac)s; + fixed-address %(ip)s; + option host-name "%(nom)s"; + } +""" + + ### Verbosité + # Si =1 ralera (chaine warnings) si machines hors zone trouvée + # Si =0 ralera seulement si réseau vide + verbose = 1 + + restart_cmd = '/etc/init.d/dhcp restart' + + ######################################FIN PARTIE DE CONFIGURATION + + def __str__(self) : + return 'dhcp' + + def _gen(self) : + warnings ='' + + ### Construction de la partie du fichier contenant les machines + hosts={} + + self.anim.iter=len(self.machines) + for machine in self.machines : + self.anim.cycle() + t = 0 + for net in self.reseaux.keys() : + if AddrInNet(machine.ip(),net) : + d = { 'nom' : machine.nom().split('.')[0] , 'mac' : machine.mac() , 'ip' : machine.ip() } + try : hosts[net] += self.host_template % d + except : hosts[net] = self.host_template % d + t = 1 + if not t and self.verbose : + warnings += u'Machine ignorée (mid=%s) : ip en dehors des réseaux servis (%s)\n' % ( machine.id(), machine.ip() ) + + ### Ecriture du fichier + fd = self._open_conf(self.DHCPD_CONF,'#') + for net, options in self.reseaux.items() : + if not hosts.has_key(net) : + warnings += u'Réseau %s ignoré : aucune machine à servir\n' % net + continue + d = param(net) + d['OPTIONS_RESEAU'] = options + d['HOSTs'] = hosts[net] + + fd.write(self.base_dhcp % d) + + fd.close() + + return warnings diff --git a/gestion/gen_confs/droits.py b/gestion/gen_confs/droits.py new file mode 100755 index 00000000..bcb82a20 --- /dev/null +++ b/gestion/gen_confs/droits.py @@ -0,0 +1,80 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-15 -*- + +import sys, signal +from gen_confs import gen_config, anim, cprint, OK, ERREUR + +sys.path.append('/usr/scripts/gestion') +from ldap_crans import crans_ldap, crans, ann_scol, preattr, ldap + +class droits(crans_ldap,gen_config) : + ####### Les groupes + base_group_dn = 'ou=Group,dc=crans,dc=org' + + # Quels droits donnent l'appartenacne à quel groupe ? + groupes = { 'adm' : [ u'Nounou' ] , + 'respbats' : [ u'Câbleur' , u'Déconnecteur', u'Nounou' ] , + 'moderateurs' : [ u'Modérateur' ] , + 'disconnect' : [ u'Déconnecteur' ] , + 'webcvs' : [ u'CVSWeb'] } + + ####### Les ML + # Le + devant un nom de ML indique une synchronisqtion + # ML <-> fonction partielle : il n'y a pas d'effacement + # des abonnés si le droit est retiré + mailing_listes = { 'roots' : [ u'Nounou', u'Apprenti' ], + '+nounou' : [ u'Nounou', u'Apprenti' ], + 'respbats' : [ u'Câbleur', u'Nounou' ], + 'moderateurs' : [ u'Modérateur' ], + 'disconnect' : [ u'Déconnecteur' ] } + + def restart(s) : + # Rien à faire + pass + + def __str__(self): + return "droits" + + def build_group(self) : + """ Reconstruit les groupes dans la base LDAP """ + self.anim.iter = len( self.groupes.keys() ) + for group, fonctions in self.groupes.items() : + self.anim.cycle() + # Qui doit être dans ce groupe ? + res = [] + for f in fonctions : + res += self.search('droits=%s' % f)['adherent'] + + # Récupération de la constitution du groupe actuel + dn = 'cn=%s,%s' % (group, self.base_group_dn) + data = self.conn.search_s(dn ,0,'objectClass=posixGroup')[0][1] + init_data = data.copy() + + # Supression de tout les membres + data['memberUid'] = [] + + # Ajout des bonnes personnes + for adher in res : + uid = preattr(adher.compte())[1] + if uid and uid not in data['memberUid'] : + data['memberUid'].append(uid) + + # Sauvegarde + modlist = ldap.modlist.modifyModlist(init_data,data) + self.conn.modify_s(dn,modlist) + + def gen_conf(self) : + self.anim = anim('\tconfiguration groupes') + try : + self.build_group() + self.anim.reinit() + print OK + except : + self.anim.reinit() + print ERREUR + if self.debug : + import traceback + traceback.print_exc() + + self.anim = anim('\tconfiguration ML Crans') + print 'TODO' diff --git a/gestion/gen_confs/firewall.py b/gestion/gen_confs/firewall.py new file mode 100755 index 00000000..07839ce8 --- /dev/null +++ b/gestion/gen_confs/firewall.py @@ -0,0 +1,60 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-15 -*- + +""" Génération de la configuration pour le firewall + +Copyright (C) Frédéric Pauget +Licence : GPLv2 +""" + +from gen_confs import gen_config +from time import localtime + +class firewall(gen_config) : + """ Génère le fichier de paires MAC-IP """ + # Fichier + MACIP = '/CRANS/generated/ether/pairesMAC-IP.txt' + + restart_cmd = '/etc/init.d/firewall macip' + + def __str__(self) : + return "firewall" + + def _gen(self) : + macip= self._open_conf(self.MACIP) + + self.anim.iter=len(self.machines) + for machine in self.machines : + self.anim.cycle() + macip.write( "%s %s\n" % ( machine.mac(), machine.ip() ) ) + + macip.close() + +class bl_upload_fw(gen_config) : + """ Génère le fichier de blackliste d'upload pour le firewall""" + # Fichier + BL_UPLOAD = '/tmp/bl_upload_fw' + + restart_cmd = '/etc/init.d/firewall blacklist' + + def __str__(self) : + return "blackliste upload firewall" + + def _gen(self) : + upload = self._open_conf( self.BL_UPLOAD, '#' ) + + if localtime()[1] == 9: + # On est en septembre, on autorise ceux qui ont payé l'an dernier et cette année + base = self.base.search('(paiement=%d|paiement=%d)' % (int(self.ann_scol), + int(self.ann_scol) - 1)) + else: + base = self.base.search('paiement=%s' % self.ann_scol) + for adh in ( [ self.crans ] + base['adherent'] + base['club'] ): + for machine in adh.machines() : + self.anim.cycle() + bl = machine.blacklist_actif() + if 'bl_upload' in bl and not 'bloq' in bl : + upload.write( '%s:smtp,smtps,pop3,pop3s,imap,imaps,http\n' % machine.nom() ) + + upload.close() + diff --git a/gestion/gen_confs/generate.py b/gestion/gen_confs/generate.py new file mode 100755 index 00000000..31bef6a9 --- /dev/null +++ b/gestion/gen_confs/generate.py @@ -0,0 +1,161 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-15 -*- + +import sys, signal, os, commands, getopt + +sys.path.append('/usr/scripts/gestion') + +from ldap_crans import crans_ldap, crans, ann_scol +from lock import * +from affich_tools import anim, cprint, OK, ERREUR, WARNING +from time import localtime +import config + +signal.signal(signal.SIGINT,signal.SIG_IGN) # Pas de Ctrl-C + +db = crans_ldap() +make_lock('auto_generate') + +##### Options fournies ? +try : + if len(sys.argv) > 1 : + options, arg = getopt.getopt(sys.argv[1:], '', ['quiet', 'home=', 'ML-ENS=', 'droits', 'switch=' , 'dhcp', 'dns', 'firewall' ]) + else : + options, arg = ( [],'') +except getopt.error, msg : + sys.stderr.write('%s\n' % msg) + sys.exit(255) + +debug = 1 # défaut +to_do = {} + +for opt, val in options : + if opt == '--quiet' : + debug = 0 + elif len(opt)>2 and opt[:2]=='--' : + to_do[opt[2:]] = [ val ] + +##### Lecture de la base LDAP si besion ce qu'il y a a faire et préparation +if not to_do : + if debug : print 'Lecture services à redémarrer dans la base LDAP' + to_do = db.services_to_restart() + auto = 1 +else : + auto = 0 + if debug : print 'Services à redémarrer imposés (non lecture de la base LDAP)' + +inst = [] +if 'home' in to_do.keys() : + if auto : db.services_to_restart('-home') + cprint('Création home','gras') + for args in to_do['home'] : + anim('\t' + args) + try : + home, uid, login = args.split(',') + os.mkdir(home, 0755) + os.chown(home, int(uid) ,config.gid) + os.mkdir(home + '/Mail', 0700) + os.chown(home + '/Mail', int(uid) ,config.gid) + status, output = commands.getstatusoutput('/usr/sbin/edquota -p pauget %s' % login ) + if status : + print WARNING + if debug : + sys.stderr.write(output+'\n') + else : + print OK + except : + print ERREUR + if debug : + import traceback + traceback.print_exc() + +if 'ML-ENS' in to_do.keys() : + db.services_to_restart('-ML-ENS') + cprint('Inscription ML-ENS','gras') + for mail in to_do['ML-ENS'] : + anim('\t'+mail) + status, output = commands.getstatusoutput("echo '%s' | /usr/sbin/add_members -r - com-ens >/dev/null 2>&1" % mail) + if status : + # Il y a eu une erreur + print ERREUR + if debug : + sys.stderr.write(output+'\n') + else : + print OK + +if 'droits' in to_do.keys() : + db.services_to_restart('-droits') + from gen_confs.droits import droits + a = droits() + a.debug = debug + a.reconfigure() + +if 'switch' in to_do.keys() : + if auto : db.services_to_restart('-switch') + from gen_confs.switchs import switch + a = switch(to_do['switch']) + a.debug = debug + a.reconfigure() + +# Les services suivants ont besoin de la liste des machines +# On va donc la lire une seule fois pour leur passer ensuite + +if 'firewall' in to_do.keys() : + # Quand sila et komaz liront la base LDAP + # db.services_to_restart('firewall-komaz') + # db.services_to_restart('firewall-sila') + db.services_to_restart('-firewall') + from gen_confs.firewall import firewall + inst.append(firewall()) + +if 'dns' in to_do.keys() : + db.services_to_restart('-dns') + from gen_confs.bind import dns + inst.append(dns()) + +if 'dhcp' in to_do.keys() : + from gen_confs.dhcpd import dhcp + db.services_to_restart('-dhcp') + inst.append(dhcp()) + +##### On fait ce qu'il reste à faire + +if inst : + ##### Récolte des données + cprint('Lecture base LDAP','gras') + # Machines de l'assoce + machines = crans().machines() + # Machines des adhérents et clubs de l'année en cours + if localtime()[1] == 9: + # On est en septembre, on autorise ceux qui ont payé l'an dernier et cette année + base = db.search('(paiement=%d|paiement=%d)' % (int(ann_scol), + int(ann_scol) - 1)) + else: + base = db.search('paiement=%s' % ann_scol) + base = base['adherent'] + base['club'] + a = anim('\ttri machines',len(base)) + for adh in base : + a.cycle() + # Adhérent ayant payé l'année en cours + if 'bloq' in adh.blacklist_actif() : + # Adhérent ignoré + continue + machines += adh.machines() + a.reinit() + print OK + + #### Reconfiguration des services + for i in inst : + i.debug = debug + i.machines = machines + try : + i.reconfigure() + except : + sys.stderr.write('Erreur dans le service %s\n' % i) + +if debug : + print 'Non traité ici mais signalé dans la base LDAP : \n\t', db.services_to_restart() + +signal.signal(signal.SIGINT,signal.SIG_DFL) # Comportement normal de Ctrl-C + +remove_lock('auto_generate') diff --git a/gestion/gen_confs/home.py b/gestion/gen_confs/home.py new file mode 100644 index 00000000..d5ce9b23 --- /dev/null +++ b/gestion/gen_confs/home.py @@ -0,0 +1,14 @@ + +home + home = args[0] + os.mkdir(home, 0755) + os.mkdir(home + '/Mail', 0700) + os.lchown(home, int(args[1]) ,config.gid) + os.system('/usr/sbin/edquota -p pauget %s' % arg[3] ) + +ML-ENS + if os.system("echo '%s' | /usr/sbin/add_members -r - com-ens" % mail) : + # Il y a eu une erreur + arg = u'--title "Inscription Mailing liste de communiquation ENS" ' + arg+= u'--msgbox "Une erreur s\'est produite lors de l\'ajout à la mailing liste.\n\n\n" 0 0' + dialog(arg) diff --git a/gestion/gen_confs/squid.py b/gestion/gen_confs/squid.py new file mode 100755 index 00000000..44039c3a --- /dev/null +++ b/gestion/gen_confs/squid.py @@ -0,0 +1,90 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-15 -*- + +""" Génération de la configuration pour squid """ + +from gen_confs import gen_config +from time import localtime + +class bl_carte_etudiant(gen_config) : + """ Génère le fichier de blackliste pour carte d'étudiant pour squid""" + BL_CARTE = '/tmp/bl_carte_et' + restart_cmd = '/etc/init.d/squid reload' + + def __str__(self) : + return "blackliste cartes d'étudiant" + + def _gen(self) : + fd=self._open_conf(self.BL_CARTE) + + if localtime()[1] == 9: + # On est en septembre, on autorise ceux qui ont payé l'an dernier et cette année + base = self.base.search('carteEtudiant!=%s&(paiement=%d|paiement=%d)' % (self.ann_scol, + int(self.ann_scol), + int(self.ann_scol) - 1)) + else: + base = self.base.search('paiement=%s' % self.ann_scol) + for adh in ( [ self.crans ] + base['adherent'] + base['club'] ): + for machine in adh.machines() : + self.anim.cycle() + if 'bloq' in machine.blacklist_actif() : continue + if machine.proprietaire().idn != 'aid' : continue + fd.write(machine.nom() + '\n') + + fd.close() + +class bl_upload_squid(gen_config) : + """ Génère le fichier de blackliste d'upload pour squid""" + # Fichier + BL_UPLOAD = '/tmp/bl_upload_squid' + restart_cmd = '/etc/init.d/squid reload' + + def __str__(self) : + return "blackliste upload squid" + + def _gen(self) : + upload = self._open_conf( self.BL_UPLOAD ) + + if localtime()[1] == 9: + # On est en septembre, on autorise ceux qui ont payé l'an dernier et cette année + base = self.base.search('(paiement=%d|paiement=%d)' % (int(self.ann_scol), + int(self.ann_scol) - 1)) + else: + base = self.base.search('paiement=%s' % self.ann_scol) + for adh in ( [ self.crans ] + base['adherent'] + base['club'] ): + for machine in adh.machines() : + self.anim.cycle() + bl = machine.blacklist_actif() + if 'bl_upload' in bl and not 'bloq' in bl : + upload.write( machine.nom() + '\n' ) + + upload.close() + +class bl_virus(gen_config) : + """ Génère le fichier de blackliste virus pour squid""" + # Fichiers + BL_VIRUS = '/tmp/bl_virus' + + def __str__(self) : + return "blackliste virus" + + restart_cmd = '/etc/init.d/squid reload' + + description = u'Bloquage accès http vers l\'extérieur, page expliquative' + + def _gen(self) : + virus = self._open_conf( self.BL_VIRUS ) + + if localtime()[1] == 9: + # On est en septembre, on autorise ceux qui ont payé l'an dernier et cette année + base = self.base.search('(paiement=%d|paiement=%d)' % (int(self.ann_scol), + int(self.ann_scol) - 1)) + else: + base = self.base.search('paiement=%s' % self.ann_scol) + for machine in base['machine'] : + self.anim.cycle() + bl = machine.blacklist_actif() + if 'bl_virus' in bl and not 'bloq' in bl : + virus.write( machine.nom() + '\n') + + virus.close() diff --git a/gestion/gen_confs/switchs.py b/gestion/gen_confs/switchs.py new file mode 100755 index 00000000..c04e652d --- /dev/null +++ b/gestion/gen_confs/switchs.py @@ -0,0 +1,372 @@ +#!/usr/bin/env python +# -*- coding: iso-8859-15 -*- + +""" met à jour les propriétés des prises des switchs du bat : + mac autorisée(s), état (activé ou non) et nom de la prise + argument : nom du switch + + procédure de configuration initiale : + * mot de passe admin (password manager user-name ) + * upgrade firmware (copy tftp flash 138.231.136.7 ) + * reboot (boot) + * génération clef ssh (crypto key generate ssh) + * copie fichier de conf (copy tftp startup-config 138.231.136.7 ) + * faire le stacking et le snmpv3 à la main + pour les reconfiguration juste copier le fichier de conf +""" + +import string, sys, os, commands + +sys.path.append('/CRANS/code') +#sys.path.append('/home/fred/Crans/zamok/CRANS/code') +from hptools import hp + +sys.path.append('/usr/scripts/gestion') +from ldap_crans import crans_ldap, ann_scol +from annuaires import chbre_prises #, uplink_prises, reverse +from gen_confs import gen_config, OK, ERREUR, anim +from time import localtime +# from qqch import switchs # Liste des switchs +bat_switchs = [ 'b', 'c', 'h', 'i', 'g' ] + +class switch(gen_config) : + def __init__(self,chbres): + """ Chbre doit être une liste de chambres """ + self.db = crans_ldap() + # On enlève la chambre "CRA" + self.chbres = [ch for ch in chbres if ch != "CRA"] + + def __str__(self) : + return 'switchs' + + def restart(self) : + # Rien à faire ici + pass + + def gen_conf(self) : + self.chbres.sort() + for chbre in self.chbres : + bat = chbre[0].lower() + a = self.db.search('chbre=%s' % chbre)['adherent'] + action = '' + for adh in a : + if ((ann_scol in adh.paiement() or + ((ann_scol-1) in adh.paiement and localtime()[1]==9)) and + 'bloq' not in adh.blacklist_actif()) : + # Il faut activer la prise + anim('\tactivation chbre %s' % chbre) + action = 'enable' + break + if action == '' : + # Il faut désactiver la prise + anim('\tdésactivation chbre %s' % chbre) + action = 'disable' + + try : + if bat in bat_switchs : + # Action sur le switch + prise = chbre_prises[bat][chbre[1:].lower()] + sw=hp(bat,int(prise[0])) + r = sw.set_prise(int(prise[1:4]),'disable') + sw.close() + if not r : + raise RuntimeError('Erreur de communiquation') + else : + # Mail au bat + To = "bat%s@crans.org" % bat + From = To + conn=smtplib.SMTP('localhost') + txt_mail = "From: Crans scripts <%(From)s>\n" + txt_mail+= "To: %(To)s\n" + txt_mail+= "Subject: Bienvenue au Cr@ns !\n\n" + txt_mail+= "Chambre %s à débrancher." % chbre + conn.sendmail(From, To , txt_mail % { 'From' : From, 'To' : To }) + conn.quit() + print OK + except : + print ERREUR + if self.debug : + import traceback + traceback.print_exc() + +### BROUILLON POUR PLUS TARD + +class switch2(gen_config) : + # Répertoire ou écire les fichiers de conf + CONF_REP='/tmp/' # avec un / derrière + + config = """; J4899A Configuration Editor; Created on release #H.07.32 + +hostname "%(switch)s" +;-------------------------------------------------------- Snmp +snmp-server contact "root@crans.org" +snmp-server location "Batiment %(bat)s" +;A faire à la main +;snmpv3 enable +;snmpv3 restricted-access +;snmpv3 user "initial" +;snmpv3 user "crans" +;snmpv3 group ManagerPriv user "crans" sec-model ver3 +;snmp-server community "public" Operator +;-------------------------------------------------------- Réglage heure/date +time timezone 60 +time daylight-time-rule Western-Europe +sntp server 138.231.136.6 +timesync sntp +sntp unicast +;-------------------------------------------------------- Misc +console inactivity-timer 30 +;-------------------------------------------------------- Logs +logging 138.231.136.7 +;-------------------------------------------------------- Logs +%(INTERFACES_CONF)s +;-------------------------------------------------------- IP du switch +ip default-gateway 138.231.136.4 +vlan 1 + name "DEFAULT_VLAN" + untagged 1-%(nb_prises)d + ip address %(ip)s 255.255.248.0 + ip igmp + no ip igmp querier + exit +;-------------------------------------------------------- Accès d'adminsitration +no web-management +aaa authentication ssh login public-key +ip ssh +ip ssh version 2 +ip authorized-managers 138.231.136.0 255.255.255.0 +ip authorized-managers 138.231.137.216 +ip authorized-managers 138.231.137.215 +;STACKING_CONF +;-------------------------------------------------------- Spanning-tree +spanning-tree +; Config des uplinks +no spanning-tree %(uplinks)s edge-port +; Config des prises adhérent +spanning-tree %(non_uplinks)s point-to-point-mac auto +spanning-tree %(non_uplinks)s priority 15 +no cdp run +;-------------------------------------------------------- Avec mot de passe ;) +password manager +""" + + interface_template = """interface %(prise)i\n%(etat)s + name %(nom)s + flow-control%(speed)s + no lacp +exit +""" + filtre_mac_template = "port-security %(prise)i learn-mode static address-limit 3 mac-address%(macs)s\n" + + nom = 'switchs' + + def __str__(self) : + return self.nom + + def __init__(self,qqch='') : + """ + Si qqch='' : reconfigure tous les switchs + Si qqch= (batX-N) : reconfigure que ce switch + Si qqch= (XNNN) : regénère le fichier de conf du switch correspondant + et reconfigure uniquement la prise """ + + # Traitement argument + if not qqch : + self.__params = {} + elif qqch in switchs : + self.__params = { 'bat' : qqch[3].lower() , + 'sw_num' : qqch[5] , + 'switch' : qqch.lower() } + self.nom = 'switch %(switch)s' % self.__params + else : + # Ca doit être une chambre + bat = qqch[0].lower() + if bat in mail_bats : + # Pas de switch à reconfigurer + self.__params = { 'chbre' : qqch.capitalize() , + 'bat' : bat } + else : + try : + prise = chbre_prises[bat][qqch[1:]] + self.__params = { 'nom_prise' : qqch.capitalize() , + 'sw_prise' : int(prise[1:]) , + 'bat' : bat , + 'sw_num': int(prise[0]), + 'switch': 'bat%s-%s' % (bat, prise[0]) } + self.nom = 'prise %s%s (chbre %s)' % (bat.upper(), prise,qqch) + except : + # Prise inconnue + raise RuntimeError("Impossible d'associer une prise à la chambre.") + + + def gen_conf(self) : + """ Génération configuration : + * soit d'un switch si présent dans self.__params + * soit de tous les switchs si aucun présent dans self.__params + * soit d'aucun switch si chbre dans bat non manageable, dans ce cas + envoi un mail quand il faut aller débrancher et retourne 2 + En cas d'erreur retourne 1 + """ + if self.__params.has_key('chbre') : + # Pas de switch manageable, il suffit de mailer + anim('\tmail à bat%(bat)s' % self.__params) + print 'TODO' + return 2 + + self.lock() + + if self.__params.has_key('switch') : + # Regénération de la conf d'un switch + self.__gen_switch(self.__params) + else : + # Regénération de la conf de tous les switchs + for switch in switchs : + params = { 'bat' : switch[3].lower() , + 'sw_num' : int(switch[5]) , + 'switch' : switch.lower() } + self.__gen_switch(params) + + self.unlock() + + def restart(self) : + """ + Si l'instance à été initialisée avec une chbre reconfigure cette chambre + Sinon reconfigure le switch founi, si aucun fourni, les reconfigure tous + """ + if self.__params.has_key('chbre') : + # Rien à faire, le mail doit être envoyé + return + + self.lock() + + if self.__params.has_key('sw_prise') : + # Utiliser la classe hptools et/ou faire du snmp + anim('\treconfiguration prise' ) + + elif self.__params.has_key('switch') : + # Restart d'un seul switch + self.__restart_switch(self.__params['switch']) + else : + # Restart tous les switchs + for switch in switchs : + self.__restart_switch(switch) + + self.unlock() + + def __gen_switch(self,params) : + """ params est un dictionnaire avec les keys bat, sw_num et switch """ + aff = anim('\tgénération de %(switch)s.conf' % params) + + try : + bat = params['bat'] + + # Nombre de prises + nb_prises = """ + METTRE CA DANS LA CLASSE S'OCCUPANT DU SNMP +try : + nb = int(sys.argv[2]) +except : + a=os.popen("snmpget -c public %s system.sysDescr.0 2>/dev/null" % switch) + try : + version = string.strip(string.split(string.split(a.readlines()[0],',')[0],'=')[1]) + except : + version = '' + a.close() + if version == 'HP J4900A ProCurve Switch 2626' : + nb=26 + elif version == 'HP J4899A ProCurve Switch 2650' : + nb=50 + else : + print "Erreur : impossible de déterminer le nombre de ports" + sys.exit(1) + """ + nb_prises = 50 + params['nb_prises'] = nb_prises + + # Récupération IP + params['ip'] = commands.getoutput("host %s" % switch).split()[-1] + + params['INTERFACES_CONF'] = '' + params['MAC_FILTER'] = '' + + # Dictionnaire prise -> chambre + prise_chbres = reverse(bat) + + # Configuration des prises + mac_filter=[] + aff.iter = nb_prises+1 + for prise in range(1,nb_prises+1): + aff.cycle() + + prise_params = { 'prise' : prise , 'speed' : '', 'etat' : '' } + annu_prise = '%i%02i' % (params['sw_num'], prise) + + if uplink_prises[bat].has_key(int(annu_prise)) : + ### Prise d'uplink + prise_params['nom'] = uplink_prises[bat][int(annu_prise)] + try : params['uplinks'] += ',%i' % prise + except : params['uplinks'] = str(prise) + else : + ### Prise adhérent + try : params['non_uplinks'] += ',%i' % prise + except : params['non_uplinks'] = str(prise) + + if prise_chbres.has_key(annu_prise) : + chbres = prise_chbres[annu_prise] + elif prise_chbres.has_key(annu_prise+'-') : + # Prise en 10 + prise_params['speed'] = ' speed-duplex auto-10\n' + chbres = prise_chbres[annu_prise+'-'] + else : + # Prise non référencée dans l'annuaire + prise_params['nom'] = "Pas_dans_l'annuaire" + prise_params['etat']=' disable\n' + chbres = [] + + if chbres : + prise_params['nom'] = 'Chambre' + if len(chbres) > 1 : + prise_params['nom'] += 's' + + for chbre in chbres : + prise_params['nom'] += '_' + chbre + + # Etat + macs = '' + machines = self.base.search('chbre=%s%s' % (bat.upper(), chbre) )['machine'] + for m in machines : + if 'bloq' in m.blacklist_actif() : continue + macs += ' ' + m.mac() + if not macs : + prise_params['etat']=' disable\n' + else : + params['MAC_FILTER'] += self.filtre_mac_template % vars() + + params['INTERFACES_CONF'] += self.interface_template % prise_params + + # Petites verif + if not params.has_key('uplinks') or not params.has_key('non_uplinks') : + raise RuntimeError('Switch sans uplink ou sans prise adhérent.') + + fd = self._open_conf(self.CONF_REP + 'switch' + '.conf') + fd.write(self.config % params) + fd.close() + + aff.reinit() + print OK + + except : + aff.reinit() + self._restore() + return 1 + + def __restart_switch(self,switch) : + anim('\trestart %s' % switch) + try : + print 'TODO' + except : + print ERREUR + if self.debug : + import traceback + traceback.print_exc() + return 1 diff --git a/gestion/gest_crans.py b/gestion/gest_crans.py new file mode 100755 index 00000000..ad7222a7 --- /dev/null +++ b/gestion/gest_crans.py @@ -0,0 +1,1540 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-15 -*- + +""" +Interface utilisateur du système de gestion des machines +et adhérents du crans + +Copyright (C) Frédéric Pauget +Licence : GPLv2 + +Les fonctions set_* permettent de définir certains +paramètres liés à un adhérent ou une machine, elles posent les questions +puis apellent la méthode adpatée de la classe adhérent ou machine. +Elles prennent toute une instances de cette classe en paramètre. +Elles retournent 1 si l'action à échoué et None si OK. +Les fonction select permettent de choisir un objet dans la base +Retournent None si pas d'objet trouvé. +""" + +import string, os, sys +from whos import aff +import time, signal, getopt + +import affich_tools, config +from lock import make_lock, remove_lock +from ldap_crans import adherent, machine, crans_ldap, crans, club, blacklist_items, isadm, isdeconnecteur, ann_scol, droits_possibles + +def dialog(arg) : + return affich_tools.dialog(u'Gestion des adhérents et machines du Crans',arg) + +######################################################################### +## Fonctions de remplissage ou modification des paramètres d'un adhérent + +def set_bases(adher) : + """ + Définition des paramètres de bases d'un adhérent : + * Nom + * Prenom + * Téléphone + * Chambre + """ + + # Construction de la boite de dialogue + arg = u'--title "Inscription adhérent" ' + arg+= u'--form "" 0 0 0 ' + arg+= u'"Nom :" 1 1 "%s" 1 13 20 20 ' % adher.nom() + arg+= u'"Prénom :" 2 1 "%s" 2 13 20 20 ' % adher.prenom() + arg+= u'"Numéro de téléphone :" 3 1 "%s" 3 23 15 00 ' % adher.tel() + arg+= u'"Chambre :" 4 1 "%s" 4 11 05 00 ' % adher.chbre() + arg+= u'"(bat+numéro)" 4 17 "" 0 0 0 0 ' + arg+= u'"EXT pour chambre extérieure au campus" 5 1 "" 0 0 0 0 ' + + # Affichage + annul , result = dialog(arg) + if annul : return 1 + + # Traitement + err = '' + try : adher.nom(result[0]) + except ValueError, c : err += c.args[0] + '\n' + + try : adher.prenom(result[1]) + except ValueError, c : err += c.args[0] + '\n' + + try : adher.tel(result[2]) + except ValueError, c : err += c.args[0] + '\n' + + try : c = adher.chbre(result[3]) + except ValueError, c : err += c.args[0] + '\n' + except EnvironmentError, c : err += c.args[0] + '\n' + + # Des erreurs ? + if err : + arg = u'--title "Inscription adhérent" ' + arg+= u'--msgbox "%s\n\n" 0 0' % err + dialog(arg) + # On redemande + return set_bases(adher) + + if c =='EXT' : + # Il faut demander l'adresse extérieure + if set_addr_ext(adher) : + # Annulation + return set_bases(adher) + +def set_addr_ext(adher) : + """ Définition de l'adresse extérieure d'un adhérent + La chambre de cet adhérent doit être EXT, sinon erreur """ + arg = u'--title "Adresse extérieure de %s" ' % adher.Nom() + arg+= u'--form "" 0 0 0 ' + arg+= u'"" 1 1 "%s" 1 1 46 50 ' % adher.adresse()[0] + arg+= u'"" 2 1 "%s" 2 1 46 50 ' % adher.adresse()[1] + arg+= u'"Code postal :" 3 1 "%s" 3 15 5 0 ' % adher.adresse()[2] + arg+= u'"Ville :" 3 21 "%s" 3 30 17 30 ' % adher.adresse()[3] + # Affichage + annul , result = dialog(arg) + if annul : return 1 + + try : adher.adresse(result) + except ValueError, c : + arg = u'--title "Adresse extérieure de %s" ' % adher.Nom() + arg+= u'--msgbox "%s\n\n\n" 0 0' % c.args[0] + dialog(arg) + # On redemande + return set_addr_ext(adher) + +def set_etudes(adher) : + """ + Définition des études en 3 questions : + * établissement + * année administrative + * section/labo + """ + + def __etudes_etab() : + arg = u'--title "Etudes de %s (1/3)" ' % adher.Nom() + if adher.etudes(0) : + arg+= u'--default-item "%s" ' % adher.etudes(0) + arg+= u'--menu "Choisissez l\'établissement : " 0 0 0 ' + if adher.etudes(0) : + arg+= u'"Autre" "" ' + arg+= u'"ENS" "" ' + arg+= u'"IUT Cachan" "" ' + arg+= u'"Maximilien Sorre" "" ' + arg+= u'"Gustave Eiffel" "" ' + arg+= u'"P1" "Université Panthéon Sorbonne" ' + arg+= u'"P2" "Université Panthéon Assas" ' + arg+= u'"P3" "Université de la Sorbonne Nouvelle" ' + arg+= u'"P4" "Université Paris Sorbonne" ' + arg+= u'"P5" "Université René Descartes" ' + arg+= u'"P6" "Université Pierre et Marie Curie" ' + arg+= u'"P7" "Université Denis Diderot" ' + arg+= u'"P8" "Université Vincennes Saint Denis" ' + arg+= u'"P9" "Université Paris Dauphine" ' + arg+= u'"P10" "Université de Nanterre" ' + arg+= u'"P11" "Université de Paris Sud (Orsay)" ' + arg+= u'"P12" "Université Val de Marne" ' + arg+= u'"P13" "Université Paris Nord" ' + arg+= u'"IUFM" "" ' + if not adher.etudes(0) : + arg+= u'"Autre" ""' + + annul , result = dialog(arg) + if annul : return 1 + + if result[0] == 'Autre' : + arg = u'--title "Etudes de %s (1/3)" ' % adher.Nom() + arg+= u'--inputbox "Précisez l\'établissement d\'études" 0 0 "%s"' % adher.etudes(0) + annul , result = dialog(arg) + if annul : return 1 + if result==[''] : + # Pas bon + arg = u'--title "Etudes (2/3)" ' + arg+= u'--msgbox "Réponse invalide\n\n\n" 0 0' + dialog(arg) + return __etudes_etab() + + etudes[0] = result[0] + + def __etudes_annee() : + arg = u'--title "Etudes de %s (2/3)" ' % adher.Nom() + if etudes[0]=='ENS' : + arg+= u'--default-item "%s" ' % adher.etudes(1) + arg+= u'--menu "Choisissez l\'année administrative" 0 0 0 ' + arg+= u'"1" "1ère année" ' + arg+= u'"2" "2ème année" ' + arg+= u'"3" "3ème année, agrégation" ' + arg+= u'"4" "DEA" ' + arg+= u'"5" "1ère année thèse" ' + arg+= u'"6" "2ème année thèse" ' + arg+= u'"7" "3ème année thèse" ' + arg+= u'"Autre" ""' + annul , result = dialog(arg) + if annul : return 1 + elif etudes[0] in [ 'P1','P2','P3','P4','P5','P6','P7','P8','P9','P10','P11','P12','P13' ] : + arg+= u'--default-item "%s" ' % adher.etudes(1) + arg+= u'--menu "Choisissez l\'année administrative" 0 0 0 ' + arg+= u'"Deug 1" "Deug 1ère année" ' + arg+= u'"Deug 2" "Deug 2ème année" ' + arg+= u'"License" "" ' + arg+= u'"Master 1" "Master 1ère année" ' + arg+= u'"Master 2" "Master 2ème année" ' + arg+= u'"Thèse 1" "1ème année thèse" ' + arg+= u'"Thèse 2" "2ème année thèse" ' + arg+= u'"Thèse 3" "3ème année thèse" ' + arg+= u'"Autre" ""' + annul , result = dialog(arg) + if annul : return 1 + else : + result=['Autre'] + + if result[0] == 'Autre' : + arg = u'--title "Etudes de %s (2/3)" ' % adher.Nom() + arg+= u'--inputbox "Année adminstrative :\nET UNIQUEMENT l\'ANNEE : la section sera demandée après." 0 0 "%s"' % adher.etudes(1) + annul , result = dialog(arg) + if annul : return 1 + if result==[''] : + # Pas bon + arg = u'--title "Etudes (2/3)" ' + arg+= u'--msgbox "Réponse invalide\n\n\n" 0 0' + dialog(arg) + return __etudes_annee() + else : + result[0] += '_' + + etudes[1] = result[0] + + def __etudes_section() : + arg = u'--title "Etudes de %s (3/3)" ' % adher.Nom() + + # Pour l'ENS + if etudes[0]=='ENS' : + arg+= u'--default-item "%s" ' % adher.etudes(2) + if etudes[1] in '1234' : + arg+= u'--menu "Choisissez la section : " 0 0 0 ' + arg+= u'"A1" "Mathématiques" ' + arg+= u'"A\'1" "Informatique" ' + arg+= u'"A2" "Physique fondamentale" ' + arg+= u'"A\'2" "Physique appliquée" ' + arg+= u'"A\'\'2" "Chimie" ' + arg+= u'"A3" "Biochimie" ' + if etudes[1] == '1' : + arg+= u'"B123" "Technologie mécanique" ' + else : + arg+= u'"B1" "Mécanique" ' + arg+= u'"B2" "Génie civil" ' + arg+= u'"B3" "Génie mécanique" ' + arg+= u'"B4" "Génie électrique" ' + arg+= u'"C" "Art et création industrielle" ' + arg+= u'"D2" "Economie gestion" ' + arg+= u'"D3" "Sciences sociales" ' + arg+= u'"E" "Anglais" ' + else : + arg+= u'--menu "Choisissez le laboratoire :" 0 0 0 ' + arg+= u'"CMLA" "Centre de Mathématiques et de Leurs Applications" ' + arg+= u'"GAPP" "Groupe d\'Analyse des Politiques Publiques" ' + arg+= u'"IDHE" "Institutions et Dynamiques Historiques de l\'Economie" ' + arg+= u'"LBPA" "Laboratoire de Biotechnologies et Pharmacologie génétique Appliquées" ' + arg+= u'"LMT" "Laboratoire de Mécanique et Technologie" ' + arg+= u'"LPQM" "Laboratoire de Photonique Quantique et Moléculaire" ' + arg+= u'"LSV" "Laboratoire de Spécification et Vérification" ' + arg+= u'"LURPA" "Laboratoire Universitaire de Recherche en Production Automatisée" ' + arg+= u'"PPSM" "Laboratoire de Photophysique et Photochimie Supramoléculaires et Macromoléculaires" ' + arg+= u'"SATIE" "Systèmes et Applications des Technologies de l\'Information et de l\'Energie" ' + arg+= u'"STEF" "Sciences Techniques Education Formation" ' + + arg+= u'"Autre" ""' + annul , result = dialog(arg) + if annul : return 1 + if result==[''] : + # Pas bon + arg = u'--title "Etudes (2/3)" ' + arg+= u'--msgbox "Réponse invalide\n\n\n" 0 0' + dialog(arg) + return __etudes_annee() + + else : + result=['Autre'] + + if result[0] == 'Autre' : + arg = u'--title "Etudes de %s (3/3)" ' % adher.Nom() + arg+= u'--inputbox "Section : " 0 0 "%s"' % adher.etudes(2) + annul , result = dialog(arg) + if annul : return 1 + if result==[''] : + # Pas bon + arg = u'--title "Etudes (3/3)" ' + arg+= u'--msgbox "Réponse invalide\n\n\n" 0 0' + dialog(arg) + return __etudes_section() + + etudes[2] = result[0] + + etudes=['','',''] + + step = 1 + while 1 : + if step == 1 : + if __etudes_etab() : return 1 + else : step +=1 + if step == 2 : + if __etudes_annee() : step -= 1 + else : step +=1 + if step ==3 : + if __etudes_section(): step -=1 + else : break + + try : + adher.etudes(etudes) + except ValueError, c : + arg = u'--title "Etudes de %s" ' % adher.Nom() + arg+= u'--msgbox "%s\n\n\n" 0 0' % c.args[0] + dialog(arg) + return set_etudes(adher) + +def set_mail(adher) : + """ + Choix d'une adresse mail crans ou extérieure. + Retourne ensuite le résultat de : + * set_mail_ext si adresse ext + * set_compte si compte crans + """ + + while 1 : + arg = u'--title "Adresse mail de %s" ' % adher.Nom() + arg+= u'--menu "Adresse mail de l\'adhérent :" 0 0 0 ' + arg+= u'"Adresse mail extérieure" "" ' + if adher.compte() : + arg+= u'"Laisser le compte sur zamok" "(login : %s)"' % adher.compte() + else : + arg+= u'"Créer un compte sur zamok" "(adresse @crans.org)"' + + annul , result = dialog(arg) + if annul : return 1 + + if result[0].split()[0]=='Laisser' : + break + elif result[0].split()[0]=='Créer' : + if not set_compte(adher) : break + else : + if not set_mail_ext(adher) : break + +def set_mail_ext(adher) : + """ + Demande l'adresse mail extérieure d'un adhérent + """ + default = adher.mail() + if default.find('@')==-1 : + # C'était une adresse crans + default = '' + + arg = u'--title "Adresse mail extérieure pour %s" ' % adher.Nom() + arg+= u'--inputbox "Adresse : " 0 0 "%s"' % default + annul , result = dialog(arg) + if annul : return 1 + + try : adher.mail(result[0]) + except ValueError, c : + arg = u'--title "Adresse mail extérieure de %s" ' % adher.Nom() + arg+= u'--msgbox "%s\n\n\n" 0 0' % c.args[0] + dialog(arg) + return set_mail_ext(adher) + +def set_compte(adher) : + """ + Créé un compte sur zamok pour un adhérent. + """ + # Message d'avertissement + arg = u'--title "Création compte sur zamok pour %s" ' % adher.Nom() + arg+= u'--colors --yesno "\Zr\Z1AVERTISSEMENT :\Zn \n' + arg+= u'L\'adhérent devra impérativement consulter l\'adresse mail associée\n\n\n\\ZnContinuer ?" ' + arg+= u'0 0' + no, res = dialog(arg) + if no : return 1 + + # Il faut déterminer le login + login = adher.nom() + + # Première tentative + err = 0 + try : login = adher.compte(login) + except EnvironmentError, c : err = 1 # Locké + except ValueError, c : + try : + c.arg[1] + # Le compte existe => 2ème tentative (1ere lettre prénom + nom) + login = adher.prenom()[0]+login + err =2 + except : err = 1 + + if err : + while 1: + # Mauvais login => on demande + arg = u'--title "Création d\'un compte sur zamok pour %s" ' % adher.Nom() + arg+= u'--inputbox "Choix du login\n' + arg+= u'Le login doit faire au maximum %s caractères\n' % config.maxlen_login + arg+= u'Il ne doit pas être un pseudo ou prénom mais doit être relié au nom de famille\n' + arg+= u'Seuls les caractères alphabétiques et le - sont autorisés" ' + arg+= u'0 0 "%s"' % login + annul , result = dialog(arg) + if annul : return 1 + login=result[0] + + e = 0 + try : login = adher.compte(login) + except EnvironmentError, c : e = c.args[0] + except ValueError, c : e = c.args[0] + if e : + arg = u'--title "Création compte sur zamok pour %s" ' % adher.Nom() + arg+= u'--msgbox "%s\n\n\n" 0 0' % e + dialog(arg) + continue + break + + txt = u"Le compte ne sera créé que lors de l'enregistrement des données\n\n" + txt+= u"L'adresse mail de l'adhérent est : %s@crans.org\n" % login + + a = adher.cannonical_alias() + if a : + txt+= u"L'adhérent possède également l'alias : %s@crans.org" % a + else : + txt+= u"\n\Zr\Z1L'adresse mail %s.%s@crans.org étant déja prise l'adhérent ne peux pas l'utiliser.\Zn" % ( adher.prenom(), adher.nom() ) + + txt+= u'\n' + + arg = u'--title "Création compte sur zamok pour %s" ' % adher.Nom() + arg+= u'--colors --msgbox "%s\n\n\n" 0 0' % txt + dialog(arg) + +def set_droits(adher) : + """ Définition des droits de l'adhérent """ + arg = u'--title "Droits de %s" ' % adher.Nom() + arg+= u'--separate-output ' + arg+= u'--checklist "" 0 0 0 ' + + droits = adher.droits() + for key in droits_possibles : + actif = key in droits + if actif : actif = u'on' + arg+= u'"%s" " " "%s" ' % ( key , actif) + + annul , result = dialog(arg) + if annul : return 1 + + # Traitement + adher.droits(result) + +def del_adher(adher) : + """ + Destruction adhérent + """ + quoi = u'Toutes les machines associées à cet adhérent seront détruites' + if adher.mail().find('@')==-1 : + # L'adhérent a un compte + machines = adher.machines() + if not machines : + quoi = u'Le compte de cet adhérent sera détruit' + else : + # Et aussi des machines + # -> soit on détruit tout + # -> soit on garde le compte mais on vire les machines + arg = u'--title "Destruction adhérent %s " ' % adher.Nom() + arg+= u'--menu "Cette adhérent possède également un compte." 0 0 0 ' + if adher : + arg+= u'"1" "Détruire seulement les machines de l\'adhérent" ' + arg+= u'"2" "Détruire les machines et le compte de l\'adhérent" ' + annul, res = dialog(arg) + if annul : return 1 + if res[0]=='2' : + # On détruit tout + quoi += u' ainsi que son compte' + else : + # Destruction uniquement des machines + arg = u'--title "Destruction machines adhérent %s " --colors ' % adher.Nom() + arg+= u'--inputbox "\Zr\Z1ATTENTION\Zn : la destruction est définitive\nToutes les machines associées à cet adhérent seront détruites, seul le compte sera conservé.\nCommentaire à insérer ?" 0 0' + annul, res = dialog(arg) + if annul : return 1 + for m in machines : + m.delete(res[0]) + adher.chbre('EXT') + arg = u'--title "Destruction machines" ' + arg+= u'--msgbox "Machines détruites\n\n\n" 0 0' + dialog(arg) + return + + arg = u'--title "Destruction adhérent %s " --colors ' % adher.Nom() + arg+= u'--inputbox "\Zr\Z1ATTENTION\Zn : la destruction est définitive\n\n%s.\n\nCommentaire à insérer ?" 0 0' % quoi + annul, res = dialog(arg) + if annul : return 1 + adher.delete(res[0]) + + arg = u'--title "Destruction adhérent" ' + arg+= u'--msgbox "Adhérent détruit\n\n\n" 0 0' + dialog(arg) + + +###################################################################################### +## Fonctions de remplissage ou modification des paramètres adhérent ou machine ou club +## (suivant la classe fournie) + +def set_rque(clas) : + """Définit le commentaire (méthode info de clas)""" + return __prompt_input_menu(clas.info,u'Remarque',u"Ajouter ou modifier une remarque\nPour ajouter une remarque modifier la dernière de la liste.") + +def __prompt_input_menu(method,titre,prompt) : + arg = u'--title "%s" ' % titre + arg+= u'--extra-label "Ajout/modif" ' + arg+= u'--inputmenu "%s\nPour supprimer, laisser le champ vide." 0 100 7 ' % prompt + invite = '##nouveau##' + + i=1 + for item in method() : + if item=='' : continue + arg+= u'"%d" "%s" '% (i, item) + i+= 1 + arg += '"%d" "%s" '% (i, invite) + + annul , result = dialog(arg) + if annul : + return 1 + + # si modif alors result=[ 'RENAMED X nouvelle remarque' ] + result = result[0].split(' ', 2) + if result[0]!='RENAMED' : + # Pas de modif + return + + # maintenant result=['RENAMED', 'X', 'nouvelle remarque'] + num = int(result[1]) + val = result[2].strip() + + if val == invite : + # Rien à faire + return + + try: + if num==i : + # Nouvelle valeur + method(val) + else : + method([num-1,val]) + + except ValueError, c : + arg = u'--title "%s" ' % titre + arg+= u'--msgbox "%s\n\n\n" 0 0' % c.args[0] + dialog(arg) + return __prompt_input_menu(method,titre,prompt) + +def confirm(clas) : + """ Demande confirmation avant enregistrement""" + # On va faire en texte, les couleurs ne passent pas en curses + os.system('clear') + aff(clas) + while 1 : + r = affich_tools.prompt("Valider et enregister ? [O/N]") + if r=='O' or r=='o' : + break + elif r=='N' or r=='n' : + return 1 + try : + res = clas.save() + except RuntimeError, c : + arg = u'--title "Enregistrement" ' + arg+= u'--msgbox "%s\n\n\n" 0 0' % c.args[0] + dialog(arg) + return 1 + + print res + affich_tools.prompt("Appuyez sur ENTREE pour continuer") + +def set_blackliste(clas) : + """ Edite ou ajoute un item de la blackliste """ + bl = clas.blacklist() + if not bl : + # Pas d'entrée à éditer + index = -1 + else : + arg = u'--title "Edition blackliste de %s" ' % clas.Nom() + arg+= u'--menu "Choisir l\'entrée à éditer :" 0 0 0 ' + arg+= u'"0" "Ajouter une nouvelle entrée" ' + i = 1 + for b in bl : + arg += '"%i" "%s" ' % (i, b) + i += 1 + annul , res = dialog(arg) + if annul : return 1 + index = int(res[0]) - 1 + + # Edition + if index != -1 : + t = clas.blacklist()[index].split(',') + else : + t = [ 'now' , '-', '', '' ] + + step = 1 + while 1 : + if step == 1 : + # Sanction + arg = u'--title "Blacklistage %s" ' % clas.Nom() + arg+= u'--default-item "%s" ' % t[2] + arg+= u'--menu "Choisir la sanction :" 0 0 0 ' + for n, c in blacklist_items : + arg+= u'"%s" "%s" ' % (n, c.description) + annul , res = dialog(arg) + if annul : return 1 + t[2] = res[0] + step += 1 + + if step == 2 : + # Dates + commentaire + arg = u'--title "Blacklistage %s" ' % clas.Nom() + arg+= u'--form "Entrez les dates de début et de fin (format jj/mm/aaaa) ainsi que le commentaire associé\n" 0 0 0 ' + arg+= u'"Début : " 1 1 "%s" 1 8 10 0 ' % t[0] + arg+= u'"now pour immédiatement " 1 20 "" 0 0 0 0 ' + arg+= u'"Fin : " 2 1 "%s" 2 8 10 0 ' % t[1] + arg+= u'"- pour fin indéterminée" 2 20 "" 0 0 0 0 ' + arg+= u'"Les jours de début et de fin sont inclus." 3 1 "" 0 0 0 0 ' + arg+= u'"Sanction : %s" 4 1 "" 0 0 0 0 ' % t[2] + arg+= u'"Commentaire : (le login sera automatiquement ajouté)" 5 1 "%s" 6 1 52 0 ' % t[3] + annul , r = dialog(arg) + if annul : return 1 + + # Ajout des heures + t[0] = r[0].strip() + if len(t[0]) == 10 : t[0] += ' 00:00' + t[1] = r[1].strip() + if len(t[1]) == 10 : t[1] += ' 23:59' + + # Commentaire + c = r[2].strip() + login = os.getlogin() + if c.split(' :')[0] != login : + t[3] = login + ' : ' + c + + try : + if index == -1 : + clas.blacklist(t) + else : + clas.blacklist( ( index, t ) ) + step += 1 + except ValueError, c : + arg = u'--title "Erreur" ' + arg+= u'--msgbox "%s\n\n\n" 0 0' % c.args[0] + dialog(arg) + step -= 1 + + if step == 3 : + if confirm(clas) : step -= 1 + else : break + +########################################################################## +## Fonction de remplissage ou modification des paramètres club ou adhérent + +def set_admin(proprio) : + """ + Définition de l'état administratif : carte d'étudiant et paiement + """ + # Le proprietaire a t-il une section carte d'étudiant ? + if proprio.idn != 'cid' : + card = 1 + else : + card = 0 + + # Initialisation + if card and ann_scol in proprio.carteEtudiant() : carte='on' + else : carte='off' + + if ann_scol in proprio.paiement() : paid_now='on' + else : paid_now='off' + + if ann_scol+1 in proprio.paiement() : precab='on' + else : precab='off' + + arg = u'--title "Etat administratif de %s" ' % proprio.Nom() + arg+= u'--separate-output ' + arg+= u'--checklist "" 0 0 0 ' + if card : + arg+= u'"1" "Carte d\'étudiant %d/%d fournie" "%s" ' % (ann_scol,ann_scol+1, carte) + arg+= u'"2" "Adhésion %d/%d réglée et charte signée" "%s" ' % (ann_scol,ann_scol+1,paid_now) + if config.precab : + arg+= u'"3" "Adhésion %d/%d réglée et charte signée (précâblage)" "%s" ' % (ann_scol+1,ann_scol+2,precab) + + annul , result = dialog(arg) + if annul : return 1 + + # Traitement + if card : + if '1\n' in result : proprio.carteEtudiant(ann_scol) + else : proprio.carteEtudiant(-ann_scol) + + if '2\n' in result : proprio.paiement(ann_scol) + else : proprio.paiement(-ann_scol) + + if '3\n' in result : proprio.paiement(ann_scol+1) + else : proprio.paiement(-ann_scol-1) + +############################################################### +## Fonctions de remplissage ou modification des paramètres club + +def set_club(club) : + step = 1 + while 1 : + if step == 1 : + # Responsable + resp = club.responsable() + if resp : + arg = u'--title "Responsable du club" ' + arg+= u'--yesno "\nLe responsable du club est-il toujours %s ?\n\n\n" 0 0' % resp.Nom() + no, res = dialog(arg) + if not resp or no : + arg = u'--title "Responsable du club" ' + arg+= u'--msgbox "Séléctionnez l\'adhérent responsable du club\n\n\n" 0 0' + dialog(arg) + resp = select(club,u'du responsable du club a','ro') + if not resp : return 1 + else : + club.responsable(resp) + step += 1 + else : + step += 1 + if step == 2 : + # Nom du club + arg = u'--title "Nom" ' + arg+= u'--inputbox "Nom du club ?" 0 0 "%s"' % club.Nom() + annul, res = dialog(arg) + if annul : step -= 1 + else : + try : + club.Nom(res[0]) + step += 1 + except ValueError, c : + arg = u'--title "Nom club" ' + arg+= u'--msgbox "%s\n\n\n" 0 0' % c.args[0] + dialog(arg) + + if step == 3 : + # Local + try : + club.chbre('&é"') # Fait une erreur + except ValueError, c : + locaux = c.args[1] + + arg = u'--title "Local du club" ' + arg+= u'--default-item "%s" ' % club.chbre() + arg+= u'--menu "Choisissez le local du club :" 0 0 0 ' + key = locaux.keys() + key.sort() + for k in key : + arg+= u'"%s" "%s" ' % ( k , locaux[k] ) + annul , result = dialog(arg) + if annul : step -= 1 + else : + club.chbre(result[0]) + step += 1 + + if step == 4 : + # Administratif + if set_admin(club) : step -=1 + else : step += 1 + + if step == 5 : + # Remarque + if set_rque(club) : step -= 1 + else : step += 1 + + if step == 6 : + # Confirmation + if confirm(club) : step -= 1 + else : break + +def select_club(clas) : + """ Choix d'un club """ + arg = u'--title "Recherche d\'unclub" ' + + clubs = clas.search('cid=*')['club'] + if not clubs : + # Pas de club dans la base + arg += '--msgbox "Il n\'y a pas de clubs inscrits dans la base\n\n\n" 0 0 ' + dialog(arg) + return + arg+= u'--menu "Choisissez un club :" 0 0 0 ' + for club in clubs : + arg += '"%s" "%s" ' % ( club.id(), club.Nom() ) + annul, res = dialog(arg) + if annul : return + return clas.search('cid=%s' % res[0],'w')['club'][0] + +def del_club(club) : + """ Destruction club """ + quoi ='Toutes les machines associées à cet adhérent seront détruites' + arg = u'--title "Destruction club " --colors ' + arg+= u'--inputbox "\Zr\Z1ATTENTION\Zn : la destruction est définitive\n\nToutes les machines de ce club seront également détruites.\n\nCommentaire à insérer ?" 0 0' + annul, res = dialog(arg) + if annul : return 1 + club.delete(res[0]) + arg = u'--title "Destruction club" ' + arg+= u'--msgbox "Club détruit\n\n\n" 0 0' + dialog(arg) + +################################################################## +## Fonctions de remplissage ou modification des paramètres machine + +def set_machine(machine) : + """ + Définition des paramètres d'une machine : + * Nom de machine + * Adresse MAC + * IP + * ports ouverts si adm + * Remarque + Si borne wifi demande aussi canal et puissance + """ + + if machine.proprietaire().__class__ == crans : + if not isadm : + arg = u'--title "Erreur" ' + arg+= u'--msgbox "Vous n\'avez pas les droits de modification de cette machine.\n\n" 0 0' + dialog(arg) + # On redemande + return 1 + nom = machine.nom() + l = 50 + else : + nom = machine.nom().split('.')[0] + l = 17 + + # Construction de la boite de dialogue + arg = u'--title "Paramètres machine" ' + arg+= u'--form "" 0 0 0 ' + arg+= u'"Nom de machine :" 1 1 "%s" 1 18 %i 0 ' % (nom, l) + arg+= u'"Adresse mac :" 2 1 "%s" 2 15 17 0 ' % machine.mac() + arg+= u'"IP :" 3 1 "%s" 3 6 15 0 ' % machine.ip() + if isadm : + arg+= u'"PortsTCP ext->machine :" 4 1 "%s" 4 25 50 0 ' % machine.portTCPin() + arg+= u'"PortsTCP machine->ext :" 5 1 "%s" 5 25 50 0 ' % machine.portTCPout() + arg+= u'"PortsUDP ext->machine :" 6 1 "%s" 6 25 50 0 ' % machine.portUDPin() + arg+= u'"PortsUDP machine->ext :" 7 1 "%s" 7 25 50 0 ' % machine.portUDPout() + if machine.puissance()!=None and isadm : + # Borne wifi + p = u'Mettre le lieu de la borne comme première remarque.' + arg+= u'"Canal :" 2 35 "%s" 2 43 2 0 ' % machine.canal() + arg+= u'"Puissance :" 3 35 "%s" 3 47 2 0 ' % machine.puissance() + else : + p = u'Pour ajouter une remarque modifier la dernière de la liste.' + + # Affichage + annul , result = dialog(arg) + if annul : return 1 + + # Traitement + err = '' + try : machine.nom(result[0]) + except ValueError, c : err += c.args[0] + '\n' + except EnvironmentError, c : err += c.args[0] + '\n' + + try : machine.mac(result[1]) + except ValueError, c : + if len(c.args)>1 and isadm : + # Mac en double + arg = u'--title "Adresse MAC" ' + arg+= u'--yesno "L\'adresse MAC existe déja, continuer ? \n" 0 0' + no, res = dialog(arg) + if no : + return set_machine(machine) + else : + try : machine.mac(result[1],1) + except ValueError, c : err += c.args[0] + '\n' + except EnvironmentError, c : err += c.args[0] + '\n' + else : + err += c.args[0] + '\n' + except EnvironmentError, c : err += c.args[0] + '\n' + + try : machine.ip(result[2]) + except ValueError, c : err += c.args[0] + '\n' + except EnvironmentError, c : err += c.args[0] + '\n' + except RuntimeError,c : err += c.args[0] + '\n' # Plus d'IP libres, peut-être à traiter differement ? + + if isadm : + try : + machine.portTCPin(result[3]) + machine.portTCPout(result[4]) + machine.portUDPin(result[5]) + machine.portUDPout(result[6]) + except ValueError, c : err += c.args[0] + '\n' + + if isadm and machine.puissance()!=None : + try : machine.canal(result[7]) + except ValueError, c : err += c.args[0] + '\n' + try : machine.puissance(result[8]) + except ValueError, c : err += c.args[0] + '\n' + + # Des erreurs ? + if err : + arg = u'--title "Paramètres machine" ' + arg+= u'--msgbox "%s\n\n" 0 0' % err + dialog(arg) + # On redemande + return set_machine(machine) + + if __prompt_input_menu(machine.info,u'Remarque',u"Entrez ou modifier une remarque\n%s\n" % p) : + return set_machine(machine) + + if machine.modifs and confirm(machine) : + return set_machine(machine) + +def del_machine(machine) : + """ + Destruction machine + """ + arg = u'--title "Destruction machine %s" --colors ' % machine.nom() + arg+= u'--inputbox "\Zr\Z1ATTENTION\Zn : la destruction est définitive\nCommentaire à insérer ?" 0 0' + annul, res = dialog(arg) + if annul : return 1 + try : + machine.delete(res[0]) + except EnvironmentError, c : + arg = u'--title "Destruction machine" ' + arg+= u'--msgbox "%s\n\n\n" 0 0' % c.args[0] + dialog(arg) + return 1 + + arg = u'--title "Destruction machine" ' + arg+= u'--msgbox "Machine détruite\n\n\n" 0 0' + dialog(arg) + + +#################################### +## Fonctions principales d'interface + +def all(adher) : + """ + Définition des propriétés d'un adhérent + 4 etapes : + * set_bases + * set_etudes + * set_admin + * set_mail + * set_rque + Retourne 1 si annulation. + """ + step = 1 + while 1 : + if step == 1 : + if set_bases(adher) : return 1 + else : step +=1 + if step == 2 : + if set_etudes(adher) : step -= 1 + else : step +=1 + if step == 3 : + if set_admin(adher) : step -= 1 + else : step +=1 + if step == 4 : + if set_mail(adher) : step -= 1 + else : step +=1 + if step == 5 : + if set_rque(adher) : step -= 1 + else : step +=1 + if step == 6 : + if confirm(adher) : step = 1 + else : break + + arg = u'--title "Inscription Mailing liste de communication ENS" --yesno "\nInscrire l\'adhérent à la mailing liste de communication de l\'ENS ?\n\n\n" 0 0' + no, res = dialog(arg) + if not no : + mail = adher.mail() + if mail.find('@')==-1 : mail += '@crans.org' + adher.services_to_restart('ML-ENS',[mail]) + +def modif_adher(adher) : + """ + Modification de l'adhérent fourni (instance de adhérent) + Retourne 1 si annulation. + """ + arg = u'--title "Modification de %s" ' % adher.Nom() + arg+= u'--menu "Que souhaitez vous modifier ?" 0 0 0 ' + arg+= u'"Admistratif" "Précâblage, carte d\'étudiant, études" ' + if adher.chbre() == 'EXT' : + arg+= u'"Adresse" "Déménagement" ' + else : + arg+= u'"Chambre" "Déménagement" ' + arg+= u'"Etudes" "Changement d\'année ou de filière" ' + arg+= u'"Téléphone" "Changement de numéro de téléphone" ' + arg+= u'"Mail" "Créer un compte ou changer l\'adresse mail de contact" ' + arg+= u'"Alias" "Créer ou supprimer un alias mail" ' + arg+= u'"Remarque" "Ajouter ou modifer un commentaire" ' + if isadm : + arg+= u'"Droits" "Modifier les droits alloués à cet adhérent" ' + if isdeconnecteur : + arg+= u'"Blackliste" "Modifier la blackliste de cet adhérent" ' + annul, res = dialog(arg) + + if annul : return 1 + + if res[0]=='Etudes' : + set_etudes(adher) + elif res[0]=='Admistratif' : + if not set_admin(adher) : + set_etudes(adher) + elif res[0]=='Mail' : + set_mail(adher) + elif res[0]=='Remarque' : + set_rque(adher) + elif res[0]=='Droits' : + set_droits(adher) + elif res[0]=='Blackliste' : + set_blackliste(adher) + elif res[0]=='Chambre' : + while 1 : + arg = u'--title "Déménagement de la %s" ' % adher.chbre() + arg+= u'--colors --inputbox "Nouvelle chambre ? (EXT si extérieur au campus)\n\n' + arg+="\Zr\Z1ATTENTION\Zn : Un changement de bat entrainera un changement d'IP des machines fixes\n" + arg+= u'" 0 0 ' + annul,res = dialog(arg) + if annul : return 1 + + e=0 + try : + c = adher.chbre(res[0]) + if c =='EXT' : + # Il faut demander l'adresse extérieure + if set_addr_ext(adher) : + # Annulation + continue + break + except EnvironmentError, c : e = c.args[0] + except ValueError, c : e = c.args[0] + if e : + arg = u'--title "Déménagement de la %s" ' % adher.chbre() + arg+= u'--msgbox "%s\n\n\n" 0 0' % e + dialog(arg) + elif res[0]=='Adresse' : + arg = u'--title "Déménagement de %s" ' % adher.Nom() + arg+= u'--menu "Question :" 0 0 0 ' + arg+= u'"1" "Déménagement à l\'extérieur ?" ' + arg+= u'"2" "Déménagement sur le campus ? " ' + annul , result = dialog(arg) + if annul : return 1 + if result[0]=='1' : + if set_addr_ext(adher) : + # Annulation + return 1 + else : + while 1 : + arg = u'--title "Déménagement de %s" ' % adher.Nom() + arg+= u'--inputbox "Chambre ?" 0 0 ' + annul,res = dialog(arg) + if annul : return 1 + + e=0 + try : + c = adher.chbre(res[0]) + if c =='EXT' : + # Il faut demander l'adresse extérieure + if set_addr_ext(adher) : + # Annulation + continue + break + except EnvironmentError, c : e = c.args[0] + except ValueError, c : e = c.args[0] + if e : + arg = u'--title "Déménagement de %s" ' % adher.Nom() + arg+= u'--msgbox "%s\n\n\n" 0 0' % e + dialog(arg) + + elif res[0]=='Téléphone' : + while 1 : + arg = u'--title "Changement numéro de téléphone de de %s" ' % adher.Nom() + arg+= u'--inputbox "Nouveau numéro ?" 0 0 "%s" ' % adher.tel() + annul,res = dialog(arg) + if annul : return 1 + + try : + adher.tel(res[0].replace(' ','')) + break + except ValueError, c : + arg = u'--title "Changement numéro de téléphone de de %s" ' % adher.Nom() + arg+= u'--msgbox "%s\n\n\n" 0 0' % c.args[0] + dialog(arg) + + elif res[0]=='Alias' : + __prompt_input_menu(adher.alias,'Alias mail', "Entrez ou modifier un alias mail.\nPour ajouter un alias modifier le dernier de la liste.") + + if adher.modifs : + return confirm(adher) + +######################################################################## +## Fonction de sélection (adhérent ou machine) + +def select(clas,quoi,mde='') : + """ + Interface de choix d'un adhérent, d'une machine ou d'un club + Retourne une instance de la classe choisie. + quoi est la chaine utilisée pour la demande (%sid=?) + exemples : quoi = 'adhérent a' => Votre choix ? adhérent aid=: + il faut que le dernier caractère de quoi soit a pour adhérent ou m pour machine + Retourne None si annulation. + + si m = ro ouvre l'objet en mode read-only + """ + + s=['','','','','','','','','',''] + def f(a) : + try: + return unicode(a,'iso-8859-15') + except: + return a + while 1 : + s = map(f,s) + arg = u'--title "Recherche %s" ' % ' '.join(quoi.split()[:-1]) + arg+= u'--help-button ' + arg+= u'--form "Entrez vos paramètres de recherche" 0 0 0 ' + arg+= u'"Nom :" 1 1 "%s" 1 13 20 20 ' % s[0] + arg+= u'"Prenom :" 2 1 "%s" 2 13 20 20 ' % s[1] + arg+= u'"Téléphone :" 3 1 "%s" 3 13 10 00 ' % s[2] + arg+= u'"Chambre :" 4 1 "%s" 4 13 05 00 ' % s[3] + arg+= u'"aid :" 5 1 "%s" 5 13 5 5 ' % s[4] + arg+= u'"Login / mail :" 6 1 "%s" 6 16 30 00 ' % s[5] + arg+= u'"Machine :" 1 35 "" 0 0 0 0 ' + arg+= u'"Nom :" 2 37 "%s" 2 43 17 17 ' % s[6] + arg+= u'"Mac :" 3 37 "%s" 3 43 17 17 ' % s[7] + arg+= u'"IP :" 4 37 "%s" 4 43 15 15 ' % s[8] + arg+= u'"mid :" 5 37 "%s" 5 43 5 5 ' % s[9] + arg+= u'"Les champs vides sont ignorés." 7 1 "" 0 0 0 0' + + annul , result = dialog(arg) + if annul : return + + if result[0][:4]=='HELP' : + arg = u'--title Aide ' + arg+= u'--msgbox "Il n\'est pas necessaire de remplir tous les champs.\n' + arg+= u'Il est possible d\'utiliser les * dans les champs de recherche.\n\n\n" 0 0' + dialog(arg) + continue + + s=[] + for i in result : + i = i.strip() + if not i : i=u'*' + s.append(i) + + ### Contruction de la chaîne de recherche + filtre_adher = 'nom=%s&prenom=%s&tel=%s&chbre=%s&aid=%s&mail=%s&' % tuple(s[:6]) + filtre_machine = 'host=%s&macAddress=%s&ipHostNumber=%s&mid=%s&' % tuple(s[6:]) + + filtre='' + if filtre_adher.count('=*&') != 6 : + # Au moins une condition adhérent + filtre += filtre_adher[:-1] + if filtre_machine.count('=*&') != 4 : + # Au moins une condition machine + if filtre : filtre += '&' + filtre += filtre_machine[:-1] + if filtre == '' : + # Aucune condion => erreur + arg = u'--title "Recherche" ' + arg+= u'--msgbox "Il faut au moins une condition.\n\n\n" 0 0' + dialog(arg) + continue + + ### Recherche + try: + if mde =='ro' : + res = clas.search(filtre,'ro') + else : + res = clas.search(filtre,'w') + except ValueError : + arg = u'--title "Recherche" ' + arg+= u'--msgbox "Caractère interdit.\n\n\n" 0 0' + dialog(arg) + continue + + # Affichage + if quoi[-1] == 'a' : + valid = res['adherent'] + if not valid and res['machine'] : + # On va récupérer les adhérents correspondants aux machines trouvés + deja=[] + for m in res['machine'] : + a = m.proprietaire() + if a.id() in deja : continue + deja.append(a.id()) + valid.append(a) + elif quoi[-1] =='m' : + valid = res['machine'] + if not valid and res['adherent'] : + # On va récupérer les machines des adhérents trouvés + for a in res['adherent'] : + for m in a.machines() : + valid.append(m) + else : + raise TypeError('Argument fonction invalide') + + if not valid or ( len(valid)==1 and quoi[-1] == 'a' and valid[0].__class__ == crans ) : + arg = u'--title "Recherche" ' + arg+= u'--msgbox "Aucun résultat.\n\n\n" 0 0' + dialog(arg) + # Retour au formulaire + continue + + if len(valid)==1 : + # Une seule réponse + choix = valid[0] + else : + # Il faut choisir + while 1 : + os.system('clear') + choix = None + print "Plusieurs réponses correspondant à votre requête ont été trouvées :" + aff(valid) + txt = u'Votre choix ? (0 pour annuler) %sid =' % quoi + i = affich_tools.prompt(txt.encode('iso-8859-15')) + if i == '0' : break + for v in valid : + if v.id() == i : + choix = v + break + if not choix : + # Redemande le choix + print 'Choix invalide' + continue + if choix : break + + if not choix : + # Retour à l'interface de recherche + continue + + os.system('clear') + print "Sélection : " + aff(choix) + + while 1 : + r = affich_tools.prompt('Confirmer sélection ? [O/N]' ) + if r=='O' or r=='o' : + break + elif r=='N' or r=='n' : + # Annulation du choix + choix = None + break + print 'Répondre O ou N' + # Retour à la confirmation + + if choix : + if mde!='ro' and not choix._modifiable : + arg = u'--title "Recherche" ' + arg+= u'--msgbox "Objet sélectionné locké, attendre.\n\n\n" 0 0' + dialog(arg) + return + return choix + # Sinon retour interface de sélection + +def menu_principal() : + """ + Affiche le menu de choix initial + """ + proprio = None + becane = None + choix = None + while 1 : + arg = u'--title "Menu principal" ' + arg+= u'--help-button --item-help --cancel-label "Quitter" ' + arg+= u'--default-item "%s" ' % choix + arg+= u'--menu "Que souhaitez vous faire ?" 0 55 13 ' + if proprio.__class__ == adherent : + arg+= u'"mAc"' + elif proprio.__class__ == club : + arg+= u'"mCc"' + if proprio : + arg+= u' "Modifier l\'inscription de %s" "" ' % proprio.Nom() + arg+= u'"aMc" "Ajouter une machine à %s" "" ' % proprio.Nom() + if isdeconnecteur : + arg+= u'"ebA" "Editer blackliste de %s" "" ' % proprio.Nom() + if becane : + arg+= u'"mMc" "Modifier la machine %s" "Modification du nom, de l\'IP ou de la MAC" ' % becane.nom().split('.')[0] + if isadm : + arg+= u'"eac" "Editer les alias de la machine %s" "" ' % becane.nom().split('.')[0] + if isdeconnecteur : + arg+= u'"ebM" "Editer blackliste de la machine %s" "" ' % becane.nom().split('.')[0] + if proprio or becane : + arg+= u'"" "---------------------------------------" "" ' + + arg+= u'"aA" "Inscrire un nouvel adhérent" "" ' + arg+= u'"mA" "Modifier l\'inscription d\'un adhérent" "Changer la chambre, la remarque, la section, la carte d\'étudiant ou précâbler." ' + arg+= u'"aMA" "Ajouter une machine à un adhérent" "" ' + arg+= u'"dA" "Détruire un adhérent" "Supression de l\'adhérent ainsi que de ses machines" ' + arg+= u'"" "---------------------------------------" "" ' + arg+= u'"mM" "Modifier une machine existante" "Changer le nom ou la MAC d\'une machine." ' + if isadm : + arg+= u'"ea" "Editer les alias d\'une machine" "" ' + arg+= u'"dM" "Détruire une machine" "" ' + arg+= u'"" "---------------------------------------" "" ' + arg+= u'"aC" "Inscrire un nouveau club" "" ' + arg+= u'"mC" "Modifier un club" "" ' + arg+= u'"aMC" "Ajouter une machine à un club" "" ' + arg+= u'"dC" "Détruire un club" "Supression du club ainsi que de ses machines" ' + if isadm or isdeconnecteur : + arg+= u'"" "---------------------------------------" "" ' + if isadm : + arg+= u'"aaM" "Ajouter une machine à l\'association" "" ' + arg+= u'"abW" "Ajouter une borne wifi" "" ' + if isdeconnecteur : + arg+= u'"bl" "Modifier blackliste" "" ' + annul , result = dialog(arg) + if annul : break + + if result[0][:4]=='HELP' : + arg = u'--title Aide ' + arg+= u'--msgbox "Interface utilisable au clavier ou a la souris pour les terminaux le supportant.\n' + arg+= u'Pour revenir à une question précédente utiliser le bouton annuler ou Ctrl+C.\n' + arg+= u'Pour quitter sans enregister les dernières modifications utilisez ESC.\n\n' + arg+= u'Pour toute remarque ou problème : fred@crans.org\n\n\n" 0 0' + dialog(arg) + continue + + choix = result[0] + if not choix : continue + + if choix=='aA' : + # Inscription nouvel adhérent + proprio = adherent() + if all(proprio) : + del proprio + proprio = None + else : choix = 'aMc' # ajout d'une machine + + elif choix=='mA' : + # Modif adhérent + proprio = select(db,u'adhérent à modifier a') + if not proprio : continue + choix = 'mAc' + + elif choix=='aMA' : + # Ajout machine, adhérent à choisir + proprio = select(db,u'adhérent auquel ajouter une machine a') + if not proprio : continue + choix='aMc' + + elif choix=='aMC' : + # Ajout machine, club à choisir + proprio = select_club(db) + if not proprio : continue + choix='aMc' + + elif choix=='mM' : + # Modif machine, machine à choisir + becane = select(db,u'machine à modifier m') + if not becane : continue + choix='mMc' + + elif choix=='ea' : + # Modif alias machine + becane = select(db,u'machine à modifier m') + if not becane : continue + choix = 'eac' + + elif choix=='aC' : + # Ajout d'un club + proprio = club() + if set_club(proprio) : + del proprio ; proprio = None + else : choix = 'aMc' # ajout d'une machine + + elif choix=='mC' : + # Modif club + proprio = select_club(db) + if not proprio : continue + choix='mCc' + + elif choix=='dA' : + # Destruction adhérent + proprio = select(db,u'adhérent à détruire a') + if not proprio : continue + if del_adher(proprio) : continue + del(proprio) ; proprio=None + del(becane) ; becane=None + + elif choix=='dM' : + # Destruction machine + becane = select(db,u'machine à détruire m') + if not becane : continue + if del_machine(becane) : continue + del(becane) ; becane=None + + elif choix=='dC' : + # Destruction club + proprio = select_club(db) + if not proprio : continue + if del_club(proprio) : continue + del(proprio) ; proprio=None + del(becane) ; becane=None + + elif choix=='aaM' : + # Ajout machine au crans + becane = machine(crans(),"fixe") + choix = 'mMc' + + elif choix=='abW' : + # Ajout borne wifi + becane = machine(crans(),"borne") + choix = 'mMc' + + elif choix=='bl' : + # Edition blackliste + arg = u'--title "Edition blackliste" ' + arg+= u'--menu "Editer la blackliste de : " 0 0 0 ' + arg+= u'"1" "Adhérent" ' + arg+= u'"2" "Machine" ' + arg+= u'"3" "Club" ' + annul , result = dialog(arg) + if annul : continue + choix=int(result[0]) + if choix == 1 : + proprio = select(db,u'adhérent a') + if proprio : choix = 'ebA' + elif choix == 2 : + becane = select(db,u'machine m') + if becane : choix = 'ebM' + elif choix == 3 : + proprio = select_club(db) + if proprio : choix = 'ebA' + + ############################################## + + if choix=='aMc' : + # Ajout d'une machine à l'adhérent courant + arg = u'--title "Nouvelle machine" ' + arg+= u'--menu "Type de machine ?" 0 0 0 ' + arg+= u'"Fixe" "Machine fixe" ' + arg+= u'"Wifi" "Machine wireless" ' + annul , result = dialog(arg) + if annul : continue + choix=result[0] + if choix=='Fixe' : + try : becane = machine(proprio,'fixe') + except ValueError, c: + arg = '--title "Nouvelle machine" ' + arg += '--msgbox "%s\n\n\n" 0 0' % c.args[0] + dialog(arg) + continue + elif choix=='Wifi' : + becane = machine(proprio,'wifi') + if set_machine(becane) : + # Annulation + del(becane) ; becane = None + + + if choix=='mAc' : + # Modif propriétaire courant + del(becane) ; becane=None + # On supprime la machine car des modifs du propriétaire (bat) ne seraient + # alors pas vu par l'instance actuelle de machine + if modif_adher(proprio) : + # Annulation des modifs + proprio.restore() + + elif choix=='mMc' : + # Modif machine courante + if set_machine(becane) : + # Annulation des modifs + becane.restore() + + elif choix=='mCc' : + # Modif club courant + if set_club(proprio) : + # Annulation des modifs + proprio.restore() + + elif choix=='eac' : + # Modif alias machine courante + if __prompt_input_menu(becane.alias,'Alias machine', "Entrez ou modifier un alias machine.\nPour ajouter un alias modifier le dernier de la liste.") or confirm(becane) : + becane.restore() + + elif choix=='ebA' : + # Edition blackliste adhérent ou club + if set_blackliste(proprio) : + proprio.restore() + + elif choix=='ebM' : + # Edition blackliste machine + if set_blackliste(becane) : + becane.restore() + + os.system('clear') + +def killed(a,z) : + sys.exit(254) # Va tomber dans les exceptions + +if __name__ == '__main__' : + global db, debug + debug = 1 + + signal.signal(signal.SIGTERM,killed) # Interception du signal TERM + signal.signal(signal.SIGINT,signal.SIG_DFL) # Comportement normal de Ctrl-C + + # Traitement des options + try : + if len(sys.argv) > 1 : + options, arg = getopt.getopt(sys.argv[1:], '', ['debug']) + else : + options, arg = ( [],'') + except getopt.error, msg : + print msg + sys.exit(255) + + for opt, val in options : + if opt == '--debug' : + debug = 1 + + # Phase principale + try : + db = crans_ldap() + menu_principal() + #os.system('clear') + exit = 0 + except KeyboardInterrupt : + os.system('clear') + print "Interruption par l'utilisateur." + exit = 255 + except SystemExit , c: + if c.__str__() == '254' : + os.system('reset') + print "Votre session d'édition à été tuée." + exit = c + except : + if not debug : os.system('clear') + print """Une erreur fatale c'est produite durant l'exécution. +Pour l'amélioration de ce programme merci de prévenir nounou en spécifiant la +marche à suivre pour reproduire cette erreur.""" + if debug : + print '-'*40 + print 'Détails techniques :' + import traceback + traceback.print_exc() + print '-'*40 + exit = 1 + + # Restart des services + signal.signal(signal.SIGINT,signal.SIG_IGN) # Pas de Ctrl-C + signal.signal(signal.SIGTERM,signal.SIG_IGN) # Pas de kill non plus + + try : + serv = db.services_to_restart() + except : + # Erreur trop tot probablement + serv = '' + if serv : + mn = int(time.strftime('%M')) + # Restart toutes les 10 min : 03, 13, 23, 33, 43, 53 + t = ( 14 - mn % 10 ) % 10 # On prend en plus une marge de 1 min + if t == 0 : t = 10 + print "Les modifications apportées à la base seront prises en compte dans %imin environ." % t + if debug : + print "Les services suivants seront redémarrés: " + print ', '.join(serv.keys()) + + if debug : print '-*- Fin -*-' + + # Rétablissement du Ctrl-C + signal.signal(signal.SIGINT,signal.SIG_DFL) + sys.exit(exit) + diff --git a/gestion/iptools.py b/gestion/iptools.py new file mode 100755 index 00000000..68f18085 --- /dev/null +++ b/gestion/iptools.py @@ -0,0 +1,93 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-15 -*- +""" +Manipulation d'IPv4 + +Copyright (C) Frédéric Pauget +Licence : GPLv2 +""" + +def QuadToDec(ip) : + """ + Retourne la représentation décimale d'une ip + ip est de la forme xxx.xxx.xxx.xxx + """ + test = ip.split('.') + if len(test)!=4 : raise ValueError('IP Invalide') + ip_dec = 0 + for z in range(0,4) : + n = int(test[z]) + if n<0 or n>255 : raise ValueError('IP Invalide') + ip_dec += n * ( 256**(3-z) ) + + return ip_dec + +def DecToQuad(ip_dec) : + """ + Retourne la représentation habituelle d'une ip (xxx.xxx.xxx.xxx) + ip_dec est l'IP en base 10 + """ + try : + return "%d.%d.%d.%d" % ( \ + ip_dec/(256**3) , + (ip_dec%(256**3)) / (256**2) , + ( (ip_dec%(256**3)) % (256**2) ) / 256 , + ( (ip_dec%(256**3)) % (256**2) ) % 256 ) + except : + raise ValueError('IP Invalide') + +def param(net) : + """ + net est un résau fourni sous la forme xxx.xxx.xxx.xxx/yy + si donnée valide retourne un dictionnaire : + { 'network' : xxx.xxx.xxx.xxx , + 'netmask' : yyy.yyy.yyy.yyy , + 'broadcast' : zzz.zzz.zzz.zzz } + sinon retourne {} + """ + reseau = {} + ip, mask = net.split('/') + + try : + mask = int(mask) + dec_ip = QuadToDec(ip) + if dec_ip == -1 : raise + except : + return {} + + # Calcul du netmask + dec_netmask=0 + non_dec_netmask=0 # On calcule aussi le complémentaire + for i in range(0,32) : + if i < mask : + dec_netmask += 2**(31-i) + else : + non_dec_netmask += 2**(31-i) + + reseau['netmask'] = DecToQuad(dec_netmask) + + # Calcul du network + reseau['network'] = DecToQuad( dec_ip & dec_netmask ) + + # Calcul du broadcast + reseau['broadcast'] = DecToQuad( dec_ip | non_dec_netmask ) + return reseau + +def AddrInNet(ip,net) : + """ + ip est de la forme xxx.xxx.xxx.xxx + net est de la forme xxx.xxx.xxx.xxx/yy + net peut être une liste de chaînes ci-dessus + Retourne True si l'ip est dans un des réseaux. + Note : retourne Fasle si l'IP est une adresse de réseau ou broadcast + """ + if type(net)==str : net = [ net ] + + r = False + for ne in net : + n = param(ne) + if ip == n['broadcast'] or ip ==n['network'] : + return False + r = r or QuadToDec(n['netmask']) & QuadToDec(ip) == QuadToDec(n['network']) + + return r diff --git a/gestion/ldap_crans.py b/gestion/ldap_crans.py new file mode 100755 index 00000000..76dc19c3 --- /dev/null +++ b/gestion/ldap_crans.py @@ -0,0 +1,1909 @@ +#! /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 +""" + +date_format='%d/%m/%Y %H:%M' +uri = 'ldapi://%2fvar%2frun%2fldapi/' +smtpserv = "localhost" + +# TODO : + # Interdire la modif des comptes nounou + +import smtplib, sre, os, random, string, time, commands, sys +import ldap, ldap.modlist + +import config, annuaires, iptools, chgpass, user_tests +from affich_tools import coul +from time import sleep + +import ldap_secret + +################################################################################## +### Différent services redémarrables +#dns, dhcp, firewall, bornes_wifi(nom_borne), conf_wifi, bl_carte_etudiant, switch(chbre) + +################################################################################## +### Items de la blackliste +blacklist_items = [ u'bloq' , u'bl_virus' ,u'bl_upload' ] + +################################################################################## +### Droits possibles +droits_possibles = [ u'Nounou', u'Apprenti', u'Modérateur', u'Câbleur', u'Déconnecteur',u'CVSWeb' ] + +################################################################################## +### Variables internes diverses +isadm = user_tests.isadm() +isdeconnecteur = user_tests.isdeconnecteur() +hostname = commands.getoutput('hostname') +ann_scol = config.ann_scol + +################################################################################## +### Fonctions utiles +def decode(s) : + """ Retourne un unicode à paritr de s + s doit être en utf-8 """ + return s.decode('utf-8','ignore') # On ignore les erreurs + +accents = "êëèéÉÈÀÙâäàûüôöÖÔîïÎÏ'çÇÿßæÆøØ" # Si modif ici modifier aussi la fonction +def strip_accents(a) : + a = a.replace(u'ê','e').replace(u'ë','e').replace(u'è','e').replace(u'é','e').replace(u'É','e').replace(u'È','e') + a = a.replace(u'â','a').replace(u'ä','a').replace(u'à','a').replace(u'À','a') + a = a.replace(u'û','u').replace(u'ü','u').replace(u'ù','u').replace(u'Ù','u') + a = a.replace(u'ô','o').replace(u'ö','o').replace(u'Ö','o').replace(u'Ô','o') + a = a.replace(u'î','i').replace(u'ï','i').replace(u'Ï','i').replace(u'Î','i') + a = a.replace(' ','_').replace(u"'",'').replace(u'ç','c').replace(u'Ç','c') + a = a.replace(u'ÿ','y').replace(u'ß','ss').replace(u'æ','ae').replace(u'Æ','ae').replace(u'ø','o').replace(u'Ø','o') + return a + +def mailexist(mail) : + """ Vérifie si un mail existe ou non + grace à la commande vrfy du serveur mail """ + try : + s=smtplib.SMTP(smtpserv) + r = s.vrfy(mail) + s.close() + except : + raise ValueError(u'Serveur de mail injoignable') + + if r[0] == 250 : + return True + else : + return False + +def preattr(val) : + """ + val est : + * un entier + * une chaîne + * une liste avec un seul entier ou chaine + + Retourne + [ len(str(val).strip), str(val).strip ] + """ + + t = type(val) + + if t==list and len(val)==1 : + return preattr(val[0]) + + elif t==str or t==int : + val = str(val).strip() + # On passe tout en utf-8 pour ne pas avoir de problèmes + # d'accents dans la base + return [ len(val) , unicode(val,'iso-8859-1').encode('utf-8') ] + elif t==unicode : + val = val.strip() + return [ len(val) , val.encode('utf-8') ] + else : + raise TypeError + + +def is_actif(sanction) : + """ + Retourne True ou False suivant si la sanction founie (chaine venant de blacklist) + est active ou non + """ + bl = sanction.split(',') + try : + now = time.time() + debut = time.mktime( time.strptime(bl[0],date_format) ) + if bl[1]=='-' : + fin = now + 1 + else : + fin = time.mktime( time.strptime(bl[1],date_format) ) + return debut < now and fin > now + except : + return False + +def format_mac(mac) : + """ + Formatage des adresses mac + Transforme une adresse pour obtenir la forme xx:xx:xx:xx:xx:xx + Le séparateur original peut être :, - ou rien + Retourne la mac formatée. + """ + l, mac = preattr(mac) + mac= mac.replace(':','').replace('-','').replace(' ','').lower() + if len(mac)!=12 : + raise ValueError(u'Longueur adresse mac incorrecte.') + for c in mac[:] : + if c not in string.hexdigits : + raise ValueError(u"Caractère interdit '%s' dans adresse mac." % c) + if mac=='000000000000' : + raise ValueError(u"MAC nulle interdite\nIl doit être possible de modifier l'adresse de la carte.") + + # Formatage + mac="%s:%s:%s:%s:%s:%s" % ( mac[:2],mac[2:4],mac[4:6], mac[6:8], mac[8:10], mac[10:] ) + + return mac + +################################################################################## +### Définition des classes + +class crans_ldap : + """ + Classe de connexion à la base LDAP du crans. + """ + conn=None + base_dn='ou=data,dc=crans,dc=org' + base_lock = 'ou=lock,dc=crans,dc=org' + base_services = 'ou=services,dc=crans,dc=org' + + ### Configuration de la recheche + # Dictionnaire de tranformation des champs + trans = { 'prénom' : 'prenom' , + 'chambre' : 'chbre', + 'login' : 'mail' , + 'hostname' : 'host', + 'mac' : 'macAddress', + 'ip' : 'ipHostNumber' , + 'telephone' : 'tel' } + + # Champs de recherche pour la recherche automatique + auto_search_champs = { 'adherent' : [ 'nom', 'prenom', 'tel', 'mail', 'chbre', 'mailAlias', 'cannonicalAlias' ], \ + 'machine' : [ 'macAddress', 'host', 'ipHostNumber', 'hostAlias'] , + 'club' : [ 'nom', 'chbre' ] } + + # Champs de recherche pour la recherche manuelle (en plus de la recherche auto) + non_auto_search_champs = { 'adherent' : [ 'etudes', 'paiement', 'carteEtudiant', 'aid' , 'postalAddress', 'historique' ,'blacklist', 'droits', 'uidNumber' ], \ + 'machine' : [ 'mid' , 'ipsec', 'historique', 'blacklist' , 'puissance', 'canal', 'portTCPin', 'portTCPout', 'portUDPin', 'portUDPout' ] , + 'club' : [ 'cid' , 'responsable', 'paiement', 'historique', 'blacklist'] } + + # Scope des différentes recherches + scope = { 'adherent' : 1 , 'machine' : 2 , 'club' : 1 } + + def __init__(self) : + self.connect() + + def connect(self): + # Ouverture de la connexion + nbessais = 0 + ok = False + while not ok: + try: + self.conn = ldap.initialize(uri) + self.conn.bind_s(ldap_secret.auth_dn,ldap_secret.password,ldap.AUTH_SIMPLE) + ok = True + except ldap.SERVER_DOWN : + nbessais += 1 + if nbessais > 2: + sys.stderr.write("ERREUR : serveur LDAP injoignable\n") + sys.exit(1) + else: + sleep(0.3) + + #self.conn.start_tls_s() + + def exist(self,arg) : + """ + Vérifie l'existence d'une entrée dans la base et que cette entrée + n'appartient pas à l'objet en cours, prend en compte les locks + arg doit être une expression de recherche ldap + Si existence, retourne la liste de dn correspondants + Sinon retourne une liste vide + Exemple : exist('chbre=Z345') vérifie si il y a un adhérent en Z345 + """ + if not self.conn : self.connect() + r=[] + ret = self.conn.search_s(self.base_dn,2,arg) + ret += self.conn.search_s(self.base_lock,1,arg) + + for res in ret : + # C'est peut être l'objet courant + try : # Si ce n'est pas une classe fille avec l'attribu dn => erreur + if res[0] == self.dn or res[1]['lockid'][0] == '%s-%s' % (hostname, os.getpid()) : + return [] + except : + None + 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 + """ + if not self.conn : self.connect() + + valeur = valeur.encode('utf-8') + + lock_dn = '%s=%s,%s' % ( item, valeur, self.base_lock ) + lockid = '%s-%s' % (hostname, os.getpid() ) + modlist = ldap.modlist.addModlist({ 'objectClass' : 'lock' , + 'lockid' : lockid , + item : valeur } ) + + try : + self.conn.add_s(lock_dn,modlist) + except ldap.ALREADY_EXISTS : + # Pas de chance, le lock est déja pris + try : + res = self.conn.search_s(lock_dn,2,'objectClass=lock')[0] + l = res[1]['lockid'][0] + except : l = '' + if l != lockid : + # C'est locké par un autre process que le notre + # il tourne encore ? + try : + if os.system('ps %s > /dev/null 2>&1' % l.split('-')[1] ) : + # Il ne tourne plus + self.remove_lock(res[0]) # delock + return self.lock(item,valeur) # relock + except : + pass + raise EnvironmentError(u'Objet (%s=%s) locké, patienter.' % (item, valeur), l) + + return lock_dn + + def remove_lock(self,lockdn) : + """ Destruction d'un lock + Destruction de tous les locks si lockdn=*""" + if not self.conn : self.connect() + # Mettre des verifs ? + if lockdn!='*' : + self.conn.delete_s(lockdn) + else : + locks = self.list_locks() + for l in locks : + self.conn.delete_s(l[0]) + + def list_locks(self) : + """ Liste les locks """ + if not self.conn : self.connect() + return self.conn.search_s(self.base_lock,1,'objectClass=lock') + + def services_to_restart(self,new=None,args=[]) : + """ Si new = None retourne la liste des services à redémarrer + Si new est founi et ne comence pas par - ajoute le service à la liste + avec les arguments args (args doit être une liste). + Si new est founi et ne comence par - supprime le service de la liste + """ + if not self.conn : self.connect() + + if new and new[0] == '-' : + serv_dn = 'cn=%s,%s' % ( new[1:], self.base_services ) + try : self.conn.delete_s(serv_dn) + except : pass + # Si n'existe pas => Erreur mais le résultat est la. + return + + # Quels services sont déjà à redémarrer ? + serv = {} # { service : [ arguments ] } + for s in self.conn.search_s(self.base_services,1,'objectClass=service') : + s=s[1] + serv[s['cn'][0]] = s.get('args',[]) + + if not new : return serv + + serv_dn = 'cn=%s,%s' % ( new, self.base_services ) + + # Petite fonction à appliquer aux arguments + def tr(arg) : + return preattr(arg)[1] + args=map(tr,args) + + if new in serv.keys() : + if args not in serv[new] : + modlist = ldap.modlist.modifyModlist({ 'args' : serv[new] }, { 'args' : serv[new] + args }) + self.conn.modify_s(serv_dn,modlist) + # else rien à faire + else : + modlist = ldap.modlist.addModlist({ 'objectClass' : 'service' , + 'cn' : new , + 'args' : args } ) + try : + self.conn.add_s(serv_dn,modlist) + except ldap.ALREADY_EXISTS : + # Existe déja => rien à faire + pass + + def search(self,expression,mode='') : + """ + Recherche dans la base LDAP, expression est une chaîne : + une expression : champ1=expr1 champ2=expr2 champ3!=expr3.... + soit un seul terme, dans ce cas cherche sur les champs de auto_search_champs + Si mode ='w' les instances crées seront en mode d'écriture + """ + if not self.conn : self.connect() + + if type(expression)==str : + # Transformation de l'expression en utf-8 + expression = unicode(expression,'iso-8859-15').encode('utf-8') + elif type(expression)==unicode : + expression = expression.encode('utf-8') + else : + raise TypeError(u'Chaine attendue') + + if not expression : + return [] + + # Il faut un filtre par type d'objet de la base + filtres = self.auto_search_champs.keys() + result={'adherent' : [], 'machine' : [], 'club' : []} + + # Fonction utile + def build_filtre(champ,expr,neg=0) : + """ Retourne une chaine pour recherche dans la base LDAP + du style (champ=expr) en adaptant les valeurs de expr au champ + si neg = 1 : retourne le négatif : (!(champ=expr))""" + el = '' + if champ in [ 'host', 'hostAlias' ] : + if expr[-1] == '*' : + el = '(%s=%s)' % (champ, expr) + elif expr.find('.')==-1 : + el = '(%s=%s.*)' % ( champ, expr) + else : + el = '(%s=%s*)' % ( champ, expr) + elif champ == 'macAddress' : + # Formatage adresse mac + try : el = '(macAddress=%s)' % format_mac(expression) + except : return '' + else : + # Cas général + el = '(%s=%s)' % (champ, expr) + if neg : el = '(!%s)' % el + return el + + if expression.find('=')!=-1 : + #### Recherche avec conditions explicites + ## Construction des filtres + + # initialisation + filtre={} + filtre_cond={} # Stokage si filtre avec condition (ne comporte pas que des *) + filtre_tout=1 # Passse à 0 si au moins une condition + + for i in filtres : + filtre[i]='(&' + filtre_cond[i] = 0 + conds = expression.split('&') + + # Test de l'expression de recherche et classement par filtres + for cond in conds : + neg = 0 + try : + champ, expr = cond.strip().split('=') + if champ[-1] == '!' : + # Négation pour se champ + champ = champ[:-1] + neg = 1 + except : + raise ValueError(u'Syntaxe de recherche invalide.') + + # transformation de certains champs + champ = self.trans.get(champ,champ) + + if expr=='' : expr='*' + + ok = 0 + + # Construction du filtre + for i in filtres : + if champ in self.auto_search_champs[i] + self.non_auto_search_champs[i] : + filtre[i] += build_filtre(champ,expr,neg) + ok = 1 + if expr!='*' : + filtre_cond[i] = 1 + filtre_tout=0 + if not ok : + raise ValueError(u'Champ de recherche inconnu (%s)'% champ ) + + ## Recherche avec chacun des filtres + r={} # contiendra les réponses par filtre + for i in filtres : + if (filtre_tout and filtre!='(&' ) or filtre_cond[i] : + # Filtre valide + #filtre[i] += ')' + filtre[i] += '(objectClass=%s))' % i + r[i] = self.conn.search_s(self.base_dn,self.scope[i],filtre[i]) + else : + r[i] = None + + ## On a alors une liste de résultats + ## r = { categorie1 : [ (result1), (result2), ...] , ... } + + # Traitement + if not r['machine'] and not r['adherent'] and not r['club'] : + # Pas de réponses + return result + elif not r['adherent'] and not r['club'] : + # Il n'y avait seulement un filtre machine + # => on retourne uniquement les machines trouvées + for m in r['machine'] : + result['machine'].append(machine(m,mode) ) + elif not r['machine'] : + # Il n'y avait pas de filtre machine + # => on retourne uniquement les adhérents + if r['adherent'] : + for a in r['adherent'] : + result['adherent'].append(adherent(a,mode) ) + if r['club'] : + for a in r['club'] : + result['club'].append(club(a,mode) ) + else : + # Il faut croiser les résultats machine et propriétaire + # Traitement des machines + mach_adh = [] # liste de dn d'adhérents et de clubs + for res in r['machine'] : + dn = string.join(res[0].split(',')[-4:],',') + if dn[:3] != 'aid' and dn[:3] != 'cid' : continue + if dn not in mach_adh : + mach_adh.append(dn) + + # Croisement + bons_dn = [] # liste des dn d'adhérents qui correspondent aux critères + if r['adherent'] : + for a in r['adherent'] : + if a[0] in mach_adh and not a[0] in bons_dn : + bons_dn.append(a[0]) + result['adherent'].append(adherent(a,mode) ) + if r['club'] : + for a in r['club'] : + if a[0] in mach_adh and not a[0] in bons_dn : + bons_dn.append(a[0]) + result['club'].append(club(a,mode) ) + + # Maintenant c'est au tour des bonnes machines + bons_dn2 = [] + for a in r['machine'] : + dn = string.join(a[0].split(',')[-4:],',') + if dn in bons_dn and not a[0] in bons_dn2 : + bons_dn2.append(dn) + result['machine'].append(machine(a,mode) ) + + else : + ### Recherche d'une chaine sur tous les champs + conv = { 'machine' : machine , 'club' : club, 'adherent' : adherent } + + for i in filtres : + cl = conv[i] + + # Construction du filtre + filtre = '(&(|' + for champ in self.auto_search_champs[i] : + filtre += build_filtre(champ,expression) + filtre+=')(objectClass=%s))' %i + + # Recherche + for r in self.conn.search_s(self.base_dn,self.scope[i],filtre) : + result[i].append( cl(r,mode) ) + + return result + +############################################################################# + +class base_classes_crans(crans_ldap) : + """ Méthodes de base des classes machines, et base_proprietaire """ + + def __del__(self) : + # Destruction des locks résiduels + for lock in self._locks : + try : + self.remove_lock(lock) + except : + pass + + def id(self): + """ Retourne la valeur de l'attribu caractéristique de la classe (aid,mid,cid)""" + try : + s = self.dn.split(',')[0].split('=') + if s[0] == self.idn : + return s[1] + except : + return '' + + def blacklist_actif(self) : + """ + Vérifie si l'instance courante est blacklistée. + Retourne les sanctions en cours (liste) + Retourne une liste vide si aucune sanction en cours + """ + bl_liste = self._data.get('blacklist',[]) + + if 'machine' in self._data['objectClass'] : + # Il faut aussi regarder la blackliste du propriétaire + p = self.proprietaire() + bl_liste += p.blacklist() + + actifs = [] + + for sanction in bl_liste : + s = sanction.split(',')[2] + if not s in actifs and is_actif(sanction) : + actifs.append(s) + return actifs + + def blacklist(self,new=None) : + """ + Blacklistage de la ou de toutes la machines du propriétaire + new est une liste de 4 termes : + [ debut_sanction , fin_sanction, sanction, commentaire ] + début et fin doivent être sous la forme donnée par date_format + pour un début ou fin immédiate mettre now + pour une fin indéterminée mettre '-' + + pour modifier une entrée donner un tuple de deux termes : + ( index dans blacklist à modifier , nouvelle liste ) + l'index est celui obtenu dans la liste retournée par blacklist() + """ + if not self._data.has_key('blacklist') : + self._data['blacklist']=[] + liste = list(self._data['blacklist']) + if new==None : return map(decode,liste) + + if type(new)==tuple : + # Modif + index = new[0] + new = new[1] + if new=='' : + liste.pop(index) + self._set('blacklist',liste) + return liste + else : + index = -1 + + if type(new)!=list or len(new)!=4 : + raise TypeError + + # Verif que les dates sont OK + if new[0] == 'now' : + new[0] = time.strftime(date_format) + else : + try : time.strptime(new[0],date_format) + except : raise ValueError(u'Date de début blacklist invalide') + + if new[1] == 'now' : + new[1] = time.strftime(date_format) + elif new[1]!='-' : + try : time.strptime(new[1],date_format) + except : raise ValueError(u'Date de fin blacklist invalide') + + new = ','.join(new) + new = preattr(new)[1] + + if index!=-1 : + liste[index] = new + else : + liste = liste + [ new ] + + self._set('blacklist',liste) + + return liste + + def restore(self) : + """ Restore les données à l'état initial """ + self._data = self._init_data.copy() + self.modifs=[] + + def historique(self) : + """ Retourne l'historique de l'objet """ + return map(decode,self._data.get('historique',[])) + + def info(self,new=None) : + """ + Pour ajouter une remarque new doit être la chaîne + représentant la remarque à ajouter + Pour modifier new doit être une liste de la forme : + [ index de la remarque à modifier , nouvelle remarque ] + l'index est celui obtenu dans la liste retournée par info() + """ + if not self._data.has_key('info') : + self._data['info']=[] + liste = list(self._data['info']) + if new==None : return map(decode,liste) + + if type(new)==list : + # Modif + index = new[0] + l, new = preattr(new[1]) + if not new : + # Supression remarque + liste.pop(index) + else : + # Modif remarque + liste[index]=new + elif type(new)==str : + # Remarque supplémentaire + l, new = preattr(new) + if not new : + # On ajoute pas de remarque vide + return liste + # Ajout à la liste + liste = liste + [ new ] + else : + raise TypeError + + self._set('info',liste) + return liste + + def _save(self) : + """ Sauvegarde dans la base LDAP """ + if not self.modifs : + # Rien à faire + return [] + + if not self.conn : self.connect() + + if not self.dn : + # Enregistrement à placer en tête de base + self.dn = self.base_dn + + # Construction de l'historique + if not self._init_data : + modif='inscription' + else : + modif=', '.join(self.modifs) + + hist = "%s, %s" % ( time.strftime(date_format), os.getlogin() ) + + # Suffit-t-il d'ajouter un item au dernier élément de l'historique ? + try: + dern = self._data['historique'][-1].split(' : ',2) + if dern[0] == hist : + # Même date et même cableur + if modif not in dern[1].split(', ') : + # Qqch de plus de modifié + self._data['historique'][-1] = self._data['historique'][-1] + ', ' +modif + else : + # Nouvelle entrée + # NE PAS UTILISER L'OPERATEUR += ICI sinon self._init_data aussi modififié + self._data['historique'] = self._data['historique'] + [ "%s : %s" % ( hist, modif ) ] + except: + # Nouvelle inscription + self._data['historique'] = [ "%s : %s" % ( hist, modif ) ] + + if not self._init_data : + ### Nouvel enregistrement + # Génération du dn + res = self.conn.search_s(self.base_dn,2,'objectClass=%s' % self._data['objectClass'][0],['']) + vidn=1 + vidns=[] + # Liste des dn pris + for r in res : + # r=( dn, {} ) + r = r[0].split(',')[0] + if r[:4] != '%s=' % self.idn : continue + vidns.append(int(r[4:])) + # On prend le premier libre + while vidn in vidns : + vidn += 1 + + self.dn='%s=%s,%s' % (self.idn, vidn, self.dn) + self._data[self.idn]= [ '%d' % vidn ] + + # Ecriture + modlist = ldap.modlist.addModlist(self._data) + self.conn.add_s(self.dn,modlist) + else : + ### Modification entrée + if not self._modifiable : + raise RuntimeError(u'Objet non modifiable') + modlist = ldap.modlist.modifyModlist(self._init_data,self._data) + try : + self.conn.modify_s(self.dn,modlist) + except ldap.TYPE_OR_VALUE_EXISTS , c : + champ = c.args[0]['info'].split(':')[0] + raise RuntimeError(u'Entrée en double dans le champ %s' % champ) + + + ### Génération de la liste de services à redémarrer + + # Correspondance modif de la base -> service ayant besoin d'être redémarré + # pour paiement et carte d'étudiant : traitement dans la classe adhérent + # car il faut vérifier l'existance de machines + annuaire_modif_service = { 'host' : [ 'dhcp', 'dns' ], + 'ipHostNumber' : [ 'dhcp', 'dns', 'firewall' ], + 'macAddress' : [ 'dhcp', 'dns', 'firewall' ], + 'ipsec' : [ 'conf_wifi' ], + 'hostAlias' : [ 'dns' ] , + 'droits' : [ 'droits' ] , + 'ports' : [ 'firewall-komaz' ] + } + serv = [] + for m in self.modifs : + for s in annuaire_modif_service.get(m,[]) : + if s not in serv : + serv.append(s) + + # Regénération blackliste nécessaire ? + bl = self.blacklist_actif() + if bl and ( 'host' in self.modifs or 'blacklist' in self.modifs ): + for s in bl : + if s not in serv : + serv.append(s) + + # Reinitialisation + self._init_data = self._data.copy() + + for s in serv : + self.services_to_restart(s) + + def _delete(self,dn,comment='') : + """ Sauvegarde puis destruction du dn (et des sous-dn) fourni """ + if not self.conn : self.connect() + data = self.conn.search_s(dn,2) + txt = "%s # Destruction le %s par %s" % (data, time.strftime(date_format), os.getlogin()) + if comment : + txt += ' (%s)' % comment + txt += '\n' + + 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.delete_log,'a') + log.write(txt) + log.close() + except : + pass + + def _set(self,champ,val) : + """ Met à jour les données de data et modifie modifs si besoin """ + if not self._data.has_key(champ) \ + or self._data.has_key(champ) and self._data[champ]!=val : + self._data[champ]=val + if champ not in self.modifs : + self.modifs.append(champ) + +############################################################################# + +class base_proprietaire(base_classes_crans) : + """ Méthodes de bases pour les classes adherent et club """ + def __init__(self,data=(),mode='') : + """ + 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 + """ + if type(data) != tuple : + raise TypeError + + self.modifs=[] + self._locks = [] + + if data : + self.dn=data[0] + if mode == 'w' : + try : + self._locks.append(self.lock(self.idn, self.id())) + self._modifiable = 'w' + except EnvironmentError , c: + self._modifiable = 0 + else : + self._modifiable = 0 + # Utile pour construire l'instruction LDAP de modif + self._init_data = data[1].copy() + self._data = data[1] + else : + # Propriétaire vide + self.dn='' # Génération du reste au moment de l'écriture + self._data={ 'objectClass' : [ self.objectClass ] } + self._init_data={} + self._modifiable = 'w' + + def machines(self) : + """ Retroune les machines (instances) appartenant à la classe """ + if not self.conn : + self.connect() + if self.id() : + res = [] + for r in self.conn.search_s('%s=%s,%s' % ( self.idn,self.id() , self.base_dn ),1,'objectClass=machine') : + res.append(machine(r, self._modifiable) ) + return res + else : + return [] + + def paiement(self,action=None) : + """ + Action est un entier représentant une année + si positif ajoute l'année à la liste + si négatif le supprime + """ + return self._an('paiement',action) + + def prise(self) : + """ Retourne la prise associée ou au club + Si chbre est dans un bat sans correspondance chbre <-> prise + retourne '' + Si prise non trouvée retourne inconnue + """ + if self.chbre()[0].lower() in annuaires.chbre_prises.keys() : + try : + return annuaires.chbre_prises[self.chbre()[0].lower()][self.chbre()[1:]] + except : + return 'inconnue' + else : + return '' + + def delete(self,comment='') : + """ + Destruction du proprietaire, retourne le liste des services à redémarrer + """ + + done = 0 + for m in self.machines() : + if m.ipsec() and not 'conf_wifi' in serv : + self.services_to_restart('conf_wifi') + elif not done : + done = 1 + self.services_to_restart('switch',[self.chbre()]) + if self.machines() : + self.services_to_restart('dhcp') + self.services_to_restart('dns') + self.services_to_restart('firewall',[ self.nom() ]) + + self._delete(self.dn,comment) + + def save(self) : + """ + Enregistre l'adhérent ou le club courant dans la base LDAP + Envoie le mail de bienvenue + Retourne une chaîne indiquant les opération effectuées. + """ + # Note : un peu trop de fonctions pour un club mais ce n'est pas génant + + ret ='' + serv = [] + if self._init_data : + nouveau =0 + # Reconfiguration switch si changement de chambre et si machine fixe + if 'chbre' in self.modifs : + for m in self.machines() : + if not m.ipsec() : + self.services_to_restart('switch',[self._data['chbre'][0]]) + self.services_to_restart('switch',[self._init_data.get('chbre','')[0]]) + break + else : + nouveau = 1 + + # Enregistrement + self._save() + + # Message de sortie + if nouveau : + ret += coul(u"%s inscrit avec succès." % self.Nom(), 'vert') + + if self.idn !='cid' : + # Envoi du mail de bienvenue + try : + From = "respbats@crans.org" + To = self.mail().encode('iso-8859-15') + if To.find('@') == -1 : To += '@crans.org' + conn=smtplib.SMTP(smtpserv) + conn.sendmail(From, To , config.txt_mail_bienvenue % { 'From' : From, 'To' : To }) + conn.quit() + ret += coul(u"\nMail de bienvenue envoyé.",'vert') + except Exception, c: + ret += coul(u"\nErreur lors de l'envoi du mail de bienvenue (%s)" % c.__doc__,'jaune') + + else : + ret += coul(u"Modification %s effectuée avec succès." % self.Nom(), 'vert') + + # Faut-il redémarrer plus de services que ceux traités dans _save ? + if 'carteEtudiant+%s' % ann_scol in self.modifs \ + or 'carteEtudiant-%s' % ann_scol in self.modifs \ + and self.machines() and 'bl_carte_etudiant' not in serv : + self.services_to_restart('bl_carte_etudiant') + + if 'paiement+%s' % ann_scol in self.modifs \ + or 'paiement-%s' % ann_scol in self.modifs : + for m in self.machines() : + if m.ipsec() and not 'conf_wifi' in serv : + self.services_to_restart('conf_wifi') + elif not self.chbre() in serv : + self.services_to_restart('chbre',[self.chbre()]) + if self.machines() : + for s in ['dhcp', 'dns', 'firewall' ] : + if s not in serv : + serv.append(s) + + # Vérification si changement de bât, ce qui obligerai un changement d'IP + if 'chbre' in self.modifs : + # Verif si machines avec bonnes ip + err = 0 + for m in self.machines() : + if m.ipsec() : + # Machine Wifi + continue + # Machine fixe + ip = m.ip() + try : + # Tentative de changement d'IP de la machine + m.ip(ip) + except ValueError, c : + if len(c.args) == 2 : + # IP invalide, on la change + ret += "\nChangement d'IP machine %s : " % m.nom() + try : + ret += "%s -> %s" % ( ip, m.ip('') ) + se, r = m.save() + for s in se : + if s not in serv : + serv.append(s) + except c : + ret += coul(u'ERREUR : %s' % c.args[0], rouge) + err = 1 + else : + # Autre erreur, c pas normal + raise RuntimeError(u'Erreur interne') + + if err : ret += '\nEssayer de corriger les erreurs machines en éditant celles-ci.\n' + + # Faut-il créer un compte sur zamok ? + if 'compte' in self.modifs : + ret += u'\nUn compte a été créé :\n login : %s\n' % self.compte() + args = self._data['homeDirectory'][0] + ',' + args+= self._data['uidNumber'][0] + ',' + args+= self._data['uid'][0] + self.services_to_restart('home',[ args ]) + + # Remise à zero + self.modifs=[] + + for s in serv : + self.services_to_restart(s) + + return ret + + def _an(self,champ,action) : + """ + Champ est un champ contenant une liste d'entiers + Action est un entier représentant une année + si positif ajoute l'année à la liste + si négatif le supprime + """ + if not self._data.has_key(champ) : + trans=[] + else : + # On va travailler sur une liste d'entiers + trans = map(int,self._data[champ]) + + if action==None : + return trans + + if type(action)!=int : raise TypeError + + if action>0 and action not in trans : + trans.append(action) + act = '%s+%d' % (champ,action) + if act not in self.modifs : + self.modifs.append(act) + elif action<0 and -action in trans : + trans.remove(-action) + act = '%s%s' % (champ,action) + if act not in self.modifs : + self.modifs.append(act) + + trans.sort() + new=[] + for an in trans : + new.append('%d' % an ) + + self._data[champ] = new + + return self._data[champ] + +############################################################################# + +class adherent(base_proprietaire) : + """ Classe de définition d'un adhérent """ + objectClass = 'adherent' + idn = 'aid' + + ### Méthodes Nom utilisée lors de l'affichage des propriétés + ### (commune avec les classes crans et club) + def Nom(self) : + """ Retourne prenom nom """ + return "%s %s" % ( self.prenom() , self.nom() ) + + def nom(self,new=None) : + return self.__nom_prenom('nom',new) + + def prenom(self,new=None) : + return self.__nom_prenom('prenom',new) + + def __nom_prenom(self,champ,new) : + if new==None : + return decode(self._data.get(champ,[''])[0]) + + l, new = preattr(new) + new = new.capitalize() + for c in new[:] : + if c not in (string.letters + '- ' + preattr(accents)[1] ) : + raise ValueError(u"Seuls les caractères alphabétiques, l'espace et le - sont permis dans %s." % champ.replace(u'e',u'é') ) + if l<2 : + raise ValueError(u"%s trop court." % champ.capitalize().replace(u'e',u'é')) + if new[0] not in string.letters : + raise ValueError(u"Le premier caractère du %s doit être une lettre" % champ.replace(u'e',u'é') ) + + self._set(champ,[new]) + return new + + def tel(self,new=None) : + if new==None : + return self._data.get('tel',[''])[0] + + if new != 'inconnu' : + l, new = preattr(new) + if not new.isdigit() or l<6 or l>15 : + raise ValueError(u"Numéro de téléphone incorrect (il doit comporter uniquement des chiffres).") + + self._set('tel',[new]) + return new + + def chbre(self,new=None) : + """ + Défini la chambre d'un adhérent, EXT pour personne extérieure au campus + Pour que EXT soit accepté il est nécessaire que l'adhérent n'ait pas + de machine fixe. + """ + if new==None : + return decode(self._data.get('chbre',[''])[0]) + + l, new = preattr(new) + if l==0 : + raise ValueError(u"Chambre incorrecte.") + + if new.upper() == 'EXT' : + # N'est pas ou plus sur le campus + # Machine fixe ? + for m in self.machines() : + if not m.ipsec() : + raise ValueError(u'Un adhérent en dehors du campus ne doit pas avoir de machine fixe.') + + self._set('chbre',['EXT']) + return 'EXT' + + 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 bâtiment, 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 ? + if self.exist('chbre=%s' % new) : + raise ValueError(u'Chambre déjà occupée.') + + # Lock de la chambre + self._locks.append(self.lock('chbre',new)) + + self._set('chbre',[new]) + return new + + def adresse(self,new=None): + """ Défini l'adresse pour les personnes extérieures (dont la chambre = EXT) + L'adresse est une liste de 4 éléments : numero, rue, code postal, ville + """ + if new==None : + if self.chbre() != 'EXT' : + # Personne sur le campus + return '' + else : + return map(decode,self._data.get('postalAddress', ['','','',''])[:4]) + + if type(new)!=list and len(new)!=4 : + raise TypeError + + l_min = [ 2, 0, 5, 2 ] + for i in range(0,4) : + l, new[i] = preattr(new[i]) + if l < l_min[i] : raise ValueError(u"Adresse incorrecte.") + + # Correction si necessaire + if not new[1] : + new[1] = ' ' + + self._set('postalAddress',new) + return new + + def mail(self,new=None) : + if new==None : + return decode(self._data.get('mail',[''])[0]) + + l, new = preattr(new) + new = new.lower() + + #Emplacement de l'@ + a=new.find('@') + #Emplacement du . final + b=new.rfind('.') + + # Les tests : + # exactement un @ + # 2 ou 3 caractères après le . final + # @ pas en premier ni juste avant le dernier . + if new.count('@')!=1 \ + or not ( l-b==4 or l-b==3) \ + or a<1 or b-a<2 : + raise ValueError(u"Adresse mail incorrecte.") + + # Pas de caractèrs bizarres + for l in new[:]: + if not l in (string.lowercase + string.digits + '-_.@') : + raise ValueError(u"Caractère interdits dans l'adresse mail (%s)." % l) + + # Pour les vicieux + if sre.match('.*crans.(org|ens-cachan.fr)$',new) : + raise ValueError(u"Adresse mail @crans interdite ici") + + self._set('mail',[new]) + + # Il ne doit pas y avoir de compte + try: + self._data['objectClass'] = [ 'adherent' ] + self._data.pop('uid') + self._data.pop('cn') + self._data.pop('shadowLastChange') + self._data.pop('shadowMax') + self._data.pop('shadowWarning') + self._data.pop('loginShell') + self._data.pop('userPassword') + self._data.pop('uidNumber') + self._data.pop('gidNumber') + self._data.pop('homeDirectory') + self._data.pop('gecos') + self._data.pop('droits') + except : + return new + + def etudes(self,index_or_new) : + """ + Retourne l'un des 3 champs études (selon index_or_new si entier) + """ + if type(index_or_new)==int : + if self._data.has_key('etudes'): + return decode(self._data['etudes'][index_or_new]) + else: + return '' + + if type(index_or_new)!=list : + raise TypeError + + if not self._data.has_key('etudes') : + self._data['etudes']=['','',''] + + # Pas grand chose à faire à part vérifier que ce sont bien des chaines + if len(index_or_new)!=3 : + raise ValueError(u"Format études non valides.") + + new = ['','',''] + for i in range(0,3) : + n = preattr(index_or_new[i])[1] + if n in new or n=='' : + raise ValueError(u"Etudes non valides.") + new[i] = n + + self._set('etudes',new) + + return new + + def carteEtudiant(self,action=None) : + """ + Action est un entier représentant une année + si positif ajoute l'année à la liste + si négatif le supprime + """ + return self._an('carteEtudiant',action) + + def compte(self,login=None,uidNumber=0,hash_pass='',shell=config.login_shell) : + """ + Création d'un compte à un adhérent + (la création se fait après l'écriture dans la base par la méthode save) + Si login = None, retourne le compte de l'adhérent + """ + if not login : + if self.mail().find('@') != -1 : + return '' + else : + return self.mail() + + # Supression des accents et espaces + login = strip_accents(login) + + l, login = preattr(login) + login = login.lower() + + if 'posixAccount' in self._data['objectClass'] : + if login != self._data['uid'] : + # A déja un compte + raise ValueError(u"L'adhérent à déjà un compte. Login : %s" % self._data['uid'][0]) + else : + return login + + for c in login[:]: + if not c in (string.letters + '-') : + raise ValueError(u"Seuls les caractères alphabétiques non accentués et le - sont autorisés dans le login.") + if l<2 : + raise ValueError(u"Login trop court.") + if l>config.maxlen_login : + raise ValueError(u"Login trop long.") + if login[0]=='-' : + raise ValueError(u"- interdit en première position.") + + + if mailexist(login) : + raise ValueError(u"Login existant ou correspondant à un alias mail.",1) + + # Lock du mail + self._locks.append(self.lock('mail',login)) + + self._data['mail']= [ login ] + if not 'compte' in self.modifs : + self.modifs.append('compte') + + # Création de l'alias cannonique + if self.nom() and self.prenom() : + a = '%s.%s' % (self.prenom().capitalize(), self.nom().capitalize()) + self.cannonical_alias(a) + + self._data['objectClass'] = [ 'adherent', 'posixAccount', 'shadowAccount' ] + self._data['uid'] = [ login ] + self._data['cn'] = [ preattr(self.Nom())[1] ] + self._data['shadowLastChange'] = [ '12632' ] + self._data['shadowMax'] = [ '99999'] + self._data['shadowWarning'] = [ '7' ] + self._data['loginShell' ] = [ shell ] + if hash_pass : + self._data['userPassword'] = [ hash_pass ] + + if uidNumber : + if self.exist('(uidNumber=%s)' % uidNumber) : + raise ValueError(u'uidNumber pris') + else : + uidNumber = 1000 + while self.exist('(uidNumber=%s)' % uidNumber) : + uidNumber += 1 + try : + self._locks.append(self.lock('uidNumber',str(uidNumber))) + except : + # Quelqu'un nous a piqué l'uid que l'on venait de choisir ! + return self.compte(login,uidNumber,hash_pass,shell) + + self._data['uidNumber']= [ str(uidNumber) ] + self._data['gidNumber']=[ str(config.gid) ] + + home = '/home/' + login + if os.path.exists(home) : + raise RuntimeError(u'Création du compte impossible : home existant') + + self._data['homeDirectory']=[ preattr(home)[1] ] + self._data['gecos'] = [ preattr(self.Nom())[1] + ',,,' ] + + return decode(login) + + def cannonical_alias(self,new=None) : + """ Retourne ou défini l'alias canonique""" + if new == None : + try : return decode(self._data['cannonicalAlias'][0]) + except : return '' + else : + a = strip_accents(new) + a = preattr(a)[1] + + if not mailexist(a) : + # Attribution de l'alias, sinon on passe + + # Lock de cannonicalAlias + self._locks.append(self.lock('cannonicalAlias',a)) + + # Attribution + self._set('cannonicalAlias',[a]) + return a + + def alias(self,new=None) : + """ + Création ou visualisation des alias mail + Même sytème d'argument que la méthode info. + """ + if not self._data.has_key('mailAlias') : + self._data['mailAlias']=[] + liste = list(self._data['mailAlias']) + if new==None : + return map(decode,liste) + + if type(new)==list : + # Modif + index = new[0] + new = new[1] + if new=='' : + # Supression alias + liste.pop(index) + self._set('mailAlias',liste) + return liste + else : + new = new.replace('@crans.org','') + index=-1 + + # Tests + l, new = preattr(new) + new = new.lower() + if l<2 : + raise ValueError(u"Alias trop court.") + for c in new[:]: + if not c in (string.letters + string.digits + '-_.') : + raise ValueError(u"Alias : seuls les caractères alphanumériques, le -, le _ et le . sont autorisés." ) + if new[0] not in string.letters : + raise ValueError(u"Le premier caractère de l'alias doit être alphabétique.") + if mailexist(new) : + raise ValueError(u"Alias existant ou correspondand à un compte.") + + if index!=-1 : + liste[index]=new + else : + liste = liste + [ new ] + + # Lock de mailAlias + self._locks.append(self.lock('mailAlias',new)) + + self._set('mailAlias',liste) + return liste + + def droits(self,droits=None) : + """ droits est la liste des droits à donner à l'utilisateur """ + if droits==None : + return map(decode,self._data.get('droits',[])) + + if not isadm : + raise EnvironmentError(u'Il faut être administrateur pour effectuer cette opération.') + + if type(droits)!=list : + raise TypeError(u'Une liste est attendue') + + new = [] + for droit in droits : + if droit == '' : continue + droit = unicode(droit.strip(),'iso-8859-15') + if droit not in droits_possibles : + raise ValueError(u'Droit %s incorrect' % droit) + new.append(droit.encode('utf-8')) + + if new != self._data.get('droits',[]) : + self._set('droits',new) + + return new + +class club(base_proprietaire) : + """ Classe de définition d'un club """ + idn = 'cid' + objectClass = 'club' + + def Nom(self,new=None) : + if new==None : + return decode(self._data.get('nom',[''])[0]) + + l, new = preattr(new) + new = new.capitalize() + if l<2 : + raise ValueError(u"Nom trop court.") + + self._set('nom',[new]) + return new + + def carteEtudiant(self,pd=None) : + return [ ann_scol ] + + def responsable(self,adher=None) : + """ Responsable du club, adher doit être une instance de la classe adhérent """ + if adher==None : + aid = decode(self._data.get('responsable',[''])[0]) + if aid : + return self.search('aid=%s' % aid)['adherent'][0] + else : return '' + + if adher.__class__ != adherent : + raise ValueError + + if not adher.id() : + raise AttributeError(u'Adhérent invalide') + + self._set('responsable',[adher.id()]) + return adher + + def chbre(self,new=None) : + """ Défini le local du club + new doit être une des clefs de l'annuaire locaux_clubs""" + if new==None : + return decode(self._data.get('chbre',[''])[0]) + + annu = annuaires.locaux_clubs() + if new not in annu.keys() : + raise ValueError(u'Local invalide',annu) + + self._set('chbre',[new]) + return new + + def local(self) : + """ Retourne le local à partir de la chambre enregistrée et + de la conversion avec l'annuaire locaux_clubs """ + annu = annuaires.locaux_clubs() + return decode(annu.get(self.chbre(),'')) + +class machine(base_classes_crans) : + """ Classe de définition d'une machine """ + idn = 'mid' + + def __init__(self,parent_or_tuple,typ='fixe') : + """ + parent_or_tuple est : + *soit une instance d'une classe pouvant posséder une machine + (adherent, club ou crans), la nouvelle machine lui sera alors associé. + *soit directement le tuple définissant une machine (tel que + retourné par les fonctions de recherche ldap) + typ permet de définir le type de machine : wifi ou fixe, + pris en compte uniquement pour une nouvelle machine (parent donné) + + Pour édition d'une machine, typ devra être égal à 'w' + Attention, si typ ='w' mais si l'objet est déja locké il n'y a pas d'erreur + vérifier l'obtetion du lock grace à la valeur de _modifiable (si =w c'est bon) + + """ + + self.modifs=[] + self._locks=[] + t = parent_or_tuple.__class__ + if t == tuple : + # Initialisation avec données fournies + self.dn = parent_or_tuple[0] + if typ == 'w' : + try : + self._locks.append(self.lock(self.idn, self.id())) + self._modifiable = 'w' + except EnvironmentError , c: + self._modifiable = 0 + else : + self._modifiable = 0 + self._init_data = parent_or_tuple[1].copy() # Utile pour construire l'instruction LDAP de modif + self._data = parent_or_tuple[1] + + # Type de machine + if self._init_data.has_key('ipsec') : + self.__typ = 'wifi' + elif self._init_data.has_key('puissance') : + self.__typ = 'borne' + else : + self.__typ = 'fixe' + + # Propriéraire inconnu mais ce n'est pas grave + self.__proprietaire = None + + elif t == adherent or t == club or t == crans and typ in [ 'fixe' , 'wifi' , 'borne' ] : + # Machine vide + self.__proprietaire = parent_or_tuple + self.dn = parent_or_tuple.dn + self._data={ 'objectClass' : [ 'machine' ] } + self._init_data={} + self.__typ = typ + self._modifiable = 'w' + + if self.__proprietaire.chbre() == 'EXT' and typ == 'fixe' : + raise ValueError(u'Il faut une chambre pour pouvoir posséder une machine fixe') + + if typ == 'wifi' : + # Génération de la clef IPsec + self.ipsec(1) + + else : + raise TypeError(u'Arguments invalides') + + def Nom(self) : + """ Retourne le nom de la machine """ + return self.nom() + + def mac(self,mac=None,multi_ok=0) : + """ + Défini ou retourne l'adresse mac de la machine + Adresse valide si : + 12 caractères hexa avec - ou : comme séparateurs + non nulle + Stoque l'adresse sous la forme xx:xx:xx:xx:xx:xx + Si multi_ok = 1 permet d'avoir plusieur fois la même mac sur le réseau + """ + + if mac==None : + return decode(self._data.get('macAddress',[''])[0]) + + mac = format_mac(mac) + + # La mac serait-elle déjà connue ? + if not multi_ok and self.exist('macAddress=%s' % mac) : + raise ValueError(u'Mac déja utilisée sur le réseau.',1) + + # Lock de la mac + self._locks.append(self.lock('macAddress',mac)) + + self._set('macAddress',[mac]) + return mac + + def __host_alias(self,champ,new) : + """ Vérification de la validité d'un nom de machine """ + # Supression des accents + new = strip_accents(unicode(new,'iso-8859-15')) + + l, new = preattr(new) + new = new.lower() + l = len(new.split('.')[0]) + + if l<2 : + raise ValueError(u"%s trop court." % champ.capitalize()) + + if self.proprietaire().__class__ != crans : + new = new.split('.',1)[0] + for c in new[:]: + if not c in (string.letters + string.digits + '-') : + raise ValueError(u"Seuls les caractères alphabétiques minuscules et les - sont autorisés pour %s" % champ) + + if l>17 : + raise ValueError(u"%s trop long." % champ.capitalize()) + + if not new[0].isalnum() : + raise ValueError(u"Le premier caractère du champ %s doit être alphanumérique" % champ) + + # Ajout du domaine si necessaire + if new.find('.')==-1 : + try : + new += '.' + config.domains[self.__typ] + except : + raise RuntimeError(u"%s : domaine non trouvé" % champ.capitalize() ) + + # Pas déja pris ? + if self.exist('(|(host=%s)(hostAlias=%s))' % (new,new)) : + raise ValueError(u"%s : nom déjà pris" % champ.capitalize()) + + # Lock host + self._locks.append(self.lock('host',new)) + + return new + + def nom(self,new=None) : + """ + Défini ou retourne le nom de machine. + Un nom de machine valide ne comporte que des caractères + alphabétiques minuscules et le - + """ + if new==None : + return decode(self._data.get('host',[''])[0]) + + new = self.__host_alias('nom de machine',new) + self._set('host',[new]) + return new.split('.')[0] + + def alias(self,new=None) : + """ + Création ou visualisation des alias d'une machine. + Même sytème d'argument que la méthode info. + """ + if not self._data.has_key('hostAlias') : + self._data['hostAlias']=[] + liste = list(self._data['hostAlias']) + if new==None : return map(decode,liste) + + if type(new)==list : + # Modif + index = new[0] + new = new[1] + if new=='' : + liste.pop(index) + self._set('hostAlias',liste) + return liste + else : + index=-1 + + # Test si valide + new = self.__host_alias('alias',new) + + if index!=-1 : + liste[index] = new + else : + liste = liste + [ new ] + + self._set('hostAlias',liste) + return liste + + def ip(self,ip=None) : + """ + Défini ou retourne l'IP de la machine. + Les IP sont stoquées sous forme xxx.xxx.xxx.xxx et doivent être fournies ainsi. + Si l'IP n'est pas définie retourne . + Si ip= attribue la permière IP libre du sous réseau. + """ + if ip==None : + if self._data.has_key('ipHostNumber') : + return decode(self._data['ipHostNumber'][0]) + elif self.proprietaire().__class__ == crans : + return '' + else : + return '' + + l, ip = preattr(ip) + + # Dans quel réseau la machine doit-elle être placée ? + if self.__typ == 'wifi' : + net = config.NETs['wifi'] + elif self.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=='' : + 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 : raise RuntimeError(u'Impossible de trouver une IP libre.') + ip = "%d.%d.%d.%d" % tuple(n) + if not self.exist('ipHostNumber=%s' % ip) : + # On a trouvé la première ip libre + break + + # Test final + if not iptools.AddrInNet(ip,ne) : + # Rien dans ce sous réseau + ip = '' + else : + break + + if ip =='' : + raise RuntimeError(u'Plus d\'IP libres dans %s.' % string.join(net,' et ') ) + + else : + # L'ip est elle dans le bon sous réseau ? + # (accessoirement teste si l'IP est valide et ne correspond pas + # à l'adresse de broadcast ou de réseau) + if not iptools.AddrInNet(ip,net) : + raise ValueError(u'IP invalide ou en dehors du sous réseau alloué.',1) + # L'ip est-elle déja allouée ? + if self.exist('ipHostNumber=%s' % ip) : + raise ValueError(u'IP déjà prise.') + + # Lock ip + self._locks.append(self.lock('ipHostNumber',ip)) + + self._set('ipHostNumber',[ip]) + return ip + + def proprietaire(self) : + """ + retroune le propriétaire de la machine (classe adherent, club ou crans) + """ + if not self.__proprietaire : + if not self.conn : self.connect() + + res = self.conn.search_s(','.join(self.dn.split(',')[1:]),0) + if len(res) != 1 : njoqmf + + if 'adherent' in res[0][1]['objectClass'] : + self.__proprietaire = adherent(res[0],self._modifiable) + elif 'club' in res[0][1]['objectClass'] : + self.__proprietaire = club(res[0],self._modifiable) + else : + self.__proprietaire = crans() + + return self.__proprietaire + + def ipsec(self,clef=0) : + """ Génération (clef=1) ou affichage de la clef IPsec de la machine + Si clef!=1 : prend la clef fournie. + """ + if self.__typ != 'wifi' : return None + + if not clef : + return decode(self._data.get('ipsec',[''])[0]) + + if clef == 1 : + # Génération + clef = '' + for i in range(12) : + clef += random.choice(string.lowercase + string.digits) + + self._set('ipsec',[clef]) + + return clef + + def canal(self,new=None) : + """ Attribution ou visualisation du canal d'une borne wifi """ + if self.__typ != 'borne' : return None + if not new : + return self._data.get('canal',[''])[0] + + try : + new = int(new) + if new < 0 or new > 14 : raise + except : + raise ValueError(u'Canal invalide : doit être entre 0 et 14') + + self._set('canal',[str(new)]) + return new + + def puissance(self,new=None) : + """ Attribution ou visualisation de la puissance d'une borne wifi """ + if self.__typ != 'borne' : return None + if not new : + return self._data.get('puissance',[''])[0] + + + try : + new = int(new) + if new < 0 or new > 99 : raise + except : + raise ValueError(u'Puissance invalide : doit être entre 0 et 99') + + self._set('puissance',[str(new)]) + return new + + def save(self) : + """ + Enregistre la machine courante dans la base LDAP + Retourne une chaîne indiquant les opération effectuées. + """ + if self.proprietaire() == crans and not isadm : + raise EnvironmentError(u'Il faut être administrateur pour effectuer cette opération.') + + ret ='' + + # Enregistrement + self._save() + + # Clef IPsec + if 'ipsec' in self.modifs : + ret += coul(u'Clef IPsec de la machine : %s\n' % self.ipsec(),'cyan') + elif 'macAddress' in self.modifs and self.__typ == 'fixe' : + # Reconfiguration switch necessaire + self.services_to_restart('switch',[self.proprietaire().chbre()]) + + if 'canal' in self.modifs or 'puissance' in self.modifs : + self.services_to_restart('bornes_wifi',['self.nom()']) + + # 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, retourne la liste de services à redémarrer + """ + if self.proprietaire() == crans and not isadm : + raise EnvironmentError(u'Il faut être administrateur pour effectuer cette opération.') + + if self.__typ == 'wifi' : + self.services_to_restart('conf_wifi') + elif self.__typ == 'fixe' : + self.services_to_restart('switch',[ self.proprietaire().chbre() ]) + + self._delete(self.dn,comment) + + self.services_to_restart('dhcp') + self.services_to_restart('dns') + self.services_to_restart('firewall',[ self.nom() ]) + + def portTCPin(self,ports=None) : + """ Ports TCP ouverts depuis l'extérieur pour la machine """ + return self.__port(ports,'portTCPin') + def portTCPout(self,ports=None) : + """ Ports TCP ouverts vers l'extérieur pour la machine """ + return self.__port(ports,'portTCPout') + def portUDPin(self,ports=None) : + """ Ports UDP ouverts vers l'extérieur pour la machine """ + return self.__port(ports,'portUDPin') + def portUDPout(self,ports=None) : + """ Ports UDP ouverts vers l'extérieur pour la machine """ + return self.__port(ports,'portUDPout') + + def __port(self,ports,champ): + if ports == None : + return self._data.get(champ,[''])[0] + + ports = preattr(ports)[1] + if ports and self._data.get(champ)!=ports : + self._data[champ] = [ ports ] + if 'ports' not in self.modifs : + self.modifs.append('ports') + elif self._data.has_key(champ) : + self._data.pop(champ) + if 'ports' not in self.modifs : + self.modifs.append('ports') + +class crans(crans_ldap) : + """ Classe définissant l'assoce (pour affichage de ses machines) """ + idn = '' + def __init__(s): + s.dn = s.base_dn + def id(s) : + return '' + def Nom(s) : + return u"Crans" + def chbre(s) : + return u"CRA" + def blacklist(s) : + return [] + def paiement(s) : + return [ ann_scol ] + def carteEtudiant(s) : + return [ ann_scol ] + def blacklist_actif(s) : + return [] + def machines(s) : + if not s.conn : s.connect() + res = s.conn.search_s(s.dn,1,'objectClass=machine') + m = [] + for r in res : + m.append(machine(r)) + return m + +if __name__ == '__main__' : + import sys + if 'lock' in sys.argv : + db = crans_ldap() + print db.list_locks() + + if 'purgelock' in sys.argv : + db = crans_ldap() + db.remove_lock('*') + diff --git a/gestion/lock.py b/gestion/lock.py new file mode 100755 index 00000000..576ff138 --- /dev/null +++ b/gestion/lock.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# -*- coding: iso-8859-15 -*- + +""" Gestion de lock + +Copyright (C) Frédéric Pauget +Licence : GPLv2 +""" + +import os,string,time,sys,pwd, affich_tools + +def make_lock(lock_name, lock_comment='') : + """ Création d'un lock """ + lock_file = '/var/lock/' + lock_name + if os.path.isfile(lock_file) : + ### Lock existant + + # Lecture du lock + fd = open(lock_file, "r") + pid= fd.readline().strip() + fd.close() + + # Informations sur le processus lockant + if os.system( "ps %s > /dev/null 2>&1" % pid ) : + # Le script lockant ne tourne plus + os.remove(lock_file) + else : + # Il faut attendre + a = affich_tools.anim('\tattente du lock') + for i in range(8) : + time.sleep(1) + a.cycle() + sys.stdout.write('\r') + return make_lock(lock_name, lock_comment) + + ### Prise du lock + lock_fd=open(lock_file, "w") + lock_fd.write("%s\n%s\n%s" % (os.getpid(), pwd.getpwuid(os.getuid())[0], lock_comment) ) + lock_fd.close() + +def remove_lock( lock_name ) : + """ Destruction du lock """ + lock_file = '/var/lock/' + lock_name + try : + fd = open(lock_file, "r") + if fd.readline().strip()=="%s" % os.getpid(): + os.remove(lock_file) + fd.close() + except : + None diff --git a/gestion/secours.py b/gestion/secours.py new file mode 100755 index 00000000..084a799e --- /dev/null +++ b/gestion/secours.py @@ -0,0 +1,69 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-15 -*- + +# Fichiers à modifier et chaine indiquant un commentaire dans ceux-ci +fichiers = { '/etc/bind/named.conf.options' : '//' , + '/etc/postfix/main.cf' : '#' } + +import sys, sre + +def edit(file,comment,secours) : + """ Edite le fichier fourni en commentant (mode normal) + ou décommentant (mode secours) les lignes se terminant avec #POUR SECOURS """ + + signal = '#POUR SECOURS' + l = len(signal) + + fd = open(file) + line = fd.readline() + new = '' + while line : + l = line.rstrip() + if sre.match('.*'+signal+'$',l) : + # Ligne pour secours + if not sre.match('^' + comment,l) and not secours: + # On est actuellement configuré en secours + # Il faut passer en normal + new += comment + line + elif sre.match('^' + comment,l) and secours : + # On est actuellement configuré en normal + # Il faut passer en secours + new += line.replace(comment,'',1) + else : + # Rien à faire, on est bien configuré + new += line + else : + # Ligne normale + new += line + + line = fd.readline() + + fd.close() + + # Ecriture de la nouvelle version + fd = open(file,'w') + fd.write(new) + fd.close() + + +def usage() : + print 'Usage : %s 0 pour reconfigurer les services locaux en normal' % sys.argv[0] + print ' %s 1 pour reconfigurer les services locaux en secours' % sys.argv[0] + print 'Fichiers modifiés par le changement de mode : \n\t', '\n\t'.join(fichiers.keys()) + +if __name__ == '__main__' : + if len(sys.argv) != 2 : + usage() + else : + mode = sys.argv[1] + if mode not in '01' : + usage() + else : + mode = int(mode) + for f, c in fichiers.items() : + try : + print 'Edition de %s' % f + edit(f,c,mode) + except : + import traceback + traceback.print_exc() diff --git a/gestion/whos.py b/gestion/whos.py new file mode 100755 index 00000000..9daf6e2b --- /dev/null +++ b/gestion/whos.py @@ -0,0 +1,759 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-15 -*- + +# Copyright (C) Frédéric Pauget +# Licence : GPLv2 + +"""Ce script permet de recherche et d'afficher le détail d'une machine ou +d'un adhérent. + +Usage: %(prog)s [options] + +La chaine de recherche peut être : + * soit un terme unique, dans ce cas la recherche sera effectuée sur les +champs en bleu ci-dessous. + * soit du type "champ1=valeur1&champ2!=valeur2 ...", les résultats seront +alors limités aux entrées correspondantes à tous les critères. + +Les champs de recherche possibles sont : +%(champs_rech)s + +Les options de recherches sont : + * limitations sur la recherche : + -a ou --adherent : limitation de la recherche aux adhérents + -m ou --machine : limitation de la recherche aux machines + -c ou --club : limitation de la recherche aux clubs + -b ou --bornes : limitation de la recherche aux bornes wifi + * options d'affichage : + -t ou --tech : affichages des infos techniques des machines +à la place des infos administratives dans les résumés. + -i ou --ipsec : montre la clef ipsec des machines wifi + -l ou --limit= : limite du nombre de résultats pour utiliser +le mode d'affichage condensé au lieu du mode détaillé (défaut %(limit_aff_details)i) + -L ou --limit-historique= : limitation du nombre de lignes +d'historique affichées (défaut %(limit_aff_historique)i) + +""" + +from ldap_crans import is_actif , crans_ldap, isadm, ann_scol +from affich_tools import * + +limit_aff_details = 1 +limit_aff_historique = 4 +aff_ipsec = 0 + +def aff(qqch,mtech=0) : + """ Affichage de qqch. + qqch peut être une liste d'instances des classes adhérent ou machine + (un seul type dans la liste) dans ce cas : + * si la longueur de la liste est inférieure à limit_aff_details + affiche les propriétés détaillées de chaque élément. + * sinon résume dans un tabeau des principales propriétés + si qqch est une instance seul la traité comme une liste à une élément + Si mtech = 1 affiches les infomations techniques des machines plutot + qu'administratives dans le tableau des propriétés + """ + if type(qqch) != list : + qqch = [ qqch ] + + if len(qqch) > limit_aff_details : + t = qqch[0].idn + if t == 'aid' : + print adhers_brief(qqch) + elif t == 'mid' : + if mtech : print list_machines(qqch) + else : print machines_brief(qqch) + elif t == 'cid' : + print clubs_brief(qqch) + else : + i = 0 + for c in qqch : + t = c.idn + if i : print coul(u'='*80,'cyan') + i = 1 + if t == 'aid' : print adher_details(c) + elif t == 'mid' : + print machine_details(c) + elif t == 'cid' : print club_details(c) + +def adhers_brief(adhers) : + """ + Formatage sous forme de tableau des infos sur la liste d'adhérent fournie : + * aid + * prénom nom + * chambre + * machines + """ + data = [ ( u'aid' , u'Prénom Nom' , u'Chbre', u'P', u'C', u'Machines' ) ] + + for a in adhers : + ## Etat administratif + ok = u'\x1b[1;32mo\x1b[1;0m' + nok = u'\x1b[1;31mn\x1b[1;0m' + # Paiement + if ann_scol in a.paiement() : paid = ok + else : paid = nok + + # Précablage + if ann_scol+1 in a.paiement() : paid = coul(paid,'f_vert') + + # Carte d'étudiant + if ann_scol in a.carteEtudiant() : carte = ok + else : carte = nok + + machines = '' + # Récupération des machines + for machine in a.machines() : + nom = machine.nom().split('.')[0] + if machine.blacklist_actif() : k = 'rouge' + else : k= '' + if machines : machines += ', ' + coul(nom,k) + else : machines = coul(nom,k) + + # Données + data.append((a.id() , a.Nom(), a.chbre(),paid,carte,machines )) + + return u"Machines en rouge = machines avec limitation de services\n" + \ + u"P : paiement année en cours, le fond vert indique le précâblage\n" + \ + u"C : carte d'étudiant année en cours\n" + \ + tableau([5, 30 , 5, 1, 1,30], data) + +def machines_brief(machines) : + """ + Formatage sous forme d'un tableau des propriétés de la liste de machine : + * mid + * type (fixe ou wifi, born) + * nom + * adresse IP + * adresse MAC + * si blacklistée + """ + data = [ ( u'mid' , u'Type', u'Nom de machine', u'Propriétaire', u'Chbre', u'Limitation' ) ] + + for m in machines : + t, bl = __bases_machines(m) + + # Propriétaire + a = m.proprietaire() + p = a.Nom() + + # A jour administrativement + if ann_scol not in a.paiement() or ann_scol not in a.carteEtudiant() : + p = coul(p,'rouge') + + # Données + data.append((m.id() , t, m.nom().split('.')[0], p, a.chbre(), bl)) + + return u"Le propriétaire en rouge signale un problème administratif\n" + \ + tableau([5, 4, 18, 30, 5, 10], data) + +def clubs_brief(clubs) : + """ + Formatage sous forme de tableau des infos sur la liste de clubs fournie : + * cid + * nom + * local + * machines + """ + data = [ ( u'cid' , u'Nom ', u'Local',u'P', u'Responsable', u'Machines' ) ] + + for c in clubs : + ## Etat administratif + ok = u'\x1b[1;32m\xa4\x1b[1;0m' + nok = u'\x1b[1;31m\xa4\x1b[1;0m' + # Paiement + if ann_scol in c.paiement() : paid = ok + else : paid = nok + + # Précablage + if ann_scol+1 in c.paiement() : paid = coul(paid,'f_vert') + + machines = '' + # Récupération des machines + for machine in c.machines() : + nom = machine.nom().split('.')[0] + if machine.blacklist_actif() : k = 'rouge' + else : k= '' + if machines : machines += ', ' + coul(nom,k) + else : machines = coul(nom,k) + + # Responsable + resp = c.responsable().Nom() + + # Données + data.append((c.id() , c.Nom(), c.local(),paid, resp, machines )) + + return u"Machines en rouge = machines avec limitation de services\n" + \ + u"P : signature charte année en cours, le fond vert indique le précâblage\n" + \ + tableau([5, 15 , 6, 1, 21, 24], data) + + +def list_machines(machines) : + """ + Formatage sous forme d'un tableau des propriétés de la liste de machine : + * mid + * type (fixe ou wifi) + * nom + * adresse IP + * adresse MAC + * si blacklistée + """ + data = [ ( u'mid' , u'Type', u'Nom de machine', u'Adresse IP', u'Adresse MAC', u'Limitation' ) ] + + for m in machines : + t, bl = __bases_machines(m) + + # Données + data.append((m.id() , t, m.nom().split('.')[0], m.ip(), m.mac(), bl)) + + return tableau([5, 4, 18, 17, 19, 10], data) + +def list_bornes(bornes) : + """ + Formatage sous forme d'un tableau des propriétés de la liste de bornes wifi : + * mid + * nom + * adresse IP + * adresse MAC + * puissance + * canal + * lieu (la première remarque en fait) + """ + p = u'**' + + if isadm : p = u'P' + + data = [ ( u'mid' , u'Nom', u'Adresse IP', u'Adresse MAC', u'C' , p, u'Lieu') ] + + for b in bornes : + t, bl = __bases_machines(b) + if t != 'born' : continue + + if isadm : + p = b.puissance() + + # Données + data.append((b.id() , b.nom().split('.')[0], b.ip(), b.mac(), b.canal(), p, b.info()[0] )) + + if isadm : + t = u"C=canal, P=puissance\n" + else : + t = u"C=canal\n" + + return t + tableau([5, 14, 17, 19, 2, 2, 13], data) + +def adher_details(adher) : + """ + Affichage du détail des propriétés d'un adhérent + """ + f='' + # Aid + f+= coul(u'aid=%s ' % adher.id() ,'bleu') + # Nom, prenom + f += coul(u'Nom : ','gras') + "%s\n" % adher.Nom() + + # Mail + if adher.mail().find('@')!=-1 : + f += coul(u'Adresse mail : ','gras') + "%s" % adher.mail() + else : + f += coul(u'Login : ','gras') + "%s\t" % adher.mail() + alias = ', '.join([adher.cannonical_alias()] + adher.alias()) + if alias : + if alias[0]==',' : + # Cannonical étéait vide + alias = alias[2:] + f += coul(u'Alias : ','gras') + alias + f+= u'\n' + + # Etat administratif + f += coul(u'Etat administratif : ','gras') + jour=1 + if ann_scol not in adher.carteEtudiant() : + f += coul(u"manque carte d'étudiant",'violet') + jour = 0 + if ann_scol not in adher.paiement() : + if not jour : f += ' et ' + f += coul(u"cotisation %s/%d non réglée"% (ann_scol, ann_scol+1 ),'violet') + jour = 0 + + if jour : + f += coul(u"à jour",'vert') + f += '\n' + + # Telephone + tel = adher.tel() + try : + tel = u'%s %s %s %s %s' % ( tel[:2], tel[2:4], tel[4:6], tel[6:8], tel[8:] ) + except : + None + f += coul(u'Numéro de téléphone : ','gras') + "%s\n" % tel.ljust(12) + + # Adresse + chbre = adher.chbre() + if chbre == 'EXT' : + # Adhérent extérieur + f += coul(u'Adresse : ','gras') + addr = adher.adresse() + f += addr[0] + u'\n' + if addr[1] != ' ' : f += u' ' + addr[1] + u'\n' + f+= u' ' + addr[2] + u' ' + addr[3] + else : + # Chambre + prise (d'après annuaire) + f += coul(u'Chambre : ','gras') + u"%s " % chbre + prise = adher.prise() + if prise : + f += u'(prise %s)' % prise + f += '\n' + + # Etudes + if adher.etudes(1).isdigit() : + f += coul(u'Etudes : ','gras')+ "%s %s%s\n" % \ + ( adher.etudes(0), adher.etudes(1), adher.etudes(2) ) + else : + f += coul(u'Etudes : ','gras')+ "%s %s %s\n" % \ + ( adher.etudes(0), adher.etudes(1), adher.etudes(2) ) + + # Role dans l'assoce + d = adher.droits() + if d : + f += coul(u"Droits sur les serveurs : ",'gras') + ', '.join(d) + f += u'\n' + + # Paiement + if adher.paiement() : + if len(adher.paiement()) == 1 : + f += coul(u'Cotisation payée pour l\'année scolaire :','gras') + else : + f += coul(u'Cotisation payée pour les années scolaires :','gras') + g = u'' + for an in adher.paiement() : g += u" %i-%i" % ( an, an+1 ) + if len(g) > 35 : f += '\n\t' + f += g + f += u'\n' + + # Cartes d'étudiant fournie + if adher.carteEtudiant() : + if len(adher.carteEtudiant()) == 1 : + f += coul(u"Carte d'étudiant fournie pour l'année scolaire :",'gras') + else : + f += coul(u"Carte d'étudiant fournie pour les années scolaires :",'gras') + g = u'' + for an in adher.carteEtudiant() : g += u" %i-%i" % ( an, an+1 ) + if len(g) > 25 : f += '\n\t' + f += g + f += u'\n' + + f += _blacklist(adher) + f += _info(adher) + f += _hist(adher) + + # Formatage des machines aussi + f += coul(u'Machine(s) : ','gras') + m = adher.machines() + if m : + f += u'\n' + list_machines(m) + else : + f += u'aucune' + + return f + +def machine_details(machine) : + """ + Formatage du détail des propriétés d'une machine + """ + f = '' + f+= coul(u'mid=%s ' % machine.id(),'bleu') + + # Type de machine + if machine.ipsec() : a='Machine wifi' + elif machine.canal() : a='Borne wifi' + else : a='Machine fixe' + f+= coul(a+' : ' ,'gras') + + f+= "%s\n" % machine.nom() + + # Alias ? + alias = machine.alias() + if machine.alias() : + f += coul(u'Alias : ' ,'gras') + ', '.join(alias) + f+= '\n' + + f+= coul(u'IP : ','gras') + "%s\t\t" %machine.ip() + f+= coul(u'MAC : ','gras') + "%s\n" %machine.mac() + + # Propriétaire + a = machine.proprietaire() + f+= coul(u'Propriétaire : ','gras') + f += "%s" % a.Nom() + if a.chbre() : f += " (%s)" % a.chbre() + f+= '\n' + + # Adhérent blacklisté ? + bl = a.blacklist_actif() + if bl : + f += coul(u'Restrictions sur adhérent : ','gras') + f += coul(u', '.join(bl),'rouge') + f += '\n' + + # Borne wifi + if isadm and machine.puissance() : + f += coul(u'Puissance : ','gras') + machine.puissance() + f += coul(u'\tCanal : ', 'gras') + machine.canal() + f += '\n' + + if aff_ipsec and machine.ipsec() : + f += coul(u'Clef IPsec : ','gras') + machine.ipsec() + f += '\n' + + # Ports spéciaux + if machine.portTCPin() : + f += coul(u'Ports TCP ouvert ext->machine : ','gras') + machine.portTCPin() + '\n' + if machine.portTCPout() : + f += coul(u'Ports TCP ouvert machine->ext : ','gras') + machine.portTCPout() + '\n' + if machine.portTCPin() : + f += coul(u'Ports UDP ouvert ext->machine : ','gras') + machine.portUDPin() + '\n' + if machine.portUDPout() : + f += coul(u'Ports UDP ouvert machine->ext : ','gras') + machine.portUDPout() + '\n' + + f += _blacklist(machine) + f += _info(machine) + f += _hist(machine) + + return f + +def club_details(club) : + """ + Affichage du détail des propriétés d'un adhérent + """ + f='' + # Cid + f+= coul(u'cid=%s ' % club.id() ,'bleu') + # Nom + f += coul(u'Nom : ','gras') + "%s\n" % club.Nom() + + # Etat administratif + f += coul(u'Etat administratif : ','gras') + jour=1 + if ann_scol not in club.paiement() : + if not jour : f += ' et ' + f += coul(u"charte %s/%d non signée"% (ann_scol, ann_scol+1 ),'violet') + jour = 0 + + if jour : + f += coul(u"à jour",'vert') + f += '\n' + + # Chambre + prise + f += coul(u'Local : ','gras') + "%s " % club.local() + prise = club.prise() + if prise : + f += '(prise %s)' % prise + f += '\n' + + # Paiement + if club.paiement() : + f += coul(u'Charte signée pour les années scolaires :','gras') + g = '' + for an in club.paiement() : g += " %i-%i" % ( an, an+1 ) + if len(g) > 35 : f += '\n\t' + f += g + f += '\n' + + f += _blacklist(club) + f += _info(club) + f += _hist(club) + + # Formatage des machines aussi + f += coul(u'Machine(s) : ','gras') + m = club.machines() + if m : + f += '\n' + list_machines(m) + else : + f += 'aucune' + + return f + +########################################### +# Fonctions annexes de formatage de données + +def _blacklist(clas) : + """ Formatage blackliste de la classe fournie """ + f = u'' + for event in clas.blacklist() : + if is_actif(event) : + # Colorisation si sanction en cours + c = 'rouge' + else : + c = 'blanc' + f += u"%s\n\t " % coul(u'du %s au %s : %s, %s' % tuple(event.split(',')) ,c) + + f = f[:-6] # supression des espaces superflus + + if f : + return coul(u'Blackliste : ', 'gras') + f + else : + return '' + +def _info(clas) : + """ Formatage des remarques de la classe fournie """ + f= u'' + c = clas.info() + if c : + f += coul(u'Remarque :\n ' ,'gras') + f += u'\n '.join(c) + f += u'\n' + return f + +def _hist(clas) : + """ Formatage de l'historique de la classe fournie """ + if limit_aff_historique==0 : return '' + f='' + h = clas.historique() + h.reverse() + if h : + f += coul(u'Historique : ','gras') + for i in range(0,limit_aff_historique) : + try : + a = h[i] # Produit une erreur si i trop grand + if i !=0 : f += ' ' + f += '%s\n' % a + except : + break + try : + if h[i+1] : f += ' [...]\n' + except : + None + + return f + +def __bases_machines(m) : + """ Retourne [ type de la machines, blacklist ] """ + #Type + if m.ipsec() : t='wifi' + elif m.canal() : t='born' + else : t='fixe' + + # Déconnectée ? + b = m.blacklist_actif() + if not b : + bl = '-' + elif len(b) == 1 : + bl = coul(b[0],'rouge') + else : + bl = coul(u'cf détails','rouge') + + return t , bl + +############################################################################## +## Partie dévolue au système de recherche + +def __usage_brief(err='') : + """ Message d'erreur """ + if err : cprint(err,'gras') + print "Pour obtenir de l'aide sur l'utilisation de ce programme utilisez l'option -h" + sys.exit(2) + +def __usage() : + """ Comment ca marche ? """ + list = [''] + for c in base.auto_search_champs.values() : + for champ in c : + coul_champ = coul(champ,'bleu') + if list[-1] == '' : + list[-1] = coul_champ + l = len(champ) + else : + l += len(champ) + 2 + if l < 80 : + list[-1] += ', ' + coul_champ + else : + list.append(coul_champ) + l = len(champ) + + for c in base.non_auto_search_champs.values() : + for champ in c : + l += len(champ) + 2 + if l < 80 : + list[-1] += ', ' + champ + else : + list.append(champ) + l = len(champ) + + print __doc__ % { 'prog' : sys.argv[0].split('/')[-1] , + 'champs_rech' : '\n'.join(list) , + 'limit_aff_details' : limit_aff_details , + 'limit_aff_historique' : limit_aff_historique } + sys.exit(0) + +def __recherche() : + """ + Recherche et affichage des résultats à partir des options founies (sys.argv) + """ + global aff_ipsec, limit_aff_details, limit_aff_historique, debug + + # Récupération des options + if len(sys.argv) == 1 : + # Pas d'option fournie + __usage_brief() + + try : + options, arg = getopt.getopt(sys.argv[1:], 'hamctbil:L:', [ 'debug', 'help', 'adherent', 'machine', 'club' , 'tech', 'bornes', 'limit=', 'limit-historique=', 'ipsec' ]) + except getopt.error, msg : + __usage_brief(msg) + + # Traitement des options + only_adh=0 + only_mac=0 + only_club=0 + only_bornes=0 + mtech = 0 + + for opt, val in options : + if opt == '-h' or opt=='--help' : + __usage() + elif opt =='--debug' : + # Mode debug + debug = 1 + elif opt == '-l' or opt =='--limit': + # Passage mode condensé, mode détaillé + try : limit_aff_details = int(val) + except : + __usage_brief('Valeur du paramètre %s incorecte (doit être un entier positif)' % opt) + elif opt == '-L' or opt =='--limit-historique': + # Limitation du nombre de lignes d'historique + try : limit_aff_historique = int(val) + except : + __usage_brief('Valeur du paramètre %s incorecte (doit être un entier positif)' % opt) + elif opt in [ '-a', '--adherent' ] : + only_adh = 1 + print "Recherche limitée aux adhérents." + elif opt in [ '-m', '--machine' ] : + only_mac = 1 + print "Recherche limitée aux machines." + elif opt in [ '-c', '--club' ] : + only_club = 1 + print "Recherche limitée aux clubs." + elif opt in [ '-b', '--bornes' ] : + only_bornes = 1 + print "Recherche limitée aux bornes wifi." + # On va tenter de limiter un peu la recherche + if arg[0] == '*' : + # Recherche initiale sans critètre + arg = [ 'canal=*' ] + elif arg[0].find('=')!=-1 : + # Recherche avec critères + arg += [ 'canal=*' ] + elif opt in [ '-t', '--tech' ] : + # Format affichage des machines + mtech = 1 + elif opt in [ '-i', '--ipsec' ] : + # Affichage des clefs ipsec + aff_ipsec = 1 + + if only_adh + only_mac + only_club + only_bornes > 1 : + __usage_brief('Options utilisées incompatibles') + + if not arg : + # Pas de chaine de recherche fournie + __usage_brief('Chaine de recherche incorrecte.') + + try : + res = base.search(' '.join(arg)) + except ValueError, c : + __usage_brief(c.args[0]) + + # Traitement du résultat + if not res['adherent'] and not res['machine'] and not res['club']: + print "Aucun résultat trouvé." + sys.exit(3) + # L'affichage souhaité a été précisé ? + elif only_bornes : + if not res['machine'] : + print 'Aucun résultat à afficher' + sys.exit(4) + else : + if len(res['machine']) > limit_aff_details : + print list_bornes(res['machine']) + else : + aff(res['machine']) + elif only_adh : + if res['adherent'] : aff(res['adherent']) + elif res['machine'] : + to_aff=[] + traite =[] + for m in res['machine'] : + a = m.proprietaire() + if a.idn != 'aid' or a.id() in traite : continue + traite.append(a.id()) + to_aff.append(m.proprietaire()) + if not(to_aff) : + print 'Aucun résultat à afficher' + sys.exit(4) + aff(to_aff) + elif res['club'] : + print 'Aucun résultat à afficher' + sys.exit(4) + elif only_mac : + if res['machine'] : aff(res['machine'],mtech) + else : + to_aff = [] + for a in res['adherent'] + res['club'] : + to_aff += a.machines() + aff(to_aff) + elif only_club : + if res['club'] : aff(res['club']) + elif res['machine'] : + to_aff=[] + traite =[] + for m in res['machine'] : + a = m.proprietaire() + if a.idn != 'cid' or a.id() in traite : continue + if a.id() in traite : continue + traite.append(a.id()) + to_aff.append(m.proprietaire()) + if not(to_aff) : + print 'Aucun résultat à afficher' + sys.exit(4) + aff(to_aff) + elif res['adherent'] : + print 'Aucun résultat à afficher' + sys.exit(4) + # Non : on affiche tout. + else : + if res['adherent'] : + cprint("Résultats trouvés parmi les adhérents :",'cyan') + aff(res['adherent']) + if res['machine'] : + cprint("Résultats trouvés parmi les machines :",'cyan') + aff(res['machine'],mtech) + if res['club']: + cprint("Résultats trouvés parmi les clubs :",'cyan') + aff(res['club']) + +if __name__ == '__main__' : + global debug + debug = 0 + + import sys, getopt + + base = crans_ldap() + + try : + __recherche() + except KeyboardInterrupt : + print "Recherche interrompue par l'utilisateur." + sys.exit(255) + except SystemExit, c : + # Fin + sys.exit(c) + except : + print """Une erreur fatale c'est produite durant l'exécution. +Pour l'amélioration de ce programme merci de prévenir nounou en spécifiant la +marche à suivre pour reproduire cette erreur.""" + if debug : + print '-'*40 + print 'Détails techniques :' + import traceback + traceback.print_exc() + sys.exit(1) +