Compare commits

..

1 commit

Author SHA1 Message Date
Pauline Pommeret
1fc605c7e0 [gest_crans] set_etat_civil passe une liste a dialog 2015-03-05 14:06:48 +01:00
338 changed files with 24750 additions and 33474 deletions

11
.gitignore vendored
View file

@ -17,8 +17,17 @@
# Cr@ns specific ignore files # # Cr@ns specific ignore files #
############################### ###############################
# On ne versionne pas les fiches de déconnexion
surveillance/fiche_deconnexion/*
# Mais on garde de quoi les générer
!/surveillance/fiche_deconnexion/deconnexion_p2p.tex
!/surveillance/fiche_deconnexion/deconnexion_upload.tex
!/surveillance/fiche_deconnexion/generate.py
!/surveillance/fiche_deconnexion/logo.eps
!/surveillance/fiche_deconnexion/logo.eps.old
# Les clés wifi privées # Les clés wifi privées
archive/gestion/clef-wifi* gestion/clef-wifi*
# Autres dépôts git # Autres dépôts git
gestion/logreader/ gestion/logreader/

View file

@ -1,31 +0,0 @@
## Sous-dépôts
À cloner pour faire marcher certains scripts
* `./lc_ldap`
* `./wifi_new`
## Paquets Debian
Rajoutez-en si vous vous rendez compte qu'il en manque, à l'occasion.
* python-ldap
* python-netifaces
* python-psycopg2
* python-netsnmp
* python-pyparsing
* python-markdown
* python-jinja2
* python-beautifulsoup
* python-ipaddr
* python-passlib
* python-dateutil
* python-tz
* python-netaddr
## À faire
* Expliquer l'environnement de test
* tunnel pour apprentis
* http://stackoverflow.com/questions/8021/allow-user-to-set-up-an-ssh-tunnel-but-nothing-else
* snmp et les mibs ! !!

View file

@ -1,8 +1,7 @@
#!/bin/bash /usr/scripts/python.sh #! /usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) Stéphane Glondu, Alexandre Bos, Michel Blockelet # Copyright (C) Stéphane Glondu, Alexandre Bos, Michel Blockelet
# Remanié en 2015 par Gabriel Détraz
# Licence : GPLv2 # Licence : GPLv2
u"""Ce script permet au secrétaire de repérer plus facilement les membres u"""Ce script permet au secrétaire de repérer plus facilement les membres
@ -22,23 +21,22 @@ Les commandes sont :
import sys, os, re import sys, os, re
sys.path.append('/usr/scripts/gestion')
import config
import config.mails
from email_tools import send_email, parse_mail_template
# Fonctions d'affichage # Fonctions d'affichage
from gestion.affich_tools import coul, tableau, prompt, cprint from affich_tools import coul, tableau, prompt, cprint
from utils.sendmail import actually_sendmail
from gestion import mail
# Importation de la base de données # Importation de la base de données
from lc_ldap import shortcuts from ldap_crans import crans_ldap, ann_scol
db = crans_ldap()
# Lors des tests, on m'envoie tous les mails ! # Lors des tests, on m'envoie tous les mails !
from socket import gethostname from socket import gethostname
debug = False debug = False
# Conn à la db
ldap = shortcuts.lc_ldap_admin()
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) > 3 and sys.argv[-2] == '--debug': if len(sys.argv) > 3 and sys.argv[-2] == '--debug':
debug = sys.argv[-1] debug = sys.argv[-1]
@ -67,28 +65,33 @@ def _controle_interactif_adherents(liste):
nb = 0 nb = 0
for a in liste: for a in liste:
valeur = a['charteMA'] valeur = a.charteMA()
if valeur: if valeur:
suggestion = 'o' suggestion = 'o'
else: else:
suggestion = 'n' suggestion = 'n'
ok = prompt(u'[%3d] %s, %s (%s) ?' ok = prompt(u'[%3d] %s, %s (%s) ?'
% (restant, a['nom'][0], a['prenom'][0], a['aid'][0]), suggestion, '').lower() % (restant, a.nom(), a.prenom(), a.id()), suggestion, '').lower()
restant -= 1 restant -= 1
if ok == 'o': if ok == 'o':
nb += 1 nb += 1
if a['charteMA'] != True : if a.charteMA() == False :
modifiable = ldap.search(u'aid=%s' % a['aid'][0], mode='rw') modifiable = db.search('aid=%s' % a.id(), 'w')['adherent'][0]
try: if modifiable._modifiable:
with modifiable[0] as adh: modifiable.charteMA(True)
adh['charteMA']=True cprint(modifiable.save())
adh.history_gen() else:
adh.save() cprint(u'Adhérent %s locké, réessayer plus tard' % modifiable.Nom(), 'rouge')
cprint(u'Controle OK') elif ok == 'n':
except: if a.charteMA() == True:
cprint(u'Adhérent %s locké, réessayer plus tard' % a['nom'][0], 'rouge') modifiable = db.search('aid=%s' % a.id(), 'w')['adherent'][0]
elif ok != 'n': if modifiable._modifiable:
cprint(u'Arrêt du contrôle des membres actifs', 'rouge') modifiable.charteMA(False)
cprint(modifiable.save())
else:
cprint(u'Adhérent %s locké, réessayer plus tard' % modifiable.Nom(), 'rouge')
else:
cprint(u'Arrêt du contrôle %s des membres actifs' % explicite, 'rouge')
break break
return nb, len(liste)-nb return nb, len(liste)-nb
@ -96,12 +99,12 @@ def _controle_interactif_adherents(liste):
def liste_charte_nok(): def liste_charte_nok():
"""Retourne la liste des membres actifs qui n'ont pas signé la charte.""" """Retourne la liste des membres actifs qui n'ont pas signé la charte."""
liste_actifs = ldap.search(u'droits=*') liste_actifs = db.search('droits=*')['adherent']
liste_nok = [] liste_nok = []
for adh in liste_actifs: for adh in liste_actifs:
if (len([droit for droit in adh['droits'] if (len([droit for droit in adh.droits()
if droit not in ['Multimachines', 'Webradio']]) > 0 if droit not in ['Multimachines', 'Webradio']]) > 0
and not adh['charteMA']): and not adh.charteMA()):
liste_nok.append(adh) liste_nok.append(adh)
return liste_nok return liste_nok
@ -113,7 +116,7 @@ def controle_interactif():
# Tri de la liste des adhérents selon nom, prénom # Tri de la liste des adhérents selon nom, prénom
# Ça peut se faire plus facilement en Python 2.4 avec l'argument key # Ça peut se faire plus facilement en Python 2.4 avec l'argument key
todo_list.sort(lambda x, y: cmp((x['nom'][0], x['prenom'][0]), (y['nom'][0], y['prenom'][0]))) todo_list.sort(lambda x, y: cmp((x.nom(), x.prenom()), (y.nom(), y.prenom())))
# Zou ! # Zou !
ok, nok = _controle_interactif_adherents(todo_list) ok, nok = _controle_interactif_adherents(todo_list)
@ -129,18 +132,19 @@ def spammer():
todo_list = liste_charte_nok() todo_list = liste_charte_nok()
if todo_list: if todo_list:
from smtplib import SMTP
connexion = SMTP()
if gethostname().split(".")[0] == 'redisdead':
connexion.connect("localhost")
else: connexion.connect("redisdead.crans.org")
print "Envoi des mails de rappel pour les chartes des membres actifs" print "Envoi des mails de rappel pour les chartes des membres actifs"
for adh in todo_list: for adh in todo_list:
to = adh['mail'][0] to = adh.email()
print to print to
if not debug: if not debug:
From = u"ca@crans.org" data = config.mails.txt_charte_MA % {'From' : u"ca@crans.org", 'To' : to}
data=mail.generate('missing_charte_MA', { connexion.sendmail("ca@crans.org",to,data.encode('utf-8'))
'To': unicode(to),
'From': From,
})
actually_sendmail(u'ca@crans.org', (unicode(to),), data)
def __usage(message=None): def __usage(message=None):
""" Comment ça marche ? """ """ Comment ça marche ? """
@ -159,7 +163,7 @@ if __name__ == '__main__' :
__usage(u'Mauvaise utilisation de liste') __usage(u'Mauvaise utilisation de liste')
print "Liste des membres actifs n'ayant pas signé la charte :" print "Liste des membres actifs n'ayant pas signé la charte :"
for adh in liste_charte_nok(): for adh in liste_charte_nok():
print unicode(adh['prenom'][0]) + u" " + unicode(adh['nom'][0]) print adh.Nom()
elif sys.argv[1] == 'modif': elif sys.argv[1] == 'modif':
if len(sys.argv) != 2: if len(sys.argv) != 2:
__usage(u'Mauvaise utilisation de modif') __usage(u'Mauvaise utilisation de modif')

View file

@ -1,4 +1,4 @@
#!/bin/bash /usr/scripts/python.sh #! /usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
@ -12,8 +12,9 @@ Licence : GPL v2
import os, sys, time import os, sys, time
import subprocess import subprocess
from lc_ldap import shortcuts sys.path.append('/usr/scripts/gestion')
from gestion.config import upload from ldap_crans import crans_ldap
from config import upload
# logging tools # logging tools
import syslog import syslog
def log(x): def log(x):
@ -29,8 +30,6 @@ import utils.exceptions
import locale import locale
locale.setlocale(locale.LC_TIME, 'fr_FR.UTF-8') locale.setlocale(locale.LC_TIME, 'fr_FR.UTF-8')
# On blackliste 14 jours après que le script ait été éxécuté
DELAY = 14
help = """Script de déconnexion pour mail invalide. help = """Script de déconnexion pour mail invalide.
Une fiche sera générée pour chaque adhérent. Une fiche sera générée pour chaque adhérent.
@ -50,23 +49,22 @@ l'adhérent ayant l'aid 42."""
def generate_ps(proprio, mail): def generate_ps(proprio, mail):
"""On génère la feuille d'avertissement et on retourne son emplacement.""" """On génère la feuille d'avertissement et on retourne son emplacement."""
barcode = "/usr/scripts/admin/mail_invalide/barcode.eps" barcode = "/usr/scripts/admin/mail_invalide/barcode.eps"
name = unicode(proprio['prenom'][0]) + u" " + unicode(proprio['nom'][0])
try: try:
log(u'Generate invalid mail notice for %s' % name) log('Generate invalid mail notice for %s' % proprio.Nom())
# Dossier de génération du ps # Dossier de génération du ps
dossier = '/usr/scripts/var/mails_invalides' dossier = '/usr/scripts/var/mails_invalides'
# Base pour le nom du fichier # Base pour le nom du fichier
fichier = time.strftime('%Y-%m-%d-%H-%M') + '-mail-%s' % (name. fichier = time.strftime('%Y-%m-%d-%H-%M') + '-mail-%s' % (proprio.Nom().
lower().replace(' ', '-')) lower().replace(' ', '-'))
# Création du fichier tex # Création du fichier tex
format_date = '%A %d %B %Y' format_date = '%A %d %B %Y'
with open('%s/mail_invalide.tex' % os.path.dirname(__file__), 'r') as tempfile: with open('%s/mail_invalide.tex' % os.path.dirname(__file__), 'r') as tempfile:
template = tempfile.read() template = tempfile.read()
template = template.replace('~prenom~', proprio['prenom'][0].encode('utf-8')) template = template.replace('~prenom~', proprio.prenom().encode('utf-8'))
template = template.replace('~nom~', proprio['nom'][0].encode('utf-8')) template = template.replace('~nom~', proprio.nom().encode('utf-8'))
template = template.replace('~chambre~', proprio['chbre'][0].encode('utf-8')) template = template.replace('~chambre~', proprio.chbre().encode('utf-8'))
template = template.replace('~mail~', mail.encode('utf-8').replace('_', '\\_')) template = template.replace('~mail~', mail.encode('utf-8').replace('_', '\\_'))
template = template.replace('~fin~', template = template.replace('~fin~',
time.strftime(format_date, time.localtime(time.time()+14*86400))) time.strftime(format_date, time.localtime(time.time()+14*86400)))
@ -85,37 +83,35 @@ def generate_ps(proprio, mail):
except Exception, e: except Exception, e:
log('Erreur lors de la génération du ps : ') log('Erreur lors de la génération du ps : ')
log(str(e)) log(str(e))
log("Values : adherent:%s" % name) log("Values : adherent:%s" % proprio.Nom())
log(utils.exceptions.formatExc()) log(utils.exceptions.formatExc())
raise raise
def set_mail_invalide(adherent, mail, a_verifier, a_imprimer): def set_mail_invalide(adherent, mail, a_verifier, a_imprimer):
name = unicode(adherent['prenom'][0]) + u" " + unicode(adherent['nom'][0]) if adherent.chbre() in ['????', 'EXT']:
if adherent['chbre'][0] in ['????', 'EXT']: print u"Chambre de %s : %s, générer la fiche ? [Yn]" % (adherent.Nom().encode('utf-8'), adherent.chbre())
print u"Chambre de %s : %s, générer la fiche ? [Yn]" % (name, adherent['chbre'][0])
read = '' read = ''
while read not in ['y', 'n']: while read not in ['y', 'n']:
read = raw_input().lower() read = raw_input().lower()
if read == 'n': if read == 'n':
print u"Chambre de %s : %s, impossible de générer la fiche." % (name, adherent['chbre'][0]) print u"Chambre de %s : %s, impossible de générer la fiche." % (adherent.Nom().encode('utf-8'), adherent.chbre())
a_verifier.append(mail) a_verifier.append(mail)
return return
print u"Génération de la fiche pour %s :" % name print "Génération de la fiche pour %s :" % adherent.Nom().encode('utf-8')
fiche = generate_ps(adherent, mail) fiche = generate_ps(adherent, mail)
print fiche print fiche
a_imprimer.append(fiche) a_imprimer.append(fiche)
with adherent as adh: adherent.blacklist([time.time() + 14 * 24 * 3600,
adh.blacklist('mail_invalide','Mail Invalide - Script',debut=int(time.time()) + DELAY * 24 * 3600) '-', 'mail_invalide', "Mail invalide"])
adh.history_gen() adherent.save()
adh.save()
if __name__ == "__main__": if __name__ == "__main__":
if '--help' in sys.argv or '-h' in sys.argv or len(sys.argv) < 2: if '--help' in sys.argv or '-h' in sys.argv or len(sys.argv) < 2:
print help print help
sys.exit(0) sys.exit(0)
ldap = shortcuts.lc_ldap_admin() db = crans_ldap()
# On fait la liste des .forwards dans les homes # On fait la liste des .forwards dans les homes
print " * Lecture des .forward ..." print " * Lecture des .forward ..."
@ -145,24 +141,24 @@ if __name__ == "__main__":
# Est-ce un aid ? # Est-ce un aid ?
if adresse[0] == '-': if adresse[0] == '-':
print " * Recherche de aid=%s ..." % adresse[1:] print " * Recherche de aid=%s ..." % adresse[1:]
res = ldap.search(u"aid=%s" % adresse[1:], mode='rw') res = db.search("aid=%s" % adresse[1:], 'w')['adherent']
if len(res) == 0: if len(res) == 0:
print "*** Erreur : aucun résultat pour aid=%s" % adresse[1:] print "*** Erreur : aucun résultat pour aid=%s" % adresse[1:]
a_verifier.append(adresse) a_verifier.append(adresse)
elif len(res) > 1: elif len(res) > 1:
print "*** Erreur : plusieurs résultats pour aid=%s :" % adresse[1:] print "*** Erreur : plusieurs résultats pour aid=%s :" % adresse[1:]
for adh in res: for adh in res:
print unicode(adh['prenom'][0]) + u" " + unicode(adh['nom'][0]) print adh.Nom()
a_verifier.append(adresse) a_verifier.append(adresse)
else: else:
adherent = res[0] adherent = res[0]
set_mail_invalide(adherent, adherent['mail'][0], a_verifier, a_imprimer) set_mail_invalide(adherent, adherent.email(), a_verifier, a_imprimer)
continue continue
print " * Recherche de %s ..." % adresse print " * Recherche de %s ..." % adresse
# Est-ce un .forward ? # Est-ce un .forward ?
if forwards.has_key(adresse): if forwards.has_key(adresse):
res = ldap.search(u"uid=%s" % forwards[adresse], mode='rw') res = db.search("uid=%s" % forwards[adresse], 'w')['adherent']
if len(res) == 0: if len(res) == 0:
print "*** Erreur : aucun résultat pour uid=%s" % forwards[adresse] print "*** Erreur : aucun résultat pour uid=%s" % forwards[adresse]
a_verifier.append(adresse) a_verifier.append(adresse)
@ -172,18 +168,18 @@ if __name__ == "__main__":
continue continue
# Est-ce une adresse mail sans compte Cr@ns ? # Est-ce une adresse mail sans compte Cr@ns ?
res = ldap.search(u"(|(mail=%s)(mailExt=%s))" % (adresse,adresse), mode='rw') res = db.search("mail=%s" % adresse, 'w')['adherent']
if len(res) == 0: if len(res) == 0:
print "*** Erreur : aucun résultat pour %s" % adresse print "*** Erreur : aucun résultat pour %s" % adresse
a_verifier.append(adresse) a_verifier.append(adresse)
elif len(res) > 1: elif len(res) > 1:
print "*** Erreur : plusieurs résultats pour %s :" % adresse print "*** Erreur : plusieurs résultats pour %s :" % adresse
for adh in res: for adh in res:
print unicode(adh['prenom'][0]) + u" " + unicode(adh['nom'][0]) print adh.Nom()
a_verifier.append(adresse) a_verifier.append(adresse)
else: else:
adherent = res[0] adherent = res[0]
set_mail_invalide(adherent, adresse, a_verifier, a_imprimer) set_mail_invalide(adherent, adherent.email(), a_verifier, a_imprimer)
if len(a_verifier) + len(a_imprimer) > 0: if len(a_verifier) + len(a_imprimer) > 0:
print '' print ''

View file

@ -1,8 +1,9 @@
#!/bin/bash /usr/scripts/python.sh #! /usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import sys
# Copyright (C) Stéphane Glondu, Alexandre Bos, et autres # Copyright (C) Stéphane Glondu, Alexandre Bos
# Licence : GPLv2 # Licence : GPLv2
__doc__ = u"""Ce script permet de faire le menages parmis les câbleurs qui ne __doc__ = u"""Ce script permet de faire le menages parmis les câbleurs qui ne
@ -20,14 +21,16 @@ Les commandes sont :
import sys, os, re import sys, os, re
import gestion.config sys.path.append('/usr/scripts/gestion')
import config
from email_tools import send_email, parse_mail_template
# Fonctions d'affichage # Fonctions d'affichage
from gestion.affich_tools import coul, tableau, prompt, cprint from affich_tools import coul, tableau, prompt, cprint
# Importation de la base de données # Importation de la base de données
from lc_ldap import shortcuts from ldap_crans import crans_ldap, ann_scol
ldap = shortcuts.lc_ldap_admin() db = crans_ldap()
def _controle_interactif_adherents(liste): def _controle_interactif_adherents(liste):
""" """
@ -47,28 +50,26 @@ def _controle_interactif_adherents(liste):
nb = 0 nb = 0
for a in liste: for a in liste:
ok = prompt(u'[%3d] %s, %s (%s) ?' ok = prompt(u'[%3d] %s, %s (%s) ?'
% (restant, a['nom'][0], a['prenom'][0], a['aid'][0]), 'n', '').lower() % (restant, a.nom(), a.prenom(), a.id()), 'n', '').lower()
restant -= 1 restant -= 1
if ok == 'o': if ok == 'o':
modifiable = ldap.search(u'aid=%s' % a['aid'][0], mode='rw')[0] modifiable = db.search('aid=%s' % a.id(), 'w')['adherent'][0]
try: if modifiable._modifiable:
with modifiable as adh: modifiable.droits([])
adh['droits'].remove(u'Cableur') cprint(modifiable.save())
adh.history_gen() else:
adh.save() cprint(u'Adhérent %s locké, réessayer plus tard' % modifiable.Nom(), 'rouge')
cprint(u'Droits cableurs retirés', 'rouge')
except:
cprint(u'Adhérent %s locké, réessayer plus tard' % modifiable['nom'][0], 'rouge')
elif ok != 'n': elif ok != 'n':
cprint(u'Arrêt du contrôle %s des membres actifs' % explicite, 'rouge') cprint(u'Arrêt du contrôle %s des membres actifs' % explicite, 'rouge')
break break
def candidats(): def candidats():
todo_list1 = ldap.search(u'droits=cableur') todo_list1 = db.search('droits=*')['adherent']
todo_list = [] todo_list = []
for adh in todo_list1: for adh in todo_list1:
if not adh.paiement_ok(): if adh.droitsGeles():
todo_list.append(adh) todo_list.append(adh)
todo_list.sort(lambda x, y: cmp((x.nom(), x.prenom()), (y.nom(), y.prenom())))
return todo_list return todo_list
def lister(): def lister():
@ -79,7 +80,7 @@ def lister():
print "Liste des câbleur dont la cotisation n'est pas à jour." print "Liste des câbleur dont la cotisation n'est pas à jour."
print print
for adh in todo_list: for adh in todo_list:
print unicode(adh['prenom'][0]) + u" " + unicode(adh['nom'][0]) print adh.prenom() + " " + adh.nom()
print print
print "total : " + str(len(todo_list)) print "total : " + str(len(todo_list))

View file

@ -1,4 +1,4 @@
#!/bin/bash /usr/scripts/python.sh #!/usr/bin/python
# -*- mode: python; coding: utf-8 -*- # -*- mode: python; coding: utf-8 -*-
# #
# total_impression.py # total_impression.py
@ -6,7 +6,6 @@
# #
# Copyright (C) 2007 Michel Blockelet <blockelet@crans.org> # Copyright (C) 2007 Michel Blockelet <blockelet@crans.org>
# #
# Revu et corrigé en 2015 par Gabriel Détraz
# This file is free software; you can redistribute it and/or modify # This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or # the Free Software Foundation; either version 2 of the License, or
@ -33,11 +32,14 @@ Options :
Les dates doivent etre de la forme jj/mm/aaaa.""" Les dates doivent etre de la forme jj/mm/aaaa."""
import sys import sys
from lc_ldap import shortcuts sys.path.append("/usr/scripts/gestion/")
from gestion.affich_tools import cprint from ldap_crans import crans_ldap
from config import ann_scol
from affich_tools import cprint
import time import time
ldap = shortcuts.lc_ldap_admin() db = crans_ldap()
date_debut_ann_scol = time.mktime((ann_scol, 8, 1, 0, 0, 0, 0, 0, 0))
def datestrtoint(strdate): def datestrtoint(strdate):
u""" Convertit une date en entier. """ u""" Convertit une date en entier. """
@ -50,7 +52,7 @@ def soldes_adherent(dlinf, dlsup, adherent, verbose):
totaldebit = 0 totaldebit = 0
totalcredit = 0 totalcredit = 0
for hist in adherent['historique']: for hist in adherent.historique():
sep = ' ' sep = ' '
champ = hist.replace(',', '').replace(': ', '').split(sep) champ = hist.replace(',', '').replace(': ', '').split(sep)
if datestrtoint(champ[0]) >= dlinf and (dlsup == 0 or datestrtoint(champ[0]) <= dlsup): if datestrtoint(champ[0]) >= dlinf and (dlsup == 0 or datestrtoint(champ[0]) <= dlsup):
@ -110,23 +112,19 @@ def calcul_soldes():
totaldebit = 0 totaldebit = 0
totalcredit = 0 totalcredit = 0
liste = ldap.search(u"uid=*",sizelimit=10000) liste = db.search("login=*")['adherent']
for adherent in liste: for adherent in liste:
try:
adhdebit, adhcredit = soldes_adherent(dlinf, dlsup, adherent, verbose) adhdebit, adhcredit = soldes_adherent(dlinf, dlsup, adherent, verbose)
if adhdebit + adhcredit > 0 and adhdebit + adhcredit < 1000000: # On evite Toto Passoir if adhdebit + adhcredit > 0 and adhdebit + adhcredit < 1000000: # On evite Toto Passoir
if verbose >= 2: if verbose >= 2:
cprint('-' * 40, 'cyan') cprint('-' * 40, 'cyan')
if verbose >= 1: if verbose >= 1:
name = unicode(adherent['prenom'][0]) + u" " + unicode(adherent['nom'][0]) cprint('Debit total pour ' + adherent.Nom() + ' : ' + str(adhdebit) + ' euros', 'rouge')
cprint(u'Debit total pour ' + name + u' : ' + unicode(adhdebit) + u' euros', 'rouge') cprint('Credit total pour ' + adherent.Nom() + ' : ' + str(adhcredit) + ' euros', 'vert')
cprint(u'Credit total pour ' + name + u' : ' + unicode(adhcredit) + u' euros', 'vert')
cprint('=' * 40, 'bleu') cprint('=' * 40, 'bleu')
totaldebit += adhdebit totaldebit += adhdebit
totalcredit += adhcredit totalcredit += adhcredit
except KeyError:
pass
if verbose >= 1: if verbose >= 1:
cprint('=' * 80, 'bleu') cprint('=' * 80, 'bleu')
if dlinf == 0: if dlinf == 0:

View file

@ -1,683 +0,0 @@
"""All Python Type client support for Bcfg2."""
__revision__ = '$Revision$'
import binascii
from datetime import datetime
import difflib
import errno
import grp
import logging
import os
import pwd
import shutil
import stat
import sys
import time
# py3k compatibility
if sys.hexversion >= 0x03000000:
unicode = str
import Bcfg2.Client.Tools
import Bcfg2.Options
from Bcfg2.Client import XML
log = logging.getLogger('python')
# map between dev_type attribute and stat constants
device_map = {'block': stat.S_IFBLK,
'char': stat.S_IFCHR,
'fifo': stat.S_IFIFO}
def calcPerms(initial, perms):
"""This compares ondisk permissions with specified ones."""
pdisp = [{1:stat.S_ISVTX, 2:stat.S_ISGID, 4:stat.S_ISUID},
{1:stat.S_IXUSR, 2:stat.S_IWUSR, 4:stat.S_IRUSR},
{1:stat.S_IXGRP, 2:stat.S_IWGRP, 4:stat.S_IRGRP},
{1:stat.S_IXOTH, 2:stat.S_IWOTH, 4:stat.S_IROTH}]
tempperms = initial
if len(perms) == 3:
perms = '0%s' % (perms)
pdigits = [int(perms[digit]) for digit in range(4)]
for index in range(4):
for (num, perm) in list(pdisp[index].items()):
if pdigits[index] & num:
tempperms |= perm
return tempperms
def normGid(entry):
"""
This takes a group name or gid and
returns the corresponding gid or False.
"""
try:
try:
return int(entry.get('group'))
except:
return int(grp.getgrnam(entry.get('group'))[2])
except (OSError, KeyError):
log.error('GID normalization failed for %s. Does group %s exist?'
% (entry.get('name'), entry.get('group')))
return False
def normUid(entry):
"""
This takes a user name or uid and
returns the corresponding uid or False.
"""
try:
try:
return int(entry.get('owner'))
except:
return int(pwd.getpwnam(entry.get('owner'))[2])
except (OSError, KeyError):
log.error('UID normalization failed for %s. Does owner %s exist?'
% (entry.get('name'), entry.get('owner')))
return False
def isString(strng, encoding):
"""
Returns true if the string contains no ASCII control characters
and can be decoded from the specified encoding.
"""
for char in strng:
if ord(char) < 9 or ord(char) > 13 and ord(char) < 32:
return False
try:
strng.decode(encoding)
return True
except:
return False
class Python(Bcfg2.Client.Tools.Tool):
"""Python File support code."""
name = 'Python'
__handles__ = [('Python', 'file'),
('Python', None)]
__req__ = {'Python': ['name']}
# grab paranoid options from /etc/bcfg2.conf
opts = {'ppath': Bcfg2.Options.PARANOID_PATH,
'max_copies': Bcfg2.Options.PARANOID_MAX_COPIES}
setup = Bcfg2.Options.OptionParser(opts)
setup.parse([])
ppath = setup['ppath']
max_copies = setup['max_copies']
def canInstall(self, entry):
"""Check if entry is complete for installation."""
if Bcfg2.Client.Tools.Tool.canInstall(self, entry):
if (entry.tag,
entry.get('type'),
entry.text,
entry.get('empty', 'false')) == ('Python',
'file',
None,
'false'):
return False
return True
else:
return False
def gatherCurrentData(self, entry):
if entry.tag == 'Python' and entry.get('type') == 'file':
try:
ondisk = os.stat(entry.get('name'))
except OSError:
entry.set('current_exists', 'false')
self.logger.debug("%s %s does not exist" %
(entry.tag, entry.get('name')))
return False
try:
entry.set('current_owner', str(ondisk[stat.ST_UID]))
entry.set('current_group', str(ondisk[stat.ST_GID]))
except (OSError, KeyError):
pass
entry.set('perms', str(oct(ondisk[stat.ST_MODE])[-4:]))
def Verifydirectory(self, entry, modlist):
"""Verify Path type='directory' entry."""
if entry.get('perms') == None or \
entry.get('owner') == None or \
entry.get('group') == None:
self.logger.error('Entry %s not completely specified. '
'Try running bcfg2-lint.' % (entry.get('name')))
return False
while len(entry.get('perms', '')) < 4:
entry.set('perms', '0' + entry.get('perms', ''))
try:
ondisk = os.stat(entry.get('name'))
except OSError:
entry.set('current_exists', 'false')
self.logger.debug("%s %s does not exist" %
(entry.tag, entry.get('name')))
return False
try:
owner = str(ondisk[stat.ST_UID])
group = str(ondisk[stat.ST_GID])
except (OSError, KeyError):
self.logger.error('User/Group resolution failed for path %s' % \
entry.get('name'))
owner = 'root'
group = '0'
finfo = os.stat(entry.get('name'))
perms = oct(finfo[stat.ST_MODE])[-4:]
if entry.get('mtime', '-1') != '-1':
mtime = str(finfo[stat.ST_MTIME])
else:
mtime = '-1'
pTrue = ((owner == str(normUid(entry))) and
(group == str(normGid(entry))) and
(perms == entry.get('perms')) and
(mtime == entry.get('mtime', '-1')))
pruneTrue = True
ex_ents = []
if entry.get('prune', 'false') == 'true' \
and (entry.tag == 'Path' and entry.get('type') == 'directory'):
# check for any extra entries when prune='true' attribute is set
try:
entries = ['/'.join([entry.get('name'), ent]) \
for ent in os.listdir(entry.get('name'))]
ex_ents = [e for e in entries if e not in modlist]
if ex_ents:
pruneTrue = False
self.logger.debug("Directory %s contains extra entries:" % \
entry.get('name'))
self.logger.debug(ex_ents)
nqtext = entry.get('qtext', '') + '\n'
nqtext += "Directory %s contains extra entries:" % \
entry.get('name')
nqtext += ":".join(ex_ents)
entry.set('qtest', nqtext)
[entry.append(XML.Element('Prune', path=x)) \
for x in ex_ents]
except OSError:
ex_ents = []
pruneTrue = True
if not pTrue:
if owner != str(normUid(entry)):
entry.set('current_owner', owner)
self.logger.debug("%s %s ownership wrong" % \
(entry.tag, entry.get('name')))
nqtext = entry.get('qtext', '') + '\n'
nqtext += "%s owner wrong. is %s should be %s" % \
(entry.get('name'), owner, entry.get('owner'))
entry.set('qtext', nqtext)
if group != str(normGid(entry)):
entry.set('current_group', group)
self.logger.debug("%s %s group wrong" % \
(entry.tag, entry.get('name')))
nqtext = entry.get('qtext', '') + '\n'
nqtext += "%s group is %s should be %s" % \
(entry.get('name'), group, entry.get('group'))
entry.set('qtext', nqtext)
if perms != entry.get('perms'):
entry.set('current_perms', perms)
self.logger.debug("%s %s permissions are %s should be %s" %
(entry.tag,
entry.get('name'),
perms,
entry.get('perms')))
nqtext = entry.get('qtext', '') + '\n'
nqtext += "%s %s perms are %s should be %s" % \
(entry.tag,
entry.get('name'),
perms,
entry.get('perms'))
entry.set('qtext', nqtext)
if mtime != entry.get('mtime', '-1'):
entry.set('current_mtime', mtime)
self.logger.debug("%s %s mtime is %s should be %s" \
% (entry.tag, entry.get('name'), mtime,
entry.get('mtime')))
nqtext = entry.get('qtext', '') + '\n'
nqtext += "%s mtime is %s should be %s" % \
(entry.get('name'), mtime, entry.get('mtime'))
entry.set('qtext', nqtext)
if entry.get('type') != 'file':
nnqtext = entry.get('qtext')
nnqtext += '\nInstall %s %s: (y/N) ' % (entry.get('type'),
entry.get('name'))
entry.set('qtext', nnqtext)
return pTrue and pruneTrue
def Installdirectory(self, entry):
"""Install Path type='directory' entry."""
if entry.get('perms') == None or \
entry.get('owner') == None or \
entry.get('group') == None:
self.logger.error('Entry %s not completely specified. '
'Try running bcfg2-lint.' % \
(entry.get('name')))
return False
self.logger.info("Installing directory %s" % (entry.get('name')))
try:
fmode = os.lstat(entry.get('name'))
if not stat.S_ISDIR(fmode[stat.ST_MODE]):
self.logger.debug("Found a non-directory entry at %s" % \
(entry.get('name')))
try:
os.unlink(entry.get('name'))
exists = False
except OSError:
self.logger.info("Failed to unlink %s" % \
(entry.get('name')))
return False
else:
self.logger.debug("Found a pre-existing directory at %s" % \
(entry.get('name')))
exists = True
except OSError:
# stat failed
exists = False
if not exists:
parent = "/".join(entry.get('name').split('/')[:-1])
if parent:
try:
os.stat(parent)
except:
self.logger.debug('Creating parent path for directory %s' % (entry.get('name')))
for idx in range(len(parent.split('/')[:-1])):
current = '/'+'/'.join(parent.split('/')[1:2+idx])
try:
sloc = os.stat(current)
except OSError:
try:
os.mkdir(current)
continue
except OSError:
return False
if not stat.S_ISDIR(sloc[stat.ST_MODE]):
try:
os.unlink(current)
os.mkdir(current)
except OSError:
return False
try:
os.mkdir(entry.get('name'))
except OSError:
self.logger.error('Failed to create directory %s' % \
(entry.get('name')))
return False
if entry.get('prune', 'false') == 'true' and entry.get("qtest"):
for pent in entry.findall('Prune'):
pname = pent.get('path')
ulfailed = False
if os.path.isdir(pname):
self.logger.info("Not removing extra directory %s, "
"please check and remove manually" % pname)
continue
try:
self.logger.debug("Unlinking file %s" % pname)
os.unlink(pname)
except OSError:
self.logger.error("Failed to unlink path %s" % pname)
ulfailed = True
if ulfailed:
return False
return self.Installpermissions(entry)
def Verifyfile(self, entry, _):
"""Verify Python type='file' entry."""
# permissions check + content check
permissionStatus = self.Verifydirectory(entry, _)
tbin = False
if entry.text == None and entry.get('empty', 'false') == 'false':
self.logger.error("Cannot verify incomplete Python type='%s' %s" %
(entry.get('type'), entry.get('name')))
return False
if entry.get('encoding', 'ascii') == 'base64':
tempdata = binascii.a2b_base64(entry.text)
tbin = True
elif entry.get('empty', 'false') == 'true':
tempdata = ''
else:
tempdata = entry.text
if type(tempdata) == unicode:
try:
tempdata = tempdata.encode(self.setup['encoding'])
except UnicodeEncodeError:
e = sys.exc_info()[1]
self.logger.error("Error encoding file %s:\n %s" % \
(entry.get('name'), e))
different = False
content = None
if not os.path.exists(entry.get("name")):
# first, see if the target file exists at all; if not,
# they're clearly different
different = True
content = ""
else:
# next, see if the size of the target file is different
# from the size of the desired content
try:
estat = os.stat(entry.get('name'))
except OSError:
err = sys.exc_info()[1]
self.logger.error("Failed to stat %s: %s" %
(err.filename, err))
return False
if len(tempdata) != estat[stat.ST_SIZE]:
different = True
else:
# finally, read in the target file and compare them
# directly. comparison could be done with a checksum,
# which might be faster for big binary files, but
# slower for everything else
try:
content = open(entry.get('name')).read()
except IOError:
err = sys.exc_info()[1]
self.logger.error("Failed to read %s: %s" %
(err.filename, err))
return False
different = content != tempdata
if different:
if self.setup['interactive']:
prompt = [entry.get('qtext', '')]
if not tbin and content is None:
# it's possible that we figured out the files are
# different without reading in the local file. if
# the supplied version of the file is not binary,
# we now have to read in the local file to figure
# out if _it_ is binary, and either include that
# fact or the diff in our prompts for -I
try:
content = open(entry.get('name')).read()
except IOError:
err = sys.exc_info()[1]
self.logger.error("Failed to read %s: %s" %
(err.filename, err))
return False
if tbin or not isString(content, self.setup['encoding']):
# don't compute diffs if the file is binary
prompt.append('Binary file, no printable diff')
else:
diff = self._diff(content, tempdata,
difflib.unified_diff,
filename=entry.get("name"))
if diff:
udiff = '\n'.join(diff)
try:
prompt.append(udiff.decode(self.setup['encoding']))
except UnicodeDecodeError:
prompt.append("Binary file, no printable diff")
else:
prompt.append("Diff took too long to compute, no "
"printable diff")
prompt.append("Install %s %s: (y/N): " % (entry.tag,
entry.get('name')))
entry.set("qtext", "\n".join(prompt))
if entry.get('sensitive', 'false').lower() != 'true':
if content is None:
# it's possible that we figured out the files are
# different without reading in the local file. we
# now have to read in the local file to figure out
# if _it_ is binary, and either include the whole
# file or the diff for reports
try:
content = open(entry.get('name')).read()
except IOError:
err = sys.exc_info()[1]
self.logger.error("Failed to read %s: %s" %
(err.filename, err))
return False
if tbin or not isString(content, self.setup['encoding']):
# don't compute diffs if the file is binary
entry.set('current_bfile', binascii.b2a_base64(content))
else:
diff = self._diff(content, tempdata, difflib.ndiff,
filename=entry.get("name"))
if diff:
entry.set("current_bdiff",
binascii.b2a_base64("\n".join(diff)))
elif not tbin and isString(content, self.setup['encoding']):
entry.set('current_bfile', binascii.b2a_base64(content))
elif permissionStatus == False and self.setup['interactive']:
prompt = [entry.get('qtext', '')]
prompt.append("Install %s %s: (y/N): " % (entry.tag,
entry.get('name')))
entry.set("qtext", "\n".join(prompt))
return permissionStatus and not different
def Installfile(self, entry):
"""Install Python type='file' entry."""
self.logger.info("Installing file %s" % (entry.get('name')))
parent = "/".join(entry.get('name').split('/')[:-1])
if parent:
try:
os.stat(parent)
except:
self.logger.debug('Creating parent path for config file %s' % \
(entry.get('name')))
current = '/'
for next in parent.split('/')[1:]:
current += next + '/'
try:
sloc = os.stat(current)
try:
if not stat.S_ISDIR(sloc[stat.ST_MODE]):
self.logger.debug('%s is not a directory; recreating' \
% (current))
os.unlink(current)
os.mkdir(current)
except OSError:
return False
except OSError:
try:
self.logger.debug("Creating non-existent path %s" % current)
os.mkdir(current)
except OSError:
return False
# If we get here, then the parent directory should exist
if (entry.get("paranoid", False) in ['true', 'True']) and \
self.setup.get("paranoid", False) and not \
(entry.get('current_exists', 'true') == 'false'):
bkupnam = entry.get('name').replace('/', '_')
# current list of backups for this file
try:
bkuplist = [f for f in os.listdir(self.ppath) if
f.startswith(bkupnam)]
except OSError:
e = sys.exc_info()[1]
self.logger.error("Failed to create backup list in %s: %s" %
(self.ppath, e.strerror))
return False
bkuplist.sort()
while len(bkuplist) >= int(self.max_copies):
# remove the oldest backup available
oldest = bkuplist.pop(0)
self.logger.info("Removing %s" % oldest)
try:
os.remove("%s/%s" % (self.ppath, oldest))
except:
self.logger.error("Failed to remove %s/%s" % \
(self.ppath, oldest))
return False
try:
# backup existing file
shutil.copy(entry.get('name'),
"%s/%s_%s" % (self.ppath, bkupnam,
datetime.isoformat(datetime.now())))
self.logger.info("Backup of %s saved to %s" %
(entry.get('name'), self.ppath))
except IOError:
e = sys.exc_info()[1]
self.logger.error("Failed to create backup file for %s" % \
(entry.get('name')))
self.logger.error(e)
return False
try:
newfile = open("%s.new"%(entry.get('name')), 'w')
if entry.get('encoding', 'ascii') == 'base64':
filedata = binascii.a2b_base64(entry.text)
elif entry.get('empty', 'false') == 'true':
filedata = ''
else:
if type(entry.text) == unicode:
filedata = entry.text.encode(self.setup['encoding'])
else:
filedata = entry.text
newfile.write(filedata)
newfile.close()
try:
os.chown(newfile.name, normUid(entry), normGid(entry))
except KeyError:
self.logger.error("Failed to chown %s to %s:%s" %
(newfile.name, entry.get('owner'),
entry.get('group')))
os.chown(newfile.name, 0, 0)
except OSError:
err = sys.exc_info()[1]
self.logger.error("Could not chown %s: %s" % (newfile.name,
err))
os.chmod(newfile.name, calcPerms(stat.S_IFREG, entry.get('perms')))
os.rename(newfile.name, entry.get('name'))
if entry.get('mtime', '-1') != '-1':
try:
os.utime(entry.get('name'), (int(entry.get('mtime')),
int(entry.get('mtime'))))
except:
self.logger.error("File %s mtime fix failed" \
% (entry.get('name')))
return False
return True
except (OSError, IOError):
err = sys.exc_info()[1]
if err.errno == errno.EACCES:
self.logger.info("Failed to open %s for writing" % (entry.get('name')))
else:
print(err)
return False
def Verifypermissions(self, entry, _):
"""Verify Path type='permissions' entry"""
if entry.get('perms') == None or \
entry.get('owner') == None or \
entry.get('group') == None:
self.logger.error('Entry %s not completely specified. '
'Try running bcfg2-lint.' % (entry.get('name')))
return False
if entry.get('recursive') in ['True', 'true']:
# verify ownership information recursively
owner = normUid(entry)
group = normGid(entry)
for root, dirs, files in os.walk(entry.get('name')):
for p in dirs + files:
path = os.path.join(root, p)
pstat = os.stat(path)
if owner != pstat.st_uid:
# owner mismatch for path
entry.set('current_owner', str(pstat.st_uid))
self.logger.debug("%s %s ownership wrong" % \
(entry.tag, path))
nqtext = entry.get('qtext', '') + '\n'
nqtext += ("Owner for path %s is incorrect. "
"Current owner is %s but should be %s\n" % \
(path, pstat.st_uid, entry.get('owner')))
nqtext += ("\nInstall %s %s: (y/N): " %
(entry.tag, entry.get('name')))
entry.set('qtext', nqtext)
return False
if group != pstat.st_gid:
# group mismatch for path
entry.set('current_group', str(pstat.st_gid))
self.logger.debug("%s %s group wrong" % \
(entry.tag, path))
nqtext = entry.get('qtext', '') + '\n'
nqtext += ("Group for path %s is incorrect. "
"Current group is %s but should be %s\n" % \
(path, pstat.st_gid, entry.get('group')))
nqtext += ("\nInstall %s %s: (y/N): " %
(entry.tag, entry.get('name')))
entry.set('qtext', nqtext)
return False
return self.Verifydirectory(entry, _)
def _diff(self, content1, content2, difffunc, filename=None):
rv = []
start = time.time()
longtime = False
for diffline in difffunc(content1.split('\n'),
content2.split('\n')):
now = time.time()
rv.append(diffline)
if now - start > 5 and not longtime:
if filename:
self.logger.info("Diff of %s taking a long time" %
filename)
else:
self.logger.info("Diff taking a long time")
longtime = True
elif now - start > 30:
if filename:
self.logger.error("Diff of %s took too long; giving up" %
filename)
else:
self.logger.error("Diff took too long; giving up")
return False
return rv
def Installpermissions(self, entry):
"""Install POSIX permissions"""
if entry.get('perms') == None or \
entry.get('owner') == None or \
entry.get('group') == None:
self.logger.error('Entry %s not completely specified. '
'Try running bcfg2-lint.' % (entry.get('name')))
return False
plist = [entry.get('name')]
if entry.get('recursive') in ['True', 'true']:
# verify ownership information recursively
owner = normUid(entry)
group = normGid(entry)
for root, dirs, files in os.walk(entry.get('name')):
for p in dirs + files:
path = os.path.join(root, p)
pstat = os.stat(path)
if owner != pstat.st_uid or group != pstat.st_gid:
# owner mismatch for path
plist.append(path)
try:
for p in plist:
os.chown(p, normUid(entry), normGid(entry))
os.chmod(p, calcPerms(stat.S_IFDIR, entry.get('perms')))
return True
except (OSError, KeyError):
self.logger.error('Permission fixup failed for %s' % \
(entry.get('name')))
return False
def InstallNone(self, entry):
return self.Installfile(entry)
def VerifyNone(self, entry, _):
return self.Verifyfile(entry, _)
def InstallPython(self, entry):
"""Dispatch install to the proper method according to type"""
ret = getattr(self, 'Install%s' % entry.get('type'))
return ret(entry)
def VerifyPython(self, entry, _):
"""Dispatch verify to the proper method according to type"""
ret = getattr(self, 'Verify%s' % entry.get('type'))
return ret(entry, _)

View file

@ -1,50 +0,0 @@
# -*- coding: utf8 -*-
import os
LABELS = {
"/home":u"Dossier personnel",
"/var/mail":u"Boite de réception"
}
def getFloat( chose ):
chose = chose.replace(',', '.')
return float(chose)
def getUserQuota( userLogin ):
pipe = os.popen("sudo quota %s" % userLogin)
string_result = pipe.read()
pipe.close()
string_result = string_result.split("\n")
quotas = []
for a_line in string_result[2:-1]:
usage, quota, limite, percentage, fs = a_line.split("\t")
line_dict = {
"label": "Quota personnel",
"usage":getFloat(usage),
"quota":getFloat(quota),
"limite":getFloat(limite),
"%":getFloat(percentage),
"filesystem":fs, # pourquoi pas ?
}
quotas.append(line_dict)
return quotas
def fake_getUserQuota( userLogin ):
return [
{'%': 33.9,
'quota': 390.62,
'label': u'Dossier personnel (fake)',
'limite': 585.94,
'filesystem': '/home',
'usage': 420.32},
{'%': 0.1,
'quota': 100.00,
'label': u'Boite de r\xe9ception (fake)',
'limite': 150.00,
'filesystem': '/var/mail',
'usage': 0.06}
]

View file

@ -1,10 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Contient les valeurs par défaut du plugin python
de Bcfg2"""
DEFAULT_USER = 'root'
DEFAULT_GROUP = 'root'
DEFAULT_ACLS = 0644
INCLUDES = "../etc/python"

View file

@ -1,113 +0,0 @@
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
"""SafeEnvironment implementation for use of exec"""
import os
import cStringIO
import PythonDefaults
import PythonFile
class SafeEnvironment(dict):
"""Environnement isolé dans lequel on exécute un script"""
def __init__(self, additionnal=None, parent=None):
# Création de l'environment initial
super(self.__class__, self).__init__({
# Écrit: variable keysep tostring(value)
"defvar": self.defvar,
# La convertion en chaîne de charactère
"tostring": self.tostring,
# Définition des convertions
"conv": {
bool: {
True: "yes",
False: "no",
},
list: lambda l: ", ".join([
str(x)
for x in l
]),
tuple: lambda l: ", ".join([
str(x)
for x in l
]),
},
# Fonction de base pour imprimer quelque chose
"out": self.out,
"_out": self._out,
# Le séparateur pour la forme: variable keysep valeur
"keysep": "=",
# Le charactère de commentaire
"comment_start": "#",
# Du mapping de certaines fonctions
"include": self.include,
# Du mapping de certaines fonctions
"dump": self.dump,
# Infos standard pour le fichier (écrasable localement)
"info": {
'owner': PythonDefaults.DEFAULT_USER,
'group': PythonDefaults.DEFAULT_GROUP,
'mode': PythonDefaults.DEFAULT_ACLS,
}
})
if additionnal is None:
additionnal = {}
super(self.__class__, self).update(additionnal)
# On crée le flux dans lequel le fichier de config sera généré
self.stream = cStringIO.StringIO()
# Le Pythonfile parent est référencé ici
self.parent = parent
# Les trucs inclus
self.included = []
def __setitem__(self, variable, value):
"""Lorsqu'on définit une variable, si elle est listée dans la variable
exports, on l'incorpore dans le fichier produit"""
super(self.__class__, self).__setitem__(variable, value)
def defvar(self, variable, value):
"""Quand on fait un export, on utilise defvar pour incorporer la variable
et sa valeur dans le fichier produit"""
# On écrit mavariable = toto, en appliquant une éventuelle conversion à toto
self.out("%s%s%s" % (variable, self['keysep'], self.tostring(value)))
def out(self, string=""):
"""C'est le print local. Sauf qu'on écrit dans self.stream"""
self._out("%s\n" % (string,))
def _out(self, string=""):
"""C'est le print local sans retour à la ligne."""
self.stream.write(string)
def tostring(self, value):
"""On convertit un objet python dans un format "string" sympa.
En vrai c'est horrible et il faudrait virer ce genre de kludge."""
convertor = self["conv"].get(type(value))
if convertor:
if type(convertor) == dict:
return convertor[value]
else:
return convertor(value)
else:
return str(value)
def dump(self, incfile):
"""On exécute le fichier python dans l'environnement courant
incfile est le nom du fichier, sans le .py"""
filename = os.path.join(self.parent.parent.include, "%s.py" % (incfile,))
python_file = PythonFile.PythonFile(filename, self.parent.parent)
python_file.run(environment=self)
def include(self, incfile):
"""Pareil qu'au dessus, mais on ne le fait que si ça n'a pas
été fait"""
if incfile in self.included:
return
self.included.append(incfile)
self.dump(incfile)

View file

@ -1,33 +0,0 @@
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
"""Ce module est prévu pour héberger des factories, stockant toute
instance d'un fichier Python déjà compilé."""
class PythonFileFactory(object):
"""Cette Factory stocke l'ensemble des fichiers Python déjà instanciés.
Elle garantit entre autre leur unicité dans le fonctionnement du plugin"""
#: Stocke la liste des instances avec leur chemin absolu.
files = {}
@classmethod
def get(cls, path):
"""Récupère l'instance si elle existe, ou renvoit None"""
return cls.files.get(path, None)
@classmethod
def record(cls, path, instance):
"""Enregistre l'instance dans la Factory"""
cls.files[path] = instance
@classmethod
def flush_one(cls, path):
"""Vire une instance du dico"""
instance_to_delete = cls.files.pop(path, None)
del instance_to_delete
@classmethod
def flush(cls):
"""Vire toutes les instances du dico"""
for path in cls.files.keys():
cls.flush_one(path)

View file

@ -1,221 +0,0 @@
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
"""Fournit une couche d'abstraction Python pour les fichiers du même
nom"""
import os
import sys
import re
import marshal
import cStringIO
from Bcfg2.Server.Plugin import Debuggable
from .PythonFactories import PythonFileFactory
import PythonEnv
import PythonTools
__RE_SPECIAL_LINE = re.compile(r"^([ \t]*)(@|%)(.*)$", re.MULTILINE)
__RE_AFFECTATION = re.compile(r"([a-zA-Z_][a-zA-Z_0-9]*)[ \t]*=")
__RE_SPACE_SEP = re.compile(r"([^ \t]*)[ \t]+=?(.*)")
class PythonFile(Debuggable):
"""Classe représentant un fichier Python"""
#: Permet de savoir si l'instance a déjà été initialisée
initialized = False
def __new__(cls, path, parent=None):
"""Si le fichier a déjà été enregistré dans la Factory, on
le retourne, et on évite de réinstancier la classe.
path est le chemin absolu du fichier"""
path = os.path.normpath(path)
file_instance = PythonFileFactory.get(path)
if file_instance is None:
file_instance = super(PythonFile, cls).__new__(cls)
PythonFileFactory.record(path, file_instance)
return file_instance
def __init__(self, path, parent=None):
"""Initialisation, si non déjà faite"""
if self.initialized:
return
super(self.__class__, self).__init__()
#: A string containing the raw data in this file
self.data = None
#: Le chemin complet du fichier
self.path = os.path.normpath(path)
#: Le nom du fichier
self.name = os.path.basename(self.path)
#: Un logger
self.logger = PythonTools.LOGGER
#: Le plugin parent est pointé pour des raisons pratiques
self.parent = parent
#: C'est bon, c'est initialisé
self.initialized = True
def exists(self):
"""Teste l'existence du fichier"""
return os.path.exists(self.path)
def HandleEvent(self, event=None):
""" HandleEvent is called whenever the FAM registers an event.
:param event: The event object
:type event: Bcfg2.Server.FileMonitor.Event
:returns: None
"""
if event and event.code2str() not in ['exists', 'changed', 'created']:
return
try:
self.load()
except IOError:
err = sys.exc_info()[1]
self.logger.error("Failed to read file %s: %s" % (self.name, err))
except:
err = sys.exc_info()[1]
self.logger.error("Failed to parse file %s: %s" % (self.name, err))
def __repr__(self):
return "%s: %s" % (self.__class__.__name__, self.name)
def load(self, refresh=True):
"""Charge le fichier"""
if self.data is not None and not refresh:
return
try:
directory = os.path.dirname(self.path)
compiled_file = os.path.join(directory, ".%s.COMPILED" % (self.name,))
if os.path.exists(compiled_file) and os.stat(self.path).st_mtime <= os.stat(compiled_file).st_mtime:
self.data = marshal.load(open(compiled_file, 'r'))
else:
self.data = compileSource(open(self.path, 'r').read(), self.path, self.logger)
cfile = open(compiled_file, "w")
marshal.dump(self.data, cfile)
cfile.close()
except Exception as error:
PythonTools.log_traceback(self.path, 'compilation', error, self.logger)
def run(self, additionnal=None, environment=None):
"""Exécute le code"""
if self.data is None:
self.load(True)
if additionnal is None:
additionnal = {}
if environment is None:
environment = PythonEnv.SafeEnvironment(additionnal, self)
# Lors de l'exécution d'un fichier, on inclut
# toujours common (ie on l'exécute dans l'environnement)
environment.include("common")
try:
exec(self.data, environment)
except Exception:
sys.stderr.write('code: %r\n' % (self.data,))
raise
return environment.stream.getvalue(), environment['info']
#+---------------------------------------------+
#| Tools for compilation |
#+---------------------------------------------+
def compileSource(source, filename="", logger=None):
'''Compile un script'''
# On commence par remplacer les lignes de la forme
# @xxx par out("xxx")
newsource = cStringIO.StringIO()
start = 0
# Parsing de goret : on boucle sur les lignes spéciales,
# c'est-à-dire celles commençant par un @ ou un % précédé
# par d'éventuelles espaces/tabs.
for match in __RE_SPECIAL_LINE.finditer(source):
# On prend tout ce qui ne nous intéresse pas et on l'ajoute.
newsource.write(source[start:match.start()])
# On redéfinit start.
start = match.end()
# On écrit le premier groupe (les espaces et cie)
newsource.write(match.group(1))
# Le linetype est soit @ soit %
linetype = match.group(2)
# @ c'est du print.
if linetype == "@":
# On prend ce qui nous intéresse, et on fait quelques remplacements
# pour éviter les plantages.
line = match.group(3).replace("\\", "\\\\").replace('"', '\\"')
# Si la ligne est un commentaire, on la reproduit en remplaçant éventuellement
# le # par le bon caractère.
if line and line[0] == "#":
newsource.write('out(comment_start + "')
line = line[1:]
# Sinon bah....
else:
newsource.write('out("')
# On écrit ladite ligne
newsource.write(line)
# Et un superbe \n.
newsource.write('")')
# %, affectation.
elif linetype == "%":
# On récupère le reste.
line = match.group(3)
# On fait du matching clef/valeur
match = __RE_AFFECTATION.match(line)
if match:
# Le nom est le premier groupe.
# Et après c'est weird...
varname = match.group(1)
newsource.write(line)
newsource.write("; defvar('")
newsource.write(varname)
newsource.write("', tostring(")
newsource.write(varname)
newsource.write("))\n")
else:
# Pareil, sauf que cette fois, ce qu'on fait a un sens.
match = __RE_SPACE_SEP.match(line)
newsource.write("defvar('")
newsource.write(match.group(1))
# Le tostring est facultatif.
newsource.write("', tostring(")
newsource.write(match.group(2))
newsource.write("))\n")
# On continue.
newsource.write(source[start:])
if logger:
try:
logger.info(newsource.getvalue())
except:
print "Le logger de BCFG2 c'est de la merde, il refuse le non ascii."
print "Voici ce que j'ai essayé de logguer."
print newsource.getvalue()
return compile(newsource.getvalue(), filename, "exec")

View file

@ -1,294 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# PythonPlugin.py
# ---------
#
# Copyright © 2015 Pierre-Elliott Bécue <becue@crans.org>
"""Plugin servant à gérer des fichiers python, dont la sortie sera
la configuration d'un client."""
#: N'exporte que la classe Python
__all__ = [
"Python",
]
import os
import re
import binascii
from Bcfg2.Server.Plugin import Plugin, Generator, PluginExecutionError, track_statistics
from Bcfg2.Server.Plugin.base import Debuggable
import PythonTools
import PythonDefaults
import PythonFile
class Python(Plugin, Generator, Debuggable):
"""Générateur offrant des fonctionnalités de templating pour les fichiers python"""
name = 'Python'
#: Les DirectoryBacked ont des fonctions de monitoring
#: intégrées. Quand des changements arrivent sur les dossiers,
#: c'est la merde, il est préférable de relancer Bcfg2, car
#: FileMonitor ne sait pas démonitorer/remonitorer.
#: En revanche, pour les fichiers, il appelle __child__ comme
#: "générateur" pour les trucs à surveiller. Quand un fichier
#: est créé/modifié, sa méthode HandleEvent est appelée.
__child__ = PythonFile.PythonFile
#: Ce module gère plein de choses.
patterns = re.compile(r'.*')
#: Ignore ces chemins spécifiques
ignore = re.compile(r'.*\.(COMPILED|pyc)')
__version__ = '2.0'
__author__ = 'becue@crans.org'
def __init__(self, core, datastore):
"""Pour initialiser le plugin"""
#: Initialise un certain nombre de choses en background
Plugin.__init__(self, core, datastore)
Debuggable.__init__(self)
#: self.entries contains information about the files monitored
#: by this object. The keys of the dict are the relative
#: paths to the files. The values are the objects (of type
#: :attr:`__child__`) that handle their contents.
self.entries = {}
#: self.handles contains information about the directories
#: monitored by this object. The keys of the dict are the
#: values returned by the initial fam.AddMonitor() call (which
#: appear to be integers). The values are the relative paths of
#: the directories.
self.handles = {}
#: FileMonitor
self.fam = self.core.fam
#: Monitor everything in the plugin's directory
if not os.path.exists(self.data):
self.logger.warning("%s does not exist, creating" % (self.data,))
os.makedirs(self.data)
self.add_directory_monitor('')
#: Dossier des includes
self.include = os.path.abspath(os.path.join(self.data, PythonDefaults.INCLUDES))
#: Quand on initialise un DirectoryBacked, on a déjà un monitoring de
#: self.data, donc on a besoin que des includes
self.add_directory_monitor(PythonDefaults.INCLUDES)
@track_statistics()
def HandlesEntry(self, entry, metadata):
"""Vérifie si l'entrée est gérée par le plugin"""
relpath = entry.get('name')[1:]
if relpath in self.entries:
return True
return False
@track_statistics()
def HandleEntry(self, entry, metadata):
"""Construit le fichier demandé"""
# On récupère le code qui va bien.
relpath = entry.get('name')[1:]
python_file = self.entries[relpath]
# Et le nom de fichier.
fname = entry.get('realname', entry.get('name'))
# Si on est en débug, on loggue ce qu'on fait.
PythonTools.debug("Building config file: %s" % (fname,), PythonTools.LOGGER, 'blue')
# On crée un environnement autonome pour exécuter le fichier.
additionnal = {
'metadata': metadata,
}
try:
text, info = python_file.run(additionnal)
except Exception as error:
PythonTools.log_traceback(fname, 'exec', error, PythonTools.LOGGER)
raise PluginExecutionError
# On récupère les infos
if info.get('encoding', '') == 'base64':
text = binascii.b2a_base64(text)
# lxml n'accepte que de l'ascii ou de l'unicode
# donc faut décoder.
try:
entry.text = text.decode("UTF-8")
except:
# solution de fallback
entry.text = text.decode("ISO-8859-15")
# En cas de débug, on stocke les données
PythonTools.debug(entry.text, PythonTools.LOGGER)
# On récupère les permissions depuis le dico "info".
# En théorie, les valeurs par défaut ne devraient pas être utilisées
# Car elles sont déjà affectées dans Pygen
entry.attrib['owner'] = info.get('owner', PythonDefaults.DEFAULT_USER)
entry.attrib['group'] = info.get('group', PythonDefaults.DEFAULT_GROUP)
entry.attrib['mode'] = oct(info.get('mode', PythonDefaults.DEFAULT_ACLS))
if 'encoding' in info:
entry.attrib['encoding'] = info['encoding']
def add_directory_monitor(self, relative):
""" Add a new directory to the FAM for monitoring.
:param relative: Path name to monitor. This must be relative
to the plugin's directory. An empty string
value ("") will cause the plugin directory
itself to be monitored.
:type relative: string
:returns: None
"""
#: On normalise pour éviter des problèmes quand le FileMonitor
#: voit des changements par la suite.
#: Les chemins sont absolus pour la même raison.
dirpathname = os.path.normpath(os.path.join(self.data, relative))
if relative not in self.handles.values():
if not os.path.isdir(dirpathname):
self.logger.error("%s is not a directory" % (dirpathname,))
return
#: reqid est un chemin absolu sans trailing slash
reqid = self.fam.AddMonitor(dirpathname, self)
self.handles[reqid] = relative
def add_entry(self, relative, event):
""" Add a new file to our tracked entries, and to our FAM for
monitoring.
:param relative: Path name to monitor. This must be relative
to the plugin's directory.
:type relative: string:
:param event: FAM event that caused this entry to be added.
:type event: Bcfg2.Server.FileMonitor.Event
:returns: None
"""
#: Les entrées sont en relatif depuis le dossier de config
self.entries[relative] = self.__child__(
os.path.join(self.data, relative),
self
)
self.entries[relative].HandleEvent(event)
def HandleEvent(self, event):
""" Handle FAM events.
This method is invoked by the FAM when it detects a change to
a filesystem object we have requsted to be monitored.
This method manages the lifecycle of events related to the
monitored objects, adding them to our list of entries and
creating objects of type :attr:`__child__` that actually do
the domain-specific processing. When appropriate, it
propogates events those objects by invoking their HandleEvent
method in turn.
:param event: FAM event that caused this entry to be added.
:type event: Bcfg2.Server.FileMonitor.Event
:returns: None
"""
action = event.code2str()
# Exclude events for actions we don't care about
if action == 'endExist':
return
if event.requestID not in self.handles:
self.logger.warn(
"Got %s event with unknown handle (%s) for %s" % (action, event.requestID, event.filename)
)
return
# Clean up path names
event.filename = os.path.normpath(event.filename)
if event.filename.startswith(self.data) or os.path.normpath(event.requestID) == event.filename:
# the first event we get is on the data directory itself
event.filename = event.filename[len(os.path.normpath(event.requestID)) + 1:]
if self.ignore and self.ignore.search(event.filename):
self.logger.debug("Ignoring event %s" % (event.filename,))
return
# Calculate the absolute and relative paths this event refers to
abspath = os.path.join(self.data, self.handles[event.requestID],
event.filename)
relpath = os.path.join(self.handles[event.requestID],
event.filename).lstrip('/')
if action == 'deleted':
for key in list(self.entries.keys()):
if key.startswith(relpath):
del self.entries[key]
# We remove values from self.entries, but not
# self.handles, because the FileMonitor doesn't stop
# watching a directory just because it gets deleted. If it
# is recreated, we will start getting notifications for it
# again without having to add a new monitor.
elif os.path.isdir(abspath):
# Deal with events for directories
if action in ['exists', 'created']:
self.add_directory_monitor(relpath)
elif action == 'changed':
if relpath in self.entries:
# Ownerships, permissions or timestamps changed on
# the directory. None of these should affect the
# contents of the files, though it could change
# our ability to access them.
#
# It seems like the right thing to do is to cancel
# monitoring the directory and then begin
# monitoring it again. But the current FileMonitor
# class doesn't support canceling, so at least let
# the user know that a restart might be a good
# idea.
self.logger.warn(
"Directory properties for %s changed, please consider restarting the server" % (abspath)
)
else:
# Got a "changed" event for a directory that we
# didn't know about. Go ahead and treat it like a
# "created" event, but log a warning, because this
# is unexpected.
self.logger.warn(
"Got %s event for unexpected dir %s" % (action, abspath)
)
self.add_directory_monitor(relpath)
else:
self.logger.warn(
"Got unknown dir event %s %s %s" % (event.requestID, event.code2str(), abspath)
)
elif self.patterns.search(event.filename):
if action in ['exists', 'created']:
self.add_entry(relpath, event)
elif action == 'changed':
if relpath in self.entries:
self.entries[relpath].HandleEvent(event)
else:
# Got a "changed" event for a file that we didn't
# know about. Go ahead and treat it like a
# "created" event, but log a warning, because this
# is unexpected.
self.logger.warn(
"Got %s event for unexpected file %s" % (action, abspath)
)
self.add_entry(relpath, event)
else:
self.logger.warn(
"Got unknown file event %s %s %s" % (event.requestID, event.code2str(), abspath)
)
else:
self.logger.warn(
"Could not process filename %s; ignoring" % (event.filename)
)

View file

@ -1,73 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Fournit quelques outils pour le plugin Python"""
import os
import logging
import cStringIO
import traceback
LOGGER = logging.getLogger('Bcfg2.Plugins.Python')
COLOR_CODE = {
'grey': 30,
'red': 31,
'green': 32,
'yellow': 33,
'blue': 34,
'purple': 35,
'cyan': 36,
}
BCFG2_DEBUG = os.getenv("BCFG2_DEBUG")
BCFG2_DEBUG_COLOR = os.getenv("BCFG2_DEBUG_COLOR")
def debug(message, logger, color=None):
"""Stocke dans un logger les messages de debug"""
if not BCFG2_DEBUG:
return
if BCFG2_DEBUG_COLOR and color:
logger.info("\033[1;%dm%s\033[0m" % (COLOR_CODE[color], message))
else:
logger.info(message)
def log_traceback(fname, section, exn, logger):
"""En cas de traceback, on le loggue sans faire planter
le serveur bcfg2"""
logger.error('Python %s error: %s: %s: %s' % (section, fname, str(exn.__class__).split('.', 2)[1], str(exn)))
stream = cStringIO.StringIO()
traceback.print_exc(file=stream)
for line in stream.getvalue().splitlines():
logger.error('Python %s error: -> %s' % (section, line))
class PythonIncludePaths(object):
"""C'est un objet qui stocke les dossier d'inclusion python"""
includes = []
@classmethod
def get(cls, index, default):
"""Retourne includes[index] ou default"""
if len(cls.includes) > index:
return cls.includes[index]
return default
@classmethod
def append(cls, value):
"""Ajoute une valeur à la liste"""
cls.includes.append(value)
@classmethod
def remove(cls, value):
"""Retire une valeur à la liste"""
if value in cls.includes:
cls.includes.remove(value)
@classmethod
def pop(cls, index):
"""Vire un index si existant"""
if len(cls.includes) > index:
return cls.includes.pop(index)

View file

@ -1,6 +0,0 @@
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
"""Python plugin initializator for
Bcfg2"""
from .PythonPlugin import Python

View file

@ -29,21 +29,21 @@ device_map = {'block': stat.S_IFBLK,
'fifo': stat.S_IFIFO} 'fifo': stat.S_IFIFO}
def calcMode(initial, mode): def calcPerms(initial, perms):
"""This compares ondisk permissions with specified ones.""" """This compares ondisk permissions with specified ones."""
pdisp = [{1:stat.S_ISVTX, 2:stat.S_ISGID, 4:stat.S_ISUID}, pdisp = [{1:stat.S_ISVTX, 2:stat.S_ISGID, 4:stat.S_ISUID},
{1:stat.S_IXUSR, 2:stat.S_IWUSR, 4:stat.S_IRUSR}, {1:stat.S_IXUSR, 2:stat.S_IWUSR, 4:stat.S_IRUSR},
{1:stat.S_IXGRP, 2:stat.S_IWGRP, 4:stat.S_IRGRP}, {1:stat.S_IXGRP, 2:stat.S_IWGRP, 4:stat.S_IRGRP},
{1:stat.S_IXOTH, 2:stat.S_IWOTH, 4:stat.S_IROTH}] {1:stat.S_IXOTH, 2:stat.S_IWOTH, 4:stat.S_IROTH}]
tempmode = initial tempperms = initial
if len(mode) == 3: if len(perms) == 3:
mode = '0%s' % (mode) perms = '0%s' % (perms)
pdigits = [int(mode[digit]) for digit in range(4)] pdigits = [int(perms[digit]) for digit in range(4)]
for index in range(4): for index in range(4):
for (num, perm) in list(pdisp[index].items()): for (num, perm) in list(pdisp[index].items()):
if pdigits[index] & num: if pdigits[index] & num:
tempmode |= perm tempperms |= perm
return tempmode return tempperms
def normGid(entry): def normGid(entry):
@ -137,18 +137,18 @@ class Python(Bcfg2.Client.Tools.Tool):
entry.set('current_group', str(ondisk[stat.ST_GID])) entry.set('current_group', str(ondisk[stat.ST_GID]))
except (OSError, KeyError): except (OSError, KeyError):
pass pass
entry.set('mode', str(oct(ondisk[stat.ST_MODE])[-4:])) entry.set('perms', str(oct(ondisk[stat.ST_MODE])[-4:]))
def Verifydirectory(self, entry, modlist): def Verifydirectory(self, entry, modlist):
"""Verify Path type='directory' entry.""" """Verify Path type='directory' entry."""
if entry.get('mode') == None or \ if entry.get('perms') == None or \
entry.get('owner') == None or \ entry.get('owner') == None or \
entry.get('group') == None: entry.get('group') == None:
self.logger.error('Entry %s not completely specified. ' self.logger.error('Entry %s not completely specified. '
'Try running bcfg2-lint.' % (entry.get('name'))) 'Try running bcfg2-lint.' % (entry.get('name')))
return False return False
while len(entry.get('mode', '')) < 4: while len(entry.get('perms', '')) < 4:
entry.set('mode', '0' + entry.get('mode', '')) entry.set('perms', '0' + entry.get('perms', ''))
try: try:
ondisk = os.stat(entry.get('name')) ondisk = os.stat(entry.get('name'))
except OSError: except OSError:
@ -165,14 +165,14 @@ class Python(Bcfg2.Client.Tools.Tool):
owner = 'root' owner = 'root'
group = '0' group = '0'
finfo = os.stat(entry.get('name')) finfo = os.stat(entry.get('name'))
mode = oct(finfo[stat.ST_MODE])[-4:] perms = oct(finfo[stat.ST_MODE])[-4:]
if entry.get('mtime', '-1') != '-1': if entry.get('mtime', '-1') != '-1':
mtime = str(finfo[stat.ST_MTIME]) mtime = str(finfo[stat.ST_MTIME])
else: else:
mtime = '-1' mtime = '-1'
pTrue = ((owner == str(normUid(entry))) and pTrue = ((owner == str(normUid(entry))) and
(group == str(normGid(entry))) and (group == str(normGid(entry))) and
(mode == entry.get('mode')) and (perms == entry.get('perms')) and
(mtime == entry.get('mtime', '-1'))) (mtime == entry.get('mtime', '-1')))
pruneTrue = True pruneTrue = True
@ -217,19 +217,19 @@ class Python(Bcfg2.Client.Tools.Tool):
nqtext += "%s group is %s should be %s" % \ nqtext += "%s group is %s should be %s" % \
(entry.get('name'), group, entry.get('group')) (entry.get('name'), group, entry.get('group'))
entry.set('qtext', nqtext) entry.set('qtext', nqtext)
if mode != entry.get('mode'): if perms != entry.get('perms'):
entry.set('current_mode', mode) entry.set('current_perms', perms)
self.logger.debug("%s %s permissions are %s should be %s" % self.logger.debug("%s %s permissions are %s should be %s" %
(entry.tag, (entry.tag,
entry.get('name'), entry.get('name'),
mode, perms,
entry.get('mode'))) entry.get('perms')))
nqtext = entry.get('qtext', '') + '\n' nqtext = entry.get('qtext', '') + '\n'
nqtext += "%s %s mode are %s should be %s" % \ nqtext += "%s %s perms are %s should be %s" % \
(entry.tag, (entry.tag,
entry.get('name'), entry.get('name'),
mode, perms,
entry.get('mode')) entry.get('perms'))
entry.set('qtext', nqtext) entry.set('qtext', nqtext)
if mtime != entry.get('mtime', '-1'): if mtime != entry.get('mtime', '-1'):
entry.set('current_mtime', mtime) entry.set('current_mtime', mtime)
@ -249,7 +249,7 @@ class Python(Bcfg2.Client.Tools.Tool):
def Installdirectory(self, entry): def Installdirectory(self, entry):
"""Install Path type='directory' entry.""" """Install Path type='directory' entry."""
if entry.get('mode') == None or \ if entry.get('perms') == None or \
entry.get('owner') == None or \ entry.get('owner') == None or \
entry.get('group') == None: entry.get('group') == None:
self.logger.error('Entry %s not completely specified. ' self.logger.error('Entry %s not completely specified. '
@ -547,7 +547,7 @@ class Python(Bcfg2.Client.Tools.Tool):
err = sys.exc_info()[1] err = sys.exc_info()[1]
self.logger.error("Could not chown %s: %s" % (newfile.name, self.logger.error("Could not chown %s: %s" % (newfile.name,
err)) err))
os.chmod(newfile.name, calcMode(stat.S_IFREG, entry.get('mode'))) os.chmod(newfile.name, calcPerms(stat.S_IFREG, entry.get('perms')))
os.rename(newfile.name, entry.get('name')) os.rename(newfile.name, entry.get('name'))
if entry.get('mtime', '-1') != '-1': if entry.get('mtime', '-1') != '-1':
try: try:
@ -568,7 +568,7 @@ class Python(Bcfg2.Client.Tools.Tool):
def Verifypermissions(self, entry, _): def Verifypermissions(self, entry, _):
"""Verify Path type='permissions' entry""" """Verify Path type='permissions' entry"""
if entry.get('mode') == None or \ if entry.get('perms') == None or \
entry.get('owner') == None or \ entry.get('owner') == None or \
entry.get('group') == None: entry.get('group') == None:
self.logger.error('Entry %s not completely specified. ' self.logger.error('Entry %s not completely specified. '
@ -637,7 +637,7 @@ class Python(Bcfg2.Client.Tools.Tool):
def Installpermissions(self, entry): def Installpermissions(self, entry):
"""Install POSIX permissions""" """Install POSIX permissions"""
if entry.get('mode') == None or \ if entry.get('perms') == None or \
entry.get('owner') == None or \ entry.get('owner') == None or \
entry.get('group') == None: entry.get('group') == None:
self.logger.error('Entry %s not completely specified. ' self.logger.error('Entry %s not completely specified. '
@ -659,7 +659,7 @@ class Python(Bcfg2.Client.Tools.Tool):
try: try:
for p in plist: for p in plist:
os.chown(p, normUid(entry), normGid(entry)) os.chown(p, normUid(entry), normGid(entry))
os.chmod(p, calcMode(stat.S_IFDIR, entry.get('mode'))) os.chmod(p, calcPerms(stat.S_IFDIR, entry.get('perms')))
return True return True
except (OSError, KeyError): except (OSError, KeyError):
self.logger.error('Permission fixup failed for %s' % \ self.logger.error('Permission fixup failed for %s' % \

View file

@ -1,4 +1,4 @@
#!/bin/bash /usr/scripts/python.sh #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" Envoie un mail avec la liste des serveurs qui ne sont pas synchro avec bcfg2. """ Envoie un mail avec la liste des serveurs qui ne sont pas synchro avec bcfg2.
@ -43,6 +43,7 @@ if __name__ == "__main__":
debug = "--debug" in sys.argv debug = "--debug" in sys.argv
if "--mail" in sys.argv: if "--mail" in sys.argv:
if hosts != "": if hosts != "":
sys.path.append("/usr/scripts/")
import utils.sendmail import utils.sendmail
utils.sendmail.sendmail("root@crans.org", "roots@crans.org", u"Serveurs non synchronisés avec bcfg2", hosts, more_headers={"X-Mailer" : "bcfg2-reports"}, debug=debug) utils.sendmail.sendmail("root@crans.org", "roots@crans.org", u"Serveurs non synchronisés avec bcfg2", hosts, more_headers={"X-Mailer" : "bcfg2-reports"}, debug=debug)
elif debug: elif debug:

View file

@ -1,16 +0,0 @@
#!/bin/bash
if [[ $1 = "" ]] || [[ $1 = $USER ]] ; then
/usr/bin/quota
else
/usr/bin/quota $*
fi | sed 's/home-adh/home/' | awk -F'(:| *)' '
BEGIN { fs = "" }
/Disk/ { print; print "utilisé\tquota\tlimite\t%\t(en Mo)" }
{
if (NF == 2) { fs = $2 }
else if (fs != "") {
printf "%3.2f\t%3.2f\t%3.2f\t%3.1f\t%s\n", $2/1024, $3/1024, $4/1024, $2*100/$3, fs
fs = ""
}
}'

View file

@ -6,117 +6,38 @@
# License : GPLv3 # License : GPLv3
# Date : 27/04/2014 # Date : 27/04/2014
import os
import datetime
import pytz
import logging import logging
TZ = pytz.timezone('Europe/Paris')
LDIRPATH = os.getenv('DBG_CLOGGER_PATH', '/var/log/clogger')
class CLogger(logging.Logger): class CLogger(logging.Logger):
""" """
Crans logger. Crans logger
""" """
def __init__(self, loggerName, service=None, level="info", debug=False): def __init__(self, loggerName, service, level, debug=False):
""" """
Initializes logger. The debug variable is useful to have a print to stdout (when debugging) Initializes logger. The debug variable is useful to have a print to stdout (when debugging)
""" """
super(CLogger, self).__init__(loggerName) super(CLogger, self).__init__(loggerName)
self.c_formatter = None
self.c_file_handler = None
self.c_sh = None
self.c_level = level
# When no service is specified, we don't put the reference in the format.
if service is None:
self.c_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
else:
self.c_format = "%%(asctime)s - %%(name)s - %(service)s - %%(levelname)s - %%(message)s" % {'service': service}
self.create_formatter()
self.apply_file_handler(loggerName)
if debug:
self.apply_stream_handler()
def get_file_handler_path(self):
"""Returns the file handler path"""
if self.__file_handler_path is None:
return ''
return self.__file_handler_path
def create_formatter(self):
"""Creates a formatter based on CFormatter class.
It uses self.format as a source."""
if self.c_formatter is not None:
return
# Creates formatter
self.c_formatter = CFormatter(self.c_format, "%Y-%m-%dT%H:%M:%S.%f%z")
def apply_stream_handler(self):
"""Creates a streamhandler that prints to stdout.
Its level is debug"""
self.c_sh = logging.StreamHandler()
self.c_shlevel = logging.DEBUG
self.c_sh.setLevel(self.c_shlevel)
self.c_sh.setFormatter(self.c_formatter)
self.addHandler(self.c_sh)
def apply_file_handler(self, loggerName):
"""Creates a file handler which level is given by self.c_level"""
if self.c_file_handler is not None:
return
# Computes the file handler name using service name.
self.__file_handler_path = os.path.join(LDIRPATH, "%s.log" % (loggerName,))
# Creates FileHandler # Creates FileHandler
self.c_file_handler = logging.FileHandler(self.__file_handler_path) self.fh = logging.FileHandler("/var/log/clogger/%s.log" % (loggerName,))
# Catches appropriate level in logging. # Catches appropriate level in logging.
self.c_file_handler_level = getattr(logging, self.c_level.upper(), logging.INFO) self.fhlevel = getattr(logging, level.upper(), logging.INFO)
self.c_file_handler.setLevel(self.c_file_handler_level) self.fh.setLevel(self.fhlevel)
# Creates formatter
self.formatter = logging.Formatter('%%(asctime)s - %%(name)s - %(service)s - %%(levelname)s - %%(message)s' % {'service': service})
# Adds formatter to FileHandler # Adds formatter to FileHandler
self.c_file_handler.setFormatter(self.c_formatter) self.fh.setFormatter(self.formatter)
if debug:
self.sh = logging.StreamHandler()
self.shlevel = logging.DEBUG
self.sh.setLevel(self.shlevel)
self.sh.setFormatter(self.formatter)
self.addHandler(self.sh)
# Adds FileHandler to Handlers # Adds FileHandler to Handlers
self.addHandler(self.c_file_handler) self.addHandler(self.fh)
class CFormatter(logging.Formatter):
"""
This Formatter subclasses the classic formatter to provide a
timezone-aware logging.
"""
converter = datetime.datetime.fromtimestamp
def formatTime(self, record, datefmt=None):
"""
Return the creation time of the specified LogRecord as formatted text.
This method should be called from format() by a formatter which
wants to make use of a formatted time. This method can be overridden
in formatters to provide for any specific requirement, but the
basic behaviour is as follows: if datefmt (a string) is specified,
it is used with time.strftime() to format the creation time of the
record. Otherwise, the ISO8601 format is used. The resulting
string is returned. This function uses a user-configurable function
to convert the creation time to a tuple. By default, time.localtime()
is used; to change this for a particular formatter instance, set the
'converter' attribute to a function with the same signature as
time.localtime() or time.gmtime(). To change it for all formatters,
for example if you want all logging times to be shown in GMT,
set the 'converter' attribute in the Formatter class.
"""
ct = self.converter(record.created, TZ)
ct = ct.replace(microsecond=int(record.msecs * 1000))
if datefmt:
s = ct.strftime(datefmt)
else:
s = ct.strftime("%Y-%m-%d %H:%M:%S.%f")
return s

View file

@ -1,20 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import functools
def static_var(*couples):
"""Decorator setting static variable
to a function.
"""
# Using setattr magic, we set static
# variable on function. This avoid
# computing stuff again.
def decorate(fun):
functools.wraps(fun)
for (name, val) in couples:
setattr(fun, name, val)
return fun
return decorate

View file

@ -1,22 +1,15 @@
# ⁻*- coding: utf-8 -*- # ⁻*- coding: utf-8 -*-
""" #
Backend python pour freeradius. # Ce fichier contient la définition de plusieurs fonctions d'interface à freeradius
# qui peuvent être appelées (suivant les configurations) à certains moment de
Ce fichier contient la définition de plusieurs fonctions d'interface à # l'éxécution.
freeradius qui peuvent être appelées (suivant les configurations) à certains #
moment de l'authentification, en WiFi, filaire, ou par les NAS eux-mêmes.
Inspirés d'autres exemples trouvés ici :
https://github.com/FreeRADIUS/freeradius-server/blob/master/src/modules/rlm_python/
"""
import logging import logging
import netaddr import netaddr
import radiusd # Module magique freeradius (radiusd.py is dummy) import radiusd # Module magique freeradius (radiusd.py is dummy)
import ldap import ldap
import os import os
import binascii
import hashlib
import lc_ldap.shortcuts import lc_ldap.shortcuts
from lc_ldap.crans_utils import escape as escape_ldap from lc_ldap.crans_utils import escape as escape_ldap
@ -25,63 +18,40 @@ import lc_ldap.objets
import gestion.config.config as config import gestion.config.config as config
from gestion.gen_confs.trigger import trigger_generate_cochon as trigger_generate from gestion.gen_confs.trigger import trigger_generate_cochon as trigger_generate
import annuaires_pg import annuaires_pg
from gestion import secrets_new as secrets
#: Serveur radius de test (pas la prod)
TEST_SERVER = bool(os.getenv('DBG_FREERADIUS', False)) TEST_SERVER = bool(os.getenv('DBG_FREERADIUS', False))
#: Le taggage dynamique de vlan (dans la réponse) est désactivé sur WiFi
WIFI_DYN_VLAN = TEST_SERVER
#: Suffixe à retirer du username si présent (en wifi)
USERNAME_SUFFIX_WIFI = '.wifi.crans.org' USERNAME_SUFFIX_WIFI = '.wifi.crans.org'
#: Suffixe à retirer du username si présent (filaire)
USERNAME_SUFFIX_FIL = '.crans.org' USERNAME_SUFFIX_FIL = '.crans.org'
## -*- Logging -*- ## -*- Logging -*-
# Initialisation d'un logger pour faire des stats etc
class RadiusdHandler(logging.Handler): # pour l'instant, on centralise tout sur thot en mode debug
"""Handler de logs pour freeradius"""
def emit(self, record):
"""Process un message de log, en convertissant les niveaux"""
if record.levelno >= logging.WARN:
rad_sig = radiusd.L_ERR
elif record.levelno >= logging.INFO:
rad_sig = radiusd.L_INFO
else:
rad_sig = radiusd.L_DBG
radiusd.radlog(rad_sig, record.msg)
# Initialisation d'un logger (pour logguer unifié)
logger = logging.getLogger('auth.py') logger = logging.getLogger('auth.py')
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(name)s: [%(levelname)s] %(message)s') formatter = logging.Formatter('%(name)s: [%(levelname)s] %(message)s')
handler = RadiusdHandler() handler = logging.handlers.SysLogHandler(address = '/dev/log')
handler.setFormatter(formatter) try:
handler.addFormatter(formatter)
except AttributeError:
handler.formatter = formatter
logger.addHandler(handler) logger.addHandler(handler)
## -*- Types de blacklists -*- ## -*- Types de blacklists -*-
#: reject tout de suite #: reject tout de suite
BL_REJECT = [u'bloq'] bl_reject = [u'bloq']
#: place sur le vlan isolement #: place sur le vlan isolement
BL_ISOLEMENT = [u'virus', u'autodisc_virus', u'autodisc_p2p', u'ipv6_ra'] bl_isolement = [u'virus', u'autodisc_virus', u'autodisc_p2p', u'ipv6_ra']
# TODO carte_etudiant: dépend si sursis ou non (regarder lc_ldap)
# TODO LOGSSSSS
#: place sur accueil #: place sur accueil
BL_ACCUEIL = [u'paiement'] bl_accueil = []
# À classer: # Ces blacklists ont des effets soft (portail captif port 80)
# [u'carte_etudiant', u'chambre_invalide', ] #bl_accueil = [u'carte_etudiant', u'chambre_invalide', u'paiement']
# TODO: mettre ça dans config.py en explicitant un peu comment ça marche
# et en trouvant moyen de refresh en fonction de la période de l'année
# (bl soft/hard parefeu ou pas)
#: chambre qui n'en sont pas vraiment. Il s'agit de prises en libre accès,
# pour lequelles il est donc idiot d'activer la protection antisquattage:
# personne n'y habite ! ( G091 -> G097: salle d'étude du rdc du G)
PUBLIC_CHBRE = ['G091', 'G092', 'G093', 'G094', 'G095', 'G096', 'G097']
## -*- Decorateurs -*- ## -*- Decorateurs -*-
# À appliquer sur les fonctions qui ont besoin d'une conn ldap # À appliquer sur les fonctions qui ont besoin d'une conn ldap
@ -90,7 +60,7 @@ use_ldap_admin = lc_ldap.shortcuts.with_ldap_conn(retries=2, delay=5,
use_ldap = lc_ldap.shortcuts.with_ldap_conn(retries=2, delay=5, use_ldap = lc_ldap.shortcuts.with_ldap_conn(retries=2, delay=5,
constructor=lc_ldap.shortcuts.lc_ldap_anonymous) constructor=lc_ldap.shortcuts.lc_ldap_anonymous)
def radius_event(fun): def radius_event(f):
"""Décorateur pour les fonctions d'interfaces avec radius. """Décorateur pour les fonctions d'interfaces avec radius.
Une telle fonction prend un uniquement argument, qui est une liste de tuples Une telle fonction prend un uniquement argument, qui est une liste de tuples
(clé, valeur) et renvoie un triplet dont les composantes sont : (clé, valeur) et renvoie un triplet dont les composantes sont :
@ -99,23 +69,22 @@ def radius_event(fun):
et autres trucs du genre) et autres trucs du genre)
* un tuple de couples (clé, valeur) pour les valeurs internes à mettre à * un tuple de couples (clé, valeur) pour les valeurs internes à mettre à
jour (mot de passe par exemple) jour (mot de passe par exemple)
Voir des exemples plus complets ici:
https://github.com/FreeRADIUS/freeradius-server/blob/master/src/modules/rlm_python/
On se contente avec ce décorateur (pour l'instant) de convertir la liste de On se contente avec ce décorateur (pour l'instant) de convertir la liste de
tuples en entrée en un dictionnaire.""" tuples en entrée en un dictionnaire."""
def new_f(auth_data): def new_f(auth_data):
if type(auth_data) == dict:
data = auth_data
else:
data = dict() data = dict()
for (key, value) in auth_data or []: for (key, value) in auth_data or []:
# Beware: les valeurs scalaires sont entre guillemets # Beware: les valeurs scalaires sont entre guillemets
# Ex: Calling-Station-Id: "une_adresse_mac" # Ex: Calling-Station-Id: "une_adresse_mac"
data[key] = value.replace('"', '') data[key] = value.replace('"', '')
try: try:
return fun(data) return f(data)
except Exception as err: except Exception as e:
logger.error('Failed %r on data %r' % (err, auth_data)) logger.error(repr(e) + ' on data ' + repr(auth_data))
raise raise
return new_f return new_f
@ -135,21 +104,18 @@ def get_machines(data, conn, is_wifi=True, proprio=None):
try: try:
mac = lc_ldap.crans_utils.format_mac(mac.decode('ascii', 'ignore')) mac = lc_ldap.crans_utils.format_mac(mac.decode('ascii', 'ignore'))
except: except:
logger.error('Cannot format MAC !') radiusd.radlog(radiusd.L_ERR, 'Cannot format MAC !')
mac = None mac = None
username = data.get('User-Name', None) username = data.get('User-Name', None)
if username: if username:
# Pour les requètes venant de federezwifi
username = username.split('@', 1)[0]
username = escape_ldap(username.decode('ascii', 'ignore')) username = escape_ldap(username.decode('ascii', 'ignore'))
if username.endswith(suffix): if username.endswith(suffix):
username = username[:-len(suffix)] username = username[:-len(suffix)]
if mac is None: if mac is None:
logger.error('Cannot read mac from AP') radiusd.radlog(radiusd.L_ERR, 'Cannot read client MAC from AP !')
if username is None: if username is None:
logger.error('Cannot read client User-Name !') radiusd.radlog(radiusd.L_ERR, 'Cannot read client User-Name !')
# Liste de recherches ldap à essayer, dans l'ordre # Liste de recherches ldap à essayer, dans l'ordre
# ** Case 1: Search by mac # ** Case 1: Search by mac
@ -172,9 +138,6 @@ def get_machines(data, conn, is_wifi=True, proprio=None):
res = conn.search(u'(&%s(macAddress=<automatique>)(host=%s%s))' % res = conn.search(u'(&%s(macAddress=<automatique>)(host=%s%s))' %
(base, username, suffix), **opt) (base, username, suffix), **opt)
if TEST_SERVER:
res += conn.search(u'(&%s(host=%s%s))' %
(base, username, suffix), **opt)
return res return res
def get_prise_chbre(data): def get_prise_chbre(data):
@ -201,7 +164,7 @@ def get_prise_chbre(data):
try: try:
bat_name = nas[3].upper() bat_name = nas[3].upper()
bat_num = int(nas.split('-', 1)[1]) bat_num = int(nas.split('-', 1)[1])
except (IndexError, ValueError): except IndexError, ValueError:
pass pass
port = data.get('NAS-Port', None) port = data.get('NAS-Port', None)
if port: if port:
@ -219,7 +182,7 @@ def get_prise_chbre(data):
def realm_of_machine(machine): def realm_of_machine(machine):
"""Renvoie le `realm` d'une machine. Don't ask""" """Renvoie le `realm` d'une machine. Don't ask"""
if isinstance(machine, lc_ldap.objets.machineFixe): if isinstance(machine, lc_ldap.objets.machineFixe):
return 'adherents' return 'fil'
elif isinstance(machine, lc_ldap.objets.machineWifi): elif isinstance(machine, lc_ldap.objets.machineWifi):
return 'wifi-adh' return 'wifi-adh'
else: else:
@ -227,29 +190,29 @@ def realm_of_machine(machine):
def get_fresh_rid(machine): def get_fresh_rid(machine):
"""Génère un rid tout frais pour la machine. Fonction kludge""" """Génère un rid tout frais pour la machine. Fonction kludge"""
lock_id = machine.conn.lockholder.newid() lockId = machine.conn.lockholder.newid()
realm = realm_of_machine(machine) realm = realm_of_machine(machine)
try: try:
return machine.conn._find_id('rid', realm, lock_id) return machine.conn._find_id('rid', realm, lockId)
finally: finally:
machine.conn.lockholder.purge(lock_id) machine.conn.lockholder.purge(lockId)
@use_ldap_admin @use_ldap_admin
def register_machine(data, machine, conn): def register_mac(data, machine, conn):
"""Enregistre la mac actuelle et/ou assigne le rid sur une machine donnée.""" """Enregistre la mac actuelle sur une machine donnée."""
# TODO lc_ldap devrait posséder une fonction pour passer en rw depuis un ro # TODO lc_ldap devrait posséder une fonction pour passer en rw depuis un ro
if 'w' not in machine.mode: if 'w' not in machine.mode:
machine = conn.search(dn=machine.dn, scope=ldap.SCOPE_BASE, mode='rw')[0] machine = conn.search(dn=machine.dn, scope=ldap.SCOPE_BASE, mode='rw')[0]
mac = data.get('Calling-Station-Id', None) mac = data.get('Calling-Station-Id', None)
if mac is None: if mac is None:
logger.warn('Cannot find MAC for registration (aborting)') radiusd.radlog(radiusd.L_ERR, 'Cannot find MAC')
return return
mac = mac.decode('ascii', 'ignore').replace('"','') mac = mac.decode('ascii', 'ignore').replace('"','')
try: try:
mac = lc_ldap.crans_utils.format_mac(mac).lower() mac = lc_ldap.crans_utils.format_mac(mac).lower()
except Exception: except:
logger.warn('Cannot format MAC for registration (aborting)') radiusd.radlog(radiusd.L_ERR, 'Cannot format MAC !')
return return
with machine: with machine:
@ -270,25 +233,13 @@ def register_machine(data, machine, conn):
@radius_event @radius_event
@use_ldap_admin @use_ldap_admin
@use_ldap @use_ldap
def instantiate(*_): def instantiate(p, *conns):
"""Utile pour initialiser les connexions ldap une première fois (otherwise, """Utile pour initialiser les connexions ldap une première fois (otherwise,
do nothing)""" do nothing)"""
logger.info('Instantiation') logger.info('Instantiation')
if TEST_SERVER: if TEST_SERVER:
logger.info('DBG_FREERADIUS is enabled') logger.info('DBG_FREERADIUS is enabled')
@radius_event
def authorize(data):
"""Fonction qui aiguille entre nas, wifi et filaire pour authorize
On se contecte de faire une verification basique de ce que contien la requète
pour déterminer la fonction à utiliser"""
if data.get('NAS-Port-Type', '')==u'Ethernet':
return authorize_fil(data)
elif u"Wireless" in data.get('NAS-Port-Type', ''):
return authorize_wifi(data)
else:
return authorize_nas(data)
@radius_event @radius_event
def authorize_wifi(data): def authorize_wifi(data):
"""Section authorize pour le wifi """Section authorize pour le wifi
@ -300,29 +251,26 @@ def authorize_wifi(data):
items = get_machines(data) items = get_machines(data)
if not items: if not items:
logger.error('No machine found in lc_ldap') radiusd.radlog(radiusd.L_ERR, 'lc_ldap: Nobody found')
return radiusd.RLM_MODULE_NOTFOUND return radiusd.RLM_MODULE_NOTFOUND
if len(items) > 1: if len(items) > 1:
logger.warn('lc_ldap: Too many results (taking first)') radiusd.radlog(radiusd.L_ERR, 'lc_ldap: Too many results (took first)')
machine = items[0] machine = items[0]
proprio = machine.proprio() proprio = machine.proprio()
if isinstance(proprio, lc_ldap.objets.AssociationCrans): if isinstance(proprio, lc_ldap.objets.AssociationCrans):
logger.error('Crans machine trying to authenticate !') radiusd.radlog(radiusd.L_ERR, 'Crans machine trying to authenticate !')
return radiusd.RLM_MODULE_INVALID return radiusd.RLM_MODULE_INVALID
for bl in machine.blacklist_actif(): for bl in machine.blacklist_actif():
if bl.value['type'] in BL_REJECT: if bl.value['type'] in bl_reject:
return radiusd.RLM_MODULE_REJECT return radiusd.RLM_MODULE_REJECT
# Kludge : vlan isolement pas possible, donc reject quand-même
if not WIFI_DYN_VLAN and bl.value['type'] in BL_ISOLEMENT:
return radiusd.RLM_MODULE_REJECT
if not machine.get('ipsec', False): if not machine.get('ipsec', False):
logger.error('WiFi auth but machine has no password') radiusd.radlog(radiusd.L_ERR, 'WiFi authentication but machine has no' +
'password')
return radiusd.RLM_MODULE_REJECT return radiusd.RLM_MODULE_REJECT
password = machine['ipsec'][0].value.encode('ascii', 'ignore') password = machine['ipsec'][0].value.encode('ascii', 'ignore')
@ -337,124 +285,17 @@ def authorize_wifi(data):
@radius_event @radius_event
def authorize_fil(data): def authorize_fil(data):
"""For now, do nothing.
TODO: check bl_reject.
TODO: check chap auth
""" """
Check le challenge chap, et accepte.
TODO: check BL_REJECT.
"""
chap_ok = False
# Teste l'authentification chap fournie
# password et challenge doivent être données
# en hexa (avec ou sans le 0x devant)
# le User-Name est en réalité la mac ( xx:xx:xx:xx:xx )
password = data.get('CHAP-Password', '')
challenge = data.get('CHAP-Challenge', '')
mac = data.get('User-Name', '')
logger.debug('(fil) authorize(%r)' % ((password, challenge, mac),))
try:
challenge = binascii.a2b_hex(challenge.replace('0x',''))
password = binascii.a2b_hex(password.replace('0x',''))
if hashlib.md5(password[0] + mac + challenge).digest() == password[1:]:
logger.info("(fil) Chap ok")
chap_ok = True
else:
logger.info("(fil) Chap wrong")
except Exception as err:
logger.info("(fil) Chap challenge check failed with %r" % err)
if not chap_ok:
if TEST_SERVER:
logger.debug('(fil) Continue auth (debug)')
else:
return radiusd.RLM_MODULE_REJECT
return (radiusd.RLM_MODULE_UPDATED, return (radiusd.RLM_MODULE_UPDATED,
(), (),
( (
("Auth-Type", "Accept"), ("Auth-Type", "crans_fil"),
), ),
) )
def radius_password(secret_name, machine=None):
"""Cherche le mdp radius pour la machine donnée, et fallback sur le
secret canonique nommé"""
if machine and machine.has_key('TODO'):
pass
return secrets.get(secret_name)
@radius_event
@use_ldap
def authorize_nas(data, ldap):
"""Remplis le mdp d'une borne, ou d'un switch"""
logger.info('nas_auth with %r' % data)
ip = data.get('NAS-Identifier', '')
is_v6 = ':' in ip
ip_stm = ("FreeRADIUS-Client-IP%s-Address" % ('v6'*is_v6, ), ip)
# Find machine
# On rajoute les Machines du club federez au base_filter (federez-wifi):
fed = ldap.search(u'(nom=Federez)')[0]
mach_fed = fed.machines()
base_filter = u'(|(objectClass=machineCrans)(objectClass=borneWifi)'
for mach in mach_fed:
base_filter = base_filter + "(mid=%s)" % mach['mid'][0]
base_filter = base_filter + u')'
if is_v6:
addr = netaddr.IPAddress(ip).value
# EUI64, hein ?
assert ((addr >> 24) & 0xffff) == 0xfffe
# Extrait la mac de l'EUI64 (« trust me, it works »)
mac = (addr >> 16) & (0xffffff << 24) ^ (addr & 0xffffff) ^ (1 << 41)
mac = lc_ldap.crans_utils.format_mac("%012x" % mac)
m_filter = u'(macAddress=%s)' % mac
else:
m_filter = u'(ipHostNumber=%s)' % escape_ldap(ip)
machines = ldap.search(u'(&%s%s)' % (base_filter, m_filter))
if not machines:
if TEST_SERVER or ip == '127.0.0.1':
password = radius_password('radius_eap_key')
shortname = "wifi"
vserver = 'inner-tunnel'
else:
logger.info('not found %r' % m_filter)
return radiusd.RLM_MODULE_NOTFOUND
elif unicode(machines[0]['host'][0]).startswith(u'bat'):
password = radius_password('radius_key', machines[0])
shortname = 'switchs'
vserver = 'filaire'
else:
password = radius_password('radius_eap_key', machines[0])
shortname = "wifi"
vserver = 'wifi'
return (radiusd.RLM_MODULE_OK,
(),
(
ip_stm,
("FreeRADIUS-Client-Require-MA", "no"),
("FreeRADIUS-Client-Secret", password),
("FreeRADIUS-Client-Shortname", shortname),
("FreeRADIUS-Client-NAS-Type", "other"),
# On teste avec une équipe qui marche
("FreeRADIUS-Client-Virtual-Server", vserver),
),
)
@radius_event
def post_auth(data):
# On cherche quel est le type de machine, et quel sites lui appliquer
if data.get('NAS-Port-Type', '')==u'Ethernet':
return post_auth_fil(data)
elif u"Wireless" in data.get('NAS-Port-Type', ''):
return post_auth_wifi(data)
@radius_event @radius_event
def post_auth_wifi(data): def post_auth_wifi(data):
"""Appelé une fois que l'authentification est ok. """Appelé une fois que l'authentification est ok.
@ -467,13 +308,14 @@ def post_auth_wifi(data):
log_message = '(wifi) %s -> %s [%s%s]' % \ log_message = '(wifi) %s -> %s [%s%s]' % \
(port, mac, vlan_name, (reason and u': ' + reason).encode('utf-8')) (port, mac, vlan_name, (reason and u': ' + reason).encode('utf-8'))
logger.info(log_message) logger.info(log_message)
radiusd.radlog(radiusd.L_AUTH, log_message)
# Si NAS ayant des mapping particuliers, à signaler ici # Si NAS ayant des mapping particuliers, à signaler ici
vlan_id = config.vlans[vlan_name] vlan_id = config.vlans[vlan_name]
# WiFi : Pour l'instant, on ne met pas d'infos de vlans dans la réponse # WiFi : Pour l'instant, on ne met pas d'infos de vlans dans la réponse
# les bornes wifi ont du mal avec cela # les bornes wifi ont du mal avec cela
if WIFI_DYN_VLAN: if TEST_SERVER:
return (radiusd.RLM_MODULE_UPDATED, return (radiusd.RLM_MODULE_UPDATED,
( (
("Tunnel-Type", "VLAN"), ("Tunnel-Type", "VLAN"),
@ -496,6 +338,7 @@ def post_auth_fil(data):
log_message = '(fil) %s -> %s [%s%s]' % \ log_message = '(fil) %s -> %s [%s%s]' % \
(port, mac, vlan_name, (reason and u': ' + reason).encode('utf-8')) (port, mac, vlan_name, (reason and u': ' + reason).encode('utf-8'))
logger.info(log_message) logger.info(log_message)
radiusd.radlog(radiusd.L_AUTH, log_message)
# Si NAS ayant des mapping particuliers, à signaler ici # Si NAS ayant des mapping particuliers, à signaler ici
vlan_id = config.vlans[vlan_name] vlan_id = config.vlans[vlan_name]
@ -546,8 +389,8 @@ def decide_vlan(data, is_wifi, conn):
proprio = machine.proprio() proprio = machine.proprio()
# Avant de continuer, on assigne la mac à la machine candidat # Avant de continuer, on assigne la mac à la machine candidat
if '<automatique>' in machine['macAddress'] or not machine['rid']: if '<automatique>' in machine['macAddress']:
register_machine(data, machine) register_mac(data, machine)
if not machine['ipHostNumber']: if not machine['ipHostNumber']:
decision = 'v6only', u'No IPv4' decision = 'v6only', u'No IPv4'
@ -560,9 +403,9 @@ def decide_vlan(data, is_wifi, conn):
# Application des blacklists # Application des blacklists
for bl in machine.blacklist_actif(): for bl in machine.blacklist_actif():
if bl.value['type'] in BL_ISOLEMENT: if bl.value['type'] in bl_isolement:
decision = 'isolement', unicode(bl) decision = 'isolement', unicode(bl)
if bl.value['type'] in BL_ACCUEIL: if bl.value['type'] in bl_accueil:
decision = 'accueil', unicode(bl) decision = 'accueil', unicode(bl)
# Filaire : protection anti-"squattage" # Filaire : protection anti-"squattage"
@ -580,8 +423,6 @@ def decide_vlan(data, is_wifi, conn):
# Pour les locaux clubs, il n'y a pas forcément un club sédentaire # Pour les locaux clubs, il n'y a pas forcément un club sédentaire
# (typiquement, les locaux sous digicode) # (typiquement, les locaux sous digicode)
decision = decision[0], decision[1] + u' (local club)' decision = decision[0], decision[1] + u' (local club)'
elif chbre in PUBLIC_CHBRE:
decision = decision[0], decision[1] + u' (lieu de vie)'
else: else:
for hebergeur in hebergeurs: for hebergeur in hebergeurs:
# Si on est hébergé par un adhérent ok, ou que c'est notre # Si on est hébergé par un adhérent ok, ou que c'est notre
@ -614,12 +455,28 @@ def decide_vlan(data, is_wifi, conn):
return (port,) + decision return (port,) + decision
@radius_event @radius_event
def dummy_fun(_): def dummy_fun(p):
"""Do nothing, successfully. (C'est pour avoir un truc à mettre)"""
return radiusd.RLM_MODULE_OK return radiusd.RLM_MODULE_OK
def detach(_=None): def detach(p=None):
"""Appelé lors du déchargement du module (enfin, normalement)""" """Appelé lors du déchargement du module (enfin, normalement)"""
print "*** goodbye from auth.py ***" print "*** goodbye from auth.py ***"
return radiusd.RLM_MODULE_OK return radiusd.RLM_MODULE_OK
# à réimplémenter dans le authorize
# chap_ok(os.getenv('CHAP_PASSWORD'), os.getenv('CHAP_CHALLENGE'), mac)
def chap_ok(password, challenge, clear_pass) :
""" Test l'authentification chap fournie
password et chalenge doivent être données
en hexa (avec ou sans le 0x devant)
retourne True si l'authentification est OK
retourne False sinon
"""
try :
challenge = binascii.a2b_hex(challenge.replace('0x',''))
password = binascii.a2b_hex(password.replace('0x',''))
if hashlib.md5(password[0] + clear_pass + challenge).digest() == password[1:] :
return True
except :
return False

View file

@ -1,31 +0,0 @@
# Define a network where clients may be dynamically defined.
client dynamic {
#
# You MUST specify a netmask!
# IPv4 /32 or IPv6 /128 are NOT allowed!
ipv6addr = 0::
netmask = 0
#
# Define the virtual server used to discover dynamic clients.
dynamic_clients = dynamic_clients
#
# Define the lifetime (in seconds) for dynamic clients.
# They will be cached for this lifetime, and deleted afterwards.
#
# If the lifetime is "0", then the dynamic client is never
# deleted. The only way to delete the client is to re-start
# the server.
lifetime = 3600
}
# Le même, en ipv4
client dynamic {
ipaddr = 0.0.0.0
netmask = 0
dynamic_clients = dynamic_clients
lifetime = 3600
}

View file

@ -1 +0,0 @@
../rlm_python_unifie.conf

View file

@ -2,10 +2,6 @@
# #
# Definitions for RADIUS programs # Definitions for RADIUS programs
# #
# This file should *NOT* be available in production mode : importing this dummy
# module in place of the radiusd module exposed by freeradius avoid logging
# function radlog to work.
#
# Copyright 2002 Miguel A.L. Paraz <mparaz@mparaz.com> # Copyright 2002 Miguel A.L. Paraz <mparaz@mparaz.com>
# #
# This should only be used when testing modules. # This should only be used when testing modules.

View file

@ -0,0 +1,37 @@
# Configuration for the Python module.
#
#
python crans_fil {
mod_instantiate = freeradius.auth
func_instantiate = instantiate
# Spécifique au filaire: accepte direct
mod_authorize = freeradius.auth
func_authorize = authorize_fil
# Renseigne le vlan
# remplacer par dummy_fun pour ignorer le tagging de vlan
mod_post_auth = freeradius.auth
func_post_auth = post_auth_fil
# Que faire avant de quitter
mod_detach = freeradius.auth
func_detach = detach
# Le reste est dumb et inutile
mod_accounting = freeradius.auth
func_accounting = dummy_fun
mod_pre_proxy = freeradius.auth
func_pre_proxy = dummy_fun
mod_post_proxy = freeradius.auth
func_post_proxy = dummy_fun
mod_recv_coa = freeradius.auth
func_recv_coa = dummy_fun
mod_send_coa = freeradius.auth
func_send_coa = dummy_fun
}

View file

@ -1,38 +0,0 @@
# Configuration for the Python module.
#
#
python crans_unifie {
mod_instantiate = freeradius.auth
func_instantiate = instantiate
# Pour le authorize, c'est auth.py qui fait le tri maintenant
mod_authorize = freeradius.auth
func_authorize = authorize
# Renseigne le vlan si necessaire
# remplacer par dummy_fun pour ignorer le tagging de vlan
mod_post_auth = freeradius.auth
func_post_auth = post_auth
# Que faire avant de quitter
mod_detach = freeradius.auth
func_detach = detach
# Le reste sert à rien
mod_accounting = freeradius.auth
func_accounting = dummy_fun
mod_pre_proxy = freeradius.auth
func_pre_proxy = dummy_fun
mod_post_proxy = freeradius.auth
func_post_proxy = dummy_fun
mod_recv_coa = freeradius.auth
func_recv_coa = dummy_fun
mod_send_coa = freeradius.auth
func_send_coa = dummy_fun
}

View file

@ -0,0 +1,37 @@
# Configuration for the Python module.
#
#
python crans_wifi {
mod_instantiate = freeradius.auth
func_instantiate = instantiate
# Spécifique au WiFi : rempli le mdp
mod_authorize = freeradius.auth
func_authorize = authorize_wifi
# Renseigne le vlan
# remplacer par dummy_fun pour ignorer le tagging de vlan
mod_post_auth = freeradius.auth
func_post_auth = post_auth_wifi
# Que faire avant de quitter
mod_detach = freeradius.auth
func_detach = detach
# Le reste est dumb et inutile
mod_accounting = freeradius.auth
func_accounting = dummy_fun
mod_pre_proxy = freeradius.auth
func_pre_proxy = dummy_fun
mod_post_proxy = freeradius.auth
func_post_proxy = dummy_fun
mod_recv_coa = freeradius.auth
func_recv_coa = dummy_fun
mod_send_coa = freeradius.auth
func_send_coa = dummy_fun
}

View file

@ -1,17 +0,0 @@
#
# This is the virtual server referenced above by "dynamic_clients".
server dynamic_clients {
#
# The only contents of the virtual server is the "authorize" section.
authorize {
# Hack dégueux: crans_nas est un backend python. Or, rlm_python ne
# fournit pas en entrée les variables "control", uniquement les variables
# "request", du coup on met ce qui nous intéresse là.
update request {
NAS-Identifier = "%{Packet-Src-IP-Address:-%{Packet-Src-IPv6-Address}}"
}
crans_unifie
}
}

View file

@ -1,20 +0,0 @@
######################################################################
#
# Authentification filaire du crans
#
######################################################################
server filaire {
authorize{
preprocess
crans_unifie
}
authenticate{
crans_unifie
}
post-auth{
crans_unifie
}
}

View file

@ -1,397 +0,0 @@
# -*- text -*-
######################################################################
#
# This is a virtual server that handles *only* inner tunnel
# requests for EAP-TTLS and PEAP types.
#
# $Id: inner-tunnel,v 1.6 2008/03/29 21:33:12 aland Exp $
#
######################################################################
server inner-tunnel {
# Authorization. First preprocess (hints and huntgroups files),
# then realms, and finally look in the "users" file.
#
# The order of the realm modules will determine the order that
# we try to find a matching realm.
#
# Make *sure* that 'preprocess' comes before any realm if you
# need to setup hints for the remote radius server
authorize {
#preprocess
crans_unifie
#
# The chap module will set 'Auth-Type := CHAP' if we are
# handling a CHAP request and Auth-Type has not already been set
#chap
#
# Pull crypt'd passwords from /etc/passwd or /etc/shadow,
# using the system API's to get the password. If you want
# to read /etc/passwd or /etc/shadow directly, see the
# passwd module, above.
#
#unix
#
# Look for IPASS style 'realm/', and if not found, look for
# '@realm', and decide whether or not to proxy, based on
# that.
# IPASS
#
# If you are using multiple kinds of realms, you probably
# want to set "ignore_null = yes" for all of them.
# Otherwise, when the first style of realm doesn't match,
# the other styles won't be checked.
#
# Note that proxying the inner tunnel authentication means
# that the user MAY use one identity in the outer session
# (e.g. "anonymous", and a different one here
# (e.g. "user@example.com"). The inner session will then be
# proxied elsewhere for authentication. If you are not
# careful, this means that the user can cause you to forward
# the authentication to another RADIUS server, and have the
# accounting logs *not* sent to the other server. This makes
# it difficult to bill people for their network activity.
#
#suffix
# ntdomain
#
# The "suffix" module takes care of stripping the domain
# (e.g. "@example.com") from the User-Name attribute, and the
# next few lines ensure that the request is not proxied.
#
# If you want the inner tunnel request to be proxied, delete
# the next few lines.
#
#update control {
# Proxy-To-Realm := LOCAL
#}
#
# This module takes care of EAP-MSCHAPv2 authentication.
#
# It also sets the EAP-Type attribute in the request
# attribute list to the EAP type from the packet.
#
# The example below uses module failover to avoid querying all
# of the following modules if the EAP module returns "ok".
# Therefore, your LDAP and/or SQL servers will not be queried
# for the many packets that go back and forth to set up TTLS
# or PEAP. The load on those servers will therefore be reduced.
#
eap {
ok = return
}
#
# Read the 'users' file
# files
#
# Look in an SQL database. The schema of the database
# is meant to mirror the "users" file.
#
# See "Authorization Queries" in sql.conf
# sql
#
# If you are using /etc/smbpasswd, and are also doing
# mschap authentication, the un-comment this line, and
# configure the 'etc_smbpasswd' module, above.
# etc_smbpasswd
#
# The ldap module will set Auth-Type to LDAP if it has not
# already been set
#ldap
#
# If the users are logging in with an MS-CHAP-Challenge
# attribute for authentication, the mschap module will find
# the MS-CHAP-Challenge attribute, and add 'Auth-Type := MS-CHAP'
# to the request, which will cause the server to then use
# the mschap module for authentication.
mschap
#
# Enforce daily limits on time spent logged in.
# daily
#
# Use the checkval module
# checkval
#expiration
#logintime
#
# If no other module has claimed responsibility for
# authentication, then try to use PAP. This allows the
# other modules listed above to add a "known good" password
# to the request, and to do nothing else. The PAP module
# will then see that password, and use it to do PAP
# authentication.
#
# This module should be listed last, so that the other modules
# get a chance to set Auth-Type for themselves.
#
#pap
}
# Authentication.
#
#
# This section lists which modules are available for authentication.
# Note that it does NOT mean 'try each module in order'. It means
# that a module from the 'authorize' section adds a configuration
# attribute 'Auth-Type := FOO'. That authentication type is then
# used to pick the apropriate module from the list below.
#
# In general, you SHOULD NOT set the Auth-Type attribute. The server
# will figure it out on its own, and will do the right thing. The
# most common side effect of erroneously setting the Auth-Type
# attribute is that one authentication method will work, but the
# others will not.
#
# The common reasons to set the Auth-Type attribute by hand
# is to either forcibly reject the user, or forcibly accept him.
#
authenticate {
#
# PAP authentication, when a back-end database listed
# in the 'authorize' section supplies a password. The
# password can be clear-text, or encrypted.
#Auth-Type PAP {
# pap
#}
#
# Most people want CHAP authentication
# A back-end database listed in the 'authorize' section
# MUST supply a CLEAR TEXT password. Encrypted passwords
# won't work.
Auth-Type CHAP {
chap
}
#
# MSCHAP authentication.
Auth-Type MS-CHAP {
mschap
}
#
# Pluggable Authentication Modules.
# pam
#
# See 'man getpwent' for information on how the 'unix'
# module checks the users password. Note that packets
# containing CHAP-Password attributes CANNOT be authenticated
# against /etc/passwd! See the FAQ for details.
#
# unix
# Uncomment it if you want to use ldap for authentication
#
# Note that this means "check plain-text password against
# the ldap database", which means that EAP won't work,
# as it does not supply a plain-text password.
#Auth-Type LDAP {
# ldap
#}
#
# Allow EAP authentication.
eap
}
######################################################################
#
# There are no accounting requests inside of EAP-TTLS or PEAP
# tunnels.
#
######################################################################
# Session database, used for checking Simultaneous-Use. Either the radutmp
# or rlm_sql module can handle this.
# The rlm_sql module is *much* faster
session {
# radutmp
#
# See "Simultaneous Use Checking Queries" in sql.conf
# sql
}
# Post-Authentication
# Once we KNOW that the user has been authenticated, there are
# additional steps we can take.
post-auth {
crans_unifie
# Note that we do NOT assign IP addresses here.
# If you try to assign IP addresses for EAP authentication types,
# it WILL NOT WORK. You MUST use DHCP.
#
# If you want to have a log of authentication replies,
# un-comment the following line, and the 'detail reply_log'
# section, above.
# reply_log
#
# After authenticating the user, do another SQL query.
#
# See "Authentication Logging Queries" in sql.conf
# sql
#
# Instead of sending the query to the SQL server,
# write it into a log file.
#
# sql_log
#
# Un-comment the following if you have set
# 'edir_account_policy_check = yes' in the ldap module sub-section of
# the 'modules' section.
#
# ldap
#
# Access-Reject packets are sent through the REJECT sub-section of the
# post-auth section.
#
# Add the ldap module name (or instance) if you have set
# 'edir_account_policy_check = yes' in the ldap module configuration
#
Post-Auth-Type REJECT {
# attr_filter.access_reject
}
#
# The example policy below updates the outer tunnel reply
# (usually Access-Accept) with the User-Name from the inner
# tunnel User-Name. Since this section is processed in the
# context of the inner tunnel, "request" here means "inner
# tunnel request", and "outer.reply" means "outer tunnel
# reply attributes".
#
# This example is most useful when the outer session contains
# a User-Name of "anonymous@....", or a MAC address. If it
# is enabled, the NAS SHOULD use the inner tunnel User-Name
# in subsequent accounting packets. This makes it easier to
# track user sessions, as they will all be based on the real
# name, and not on "anonymous".
#
# The problem with doing this is that it ALSO exposes the
# real user name to any intermediate proxies. People use
# "anonymous" identifiers outside of the tunnel for a very
# good reason: it gives them more privacy. Setting the reply
# to contain the real user name removes ALL privacy from
# their session.
#
# If you want privacy to remain, see the
# Chargeable-User-Identity attribute from RFC 4372. In order
# to use that attribute, you will have to allocate a
# per-session identifier for the user, and store it in a
# long-term database (e.g. SQL). You should also use that
# attribute INSTEAD of the configuration below.
#
#update outer.reply {
# User-Name = "%{request:User-Name}"
#}
}
#
# When the server decides to proxy a request to a home server,
# the proxied request is first passed through the pre-proxy
# stage. This stage can re-write the request, or decide to
# cancel the proxy.
#
# Only a few modules currently have this method.
#
pre-proxy {
# attr_rewrite
# Uncomment the following line if you want to change attributes
# as defined in the preproxy_users file.
# files
# Uncomment the following line if you want to filter requests
# sent to remote servers based on the rules defined in the
# 'attrs.pre-proxy' file.
# attr_filter.pre-proxy
# If you want to have a log of packets proxied to a home
# server, un-comment the following line, and the
# 'detail pre_proxy_log' section, above.
# pre_proxy_log
}
#
# When the server receives a reply to a request it proxied
# to a home server, the request may be massaged here, in the
# post-proxy stage.
#
post-proxy {
# If you want to have a log of replies from a home server,
# un-comment the following line, and the 'detail post_proxy_log'
# section, above.
# post_proxy_log
# attr_rewrite
# Uncomment the following line if you want to filter replies from
# remote proxies based on the rules defined in the 'attrs' file.
# attr_filter.post-proxy
#
# If you are proxying LEAP, you MUST configure the EAP
# module, and you MUST list it here, in the post-proxy
# stage.
#
# You MUST also use the 'nostrip' option in the 'realm'
# configuration. Otherwise, the User-Name attribute
# in the proxied request will not match the user name
# hidden inside of the EAP packet, and the end server will
# reject the EAP request.
#
eap
#
# If the server tries to proxy a request and fails, then the
# request is processed through the modules in this section.
#
# The main use of this section is to permit robust proxying
# of accounting packets. The server can be configured to
# proxy accounting packets as part of normal processing.
# Then, if the home server goes down, accounting packets can
# be logged to a local "detail" file, for processing with
# radrelay. When the home server comes back up, radrelay
# will read the detail file, and send the packets to the
# home server.
#
# With this configuration, the server always responds to
# Accounting-Requests from the NAS, but only writes
# accounting packets to disk if the home server is down.
#
# Post-Proxy-Type Fail {
# detail
# }
}
} # inner-tunnel server block

View file

@ -1,466 +0,0 @@
######################################################################
#
# Authentification wifi du crans
#
######################################################################
#
# Authorization. First preprocess (hints and huntgroups files),
# then realms, and finally look in the "users" file.
#
# The order of the realm modules will determine the order that
# we try to find a matching realm.
#
# Make *sure* that 'preprocess' comes before any realm if you
# need to setup hints for the remote radius server
server wifi {
authorize {
if (User-Name !~ /crans$/) {
if (User-Name =~ /^(.*)@(.*)/) {
update control {
Proxy-To-Realm := 'FEDEREZ'
}
}
}
#
# The preprocess module takes care of sanitizing some bizarre
# attributes in the request, and turning them into attributes
# which are more standard.
#
# It takes care of processing the 'raddb/hints' and the
# 'raddb/huntgroups' files.
#
# It also adds the %{Client-IP-Address} attribute to the request.
#preprocess
#
# If you want to have a log of authentication requests,
# un-comment the following line, and the 'detail auth_log'
# section, above.
# auth_log
#
# The chap module will set 'Auth-Type := CHAP' if we are
# handling a CHAP request and Auth-Type has not already been set
# chap
#
# If the users are logging in with an MS-CHAP-Challenge
# attribute for authentication, the mschap module will find
# the MS-CHAP-Challenge attribute, and add 'Auth-Type := MS-CHAP'
# to the request, which will cause the server to then use
# the mschap module for authentication.
#mschap
#
# If you have a Cisco SIP server authenticating against
# FreeRADIUS, uncomment the following line, and the 'digest'
# line in the 'authenticate' section.
# digest
#
# Look for IPASS style 'realm/', and if not found, look for
# '@realm', and decide whether or not to proxy, based on
# that.
# IPASS
#
# If you are using multiple kinds of realms, you probably
# want to set "ignore_null = yes" for all of them.
# Otherwise, when the first style of realm doesn't match,
# the other styles won't be checked.
#
# suffix
# ntdomain
#
# This module takes care of EAP-MD5, EAP-TLS, and EAP-LEAP
# authentication.
#
# It also sets the EAP-Type attribute in the request
# attribute list to the EAP type from the packet.
#
# As of 2.0, the EAP module returns "ok" in the authorize stage
# for TTLS and PEAP. In 1.x, it never returned "ok" here, so
# this change is compatible with older configurations.
#
# The example below uses module failover to avoid querying all
# of the following modules if the EAP module returns "ok".
# Therefore, your LDAP and/or SQL servers will not be queried
# for the many packets that go back and forth to set up TTLS
# or PEAP. The load on those servers will therefore be reduced.
#
eap {
ok = return
}
#
# Pull crypt'd passwords from /etc/passwd or /etc/shadow,
# using the system API's to get the password. If you want
# to read /etc/passwd or /etc/shadow directly, see the
# passwd module in radiusd.conf.
#
# unix
#
# Read the 'users' file
# files
#
# Look in an SQL database. The schema of the database
# is meant to mirror the "users" file.
#
# See "Authorization Queries" in sql.conf
# sql
#
# If you are using /etc/smbpasswd, and are also doing
# mschap authentication, the un-comment this line, and
# configure the 'etc_smbpasswd' module, above.
# etc_smbpasswd
#
# The ldap module will set Auth-Type to LDAP if it has not
# already been set
#ldap
#
# Enforce daily limits on time spent logged in.
# daily
#
# Use the checkval module
# checkval
# expiration
# logintime
#
# If no other module has claimed responsibility for
# authentication, then try to use PAP. This allows the
# other modules listed above to add a "known good" password
# to the request, and to do nothing else. The PAP module
# will then see that password, and use it to do PAP
# authentication.
#
# This module should be listed last, so that the other modules
# get a chance to set Auth-Type for themselves.
#
#pap
#
# If "status_server = yes", then Status-Server messages are passed
# through the following section, and ONLY the following section.
# This permits you to do DB queries, for example. If the modules
# listed here return "fail", then NO response is sent.
#
# Autz-Type Status-Server {
#
# }
}
# Authentication.
#
#
# This section lists which modules are available for authentication.
# Note that it does NOT mean 'try each module in order'. It means
# that a module from the 'authorize' section adds a configuration
# attribute 'Auth-Type := FOO'. That authentication type is then
# used to pick the apropriate module from the list below.
#
# In general, you SHOULD NOT set the Auth-Type attribute. The server
# will figure it out on its own, and will do the right thing. The
# most common side effect of erroneously setting the Auth-Type
# attribute is that one authentication method will work, but the
# others will not.
#
# The common reasons to set the Auth-Type attribute by hand
# is to either forcibly reject the user (Auth-Type := Reject),
# or to or forcibly accept the user (Auth-Type := Accept).
#
# Note that Auth-Type := Accept will NOT work with EAP.
#
# Please do not put "unlang" configurations into the "authenticate"
# section. Put them in the "post-auth" section instead. That's what
# the post-auth section is for.
#
authenticate {
#
# PAP authentication, when a back-end database listed
# in the 'authorize' section supplies a password. The
# password can be clear-text, or encrypted.
#Auth-Type PAP {
# pap
#}
#
# Most people want CHAP authentication
# A back-end database listed in the 'authorize' section
# MUST supply a CLEAR TEXT password. Encrypted passwords
# won't work.
#Auth-Type CHAP {
# chap
#}
#
# MSCHAP authentication.
Auth-Type MS-CHAP {
mschap
}
#
# If you have a Cisco SIP server authenticating against
# FreeRADIUS, uncomment the following line, and the 'digest'
# line in the 'authorize' section.
# digest
#
# Pluggable Authentication Modules.
# pam
#
# See 'man getpwent' for information on how the 'unix'
# module checks the users password. Note that packets
# containing CHAP-Password attributes CANNOT be authenticated
# against /etc/passwd! See the FAQ for details.
#
# unix
# Uncomment it if you want to use ldap for authentication
#
# Note that this means "check plain-text password against
# the ldap database", which means that EAP won't work,
# as it does not supply a plain-text password.
#Auth-Type LDAP {
# ldap
#}
#
# Allow EAP authentication.
eap
}
#
# Pre-accounting. Decide which accounting type to use.
#
preacct {
preprocess
#
# Ensure that we have a semi-unique identifier for every
# request, and many NAS boxes are broken.
acct_unique
#
# Look for IPASS-style 'realm/', and if not found, look for
# '@realm', and decide whether or not to proxy, based on
# that.
#
# Accounting requests are generally proxied to the same
# home server as authentication requests.
# IPASS
suffix
# ntdomain
#
# Read the 'acct_users' file
# files
}
#
# Accounting. Log the accounting data.
#
accounting {
#
# Create a 'detail'ed log of the packets.
# Note that accounting requests which are proxied
# are also logged in the detail file.
# detail
# daily
# Update the wtmp file
#
# If you don't use "radlast", you can delete this line.
# unix
#
# For Simultaneous-Use tracking.
#
# Due to packet losses in the network, the data here
# may be incorrect. There is little we can do about it.
# radutmp
# sradutmp
# Return an address to the IP Pool when we see a stop record.
# main_pool
#
# Log traffic to an SQL database.
#
# See "Accounting queries" in sql.conf
# sql
#
# Instead of sending the query to the SQL server,
# write it into a log file.
#
# sql_log
# Cisco VoIP specific bulk accounting
# pgsql-voip
# Filter attributes from the accounting response.
# attr_filter.accounting_response
#
# See "Autz-Type Status-Server" for how this works.
#
# Acct-Type Status-Server {
#
# }
}
# Session database, used for checking Simultaneous-Use. Either the radutmp
# or rlm_sql module can handle this.
# The rlm_sql module is *much* faster
session {
# radutmp
#
# See "Simultaneous Use Checking Queries" in sql.conf
# sql
}
# Post-Authentication
# Once we KNOW that the user has been authenticated, there are
# additional steps we can take.
post-auth {
# Get an address from the IP Pool.
# main_pool
#
# If you want to have a log of authentication replies,
# un-comment the following line, and the 'detail reply_log'
# section, above.
# reply_log
#
# After authenticating the user, do another SQL query.
#
# See "Authentication Logging Queries" in sql.conf
# sql
#
# Instead of sending the query to the SQL server,
# write it into a log file.
#
# sql_log
#
# Un-comment the following if you have set
# 'edir_account_policy_check = yes' in the ldap module sub-section of
# the 'modules' section.
#
#ldap
# exec
#
# Access-Reject packets are sent through the REJECT sub-section of the
# post-auth section.
#
# Add the ldap module name (or instance) if you have set
# 'edir_account_policy_check = yes' in the ldap module configuration
#
Post-Auth-Type REJECT {
# attr_filter.access_reject
}
}
#
# When the server decides to proxy a request to a home server,
# the proxied request is first passed through the pre-proxy
# stage. This stage can re-write the request, or decide to
# cancel the proxy.
#
# Only a few modules currently have this method.
#
pre-proxy {
# attr_rewrite
# Uncomment the following line if you want to change attributes
# as defined in the preproxy_users file.
# files
# Uncomment the following line if you want to filter requests
# sent to remote servers based on the rules defined in the
# 'attrs.pre-proxy' file.
# attr_filter.pre-proxy
# If you want to have a log of packets proxied to a home
# server, un-comment the following line, and the
# 'detail pre_proxy_log' section, above.
# pre_proxy_log
}
#
# When the server receives a reply to a request it proxied
# to a home server, the request may be massaged here, in the
# post-proxy stage.
#
post-proxy {
# If you want to have a log of replies from a home server,
# un-comment the following line, and the 'detail post_proxy_log'
# section, above.
# post_proxy_log
# attr_rewrite
# Uncomment the following line if you want to filter replies from
# remote proxies based on the rules defined in the 'attrs' file.
# attr_filter.post-proxy
#
# If you are proxying LEAP, you MUST configure the EAP
# module, and you MUST list it here, in the post-proxy
# stage.
#
# You MUST also use the 'nostrip' option in the 'realm'
# configuration. Otherwise, the User-Name attribute
# in the proxied request will not match the user name
# hidden inside of the EAP packet, and the end server will
# reject the EAP request.
#
eap
#
# If the server tries to proxy a request and fails, then the
# request is processed through the modules in this section.
#
# The main use of this section is to permit robust proxying
# of accounting packets. The server can be configured to
# proxy accounting packets as part of normal processing.
# Then, if the home server goes down, accounting packets can
# be logged to a local "detail" file, for processing with
# radrelay. When the home server comes back up, radrelay
# will read the detail file, and send the packets to the
# home server.
#
# With this configuration, the server always responds to
# Accounting-Requests from the NAS, but only writes
# accounting packets to disk if the home server is down.
#
# Post-Proxy-Type Fail {
# detail
# }
}
}

View file

@ -16,10 +16,9 @@ delattr(sys, 'argv')
auth.instantiate(()) auth.instantiate(())
# Test avec l'interface wifi d'apprentis
p=( p=(
('Calling-Station-Id', '02:69:75:42:24:03'), ('Calling-Station-Id', 'b0:79:94:cf:d1:9a'),
('User-Name', 'apprentis-wifi'), ('User-Name', 'moo-torola'),
) )
print repr(auth.authorize_wifi(p)) print repr(auth.authorize_wifi(p))

View file

@ -1 +0,0 @@
../auth.py

View file

@ -1,16 +0,0 @@
#!/bin/bash
# Teste le inner-tunnel en se connectant directement au serveur
MAC=moo-torola
PASSWORD=7syxqbbkdb
#SECRET=`PYTHONPATH=/etc/crans/secrets/ python -c \
# "import secrets; print secrets.radius_eap_key"`
SECRET=e4hmraqw6Yps
NAS_NAME=atree.wifi.crans.org
#SERVER=127.0.0.1
SERVER=pea.v6.wifi.crans.org
SERVER=138.231.136.35
SERVER=[2a01:240:fe3d:c04:0:70ff:fe65:6103]
SERVER=localhost
radtest -t mschap -x -4 $MAC $PASSWORD $SERVER 18 $SECRET $SECRET $NAS_NAME

View file

@ -94,6 +94,10 @@ def dialog(backtitle, arg, dialogrc=''):
# Récupération du contenu du pipe # Récupération du contenu du pipe
_, sortie = processus.communicate() _, sortie = processus.communicate()
# On décode la sortie du programme dialog (et on le fait ici parce que
# c'est ici l'interface).
sortie = to_unicode(sortie)
resultat = sortie.splitlines() resultat = sortie.splitlines()
# Récupération du code d'erreur # Récupération du code d'erreur

View file

@ -8,6 +8,10 @@
# Contenu : # Contenu :
# --------- # ---------
# #
# Décorateur :
# static_var([(name, val)]), un décorateur pour créer des variables
# statiques dans une fonction
#
# Fonctions : # Fonctions :
# getTerminalSize(), une fonction qui récupère le couple # getTerminalSize(), une fonction qui récupère le couple
# largeur, hauteur du terminal courant. # largeur, hauteur du terminal courant.
@ -35,13 +39,12 @@ import os
import fcntl import fcntl
import termios import termios
import struct import struct
import functools
import time import time
import re import re
from locale import getpreferredencoding from locale import getpreferredencoding
from cranslib.decorators import static_var
OCT_NAMES = ["Pio", "Tio", "Gio", "Mio", "Kio"] OCT_NAMES = ["Pio", "Tio", "Gio", "Mio", "Kio"]
OCT_SIZES = [1024**(len(OCT_NAMES) - i) for i in xrange(0, len(OCT_NAMES))] OCT_SIZES = [1024**(len(OCT_NAMES) - i) for i in xrange(0, len(OCT_NAMES))]
TERM_FORMAT = '\x1b\[[0-1];([0-9]|[0-9][0-9])m' TERM_FORMAT = '\x1b\[[0-1];([0-9]|[0-9][0-9])m'
@ -53,9 +56,7 @@ def try_decode(string):
avoir en réception. avoir en réception.
""" """
if isinstance(string, unicode): unicode_str = ""
return string
try: try:
return string.decode("UTF-8") return string.decode("UTF-8")
except UnicodeDecodeError: except UnicodeDecodeError:
@ -87,6 +88,21 @@ def guess_preferred_encoding():
return encoding return encoding
def static_var(couples):
"""Decorator setting static variable
to a function.
"""
# Using setattr magic, we set static
# variable on function. This avoid
# computing stuff again.
def decorate(fun):
functools.wraps(fun)
for (name, val) in couples:
setattr(fun, name, val)
return fun
return decorate
def getTerminalSize(): def getTerminalSize():
"""Dummy function to get term dimensions. """Dummy function to get term dimensions.
Thanks to http://stackoverflow.com/questions/566746/how-to-get-console-window-width-in-python Thanks to http://stackoverflow.com/questions/566746/how-to-get-console-window-width-in-python
@ -260,7 +276,7 @@ def nostyle(dialog=False):
return "\Zn" return "\Zn"
return "\033[1;0m" return "\033[1;0m"
@static_var(("styles", {})) @static_var([("styles", {})])
def style(texte, what=None, dialog=False): def style(texte, what=None, dialog=False):
"""Pretty text is pretty """Pretty text is pretty
On peut appliquer plusieurs styles d'affilée, ils seront alors traités On peut appliquer plusieurs styles d'affilée, ils seront alors traités
@ -558,26 +574,7 @@ if __name__ == "__main__":
time.sleep(1) time.sleep(1)
prettyDoin("Les carottes sont cuites." , "Ok") prettyDoin("Les carottes sont cuites." , "Ok")
data = [ data = [[style("Durand", "rouge"), "Toto", "40", "50 rue Döp"], ["Dupont", "Robert", "50", "42" + style(" avenue ", "vert") + style("dumotel", 'rouge')], [style("znvuzbvzruobouzb", ["gras", "vert"]), "pppoe", "1", "poiodur 50 pepe"]]
[
style("Durand", "rouge"),
"Toto",
"40",
"50 rue Döp"
],
[
"Dupont",
"Robert",
"50",
"42" + style(" avenue ", "vert") + style("dumotel", 'rouge')
],
[
style("znvuzbvzruobouzb", ["gras", "vert"]),
"pppoe",
"1",
"poiodur 50 pepe"
]
]
titres = ("Nom", "Prénom", "Âge", "Adresse") titres = ("Nom", "Prénom", "Âge", "Adresse")
longueurs = [25, 25, '*', '*'] longueurs = [25, 25, '*', '*']
print tableau(data, titres, longueurs).encode(guess_preferred_encoding()) print tableau(data, titres, longueurs).encode(guess_preferred_encoding())

View file

@ -2,11 +2,8 @@
import os import os
import psycopg2 import psycopg2
from functools import wraps from functools import wraps
import time import time
import socket
conn = None conn = None
# : échec définitif, on raise une exception direct # : échec définitif, on raise une exception direct
@ -22,19 +19,17 @@ def _need_conn(f):
raise NameError("La connexion à la pase postgresql ne peut être établie.") raise NameError("La connexion à la pase postgresql ne peut être établie.")
attempts = 0 attempts = 0
while not conn or not attempts: while not conn or not attempts:
host = os.getenv('DBG_ANNUAIRE', 'pgsql.v4.adm.crans.org') if __name__.endswith('annuaires_pg_test') or os.getenv('DBG_ANNUAIRE', False):
# Test habituel sur vo:
if host == '1' or __name__.endswith('annuaires_pg_test'):
host='localhost' host='localhost'
else:
host='pgsql.v4.adm.crans.org'
# "connecting …" # "connecting …"
try: try:
if not conn: if not conn:
if attempts: if attempts:
# Attend un peu avant de reessayer # Attend un peu avant de reessayer
time.sleep(delay) time.sleep(delay)
conn = psycopg2.connect(user='crans_ro', database='django', conn = psycopg2.connect(user='crans', database='switchs',
host=host) host=host)
return f(*args, **kwargs) return f(*args, **kwargs)
except psycopg2.OperationalError: except psycopg2.OperationalError:
@ -47,8 +42,7 @@ def _need_conn(f):
# backend pgsql. On utilise donc une exception plus standard # backend pgsql. On utilise donc une exception plus standard
return first_connect return first_connect
# Le v est virtuel. bat_switchs = ["a", "b", "c", "g", "h", "i", "j", "m", "o", "p"]
bat_switchs = ["a", "b", "c", "g", "h", "i", "j", "m", "o", "p", "v"]
class ChbreNotFound(ValueError): class ChbreNotFound(ValueError):
"""Lorsqu'une chambre n'existe pas""" """Lorsqu'une chambre n'existe pas"""
@ -61,14 +55,14 @@ def chbre_prises(batiment, chambre = None):
if chambre: if chambre:
chambre = chambre.lower() chambre = chambre.lower()
cur = conn.cursor() cur = conn.cursor()
cur.execute("SELECT prise_crans FROM prises_prise WHERE (batiment, chambre) = (%s, %s)", (batiment, chambre)) cur.execute("SELECT prise_crans FROM prises WHERE (batiment, chambre) = (%s, %s)", (batiment, chambre))
try: try:
return "%03d" % cur.fetchone()[0] return "%03d" % cur.fetchone()[0]
except TypeError: except TypeError:
raise ChbreNotFound("Chambre inexistante bat %r, chbre %r" % (batiment, chambre)) raise ChbreNotFound("Chambre inexistante bat %r, chbre %r" % (batiment, chambre))
else: else:
cur = conn.cursor() cur = conn.cursor()
cur.execute("SELECT chambre, prise_crans FROM prises_prise WHERE batiment = %s", batiment) cur.execute("SELECT chambre, prise_crans FROM prises WHERE batiment = %s", batiment)
ret = {} ret = {}
for chambre, prise_crans in cur.fetchall(): for chambre, prise_crans in cur.fetchall():
ret[chambre] = "%03d" % prise_crans ret[chambre] = "%03d" % prise_crans
@ -82,7 +76,7 @@ def chbre_commentaire(batiment, chambre):
global conn global conn
batiment = batiment.lower() batiment = batiment.lower()
cur = conn.cursor() cur = conn.cursor()
cur.execute("SELECT commentaire FROM prises_prise WHERE (batiment, chambre) = (%s,%s)", (batiment, chambre)) cur.execute("SELECT commentaire FROM prises WHERE (batiment, chambre) = (%s,%s)", (batiment, chambre))
try: try:
return cur.fetchone()[0] return cur.fetchone()[0]
except TypeError: except TypeError:
@ -94,14 +88,14 @@ def reverse(batiment, prise = None):
batiment = batiment.lower() batiment = batiment.lower()
if prise: if prise:
cur = conn.cursor() cur = conn.cursor()
cur.execute("SELECT chambre FROM prises_prise WHERE (batiment, prise_crans) = (%s, %s)", (batiment, int(prise))) cur.execute("SELECT chambre FROM prises WHERE (batiment, prise_crans) = (%s, %s)", (batiment, int(prise)))
try: try:
return [chbre for (chbre,) in cur.fetchall()] return [chbre for (chbre,) in cur.fetchall()]
except TypeError: except TypeError:
raise ValueError("Prise %s inexistante" % prise) raise ValueError("Prise %s inexistante" % prise)
else: else:
cur = conn.cursor() cur = conn.cursor()
cur.execute("SELECT chambre, prise_crans FROM prises_prise WHERE batiment = %s", batiment) cur.execute("SELECT chambre, prise_crans FROM prises WHERE batiment = %s", batiment)
ret = {} ret = {}
for chambre, prise_crans in cur.fetchall(): for chambre, prise_crans in cur.fetchall():
try: try:
@ -112,15 +106,37 @@ def reverse(batiment, prise = None):
if not ret: if not ret:
raise ValueError("Batiment %s inexistant" % batiment) raise ValueError("Batiment %s inexistant" % batiment)
return ret return ret
@_need_conn
def is_crans(batiment, chambre):
"""Chambre cablee au Cr@ns ?"""
batiment = batiment.lower()
chambre = chambre.lower()
cur = conn.cursor()
cur.execute("SELECT crans FROM prises WHERE (batiment, chambre) = (%s, %s)", (batiment, chambre))
return cur.fetchone()[0]
@_need_conn @_need_conn
def is_connected(batiment, chambre): def is_connected(batiment, chambre):
"""Cablage physique effectue ?""" """Cablage physique effectue ?"""
batiment = batiment.lower() batiment = batiment.lower()
chambre = chambre.lower() chambre = chambre.lower()
cur = conn.cursor() cur = conn.cursor()
cur.execute("SELECT cablage_effectue FROM prises_prise WHERE (batiment, chambre) = (%s, %s)", (batiment, chambre)) cur.execute("SELECT cablage_effectue FROM prises WHERE (batiment, chambre) = (%s, %s)", (batiment, chambre))
return cur.fetchone()[0] return cur.fetchone()[0]
@_need_conn
def crous_to_crans(batiment, chambre):
"""Passage d'une chambre de CROUS a Cr@ns"""
batiment = batiment.lower()
chambre = chambre.lower()
if is_crans(batiment, chambre):
return
cur = conn.cursor()
cur.execute("UPDATE prises SET (crans, crous, cablage_effectue) = (TRUE, FALSE, not cablage_effectue) WHERE (batiment, chambre) = (%s, %s)", (batiment, chambre))
conn.commit()
cur.close()
# Prises d'uplink, de machines du crans / Prises d'utilité CRANS # Prises d'uplink, de machines du crans / Prises d'utilité CRANS
uplink_prises={ 'a' : uplink_prises={ 'a' :
{ 49 : 'uplink->bata-4', 50 : 'libre-service', { 49 : 'uplink->bata-4', 50 : 'libre-service',
@ -137,8 +153,7 @@ uplink_prises={ 'a' :
349 : 'uplink->batb-4', 350 : 'libre-service', 349 : 'uplink->batb-4', 350 : 'libre-service',
401 : 'uplink->batb-0', 402 : 'uplink->batb-1', 401 : 'uplink->batb-0', 402 : 'uplink->batb-1',
403 : 'uplink->batb-2', 404 : 'uplink->batb-3', 403 : 'uplink->batb-2', 404 : 'uplink->batb-3',
405 : 'uplink->backbone', 523 : 'uplink->batb-4', 405 : 'uplink->backbone' },
},
'c' : 'c' :
{ 49 : 'uplink->batc-3', 50 : 'libre-service', { 49 : 'uplink->batc-3', 50 : 'libre-service',
149 : 'uplink->batc-3', 150 : 'libre-service', 149 : 'uplink->batc-3', 150 : 'libre-service',
@ -274,35 +289,11 @@ uplink_prises={ 'a' :
_SPECIAL_SWITCHES=['backbone.adm.crans.org', _SPECIAL_SWITCHES=['backbone.adm.crans.org',
'multiprise-v6.adm.crans.org', 'multiprise-v6.adm.crans.org',
'batk-0.crans.org', 'batk-0.crans.org',
'minigiga.adm.crans.org',
'batb-5.crans.org',
]
_HIDDEN_SWITCHES = [
'batp-4.adm.crans.org', 'batp-4.adm.crans.org',
'batv-0.adm.crans.org', 'minigiga.adm.crans.org',
] ]
def guess_switch_fqdn(switch_name): def all_switchs(bat=None, hide=_SPECIAL_SWITCHES):
"""Retourne le FQDN d'un switch à partir de son nom"""
try:
return socket.gethostbyname_ex(switch_name)[0]
except socket.gaierror:
pass
try:
return socket.gethostbyname_ex(switch_name + ".adm.crans.org")[0]
except socket.gaierror:
pass
try:
return socket.gethostbyname_ex(switch_name + ".crans.org")[0]
except socket.gaierror:
pass
raise socket.gaierror
def all_switchs(bat=None, hide=_SPECIAL_SWITCHES + _HIDDEN_SWITCHES):
"""Retourne la liste des switchs pour un batiment. """Retourne la liste des switchs pour un batiment.
Si bat est donné, seulement pour le bâtiment demandé, sinon pour Si bat est donné, seulement pour le bâtiment demandé, sinon pour
@ -311,19 +302,14 @@ def all_switchs(bat=None, hide=_SPECIAL_SWITCHES + _HIDDEN_SWITCHES):
simplement batx""" simplement batx"""
if bat == None: if bat == None:
bat = list(bat_switchs) bat = bat_switchs
if type(bat) not in [ tuple, list ] : if type(bat) not in [ tuple, list ] :
bat = [bat] bat = [bat]
switchs = [] switchs = []
for b in bat: for b in bat:
indexes = set(n/100 for n in uplink_prises[b]) indexes = set(n/100 for n in uplink_prises[b])
for i in indexes: for i in indexes:
switch_name = "bat%s-%s" % (b, i) hostname = "bat%s-%s.adm.crans.org" % (b, i)
try:
hostname = guess_switch_fqdn(switch_name)
except socket.gaierror:
print "Le switch %s ne semble pas exister." % (switch_name,)
continue
if hostname not in hide: if hostname not in hide:
switchs.append(hostname) switchs.append(hostname)
# on ajoute quand-même le backbone et/ou multiprise-v6 si demandé # on ajoute quand-même le backbone et/ou multiprise-v6 si demandé

View file

@ -18,10 +18,11 @@ import re
import affichage import affichage
import lc_ldap.shortcuts import lc_ldap.shortcuts
from lc_ldap.crans_utils import to_generalized_time_format as to_gtf
import mail as mail_module import mail as mail_module
from config import demenagement_delai as delai, \ from config import demenagement_delai as delai, \
gtf_debut_periode_transitoire, periode_transitoire debut_periode_transitoire, periode_transitoire
ERASE_DAY = { 'second': 0, 'minute': 0, 'microsecond': 0, 'hour': 0, } ERASE_DAY = { 'second': 0, 'minute': 0, 'microsecond': 0, 'hour': 0, }
DAY = datetime.timedelta(days=1) DAY = datetime.timedelta(days=1)
@ -71,28 +72,16 @@ def warn_or_delete(smtp, clandestin, fail, done):
mail_addr = clandestin.get_mail() mail_addr = clandestin.get_mail()
if not clandestin.machines() or not mail_addr: if not clandestin.machines() or not mail_addr:
return # Si pas de machine, on s'en fout. Si pas de mail, inutile return # Si pas de machine, on s'en fout. Si pas de mail, inutile
try:
data = {
'dn': clandestin.dn.split(',')[0],
'when': now.strftime('%Y/%M/%D %H:%m:%S:%s'),
'chbre' : exchambre,
}
chbre_url = mail_module.validation_url('demenagement', data, True)
chbre_url_error = u""
except Exception as error:
chbre_url_error = u"[[erreur de génération: %r]]" % error
chbre_url = u""
data = { data = {
"from" : RESP, "from" : RESP,
"chambre" : exchambre, "chambre" : exchambre,
"jours" : (date_suppr - now).days+1, "jours" : (date_suppr - now).days+1,
"to" : mail_addr, "to" : mail_addr,
"adh": clandestin, "adh": clandestin,
"chbre_url" : chbre_url,
"chbre_url_error" : chbre_url_error,
"lang_info": "English version below", "lang_info": "English version below",
} }
smtp.send_template('demenagement', data) mail = mail_module.generate('demenagement', data)
smtp.sendmail(RESP, [mail_addr], mail.as_string())
def format_entry(m): def format_entry(m):
"""Renvoie une ligne de tableau, pour une machine""" """Renvoie une ligne de tableau, pour une machine"""
@ -112,7 +101,7 @@ if __name__ == '__main__':
conn = lc_ldap.shortcuts.lc_ldap_admin() conn = lc_ldap.shortcuts.lc_ldap_admin()
if periode_transitoire: if periode_transitoire:
date = gtf_debut_periode_transitoire date = to_gtf(debut_periode_transitoire)
else: else:
date = now.strftime(FORMAT_LDAP) + 'Z' date = now.strftime(FORMAT_LDAP) + 'Z'

View file

@ -25,6 +25,7 @@ import lc_ldap.attributs
import lc_ldap.objets import lc_ldap.objets
import gestion.mail as mail_module import gestion.mail as mail_module
encoding = getattr(sys.stdout, 'encoding', "UTF-8")
current_user = os.getenv("SUDO_USER") or os.getenv("USER") or os.getenv("LOGNAME") or getpass.getuser() current_user = os.getenv("SUDO_USER") or os.getenv("USER") or os.getenv("LOGNAME") or getpass.getuser()
def check_password(password, no_cracklib=False, dialog=False): def check_password(password, no_cracklib=False, dialog=False):
@ -38,16 +39,15 @@ def check_password(password, no_cracklib=False, dialog=False):
password.decode('ascii') password.decode('ascii')
except UnicodeDecodeError: except UnicodeDecodeError:
problem = True problem = True
msg += u"Le mot de passe ne doit contenir que des caractères ascii.\n" if not dialog:
affich_tools.cprint(u'Le mot de passe ne doit contenir que des caractères ascii.', "rouge")
if len(password) >= 64: else:
problem = True msg += affich_tools.coul(u'Le mot de passe ne doit contenir que des caractères ascii.\n', "rouge", dialog=dialog)
msg += u"Le mot de passe doit faire strictement moins de 64 caractères\n"
# Nounou mode # Nounou mode
if no_cracklib: if no_cracklib:
if len(password) >= config.password.root_min_len: if len(password) >= config.password.root_min_len:
return True, msg return True
else: else:
upp = 0 upp = 0
low = 0 low = 0
@ -67,22 +67,37 @@ def check_password(password, no_cracklib=False, dialog=False):
# Recherche de manque de caractères # Recherche de manque de caractères
if cif < config.password.min_cif: if cif < config.password.min_cif:
msg += u'Le mot de passe doit contenir plus de chiffres.\n' if not dialog:
affich_tools.cprint(u'Le mot de passe doit contenir plus de chiffres.', "rouge")
else:
msg += affich_tools.coul(u'Le mot de passe doit contenir plus de chiffres.\n', "rouge", dialog=dialog)
problem = True problem = True
if upp < config.password.min_upp: if upp < config.password.min_upp:
msg += u'Le mot de passe doit contenir plus de majuscules.\n' if not dialog:
affich_tools.cprint(u'Le mot de passe doit contenir plus de majuscules.', "rouge")
else:
msg += affich_tools.coul(u'Le mot de passe doit contenir plus de majuscules.\n', "rouge", dialog=dialog)
problem = True problem = True
if low < config.password.min_low: if low < config.password.min_low:
msg += u'Le mot de passe doit contenir plus de minuscules.\n' if not dialog:
affich_tools.cprint(u'Le mot de passe doit contenir plus de minuscules.', "rouge")
else:
msg += affich_tools.coul(u'Le mot de passe doit contenir plus de minuscules.\n', "rouge", dialog=dialog)
problem = True problem = True
if oth < config.password.min_oth: if oth < config.password.min_oth:
msg += u'Le mot de passe doit contenir plus de caractères qui ne sont ni des chiffres, ni des majuscules, ni des minuscules.\n' if not dialog:
affich_tools.cprint(u'Le mot de passe doit contenir plus de caractères qui ne sont ni des chiffres, ni des majuscules, ni des minuscules.', "rouge")
else:
msg += affich_tools.coul(u'Le mot de passe doit contenir plus de caractères qui ne sont ni des chiffres, ni des majuscules, ni des minuscules.\n', "rouge", dialog=dialog)
problem = True problem = True
# Scores sur la longueur # Scores sur la longueur
longueur = config.password.upp_value*upp + config.password.low_value*low + config.password.cif_value*cif + config.password.oth_value*oth longueur = config.password.upp_value*upp + config.password.low_value*low + config.password.cif_value*cif + config.password.oth_value*oth
if longueur < config.password.min_len: if longueur < config.password.min_len:
msg += u'Le mot de passe devrait être plus long, ou plus difficile.\n' if not dialog:
affich_tools.cprint(u'Le mot de passe devrait être plus long, ou plus difficile.', "rouge")
else:
msg += affich_tools.coul(u'Le mot de passe devrait être plus long, ou plus difficile.\n', "rouge", dialog=dialog)
problem = True problem = True
if not problem: if not problem:
@ -96,46 +111,31 @@ def check_password(password, no_cracklib=False, dialog=False):
# Le mot vient-il du dico (à améliorer, on voudrait pouvoir préciser # Le mot vient-il du dico (à améliorer, on voudrait pouvoir préciser
# la rigueur du test) ? # la rigueur du test) ?
password = cracklib.VeryFascistCheck(password) password = cracklib.VeryFascistCheck(password)
if dialog:
msg = affich_tools.coul(msg, 'rouge', dialog=dialog)
return True, msg return True, msg
except ValueError as e: except ValueError as e:
msg += str(e).decode(config.in_encoding) if not dialog:
affich_tools.cprint(e.message, "rouge")
if dialog: else:
msg = affich_tools.coul(msg, 'rouge', dialog=dialog) msg += affich_tools.coul(str(e).decode(), "rouge", dialog=dialog)
return False, msg return False, msg
else: else:
if dialog:
msg = affich_tools.coul(msg, 'rouge', dialog=dialog)
return True, msg return True, msg
else: else:
if dialog:
msg = affich_tools.coul(msg, 'rouge', dialog=dialog)
return False, msg return False, msg
if dialog:
msg = affich_tools.coul(msg, 'rouge', dialog=dialog)
return False, msg return False, msg
@lc_ldap.shortcuts.with_ldap_conn(retries=2, delay=5, constructor=lc_ldap.shortcuts.lc_ldap_admin) @lc_ldap.shortcuts.with_ldap_conn(retries=2, delay=5, constructor=lc_ldap.shortcuts.lc_ldap_admin)
def change_password(ldap, login=None, verbose=False, no_cracklib=False, **kwargs): def change_password(ldap, login=None, verbose=False, no_cracklib=False, **args):
""" """
Change le mot de passe en fonction des arguments Change le mot de passe en fonction des arguments
""" """
if login is None: if login is None:
login = current_user login = current_user
if type(login) == str: if type(login) == str:
login = login.decode(config.in_encoding) login = login.decode(encoding)
if no_cracklib:
if not lc_ldap.attributs.nounou in ldap.droits:
no_cracklib = False
login = lc_ldap.crans_utils.escape(login) login = lc_ldap.crans_utils.escape(login)
query = ldap.search(u"(uid=%s)" % login, mode="w") query = ldap.search(u"(uid=%s)" % login, mode="w")
if not query: if not query:
affich_tools.cprint('Utilisateur introuvable dans la base de données, modification de l\'utilisateur local.', "rouge") affich_tools.cprint('Utilisateur introuvable dans la base de données, modification de l\'utilisateur local.', "rouge")
sys.exit(2) sys.exit(2)
@ -145,7 +145,7 @@ def change_password(ldap, login=None, verbose=False, no_cracklib=False, **kwargs
user['userPassword'] = [lc_ldap.crans_utils.hash_password("test").decode('ascii')] user['userPassword'] = [lc_ldap.crans_utils.hash_password("test").decode('ascii')]
user.cancel() user.cancel()
except EnvironmentError as e: except EnvironmentError as e:
affich_tools.cprint(str(e).decode(config.in_encoding), "rouge") affich_tools.cprint(str(e).decode(encoding), "rouge")
# Génération d'un mail # Génération d'un mail
From = 'roots@crans.org' From = 'roots@crans.org'
@ -155,7 +155,7 @@ To: %s
Subject: Tentative de changement de mot de passe ! Subject: Tentative de changement de mot de passe !
Tentative de changement du mot de passe de %s par %s. Tentative de changement du mot de passe de %s par %s.
""" % (From, To, login.encode(config.out_encoding), current_user) """ % (From, To , login.encode(encoding), current_user)
# Envoi mail # Envoi mail
with mail_module.ServerConnection() as conn: with mail_module.ServerConnection() as conn:
@ -167,23 +167,19 @@ Tentative de changement du mot de passe de %s par %s.
prenom = "Club" prenom = "Club"
else: else:
prenom = user['prenom'][0] prenom = user['prenom'][0]
affich_tools.cprint( affich_tools.cprint("Changement du mot de passe de %s %s." %
"Changement du mot de passe de %s %s." % ( (prenom, user['nom'][0]),
prenom, "vert")
user['nom'][0]
),
"vert",
)
# Règles du jeu # Règles du jeu
# (J'ai perdu) # (J'ai perdu)
if verbose: if verbose:
affich_tools.cprint( affich_tools.cprint(u"""Règles :
u"""Règles :
Longueur standard : %s, root : %s, Longueur standard : %s, root : %s,
Minimums : chiffres : %s, minuscules : %s, majuscules : %s, autres : %s, Minimums : chiffres : %s, minuscules : %s, majuscules : %s, autres : %s,
Scores de longueur : chiffres : %s, minuscules : %s, majuscules : %s, autres : %s, Scores de longueur : chiffres : %s, minuscules : %s, majuscules : %s, autres : %s,
Cracklib : %s.""" % (config.password.min_len, Cracklib : %s.""" % (
config.password.min_len,
config.password.root_min_len, config.password.root_min_len,
config.password.min_cif, config.password.min_cif,
config.password.min_low, config.password.min_low,
@ -193,37 +189,32 @@ Cracklib : %s.""" % (config.password.min_len,
config.password.low_value, config.password.low_value,
config.password.upp_value, config.password.upp_value,
config.password.oth_value, config.password.oth_value,
"Oui" * (not no_cracklib) + "Non" * (no_cracklib), "Oui" * (not no_cracklib) + "Non" * (no_cracklib)
), ),
'jaune', 'jaune')
)
else: else:
affich_tools.cprint( affich_tools.cprint(u"""Le nouveau mot de passe doit comporter au minimum %s caractères.
u"""Le nouveau mot de passe doit comporter au minimum %s caractères.
Il ne doit pas être basé sur un mot du dictionnaire. Il ne doit pas être basé sur un mot du dictionnaire.
Il doit contenir au moins %s chiffre(s), %s minuscule(s), Il doit contenir au moins %s chiffre(s), %s minuscule(s),
%s majuscule(s) et au moins %s autre(s) caractère(s). %s majuscule(s) et au moins %s autre(s) caractère(s).
CTRL+D ou CTRL+C provoquent un abandon.""" % (config.password.min_len, CTRL+D ou CTRL+C provoquent un abandon.""" %
(
config.password.min_len,
config.password.min_cif, config.password.min_cif,
config.password.min_low, config.password.min_low,
config.password.min_upp, config.password.min_upp,
config.password.min_oth config.password.min_oth
), ), 'jaune')
'jaune',
)
try: try:
while True: while True:
mdp = getpass.getpass("Nouveau mot de passe: ") mdp = getpass.getpass("Nouveau mot de passe: ")
(ret, msg) = check_password(mdp, no_cracklib) if check_password(mdp, no_cracklib)[0]:
if ret:
mdp2 = getpass.getpass("Retaper le mot de passe: ") mdp2 = getpass.getpass("Retaper le mot de passe: ")
if mdp != mdp2: if mdp != mdp2:
affich_tools.cprint(u"Les deux mots de passe diffèrent.", "rouge") affich_tools.cprint(u"Les deux mots de passe diffèrent.", "rouge")
else: else:
break break
else:
affich_tools.cprint(msg, 'rouge')
except KeyboardInterrupt: except KeyboardInterrupt:
affich_tools.cprint(u'\nAbandon', 'rouge') affich_tools.cprint(u'\nAbandon', 'rouge')
@ -240,35 +231,29 @@ CTRL+D ou CTRL+C provoquent un abandon.""" % (config.password.min_len,
affich_tools.cprint(u"Mot de passe de %s changé." % (user['uid'][0]), "vert") affich_tools.cprint(u"Mot de passe de %s changé." % (user['uid'][0]), "vert")
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Recherche dans la base des adhérents", parser = argparse.ArgumentParser(
add_help=False, description="Recherche dans la base des adhérents",
) add_help=False)
parser.add_argument('-h', parser.add_argument('-h', '--help',
'--help',
help="Affiche ce message et quitte.", help="Affiche ce message et quitte.",
action="store_true", action="store_true")
) parser.add_argument('-n', '--no-cracklib',
parser.add_argument('-n',
'--no-cracklib',
help="Permet de contourner les règles de choix du mot de passe" + help="Permet de contourner les règles de choix du mot de passe" +
"(réservé aux nounous).", "(réservé aux nounous).",
action="store_true", action="store_true")
) parser.add_argument('-v', '--verbose',
parser.add_argument('-v',
'--verbose',
help="Permet de contourner les règles de choix du mot de passe" + help="Permet de contourner les règles de choix du mot de passe" +
"(réservé aux nounous).", "(réservé aux nounous).",
action="store_true", action="store_true")
) parser.add_argument('login', type=str, nargs="?",
parser.add_argument('login', help="L'utilisateur dont on veut changer le mot de passe.")
type=str,
nargs="?",
help="L'utilisateur dont on veut changer le mot de passe.",
)
args = parser.parse_args() args = parser.parse_args()
if args.help: if args.help:
parser.print_help() parser.print_help()
sys.exit(0) sys.exit(0)
if args.no_cracklib:
if not lc_ldap.attributs.nounou in ldap.droits:
args.no_cracklib = False
change_password(**vars(args)) change_password(**vars(args))

View file

@ -10,28 +10,29 @@
import os, sys import os, sys
from gestion.affich_tools import prompt from gestion.affich_tools import prompt
from gestion.ldap_crans import crans_ldap
from lc_ldap import shortcuts db = crans_ldap()
ldap = shortcuts.lc_ldap_admin()
uid = os.getenv('SUDO_UID') uid = os.getenv('SUDO_UID')
if not uid : if not uid :
print "Impossible de déterminer l'utilisateur" print "Impossible de déterminer l'utilisateur"
sys.exit(1) sys.exit(1)
adh = ldap.search(u'uidNumber=%s' % uid,mode='w') s = db.search('uidNumber=%s' % os.getenv('SUDO_UID'),'w')
try:
adh = adh[0]
except IndexError:
print 'Erreur fatale lors de la consultation de la base LDAP'
sys.exit(3)
# On vérifie que c'est pas un club # On vérifie que c'est pas un club
if unicode(adh.ldap_name)!=u"adherent": club = s['club']
if len(club) == 1 :
print 'Pas de changement de shell pour les clubs' print 'Pas de changement de shell pour les clubs'
sys.exit(2) sys.exit(2)
# On regarde si on a des résultats dans les adhérents
adh = s['adherent']
if len(adh) != 1 :
print 'Erreur fatale lors de la consultation de la base LDAP'
sys.exit(3)
adh = adh[0]
shell = prompt(u'Nouveau shell :') shell = prompt(u'Nouveau shell :')
fd=open('/etc/shells') fd=open('/etc/shells')
lines=fd.readlines() lines=fd.readlines()
@ -44,9 +45,7 @@ if not shell in shells:
print '\n'.join(shells) print '\n'.join(shells)
sys.exit(4) sys.exit(4)
with adh as ad: adh.chsh(shell)
ad['loginShell']=shell adh.save()
ad.save()
# A cause de nscd # A cause de nscd
print "La modification sera prise en compte dans l'heure suivante." print "La modification sera prise en compte dans l'heure suivante."

View file

@ -4,4 +4,3 @@
from config import * from config import *
from encoding import * from encoding import *
import dns

View file

@ -9,35 +9,42 @@ import datetime
# Fichier généré à partir de bcfg2 # Fichier généré à partir de bcfg2
from config_srv import adm_only, role from config_srv import adm_only, role
# Valeur par défaut pour les champs d'études
etudes_defaults = [
u"Établissement inconnu",
u"Année inconnue",
u"Domaine d'études inconnu"
]
gtfepoch = "19700101000000Z" gtfepoch = "19700101000000Z"
##### Gestion des câblages ##### Gestion des câblages
# Selon la date, on met : # Selon la date, on met :
# -ann_scol : Année scolaire en cours
# -periode_transitoire : on accepte ceux qui ont payé l'année dernière # -periode_transitoire : on accepte ceux qui ont payé l'année dernière
# On récupère l'année scolaire à tout besoin # Ne modifier que les dates !
__annee = time.localtime()[0] dat = time.localtime()
if dat[1] < 8 or dat[1] == 8 and dat[2] < 16:
# Si pas encore début août, on est dans l'année précédente
ann_scol = dat[0]-1
periode_transitoire = False
# sinon on change d'année
elif dat[1] < 10:
# Si pas encore octobre, les gens ayant payé l'année précédente sont
# acceptés
ann_scol = dat[0]
periode_transitoire = True
else:
# Seulement ceux qui ont payé cette année sont acceptés
ann_scol = dat[0]
periode_transitoire = False
# Prochaine période transitoire de l'année version generalizedTimeFormat debut_periode_transitoire = time.mktime(time.strptime("%s/08/16 00:00:00" % (ann_scol,), "%Y/%m/%d %H:%M:%S"))
gtf_debut_periode_transitoire = "%s0816000000+0200" % (__annee,) fin_periode_transitoire = time.mktime(time.strptime("%s/09/30 23:59:59" % (ann_scol,), "%Y/%m/%d %H:%M:%S"))
gtf_fin_periode_transitoire = "%s0930235959+0200" % (__annee,)
# Version timestampées timezone-naïves ## Bloquage si carte d'étudiants manquante pour l'année en cours
debut_periode_transitoire = time.mktime(time.strptime("%s/08/16 00:00:00" % (__annee,), "%Y/%m/%d %H:%M:%S")) # /!\ Par sécurité, ces valeurs sont considérées comme False si
fin_periode_transitoire = time.mktime(time.strptime("%s/09/30 23:59:59" % (__annee,), "%Y/%m/%d %H:%M:%S")) # periode_transitoire est True
# Soft (au niveau du Squid)
bl_carte_et_actif = not (dat[1] in [9, 10] or dat[1] == 11 and dat[2] < 7)
# Hard (l'adhérent est considéré comme paiement pas ok)
bl_carte_et_definitif = not(dat[1] == 11 and dat[2] < 17)
# On est en période transitoire si on est dans le bon intervale #Sursis pour les inscription après le 1/11 pour fournir la carte étudiant
periode_transitoire = (debut_periode_transitoire <= time.time() <= fin_periode_transitoire) sursis_carte=8*24*3600
ann_scol = __annee
if time.time() <= debut_periode_transitoire:
ann_scol -= 1
# Gel des cableurs pas a jour de cotisation # Gel des cableurs pas a jour de cotisation
# Les droits ne sont pas retires mais il n'y a plus de sudo # Les droits ne sont pas retires mais il n'y a plus de sudo
@ -60,8 +67,7 @@ club_login_shell = '/usr/bin/rssh'
# Longueur maximale d'un login # Longueur maximale d'un login
maxlen_login=25 maxlen_login=25
shells_possibles = [ shells_possibles = [u'/bin/csh',
u'/bin/csh',
u'/bin/sh', # tout caca u'/bin/sh', # tout caca
u'/bin/dash', # un bash light u'/bin/dash', # un bash light
u'/usr/bin/rc', u'/usr/bin/rc',
@ -86,179 +92,65 @@ shells_possibles = [
u'', # le shell vide pour pouvoir les punis u'', # le shell vide pour pouvoir les punis
] ]
shells_gest_crans_order = [ shells_gest_crans_order = ["zsh", "bash", "tcsh", "screen", "rbash", "rssh",
"zsh", "badPassSh", "disconnect_shell"]
"bash",
"tcsh",
"screen",
"rbash",
"rssh",
"badPassSh",
"disconnect_shell"
]
shells_gest_crans = { shells_gest_crans = {
"zsh" : { "zsh": {"path":"/bin/zsh", "desc":"Le Z SHell, shell par defaut sur zamok"},
"path" : "/bin/zsh", "bash": {"path":"/bin/bash", "desc":"Le Boune-Again SHell, shell par defaut de la plupart des linux"},
"desc" : "Le Z SHell, shell par defaut sur zamok" "tcsh": {"path":"/bin/tcsh", "desc":"C SHell ++"},
}, "screen":{"path":'/usr/bin/screen', "desc":"Un gestionnaire de fenêtre dans un terminal"},
"bash" : { "rbash": {"path":"/bin/rbash", "desc":"Un bash très restreint, voir man rbash"},
"path" : "/bin/bash", "rssh": {"path":"/usr/bin/rssh", "desc":"Shell ne permetant que les transferts de fichiers via scp ou sftp"},
"desc" : "Le Boune-Again SHell, shell par defaut de la plupart des linux" "badPassSh":{"path":"/usr/local/bin/badPassSh", "desc":"Demande de changer de mot de passe à la connexion"},
}, "disconnect_shell":{"path":"/usr/local/bin/disconnect_shell", "desc":"Shell pour les suspensions de compte avec message explicatif"},
"tcsh" : {
"path" : "/bin/tcsh",
"desc" : "C SHell ++"
},
"screen" : {
"path" : '/usr/bin/screen',
"desc" : "Un gestionnaire de fenêtre dans un terminal"
},
"rbash" : {
"path" : "/bin/rbash",
"desc" : "Un bash très restreint, voir man rbash"
},
"rssh" : {
"path" : "/usr/bin/rssh",
"desc" : "Shell ne permetant que les transferts de fichiers via scp ou sftp"
},
"badPassSh" : {
"path" : "/usr/local/bin/badPassSh",
"desc" : "Demande de changer de mot de passe à la connexion"
},
"disconnect_shell" : {
"path" : "/usr/local/bin/disconnect_shell",
"desc" : "Shell pour les suspensions de compte avec message explicatif"
},
} }
# Quels droits donnent l'appartenance à quel groupe Unix ? # Quels droits donnent l'appartenance à quel groupe Unix ?
droits_groupes = { droits_groupes = {'adm' : [u'Nounou'],
'adm' : [ 'respbats' : [u'Imprimeur', u'Cableur', u'Nounou'],
u'Nounou', 'apprentis' : [u'Apprenti'],
], 'moderateurs' : [u'Moderateur'],
'respbats' : [ 'disconnect' : [u'Bureau'],
u'Imprimeur', 'imprimeurs' : [u'Imprimeur', u'Nounou', u'Tresorier'],
u'Cableur', 'bureau' : [u'Bureau'],
u'Nounou', 'webadm' : [u'Webmaster'],
], 'webradio' : [u'Webradio'],
'apprentis' : [
u'Apprenti',
],
'moderateurs' : [
u'Moderateur',
],
'disconnect' : [
u'Bureau',
],
'imprimeurs' : [
u'Imprimeur',
u'Nounou',
u'Tresorier',
],
'bureau' : [
u'Bureau',
],
'webadm' : [
u'Webmaster',
],
'webradio' : [
u'Webradio',
],
} }
####### Les modes de paiement accepté par le crans ####### Les modes de paiement accepté par le crans
modePaiement = [ modePaiement = ['liquide', 'paypal', 'solde', 'cheque', 'carte']
'liquide',
'paypal',
'solde',
'cheque',
'carte',
'comnpay',
'arbitraire',
'note',
]
####### Les ML ####### Les ML
# Le + devant un nom de ML indique une synchronisation # Le + devant un nom de ML indique une synchronisation
# ML <-> fonction partielle : il n'y a pas d'effacement automatique # ML <-> fonction partielle : il n'y a pas d'effacement automatique
# des abonnés si le droit est retiré # des abonnés si le droit est retiré
droits_mailing_listes = { droits_mailing_listes = {'roots' : [ u'Nounou', u'Apprenti'],
'roots' : [ 'mailman' : [ u'Nounou'],
u'Nounou', '+nounou' : [ u'Nounou', u'Apprenti'],
u'Apprenti', 'respbats' : [ u'Cableur', u'Nounou', u'Bureau'],
], 'moderateurs' : [ u'Moderateur', u'Bureau'],
'mailman' : [ 'disconnect' : [ u'Nounou', u'Bureau'],
u'Nounou', 'impression' : [ u'Imprimeur'],
], 'bureau' : [u'Bureau'],
'+nounou' : [ 'tresorier' : [u'Tresorier'],
u'Nounou', 'apprentis' : [u'Apprenti'],
u'Apprenti', '+ca' : [u'Bureau', u'Apprenti', u'Nounou', u'Cableur'],
],
'respbats' : [ '+federez' : [u'Bureau', u'Apprenti', u'Nounou'],
u'Cableur', '+install-party' : [u'Bureau', u'Apprenti', u'Nounou'],
u'Nounou',
u'Bureau',
],
'moderateurs' : [
u'Moderateur',
u'Bureau',
],
'disconnect' : [
u'Nounou',
u'Bureau',
],
'impression' : [
u'Imprimeur',
],
'bureau' : [
u'Bureau',
],
'tresorier' : [
u'Tresorier',
],
'apprentis' : [
u'Apprenti',
],
'+ca' : [
u'Bureau',
u'Apprenti',
u'Nounou',
u'Cableur',
],
'+federez' : [
u'Bureau',
u'Apprenti',
u'Nounou',
],
'+install-party' : [
u'Bureau',
u'Apprenti',
u'Nounou',
],
# Correspondance partielle nécessaire... Des adresses non-crans sont inscrites à ces ML. # Correspondance partielle nécessaire... Des adresses non-crans sont inscrites à ces ML.
'+dsi-crans' : [ '+dsi-crans' : [u'Nounou', u'Bureau'],
u'Nounou', '+crous-crans' : [u'Nounou', u'Bureau'],
u'Bureau',
], '+wrc' : [u'Webradio'],
'+crous-crans' : [
u'Nounou',
u'Bureau',
],
'+wrc' : [
u'Webradio',
],
} }
#: Répertoire de stockage des objets détruits #: Répertoire de stockage des objets détruits
cimetiere = '/home/cimetiere' cimetiere = '/home/cimetiere'
#: Adresses mac utiles #: Adresses mac utiles
# Mac du routeur est la mac du routeur du crans (actuellement odlyd) mac_komaz = 'a0:d3:c1:00:f4:04'
# Utilisé par ra2.py, à changer si le routeur est remplacé
mac_komaz = '00:19:bb:31:3b:80'
mac_du_routeur = 'a0:d3:c1:00:f4:04'
mac_titanic = 'aa:73:65:63:6f:76' mac_titanic = 'aa:73:65:63:6f:76'
#: Serveur principal de bcfg2 #: Serveur principal de bcfg2
@ -290,7 +182,7 @@ sshfp_hash = {
"sha256" : 2, "sha256" : 2,
} }
sshkey_max_age = int(9.869604401089358 * (365.25 * 24 * 3600)) sshkey_max_age=2*(365.25*24*3600)
sshkey_size = { sshkey_size = {
'rsa':4096, 'rsa':4096,
@ -325,90 +217,46 @@ plage_ens = '138.231.0.0/16'
# clefs qui cassent la bijectivité, mais qui peuvent servir. # clefs qui cassent la bijectivité, mais qui peuvent servir.
# NETs est l'union des deux # NETs est l'union des deux
NETs_primaires = { NETs_primaires = {
'serveurs' : [ 'serveurs' : ['138.231.136.0/24'],
'138.231.136.0/24', 'adherents' : ['138.231.137.0/24', '138.231.138.0/23', '138.231.140.0/22'],
], 'wifi-adh' : ['138.231.144.0/22', '138.231.148.128/25', '138.231.149.0/24', '138.231.150.0/23'],
'adherents' : [ 'bornes' : ['138.231.148.0/25'],
'138.231.137.0/24', 'adm' : ['10.231.136.0/24'],
'138.231.138.0/23', 'personnel-ens' : ['10.2.9.0/24'],
'138.231.140.0/22', 'gratuit' : ['10.42.0.0/16'],
], 'accueil' : ['10.51.0.0/16'],
'wifi-adh' : [ 'isolement' : ['10.52.0.0/16'],
'138.231.144.0/22', 'evenementiel' : ['10.231.137.0/24'],
'138.231.148.32/27', 'multicast' : ['239.0.0.0/8'],
'138.231.148.64/26', 'ens' : ['138.231.135.0/24'],
'138.231.148.128/25',
'138.231.149.0/24',
'138.231.150.0/23',
],
'bornes' : [
'138.231.148.0/27',
],
'adm' : [
'10.231.136.0/24'
],
'personnel-ens' : [
'10.2.9.0/24'
],
'gratuit' : [
'10.42.0.0/16'
],
'accueil' : [
'10.51.0.0/16'
],
'federez' : [
'10.53.0.0/16'
],
'isolement' : [
'10.52.0.0/16'
],
'evenementiel' : [
'10.231.137.0/24'
],
'multicast' : [
'239.0.0.0/8'
],
'ens' : [
'138.231.135.0/24'
],
} }
NETs_secondaires = { NETs_secondaires = {
'all' : [ 'all' : ['138.231.136.0/21', '138.231.144.0/21'],
'138.231.136.0/21', 'wifi': ['138.231.144.0/21'],
'138.231.144.0/21', 'fil' : ['138.231.136.0/21'],
],
'wifi': [
'138.231.144.0/21',
],
'fil' : [
'138.231.136.0/21',
],
} }
NETs = {} NETs = {}
NETs.update(NETs_primaires) NETs.update(NETs_primaires)
NETs.update(NETs_secondaires) NETs.update(NETs_secondaires)
NETs_regexp = { NETs_regexp = { 'all' : '^138\.231\.1(3[6789]|4[0123456789]|5[01])\.\d+$' }
'all' : r'^138\.231\.1(3[6789]|4[0123456789]|5[01])\.\d+$'
}
# Classes de rid # Classes de rid
# Merci d'essayer de les faire correspondre avec les réseaux # Merci d'essayer de les faire correspondre avec les réseaux
# ci-dessus... # ci-dessus...
# De même que pout NETs, primaires c'est pour la bijectivité, et secondaires # De même que pout NETs, primaires c'est pour la bijectivité, et secondaires
# pour les trucs pratiques # pour les trucs pratiques
# https://wiki.crans.org/CransTechnique/PlanAdressage#Machines
rid_primaires = { rid_primaires = {
# Rid pour les serveurs # Rid pour les serveurs
'serveurs' : [(0, 255),], 'serveurs' : [(0, 255),],
# Rid pour les machines fixes # Rid pour les machines fixes
'adherents' : [(256, 2047),], 'adherents' : [(256, 2047),],
# Rid pour les machines wifi # Rid pour les machines wifi
'wifi-adh' : [(2048, 3071), (3104, 4095),], 'wifi-adh' : [(2048, 3071), (3200, 4095),],
# Rid pour les bornes # Rid pour les bornes
'bornes' : [(3072, 3103), (34816, 35071),], 'bornes' : [(3072, 3199),],
# Rid pour machines spéciales # Rid pour machines spéciales
'special' : [(4096, 6143),], 'special' : [(4096, 6143),],
# Rid pour les serveurs v6-only # Rid pour les serveurs v6-only
@ -417,8 +265,8 @@ rid_primaires = {
'adherents-v6' : [(16384, 24575),], 'adherents-v6' : [(16384, 24575),],
# Rid pour les wifi v6-only # Rid pour les wifi v6-only
'wifi-adh-v6' : [(24576, 32767),], 'wifi-adh-v6' : [(24576, 32767),],
# Bornes-v6 # Bornes-v6 ?
'bornes-v6' : [(34816, 35071),], 'bornes-v6' : [(32768, 33791),],
# Rid pour les machines du vlan adm # Rid pour les machines du vlan adm
'adm-v6' : [(49152, 51199),], 'adm-v6' : [(49152, 51199),],
# Rid pour les machines du vlan adm # Rid pour les machines du vlan adm
@ -434,7 +282,7 @@ rid_primaires = {
rid_secondaires = { rid_secondaires = {
# Rid pour les machines filaire ipv4 # Rid pour les machines filaire ipv4
'fil' : [(0, 2047),], 'fil' : [(0, 2047),],
'wifi' : [(2048, 4095), (34816, 35071),], 'wifi' : [(2048, 4095),],
} }
rid = {} rid = {}
@ -461,58 +309,23 @@ ipv6_machines_speciales = {
} }
# Les préfixes ipv6 publics # Les préfixes ipv6 publics
prefix = { prefix = { 'subnet' : [ '2a01:240:fe3d::/48' ],
'subnet' : [ 'serveurs' : [ '2a01:240:fe3d:4::/64' ],
'2a01:240:fe3d::/48', 'adherents' : [ '2a01:240:fe3d:4::/64' ],
], 'fil' : [ '2a01:240:fe3d:4::/64' ],
'serveurs' : [ 'adm' : [ '2a01:240:fe3d:c804::/64' ],
'2a01:240:fe3d:4::/64', 'adm-v6' : [ '2a01:240:fe3d:c804::/64' ],
], 'wifi' : [ '2a01:240:fe3d:c04::/64' ],
'adherents' : [ 'serveurs-v6' : [ '2a01:240:fe3d:c04::/64' ],
'2a01:240:fe3d:4::/64', 'adherents-v6' : [ '2a01:240:fe3d:4::/64' ],
], 'wifi-adh-v6' : [ '2a01:240:fe3d:c04::/64' ],
'fil' : [ 'personnel-ens' : [ '2a01:240:fe3d:4::/64' ],
'2a01:240:fe3d:4::/64', 'sixxs2' : [ '2a01:240:fe00:68::/64' ],
], 'evenementiel' : [ '2a01:240:fe3d:d2::/64' ],
'adm' : [ 'bornes' : [ '2a01:240:fe3d:c04::/64' ],
'2a01:240:fe3d:c804::/64', 'bornes-v6' : [ '2a01:240:fe3d:c04::/64' ],
], 'wifi-adh' : [ '2a01:240:fe3d:c04::/64' ],
'adm-v6' : [ 'v6only' : [ '2001:470:c8b9:a4::/64' ],
'2a01:240:fe3d:c804::/64',
],
'wifi' : [
'2a01:240:fe3d:c04::/64',
],
'serveurs-v6' : [
'2a01:240:fe3d:c04::/64',
],
'adherents-v6' : [
'2a01:240:fe3d:4::/64',
],
'wifi-adh-v6' : [
'2a01:240:fe3d:c04::/64',
],
'personnel-ens' : [
'2a01:240:fe3d:4::/64',
],
'sixxs2' : [
'2a01:240:fe00:68::/64',
],
'evenementiel' : [
'2a01:240:fe3d:d2::/64',
],
'bornes' : [
'2a01:240:fe3d:c04::/64',
],
'bornes-v6' : [
'2a01:240:fe3d:c04::/64',
],
'wifi-adh' : [
'2a01:240:fe3d:c04::/64',
],
'v6only' : [
'2001:470:c8b9:a4::/64',
],
} }
# Préfixes ipv6 internes (ula) # Préfixes ipv6 internes (ula)
@ -522,12 +335,10 @@ int_prefix = {
} }
# Domaines dans lesquels les machines sont placées suivant leur type # Domaines dans lesquels les machines sont placées suivant leur type
domains = { domains = { 'machineFixe': 'crans.org',
'machineFixe': 'crans.org',
'machineCrans': 'crans.org', 'machineCrans': 'crans.org',
'machineWifi': 'wifi.crans.org', 'machineWifi': 'wifi.crans.org',
'borneWifi': 'wifi.crans.org', 'borneWifi': 'wifi.crans.org' }
}
# VLans # VLans
vlans = { vlans = {
@ -547,10 +358,10 @@ vlans = {
'v6only': 6, 'v6only': 6,
# Vlan isolement # Vlan isolement
'isolement' : 9, 'isolement' : 9,
# Vlan de tests de chiffrement DSI
'chiffrement': 11,
# VLan des appartements de l'ENS # VLan des appartements de l'ENS
'appts': 21, 'appts': 21,
# Vlan federez-wifi
'federez': 22,
# Vlan evenementiel (install-party, etc) # Vlan evenementiel (install-party, etc)
'event': 10, 'event': 10,
# Vlan zone routeur ens (zrt) # Vlan zone routeur ens (zrt)
@ -561,98 +372,66 @@ vlans = {
'freebox': 8, 'freebox': 8,
} }
filter_policy = { filter_policy = { 'komaz' : { 'policy_input' : 'ACCEPT',
'komaz' : {
'policy_input' : 'ACCEPT',
'policy_forward' : 'ACCEPT', 'policy_forward' : 'ACCEPT',
'policy_output' : 'ACCEPT', 'policy_output' : 'ACCEPT'
}, },
'zamok' : { 'zamok' : { 'policy_input' : 'ACCEPT',
'policy_input' : 'ACCEPT',
'policy_forward' : 'DROP', 'policy_forward' : 'DROP',
'policy_output' : 'ACCEPT', 'policy_output' : 'ACCEPT'
}, },
'default' : { 'default' : { 'policy_input' : 'ACCEPT',
'policy_input' : 'ACCEPT',
'policy_forward' : 'ACCEPT', 'policy_forward' : 'ACCEPT',
'policy_output' : 'ACCEPT', 'policy_output' : 'ACCEPT'
} }
} }
# Cf RFC 4890 # Cf RFC 4890
authorized_icmpv6 = [ authorized_icmpv6 = ['echo-request', 'echo-reply', 'destination-unreachable',
'echo-request', 'packet-too-big', 'ttl-zero-during-transit', 'parameter-problem']
'echo-reply',
'destination-unreachable',
'packet-too-big',
'ttl-zero-during-transit',
'parameter-problem',
]
output_file = { output_file = { 4 : '/tmp/ipt_rules',
4 : '/tmp/ipt_rules', 6 : '/tmp/ip6t_rules'
6 : '/tmp/ip6t_rules',
} }
file_pickle = { file_pickle = { 4 : '/tmp/ipt_pickle',
4 : '/tmp/ipt_pickle', 6 : '/tmp/ip6t_pickle'
6 : '/tmp/ip6t_pickle',
} }
################################################################################## ##################################################################################
#: Items de la blackliste #: Items de la blackliste
blacklist_items = { blacklist_items = { u'bloq': u'Blocage total de tous les services',
u'bloq': u'Blocage total de tous les services', u'carte_etudiant': u'Carte etudiant manquante',
u'paiement': u'Paiement manquant cette année', u'paiement': u'Paiement manquant cette année',
u'virus': u'Passage en VLAN isolement', u'virus': u'Passage en VLAN isolement',
u'upload': u"Bridage du débit montant vers l'extérieur", u'upload': u"Bridage du débit montant vers l'extérieur",
u'p2p': u"Blocage total de l'accès à l'extérieur",
u'autodisc_virus': u'Autodisconnect pour virus',
u'autodisc_upload': u'Autodisconnect pour upload', u'autodisc_upload': u'Autodisconnect pour upload',
u'autodisc_p2p': u'Autodisconnect pour P2P',
u'ipv6_ra': u'Isolement pour RA', u'ipv6_ra': u'Isolement pour RA',
u'mail_invalide': u'Blocage pour mail invalide', u'mail_invalide': u'Blocage pour mail invalide',
u'warez' : u"Présence de contenu violant de droit d'auteur sur zamok", u'warez' : u"Présence de contenu violant de droit d'auteur sur zamok",
} }
#: Blacklistes entrainant une déconnexion complète #: Blacklistes entrainant une déconnexion complète
blacklist_sanctions = [ blacklist_sanctions = ['warez', 'p2p', 'autodisc_p2p','autodisc_virus','virus', 'bloq',
'warez',
'virus',
'bloq',
'paiement', 'paiement',
] ]
if bl_carte_et_definitif:
blacklist_sanctions.append('carte_etudiant')
#: Blacklistes redirigeant le port 80 en http vers le portail captif (avec des explications) #: Blacklistes redirigeant le port 80 en http vers le portail captif (avec des explications)
blacklist_sanctions_soft = [ blacklist_sanctions_soft = ['autodisc_virus','ipv6_ra','mail_invalide','virus',
'ipv6_ra', 'warez', 'p2p', 'autodisc_p2p', 'bloq','carte_etudiant','chambre_invalide']
'mail_invalide',
'virus',
'warez',
'bloq',
'chambre_invalide',
]
#: Blacklistes entrainant un bridage de la connexion pour upload #: Blacklistes entrainant un bridage de la connexion pour upload
blacklist_bridage_upload = ['autodisc_upload', 'upload'] blacklist_bridage_upload = ['autodisc_upload', 'upload']
################################################################################## ##################################################################################
adm_users = [ adm_users = [ 'root', 'identd', 'daemon', 'postfix', 'freerad', 'amavis',
'root', 'nut', 'respbats', 'list', 'sqlgrey', 'ntpd', 'lp' ]
'identd',
'daemon',
'postfix',
'freerad',
'amavis',
'nut',
'respbats',
'list',
'sqlgrey',
'ntpd',
'lp',
]
open_ports = { open_ports = { 'tcp' : '22' }
'tcp' : '22',
}
# Debit max sur le vlan de la connexion gratuite # Debit max sur le vlan de la connexion gratuite
debit_max_radin = 1000000 debit_max_radin = 1000000
@ -662,90 +441,13 @@ debit_max_gratuit = 1000000
## Vlan accueil et isolement ## ## Vlan accueil et isolement ##
############################### ###############################
accueil_route = { accueil_route = {
'138.231.136.1' : { '138.231.136.1':{'tcp':['80','443', '22'],'hosts':['intranet.crans.org', 'ssh.crans.org', 'zamok.crans.org']},
'tcp' : [ '138.231.136.67':{'tcp':['80','443'],'hosts':['www.crans.org', 'wiki.crans.org', 'wifi.crans.org']},
'80', '138.231.136.98':{'tcp':['20','21','80','111','1024:65535'],'udp':['69','1024:65535'], 'hosts':['ftp.crans.org']},
'443', '138.231.136.130':{'tcp':['80','443'],'hosts':['intranet2.crans.org']},
'22' '138.231.136.18':{'tcp':['80','443'],'hosts':['cas.crans.org', 'login.crans.org', 'auth.crans.org']},
], '213.154.225.236':{'tcp':['80','443'], 'hosts':['crl.cacert.org']},
'hosts' : [ '213.154.225.237':{'tcp':['80','443'], 'hosts':['ocsp.cacert.org']},
'ssh.crans.org',
'zamok.crans.org',
],
},
'138.231.136.67' : {
'tcp' : [
'80',
'443',
],
'hosts' : [
'www.crans.org',
'wiki.crans.org',
'wifi.crans.org',
],
},
'138.231.136.98' : {
'tcp' : [
'20',
'21',
'80',
'111',
'1024:65535',
],
'udp' : [
'69',
'1024:65535',
],
'hosts' : [
'ftp.crans.org',
],
},
'138.231.136.130' : {
'tcp' : [
'80',
'443',
],
'hosts' : [
'intranet2.crans.org',
'intranet.crans.org',
],
},
'138.231.136.18' : {
'tcp' : [
'80',
'443',
],
'hosts' : [
'cas.crans.org',
'login.crans.org',
'auth.crans.org',
],
},
'213.154.225.236' : {
'tcp' : [
'80',
'443',
],
'hosts' : [
'crl.cacert.org',
],
},
'213.154.225.237' : {
'tcp' : [
'80',
'443',
],
'hosts' : [
'ocsp.cacert.org',
],
},
} }
dhcp_servers = ['dhcp.adm.crans.org', 'isc.adm.crans.org'] dhcp_servers = ['dhcp.adm.crans.org', 'isc.adm.crans.org']
# Le bâtiment virtuel dans lequel on place des chambres qui n'existent pas, pour faire
# des tests.
bats_virtuels = ['v']
# Liste des batiments
liste_bats = ['a', 'b', 'c', 'h', 'i', 'j', 'm', 'g', 'p', 'k']

View file

@ -10,17 +10,11 @@
# Délai minimal avant de pouvoir réadhérer. # Délai minimal avant de pouvoir réadhérer.
# Ne tient pas compte de la période transitoire, qui est un confort # Ne tient pas compte de la période transitoire, qui est un confort
# pour l'administration. # pour l'administration.
delai_readh_jour = 32 delai_readh_jour = 15
delai_readh = delai_readh_jour * 86400 delai_readh = delai_readh_jour * 86400
duree_adh_an = 1 duree_adh_an = 1
# Un compte avec une adhésion valide ne peut être détruit que lorsque celle-ci
# est expirée depuis plus que le délai indiqué ici. (secondes)
# Ici, on choisit 90 jours.
del_post_adh_jours = 90
del_post_adh = del_post_adh_jours * 86400
# Cotisation pour adhérer à l'association. Les services autres que l'accès à # Cotisation pour adhérer à l'association. Les services autres que l'accès à
# Internet sont offerts une et une fois pour toute aux personnes qui adhèrent, # Internet sont offerts une et une fois pour toute aux personnes qui adhèrent,
# et ce dès leur première fois. (comprendre : le compte Crans et cie ne sont pas # et ce dès leur première fois. (comprendre : le compte Crans et cie ne sont pas

View file

@ -3,10 +3,8 @@
""" Variables de configuration pour la gestion du DNS """ """ Variables de configuration pour la gestion du DNS """
import os
# import des variables génériques # import des variables génériques
import __init__ as config import config
#: ariane et ariane2 pour la zone parente #: ariane et ariane2 pour la zone parente
parents = [ parents = [
@ -33,58 +31,17 @@ zone_tv = 'tv.crans.org'
secours_relay='10.231.136.14'; secours_relay='10.231.136.14';
#: Serveurs autoritaires pour les zones crans, le master doit être le premier #: Serveurs autoritaires pour les zones crans, le master doit être le premier
DNSs = [ DNSs = ['sable.crans.org', 'freebox.crans.org', 'soyouz.crans.org']
'sable.crans.org',
'freebox.crans.org',
'soyouz.crans.org',
]
MXs = {
'redisdead.crans.org': {
'prio': 10,
},
'freebox.crans.org': {
'prio': 25,
},
'soyouz.crans.org': {
'prio': 15,
},
}
#: Résolution DNS directe, liste de toutes les zones crans hors reverse #: Résolution DNS directe, liste de toutes les zones crans hors reverse
zones_direct = [ zones_direct = [ 'crans.org', 'crans.ens-cachan.fr', 'wifi.crans.org', 'clubs.ens-cachan.fr', 'adm.crans.org','crans.eu','wifi.crans.eu', 'tv.crans.org', 'ap.crans.org' ]
'crans.org',
'crans.ens-cachan.fr',
'wifi.crans.org',
'clubs.ens-cachan.fr',
'adm.crans.org',
'crans.eu',
'wifi.crans.eu',
'tv.crans.org',
'ap.crans.org',
]
#: Les zones apparaissant dans des objets lc_ldap #: Les zones apparaissant dans des objets lc_ldap
zones_ldap = [ zones_ldap = [ 'crans.org', 'crans.ens-cachan.fr', 'wifi.crans.org', 'clubs.ens-cachan.fr', 'adm.crans.org', 'tv.crans.org' ]
'crans.org',
'crans.ens-cachan.fr',
'wifi.crans.org',
'clubs.ens-cachan.fr',
'adm.crans.org',
'tv.crans.org',
]
#: Zones signée par opendnssec sur le serveur master #: Zones signée par opendnssec sur le serveur master
zones_dnssec = [ zones_dnssec = ['crans.org', 'wifi.crans.org', 'adm.crans.org', 'tv.crans.org', 'crans.eu']
'crans.org',
'wifi.crans.org',
'adm.crans.org',
'tv.crans.org',
'crans.eu',
]
#: Zones alias : copie les valeur des enregistrement pour la racine de la zone et utilise un enregistemenr DNAME pour les sous domaines #: Zones alias : copie les valeur des enregistrement pour la racine de la zone et utilise un enregistemenr DNAME pour les sous domaines
zone_alias = { zone_alias = {
'crans.org' : [ 'crans.org' : ['crans.eu'],
'crans.eu',
],
} }
#: Résolution inverse v4 #: Résolution inverse v4
@ -94,61 +51,15 @@ zones_reverse_v6 = config.prefix['fil'] + config.prefix['wifi'] + config.prefix[
#: Serveurs DNS récursifs : #: Serveurs DNS récursifs :
recursiv = { recursiv = {
'fil' : [ 'fil' : ['138.231.136.98', '138.231.136.152'],
'138.231.136.98', 'wifi' : ['138.231.136.98', '138.231.136.152'],
'138.231.136.152', 'evenementiel' : ['138.231.136.98', '138.231.136.152'],
], 'adm' : ['10.231.136.98', '10.231.136.152'],
'wifi' : [ 'gratuit' : ['10.42.0.164'],
'138.231.136.98', 'accueil' : ['10.51.0.10'],
'138.231.136.152', 'isolement' : ['10.52.0.10'],
], 'personnel-ens' : ['10.2.9.10', '138.231.136.98', '138.231.136.152'],
'evenementiel' : [
'138.231.136.98',
'138.231.136.152',
],
'adm' : [
'10.231.136.98',
'10.231.136.152',
],
'gratuit' : [
'10.42.0.164',
],
'accueil' : [
'10.51.0.10',
],
'isolement' : [
'10.52.0.10',
],
'personnel-ens' : [
'10.2.9.10',
'138.231.136.98',
'138.231.136.152',
],
'federez' : [
'138.231.136.98',
'138.231.136.152',
],
} }
#: Domaines correspondant à des mails crans
mail_crans = [
'crans.org',
'crans.fr',
'crans.eu',
'crans.ens-cachan.fr',
]
#: Les ip/net des vlans limité vue par les récursifs #: Les ip/net des vlans limité vue par les récursifs
menteur_clients = [ menteur_clients = [ "138.231.136.210", "138.231.136.10" ] + config.prefix['evenementiel']
"138.231.136.210",
"138.231.136.10",
] + config.prefix['evenementiel']
# Chemins de fichiers/dossiers utiles.
DNS_DIR = '/etc/bind/generated/'
DNSSEC_DIR = '/etc/bind/signed/'
# Fichier de définition des zones pour le maître
DNS_CONF = os.path.join(DNS_DIR, 'zones_crans')
# Fichier de définition des zones pour les esclaves géré par BCfg2
DNS_CONF_BCFG2 = "/var/lib/bcfg2/Cfg/etc/bind/generated/zones_crans/zones_crans"

View file

@ -4,4 +4,3 @@ import sys
in_encoding = getattr(sys.stdin, 'encoding', None) or "UTF-8" in_encoding = getattr(sys.stdin, 'encoding', None) or "UTF-8"
out_encoding = getattr(sys.stdout, 'encoding', None) or "UTF-8" out_encoding = getattr(sys.stdout, 'encoding', None) or "UTF-8"
ldap_encoding = "UTF-8"

View file

@ -1,67 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Déclaration des items accessibles à la vente (prix coûtant) et générant
une facture.
items est un dictionnaire, dont chaque entrée est composée d'un dictionnaire
ayant une désignation, un prix unitaire, et indique si l'item n'est accessible
qu'aux imprimeurs (par défaut, non)."""
# Les clef sont un code article # Les clef sont un code article
ITEMS = { items = {
'CABLE': { 'CABLE' : {'designation': u'Cable Ethernet 5m', 'pu': 3, 'imprimeur': False},
'designation': u'Cable Ethernet 5m', 'ADAPTATEUR' : {'designation': u'Adaptateur Ethernet/USB', 'pu': 17, 'imprimeur': False},
'pu': 3., 'RELIURE': {'designation': u'Reliure plastique', 'pu': 0.12, 'imprimeur': False},
}, 'SOLDE':{'designation': u'Rechargement du solde', 'pu':'*', 'imprimeur': False},
'ADAPTATEUR_TrendNet': {
'designation': u'Adaptateur 10/100 Ethernet/USB-2',
'pu': 17.,
},
'ADAPTATEUR_UGreen': {
'designation': u'Adaptateur 10/100/1000 Ethernet/USB-3',
'pu': 14.,
},
'RELIURE': {
'designation': u'Reliure plastique',
'pu': 0.12,
},
'PULL_ZIP_MARK': {
'designation': u'Zipper marqué',
'pu': 39.18,
},
'PULL_ZIP': {
'designation': u'Zipper non marqué',
'pu': 35.8,
},
'PULL_MARK': {
'designation': u'Capuche marqué',
'pu': 32.28,
},
'PULL': {
'designation': u'Capuche non marqué',
'pu': 28.92,
},
}
# Utilisé par gest_crans_lc, contient également le rachargement de solde
ITEM_SOLDE = {'SOLDE': {'designation': u'Rechargement de solde', 'pu': u'*'}}
# Dico avec les modes de paiement pour modification du solde
SOLDE = {
'liquide' : u'Espèces',
'cheque' : u'Chèque',
'carte': u'Carte bancaire',
'note': u'Note Kfet',
'arbitraire': u'Modification arbitraire du solde',
}
# Dico avec les modes de paiement pour la vente
VENTE = {
'liquide' : u'Espèces',
'cheque' : u'Chèque',
'carte': u'Carte bancaire',
'note': u'Note Kfet',
'solde': u'Vente à partir du Solde',
} }

View file

@ -21,7 +21,6 @@ dev = {
'wifi' : 'crans.3', 'wifi' : 'crans.3',
'fil' : 'crans', 'fil' : 'crans',
'app' : 'crans.21', 'app' : 'crans.21',
'federez' : 'crans.22',
'adm' : 'crans.2', 'adm' : 'crans.2',
'tun-soyouz' : 'tun-soyouz' 'tun-soyouz' : 'tun-soyouz'
}, },
@ -53,20 +52,12 @@ mask = [24]
now=datetime.datetime.now() now=datetime.datetime.now()
if now.hour >= 6 and now.hour < 19 and now.weekday() < 5 and not is_ferie(): if now.hour >= 6 and now.hour < 19 and now.weekday() < 5 and not is_ferie():
#: Débit maximal autorisé #: Débit maximal autorisé
debit_max = { 'total' : 250, debit_max = 150 # mbits per second en connexion de jour
'out' : 250,
'wifi' : 100,
'fil' : 150 }
# mbits per second en connexion de jour
#: Est-ce qu'on est en connexion de jour ou de nuit/week-end ? #: Est-ce qu'on est en connexion de jour ou de nuit/week-end ?
debit_jour = True debit_jour = True
else: else:
#: Débit maximal autorisé #: Débit maximal autorisé
debit_max = { 'total' : 600, debit_max = 500 # mbits per second en conn de nuit et du week-end
'out' : 600,
'wifi' : 150,
'fil' : 450 }
# mbits per second en conn de nuit et du week-end
#: Est-ce qu'on est en connexion de jour ou de nuit/week-end ? #: Est-ce qu'on est en connexion de jour ou de nuit/week-end ?
debit_jour = False debit_jour = False
@ -76,12 +67,9 @@ bl_upload_debit_max = 60 #kbytes per second
# Débit pour upload des gens en appartement ens # Débit pour upload des gens en appartement ens
appt_upload_max = 1 # mbytes per second appt_upload_max = 1 # mbytes per second
# Debit pour l'upload de federez-wifi
federez_upload_max = 10 #mbytes per second
# Debit appartement down max # Debit appartement down max
# TODO : mettre en place dans komaz.py # TODO : mettre en place dans komaz.py
appt_download_max = debit_max['total']/10 appt_download_max = debit_max/10
#: Liste des réseaux non routables #: Liste des réseaux non routables
reseaux_non_routables = [ '10.0.0.0/8', '172.16.0.0/12','198.18.0.0/15', reseaux_non_routables = [ '10.0.0.0/8', '172.16.0.0/12','198.18.0.0/15',

View file

@ -0,0 +1,32 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" Templates des mails envoyés en cas d'upload.
TODO: à migrer dans /usr/scripts/gestion/mail/templates
"""
#: Envoyé à la ML disconnect@ en cas de dépassement de la limite soft (désactivé)
message_disconnect_soft = u"""From: %(from)s
To: %(to)s
Subject: %(proprio)s uploade
Content-Type: text/plain; charset="utf-8"
%(proprio)s uploade actuellement %(upload)s Mio.
--\u0020
Message créé par deconnexion.py"""
#: Envoyé à la ML disconnect@ en cas de dépassement de la limite hard plusieurs fois
message_disconnect_multi = u"""From: %(from)s
To: %(to)s
Subject: %(proprio)s a =?utf-8?q?=C3=A9t=C3=A9_brid=C3=A9?= %(nbdeco)d fois pour upload en un mois !
Content-Type: text/plain; charset="utf-8"
L'adhérent %(proprio)s a été bridé %(nbdeco)d fois pour upload en un mois !
Le PS a été généré et se trouve sur zamok :
%(ps)s
--\u0020
Message créé par deconnexion.py"""

View file

@ -1,11 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import print_function
import sys
try:
from .services_etc import services
except ImportError:
print("Cannot import /etc/crans/services.py, continuing empty", file=sys.stderr)
services = {}

1
gestion/config/services.py Symbolic link
View file

@ -0,0 +1 @@
/etc/crans/services.py

View file

@ -1 +0,0 @@
/etc/crans/services.py

View file

@ -1,10 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
PRELOAD_MIBS = (
"STATISTICS-MIB",
"SNMPv2-SMI",
"SNMPv2-MIB",
"IF-MIB",
"CONFIG-MIB",
)

View file

@ -5,9 +5,8 @@
# License : GPLv3 # License : GPLv3
import itertools import itertools
import os
debug = (int(os.environ.get('DBG_TRIGGER', 0)) == 1) or True debug = True
log_level = "info" log_level = "info"
# Serveur maître # Serveur maître
@ -16,30 +15,24 @@ user = "trigger"
port = 5671 port = 5671
ssl = True ssl = True
# TTL en secondes pour les messages en attente.
# Une suite d'opérations a faire a un ob_id, qui est un hash.
# Quand cette suite traîne depuis trop longtemps en attente sans que rien
# ne se passe, on la jette.
MSG_TTL = 3600
# Liste des services associés aux hôtes # Liste des services associés aux hôtes
# useradd : Envoie le mail de bienvenue, et crée le home # useradd : Envoie le mail de bienvenue, et crée le home
# userdel : Détruit le home, déconnecte l'utilisateur sur zamok, détruit les indexes dovecot, désinscrit l'adresse crans des mailing listes associées # userdel : Détruit le home, déconnecte l'utilisateur sur zamok, détruit les indexes dovecot, désinscrit l'adresse crans des mailing listes associées
services = { services = {
'civet' : ["event", "ack"], 'civet' : ["event"],
'dhcp' : ["dhcp"], 'dhcp' : ["dhcp"],
'dyson' : ["autostatus"], 'dyson' : ["autostatus"],
'isc' : ["dhcp"], 'isc' : ["dhcp"],
'odlyd' : ["firewall", "secours"], 'komaz' : ["firewall", "secours"],
'owl' : ["users"], 'owl' : ["userdel"],
'redisdead' : ["mailman", "modif_ldap", "solde", "users", "secours"], 'redisdead' : ["mailman", "modif_ldap", "solde", "userdel", "secours"],
'sable' : ["dns"], 'sable' : ["dns"],
'titanic' : ["secours"], 'titanic' : ["secours"],
'zamok' : ["users"], 'zamok' : ["userdel"],
'zbee' : ["users"], 'zbee' : ["useradd", "userdel"],
} }
# XXX - Uncomment this when in prod # XXX - Uncomment this when in prod
#all_services = set([service for service in itertools.chain(*services.values())]) #all_services = set([service for service in itertools.chain(*services.values())])
all_services = ['dhcp', 'firewall', 'secours'] all_services = ['dhcp', 'firewall']

View file

@ -3,22 +3,10 @@
""" Définitions des variables pour le contrôle d'upload. """ """ Définitions des variables pour le contrôle d'upload. """
#: Intervalle en heures pour le comptage
interval = 24
#: liste des exemptions générales #: liste des exemptions générales
exempt = [ ['138.231.136.0/21', '138.231.0.0/16'], exempt = [ ['138.231.136.0/21', '138.231.0.0/16'],
['138.231.148.0/22', '138.231.0.0/16'] ] ['138.231.148.0/22', '138.231.0.0/16'] ]
#: Limite en nombre de lignes pour analyse2
analyse_limit = "3000"
#: Template fichier d'analyse
analyse_file_tpl = "/usr/scripts/var/analyse/%s_%s_%s.txt"
#: Période de surveillance pour le max de décos
periode_watch = 30 * 86400
#: limite soft #: limite soft
soft = 1024 # Mio/24h glissantes soft = 1024 # Mio/24h glissantes
@ -28,6 +16,11 @@ hard = 8192 # Mio/24h glissantes
#: max déconnexions #: max déconnexions
max_decos = 7 max_decos = 7
#: envoyer des mails à disconnect@ en cas de dépassement soft ?
disconnect_mail_soft = False
#: envoyer des mails à disconnect@ en cas de dépassement hard ?
disconnect_mail_hard = True
#: expéditeur des mails de déconnexion #: expéditeur des mails de déconnexion
expediteur = "disconnect@crans.org" expediteur = "disconnect@crans.org"

View file

@ -19,7 +19,7 @@ if '/usr/scripts' not in sys.path:
from pythondialog import Dialog as PythonDialog from pythondialog import Dialog as PythonDialog
from pythondialog import DialogTerminatedBySignal, PythonDialogErrorBeforeExecInChildProcess from pythondialog import DialogTerminatedBySignal, PythonDialogErrorBeforeExecInChildProcess
from pythondialog import error as DialogError from pythondialog import error as DialogError
from gestion import affichage from gestion.affich_tools import get_screen_size, coul
debug_enable = False debug_enable = False
debugf = None debugf = None
@ -203,7 +203,7 @@ class Dialog(object):
setattr(self, attr, ret) setattr(self, attr, ret)
return ret return ret
def __init__(self, debug_enable=False, dialogrc=False): def __init__(self, debug_enable=False):
signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGINT, signal.SIG_IGN)
self.debug_enable = debug_enable self.debug_enable = debug_enable
@ -211,7 +211,6 @@ class Dialog(object):
# On met un timeout à 10min d'innactivité sur dialog # On met un timeout à 10min d'innactivité sur dialog
self.timeout = 600 self.timeout = 600
self.error_to_raise = (Continue, DialogError, ldap.SERVER_DOWN) self.error_to_raise = (Continue, DialogError, ldap.SERVER_DOWN)
self.dialogrc = dialogrc
_dialog = None _dialog = None
@property @property
@ -219,9 +218,7 @@ class Dialog(object):
""" """
Renvois l'objet dialog. Renvois l'objet dialog.
""" """
if self.dialogrc: if self._dialog is None:
self._dialog = PythonDialog(DIALOGRC=self.dialogrc)
else:
self._dialog = PythonDialog() self._dialog = PythonDialog()
self.dialog_last_access = time.time() self.dialog_last_access = time.time()
return self._dialog return self._dialog
@ -230,7 +227,7 @@ class Dialog(object):
""" """
Nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan Nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan
""" """
(cols, lines) = affichage.getTerminalSize() (lines, cols) = get_screen_size()
print "\033[48;5;17m" print "\033[48;5;17m"
print " "*(lines * cols) print " "*(lines * cols)
cols = int(min(cols/2, 65)) cols = int(min(cols/2, 65))

View file

@ -9,8 +9,6 @@ Licence : GPLv3
import sys import sys
import time import time
import datetime import datetime
import subprocess
import pytz
import dateutil.relativedelta import dateutil.relativedelta
if '/usr/scripts' not in sys.path: if '/usr/scripts' not in sys.path:
sys.path.append('/usr/scripts') sys.path.append('/usr/scripts')
@ -20,7 +18,6 @@ import config.cotisation
import lc_ldap.objets as objets import lc_ldap.objets as objets
import lc_ldap.attributs as attributs import lc_ldap.attributs as attributs
from lc_ldap.attributs import UniquenessError from lc_ldap.attributs import UniquenessError
from lc_ldap import crans_utils
import proprio import proprio
from CPS import TailCall, tailcaller, Continue from CPS import TailCall, tailcaller, Continue
@ -48,20 +45,20 @@ class Dialog(proprio.Dialog):
'GPGFingerprint' : [a.nounou, a.soi], 'GPGFingerprint' : [a.nounou, a.soi],
'Remarques' : [a.cableur, a.nounou], 'Remarques' : [a.cableur, a.nounou],
'Droits':[a.nounou, a.bureau], 'Droits':[a.nounou, a.bureau],
'Blackliste':[a.bureau, a.nounou], 'Blackliste':[a.cableur, a.nounou],
'Vente':[a.cableur, a.nounou], 'Vente':[a.cableur, a.nounou],
'Supprimer':[a.nounou, a.bureau], 'Supprimer':[a.nounou, a.bureau],
} }
menu = { menu = {
'Administratif' : {'text' : "Adhésion, chartes", "callback":self.adherent_administratif}, 'Administratif' : {'text' : "Adhésion, carte étudiant, chartes", "callback":self.adherent_administratif},
'Personnel' : {'text' : "Nom, prénom, téléphone, et mail de contact", 'callback':self.adherent_personnel}, 'Personnel' : {'text' : "Nom, prénom, téléphone... (ajouter l'age ?)", 'callback':self.adherent_personnel},
'Études' : {'text' : "Étude en cours", "callback":self.adherent_etudes}, 'Études' : {'text' : "Étude en cours", "callback":self.adherent_etudes},
'Chambre' : {'text' : 'Déménagement', "callback":self.adherent_chambre}, 'Chambre' : {'text' : 'Déménagement', "callback":self.adherent_chambre},
'Compte' : {'text' : "Gestion du compte crans", "adherent":"proprio", "callback":TailCall(self.proprio_compte, update_obj='adherent'), 'help':"Création/Suppression/Activation/Désactivation du compte, gestion des alias mails crans du compte"}, 'Compte' : {'text' : "Gestion du compte crans", "adherent":"proprio", "callback":TailCall(self.proprio_compte, update_obj='adherent'), 'help':"Création/Suppression/Activation/Désactivation du compte, gestion des alias mails crans du compte"},
'GPGFingerprint' : {'text':'Ajouter ou supprimer une empeinte GPG', 'attribut':attributs.gpgFingerprint}, 'GPGFingerprint' : {'text':'Ajouter ou supprimer une empeinte GPG', 'attribut':attributs.gpgFingerprint},
'Remarques' : {'text':'Ajouter ou supprimer une remarque à cet adhérent', 'attribut':attributs.info}, 'Remarques' : {'text':'Ajouter ou supprimer une remarque de la machine', 'attribut':attributs.info},
'Droits' : {'text':"Modifier les droits alloués à cet adhérent", "callback":self.adherent_droits}, 'Droits' : {'text':"Modifier les droits alloués à cet adhérent", "callback":self.adherent_droits},
'Blackliste' : {'text': 'Modifier les blacklist de cet adhérent', 'callback':self.modif_adherent_blacklist}, 'Blackliste' : {'text': 'Modifier les blacklist de la machine', 'callback':self.modif_adherent_blacklist},
'Vente' : {'text':"Chargement solde crans, vente de cable ou adaptateur ethernet ou autre", "adherent":"proprio", "callback":self.proprio_vente}, 'Vente' : {'text':"Chargement solde crans, vente de cable ou adaptateur ethernet ou autre", "adherent":"proprio", "callback":self.proprio_vente},
'Supprimer' : {'text':"Supprimer l'adhérent de la base de donnée", 'callback':TailCall(self.delete_adherent, del_cont=cont(proprio=None))}, 'Supprimer' : {'text':"Supprimer l'adhérent de la base de donnée", 'callback':TailCall(self.delete_adherent, del_cont=cont(proprio=None))},
} }
@ -114,10 +111,6 @@ class Dialog(proprio.Dialog):
codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, cont_ret])] codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, cont_ret])]
) )
def modif_adherent_attributs(self, adherent, attr, cont):
"""Juste un raccourci vers edit_attributs spécifique aux adherents"""
return self.edit_attributs(obj=adherent, update_obj='adherent', attr=attr, title="Modification de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), cont=cont)
def adherent_administratif(self, cont, adherent, default_item=None): def adherent_administratif(self, cont, adherent, default_item=None):
"""Menu de gestion du compte crans d'un proprio""" """Menu de gestion du compte crans d'un proprio"""
@ -126,13 +119,17 @@ class Dialog(proprio.Dialog):
"Adhésion": [a.cableur, a.nounou], "Adhésion": [a.cableur, a.nounou],
'Connexion': [a.cableur, a.nounou], 'Connexion': [a.cableur, a.nounou],
"Charte MA" : [a.nounou, a.bureau], "Charte MA" : [a.nounou, a.bureau],
"Carte Étudiant" : [a.nounou, a.cableur, a.tresorier],
} }
menu = { menu = {
"Adhésion" : {"text":"Pour toute réadhésion *sans* connexion.", "help":"", "callback":self.adherent_adhesion}, "Adhésion" : {"text":"Pour toute réadhésion *sans* connexion.", "help":"", "callback":self.adherent_adhesion},
'Connexion' : {'text': "Mise à jour de l'accès Internet (effectue la réadhésion si besoin)", "help":"", 'callback':self.adherent_connexion}, 'Connexion' : {'text': "Mise à jour de l'accès Internet (effectue la réadhésion si besoin)", "help":"", 'callback':self.adherent_connexion},
"Carte Étudiant" : {"text" : "Validation de la carte étudiant", "help":"", "callback":self.adherent_carte_etudiant},
"Charte MA" : {"text" : "Signature de la charte des membres actifs", "help":'', "callback":self.adherent_charte}, "Charte MA" : {"text" : "Signature de la charte des membres actifs", "help":'', "callback":self.adherent_charte},
} }
menu_order = ["Adhésion", 'Connexion'] menu_order = ["Adhésion", 'Connexion']
if self.has_right(a.tresorier, adherent) or not adherent.carte_controle():
menu_order.append("Carte Étudiant")
menu_order.append("Charte MA") menu_order.append("Charte MA")
def box(default_item=None): def box(default_item=None):
return self.dialog.menu( return self.dialog.menu(
@ -220,7 +217,8 @@ class Dialog(proprio.Dialog):
# Boite si on ne peux pas réahdérer # Boite si on ne peux pas réahdérer
def box_already(end): def box_already(end):
self.dialog.msgbox("Actuellement adhérent jusqu'au %s.\nMerci de revenir lorsqu'il restera moins de %s jours avant la fin." % (end, config.cotisation.delai_readh_jour), t_end = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(end))
self.dialog.msgbox("Actuellement adhérent jusqu'au %s.\nMerci de revenir lorsqu'il restera moins de %s jours avant la fin." % (t_end, config.cotisation.delai_readh_jour),
width=0, width=0,
height=0, height=0,
timeout=self.timeout, timeout=self.timeout,
@ -228,8 +226,9 @@ class Dialog(proprio.Dialog):
# Boite de confirmation à l'ahésion # Boite de confirmation à l'ahésion
def box_adherer(end=None): def box_adherer(end=None):
if end != crans_utils.localized_datetime(): if end:
adherer = self.confirm(text="Adhésion jusqu'au %s. Réadhérer ?" % end, title="Adhésion de %s %s" % (adherent.get("prenom", [''])[0], adherent["nom"][0])) t_end = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(end))
adherer = self.confirm(text="Adhésion jusqu'au %s. Réadhérer ?" % t_end, title="Adhésion de %s %s" % (adherent.get("prenom", [''])[0], adherent["nom"][0]))
else: else:
adherer = self.confirm(text="Adhésion pour un an, continuer ?", title="Adhésion de %s %s" % (adherent.get("prenom", [''])[0], adherent["nom"][0])) adherer = self.confirm(text="Adhésion pour un an, continuer ?", title="Adhésion de %s %s" % (adherent.get("prenom", [''])[0], adherent["nom"][0]))
return adherer return adherer
@ -243,8 +242,9 @@ class Dialog(proprio.Dialog):
# Génération de la facture pour adhésion # Génération de la facture pour adhésion
def paiement(tag_paiement, adherent, finadhesion, comment, facture, cancel_cont, cont): def paiement(tag_paiement, adherent, finadhesion, comment, facture, cancel_cont, cont):
now = crans_utils.localized_datetime() now = time.time()
new_finadhesion = max(finadhesion, now).replace(year=max(finadhesion, now).year + 1) new_finadhesion = datetime.datetime.fromtimestamp(max(finadhesion, now))
new_finadhesion = time.mktime(new_finadhesion.replace(year=new_finadhesion.year + config.cotisation.duree_adh_an).timetuple()) + 86400
new_debutadhesion = now new_debutadhesion = now
if facture: if facture:
facture = self.conn.search(dn=facture.dn, scope=0, mode='rw')[0] facture = self.conn.search(dn=facture.dn, scope=0, mode='rw')[0]
@ -256,8 +256,8 @@ class Dialog(proprio.Dialog):
facture['modePaiement'] = unicode(tag_paiement, 'utf-8') facture['modePaiement'] = unicode(tag_paiement, 'utf-8')
facture['info'] = unicode(comment, 'utf-8') facture['info'] = unicode(comment, 'utf-8')
facture['article'].append(config.cotisation.dico_adh) facture['article'].append(config.cotisation.dico_adh)
facture["finAdhesion"] = new_finadhesion facture["finAdhesion"] = unicode(new_finadhesion)
facture["debutAdhesion"] = new_debutadhesion facture["debutAdhesion"] = unicode(new_debutadhesion)
# On peut retarder le credit pour ajouter des contribution pour la connexion internet à la facture # On peut retarder le credit pour ajouter des contribution pour la connexion internet à la facture
if crediter: if crediter:
if self.confirm_item(item=facture, if self.confirm_item(item=facture,
@ -280,13 +280,9 @@ class Dialog(proprio.Dialog):
raise Continue(cont(adherent=adherent)) raise Continue(cont(adherent=adherent))
now = crans_utils.localized_datetime() finadhesion = adherent.fin_adhesion()
try:
finadhesion = adherent.fin_adhesion().value
except AttributeError:
finadhesion = now
# Si fin de l'adhésion trop loin dans le futur, rien a faire # Si fin de l'adhésion trop loin dans le futur, rien a faire
if finadhesion and (finadhesion - now).days > config.cotisation.delai_readh_jour: if finadhesion and finadhesion - config.cotisation.delai_readh > time.time():
self.handle_dialog(cancel_cont if cancel_cont else cont, box_already, finadhesion) self.handle_dialog(cancel_cont if cancel_cont else cont, box_already, finadhesion)
raise Continue(cancel_cont if cancel_cont else cont) raise Continue(cancel_cont if cancel_cont else cont)
@ -340,9 +336,9 @@ class Dialog(proprio.Dialog):
# Une boite pour choisir un nombre de mois pour prolonger la connexion # Une boite pour choisir un nombre de mois pour prolonger la connexion
def box(finconnexion, default_item=None): def box(finconnexion, default_item=None):
t_end = finconnexion t_end = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(finconnexion))
return self.dialog.menu( return self.dialog.menu(
"Connexion jusqu'au %s" % t_end if finconnexion != datetime.datetime.fromtimestamp(0, tz=pytz.utc) else "N'a jamais été connecté", "Connexion jusqu'au %s" % t_end if finconnexion else "N'a jamais été connecté",
width=0, width=0,
height=0, height=0,
menu_height=0, menu_height=0,
@ -357,16 +353,17 @@ class Dialog(proprio.Dialog):
# Génération et crédit de la facture # Génération et crédit de la facture
def todo(adherent, mois, finadhesion, finconnexion, cancel_cont, cont, facture=None, tag_paiment=None, comment=None): def todo(adherent, mois, finadhesion, finconnexion, cancel_cont, cont, facture=None, tag_paiment=None, comment=None):
now = crans_utils.localized_datetime() now = time.time()
new_finconnexion = datetime.datetime.fromtimestamp(max(finconnexion, now))
# On ajoute 3600 secondes sur suggestion de Raphaël Bonaque (<bonaque@crans.org>), pour tenir compte des malheureux qui
# pourraient subir le changement d'heure.
new_finconnexion = time.mktime((new_finconnexion + dateutil.relativedelta.relativedelta(months=mois)).timetuple()) + 3600
new_debutconnexion = max(now, finconnexion) new_debutconnexion = max(now, finconnexion)
con_month = new_debutconnexion.month
con_year = new_debutconnexion.year
new_finconnexion = max(finconnexion, now).replace(year=con_year + ((con_month + mois) // 13), month= (con_month + mois - 1) % 12 + 1)
if (new_finconnexion - finadhesion.value).days > 0: if new_finconnexion > finadhesion:
t_end_adh = finadhesion.value t_end_adh = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(finadhesion))
t_end_conn = finconnexion t_end_conn = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(new_finconnexion))
if (new_finconnexion - finadhesion.value).days > 30: if new_finconnexion - finadhesion > 30 * 3600 * 24:
raise ValueError("Impossible de prolonger la connexion jusqu'au %s plus d'un mois après la fin de l'adhésion au %s" % (t_end_conn, t_end_adh)) raise ValueError("Impossible de prolonger la connexion jusqu'au %s plus d'un mois après la fin de l'adhésion au %s" % (t_end_conn, t_end_adh))
else: else:
if not self.confirm("La fin de la connexion de l'adhérent (%s) tombera après la fin de son adhésion (%s).\n" \ if not self.confirm("La fin de la connexion de l'adhérent (%s) tombera après la fin de son adhésion (%s).\n" \
@ -376,8 +373,8 @@ class Dialog(proprio.Dialog):
if facture: if facture:
with self.conn.search(dn=facture.dn, scope=0, mode='rw')[0] as facture: with self.conn.search(dn=facture.dn, scope=0, mode='rw')[0] as facture:
if mois: if mois:
facture["finConnexion"] = new_finconnexion facture["finConnexion"] = unicode(new_finconnexion)
facture["debutConnexion"] = new_debutconnexion facture["debutConnexion"] = unicode(new_debutconnexion)
facture["article"].append(config.cotisation.dico_cotis(mois)) facture["article"].append(config.cotisation.dico_cotis(mois))
if self.confirm_item(item=facture, if self.confirm_item(item=facture,
text=u"Le paiement de %sEUR a-t-il bien été reçu (mode : %s) ?\n" % (facture.total(), facture['modePaiement'][0]), text=u"Le paiement de %sEUR a-t-il bien été reçu (mode : %s) ?\n" % (facture.total(), facture['modePaiement'][0]),
@ -400,8 +397,8 @@ class Dialog(proprio.Dialog):
facture['modePaiement'] = unicode(tag_paiment, 'utf-8') facture['modePaiement'] = unicode(tag_paiment, 'utf-8')
facture['article'].append(config.cotisation.dico_cotis(mois)) facture['article'].append(config.cotisation.dico_cotis(mois))
facture['info'] = unicode(comment, 'utf-8') facture['info'] = unicode(comment, 'utf-8')
facture["finConnexion"] = new_finconnexion facture["finConnexion"] = unicode(new_finconnexion)
facture["debutConnexion"] = new_debutconnexion facture["debutConnexion"] = unicode(new_debutconnexion)
if self.confirm_item(item=facture, if self.confirm_item(item=facture,
text=u"Le paiement de %sEUR a-t-il bien été reçu (mode : %s) ?\n" % (facture.total(), tag_paiment), text=u"Le paiement de %sEUR a-t-il bien été reçu (mode : %s) ?\n" % (facture.total(), tag_paiment),
title=u"Validation du paiement", title=u"Validation du paiement",
@ -411,7 +408,7 @@ class Dialog(proprio.Dialog):
else: else:
if not self.confirm(text=u"Le paiement n'a pas été reçue.\n Annuler ?", title="Annulation de l'adhésion", defaultno=True): if not self.confirm(text=u"Le paiement n'a pas été reçue.\n Annuler ?", title="Annulation de l'adhésion", defaultno=True):
raise Continue(cancel_cont) raise Continue(cancel_cont)
raise Continue(cont) raise Continue(cont(adherent=adherent))
def todo_mois(tag, self_cont): def todo_mois(tag, self_cont):
if tag == 'An': if tag == 'An':
@ -433,18 +430,16 @@ class Dialog(proprio.Dialog):
finconnexion = adherent.fin_connexion() finconnexion = adherent.fin_connexion()
# Si l'adhésion fini avant la connexion # Si l'adhésion fini avant la connexion
if finadhesion <= crans_utils.localized_datetime() or finadhesion <= finconnexion: if finadhesion <= time.time() or finadhesion <= finconnexion:
if finadhesion: if finadhesion:
t_end_adh = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(finadhesion))
# Si l'adhésion est déjà fini # Si l'adhésion est déjà fini
if finadhesion <= crans_utils.localized_datetime(): if finadhesion <= time.time():
if finadhesion == datetime.datetime.fromtimestamp(0, tz=pytz.utc): self.dialog.msgbox(text=u"L'adhésion a expiré le %s, il va falloir réadhérer d'abord" % t_end_adh, title="Réadhésion nécessaire", width=0, height=0, timeout=self.timeout)
self.dialog.msgbox(text=u"L'adhérent n'a jamais adhéré à l'association, on va d'abord le faire adhérer (10€)", title="Adhésion nécessaire", width=0, height=0, timeout=self.timeout)
else:
self.dialog.msgbox(text=u"L'adhésion a expiré le %s, il va falloir réadhérer d'abord (10€)" % finadhesion, title="Réadhésion nécessaire", width=0, height=0, timeout=self.timeout)
# Sinon si elle fini avant la fin de la connexion courante # Sinon si elle fini avant la fin de la connexion courante
elif finadhesion < finconnexion: elif finadhesion < finconnexion:
t_end_conn = finconnexion t_end_conn = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(finconnexion))
self.dialog.msgbox(text=u"L'adhésion de termine le %s, avant la fin de la connexion le %s, il va falloir réadhérer d'abord (10€)" % (finadhesion, t_end_conn), title="Réadhésion nécessaire", width=0, height=0, timeout=self.timeout) self.dialog.msgbox(text=u"L'adhésion de termine le %s, avant la fin de la connexion le %s, il va falloir réadhérer d'abord" % (t_end_adh, t_end_conn), title="Réadhésion nécessaire", width=0, height=0, timeout=self.timeout)
# Échouera si on essaie de prolonger la connexion au dela de l'adhésion et que l'adhésion est encore valable plus de quinze jours # Échouera si on essaie de prolonger la connexion au dela de l'adhésion et que l'adhésion est encore valable plus de quinze jours
return self.adherent_adhesion(cont=self_cont, cancel_cont=cont, adherent=adherent, crediter=False) return self.adherent_adhesion(cont=self_cont, cancel_cont=cont, adherent=adherent, crediter=False)
@ -484,6 +479,76 @@ class Dialog(proprio.Dialog):
return self.proprio_choose_paiement(proprio=adherent, cont=self_cont, cancel_cont=lcont) return self.proprio_choose_paiement(proprio=adherent, cont=self_cont, cancel_cont=lcont)
return cont return cont
def adherent_carte_etudiant(self, cont, adherent, values={}, cancel_cont=None):
# Dictionnaire décrivant quelle est la valeur booléenne à donner à l'absence de l'attribut
a = attributs
choices = []
if self.has_right(a.tresorier, adherent) or not adherent.carte_controle():
choices.append((a.carteEtudiant.ldap_name, "Carte étudiant présentée", 1 if adherent[a.carteEtudiant.ldap_name] or values.get(a.carteEtudiant.ldap_name, False) else 0))
if self.has_right(a.tresorier, adherent):
choices.append(("controleCarte", "La carte a-t-elle été controlée", 1 if adherent.carte_controle() or values.get("controleCarte", False) else 0))
if not choices:
self.dialog.msgbox("Carte d'étudiant déjà validée et non modifiable", title="Gestion de la carte étudiant", width=0, height=0)
if cancel_cont:
cancel_cont(cont=cont)
try:
cont(cancel_cont=cancel_cont)
except TypeError:
pass
raise Continue(cont)
def box():
return self.dialog.checklist("Gestion de la carte étudiant",
height=0,
width=0,
timeout=self.timeout,
list_height=7,
choices=choices,
title="Gestion de la carte étudiant")
def todo(values, adherent, cont):
# On met à jour chaque attribut si sa valeur à changé
with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
# Si on est trésorier et que controleCarte a changer on enregistre le changement
if self.has_right(a.tresorier, adherent) and values["controleCarte"] and not adherent.carte_controle():
if adherent["controle"]:
adherent["controle"] = u"c%s" % adherent["controle"][0]
else:
adherent["controle"] = u"c"
elif self.has_right(a.tresorier, adherent) and not values["controleCarte"] and adherent.carte_controle():
adherent["controle"] = unicode(adherent["controle"][0]).replace('c','')
if not adherent["controle"][0]:
adherent["controle"] = []
# Si la carte n'est pas validé ou qu'on est trésorier, on sauvegarde les changements
if values[a.carteEtudiant.ldap_name] and not adherent[a.carteEtudiant.ldap_name] and (not adherent.carte_controle() or self.has_right(a.tresorier, adherent)):
adherent[a.carteEtudiant.ldap_name] = u"TRUE"
elif not values[a.carteEtudiant.ldap_name] and adherent[a.carteEtudiant.ldap_name] and (not adherent.carte_controle() or self.has_right(a.tresorier, adherent)):
adherent[a.carteEtudiant.ldap_name] = []
if adherent["controle"]:
adherent["controle"] = unicode(adherent["controle"][0]).replace('c','')
if not adherent["controle"][0]:
adherent["controle"] = []
adherent.validate_changes()
adherent.history_gen()
adherent.save()
# On s'en va en mettant à jour dans la continuation la valeur de obj
raise Continue(cont(adherent=adherent))
(code, output) = self.handle_dialog(cont, box)
# On transforme la liste des cases dialog cochée en dictionnnaire
values = dict((a[0], a[0] in output) for a in choices)
# Une continuation que l'on suivra si quelque chose se passe mal
retry_cont = TailCall(self.adherent_carte_etudiant, adherent=adherent, cont=cont, values=values)
return self.handle_dialog_result(
code=code,
output=output,
cancel_cont=cancel_cont if cancel_cont else cont,
error_cont=retry_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [values, adherent, cont])]
)
def adherent_charte(self, cont, adherent): def adherent_charte(self, cont, adherent):
a = attributs a = attributs
attribs = [a.charteMA] attribs = [a.charteMA]
@ -547,15 +612,6 @@ class Dialog(proprio.Dialog):
with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
for (key, values) in attrs.items(): for (key, values) in attrs.items():
adherent[key] = values adherent[key] = values
# On retire les éventuelle bl mail invalide
if key == u'mailExt' or key == u'mail':
for bl in adherent['blacklist']:
now = int(time.time())
if bl['type'] == u'mail_invalide' and bl['fin'] > now:
bl['fin'] = now
if bl['debut'] > now:
bl['debut'] = now
bl['comm'] += u'- mail rectifié'
adherent.validate_changes() adherent.validate_changes()
adherent.history_gen() adherent.history_gen()
adherent.save() adherent.save()
@ -593,13 +649,6 @@ class Dialog(proprio.Dialog):
if self.confirm_item(adherent, title="Créer l'adhérent suivant ?"): if self.confirm_item(adherent, title="Créer l'adhérent suivant ?"):
adherent.validate_changes() adherent.validate_changes()
adherent.create() adherent.create()
if make_compte_crans:
if self.dialog.yesno("Imprimer un ticket avec un mot de passe attribué automatiquement ?",
title="Impression de ticket pour %s %s" % (adherent.get('prenom', [''])[0], adherent["nom"][0]),
timeout=self.timeout
) == self.dialog.DIALOG_OK:
subprocess.call(['/usr/scripts/cransticket/dump_creds.py', '--forced', '--pass', 'aid=%s' % adherent['aid'][0]])
self.display_item(adherent, "Impression du ticket en cours ...")
else: else:
adherent = None adherent = None
return adherent return adherent
@ -863,11 +912,13 @@ class Dialog(proprio.Dialog):
"""Crée un adhérent et potentiellement son compte crans avec lui""" """Crée un adhérent et potentiellement son compte crans avec lui"""
def mycont(adherent=None, **kwargs): def mycont(adherent=None, **kwargs):
if adherent: if adherent:
# Une fois l'adhérent créé, on vois s'il adhére/prend la connexion internet # Une fois l'adhérent créé, on vois s'il donne sa carte étudiant et s'il adhére/prend la connexion internet
#adh_cont = TailCall(self.modif_adherent, cont=cont, adherent=adherent) #adh_cont = TailCall(self.modif_adherent, cont=cont, adherent=adherent)
conn_cont = TailCall(self.adherent_connexion, cont=cont(proprio=adherent), adherent=adherent) conn_cont = TailCall(self.adherent_connexion, cont=cont(proprio=adherent), adherent=adherent)
etude_cont = TailCall(self.adherent_etudes, cont=conn_cont, adherent=adherent) carte_cont = TailCall(self.adherent_carte_etudiant, cont=conn_cont, adherent=adherent)
etude_cont = TailCall(self.adherent_etudes, cont=carte_cont, adherent=adherent)
etude_cont(cancel_cont=etude_cont) etude_cont(cancel_cont=etude_cont)
carte_cont(cancel_cont=etude_cont)
# Comme on crée une facture, pas de retour possible # Comme on crée une facture, pas de retour possible
conn_cont(cancel_cont=conn_cont) conn_cont(cancel_cont=conn_cont)
raise Continue(etude_cont) raise Continue(etude_cont)

View file

@ -13,7 +13,7 @@ import traceback
if '/usr/scripts' not in sys.path: if '/usr/scripts' not in sys.path:
sys.path.append('/usr/scripts') sys.path.append('/usr/scripts')
from gestion import affichage from gestion.affich_tools import coul
import gestion.config as config import gestion.config as config
import lc_ldap.objets as objets import lc_ldap.objets as objets
@ -37,13 +37,9 @@ class Dialog(lc.Dialog):
index = 0 index = 0
for bl in obj['blacklist']: for bl in obj['blacklist']:
choices.append( choices.append(
( (str(index),
str(index), coul("%s [%s]" % (bl['type'], bl['comm']), 'rouge' if bl['actif'] else None,
affichage.style( dialog=True)
"%s [%s]" % (bl['type'], bl['comm']),
'rouge' if bl['actif'] else None,
dialog=True
)
) )
) )
index+=1 index+=1
@ -149,7 +145,7 @@ class Dialog(lc.Dialog):
fin_tuple = self.get_timestamp(title=title, text="Choisir la date de fin :", fin_tuple = self.get_timestamp(title=title, text="Choisir la date de fin :",
cont=self_cont(bl=bl, tag=tag, bl_type=bl_type, cont=self_cont(bl=bl, tag=tag, bl_type=bl_type,
debut=None, fin=None, comm=None)) debut=None, fin=None, comm=None))
fin = int(time.mktime(time.struct_time(fin_tuple + (0, 0, -1)))) fin = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1))))
else: else:
fin = '-' fin = '-'
bl['debut']=debut bl['debut']=debut

View file

@ -6,18 +6,16 @@ Copyright (C) Valentin Samir
Licence : GPLv3 Licence : GPLv3
""" """
import os
import sys import sys
import time import time
import ldap import ldap
import traceback import traceback
import locale
if '/usr/scripts' not in sys.path: if '/usr/scripts' not in sys.path:
sys.path.append('/usr/scripts') sys.path.append('/usr/scripts')
from pythondialog import Dialog from pythondialog import Dialog
from pythondialog import error as DialogError from pythondialog import error as DialogError
from gestion import affichage from gestion.affich_tools import get_screen_size, coul
import lc_ldap.shortcuts import lc_ldap.shortcuts
import lc_ldap.objets as objets import lc_ldap.objets as objets
@ -29,17 +27,12 @@ from CPS import TailCall, tailcaller, Continue, TailCaller
class Dialog(CPS.Dialog): class Dialog(CPS.Dialog):
def __init__(self, debug_enable=False, ldap_test=False, custom_user=None): def __init__(self, debug_enable=False, ldap_test=False, custom_user=None):
super(Dialog, self).__init__() super(Dialog, self).__init__(debug_enable=debug_enable)
# On initialise le moteur de rendu en spécifiant qu'on va faire du dialog # On initialise le moteur de rendu en spécifiant qu'on va faire du dialog
printing.template(dialog=True) printing.template(dialog=True)
self.ldap_test = ldap_test self.ldap_test = ldap_test
if custom_user:
custom_user = custom_user.decode(locale.getdefaultlocale()[1] or "ascii")
self.custom_user = custom_user self.custom_user = custom_user
self.check_ldap() self.check_ldap()
login = self.conn.current_login
dialogrc='/home/%s/.dialogrc' % login
super(Dialog, self).__init__(debug_enable=debug_enable, dialogrc=dialogrc)
def has_right(self, liste, obj=None): def has_right(self, liste, obj=None):
"""Vérifie que l'un des droits de l'utilisateur courant est inclus dans list""" """Vérifie que l'un des droits de l'utilisateur courant est inclus dans list"""
@ -347,7 +340,7 @@ class Dialog(CPS.Dialog):
# pour prendre en compte la largeur du widget dialog # pour prendre en compte la largeur du widget dialog
del items[:] # On vide la liste pour la modifier en place del items[:] # On vide la liste pour la modifier en place
items_id = {} items_id = {}
(col, line) = affichage.getTerminalSize() (line, col) = get_screen_size()
for c in classes: for c in classes:
items.extend(olist[c]) items.extend(olist[c])
items_s = printing.sprint_list(olist[c], col-20).encode('utf-8').split('\n') items_s = printing.sprint_list(olist[c], col-20).encode('utf-8').split('\n')

View file

@ -12,7 +12,6 @@ if '/usr/scripts' not in sys.path:
import lc_ldap.objets as objets import lc_ldap.objets as objets
import lc_ldap.attributs as attributs import lc_ldap.attributs as attributs
import subprocess
import certificat import certificat
import blacklist import blacklist
@ -35,12 +34,10 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
""" """
a = attributs a = attributs
# Quel sont les attributs ldap dont on veut afficher et la taille du champs d'édition correspondant # Quel sont les attributs ldap dont on veut afficher et la taille du champs d'édition correspondant
to_display = [(a.host, 30), (a.macAddress, 17), (a.ipHostNumber, 15)] to_display = [(a.host, 30), (a.macAddress, 17), (a.ipHostNumber, 15),
(a.portTCPout, 50), (a.portTCPin, 50), (a.portUDPout, 50),
to_display_port = [(a.portTCPout, 50), (a.portTCPin, 50), (a.portUDPout, 50), (a.portUDPin, 50)
(a.portUDPin, 50)] ]
to_display_borne = [(a.canal, 10), (a.hotspot, 10), (a.puissance, 10), (a.positionBorne, 50), (a.nvram, 10)]
# Quel séparateur on utilise pour les champs multivalué # Quel séparateur on utilise pour les champs multivalué
separateur = ' ' separateur = ' '
@ -61,19 +58,15 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
title="Paramètres machine", title="Paramètres machine",
backtitle="Gestion des machines du Crans") backtitle="Gestion des machines du Crans")
def check_host(host, objectClass, realm): def check_host(host, objectClass):
# Si c'est une machine wifi, host doit finir par wifi.crans.org # Si c'est une machine wifi, host doit finir par wifi.crans.org
if "machineWifi" == objectClass or 'borneWifi' == objectClass or realm == 'bornes': if "machineWifi" == objectClass or 'borneWifi' == objectClass:
hostend = ".wifi.crans.org" hostend = ".wifi.crans.org"
# Si c'est une machine wifi, host doit finir par crans.org # Si c'est une machine wifi, host doit finir par crans.org
elif "machineFixe" == objectClass or realm == 'serveurs': elif "machineFixe" == objectClass:
hostend = ".crans.org" hostend = ".crans.org"
# Si l'object class est machineCrans, pas de vérification # Si l'object class est machineCrans, pas de vérification
elif "machineCrans" == objectClass: elif "machineCrans" == objectClass:
if realm == 'adm':
hostend = ".adm.crans.org"
if not '.' in host:
host = host + hostend
return host return host
# Sinon, libre à chachun d'ajouter d'autres objectClass ou de filtrer # Sinon, libre à chachun d'ajouter d'autres objectClass ou de filtrer
# plus finement fonction des droits de self.conn.droits # plus finement fonction des droits de self.conn.droits
@ -81,7 +74,7 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
raise ValueError("La machine n'est ni une machine fixe, ni une machine wifi mais %s ?!?" % objectClass) raise ValueError("La machine n'est ni une machine fixe, ni une machine wifi mais %s ?!?" % objectClass)
if not host.endswith(hostend) and not '.' in host: if not host.endswith(hostend) and not '.' in host:
host = host + hostend host = "%s.wifi.crans.org" % host
elif host.endswith(hostend) and '.' in host[:-len(hostend)]: elif host.endswith(hostend) and '.' in host[:-len(hostend)]:
raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend) raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend)
elif not host.endswith(hostend) and '.' in host: elif not host.endswith(hostend) and '.' in host:
@ -92,7 +85,6 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
def modif_machine(machine, attrs): def modif_machine(machine, attrs):
with self.conn.search(dn=machine.dn, scope=0, mode='rw')[0] as machine: with self.conn.search(dn=machine.dn, scope=0, mode='rw')[0] as machine:
for (key, values) in attrs.items(): for (key, values) in attrs.items():
if values!=u'<automatique>' or key != 'ipHostNumber':
machine[key]=values machine[key]=values
machine.validate_changes() machine.validate_changes()
machine.history_gen() machine.history_gen()
@ -109,18 +101,13 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
} }
with self.conn.newMachine(proprio.dn, realm, ldif) as machine: with self.conn.newMachine(proprio.dn, realm, ldif) as machine:
for (key, values) in attrs.items(): for (key, values) in attrs.items():
if values!=u'<automatique>' or key != u'ipHostNumber':
machine[key]=values machine[key]=values
if attributs.ipsec in machine.attribs: if attributs.ipsec in machine.attribs:
machine[attributs.ipsec.ldap_name]=attributs.ipsec.default machine[attributs.ipsec.ldap_name]=attributs.ipsec.default
machine.validate_changes() machine.validate_changes()
if self.confirm_item(machine, "Voulez vous vraiement créer cette machine ?"): if self.confirm_item(machine, "Voulez vous vraiement créer cette machine ?"):
machine.create() machine.create()
self.display_item(machine, "La machine a bien été créée", ipsec=True) self.display_item(machine, "La machine à bien été créée", ipsec=True)
if realm == 'wifi-adh':
if self.dialog.yesno("Imprimer un ticket pour la machine ?", timeout=self.timeout, title="Impression de ticket", width=50) == self.dialog.DIALOG_OK:
subprocess.call(['/usr/scripts/cransticket/dump_creds.py', '--forced', 'mid=%s' % machine['mid'][0]])
self.display_item(machine, "Impression du ticket ...", ipsec=True)
return machine return machine
else: else:
raise Continue(cont) raise Continue(cont)
@ -136,7 +123,7 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
values = [v for v in values.split(separateur) if v] values = [v for v in values.split(separateur) if v]
# Pour host, on fait quelques vérification de syntaxe # Pour host, on fait quelques vérification de syntaxe
if a.ldap_name == 'host': if a.ldap_name == 'host':
attrs[a.ldap_name]=check_host(values, objectClass, realm) attrs[a.ldap_name]=check_host(values, objectClass)
else: else:
attrs[a.ldap_name]=values attrs[a.ldap_name]=values
# Soit on édite une machine existante # Soit on édite une machine existante
@ -147,16 +134,10 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
machine = create_machine(proprio, realm, attrs) machine = create_machine(proprio, realm, attrs)
raise Continue(cont(machine=machine)) raise Continue(cont(machine=machine))
if machine: if machine:
objectClass = machine["objectClass"][0] objectClass = machine["objectClass"][0]
if self.has_right(a.nounou, proprio):
to_display += to_display_port
# Les bornes wifi ont un to_display différent
if objectClass == 'borneWifi':
to_display += to_display_borne
(code, tags) = self.handle_dialog(cont, box) (code, tags) = self.handle_dialog(cont, box)
# On prépare les fiels à afficher à l'utilisateur si une erreure à lieu # On prépare les fiels à afficher à l'utilisateur si une erreure à lieu
@ -183,6 +164,11 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
"""Juste un raccourci vers edit_attributs spécifique aux machines""" """Juste un raccourci vers edit_attributs spécifique aux machines"""
return self.edit_attributs(obj=machine, update_obj='machine', attr=attr, title="Modification de la machine %s" % machine['host'][0], cont=cont) return self.edit_attributs(obj=machine, update_obj='machine', attr=attr, title="Modification de la machine %s" % machine['host'][0], cont=cont)
def modif_adherent_attributs(self, adherent, attr, cont):
"""Juste un raccourci vers edit_attributs spécifique aux adherents"""
return self.edit_attributs(obj=adherent, update_obj='adherent', attr=attr, title="Modification de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), cont=cont)
def modif_machine_boolean(self, machine, cont): def modif_machine_boolean(self, machine, cont):
"""Juste un raccourci vers edit_boolean_attributs spécifique aux machines""" """Juste un raccourci vers edit_boolean_attributs spécifique aux machines"""
a = attributs a = attributs
@ -207,8 +193,8 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
menu_droits = { menu_droits = {
'Information' : [a.parent, a.cableur, a.nounou], 'Information' : [a.parent, a.cableur, a.nounou],
'Autre': [a.parent, a.cableur, a.nounou], 'Autre': [a.parent, a.cableur, a.nounou],
'Blackliste':[a.nounou], 'Blackliste':[a.cableur, a.nounou],
'Certificat': [a.parent, a.nounou], 'Certificat': [a.parent, a.cableur, a.nounou],
'Exemption' : [a.nounou], 'Exemption' : [a.nounou],
'Alias' : [a.parent, a.cableur, a.nounou], 'Alias' : [a.parent, a.cableur, a.nounou],
'Remarques' : [a.cableur, a.nounou], 'Remarques' : [a.cableur, a.nounou],
@ -270,41 +256,24 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
menu_droits = { menu_droits = {
'Fixe' : [a.soi, a.cableur, a.nounou], 'Fixe' : [a.soi, a.cableur, a.nounou],
'Wifi' : [a.soi, a.cableur, a.nounou], 'Wifi' : [a.soi, a.cableur, a.nounou],
'Appartements': [a.soi, a.cableur, a.nounou],
} }
menu = { menu = {
'Fixe' : {'text' : "Machine filaire", 'objectClass':'machineFixe', 'realm':'adherents'}, 'Fixe' : {'text' : "Machine filaire", 'objectClass':'machineFixe', 'realm':'adherents'},
'Appartements' : {'text' : "Machine filaire de personnel ENS", 'objectClass':'machineFixe', 'realm':'personnel-ens'},
'Wifi' : {'text': 'Machine sans fil', 'objectClass':'machineWifi', 'realm':'wifi-adh'}, 'Wifi' : {'text': 'Machine sans fil', 'objectClass':'machineWifi', 'realm':'wifi-adh'},
} }
menu_order = ['Wifi'] menu_order = ['Fixe', 'Wifi']
# Machine appartement pour les personnels, fixe pour les autres
if proprio.get('etudes', [False])[0] == u'Personnel ENS':
menu_order.append('Appartements')
else:
# On vérifie que un non MA a qu'une machine fixe
menu_order.append('Fixe')
if not bool(proprio.get('droits', False)) and isinstance(proprio, objets.adherent):
for machine in proprio.machines():
if isinstance(machine, objets.machineFixe):
menu_order.remove('Fixe')
break
if isinstance(proprio, objets.AssociationCrans): if isinstance(proprio, objets.AssociationCrans):
menu_droits.update({ menu_droits.update({
'Fixe' : [a.nounou], 'Fixe' : [a.nounou],
'Wifi' : [a.nounou], 'Wifi' : [a.nounou],
'Wifi-v6' : [a.nounou],
'Adm' : [a.nounou], 'Adm' : [a.nounou],
}) })
menu.update({ menu.update({
'Fixe' : {'text' : "Ajouter un serveur sur le vlan adherent", 'objectClass':'machineCrans', 'realm':'serveurs'}, 'Fixe' : {'text' : "Ajouter un serveur sur le vlan adherent", 'objectClass':'machineCrans', 'realm':'serveurs'},
'Wifi' : {'text': 'Ajouter une borne WiFi sur le vlan wifi', 'objectClass':'borneWifi', 'realm':'bornes'}, 'Wifi' : {'text': 'Ajouter une borne WiFi sur le vlan wifi', 'objectClass':'borneWifi', 'realm':'bornes'},
'Wifi-v6' : {'text': 'Ajouter une borne WiFi sur le vlan wifi en ipv6 only', 'objectClass':'borneWifi', 'realm':'bornes-v6'},
'Adm' : {'text' : "Ajouter un serveur sur le vlan adm", "objectClass":"machineCrans", 'realm':'adm'}, 'Adm' : {'text' : "Ajouter un serveur sur le vlan adm", "objectClass":"machineCrans", 'realm':'adm'},
}) })
menu_order += ['Adm', 'Wifi-v6'] menu_order.append('Adm')
def box(default_item=None): def box(default_item=None):
return self.dialog.menu( return self.dialog.menu(
"Type de Machine ?", "Type de Machine ?",

View file

@ -85,10 +85,11 @@ class Dialog(machine.Dialog, blacklist.Dialog):
@tailcaller @tailcaller
def set_password(proprio, update_obj, cont): def set_password(proprio, update_obj, cont):
if self.dialog.yesno("Attribuer un mot de passe maintenant ? (Vous aurez la possibilité d'imprimer un ticket plus tard également ...)", if self.dialog.yesno("Attribuer un mot de passe maintenant ?",
title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]),
timeout=self.timeout timeout=self.timeout
) == self.dialog.DIALOG_OK: ) == self.dialog.DIALOG_OK:
#return self.proprio_compte_password(proprio=proprio, return_obj=return_obj, cont=cont(**{update_obj:proprio}))
proprio = self.proprio_compte_password(proprio=proprio, return_obj=True, cont=TailCall(set_password, proprio, update_obj, cont)) proprio = self.proprio_compte_password(proprio=proprio, return_obj=True, cont=TailCall(set_password, proprio, update_obj, cont))
if return_obj: if return_obj:
return proprio return proprio
@ -167,7 +168,7 @@ class Dialog(machine.Dialog, blacklist.Dialog):
raise Continue(cont(proprio=proprio)) raise Continue(cont(proprio=proprio))
#(code, passwords) = self.handle_dialog(cont, box) #(code, passwords) = self.handle_dialog(cont, box)
(code, passwords) = (self.dialog.DIALOG_OK, "") (code, passwords) = (self.dialog.DIALOG_OK, "")
self_cont = TailCall(self.proprio_compte_password, proprio=proprio, cont=cont, return_obj=return_obj) self_cont = TailCall(self.proprio_compte_password, proprio=proprio, cont=cont)
return self.handle_dialog_result( return self.handle_dialog_result(
code=code, code=code,
output=passwords, output=passwords,
@ -410,19 +411,14 @@ class Dialog(machine.Dialog, blacklist.Dialog):
"cheque" : "Chèque", "cheque" : "Chèque",
"carte" : "Par carte bancaire", "carte" : "Par carte bancaire",
"solde" : "Solde Crans (actuel : %s€)", "solde" : "Solde Crans (actuel : %s€)",
"note" : "Note kfet (attention, moins tracable...)",
"arbitraire" : "Création ou destruction magique d'argent.",
} }
def box_choose_paiment(tag, articles): def box_choose_paiment(tag, articles):
box_paiement_order = ["liquide", "cheque", "carte","note"] box_paiement_order = ["liquide", "cheque", "carte"]
if "cransAccount" in proprio['objectClass']: if "cransAccount" in proprio['objectClass']:
if not "SOLDE" in [art['code'] for art in articles] and proprio["solde"]: if not "SOLDE" in [art['code'] for art in articles] and proprio["solde"]:
box_paiement_order.append("solde") box_paiement_order.append("solde")
box_paiement["solde"] = box_paiement["solde"] % proprio["solde"][0] box_paiement["solde"] = box_paiement["solde"] % proprio["solde"][0]
if len(articles) == 1 and "SOLDE" in [art['code'] for art in articles]:
box_paiement_order.append("arbitraire")
choices = [] choices = []
for key in box_paiement_order: for key in box_paiement_order:
choices.append((key, box_paiement[key], 1 if key == tag else 0)) choices.append((key, box_paiement[key], 1 if key == tag else 0))
@ -461,8 +457,7 @@ class Dialog(machine.Dialog, blacklist.Dialog):
def box_choose_item(tags): def box_choose_item(tags):
choices = [] choices = []
gestion.config.factures.ITEMS.update(gestion.config.factures.ITEM_SOLDE) for code, article in gestion.config.factures.items.items():
for code, article in gestion.config.factures.ITEMS.items():
choices.append((code, u"%s%s" % (article['designation'], (u' (%s€)' % article['pu']) if article['pu'] != '*' else ""), 1 if code in tags else 0)) choices.append((code, u"%s%s" % (article['designation'], (u' (%s€)' % article['pu']) if article['pu'] != '*' else ""), 1 if code in tags else 0))
return self.dialog.checklist( return self.dialog.checklist(
text="", text="",
@ -504,19 +499,11 @@ class Dialog(machine.Dialog, blacklist.Dialog):
def paiement(have_set, tag, proprio, comment, cancel_cont, cont): def paiement(have_set, tag, proprio, comment, cancel_cont, cont):
articles = copy.deepcopy(have_set) articles = copy.deepcopy(have_set)
# On formate les articles
for article in articles: for article in articles:
if article['pu'] == '*': if article['pu'] == '*':
article['pu'] = article['nombre'] article['pu'] = article['nombre']
article['nombre'] = 1 article['nombre'] = 1
# En arbitraire, on accepte que le solde
if tag == u"arbitraire":
if len(articles) > 1 or "SOLDE" not in [art['code'] for art in articles]:
raise ValueError("Il n'est possible que de faire une opération de solde en mode arbitraire")
# Les articles classiques on facture
with self.conn.newFacture(proprio.dn, {}) as facture: with self.conn.newFacture(proprio.dn, {}) as facture:
facture['modePaiement']=unicode(tag, 'utf-8') facture['modePaiement']=unicode(tag, 'utf-8')
facture['article']=articles facture['article']=articles
@ -530,13 +517,13 @@ class Dialog(machine.Dialog, blacklist.Dialog):
arts = ["%s %s" % (art['nombre'], art['designation']) for art in facture['article'] if art['code'] != 'SOLDE'] arts = ["%s %s" % (art['nombre'], art['designation']) for art in facture['article'] if art['code'] != 'SOLDE']
if arts: if arts:
self.dialog.msgbox( self.dialog.msgbox(
text=u"Vous pouvez remettre à l'adherent les articles (si ce sont des articles) suivant :\n * %s" % '\n * '.join(arts), text=u"Vous pouvez remettre à l'adherent les articles (si se sont des articles) suivant :\n * %s" % '\n * '.join(arts),
title=u"Vente terminée", title=u"Vente terminée",
width=0, height=0, timeout=self.timeout) width=0, height=0, timeout=self.timeout)
if tag == "solde": if tag == "solde":
self.dialog.msgbox(text=u"Le solde de l'adhérent a bien été débité", title="Solde débité", width=0, height=0, timeout=self.timeout) self.dialog.msgbox(text=u"Le solde de l'adhérent à bien été débité", title="Solde débité", width=0, height=0, timeout=self.timeout)
if [a for a in facture['article'] if art['code'] == 'SOLDE']: if [a for a in facture['article'] if art['code'] == 'SOLDE']:
self.dialog.msgbox(text=u"Le solde de l'adhérent a bien été crédité", title="Solde crédité", width=0, height=0, timeout=self.timeout) self.dialog.msgbox(text=u"Le solde de l'adhérent à bien été crédité", title="Solde crédité", width=0, height=0, timeout=self.timeout)
else: else:
if not self.confirm(text=u"Le paiement n'a pas été reçue.\n Annuler la vente ?", title="Annulation de la vente", defaultno=True): if not self.confirm(text=u"Le paiement n'a pas été reçue.\n Annuler la vente ?", title="Annulation de la vente", defaultno=True):
raise Continue(cancel_cont) raise Continue(cancel_cont)
@ -572,11 +559,10 @@ class Dialog(machine.Dialog, blacklist.Dialog):
else: else:
(code, tags) = self.handle_dialog(cont, box_choose_item, tags) (code, tags) = self.handle_dialog(cont, box_choose_item, tags)
self_cont=self_cont(tags=tags, have_set=[], to_set=[], tag_paiment=None) self_cont=self_cont(tags=tags, have_set=[], to_set=[], tag_paiment=None)
gestion.config.factures.ITEMS.update(gestion.config.factures.ITEM_SOLDE)
return self.handle_dialog_result( return self.handle_dialog_result(
code=code, code=code,
output=tags, output=tags,
cancel_cont=cont, cancel_cont=cont,
error_cont=self_cont, error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], choose_item, [proprio, tags, copy.deepcopy(gestion.config.factures.ITEMS), self_cont])] codes_todo=[([self.dialog.DIALOG_OK], choose_item, [proprio, tags, copy.deepcopy(gestion.config.factures.items), self_cont])]
) )

File diff suppressed because it is too large Load diff

View file

@ -25,7 +25,6 @@ except:
# Machine sans mailman, les ML ne seront pas reconfigurées # Machine sans mailman, les ML ne seront pas reconfigurées
pass pass
CONN = crans_ldap()
class del_user: class del_user:
""" Suppression des fichiers d'un compte utilisateur """ """ Suppression des fichiers d'un compte utilisateur """
@ -115,28 +114,18 @@ class home:
for args in self.args: for args in self.args:
anim('\t' + args) anim('\t' + args)
try: try:
login, oldLogin, oldHome = args.split(",") try:
if login: home, uid, login, mail_redirect = args.split(',')
res = CONN.search("login=%s" % (login,)) except ValueError:
if res['adherent']: home, uid, login = args.split(',')
adh = res['adherent'][0] mail_redirect = None
gid = config.gid
elif res['club']:
adh = res['club'][0]
gid = config.club_gid
home = adh.home()
uid = adh.uidNumber()
mail_redirect = adh.email_exterieur()
if oldHome and hostname == "zbee":
home_service = del_user(["%s,%s" % (oldLogin, oldHome)])
home_service.delete_zbee()
homesplit = home.split("/") homesplit = home.split("/")
symlink = "/home-adh/%s" % (homesplit[-1],) symlink = "/home-adh/%s" % (homesplit[-1],)
### Home ### Home
if not os.path.exists(home): if not os.path.exists(home):
# Le home n'existe pas # Le home n'existe pas
os.mkdir(home, 0755) os.mkdir(home, 0755)
os.chown(home, int(uid), gid) os.chown(home, int(uid), config.gid)
if homesplit[-2] != "club": if homesplit[-2] != "club":
if os.path.exists(symlink) and os.path.islink(symlink): if os.path.exists(symlink) and os.path.islink(symlink):
os.unlink(symlink) os.unlink(symlink)
@ -147,7 +136,7 @@ class home:
# Il y un répertoire existant # Il y un répertoire existant
# Bon UID ? # Bon UID ?
stat = os.stat(home) stat = os.stat(home)
if stat[4] != int(uid) or stat[5] != gid: if stat[4] != int(uid) or stat[5] != config.gid:
# Le home n'est pas pas à la bonne personne # Le home n'est pas pas à la bonne personne
raise OSError('home existant') raise OSError('home existant')
if homesplit[-2] != "club": if homesplit[-2] != "club":
@ -169,24 +158,16 @@ class home:
### Mail ### Mail
if not os.path.exists(home + '/Mail'): if not os.path.exists(home + '/Mail'):
os.mkdir(home + '/Mail', 0700) os.mkdir(home + '/Mail', 0700)
os.chown(home + '/Mail', int(uid), gid) os.chown(home + '/Mail', int(uid), config.gid)
if not os.path.exists('/home-adh/mail/' + login): if not os.path.exists('/home-adh/mail/' + login):
os.mkdir('/home-adh/mail/' + login, 0700) os.mkdir('/home-adh/mail/' + login, 0700)
os.chown('/home-adh/mail/' + login, int(uid), 8) os.chown('/home-adh/mail/' + login, int(uid), 8)
### Redirection ### Redirection
if mail_redirect: if mail_redirect:
write_in_forward = True file(home + '/.forward', 'w').write(mail_redirect + '\n')
os.chown(home + '/.forward', int(uid), config.gid)
# On vérifie s'il y a déjà un .forward os.chmod(home + '/.forward', 0604)
if os.path.exists(os.path.join(home, ".forward")):
write_in_forward = False
if write_in_forward:
with open(os.path.join(home, '.forward'), 'w') as forward_file:
forward_file.write(mail_redirect + '\n')
os.chown(home + '/.forward', int(uid), gid)
os.chmod(home + '/.forward', 0600)
### Owncloud dans le home ### Owncloud dans le home
if not os.path.exists(home + '/OwnCloud'): if not os.path.exists(home + '/OwnCloud'):
os.mkdir(home + '/OwnCloud') os.mkdir(home + '/OwnCloud')

View file

@ -43,7 +43,6 @@ class autostatus(gen_config) :
"obm.crans.org", "obm.crans.org",
"obm.adm.crans.org", "obm.adm.crans.org",
"batv-3.adm.crans.org", "batv-3.adm.crans.org",
"batv-1.adm.crans.org",
# Config par défaut # Config par défaut
"non-configure.wifi.crans.org", "non-configure.wifi.crans.org",
@ -71,7 +70,6 @@ class autostatus(gen_config) :
"ragnarok.crans.org", # RIP contrôleur disque... "ragnarok.crans.org", # RIP contrôleur disque...
"zamok.crans.org", # c'est en fait fx "zamok.crans.org", # c'est en fait fx
"bati-2.adm.crans.org", # N'est plus en place "bati-2.adm.crans.org", # N'est plus en place
"batv-1.crans.org",
# Bornes wifi de test # Bornes wifi de test
"bullet5.wifi.crans.org", "bullet5.wifi.crans.org",
@ -143,11 +141,9 @@ class autostatus(gen_config) :
# quelque descriptions de routeurs triés par IP (pour la route vers l'extérieur) # quelque descriptions de routeurs triés par IP (pour la route vers l'extérieur)
infos_routeurs = {} infos_routeurs = {}
infos_routeurs [ '138.231.136.4' ] = ['Odlyd', u'Routeur principal du CRANS'] infos_routeurs [ '138.231.136.4' ] = ['Komaz', u'Routeur principal du CRANS']
infos_routeurs [ '138.231.136.3' ] = ['Komaz', u'Routeur secondaire du CRANS']
infos_routeurs [ '138.231.132.1' ] = ['Pioneer.zrt', u'Routeur principal de l\'ENS (interne)'] infos_routeurs [ '138.231.132.1' ] = ['Pioneer.zrt', u'Routeur principal de l\'ENS (interne)']
infos_routeurs [ '138.231.132.101' ] = ['Pioneer1.zrt.ens-cachan', u'Routeur principal de l\'ENS (interne)'] infos_routeurs [ '138.231.132.102' ] = ['Pioneer', u'Routeur principal de l\'ENS (interne)']
infos_routeurs [ '138.231.132.102' ] = ['Pioneer2.zrt.ens-cachan', u'Routeur principal de l\'ENS (interne)']
infos_routeurs [ '138.231.176.1' ] = ['Pioneer', u'Routeur principal de l\'ENS'] infos_routeurs [ '138.231.176.1' ] = ['Pioneer', u'Routeur principal de l\'ENS']
infos_routeurs [ '193.49.65.1' ] = ['RenaterCachan1' , u'Routeur Renater' ] infos_routeurs [ '193.49.65.1' ] = ['RenaterCachan1' , u'Routeur Renater' ]
infos_routeurs [ '193.51.181.186' ] = ['RenaterCachan2', u'Routeur Renater'] infos_routeurs [ '193.51.181.186' ] = ['RenaterCachan2', u'Routeur Renater']
@ -161,7 +157,7 @@ class autostatus(gen_config) :
services_exterieurs ['Free'] = [ 'Free', '212.27.60.27', 21, 'Le serveur FTP de free. (France)', 'nobody' ] services_exterieurs ['Free'] = [ 'Free', '212.27.60.27', 21, 'Le serveur FTP de free. (France)', 'nobody' ]
services_exterieurs ['Monde'] = [ 'Monde', '195.154.120.129', 80, 'Est-ce que LeMonde.fr fonctionne ? (France)', 'nobody' ] services_exterieurs ['Monde'] = [ 'Monde', '195.154.120.129', 80, 'Est-ce que LeMonde.fr fonctionne ? (France)', 'nobody' ]
services_exterieurs ['Yahoo!'] = [ 'Yahoo!', '206.190.36.45', 80, 'Est-ce que Yahoo! fonctionne ? (USA)', 'nobody' ] services_exterieurs ['Yahoo!'] = [ 'Yahoo!', '206.190.36.45', 80, 'Est-ce que Yahoo! fonctionne ? (USA)', 'nobody' ]
services_exterieurs ['Google'] = [ 'Google', '74.125.71.138', 80, 'Est-ce que Google fonctionne ? (USA)', 'nobody' ] services_exterieurs ['Google'] = [ 'Google', '173.194.34.20', 80, 'Est-ce que Google fonctionne ? (USA)', 'nobody' ]
# personnes à informer pour l'indiponibilité de certains serveurs # personnes à informer pour l'indiponibilité de certains serveurs
contact = {} contact = {}
@ -295,14 +291,10 @@ class autostatus(gen_config) :
# ajout du routeur # ajout du routeur
# ip # ip
try:
tmp_ip = routeur.split(' ')[1] tmp_ip = routeur.split(' ')[1]
except IndexError:
print "Skipping %r" % routeur
continue
# nom & desciption # nom & desciption
if tmp_ip in self.infos_routeurs.keys() : if routeur.split(' ')[1] in self.infos_routeurs.keys() :
tmp_name = self.infos_routeurs[tmp_ip][0] tmp_name = self.infos_routeurs[tmp_ip][0]
tmp_desc = self.infos_routeurs[tmp_ip][1] tmp_desc = self.infos_routeurs[tmp_ip][1]
else : else :

View file

@ -6,7 +6,6 @@ Copyright (C) Valentin Samir
Licence : GPLv3 Licence : GPLv3
""" """
import os
import sys import sys
import ssl import ssl
import time import time
@ -38,31 +37,24 @@ def short_name(fullhostname):
return fullhostname.split(".")[0] return fullhostname.split(".")[0]
class ResourceRecord(object): class ResourceRecord(object):
"""Classe standard définissant une ressource DNS""" def __init__(self, type, name, value, ttl=None):
self._type=type
def __init__(self, r_type, name, value, ttl=None): self._name=name
"""Affecte les valeurs de base de l'enregistrement""" self._value=value
self.r_type = r_type
self.name = name
self.value = value
self._ttl=ttl self._ttl=ttl
def __str__(self): def __str__(self):
"""Retourne une chaîne printable dans un fichier bind"""
if self._ttl: if self._ttl:
return "%s\t%s\tIN\t%s\t%s" % (self.name, self._ttl, self.r_type, self.value) return "%s\t%s\tIN\t%s\t%s" % (self._name, self._ttl, self._type, self._value)
else: else:
return "%s\tIN\t%s\t%s" % (self.name, self.r_type, self.value) return "%s\tIN\t%s\t%s" % (self._name, self._type, self._value)
def __repr__(self): def __repr__(self):
"""__repr__ == __str__"""
return str(self) return str(self)
class TLSA(ResourceRecord): class TLSA(ResourceRecord):
"""Enregistrement TLSA pour stocker des certifs dans un enregistrement DNS""" def __init__(self, name, port, proto, cert, certtype, reftype, selector=0, compat=True, format='pem', ttl=None):
"""
def __init__(self, name, port, proto, cert, certtype, reftype, selector=0, compat=True, r_format='pem', ttl=None): name: nom du domaine du certificat
""" name: nom du domaine du certificat
port: port écoute le service utilisant le certificat port: port écoute le service utilisant le certificat
proto: udp ou tcp proto: udp ou tcp
cert: le certificat au format ``format`` (pem ou der) (selector est donc toujours à 0) cert: le certificat au format ``format`` (pem ou der) (selector est donc toujours à 0)
@ -70,12 +62,8 @@ class TLSA(ResourceRecord):
reftype: 0 = plain cert, 1 = sha256, 2 = sha512 reftype: 0 = plain cert, 1 = sha256, 2 = sha512
compat: on génère un enregistement compris même par les serveurs dns n'implémentant pas TLSA compat: on génère un enregistement compris même par les serveurs dns n'implémentant pas TLSA
""" """
if not r_format in ['pem', 'der']: if not format in ['pem', 'der']:
raise ValueError("format should be pem or der") raise ValueError("format should be pem or der")
if selector != 0:
raise NotImplementedError("selector different form 0 not implemented")
if cert is None and proto == 'tcp' and name[-1] == '.': if cert is None and proto == 'tcp' and name[-1] == '.':
try: try:
cert = ssl.get_server_certificate((name[:-1], port), ca_certs='/etc/ssl/certs/ca-certificates.crt') cert = ssl.get_server_certificate((name[:-1], port), ca_certs='/etc/ssl/certs/ca-certificates.crt')
@ -83,17 +71,13 @@ class TLSA(ResourceRecord):
raise ValueError("Unable de retrieve cert dynamically: %s" % e) raise ValueError("Unable de retrieve cert dynamically: %s" % e)
elif cert is None: elif cert is None:
raise ValueError("cert can only be retrive if proto is tcp and name fqdn") raise ValueError("cert can only be retrive if proto is tcp and name fqdn")
if format is not 'der':
if r_format is not 'der':
dercert = ssl.PEM_cert_to_DER_cert(cert) dercert = ssl.PEM_cert_to_DER_cert(cert)
else: else:
dercert = cert dercert = cert
if not dercert: if not dercert:
raise ValueError("Impossible de convertir le certificat au format DER %s %s %s\n%s" % (name, port, proto, cert)) raise ValueError("Impossible de convertir le certificat au format DER %s %s %s\n%s" % (name, port, proto, cert))
certhex = TLSA.hashCert(reftype, str(dercert)) certhex = TLSA.hashCert(reftype, str(dercert))
self.certhex = certhex
if compat: if compat:
super(TLSA, self).__init__( super(TLSA, self).__init__(
'TYPE52', 'TYPE52',
@ -111,7 +95,7 @@ class TLSA(ResourceRecord):
@staticmethod @staticmethod
def hashCert(reftype, certblob): def hashCert(reftype, certblob):
"""Retourne un hash d'un certif DER en MAJUSCULES. """
certblob: un certificat au format DER certblob: un certificat au format DER
""" """
if reftype == 0: if reftype == 0:
@ -127,170 +111,98 @@ class TLSA(ResourceRecord):
return hashobj.hexdigest().upper() return hashobj.hexdigest().upper()
class SOA(ResourceRecord): class SOA(ResourceRecord):
"""Ressource pour une entrée DNS SOA"""
def __init__(self, master, email, serial, refresh, retry, expire, ttl): def __init__(self, master, email, serial, refresh, retry, expire, ttl):
super(SOA, self).__init__('SOA', '@', '%s. %s. (\n %s ; numero de serie\n %s ; refresh (s)\n %s ; retry (s)\n %s ; expire (s)\n %s ; TTL (s)\n )' % (master, email, serial, refresh, retry, expire, ttl)) super(SOA, self).__init__('SOA', '@', '%s. %s. (\n %s ; numero de serie\n %s ; refresh (s)\n %s ; retry (s)\n %s ; expire (s)\n %s ; TTL (s)\n )' % (master, email, serial, refresh, retry, expire, ttl))
class A(ResourceRecord): class A(ResourceRecord):
"""Entrée DNS pour une IPv4"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(A, self).__init__('A', name, value, ttl) super(A, self).__init__('A', name, value, ttl)
class DS(ResourceRecord): class DS(ResourceRecord):
"""Entrée DNS pour l'empreinte d'une clef DNSSEC"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(DS, self).__init__('DS', name, value, ttl) super(DS, self).__init__('DS', name, value, ttl)
class PTR(ResourceRecord): class PTR(ResourceRecord):
"""Entrée DNS inverse (pour obtenir l'IP à partir du NDD"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(PTR, self).__init__('PTR', name, value, ttl) super(PTR, self).__init__('PTR', name, value, ttl)
class AAAA(ResourceRecord): class AAAA(ResourceRecord):
"""Entrée DNS pour une IPv6"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(AAAA, self).__init__('AAAA', name, value, ttl) super(AAAA, self).__init__('AAAA', name, value, ttl)
class TXT(ResourceRecord): class TXT(ResourceRecord):
"""Entrée DNS pour un champ TXT"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(TXT, self).__init__('TXT', name, value, ttl) super(TXT, self).__init__('TXT', name, value, ttl)
if len(self.value) > 200:
self.value = '( "' + '"\n\t\t\t\t"'.join([self.value[x:x+200] for x in xrange(0, len(self.value), 200)]) + '" )'
else:
self.value = '"%s"' % (self.value,)
def __str__(self):
"""Retourne une chaîne printable dans un fichier bind"""
if self._ttl:
return '%s\t%s\tIN\t%s\t%s' % (self.name, self._ttl, self.r_type, self.value)
else:
return '%s\tIN\t%s\t%s' % (self.name, self.r_type, self.value)
class CNAME(ResourceRecord): class CNAME(ResourceRecord):
"""Entrée DNS pour un alias (toto -> redisdead)"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(CNAME, self).__init__('CNAME', name, value, ttl) super(CNAME, self).__init__('CNAME', name, value, ttl)
class DNAME(ResourceRecord): class DNAME(ResourceRecord):
"""Entrée DNS pour un alias de domaine (crans.eu -> crans.org)"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(DNAME, self).__init__('DNAME', name, value, ttl) super(DNAME, self).__init__('DNAME', name, value, ttl)
class MX(ResourceRecord): class MX(ResourceRecord):
"""Entrée DNS pour un serveur mail. crans.org IN MX 5 redisdead.crans.org veut dire
que redisdead est responsable de recevoir les mails destinés à toto@crans.org avec
une priorité 5 (plus c'est faible, plus c'est prioritaire.
"""
def __init__(self, name, priority, value, ttl=None): def __init__(self, name, priority, value, ttl=None):
super(MX, self).__init__('MX', name, '%s\t%s' % (priority, value), ttl) super(MX, self).__init__('MX', name, '%s\t%s' % (priority, value), ttl)
class NS(ResourceRecord): class NS(ResourceRecord):
"""Entrée DNS pour donner les serveurs autoritaires pour un nom de domaine"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(NS, self).__init__('NS', name, value, ttl) super(NS, self).__init__('NS', name, value, ttl)
class SPF(ResourceRecord):
def __init__(self, name, value, ttl=None):
super(SPF, self).__init__('SPF', name, value, ttl)
class SRV(ResourceRecord): class SRV(ResourceRecord):
"""Entrée DNS pour les champs SRV""" def __init__(self, service, proto, priority, weight, port, target, ttl=None):
def __init__(self, service, proto, priority, weight, port, target, ttl=None, subdomain=None): super(SRV, self).__init__('SRV', '_%s._%s' % (service, proto), '%s\t%s\t%s\t%s' % (priority, weight, port, target), ttl)
super(SRV, self).__init__('SRV', '_%s._%s' % (service, proto) + ('.%s' % subdomain if subdomain else ''), '%s\t%s\t%s\t%s' % (priority, weight, port, target), ttl)
class NAPTR(ResourceRecord): class NAPTR(ResourceRecord):
"""Entrée DNS pour les NAPTR"""
def __init__(self, name, order, preference, flag, service, replace_regexpr, value, ttl=None): def __init__(self, name, order, preference, flag, service, replace_regexpr, value, ttl=None):
super(NAPTR, self).__init__('NAPTR', name, '%s\t%s\t"%s"\t"%s"\t"%s"\t%s' % (order, preference, flag, service, replace_regexpr, value), ttl) super(NAPTR, self).__init__('NAPTR', name, '%s\t%s\t"%s"\t"%s"\t"%s"\t%s' % (order, preference, flag, service, replace_regexpr, value), ttl)
class SSHFP(ResourceRecord): class SSHFP(ResourceRecord):
"""Entrée DNS stockant une fingerprint SSH""" def __init__(self, name, hash, algo, key, ttl=None):
def __init__(self, name, r_hash, algo, key, ttl=None): if not hash in config.sshfp_hash.keys():
"""Vérifie que hash/algo sont supportés dans la config""" raise ValueError('Hash %s invalid, valid hash are %s' % (hash, ', '.join(config.sshfp_host.keys())))
if not r_hash in config.sshfp_hash.keys():
raise ValueError('Hash %s invalid, valid hash are %s' % (r_hash, ', '.join(config.sshfp_hash.keys())))
if not algo in config.sshfp_algo.keys(): if not algo in config.sshfp_algo.keys():
raise ValueError('Algo %s unknown, valid values are %s' % (algo, ', '.join(config.sshfp_algo.keys()))) raise ValueError('Algo %s unknown, valid values are %s' % (algo, ', '.join(config.sshfp_algo.keys())))
super(SSHFP, self).__init__('SSHFP', name, '%s\t%s\t%s' % (config.sshfp_algo[algo][0], config.sshfp_hash[hash], getattr(hashlib, hash)(base64.b64decode(key)).hexdigest()), ttl)
super(SSHFP, self).__init__('SSHFP', name, '%s\t%s\t%s' % (config.sshfp_algo[algo][0], config.sshfp_hash[r_hash], getattr(hashlib, r_hash)(base64.b64decode(key)).hexdigest()), ttl) class ZoneBase(object):
class ZoneBase(list):
"""Classe abstraite décrivant une zone.
Elle surcharge une liste, car l'ensemble des enregistrements de cette
zone sera contenu en elle-même."""
def __init__(self, zone_name): def __init__(self, zone_name):
"""Affecte un nom de zone""" self._rrlist=[]
super(ZoneBase, self).__init__()
self.zone_name = zone_name self.zone_name = zone_name
self.ttl = 3600
def __repr__(self): def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.zone_name) return "<%s %s>" % (self.__class__.__name__, self.zone_name)
def __str__(self): def __str__(self):
"""Version enregistrable en fichier d'une zone.""" ret="%s\n$ORIGIN %s.\n$TTL %s\n" % (disclamer.replace('//', ';'), self.zone_name, self.ttl)
_ret = "%s\n$ORIGIN %s.\n$TTL %s\n" % (disclamer.replace('//', ';'), self.zone_name, self.ttl) for rr in self._rrlist:
for rr in self: ret+="%s\n" % rr
_ret += "%s\n" % rr return ret
return _ret
def add(self, rr): def add(self, rr):
"""Ajout d'un enregistrement DNS"""
if isinstance(rr, ResourceRecord): if isinstance(rr, ResourceRecord):
self.append(rr) self._rrlist.append(rr)
else: else:
raise ValueError("You can only add ResourceRecords to a Zone") raise ValueError("You can only add ResourceRecords to a Zone")
def extend(self, rr_list):
for rr in rr_list:
self.add(rr)
def write(self, path): def write(self, path):
"""Pour dumper le tout dans le fichier idoine."""
with open(path, 'w') as f: with open(path, 'w') as f:
f.write("%s" % self) f.write("%s" % self)
class ZoneClone(ZoneBase):
"""Zone clone d'une autre zone."""
def __init__(self, zone_name, zone_clone, soa):
"""La zone clone possède, outre son nom, un pointeur vers
la zone qu'elle duplique.
Le SOA est fourni manuellement, et la première entrée de la zone clonée
est ignorée. (c'est a priori le SOA de celle-ci) class ZoneClone(ZoneBase):
""" def __init__(self, zone_name, zone_clone, soa):
super(ZoneClone, self).__init__(zone_name) super(ZoneClone, self).__init__(zone_name)
self.zone_clone = zone_clone self.zone_clone = zone_clone
self.ttl = zone_clone.ttl self.ttl = zone_clone.ttl
# On met un SOA custom.
self.add(soa) self.add(soa)
# On ajoute un DNAME, qui indique que la zone est un clone.
self.add(DNAME('', "%s." % self.zone_clone.zone_name)) self.add(DNAME('', "%s." % self.zone_clone.zone_name))
for rr in self.zone_clone._rrlist[1:]:
# Et on extrait les données nécessaires de la zone clônée if rr._name in ['', '@']:
# à savoir, celles de l'apex (la base du domaine, qui elle
# n'est pas clônée, seuls les sous-domaines le sont)
for rr in self.zone_clone[1:]:
# Si pas de nom ou si le nom est @, on duplique bêtement l'enregistrement
if rr.name in ['', '@']:
self.add(rr) self.add(rr)
if rr._name in ["%s." % self.zone_clone.zone_name]:
# Si le nom de domaine concerné est celui de la zone clonée, pareil, on self.add(ResourceRecord(rr._type, "%s." % self.zone_name, rr._value))
# "duplique", en créant un enregistrement idoine.
if rr.name in ["%s." % self.zone_clone.zone_name]:
self.add(ResourceRecord(rr.r_type, "%s." % self.zone_name, rr.value))
class Zone(ZoneBase): class Zone(ZoneBase):
"""Une zone standard""" def __init__(self, zone_name, ttl, soa, ns_list, ipv6=True, ipv4=True, other_zones=[]):
def __init__(self, zone_name, ttl, soa, ns_list, ipv6=True, ipv4=True, other_zones=None):
"""Héritage, plus quelques propriétés en plus
On définit ici si la zone comporte des ipv4/ipv6,
ainsi que des données utiles pour le comportement de celles-ci.
other_zones contient la liste de sous-zones "indépendantes".
(exemple avec wifi.crans.org qui est une sous-zone de crans.org)"""
if other_zones is None:
other_zones = []
super(Zone, self).__init__(zone_name) super(Zone, self).__init__(zone_name)
self.ttl = ttl self.ttl = ttl
self.ipv4 = ipv4 self.ipv4 = ipv4
@ -303,24 +215,13 @@ class Zone(ZoneBase):
self.add(NS('@', '%s.' % ns)) self.add(NS('@', '%s.' % ns))
def name_in_subzone(self, hostname): def name_in_subzone(self, hostname):
"""Teste si le nom qu'on observe est dans une
sous-zone (toto.wifi.crans.org. est dans wifi.crans.org., et non
dans crans.org..
"""
for zone in self.subzones: for zone in self.subzones:
if str(hostname).endswith(".%s" % zone): if str(hostname).endswith(".%s" % zone):
return True return True
return False return False
def get_name(self, hostname): def get_name(self, hostname):
"""Retourne la base du nom d'un hôte. Teste si celui-ci appartient bien # le hostname fini bien par la zone courante, et il n'appartient pas à une sous-zone
à la zone courante et s'il n'est pas lié à une sous-zone.
Si tout est bon, le nom peut valoir "", auquel cas, l'entrée concerne le domaine
courant, donc @.
Dans le cas ce nom ne devrait pas être , on retourne None.
"""
if str(hostname) == self.zone_name or str(hostname).endswith(".%s" % self.zone_name) and not self.name_in_subzone(hostname): if str(hostname) == self.zone_name or str(hostname).endswith(".%s" % self.zone_name) and not self.name_in_subzone(hostname):
ret=str(hostname)[0:- len(self.zone_name) -1] ret=str(hostname)[0:- len(self.zone_name) -1]
if ret == "": if ret == "":
@ -331,91 +232,65 @@ class Zone(ZoneBase):
return None return None
def get_name_vi(self, nom, i): def get_name_vi(self, nom, i):
"""Kludge foireux pour retourner toto.v4.crans.org à partir
de toto.crans.org (sous-zones v4/v6)."""
if not i in [4, 6]: if not i in [4, 6]:
raise ValueError("i should be 4 or 6") raise ValueError("i should be 4 or 6")
if nom == '@': if nom == '@':
return 'v%s' % i return 'v%s' % i
# On considère que le "vrai" nom est la partie avant le premier .
elif '.' in nom: elif '.' in nom:
nom_1, nom_2 = nom.split('.', 1) nom_1, nom_2 = nom.split('.', 1)
return "%s.v%s.%s" % (nom_1, i, nom_2) return "%s.v%s.%s" % (nom_1, i, nom_2)
else: else:
return "%s.v%s" % (nom, i) return "%s.v%s" % (nom, i)
def add_delegation(self, zone, server): def add_delegation(zone, server):
"""Lorsqu'on veut offrir une délégation DNS à une machine zone = self.het_name(zone)
pour un nom de domaine"""
zone = self.get_name(zone)
if zone: if zone:
self.add(NS('@', '%s.' % server)) self.add(NS('@', '%s.' % server))
def add_a_record(self, nom, machine): def add_a_record(self, nom, machine):
"""Ajout d'une entrée A."""
# Fait-on de l'IPv4 dans cette zone ?
if self.ipv4: if self.ipv4:
for ip in machine.get('ipHostNumber', []): for ip in machine.get('ipHostNumber', []):
self.add(A(nom, ip)) self.add(A(nom, ip))
# Fait-on aussi de l'IPv6 ?
if self.ipv6: if self.ipv6:
# Bon bah alors on ajoute nom.v4.crans.org en plus.
self.add(A(self.get_name_vi(nom, 4), ip)) self.add(A(self.get_name_vi(nom, 4), ip))
def add_aaaa_record(self, nom, machine): def add_aaaa_record(self, nom, machine):
"""Ajout d'une entrée AAAA (for the AAAAAAAAwesome)."""
# Fait-on de l'IPv6 dans cette zone ?
if self.ipv6: if self.ipv6:
for ip in machine.get('ip6HostNumber', []): for ip in machine.get('ip6HostNumber', []):
# Si dnsIpv6 est à True dans la base LDAP, on ajoute l'entrée. if machine.get('dnsIpv6', [True])[0]:
# On l'ajoute quand même si la zone ne fait pas d'IPv4, parce que
# ça semble assez dommage d'avoir une machine qui a une IPv6, pas
# d'IPv4, et pas d'entrée DNS pour la contacter, non mais oh.
dnsipv6 = machine.get('dnsIpv6', [True])[0]
if dnsipv6 or not self.ipv4:
self.add(AAAA(nom, ip)) self.add(AAAA(nom, ip))
# Si on fait aussi de l'IPv4...
if self.ipv4: if self.ipv4:
self.add(AAAA(self.get_name_vi(nom, 6), ip)) self.add(AAAA(self.get_name_vi(nom, 6), ip))
def add_sshfp_record(self, nom, machine): def add_sshfp_record(self, nom, machine):
"""Ajoute une fingerprint SSH"""
for sshkey in machine.get('sshFingerprint', []): for sshkey in machine.get('sshFingerprint', []):
try: try:
algo_txt, key = str(sshkey).split()[:2] algo_txt, key = str(sshkey).split()[:2]
algo=config.sshfs_ralgo[algo_txt][1] algo=config.sshfs_ralgo[algo_txt][1]
for r_hash in config.sshfp_hash.keys(): for hash in config.sshfp_hash.keys():
self.add(SSHFP(nom, r_hash, algo, key)) self.add(SSHFP(nom, hash, algo, key))
if self.ipv4: if self.ipv4 and self.ipv6:
self.add(SSHFP(self.get_name_vi(nom, 4), r_hash, algo, key)) self.add(SSHFP(self.get_name_vi(nom, 4), hash, algo, key))
if self.ipv6: self.add(SSHFP(self.get_name_vi(nom, 6), hash, algo, key))
self.add(SSHFP(self.get_name_vi(nom, 6), r_hash, algo, key))
# KeyError is l'algo dans ldap n'est pas connu # KeyError is l'algo dans ldap n'est pas connu
# TypeError si la clef n'est pas bien en base64 # TypeError si la clef n'est pas bien en base64
except (KeyError, TypeError): except (KeyError, TypeError):
pass pass
def add_tlsa_record(self, cert): def add_tlsa_record(self, cert):
"""Ajout d'un certif dans le DNS"""
if 'TLSACert' in cert['objectClass']: if 'TLSACert' in cert['objectClass']:
if not cert.get('revocked', [False])[0]:
for host in cert['hostCert']: for host in cert['hostCert']:
nom=self.get_name(host) nom=self.get_name(host)
if nom is None: continue if nom is None: continue
for port in cert['portTCPin']: for port in cert['portTCPin']:
self.add(TLSA(nom, port, 'tcp', cert['certificat'][0], cert['certificatUsage'][0], cert['matchingType'][0], cert['selector'][0], r_format='der')) self.add(TLSA(nom, port, 'tcp', cert['certificat'][0], cert['certificatUsage'][0], cert['matchingType'][0], cert['selector'][0], format='der'))
for port in cert['portUDPin']: for port in cert['portUDPin']:
self.add(TLSA(nom, port, 'udp', cert['certificat'][0], cert['certificatUsage'][0], cert['matchingType'][0], cert['selector'][0], r_format='der')) self.add(TLSA(nom, port, 'udp', cert['certificat'][0], cert['certificatUsage'][0], cert['matchingType'][0], cert['selector'][0], format='der'))
def add_machine(self, machine): def add_machine(self, machine):
"""Ajout d'une machine, à savoir chaînage d'ajout
d'IP, d'IPv6, de fingerprint et de TLSA, pour chaque
entrée "host" dans la base LDAP."""
for host in machine['host']: for host in machine['host']:
# Le nom peut être None (machine appartenant à une sous-zone, ou à une autre zone)
nom=self.get_name(host) nom=self.get_name(host)
if nom is None: if nom is None: continue
continue
self.add_a_record(nom, machine) self.add_a_record(nom, machine)
self.add_aaaa_record(nom, machine) self.add_aaaa_record(nom, machine)
@ -423,23 +298,14 @@ class Zone(ZoneBase):
for cert in machine.certificats(): for cert in machine.certificats():
self.add_tlsa_record(cert) self.add_tlsa_record(cert)
# Si la machine a bien un nom en "host", on lui ajoute aussi
# les aliases, sous forme de CNAME vers le premier nom.
if machine['host']: if machine['host']:
for alias in machine.get('hostAlias', []): for alias in machine.get('hostAlias', []):
# Si l'alias pointe dans une autre zone, on passe. (ça sera fait quand on refera le add_machine
# en toutnant dans la sous-zone
if str(alias) in self.other_zones and str(alias) != self.zone_name: if str(alias) in self.other_zones and str(alias) != self.zone_name:
continue continue
alias = self.get_name(alias) alias = self.get_name(alias)
if alias is None: if alias is None: continue
continue
to_nom = self.get_name(machine['host'][0]) to_nom = self.get_name(machine['host'][0])
# Si l'alias est sur le nom de la zone, il faut ajouter
# des entrées standard.
if alias in ['@', '%s.' % self.zone_name]: if alias in ['@', '%s.' % self.zone_name]:
self.add_a_record(alias, machine) self.add_a_record(alias, machine)
self.add_aaaa_record(alias, machine) self.add_aaaa_record(alias, machine)
@ -449,23 +315,14 @@ class Zone(ZoneBase):
if self.ipv4 and self.ipv6: if self.ipv4 and self.ipv6:
self.add(CNAME(self.get_name_vi(alias, 6), self.get_name_vi(to_nom, 6))) self.add(CNAME(self.get_name_vi(alias, 6), self.get_name_vi(to_nom, 6)))
self.add(CNAME(self.get_name_vi(alias, 4), self.get_name_vi(to_nom, 4))) self.add(CNAME(self.get_name_vi(alias, 4), self.get_name_vi(to_nom, 4)))
# Ne devrait pas arriver.
else: else:
self.add(CNAME(alias, "%s." % machine['host'][0])) self.add(CNAME(alias, "%s." % machine['host'][0]))
class ZoneReverse(Zone): class ZoneReverse(Zone):
"""Zone inverse, listant des PTR (toto.crans.org IN PTR 138.231...)"""
def __init__(self, net, ttl, soa, ns_list): def __init__(self, net, ttl, soa, ns_list):
"""Initialise une zone reverse.
net est un truc de la forme fe80::/64, ou 138.231.136.0/24
En v4, il faut que net soit un /32, un /24, un /16 ou un /8
En gros, il faut que network_to_arpanets retourne une liste à un élément."""
# Comme dit, liste à un élément.
if len(ZoneReverse.network_to_arpanets(net))!=1: if len(ZoneReverse.network_to_arpanets(net))!=1:
raise ValueError("%s n'est pas un réseau valide pour une zone de reverse dns" % net) raise ValueError("%s n'est pas un réseau valide pour une zone de reverse dns" % net)
self.net = net self.net = net
zone_name = ZoneReverse.reverse(net)[0] zone_name = ZoneReverse.reverse(net)[0]
if '.' in net: if '.' in net:
@ -476,7 +333,6 @@ class ZoneReverse(Zone):
ipv4=False ipv4=False
else: else:
raise ValueError("net should be an ipv4 ou ipv6 network") raise ValueError("net should be an ipv4 ou ipv6 network")
super(ZoneReverse, self).__init__(zone_name, ttl, soa, ns_list, ipv6=ipv6, ipv4=ipv4) super(ZoneReverse, self).__init__(zone_name, ttl, soa, ns_list, ipv6=ipv6, ipv4=ipv4)
@staticmethod @staticmethod
@ -485,43 +341,29 @@ class ZoneReverse(Zone):
l'adresse donnés, ainsi que le nombre d'éléments de l'ip a l'adresse donnés, ainsi que le nombre d'éléments de l'ip a
mettre dans le fichier de zone si elle est fournie, n'importe mettre dans le fichier de zone si elle est fournie, n'importe
quoi sinon.""" quoi sinon."""
# Initialise la plage d'IP à partir de net n = netaddr.IPNetwork(net)
_network = netaddr.IPNetwork(net) a = netaddr.IPAddress(ip if ip else n.ip)
# Prend la première adresse ip de la plage, sauf si une est fournie rev_dns_a = a.reverse_dns.split('.')[:-1]
_address = netaddr.IPAddress(ip if ip else _network.ip) assert a in n
# retourne le reverse splitté. (un reverse ressemble à 0.136.231.138.in-addr.arpa.) if n.version == 4:
rev_dns_a = _address.reverse_dns.split('.')[:-1] if n.prefixlen == 8:
# Si la config est foireuse (donc si on a fourni une IP hors de la plage, ça
# va planter ici.
assert _address in _network
# En v4, le reverse étant de la forme 0.136.231.138.in-addr.arpa., soit
# on a un /8, soit un /16, soit un /24.
if _network.version == 4:
if _network.prefixlen == 8:
return ('.'.join(rev_dns_a[3:]), 3) return ('.'.join(rev_dns_a[3:]), 3)
elif _network.prefixlen == 16: elif n.prefixlen == 16:
return ('.'.join(rev_dns_a[2:]), 2) return ('.'.join(rev_dns_a[2:]), 2)
elif _network.prefixlen == 24: elif n.prefixlen == 24:
return ('.'.join(rev_dns_a[1:]), 1) return ('.'.join(rev_dns_a[1:]), 1)
else: else:
raise ValueError("Bad network %s" % _network) raise ValueError("Bad network %s" % n)
# En v6 c'est plus calme. elif n.version == 6:
# Le reverse a cette tronche : 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa. return ('.'.join(rev_dns_a[(128-n.prefixlen)/4:]), (128-n.prefixlen)/4)
# Du coup c'est aussi fin qu'on le souhaite.
elif _network.version == 6:
return ('.'.join(rev_dns_a[(128 - _network.prefixlen)/4:]), (128 - _network.prefixlen)/4)
@staticmethod @staticmethod
def network_to_arpanets(nets): def network_to_arpanets(nets):
"""Dans reverse(net, ip), on a constaté qu'en v4, on ne pouvait définir """
que des plages reverse en /24, /16 ou /8. Cette fonction vise à retourner retourne une liste de reseaux ne contenant que
une liste des plages en tenant compte de ce critère (donc de taille des préfixes de taille 32, 24, 16 ou 8 en ipv4
32/24/16/8) et laisse inchangé les réseaux ipv6.
Ne touche à rien pour l'IPv6.
""" """
if not isinstance(nets, list): if not isinstance(nets, list):
nets = [nets] nets = [nets]
@ -529,8 +371,6 @@ class ZoneReverse(Zone):
for net in nets: for net in nets:
if not isinstance(net, netaddr.IPNetwork): if not isinstance(net, netaddr.IPNetwork):
net = netaddr.IPNetwork(net) net = netaddr.IPNetwork(net)
# Si on est en v4, on fragmente les subnets
# dans les tailles qui vont bien.
if net.version == 4: if net.version == 4:
if net.prefixlen > 24: if net.prefixlen > 24:
subnets.extend(net.subnet(32)) subnets.extend(net.subnet(32))
@ -540,13 +380,12 @@ class ZoneReverse(Zone):
subnets.extend(net.subnet(16)) subnets.extend(net.subnet(16))
else: else:
subnets.extend(net.subnet(8)) subnets.extend(net.subnet(8))
# En v6 c'est tout pété.
elif net.version == 6: elif net.version == 6:
subnets.append(net) subnets.append(net)
return subnets return subnets
def add_machine(self, machine): def add_machine(self, machine):
"""Ajout d'un reverse pour une machine."""
if machine['host']: if machine['host']:
if self.ipv4: if self.ipv4:
attr = 'ipHostNumber' attr = 'ipHostNumber'
@ -554,42 +393,33 @@ class ZoneReverse(Zone):
attr = 'ip6HostNumber' attr = 'ip6HostNumber'
else: else:
raise ValueError("A reverse zone should be ipv6 or ipv6") raise ValueError("A reverse zone should be ipv6 or ipv6")
for ip in machine[attr]: for ip in machine[attr]:
try: try:
zone, length = ZoneReverse.reverse(self.net, str(ip)) zone, length = ZoneReverse.reverse(self.net, str(ip))
nom = '.'.join(ip.value.reverse_dns.split('.')[:length]) nom = '.'.join(ip.value.reverse_dns.split('.')[:length])
# La zone retournée n'est pas le nom de la zone. A priori
# on aurait dû tomber en AssertionError.
if zone != self.zone_name: if zone != self.zone_name:
continue continue
if attr != 'ip6HostNumber' or machine.get('dnsIpv6', [True])[0]: # Hack pour envoyer le reverse vers l'adresse .v6 dans le cas où dnsIpv6 = False
if attr != 'ip6HostNumber' or machine.get('dnsIpv6', [True])[0]:
self.add(PTR(nom, '%s.' % machine['host'][0])) self.add(PTR(nom, '%s.' % machine['host'][0]))
# Gros kludge pour ajouter le reverse vers le .v6 quand on est sur
# une reverse v6 et que dnsIpv6 est faux.
else: else:
rev_nom, rev_zone = str(machine['host'][0]).split('.', 1) rev_nom, rev_zone = str(machine['host'][0]).split('.', 1)
self.add(PTR(nom, '%s.v6.%s.' % (rev_nom, rev_zone))) self.add(PTR(nom, '%s.v6.%s.' % (rev_nom, rev_zone)))
except AssertionError: except AssertionError:
# L'ip n'est pas dans la zone reverse, donc on continue silencieusement.
pass pass
class dns(gen_config) : class dns(gen_config) :
"""Classe de configuration du DNS (les services, generate, toussa)"""
######################################PARTIE DE CONFIGURATION ######################################PARTIE DE CONFIGURATION
### Fichiers à écrire ### Fichiers à écrire
# Répertoire d'écriture des fichiers de zone # Répertoire d'écriture des fichiers de zone
DNS_DIR = config.dns.DNS_DIR DNS_DIR = '/etc/bind/generated/' # Avec un / à la fin
DNSSEC_DIR = config.dns.DNSSEC_DIR DNSSEC_DIR = '/etc/bind/signed/' # Avec un / à la fin
# Fichier de définition des zones pour le maître # Fichier de définition des zones pour le maître
DNS_CONF = config.dns.DNS_CONF DNS_CONF = DNS_DIR + 'zones_crans'
# Fichier de définition des zones pour les esclaves géré par BCfg2 # Fichier de définition des zones pour les esclaves géré par BCfg2
DNS_CONF_BCFG2 = config.dns.DNS_CONF_BCFG2 DNS_CONF_BCFG2 = "/var/lib/bcfg2/Cfg/etc/bind/generated/zones_crans/zones_crans"
### Liste DNS ### Liste DNS
# Le premier doit être le maitre # Le premier doit être le maitre
@ -602,8 +432,11 @@ class dns(gen_config):
### Serveurs de mail ### Serveurs de mail
# format : [ priorité serveur , .... ] # format : [ priorité serveur , .... ]
MXs = [MX('@', config.dns.MXs[_mx].get('prio', 25), "%s." %_mx) for _mx in config.dns.MXs] MXs = [
MX('@',10, 'redisdead.crans.org.'),
MX('@',15, 'soyouz.crans.org.'),
MX('@',25, 'freebox.crans.org.'),
]
SRVs = { SRVs = {
'crans.org': [ 'crans.org': [
SRV('jabber', 'tcp', 5, 0, 5269, 'xmpp'), SRV('jabber', 'tcp', 5, 0, 5269, 'xmpp'),
@ -613,41 +446,14 @@ class dns(gen_config):
SRV('sip', 'tcp', 5, 0, 5060, 'asterisk'), SRV('sip', 'tcp', 5, 0, 5060, 'asterisk'),
SRV('sips', 'tcp', 5, 0, 5061, 'asterisk'), SRV('sips', 'tcp', 5, 0, 5061, 'asterisk'),
SRV('stun', 'udp', 5, 0, 3478, 'asterisk'), SRV('stun', 'udp', 5, 0, 3478, 'asterisk'),
]
# Quelques ancien utilisent le server XMPP avec des addresses de la forme
# login@jabber.crans.org, aussi les clients XMPP et autres serveurs de la
# fédération veulent ces ResourceRecord
SRV('jabber', 'tcp', 5, 0, 5269, 'xmpp', subdomain="jabber"),
SRV('xmpp-server', 'tcp', 5, 0, 5269, 'xmpp', subdomain="jabber"),
SRV('xmpp-client', 'tcp', 5, 0, 5222, 'xmpp', subdomain="jabber"),
],
} }
NATPRs = {
SPFs = {
'crans.org': [
TXT('@', 'v=spf1 mx ~all'),
],
}
DKIM = {
'crans.org': [
TXT('mail._domainkey', 'v=DKIM1; k=rsa; p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtwkNVd9Mmz8S4WcfuPk0X2drG39gS8+uxAv8igRILgzWeN8j2hjeZesl8pm/1UTVU87bYcdfUgXiGfQy9nR5p/Vmt2kS7sXk9nsJ/VYENgb3IJQ6paWupSTFMyeKycJ4ZHCEZB/bVvifoG6vLKqW5jpsfCiOcfdcgXATn0UPuVx9t93yRrhoEMntMv9TSodjqd3FKCtJUoh5cNQHo0T6dWKtxoIgNi/mvZ92D/IACwu/XOU+Rq9fnoEI8GukBQUR5AkP0B/JrvwWXWX/3EjY8X37ljEX0XUdq/ShzTl5iK+CM83stgkFUQh/rpww5mnxYEW3X4uirJ7VJHmY4KPoIU+2DPjLQj9Hz63CMWY3Ks2pXWzxD3V+GI1aJTMFOv2LeHnI3ScqFaKj9FR4ZKMb0OW2BEFBIY3J3aeo/paRwdbVCMM7twDtZY9uInR/NhVa1v9hlOxwp4/2pGSKQYoN2CkAZ1Alzwf8M3EONLKeiC43JLYwKH1uBB1oikSVhMnLjG0219XvfG/tphyoOqJR/bCc2rdv5pLwKUl4wVuygfpvOw12bcvnTfYuk/BXzVHg9t4H8k/DJR6GAoeNAapXIS8AfAScF8QdKfplhKLJyQGJ6lQ75YD9IwRAN0oV+8NTjl46lI/C+b7mpfXCew+p6YPwfNvV2shiR0Ez8ZGUQIcCAwEAAQ==')
],
}
NON_CLONABLE_SPFs = {
'crans.org': [
TXT(short_name(_mx), 'v=spf1 mx:crans.org ~all') for _mx in config.dns.MXs
],
}
NAPTRs = {
'crans.org' : [ 'crans.org' : [
NAPTR('@', 5, 100, "S", "SIPS+D2T", "", '_sips._tcp.crans.org.', ttl=86400), NAPTR('@', 5, 100, "S", "SIPS+D2T", "", '_sips._tcp.crans.org.', ttl=86400),
NAPTR('@', 10, 100, "S", "SIP+D2U", "", '_sip._udp.crans.org.', ttl=86400), NAPTR('@', 10, 100, "S", "SIP+D2U", "", '_sip._udp.crans.org.', ttl=86400),
NAPTR('@', 15, 100, "S", "SIP+D2T", "", '_sip._tcp.crans.org.', ttl=86400), NAPTR('@', 15, 100, "S", "SIP+D2T", "", '_sip._tcp.crans.org.', ttl=86400),
], ]
} }
# DS à publier dans zone parentes : { parent : [ zone. TTL IN DS key_id algo_id 1 hash ] } # DS à publier dans zone parentes : { parent : [ zone. TTL IN DS key_id algo_id 1 hash ] }
@ -672,23 +478,18 @@ class dns(gen_config):
restart_cmd = '/etc/init.d/bind9 reload' restart_cmd = '/etc/init.d/bind9 reload'
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Surcharge pour affecter EXTRAS"""
self.EXTRAS = {} self.EXTRAS = {}
self.anim = None
super(dns, self).__init__(*args, **kwargs) super(dns, self).__init__(*args, **kwargs)
def gen_soa(self, ns_list, serial, ttl): def gen_soa(self, ns_list, serial, ttl):
"""Génère l'enregistrement SOA pour le domaine"""
return SOA(ns_list[0], 'root.crans.org', serial, 21600, 3600, 1209600, ttl) return SOA(ns_list[0], 'root.crans.org', serial, 21600, 3600, 1209600, ttl)
def populate_zones(self, zones, machines): def populate_zones(self, zones, machines):
"""On peuple les fichiers de zones"""
self.anim.iter=len(zones.values()) self.anim.iter=len(zones.values())
for zone in zones.values(): for zone in zones.values():
# On met les mêmes MX pour toutes les zones.
zone.extend(self.MXs) zone.extend(self.MXs)
# Les RR définis ici sont ajoutés aux zones idoines, de façon à se simplifier la vie. for rr_type in [self.SRVs, self.NATPRs, self.DSs, self.EXTRAS]:
for rr_type in [self.SRVs, self.NAPTRs, self.DSs, self.EXTRAS, self.SPFs, self.NON_CLONABLE_SPFs, self.DKIM]:
if zone.zone_name in rr_type.keys(): if zone.zone_name in rr_type.keys():
zone.extend(rr_type[zone.zone_name]) zone.extend(rr_type[zone.zone_name])
for m in machines: for m in machines:
@ -697,43 +498,31 @@ class dns(gen_config):
return zones return zones
def gen_zones_ldap(self, ttl, ns_list, serial, zones={}, zones_ldap=config.dns.zones_ldap): def gen_zones_ldap(self, ttl, ns_list, serial, zones={}, zones_ldap=config.dns.zones_ldap):
"""On génère la liste des zones ldap, à partir de config.dns. C'est un peu ici que tout commence.
Le dico zones passé en argument est modifié en place."""
for zone in zones_ldap: for zone in zones_ldap:
# On crée la zone et on l'ajoute au dico.
zones[zone]=Zone(zone, ttl, self.gen_soa(ns_list, serial, ttl), ns_list, other_zones=config.dns.zones_direct) zones[zone]=Zone(zone, ttl, self.gen_soa(ns_list, serial, ttl), ns_list, other_zones=config.dns.zones_direct)
return zones return zones
def gen_zones_reverse(self, ttl, ns_list, serial, zones={}, def gen_zones_reverse(self, ttl, ns_list, serial, zones={},
zones_reverse_v4=config.dns.zones_reverse, zones_reverse_v4=config.dns.zones_reverse, zones_reverse_v6=config.dns.zones_reverse_v6):
zones_reverse_v6=config.dns.zones_reverse_v6):
"""Deuxième gros morceau, les reverses, pareil, on peuple depuis config.dns, et on crée toutes les zones
idoines. Pareil, ici, le dico zones est modifié en place"""
for net in ZoneReverse.network_to_arpanets(zones_reverse_v4 + zones_reverse_v6): for net in ZoneReverse.network_to_arpanets(zones_reverse_v4 + zones_reverse_v6):
# On crée la zone et on l'ajoute au dico.
zones[str(net)]=ZoneReverse(str(net), ttl, self.gen_soa(ns_list, serial, ttl), ns_list) zones[str(net)]=ZoneReverse(str(net), ttl, self.gen_soa(ns_list, serial, ttl), ns_list)
return zones return zones
def gen_zones_clone(self, ttl, ns_list, serial, zones={}): def gen_zones_clone(self, ttl, ns_list, serial, zones={}):
"""Les clônes, à savoir crans.eu et cie, dico zones modifié en place.""" for zone_clone, zones_alias in config.dns.zone_alias.items():
for zone_clone, zones_alias in config.dns.zone_alias.iteritems():
for zone in zones_alias: for zone in zones_alias:
# On crée la zone et on l'ajoute au dico.
zones[zone]=ZoneClone(zone, zones[zone_clone], self.gen_soa(ns_list, serial, ttl)) zones[zone]=ZoneClone(zone, zones[zone_clone], self.gen_soa(ns_list, serial, ttl))
# Et on ajoute les enregistrements concernant la zone clône (pas la clônée, ça for rr_type in [self.SRVs, self.NATPRs, self.DSs]:
# a déjà été fait à l'init) à la main.
for rr_type in [self.SRVs, self.NAPTRs, self.DSs, self.SPFs]:
if zones[zone].zone_name in rr_type.keys(): if zones[zone].zone_name in rr_type.keys():
zones[zone].extend(rr_type[zones[zone].zone_name]) zones[zone].extend(rr_type[zones[zone].zone_name])
return zones return zones
def gen_zones(self, ttl, serial, ns_list, populate=True): def gen_zones(self, ttl, serial, ns_list, populate=True):
"""On chaîne les différents gen_zones_*"""
zones = {} zones = {}
self.gen_zones_ldap(ttl, ns_list, serial, zones) self.gen_zones_ldap(ttl, ns_list, serial, zones)
self.gen_zones_reverse(ttl, ns_list, serial, zones) self.gen_zones_reverse(ttl, ns_list, serial, zones)
# Si populate, on remplit les zones avec les enregistrements \o/
if populate: if populate:
conn = lc_ldap.shortcuts.lc_ldap_admin() conn = lc_ldap.shortcuts.lc_ldap_admin()
machines = conn.search(u"mid=*", sizelimit=10000) machines = conn.search(u"mid=*", sizelimit=10000)
@ -745,15 +534,14 @@ class dns(gen_config):
self.gen_zones_clone(ttl, ns_list, serial, zones) self.gen_zones_clone(ttl, ns_list, serial, zones)
return zones return zones
def gen_tv(self, populate=True): def gen_tv(self, populate=True):
"""Génération de la TV, un peu à part."""
self.anim = affich_tools.anim('\tgénération de la zone tv') self.anim = affich_tools.anim('\tgénération de la zone tv')
zones = {} zones = {}
serial = self.serial serial = self.serial
self.gen_zones_reverse(self.TTL, config.dns.DNSs, serial, zones, zones_reverse_v4=config.NETs['multicast'], zones_reverse_v6=[]) self.gen_zones_reverse(self.TTL, config.dns.DNSs, serial, zones, zones_reverse_v4=config.NETs['multicast'], zones_reverse_v6=[])
self.gen_zones_ldap(self.TTL, config.dns.DNSs, serial, zones, zones_ldap=[config.dns.zone_tv]) self.gen_zones_ldap(self.TTL, config.dns.DNSs, serial, zones, zones_ldap=[config.dns.zone_tv])
# Pareil, si on doit peupler on ajoute ce qu'il faut niveau machines.
if populate: if populate:
conn = lc_ldap.shortcuts.lc_ldap_admin() conn = lc_ldap.shortcuts.lc_ldap_admin()
machines=conn.machinesMulticast() machines=conn.machinesMulticast()
@ -761,16 +549,13 @@ class dns(gen_config):
self.populate_zones(zones, machines) self.populate_zones(zones, machines)
for zone in zones.values(): for zone in zones.values():
zone.write(os.path.join(self.DNS_DIR, 'db.%s' % (zone.zone_name,))) zone.write(self.DNS_DIR + 'db.' + zone.zone_name)
self.anim.reinit() self.anim.reinit()
print affich_tools.OK print affich_tools.OK
return zones return zones
def gen_master(self): def gen_master(self):
"""Pour le serveur maître.
Appelle gen_zones puis écrit les fichiers."""
# Syntaxe utilisée dans le fichier DNS_CONF pour définir une zone sur le maître # Syntaxe utilisée dans le fichier DNS_CONF pour définir une zone sur le maître
zone_template=""" zone_template="""
zone "%(zone_name)s" { zone "%(zone_name)s" {
@ -782,16 +567,14 @@ zone "%(zone_name)s" {
with open(self.DNS_CONF, 'w') as f: with open(self.DNS_CONF, 'w') as f:
f.write(disclamer) f.write(disclamer)
for zone in zones.values(): for zone in zones.values():
zone.write(os.path.join(self.DNS_DIR, 'db.%s' % (zone.zone_name,))) zone.write(self.DNS_DIR + 'db.' + zone.zone_name)
if zone.zone_name in config.dns.zones_dnssec: if zone.zone_name in config.dns.zones_dnssec:
zone_path = os.path.join(self.DNSSEC_DIR, 'db.%s' % (zone.zone_name,)) zone_path = self.DNSSEC_DIR + 'db.' + zone.zone_name
else: else:
zone_path = os.path.join(self.DNS_DIR, 'db.%s' % (zone.zone_name,)) zone_path = self.DNS_DIR + 'db.' + zone.zone_name
f.write(zone_template % {'zone_name' : zone.zone_name, 'zone_path' : zone_path}) f.write(zone_template % {'zone_name' : zone.zone_name, 'zone_path' : zone_path})
def gen_slave(self): def gen_slave(self):
"""Pour les slaves, fait l'écriture de la conf dans bcfg2, mais on ne peuple rien !
On ne fait qu'écrire le fichier zone_crans."""
zone_template=""" zone_template="""
zone "%(zone_name)s" { zone "%(zone_name)s" {
type slave; type slave;
@ -804,9 +587,9 @@ zone "%(zone_name)s" {
f.write(disclamer) f.write(disclamer)
for zone in zones.values(): for zone in zones.values():
if zone.zone_name in config.dns.zones_dnssec: if zone.zone_name in config.dns.zones_dnssec:
zone_path = os.path.join(self.DNSSEC_DIR, 'db.%s' % (zone.zone_name,)) zone_path = self.DNSSEC_DIR + 'db.' + zone.zone_name
else: else:
zone_path = os.path.join(self.DNS_DIR, 'db.%s' % (zone.zone_name,)) zone_path = self.DNS_DIR + 'db.' + zone.zone_name
f.write(zone_template % {'zone_name' : zone.zone_name, 'zone_path' : zone_path, 'master_ip' : config.dns.master}) f.write(zone_template % {'zone_name' : zone.zone_name, 'zone_path' : zone_path, 'master_ip' : config.dns.master})
def _gen(self): def _gen(self):
@ -817,27 +600,27 @@ zone "%(zone_name)s" {
if __name__ == '__main__' : if __name__ == '__main__' :
HOSTNAME = short_name(gethostname()) hostname = short_name(gethostname())
if HOSTNAME == short_name(config.bcfg2_main): if hostname == short_name(config.bcfg2_main):
print "Reconfiguration du fichier de BCfg2 pour configurer le bind d'un serveur en esclave (pensez à lancer bcfg2 sur les esclaves)." print "Reconfiguration du fichier de BCfg2 pour configurer le bind d'un serveur en esclave (pensez à lancer bcfg2 sur les esclaves)."
CONFIG = dns() c = dns()
CONFIG.gen_slave() c.gen_slave()
elif HOSTNAME == short_name(config.dns.DNSs[0]): elif hostname == short_name(config.dns.DNSs[0]):
print "Serveur maître :" print "Serveur maître :"
CONFIG = dns() c = dns()
ZONES = CONFIG.gen_tv() zones = c.gen_tv()
import subprocess import subprocess
for ZONE in ZONES.values(): for zone in zones.values():
if ZONE.zone_name in config.dns.zones_dnssec: if zone.zone_name in config.dns.zones_dnssec:
ARGS = ("/usr/sbin/ods-signer sign %s" % ZONE.zone_name).split() args=("/usr/sbin/ods-signer sign %s" % zone.zone_name).split()
PROCESS = subprocess.Popen(ARGS, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p=subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
RET = PROCESS.communicate() ret=p.communicate()
print RET[0].strip() print ret[0].strip()
if RET[1].strip(): if ret[1].strip():
print RET[1].strip() print ret[1].strip()
print "Ce serveur est également serveur maitre pour les autres zones dns, mais leur reconfiguration se fait par generate." print "Ce serveur est également serveur maitre pour les autres zones dns, mais leur reconfiguration se fait par generate."
elif HOSTNAME in [short_name(FULLHOSTNAME) for FULLHOSTNAME in config.dns.DNSs[1:]]: elif hostname in map(lambda fullhostname : short_name(fullhostname),config.dns.DNSs[1:]):
print "Ce serveur est esclave! Lancez ce script sur %s, puis lancez bcfg2 ici" % (config.bcfg2_main,) print "Ce serveur est esclave! Lancez ce script sur %s, puis lancez bcfg2 ici" % bcfg2_main
else: else:
print "Ce serveur ne correspond à rien pour la configuration DNS." print "Ce serveur ne correspond à rien pour la configuration DNS."

View file

@ -48,15 +48,11 @@ class dydhcp:
msg.obj.append((b"hardware-address", pack_mac(mac))) msg.obj.append((b"hardware-address", pack_mac(mac)))
msg.obj.append((b"hardware-type", struct.pack("!I", 1))) msg.obj.append((b"hardware-type", struct.pack("!I", 1)))
msg.obj.append((b"ip-address", pack_ip(ip))) msg.obj.append((b"ip-address", pack_ip(ip)))
# See patch for hostnames at
# http://jpmens.net/2011/07/20/dynamically-add-static-leases-to-dhcpd/
if name: if name:
statem = b'supersede host-name "%s";' % bytes(name)
msg.obj.append((b"name", bytes(name))) msg.obj.append((b"name", bytes(name)))
msg.obj.append((b"statements", statem)) msg.obj.append((b"client-hostname", bytes(name)))
conn=Omapi(self.server, 9991,self.dhcp_omapi_keyname, self.dhcp_omapi_key) conn=Omapi(self.server, 9991,self.dhcp_omapi_keyname, self.dhcp_omapi_key)
response = conn.query_server(msg) response = conn.query_server(msg)
# print response.dump() # DEBUG purpose (repr() marche po)
conn.close() conn.close()
def del_host(self, ip,mac): def del_host(self, ip,mac):

View file

@ -45,13 +45,9 @@ class exemptions(gen_config):
for machine in machines: for machine in machines:
for destination in machine["exempt"]: for destination in machine["exempt"]:
if destination.value.version == 4: if destination.value.version == 4:
if not machine['ipHostNumber']:
continue
source = str(machine["ipHostNumber"][0]) source = str(machine["ipHostNumber"][0])
requete = "INSERT INTO exemptes (ip_crans, ip_dest) VALUES ('%s','%s')" % (source, destination) requete = "INSERT INTO exemptes (ip_crans, ip_dest) VALUES ('%s','%s')" % (source, destination)
else: else:
if not machine['macAddress']:
continue
source = str(machine["macAddress"][0]) source = str(machine["macAddress"][0])
requete = "INSERT INTO exemptes6 (mac_crans, ip_dest) VALUES ('%s','%s')" % (source, destination) requete = "INSERT INTO exemptes6 (mac_crans, ip_dest) VALUES ('%s','%s')" % (source, destination)
# Si ip vide, passons au suivant # Si ip vide, passons au suivant
@ -90,7 +86,6 @@ class machines(gen_config):
if not m['macAddress'][0].value == '<automatique>': if not m['macAddress'][0].value == '<automatique>':
curseur.execute("INSERT INTO machines (mac_addr, type, id) VALUES ('%s','adherent',%s);" % (m['macAddress'][0], m.proprio()['aid'][0].value)) curseur.execute("INSERT INTO machines (mac_addr, type, id) VALUES ('%s','adherent',%s);" % (m['macAddress'][0], m.proprio()['aid'][0].value))
elif m.proprio().__class__ == lc_ldap.objets.AssociationCrans: elif m.proprio().__class__ == lc_ldap.objets.AssociationCrans:
if not m['macAddress'][0].value == '<automatique>':
curseur.execute("INSERT INTO machines (mac_addr, type, id) VALUES ('%s','crans',%s);" % (m['macAddress'][0], m['mid'][0].value)) curseur.execute("INSERT INTO machines (mac_addr, type, id) VALUES ('%s','crans',%s);" % (m['macAddress'][0], m['mid'][0].value))
# on commit # on commit
pgsql.commit() pgsql.commit()

View file

@ -34,13 +34,13 @@ class firewall(utils.firewall_tools) :
self.use_ipset = [self.blacklist_hard, self.test_mac_ip, self.blacklists] self.use_ipset = [self.blacklist_hard, self.test_mac_ip, self.blacklists]
self.ipset['mac_ip']={ self.ipset['mac_ip']={
'adh' : Ipset("MAC-IP-ADH", "bitmap:ip,mac", "range 138.231.136.0-138.231.151.255"), 'adh' : Ipset("MAC-IP-ADH","macipmap","--from 138.231.136.0 --to 138.231.151.255"),
'adm' : Ipset("MAC-IP-ADM", "bitmap:ip,mac", "range 10.231.136.0-10.231.136.255"), 'adm' : Ipset("MAC-IP-ADM","macipmap","--from 10.231.136.0 --to 10.231.136.255"),
'app' : Ipset("MAC-IP-APP", "bitmap:ip,mac", "range 10.2.9.0-10.2.9.255"), 'app' : Ipset("MAC-IP-APP","macipmap","--from 10.2.9.0 --to 10.2.9.255"),
} }
self.ipset['blacklist']={ self.ipset['blacklist']={
'hard' : Ipset("BLACKLIST-HARD", "hash:ip"), 'hard' : Ipset("BLACKLIST-HARD","ipmap","--from 138.231.136.0 --to 138.231.151.255"),
} }
@ -110,7 +110,7 @@ class firewall(utils.firewall_tools) :
if fill_ipset: if fill_ipset:
# On récupère la liste de toutes les ips blacklistés hard # On récupère la liste de toutes les ips blacklistés hard
bl_hard_ips = self.blacklisted_ips(config.blacklist_sanctions) bl_hard_ips = self.blacklisted_ips(config.blacklist_sanctions, config.NETs['all'])
anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['hard']) anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['hard'])
self.ipset['blacklist']['hard'].restore(bl_hard_ips) self.ipset['blacklist']['hard'].restore(bl_hard_ips)
print OK print OK
@ -131,7 +131,7 @@ class firewall(utils.firewall_tools) :
def mac_ip_remove(self, mac, ip): def mac_ip_remove(self, mac, ip):
machine = {'macAddress':[mac], 'ipHostNumber': [ip]} machine = {'macAddress':[mac], 'ipHostNumber': [ip]}
self.test_mac_ip_dispatch(lambda set, data: self.ipset['mac_ip'][set].delete(data), machine) self.test_mac_ip_dispatch(lambda set, data: set.ipset['mac_ip'][set].delete(data), machine)
def test_mac_ip_dispatch(self, func, machine): def test_mac_ip_dispatch(self, func, machine):
"""Détermine à quel set de mac-ip appliquer la fonction ``func`` (add, delete, append, ...)""" """Détermine à quel set de mac-ip appliquer la fonction ``func`` (add, delete, append, ...)"""
@ -142,7 +142,7 @@ class firewall(utils.firewall_tools) :
# Si la machines est sur le réseau des adhérents # Si la machines est sur le réseau des adhérents
if utils.AddrInNet(str(ip), config.NETs['wifi']): if utils.AddrInNet(str(ip), config.NETs['wifi']):
# Les machines wifi sont vues à travers komaz # Les machines wifi sont vues à travers komaz
func('adh', "%s,%s" % (ip, config.mac_du_routeur)) func('adh', "%s,%s" % (ip, config.mac_komaz))
elif utils.AddrInNet(str(ip), config.NETs['fil']): elif utils.AddrInNet(str(ip), config.NETs['fil']):
func('adh', "%s,%s" % (ip, machine['macAddress'][0])) func('adh', "%s,%s" % (ip, machine['macAddress'][0]))
# Si la machine est sur le réseau admin # Si la machine est sur le réseau admin
@ -180,7 +180,7 @@ class firewall(utils.firewall_tools) :
# Proxy ARP de Komaz et Titanic pour OVH # Proxy ARP de Komaz et Titanic pour OVH
ip_soyouz = self.conn.search(u"host=soyouz.adm.crans.org")[0]['ipHostNumber'][0] ip_soyouz = self.conn.search(u"host=soyouz.adm.crans.org")[0]['ipHostNumber'][0]
self.add(table, chain, '-m mac -s %s --mac-source %s -j RETURN' % (ip_soyouz, config.mac_du_routeur)) self.add(table, chain, '-m mac -s %s --mac-source %s -j RETURN' % (ip_soyouz, config.mac_komaz))
self.add(table, chain, '-m mac -s %s --mac-source %s -j RETURN' % (ip_soyouz, config.mac_titanic)) self.add(table, chain, '-m mac -s %s --mac-source %s -j RETURN' % (ip_soyouz, config.mac_titanic))
self.add(table, chain, '-j REJECT') self.add(table, chain, '-j REJECT')
@ -238,7 +238,7 @@ class firewall_wifionly(firewall):
if utils.AddrInNet(str(ip), config.NETs['wifi']): if utils.AddrInNet(str(ip), config.NETs['wifi']):
func('adh', "%s,%s" % (ip, machine['macAddress'][0])) func('adh', "%s,%s" % (ip, machine['macAddress'][0]))
elif utils.AddrInNet(str(ip), config.NETs['fil']): elif utils.AddrInNet(str(ip), config.NETs['fil']):
func('adh', "%s,%s" % (ip, config.mac_du_routeur)) func('adh', "%s,%s" % (ip, config.mac_komaz))
# Si la machine est sur le réseau admin # Si la machine est sur le réseau admin
elif utils.AddrInNet(str(ip), config.NETs['adm']): elif utils.AddrInNet(str(ip), config.NETs['adm']):
func('adm', "%s,%s" % (ip, machine['macAddress'][0])) func('adm', "%s,%s" % (ip, machine['macAddress'][0]))

View file

@ -21,9 +21,8 @@ firewall = {
'odlyd' : komaz.firewall, 'odlyd' : komaz.firewall,
'zamok' : zamok.firewall, 'zamok' : zamok.firewall,
'routeur' : routeur.firewall, 'routeur' : routeur.firewall,
'gordon' : base.firewall_routeur,
'eap' : base.firewall_wifionly, 'eap' : base.firewall_wifionly,
'pea' : base.firewall_wifionly,
'radius' : base.firewall_wifionly
} }
if hostname in firewall.keys(): if hostname in firewall.keys():

View file

@ -19,7 +19,6 @@ class firewall(base.firewall_routeur):
'ssh_on_https' : self.ssh_on_https, 'ssh_on_https' : self.ssh_on_https,
'connexion_secours' : self.connexion_secours, 'connexion_secours' : self.connexion_secours,
'connexion_appartement' : self.connexion_appartement, 'connexion_appartement' : self.connexion_appartement,
'connexion_wififederez' : self.connexion_wififederez,
'blacklist_soft' : self.blacklist_soft, 'blacklist_soft' : self.blacklist_soft,
'blacklist_upload' : self.blacklist_upload, 'blacklist_upload' : self.blacklist_upload,
'reseaux_non_routable' : self.reseaux_non_routable, 'reseaux_non_routable' : self.reseaux_non_routable,
@ -33,21 +32,18 @@ class firewall(base.firewall_routeur):
self.use_tc.extend([self.limitation_debit]) self.use_tc.extend([self.limitation_debit])
self.ipset['reseaux_non_routable'] = { self.ipset['reseaux_non_routable'] = {
'deny' : base.Ipset("RESEAUX-NON-ROUTABLE-DENY", "hash:net"), 'deny' : base.Ipset("RESEAUX-NON-ROUTABLE-DENY","nethash"),
'allow' : base.Ipset("RESEAUX-NON-ROUTABLE-ALLOW", "hash:net"), 'allow' : base.Ipset("RESEAUX-NON-ROUTABLE-ALLOW","nethash"),
} }
self.ipset['blacklist'].update({ self.ipset['blacklist'].update({
'soft' : base.Ipset("BLACKLIST-SOFT", "hash:ip"), 'soft' : base.Ipset("BLACKLIST-SOFT","ipmap","--from 138.231.136.0 --to 138.231.151.255"),
'upload' : base.Ipset("BLACKLIST-UPLOAD", "hash:ip"), 'upload' : base.Ipset("BLACKLIST-UPLOAD","ipmap","--from 138.231.136.0 --to 138.231.151.255"),
}) })
# Portail captif/blacklist soft: ipset des gens ayant cliqué pour continuer à naviguer # Portail captif/blacklist soft: ipset des gens ayant cliqué pour continuer à naviguer
self.ipset['confirmation'] = base.Ipset("CONFIRMATION", "hash:ip", "") self.ipset['confirmation'] = base.Ipset("CONFIRMATION", "hash:ip", "")
# Ouvertures de ports temporaires
self.ipset['ip_port_tmp'] = base.Ipset("IP-PORT-TMP", "hash:ip,port", "timeout 3600")
def blacklist_maj(self, ips): def blacklist_maj(self, ips):
"""Mise à jour des blacklistes""" """Mise à jour des blacklistes"""
self.blacklist_hard_maj(ips) self.blacklist_hard_maj(ips)
@ -97,7 +93,6 @@ class firewall(base.firewall_routeur):
self.add(table, chain, '-p icmp -j ACCEPT') self.add(table, chain, '-p icmp -j ACCEPT')
self.add(table, chain, '-m state --state RELATED,ESTABLISHED -j ACCEPT') self.add(table, chain, '-m state --state RELATED,ESTABLISHED -j ACCEPT')
self.add(table, chain, '-j %s' % blacklist_soft_chain) self.add(table, chain, '-j %s' % blacklist_soft_chain)
self.add(table, chain, '-j %s' % self.limit_ssh_connexion(table))
for net in base.config.NETs['all'] + base.config.NETs['adm'] + base.config.NETs['personnel-ens']: for net in base.config.NETs['all'] + base.config.NETs['adm'] + base.config.NETs['personnel-ens']:
self.add(table, chain, '-s %s -j %s' % (net, mac_ip_chain)) self.add(table, chain, '-s %s -j %s' % (net, mac_ip_chain))
self.add(table, chain, '-j %s' % blacklist_hard_chain) self.add(table, chain, '-j %s' % blacklist_hard_chain)
@ -117,9 +112,8 @@ class firewall(base.firewall_routeur):
self.add(table, chain, '-s %s -j %s' % (net, mac_ip_chain)) self.add(table, chain, '-s %s -j %s' % (net, mac_ip_chain))
self.add(table, chain, '-j %s' % self.connexion_secours(table)) self.add(table, chain, '-j %s' % self.connexion_secours(table))
self.add(table, chain, '-j %s' % self.connexion_appartement(table)) self.add(table, chain, '-j %s' % self.connexion_appartement(table))
self.add(table, chain, '-j %s' % self.connexion_wififederez(table))
self.add(table, chain, '-j %s' % self.ingress_filtering(table)) self.add(table, chain, '-j %s' % self.ingress_filtering(table))
self.add(table, chain, '-j %s' % self.limit_ssh_connexion(table, ttl=30, counter_name="SSH2")) self.add(table, chain, '-j %s' % self.limit_ssh_connexion(table))
self.add(table, chain, '-i %s -j %s' % (dev['out'], self.filtrage_ports(table))) self.add(table, chain, '-i %s -j %s' % (dev['out'], self.filtrage_ports(table)))
self.add(table, chain, '-o %s -j %s' % (dev['out'], self.filtrage_ports(table))) self.add(table, chain, '-o %s -j %s' % (dev['out'], self.filtrage_ports(table)))
return return
@ -132,10 +126,8 @@ class firewall(base.firewall_routeur):
self.add(table, chain, '-j %s' % self.ssh_on_https(table)) self.add(table, chain, '-j %s' % self.ssh_on_https(table))
self.add(table, chain, '-j %s' % self.connexion_secours(table)) self.add(table, chain, '-j %s' % self.connexion_secours(table))
self.add(table, chain, '-j %s' % self.blacklist_soft(table)) self.add(table, chain, '-j %s' % self.blacklist_soft(table))
self.add(table, chain, '-j %s' % self.blacklist_hard(table))
chain = 'POSTROUTING' chain = 'POSTROUTING'
self.add(table, chain, '-j %s' % self.connexion_wififederez(table))
self.add(table, chain, '-j %s' % self.connexion_appartement(table)) self.add(table, chain, '-j %s' % self.connexion_appartement(table))
return return
@ -155,13 +147,13 @@ class firewall(base.firewall_routeur):
self.apply(table, chain) self.apply(table, chain)
return chain return chain
def limit_ssh_connexion(self, table=None, apply=False, ttl=120, counter_name="SSH"): def limit_ssh_connexion(self, table=None, apply=False):
chain = 'LIMIT-%s-CONNEXION' % (counter_name,) chain = 'LIMIT-SSH-CONNEXION'
if table == 'filter': if table == 'filter':
pretty_print(table, chain) pretty_print(table, chain)
self.add(table, chain, '-i %s -p tcp --dport ssh -m state --state NEW -m recent --name %s --set' % (dev['out'], counter_name)) self.add(table, chain, '-i %s -p tcp --dport ssh -m state --state NEW -m recent --name SSH --set' % dev['out'])
self.add(table, chain, '-i %s -p tcp --dport ssh -m state --state NEW -m recent --name %s --update --seconds %s --hitcount 10 --rttl -j DROP' % (dev['out'], counter_name, ttl)) self.add(table, chain, '-i %s -p tcp --dport ssh -m state --state NEW -m recent --name SSH --update --seconds 30 --hitcount 10 --rttl -j DROP' % dev['out'])
print OK print OK
if apply: if apply:
@ -251,7 +243,6 @@ class firewall(base.firewall_routeur):
if table == 'nat': if table == 'nat':
pretty_print(table, chain) pretty_print(table, chain)
self.add(table, chain, '-p tcp -d 138.231.136.2 --dport 22 -j DNAT --to-destination 138.231.136.1:22') # redirection du ssh vers zamok self.add(table, chain, '-p tcp -d 138.231.136.2 --dport 22 -j DNAT --to-destination 138.231.136.1:22') # redirection du ssh vers zamok
self.add(table, chain, '-p tcp -d 138.231.136.2 --dport 80 -j DNAT --to-destination 138.231.136.1:81') # redirection du ssh vers zamok a travers httptunnel
self.add(table, chain, '-p tcp -d 138.231.136.2 --dport 443 -j DNAT --to-destination 138.231.136.1:22') # redirection du ssh vers zamok (pour passer dans un proxy, avec corkscrew) self.add(table, chain, '-p tcp -d 138.231.136.2 --dport 443 -j DNAT --to-destination 138.231.136.1:22') # redirection du ssh vers zamok (pour passer dans un proxy, avec corkscrew)
print OK print OK
@ -306,29 +297,6 @@ class firewall(base.firewall_routeur):
self.apply(table, chain) self.apply(table, chain)
return chain return chain
def connexion_wififederez(self, table=None, apply=False):
"""PNAT le vlan wififederez derrière wififederez.crans.org"""
chain = 'CONNEXION-WIFIFEDEREZ'
if table == 'nat':
pretty_print(table, chain)
for dev_key in ['out', 'fil', 'wifi']:
for net in base.config.NETs['federez']:
self.add(table, chain, '-o %s -s %s -j SNAT --to 138.231.136.77' % (dev[dev_key], net))
print OK
if table == 'filter':
pretty_print(table, chain)
for net in base.config.NETs['federez']:
self.add(table, chain, '-s %s -j ACCEPT' % net)
self.add(table, chain, '-d %s -j ACCEPT' % net)
print OK
if apply:
self.apply(table, chain)
return chain
def blacklist_soft_maj(self, ip_list): def blacklist_soft_maj(self, ip_list):
self.blacklist_soft(fill_ipset=True) self.blacklist_soft(fill_ipset=True)
# for ip in ip_list: # for ip in ip_list:
@ -347,7 +315,7 @@ class firewall(base.firewall_routeur):
if fill_ipset: if fill_ipset:
# On récupère la liste de toutes les ips blacklistés soft # On récupère la liste de toutes les ips blacklistés soft
bl_soft_ips = self.blacklisted_ips(base.config.blacklist_sanctions_soft) bl_soft_ips = self.blacklisted_ips(base.config.blacklist_sanctions_soft, base.config.NETs['all'])
anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['soft']) anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['soft'])
self.ipset['blacklist']['soft'].restore(bl_soft_ips) self.ipset['blacklist']['soft'].restore(bl_soft_ips)
print OK print OK
@ -372,41 +340,6 @@ class firewall(base.firewall_routeur):
self.apply(table, chain) self.apply(table, chain)
return chain return chain
def blacklist_hard(self, table=None, fill_ipset=False, apply=False):
"""Bloque tout, sauf le 80 pour afficher le portail captif"""
chain = 'BLACKLIST_HARD'
if fill_ipset:
# On récupère la liste de toutes les ips blacklistés hard
bl_hard_ips = self.blacklisted_ips(base.config.blacklist_sanctions)
anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['hard'])
self.ipset['blacklist']['hard'].restore(bl_hard_ips)
print OK
if table == 'filter':
pretty_print(table, chain)
# Same as blacklist_soft: autorise le port 80 et 3128 vers soi-même
self.add(table, chain, '-p tcp --dport 80 -m set --match-set %s src -j ACCEPT' % self.ipset['blacklist']['hard'] )
self.add(table, chain, '-p tcp --sport 80 -m set --match-set %s dst -j ACCEPT' % self.ipset['blacklist']['hard'] )
self.add(table, chain, '-p tcp -d 10.231.136.4 --dport 3128 -m set --match-set %s src -j ACCEPT' % self.ipset['blacklist']['hard'] )
self.add(table, chain, '-p tcp -s 10.231.136.4 --sport 3128 -m set --match-set %s dst -j ACCEPT' % self.ipset['blacklist']['hard'] )
# Mais on continue en refusant le reste
self.add(table, chain, '-m set --match-set %s src -j REJECT' % self.ipset['blacklist']['hard'] )
self.add(table, chain, '-m set --match-set %s dst -j REJECT' % self.ipset['blacklist']['hard'] )
print OK
if table == 'nat':
pretty_print(table, chain)
for net in base.config.NETs['all']:
self.add(table, chain, '-d %s -j RETURN' % net)
self.add(table, chain, '-p tcp --dport 80 -m set --match-set %s src -j RETURN' % self.ipset['confirmation'] ) # Les gens qui ont cliqué -> fine !
self.add(table, chain, '-p tcp --dport 80 -m set --match-set %s src -j DNAT --to-destination 10.231.136.4:3128' % self.ipset['blacklist']['hard'] )
print OK
if apply:
self.apply(table, chain)
return chain
def blacklist_upload_maj(self, ip_list): def blacklist_upload_maj(self, ip_list):
self.blacklist_upload(fill_ipset=True) self.blacklist_upload(fill_ipset=True)
# for ip in ip_list: # for ip in ip_list:
@ -425,7 +358,7 @@ class firewall(base.firewall_routeur):
if fill_ipset: if fill_ipset:
# On récupère la liste de toutes les ips blacklistés pour upload # On récupère la liste de toutes les ips blacklistés pour upload
bl_upload_ips = self.blacklisted_ips(base.config.blacklist_bridage_upload) bl_upload_ips = self.blacklisted_ips(base.config.blacklist_bridage_upload, base.config.NETs['all'])
anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['upload']) anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['upload'])
self.ipset['blacklist']['upload'].restore(bl_upload_ips) self.ipset['blacklist']['upload'].restore(bl_upload_ips)
print OK print OK
@ -493,7 +426,6 @@ class firewall(base.firewall_routeur):
if table == 'filter': if table == 'filter':
pretty_print(table, chain) pretty_print(table, chain)
self.add(table, chain, '-m set --match-set %s dst,dst -j ACCEPT' % self.ipset['ip_port_tmp'] )
for net in base.config.NETs['serveurs']: for net in base.config.NETs['serveurs']:
for proto in base.config.firewall.srv_ports_default.keys(): for proto in base.config.firewall.srv_ports_default.keys():
if base.config.firewall.srv_ports_default[proto]['output']: if base.config.firewall.srv_ports_default[proto]['output']:
@ -532,10 +464,8 @@ class firewall(base.firewall_routeur):
debit_max = base.config.firewall.debit_max debit_max = base.config.firewall.debit_max
bl_upload_debit_max = base.config.firewall.bl_upload_debit_max bl_upload_debit_max = base.config.firewall.bl_upload_debit_max
appt_upload_max = base.config.firewall.appt_upload_max appt_upload_max = base.config.firewall.appt_upload_max
federez_upload_max = base.config.firewall.federez_upload_max
uplink_speed = '1024mbit' uplink_speed = '1024mbit'
if table == 'mangle': if table == 'mangle':
pretty_print(table, chain) pretty_print(table, chain)
# Pas de QoS vers/depuis la zone ENS # Pas de QoS vers/depuis la zone ENS
@ -563,11 +493,6 @@ class firewall(base.firewall_routeur):
self.add(table, chain, '-o %s -d %s -j CLASSIFY --set-class 1:3' % (dev['app'], net)) self.add(table, chain, '-o %s -d %s -j CLASSIFY --set-class 1:3' % (dev['app'], net))
self.add(table, chain, '-o %s -s %s -j CLASSIFY --set-class 1:2' % (dev['out'], net)) self.add(table, chain, '-o %s -s %s -j CLASSIFY --set-class 1:2' % (dev['out'], net))
# Classification pour federez wifi
for net in base.config.NETs['federez']:
self.add(table, chain, '-o %s -d %s -j CLASSIFY --set-class 1:5' % (dev['federez'], net))
self.add(table, chain, '-o %s -s %s -j CLASSIFY --set-class 1:4' % (dev['out'], net))
# Classification pour la voip # Classification pour la voip
self.add(table, chain, '-d sip.crans.org -j CLASSIFY --set-class 1:12') self.add(table, chain, '-d sip.crans.org -j CLASSIFY --set-class 1:12')
self.add(table, chain, '-s sip.crans.org -j CLASSIFY --set-class 1:12') self.add(table, chain, '-s sip.crans.org -j CLASSIFY --set-class 1:12')
@ -584,17 +509,17 @@ class firewall(base.firewall_routeur):
utils.tc("class add dev %s parent 1: classid 1:1 " utils.tc("class add dev %s parent 1: classid 1:1 "
"htb rate %s ceil %s" % (dev[int_key], uplink_speed, uplink_speed)) "htb rate %s ceil %s" % (dev[int_key], uplink_speed, uplink_speed))
utils.tc("class add dev %s parent 1:1 classid 1:2 " utils.tc("class add dev %s parent 1:1 classid 1:2 "
"htb rate %smbit ceil %smbit" % (dev[int_key], debit_max[int_key], debit_max[int_key])) "htb rate %smbit ceil %smbit" % (dev[int_key], debit_max, debit_max))
# Classe par defaut # Classe par defaut
utils.tc('class add dev %s parent 1:2 classid 1:10 ' utils.tc('class add dev %s parent 1:2 classid 1:10 '
'htb rate %smbit ceil %smbit prio 1' % (dev[int_key], debit_max[int_key], debit_max[int_key])) 'htb rate %smbit ceil %smbit prio 1' % (dev[int_key], debit_max, debit_max))
utils.tc('qdisc add dev %s parent 1:10 ' utils.tc('qdisc add dev %s parent 1:10 '
'handle 10: sfq perturb 10' % dev[int_key]) 'handle 10: sfq perturb 10' % dev[int_key])
# Classe par pour la voip # Classe par pour la voip
utils.tc('class add dev %s parent 1:2 classid 1:12 ' utils.tc('class add dev %s parent 1:2 classid 1:12 '
'htb rate %smbit ceil %smbit prio 0' % (dev[int_key], debit_max[int_key], debit_max[int_key])) 'htb rate %smbit ceil %smbit prio 0' % (dev[int_key], debit_max, debit_max))
utils.tc('qdisc add dev %s parent 1:12 ' utils.tc('qdisc add dev %s parent 1:12 '
'handle 12: sfq perturb 10' % dev[int_key]) 'handle 12: sfq perturb 10' % dev[int_key])
@ -622,34 +547,10 @@ class firewall(base.firewall_routeur):
# Classe pour le download des apparetments # Classe pour le download des apparetments
utils.tc("class add dev %s parent 1: classid 1:3 " utils.tc("class add dev %s parent 1: classid 1:3 "
"htb rate %smbit ceil %smbit" % (dev[int_key], debit_max['total']/10, debit_max['total']/2)) "htb rate %smbit ceil %smbit" % (dev[int_key], debit_max/10, debit_max/2))
utils.tc('qdisc add dev %s parent 1:3 ' utils.tc('qdisc add dev %s parent 1:3 '
'handle 3: sfq perturb 10' % dev[int_key]) 'handle 3: sfq perturb 10' % dev[int_key])
# Class du vlan wifi federez, on bride l'upload/download, à 10 mbytes/sec
for int_key in ['federez']:
try:
utils.tc('qdisc del dev %s root' % dev[int_key])
except utils.TcError:
pass
utils.tc('qdisc add dev %s root handle 1: htb r2q 1' % dev[int_key])
utils.tc("class add dev %s parent 1: classid 1:1 "
"htb rate %smbps ceil %smbps" % (dev[int_key], federez_upload_max, federez_upload_max))
# Classe pour l'upload wifi federez
utils.tc("class add dev %s parent 1:1 classid 1:4 "
"htb rate %smbps ceil %smbps" % (dev[int_key], federez_upload_max, federez_upload_max))
utils.tc('qdisc add dev %s parent 1:4 '
'handle 2: sfq perturb 10' % dev[int_key])
# Classe pour le download wifi federez
utils.tc("class add dev %s parent 1: classid 1:5 "
"htb rate %smbit ceil %smbit" % (dev[int_key], debit_max['total']/10, debit_max['total']/2))
utils.tc('qdisc add dev %s parent 1:5 '
'handle 3: sfq perturb 10' % dev[int_key])
print OK print OK
if apply: if apply:

View file

@ -1,10 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os import os
import sys import sys
import netaddr import netaddr
if '/usr/scripts' not in sys.path: if '/usr/scripts/' not in sys.path:
sys.path.append('/usr/scripts') sys.path.append('/usr/scripts/')
import syslog import syslog
import subprocess import subprocess
@ -55,7 +56,7 @@ class firewall_tools(object) :
"""Classe de base du pare-feu implémentant l'association mac-ip (pour les machines filaires) et les blacklists hard""" """Classe de base du pare-feu implémentant l'association mac-ip (pour les machines filaires) et les blacklists hard"""
def machines(self): def machines(self):
"""Renvoit la liste de toutes les machines""" """Renvois la liste de toutes les machines"""
if self._machines: if self._machines:
return self._machines return self._machines
# On utilise allMachinesAdherents car on a besoin que # On utilise allMachinesAdherents car on a besoin que
@ -63,37 +64,48 @@ class firewall_tools(object) :
# les blacklistes d'un proprio lorsque l'on regarde les blacklistes # les blacklistes d'un proprio lorsque l'on regarde les blacklistes
# d'une machine # d'une machine
anim('\tChargement des machines') anim('\tChargement des machines')
# On prend toutes les machines y compris celles de ceux qui n'ont pas payé self._machines, self._adherents = self.conn.allMachinesAdherents()
# elles seront ajoutées dans mac_ip mais blacklistées du fait du non paiement ensuite self._adherents = [ adh for adh in self._adherents if adh.paiement_ok() ]
self._machines = self.conn.allMachines()
print OK print OK
return self._machines return self._machines
def adherents(self):
"""
Renvois la liste de tous les adhérents à jour de paiement
(car on suppose que la blackliste paiement est hard)
"""
if self._adherents:
return self._adherents
self._machines, self._adherents = self.conn.allMachinesAdherents()
self._adherents = [ adh for adh in self._adherents if adh.paiement_ok() ]
return self._adherents
def blacklisted_machines(self): def blacklisted_machines(self):
"""Renvoit la liste de toutes les machines ayant une blackliste actives""" """Renvois la liste de toutes les machines ayant une blackliste actives"""
if self._blacklisted_machines: if self._blacklisted_machines:
return self._blacklisted_machines return self._blacklisted_machines
self._blacklisted_machines = [ machine for machine in self.machines() if machine.blacklist_actif() ] self._blacklisted_machines = [ machine for machine in self.machines() if machine.blacklist_actif() ]
return self._blacklisted_machines return self._blacklisted_machines
def blacklisted_ips(self, blacklist_sanctions=None): def blacklisted_ips(self, blacklist_sanctions=None, nets=None):
"""Renvoit l'ensemble des ips des machines ayant une blacklist dans blacklist_sanctions et étant dans nets si spécifié""" """Renvois l'ensemble des ips des machines ayant une blacklist dans blacklist_sanctions et étant dans nets si spécifié"""
bl_ips = set() bl_ips = set()
for machine in self.blacklisted_machines(): for machine in self.blacklisted_machines():
if blacklist_sanctions is None or set(bl['type'] for bl in machine.blacklist_actif()).intersection(blacklist_sanctions): if blacklist_sanctions is None or set(bl['type'] for bl in machine.blacklist_actif()).intersection(blacklist_sanctions):
for ip in machine['ipHostNumber']: for ip in machine['ipHostNumber']:
if nets is None:
bl_ips.add(str(ip))
else:
for net in nets:
if ip in netaddr.IPNetwork(net):
bl_ips.add(str(ip)) bl_ips.add(str(ip))
return bl_ips return bl_ips
def blacklisted_adherents(self, excepts=[]): def blacklisted_adherents(self, excepts=[]):
"""Renvoit la liste de tous les adhérents ayant une blackliste active en ignorant les blacklist de excepts""" """Renvois la liste de tous les adhérents ayant une blackliste active en ignorant les blacklist de excepts"""
if not self._adherents:
self._adherents = self.conn.allAdherents()
if self._blacklisted_adherents and self._blacklisted_adherents_type == set(excepts): if self._blacklisted_adherents and self._blacklisted_adherents_type == set(excepts):
return self._blacklisted_adherents return self._blacklisted_adherents
self._blacklisted_adherents = filter(lambda adh: adh.blacklist_actif(excepts), self._adherents) self._blacklisted_adherents = filter(lambda adh: adh.blacklist_actif(excepts), self.adherents())
self._blacklisted_adherents_type = set(excepts) self._blacklisted_adherents_type = set(excepts)
return self._blacklisted_adherents return self._blacklisted_adherents

View file

@ -102,7 +102,7 @@ class firewall(base.firewall):
self.add(table, chain, '-d 127.0.0.1/8 -j RETURN') self.add(table, chain, '-d 127.0.0.1/8 -j RETURN')
for net in base.config.NETs['all']: for net in base.config.NETs['all']:
self.add(table, chain, '-d %s -j RETURN' % net) self.add(table, chain, '-d %s -j RETURN' % net)
for adh in self.blacklisted_adherents(excepts=['paiement']): for adh in self.blacklisted_adherents():
if 'uidNumber' in adh: if 'uidNumber' in adh:
self.add(table, chain, '-m owner --uid-owner %s -j REJECT' % adh['uidNumber'][0]) self.add(table, chain, '-m owner --uid-owner %s -j REJECT' % adh['uidNumber'][0])
print OK print OK

View file

@ -20,10 +20,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys import sys, re, os, pwd
import re
import os
import pwd
sys.path.append('/usr/scripts/gestion') sys.path.append('/usr/scripts/gestion')
@ -60,11 +57,13 @@ def ports(dev_ip6, dev_list):
# Il semble qu'il faille un kernel >= .29 et iptables >= 1.4.3 # Il semble qu'il faille un kernel >= .29 et iptables >= 1.4.3
# http://netfilter.org/projects/iptables/files/changes-iptables-1.4.3.txt # http://netfilter.org/projects/iptables/files/changes-iptables-1.4.3.txt
ip6tables.filter.forward('-i %s -p tcp --dport ssh -m state --state NEW -m recent --name SSH2 --set ' % dev_ip6) # ip6tables.filter.forward('-i %s -p tcp --dport ssh -m state --state NEW -m \
ip6tables.filter.forward('-i %s -p tcp --dport ssh -m state --state NEW -m recent --name SSH2 --update --seconds 30 --hitcount 10 --rttl -j DROP' % dev_ip6) #recent --name SSH --set ' % dev_ip6)
ip6tables.filter.input('-i %s -p tcp --dport ssh -m state --state NEW -m recent --name SSH --set ' % dev_ip6) # ip6tables.filter.forward('-i %s -p tcp --dport ssh -m state --state NEW -m \
ip6tables.filter.input('-i %s -p tcp --dport ssh -m state --state NEW -m recent --name SSH --update --seconds 120 --hitcount 10 --rttl -j DROP' % dev_ip6) #recent --name SSH --update --seconds 60 --hitcount 4 --rttl -j DROP' %
#ip6tables.filter.forward('-i %s -p tcp --dport ssh -m state --state NEW -j ACCEPT' % dev_ip6) # dev_ip6)
# ip6tables.filter.forward('-i %s -p tcp --dport ssh -m state --state NEW \
#-j ACCEPT' % dev_ip6)
for proto in open_ports.keys(): for proto in open_ports.keys():
ip6tables.filter.forward('-i %s -p %s -m multiport --dports %s -j ACCEPT' % (dev_ip6, proto, open_ports[proto])) ip6tables.filter.forward('-i %s -p %s -m multiport --dports %s -j ACCEPT' % (dev_ip6, proto, open_ports[proto]))
@ -136,7 +135,7 @@ def main_router():
ip6tables.mangle.prerouting('-i %s -m state --state NEW -j LOG --log-prefix "LOG_ALL "' % dev_ip6 ) ip6tables.mangle.prerouting('-i %s -m state --state NEW -j LOG --log-prefix "LOG_ALL "' % dev_ip6 )
# On force le /32 de google à passer en ipv4 pour tester si ça soulage le tunnel ipv6 # On force le /32 de google à passer en ipv4 pour tester si ça soulage le tunnel ipv6
ip6tables.filter.forward('-o %s -p tcp -d 2a00:1450:4006::/32 -j REJECT --reject-with icmp6-addr-unreachable' % dev_ip6) ip6tables.filter.forward('-o %s -p tcp -d 2a00:1450:4006::/32 -j REJECT' % dev_ip6)
# Ipv6 sur évènementiel, on ne laisse sortir que si ça vient de la mac d'ytrap-llatsni # Ipv6 sur évènementiel, on ne laisse sortir que si ça vient de la mac d'ytrap-llatsni
ip6tables.filter.forward('-o %s -d 2a01:240:fe3d:d2::/64 -j ACCEPT' % dev_crans) ip6tables.filter.forward('-o %s -d 2a01:240:fe3d:d2::/64 -j ACCEPT' % dev_crans)

View file

@ -45,13 +45,17 @@ class base_reconfigure:
'macip': [ _s + '-macip' for _s in __firewalled_servers ], 'macip': [ _s + '-macip' for _s in __firewalled_servers ],
# 'droits': [ 'rouge-droits', 'ragnarok-droits' ], # 'droits': [ 'rouge-droits', 'ragnarok-droits' ],
'blacklist': __blacklist_servers, 'blacklist': __blacklist_servers,
'bl_carte_etudiant': __blacklist_servers,
'bl_chbre_invalide': __blacklist_servers, 'bl_chbre_invalide': __blacklist_servers,
'blacklist_mail_invalide': __blacklist_servers, 'blacklist_mail_invalide': __blacklist_servers,
'blacklist_virus': __blacklist_servers, 'blacklist_virus': __blacklist_servers,
'blacklist_warez': __blacklist_servers, 'blacklist_warez': __blacklist_servers,
'blacklist_ipv6_ra': __blacklist_servers, 'blacklist_ipv6_ra': __blacklist_servers,
'blacklist_upload': __blacklist_servers, 'blacklist_upload': __blacklist_servers,
'blacklist_p2p': __blacklist_servers,
'blacklist_autodisc_virus': __blacklist_servers,
'blacklist_autodisc_upload': __blacklist_servers, 'blacklist_autodisc_upload': __blacklist_servers,
'blacklist_autodisc_p2p': __blacklist_servers,
'blacklist_bloq': __blacklist_servers, 'blacklist_bloq': __blacklist_servers,
'del_user': [ 'zbee-del_user', 'owl-del_user', 'zamok-del_user' ], 'del_user': [ 'zbee-del_user', 'owl-del_user', 'zamok-del_user' ],
'port': ['%s-port' % _s for _s in __services.get('connection-main', [])], 'port': ['%s-port' % _s for _s in __services.get('connection-main', [])],
@ -64,7 +68,10 @@ class base_reconfigure:
'warez':__service_develop['blacklist_warez'], 'warez':__service_develop['blacklist_warez'],
'ipv6_ra':__service_develop['blacklist_ipv6_ra'], 'ipv6_ra':__service_develop['blacklist_ipv6_ra'],
'upload': __service_develop['blacklist_upload'], 'upload': __service_develop['blacklist_upload'],
'p2p': __service_develop['blacklist_p2p'],
'autodisc_virus':__service_develop['blacklist_autodisc_virus'],
'autodisc_upload': __service_develop['blacklist_autodisc_upload'], 'autodisc_upload': __service_develop['blacklist_autodisc_upload'],
'autodisc_p2p': __service_develop['blacklist_autodisc_p2p'],
'bloq': __service_develop['blacklist_bloq'], 'bloq': __service_develop['blacklist_bloq'],
}) })
except ImportError: except ImportError:
@ -229,7 +236,7 @@ class odlyd(base_reconfigure):
class zamok(base_reconfigure): class zamok(base_reconfigure):
def del_user(self, args): def del_user(self, args):
# Suppression des fichiers d'impression # Suppression des fichies d'impression
from adherents import del_user from adherents import del_user
self._do(del_user(args)) self._do(del_user(args))

View file

@ -16,18 +16,11 @@
import sys import sys
if '/usr/scripts' not in sys.path: sys.path.append('/usr/scripts/gestion')
sys.path.append('/usr/scripts')
import commands import commands
import os import os
IPSET_PATH = '/sbin/ipset'
# Avant jessie: ipset était dans /usr/sbin
if not os.path.exists(IPSET_PATH):
IPSET_PATH = '/usr' + IPSET_PATH
class IpsetError(Exception): class IpsetError(Exception):
# Gestion des erreurs d'ipset # Gestion des erreurs d'ipset
def __init__(self,cmd,err_code,output): def __init__(self,cmd,err_code,output):
@ -38,7 +31,7 @@ class IpsetError(Exception):
return "%s\n status : %s\n %s" % (self.cmd,self.err_code,self.output) return "%s\n status : %s\n %s" % (self.cmd,self.err_code,self.output)
class Ipset(object): class Ipset(object):
ipset = IPSET_PATH ipset="/usr/sbin/ipset"
def __str__(self): def __str__(self):
return self.set return self.set
@ -47,6 +40,7 @@ class Ipset(object):
self.set=set self.set=set
self.type=type self.type=type
self.typeopt=typeopt self.typeopt=typeopt
self.squeeze = os.uname()[2] < '3'
try: try:
self.create() self.create()
except IpsetError as error: except IpsetError as error:
@ -65,13 +59,13 @@ class Ipset(object):
return output return output
def create(self,opt=''): def create(self,opt=''):
self.call("create", "%s %s" % (self.type, self.typeopt)) self.call("-N","%s %s" % (self.type, self.typeopt))
def add(self,arg): def add(self,arg):
self.call("add", arg) self.call("-A",arg)
def list(self): def list(self):
output = self.call("list").splitlines() output=self.call("-L").splitlines()
list=[] list=[]
for line in output[6:]: for line in output[6:]:
if line=='Bindings:': if line=='Bindings:':
@ -81,11 +75,15 @@ class Ipset(object):
def delete(self,ip): def delete(self,ip):
"""Delete an IP""" """Delete an IP"""
self.call("del", ip) self.call("-D",ip)
def restore(self,rules): def restore(self,rules):
""" restore le set courrant""" """ restore le set courrant"""
rules_str=self.restore_format(rules) rules_str=self.restore_format(rules)
if self.squeeze:
create_str="-N %s %s %s" % (self.set,self.type,self.typeopt)
str="%s\n%s\nCOMMIT\n" % (create_str,rules_str)
else:
str="%s\nCOMMIT\n" % rules_str str="%s\nCOMMIT\n" % rules_str
path='/tmp/ipset_%s' % self.set path='/tmp/ipset_%s' % self.set
f=open(path, 'w+') f=open(path, 'w+')
@ -93,9 +91,9 @@ class Ipset(object):
f.close() f.close()
try: try:
self.flush() self.flush()
except IpsetError as error: if self.squeeze:
sys.stderr.write("%s\n" % error) self.destroy()
except IpsetError as error: sys.stderr.write("%s\n" % error)
cmd="cat %s | %s -R" % (path,self.ipset) cmd="cat %s | %s -R" % (path,self.ipset)
status,output=commands.getstatusoutput(cmd) status,output=commands.getstatusoutput(cmd)
if status: if status:
@ -103,11 +101,11 @@ class Ipset(object):
return output return output
def flush(self): def flush(self):
self.call("flush") self.call("-F")
def destroy(self): def destroy(self):
self.call("destroy") self.call("-X")
def restore_format(self,rules): def restore_format(self,rules):
return '\n'.join(["add %s %s" % (self.set, data) for data in rules]) return '\n'.join(["-A %s %s" % (self.set,data) for data in rules])

View file

@ -31,11 +31,11 @@ DEBIAN_BACKPORT_ARCHS="i386 amd64"
DEBIAN_BACKPORT_FTP="ftp://cdimage.debian.org/cdimage/unofficial/backports/" DEBIAN_BACKPORT_FTP="ftp://cdimage.debian.org/cdimage/unofficial/backports/"
# Définitions spécifiques à Ubuntu # Définitions spécifiques à Ubuntu
UBUNTU_DISTS="precise trusty utopic vivid" UBUNTU_DISTS="precise saucy trusty utopic"
UBUNTU_ARCHS="i386 amd64" UBUNTU_ARCHS="i386 amd64"
UBUNTU_FTP="ftp://ftp.crans.org/ubuntu/dists" UBUNTU_FTP="ftp://ftp.crans.org/ubuntu/dists"
UBUNTU_LIVE="12.04 14.04 14.10 15.04" UBUNTU_LIVE="12.04 12.10 13.04 13.10 14.04 14.10"
# il faut modifier le nfs (ajouter la sortie de export_ubuntu_live # il faut modifier le nfs (ajouter la sortie de export_ubuntu_live
# à /etc/exports) et mettre les images dans $ISODIR/ubuntu/ puis # à /etc/exports) et mettre les images dans $ISODIR/ubuntu/ puis
# les monter dans $TFTPROOT/livecd/ubuntu/$dist-$arch avec # les monter dans $TFTPROOT/livecd/ubuntu/$dist-$arch avec
@ -54,7 +54,7 @@ CENTOS_ARCHS="i386 x86_64"
CENTOS_FTP="ftp://mirror.in2p3.fr/pub/linux/CentOS" CENTOS_FTP="ftp://mirror.in2p3.fr/pub/linux/CentOS"
# Définitions spécifiques à Fedora # Définitions spécifiques à Fedora
FEDORA_DISTS="20 21 22" FEDORA_DISTS="19 20"
FEDORA_ARCHS="i386 x86_64" FEDORA_ARCHS="i386 x86_64"
FEDORA_FTP="ftp://ftp.free.fr/mirrors/fedora.redhat.com/fedora/linux/" FEDORA_FTP="ftp://ftp.free.fr/mirrors/fedora.redhat.com/fedora/linux/"

View file

@ -123,11 +123,11 @@ for dist in $DEBIAN_DISTS; do
#~ mkdir -p $TFTPROOT/debian-gtk-$dist/$arch #~ mkdir -p $TFTPROOT/debian-gtk-$dist/$arch
#~ cp $TMPDIR/netboot-debian-gtk-$dist-$arch/debian-installer/$arch/initrd.gz $TFTPROOT/debian-gtk-$dist/$arch #~ cp $TMPDIR/netboot-debian-gtk-$dist-$arch/debian-installer/$arch/initrd.gz $TFTPROOT/debian-gtk-$dist/$arch
#~ cp $TMPDIR/netboot-debian-gtk-$dist-$arch/debian-installer/$arch/linux $TFTPROOT/debian-gtk-$dist/$arch #~ cp $TMPDIR/netboot-debian-gtk-$dist-$arch/debian-installer/$arch/linux $TFTPROOT/debian-gtk-$dist/$arch
#wget $WGETOPT -c $DEBIAN_FTP/$dist/main/installer-kfreebsd-$arch/current/images/netboot/netboot.tar.gz -O $TMPDIR/netboot-debian-kfreebsd-$dist-$arch.tar.gz wget $WGETOPT -c $DEBIAN_FTP/$dist/main/installer-kfreebsd-$arch/current/images/netboot/netboot.tar.gz -O $TMPDIR/netboot-debian-kfreebsd-$dist-$arch.tar.gz
#mkdir -p $TMPDIR/netboot-debian-$dist-kfreebsd-$arch/ mkdir -p $TMPDIR/netboot-debian-$dist-kfreebsd-$arch/
#tar zxf $TMPDIR/netboot-debian-kfreebsd-$dist-$arch.tar.gz -C $TMPDIR/netboot-debian-$dist-kfreebsd-$arch/ tar zxf $TMPDIR/netboot-debian-kfreebsd-$dist-$arch.tar.gz -C $TMPDIR/netboot-debian-$dist-kfreebsd-$arch/
#mkdir -p $TFTPROOT/debian-$dist/kfreebsd-$arch/ mkdir -p $TFTPROOT/debian-$dist/kfreebsd-$arch/
# cp -r $TMPDIR/netboot-debian-$dist-kfreebsd-$arch/* $TFTPROOT/debian-$dist/kfreebsd-$arch/ cp -r $TMPDIR/netboot-debian-$dist-kfreebsd-$arch/* $TFTPROOT/debian-$dist/kfreebsd-$arch/
done done
done done
@ -170,12 +170,12 @@ cat >> $TFTPROOT/boot-screens/menu.cfg << EOF
menu end menu end
EOF EOF
done done
#for arch in $DEBIAN_ARCHS; do for arch in $DEBIAN_ARCHS; do
#cat >> $TFTPROOT/boot-screens/menu.cfg <<EOF cat >> $TFTPROOT/boot-screens/menu.cfg <<EOF
# LABEL Debian $dist kfreebsd-$arch LABEL Debian $dist kfreebsd-$arch
# kernel debian-$dist/kfreebsd-$arch/grub2pxe kernel debian-$dist/kfreebsd-$arch/grub2pxe
#EOF EOF
#done done
cat >> $TFTPROOT/boot-screens/menu.cfg << EOF cat >> $TFTPROOT/boot-screens/menu.cfg << EOF
menu end menu end
@ -573,10 +573,8 @@ for dist in $FEDORA_DISTS; do
for arch in $FEDORA_ARCHS; do for arch in $FEDORA_ARCHS; do
mkdir -p $TMPDIR/fedora-$dist/$arch/ mkdir -p $TMPDIR/fedora-$dist/$arch/
wget $WGETOPT -c $FEDORA_FTP/releases/$dist/Fedora/$arch/os/images/pxeboot/initrd.img -O $TMPDIR/fedora-$dist/$arch/initrd.img ||\ wget $WGETOPT -c $FEDORA_FTP/releases/$dist/Fedora/$arch/os/images/pxeboot/initrd.img -O $TMPDIR/fedora-$dist/$arch/initrd.img ||\
wget $WGETOPT -c $FEDORA_FTP/releases/$dist/Server/$arch/os/images/pxeboot/initrd.img -O $TMPDIR/fedora-$dist/$arch/initrd.img ||\
wget $WGETOPT -c $FEDORA_FTP/development/$dist/$arch/os/images/pxeboot/initrd.img -O $TMPDIR/fedora-$dist/$arch/initrd.img wget $WGETOPT -c $FEDORA_FTP/development/$dist/$arch/os/images/pxeboot/initrd.img -O $TMPDIR/fedora-$dist/$arch/initrd.img
wget $WGETOPT -c $FEDORA_FTP/releases/$dist/Fedora/$arch/os/images/pxeboot/vmlinuz -O $TMPDIR/fedora-$dist/$arch/vmlinuz ||\ wget $WGETOPT -c $FEDORA_FTP/releases/$dist/Fedora/$arch/os/images/pxeboot/vmlinuz -O $TMPDIR/fedora-$dist/$arch/vmlinuz ||\
wget $WGETOPT -c $FEDORA_FTP/releases/$dist/Server/$arch/os/images/pxeboot/vmlinuz -O $TMPDIR/fedora-$dist/$arch/vmlinuz ||\
wget $WGETOPT -c $FEDORA_FTP/development/$dist/$arch/os/images/pxeboot/vmlinuz -O $TMPDIR/fedora-$dist/$arch/vmlinuz wget $WGETOPT -c $FEDORA_FTP/development/$dist/$arch/os/images/pxeboot/vmlinuz -O $TMPDIR/fedora-$dist/$arch/vmlinuz
done done
done done

View file

@ -28,7 +28,7 @@ console inactivity-timer 30
logging {{ s }} logging {{ s }}
{%- endfor %} {%- endfor %}
;--- IP du switch --- ;--- IP du switch ---
ip default-gateway {{ gateway }} ip default-gateway 10.231.136.4
{%- for vlan in vlans %} {%- for vlan in vlans %}
vlan {{ vlan.id }} vlan {{ vlan.id }}
name "{{ vlan.name|capitalize }}" name "{{ vlan.name|capitalize }}"
@ -54,13 +54,12 @@ no web-management
aaa authentication ssh login public-key none aaa authentication ssh login public-key none
aaa authentication ssh enable public-key none aaa authentication ssh enable public-key none
ip ssh ip ssh
ip authorized-managers {{ network_id }} {{ subnet }} ip authorized-managers 10.231.136.0 255.255.255.0
ip ssh filetransfer ip ssh filetransfer
;--- Protection contre les boucles --- ;--- Protection contre les boucles ---
loop-protect disable-timer 30 loop-protect disable-timer 30
loop-protect transmit-interval 3 loop-protect transmit-interval 3
loop-protect {{ non_trusted }} loop-protect {{ non_trusted }}
{%- if not public %}
;--- Serveurs radius --- ;--- Serveurs radius ---
radius-server dead-time 2 radius-server dead-time 2
radius-server key {{ radius_key }} radius-server key {{ radius_key }}
@ -69,26 +68,24 @@ radius-server host {{ s }}
{%- endfor %} {%- endfor %}
;--- Filtrage mac --- ;--- Filtrage mac ---
aaa port-access mac-based addr-format multi-colon aaa port-access mac-based addr-format multi-colon
{%- endif %}
;--- Bricoles --- ;--- Bricoles ---
no cdp run no cdp run
no stack no stack
;--- DHCP Snooping --- ;--- DHCP Snooping ---
{%- if dhcp_snooping_vlan_names %} {%- if dhcp_snooping_vlan_names %}
dhcp-snooping vlan{% for n in dhcp_snooping_vlan_names %} {{ n|vlan_id }}{% endfor %} dhcp-snooping vlan{% for n in dhcp_snooping_vlan_names %} {{ n|vlan_id }}{% endfor %}
dhcp-snooping trust {{ trusted }}
no dhcp-snooping trust {{ non_trusted }}
{%- for s in dhcp_servers %} {%- for s in dhcp_servers %}
dhcp-snooping authorized-server {{ s }} dhcp-snooping authorized-server {{ s }}
{%- endfor %} {%- endfor %}
; Activation ; Activation
dhcp-snooping dhcp-snooping
{%- endif %} {%- endif %}
{% if ra_filter %};--- RA guards ---
ipv6 ra-guard ports {{ non_trusted }}
no ipv6 ra-guard ports {{ trusted }}
{% endif %}
;--- Config des prises --- ;--- Config des prises ---
{%- for port in ports %} {%- for port in ports %}
{%- if port.radius_auth() and not public %} {%- if port.radius_auth() %}
aaa port-access mac-based {{ port|int }} aaa port-access mac-based {{ port|int }}
aaa port-access mac-based {{ port|int }} addr-limit {{ port.num_mac() }} aaa port-access mac-based {{ port|int }} addr-limit {{ port.num_mac() }}
aaa port-access mac-based {{ port|int }} logoff-period 3600 aaa port-access mac-based {{ port|int }} logoff-period 3600
@ -98,9 +95,6 @@ interface {{ port|int }}
enable enable
name "{{ port }}" name "{{ port }}"
{{ port.flowcontrol() }} {{ port.flowcontrol() }}
{%- if port.is_trusted() %}
dhcp-snooping trust
{%- endif %}
{%- if gigabit %} {%- if gigabit %}
{{ port.speed() }} {{ port.speed() }}
{%- endif %} {%- endif %}

View file

@ -3,6 +3,8 @@
""" """
Génération de la configuration d'un switch. Génération de la configuration d'un switch.
Attention, cette version n'a pas encore été totalement testée.
procédure de configuration initiale : procédure de configuration initiale :
* mot de passe admin (password manager user-name <username>) * mot de passe admin (password manager user-name <username>)
* activation du ssh (crypto key generate ssh) * activation du ssh (crypto key generate ssh)
@ -51,7 +53,7 @@ V_NO = 3
# Vlans disponibles # Vlans disponibles
ENABLED_VLANS = ['adherent', 'adm', 'wifi', 'v6only', 'accueil', 'isolement', ENABLED_VLANS = ['adherent', 'adm', 'wifi', 'v6only', 'accueil', 'isolement',
'appts', 'event', 'federez'] 'appts', 'event']
def vlan_id(name): def vlan_id(name):
"""Vlan id of a name (filtre jinja)""" """Vlan id of a name (filtre jinja)"""
@ -176,7 +178,7 @@ class Port(object):
return V_NO return V_NO
elif self.bornes: elif self.bornes:
if vlan in ['wifi', 'accueil', 'isolement', 'v6only', 'appts', if vlan in ['wifi', 'accueil', 'isolement', 'v6only', 'appts',
'event', 'federez']: 'event']:
return V_TAGGED return V_TAGGED
# Cas d'une borne dans une chambre: l'adherent doit pouvoir # Cas d'une borne dans une chambre: l'adherent doit pouvoir
# se connecter # se connecter
@ -441,11 +443,7 @@ def format_prises_group(data, first, last):
def pretty_print(hostname): def pretty_print(hostname):
"""Affiche joliement le plan de connexion d'un switch""" """Affiche joliement le plan de connexion d'un switch"""
bat, sw_num = get_bat_num(hostname) bat, sw_num = get_bat_num(hostname)
try:
switch = ldap.search(u'host=bat%s-%d.adm.crans.org' % (bat, sw_num))[0] switch = ldap.search(u'host=bat%s-%d.adm.crans.org' % (bat, sw_num))[0]
except IndexError:
switch = ldap.search(u'host=bat%s-%d.crans.org' % (bat, sw_num))[0]
port_dict = get_port_dict(switch) port_dict = get_port_dict(switch)
total = max(port_dict.keys()) total = max(port_dict.keys())
@ -465,11 +463,7 @@ def conf_switch(hostname):
"""Affiche la configuration d'un switch""" """Affiche la configuration d'un switch"""
bat, sw_num = get_bat_num(hostname) bat, sw_num = get_bat_num(hostname)
try:
switch = ldap.search(u'host=bat%s-%d.adm.crans.org' % (bat, sw_num))[0] switch = ldap.search(u'host=bat%s-%d.adm.crans.org' % (bat, sw_num))[0]
except IndexError:
switch = ldap.search(u'host=bat%s-%d.crans.org' % (bat, sw_num))[0]
tpl_env = jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.dirname(__file__))) tpl_env = jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
##for info: ##for info:
@ -482,12 +476,12 @@ def conf_switch(hostname):
'date_gen': datetime.datetime.now(), 'date_gen': datetime.datetime.now(),
# TODO fill that depuis bcfg2 ou whatever # TODO fill that depuis bcfg2 ou whatever
'radius_servers': [ 'radius_servers': ['10.231.136.72', '10.231.136.9' ],
'10.231.136.72',
'10.231.136.11',
],
'radius_key': secrets.get('radius_key'), 'radius_key': secrets.get('radius_key'),
'ntp_servers': ['10.231.136.98'],
'log_servers': ['10.231.136.38'],
# dhcp et isc (secondaire) sont les deux seuls serveurs # dhcp et isc (secondaire) sont les deux seuls serveurs
'dhcp_rid_servers': [34, 160], 'dhcp_rid_servers': [34, 160],
@ -496,7 +490,7 @@ def conf_switch(hostname):
# réseaux où on fait du dhcp snooping (cf data.NETs) # réseaux où on fait du dhcp snooping (cf data.NETs)
'dhcp_snooping_vlan_names': ['adherent', 'wifi', 'accueil', 'dhcp_snooping_vlan_names': ['adherent', 'wifi', 'accueil',
'isolement', 'v6only', 'appts', 'federez'], 'isolement', 'v6only', 'appts'],
} }
for com in switch['info']: for com in switch['info']:
@ -523,30 +517,6 @@ def conf_switch(hostname):
first = netaddr.IPNetwork(net_of_vlan_name(vname)[0]).first first = netaddr.IPNetwork(net_of_vlan_name(vname)[0]).first
data['dhcp_servers'].append(str(netaddr.IPAddress(first + rid))) data['dhcp_servers'].append(str(netaddr.IPAddress(first + rid)))
# Si le switch n'est pas en .adm, il n'est pas publique (ex : batk-0)
# (désactivation de radius etc)
# On règle les logs, ntp, suivant si le switch est public ou privé (adm)
if u"adm" in unicode(switch['host']):
data['public'] = False
data['ntp_servers'] = ['10.231.136.98']
data['log_servers'] = ['10.231.136.38']
data['gateway'] = '10.231.136.4'
data['network_id'] = '10.231.136.0'
data['subnet'] = '255.255.255.0'
else:
data['public'] = True
data['ntp_servers'] = ['138.231.136.98']
data['log_servers'] = ['138.231.136.38']
data['gateway'] = '138.231.136.4'
data['network_id'] = '138.231.136.0'
data['subnet'] = '255.255.248.0'
# Ra gards ne concerne que les 2620
if "2620" in switch['info'][0].value:
data['ra_filter'] = True
else:
data['ra_filter'] = False
# Switch avec des ports gigabit uniquement # Switch avec des ports gigabit uniquement
if imodel in GIGABIT_MODELS: if imodel in GIGABIT_MODELS:
data['gigabit'] = True data['gigabit'] = True
@ -569,14 +539,9 @@ def conf_switch(hostname):
V_NO: 'no'}[assign] V_NO: 'no'}[assign]
vlan.setdefault(attr, PortList()) vlan.setdefault(attr, PortList())
vlan[attr].extend(p) vlan[attr].extend(p)
if name == 'adm' and not data['public']: if name == 'adm':
vlan['ip_cfg'] = (gethostbyname(hostname), '255.255.255.0') vlan['ip_cfg'] = (gethostbyname(hostname), '255.255.255.0')
if name == 'adherent': if name == 'adherent':
# TODO : proprifier cela
# Si le switch est publique, adh en non tagué partout
if data['public']:
vlan['untagged'] = u'1-' + unicode(switch['nombrePrises'][0])
vlan['ip_cfg'] = (gethostbyname(hostname), '255.255.248.0')
# igmp snooping (multicast) mais nous ne sommes pas querier # igmp snooping (multicast) mais nous ne sommes pas querier
vlan['extra'] = 'ip igmp\nno ip igmp querier' vlan['extra'] = 'ip igmp\nno ip igmp querier'
vlans[name] = vlan vlans[name] = vlan

File diff suppressed because it is too large Load diff

View file

@ -35,7 +35,7 @@ def handle_exit_code(d, code):
os.system('clear') os.system('clear')
sys.exit(0) sys.exit(0)
else: else:
msg = "Vous avez appuyé sur ESC ou CTRL+C dans la dernière fenêtre de dialogue.\n\n" \ msg = "Vous avez appuyer sur ESC ou CTRL+C dans la dernière fenêtre de dialogue.\n\n" \
"Voulez vous quitter le programme ?" "Voulez vous quitter le programme ?"
if d.yesno(msg, width=60) == d.DIALOG_OK: if d.yesno(msg, width=60) == d.DIALOG_OK:
os.system('clear') os.system('clear')

View file

@ -25,7 +25,7 @@ import netsnmp
if '/usr/scripts' not in sys.path: if '/usr/scripts' not in sys.path:
path.append('/usr/scripts') path.append('/usr/scripts')
import gestion.secrets_new as secrets import gestion.secrets_new as secrets
from gestion.config import vlans, bats_virtuels from gestion.config import vlans
from gestion.annuaires_pg import chbre_prises, all_switchs from gestion.annuaires_pg import chbre_prises, all_switchs
try: try:
@ -390,25 +390,15 @@ class hpswitch :
prise = prise.replace('-','') prise = prise.replace('-','')
return self.get(oid + '.' + prise) == 'up' return self.get(oid + '.' + prise) == 'up'
def is_fake(self, prise=0):
"""Retourne True ou False selon que le switch est virtuel."""
if self.switch.split('-')[0].replace('bat', '') in bats_virtuels:
return True
return False
def is_enable(self,prise=0) : def is_enable(self,prise=0) :
""" Retoune True ou False suivant si la prise est activée ou non """ Retoune True ou False suivant si la prise est activée ou non
Si prise=all retourne le nombre de prises activées sur le switch """ Si prise=all retourne le nombre de prises activées sur le switch """
if self.switch.split('-')[0].replace('bat', '') in bats_virtuels:
return False
if prise != 'all': prise = int(prise) if prise != 'all': prise = int(prise)
return self.__is('IF-MIB::ifAdminStatus',prise) return self.__is('IF-MIB::ifAdminStatus',prise)
def is_up(self,prise=0) : def is_up(self,prise=0) :
""" Retoune True ou False suivant si la prise est up """ Retoune True ou False suivant si la prise est up
Si prise=all retourne le nombre de prises up sur le switch """ Si prise=all retourne le nombre de prises up sur le switch """
if self.switch.split('-')[0].replace('bat', '') in bats_virtuels:
return False
if prise != 'all': prise = int(prise) if prise != 'all': prise = int(prise)
return self.__is('IF-MIB::ifOperStatus',prise) return self.__is('IF-MIB::ifOperStatus',prise)
@ -449,15 +439,13 @@ class hpswitch :
def vlans(self, prise = None): def vlans(self, prise = None):
"""Récupère les vlans activés sur la prise 'prise'""" """Récupère les vlans activés sur la prise 'prise'"""
result = []
if self.switch.split('-')[0].replace('bat', '') in bats_virtuels:
return result
if not prise: if not prise:
prise = self.prise prise = self.prise
prise = int(prise) prise = int(prise)
oid_base = 'SNMPv2-SMI::enterprises.11.2.14.11.5.1.7.1.15.3.1.1' oid_base = 'SNMPv2-SMI::enterprises.11.2.14.11.5.1.7.1.15.3.1.1'
oid_format = oid_base + '.%(vlan)d.%(prise)d' oid_format = oid_base + '.%(vlan)d.%(prise)d'
oids = self.walk(oid_base) oids = self.walk(oid_base)
result = []
for vlan_name, vlan in vlans.iteritems(): for vlan_name, vlan in vlans.iteritems():
if oid_format % {'vlan': vlan, 'prise': prise} in oids: if oid_format % {'vlan': vlan, 'prise': prise} in oids:
result.append(vlan_name) result.append(vlan_name)

View file

@ -1,25 +0,0 @@
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
import os
from .switch import HPSwitch, SwitchNotFound
from .tools import trace_mac
import gestion.config.snmp as config_snmp
os.environ["MIBS"] = ":".join([mib for mib in config_snmp.PRELOAD_MIBS])

View file

@ -1,67 +0,0 @@
#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
"""Ce sont les variables utiles pour les autres scripts du
module"""
OPERSTATUS = {
1: 'up',
2: 'down',
3: 'testing',
4: 'unknown',
5: 'dormant',
6: 'notPresent',
7: 'lowerLayerDown',
}
ADMINSTATUS = {
1: 'up',
2: 'down',
3: 'testing',
}
ETHSPEED = {
'HD': {
0: '5',
10: '1',
100: '2',
1000: '5',
},
'FD': {
0: '5',
10: '3',
100: '4',
1000: '6',
},
'AUTO': {
0: '5',
10: '7',
100: '8',
1000: '9',
},
}
REV_ETHSPEED = {
'1': '10 Mbs Half Duplex',
'2': '100 Mbs Half Duplex',
'3': '10 Mbs Full Duplex',
'4': '100 Mbs Full Duplex',
'6': '1000 Mbs Full Duplex',
'5': 'auto',
'7': '10 Mbs auto',
'8': '100 Mbs auto',
'9': '1000 Mbs auto',
}

View file

@ -1,93 +0,0 @@
#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
"""Contient les outils pour manipuler des adresses MAC
dans le module hptools"""
import binascii
import netaddr
def bin_to_mac(raw):
"""Convertit une OctetString en une MAC"""
return format_mac(binascii.hexlify(raw))
def format_mac(raw):
"""Formatte la mac en aa:bb:cc:dd:ee:ff"""
return str(netaddr.EUI(raw)).replace('-', ':').lower()
class MACFactory(object):
"""Factory stockant les macs"""
__macs = {}
@classmethod
def register_mac(cls, mac, parent=None):
"""Enregistre une mac dans la factory et
retourne une instance de MACAddress si besoin."""
if cls.__macs.get(mac, None) is None:
cls.__macs[mac] = MACAddress(mac, parent)
else:
cls.__macs[mac].append_parent(parent)
return cls.__macs[mac]
@classmethod
def get_mac(cls, mac):
"""Récupère une mac dans la factory"""
return cls.__macs.get(mac, None)
@classmethod
def get_macs(cls):
"""Récupère l'ensemble des MACS de la factory"""
return cls.__macs
class MACAddress(object):
"""Classe représentant une adresse MAC"""
def __init__(self, value, parent=None):
"""Stocke l'adresse mac quelque part et le parent"""
self.__value = value
if parent is not None:
self.__parents = {parent.name() : parent}
@property
def value(self):
"""Property pour lire la valeur d'une MAC"""
return self.__value
@property
def parents(self):
"""Retourne les parents"""
return self.__parents
def append_parent(self, parent):
"""Ajoute un parent à la MAC si parent n'est pas None"""
if parent is not None:
if self.__parents.get(parent.name(), None) is None:
self.__parents[parent.name()] = parent
def remove_parent(self, parent):
"""Retire le parent référencé à la MAC"""
if parent is not None:
if self.__parents.get(parent.name(), None) is not None:
_ = self.__parents.pop(parent.name())

View file

@ -1,153 +0,0 @@
#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
"""Contient la définition et les outils pour bosser avec
les ports.
C'est essentiellement une couche d'abstraction, les fonctions
utiles sont appelées avec les switches."""
import netaddr
from .mac import MACFactory, bin_to_mac
class HPSwitchPort(object):
"""Classe représentant le port d'un switch"""
def __init__(self, num, parent, ptype=None):
"""Permet de lier un port au switch parent."""
self.__num = num
self.__ptype = ptype
self.__parent = parent
self.__macs = []
self.__multicast = []
self.__vlans = []
self.__oper = False
self.__admin = False
self.__alias = None
self.__eth = None
def name(self):
"""Retourne le nom du port"""
return "%s%02d" % (self.__parent.name(), self.__num)
def get_vlans(self):
"""Retourne les vlans du port"""
return self.__vlans
def add_vlan(self, vlan):
"""Ajoute le vlan à la liste"""
self.__vlans.append(vlan)
def purge_vlans(self):
"""Purge la liste des vlans connus du port"""
self.__vlans = []
def get_eth(self):
"""Récupère l'alias du port"""
if self.__eth is None:
self.__parent.update_eth_speed()
return self.__eth
def set_eth(self, val):
"""Affecte le nom"""
self.__eth = val
def get_alias(self):
"""Récupère l'alias du port"""
if self.__alias is None:
self.__parent.update_ports_aliases()
return self.__alias
def set_alias(self, alias):
"""Affecte le nom"""
self.__alias = alias
def append_mac(self, mac):
"""Ajoute une mac au port"""
self.__macs.append(MACFactory.register_mac(bin_to_mac(mac), self))
def get_macs(self, update=False):
"""Récupère les adresses mac depuis le parent"""
if not self.__macs or update:
# On boucle sur les macs et on les sépare du parent actuel (vu
# qu'on va régénérer sa liste de macs).
self.flush_macs()
__ret = self.__parent.client.walk('hpSwitchPortFdbAddress.%d' % (self.__num,))
self.__macs = [MACFactory.register_mac(bin_to_mac(ret['val']), self) for ret in __ret]
return self.__macs
def flush_macs(self):
"""Vire les macs"""
if not self.__macs:
return True
for mac in self.__macs:
mac.remove_parent(self)
self.__macs = []
return True
def append_multicast(self, multi_ip):
"""Ajoute l'IP aux multicasts"""
self.__multicast.append(netaddr.IPAddress(multi_ip))
@property
def multicast(self):
"""Retourne les ip multicast liées au port."""
return self.__multicast
def flush_multicast(self):
"""Vire les infos sur le multicast."""
self.__multicast = []
@property
def parent(self):
"""Property sur __parent"""
return self.__parent
@property
def oper(self):
"""Retourne l'oper status"""
return self.__oper
@property
def admin(self):
"""Retourne l'admin status"""
return self.__admin
@admin.setter
def admin(self, stat):
"""Met à jour l'admin status. Si stat n'est pas bon, met 3 (testing)"""
try:
stat = int(stat)
except TypeError:
stat = 3
self.__admin = stat
@oper.setter
def oper(self, stat):
"""Met à jour l'oper status. Si stat n'est pas bon, met 4 (unknown)"""
try:
stat = int(stat)
except TypeError:
stat = 4
self.__oper = stat

View file

@ -1,138 +0,0 @@
#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
"""Ce fichier propose un client snmp basique"""
import netsnmp
import socket
import gestion.secrets_new as secrets_new
class SNMPClient(object):
"""Classe de base définissant un client SNMP."""
def __init__(self, host):
"""Crée une session pointant vers le serveur SNMP, et
peuple les variables utiles."""
# Le fait se gérer si c'est .adm.crans.org, .crans.org, ou
# si le nom est un fqdn ou pas est du ressort du DNS (dans la
# mesure où de toute façon, si on a pas de dns, contacter les
# switches dont on doit résoudre l'IP va être tendu).
try:
self.host = socket.gethostbyname_ex(host)[0]
except socket.gaierror:
self.host = host
self.__session = None
self.__version3 = False
self.__snmp_community = None
self.__snmp_version = None
self.__snmp_seclevel = None
self.__snmp_authprotocol = None
self.__snmp_authpassword = None
self.__snmp_secname = None
self.__snmp_privprotocol = None
self.__snmp_privpassword = None
def __get_session(self, version3=False):
"""Crée une session en cas de besoin, en vérifiant qu'une
session répondant aux besoins n'existe pas déjà."""
if version3 and self.__version3 and self.__session:
return self.__session
if not version3 and not self.__version3 and self.__session:
return self.__session
if version3 and (not self.__version3 or not self.__session):
self.__snmp_community = 'private'
self.__snmp_version = 3
self.__snmp_seclevel = 'authPriv'
self.__snmp_authprotocol = 'SHA'
self.__snmp_authpassword = secrets_new.get('snmp_authentication_pass')
self.__snmp_secname = 'crans'
self.__snmp_privprotocol = 'DES'
self.__snmp_privpassword = secrets_new.get('snmp_privacy_pass')
if not version3 and (self.__version3 or not self.__session):
self.__snmp_community = 'public'
self.__snmp_version = 1
self.__snmp_seclevel = 'noAuthNoPriv'
self.__snmp_authprotocol = 'DEFAULT'
self.__snmp_authpassword = ''
self.__snmp_secname = 'initial'
self.__snmp_privprotocol = 'DEFAULT'
self.__snmp_privpassword = ''
self.__version3 = version3
session = netsnmp.Session(Version=self.__snmp_version, DestHost=self.host,
Community=self.__snmp_community, SecLevel=self.__snmp_seclevel,
SecName=self.__snmp_secname, PrivProto=self.__snmp_privprotocol,
PrivPass=self.__snmp_privpassword, AuthProto=self.__snmp_authprotocol,
AuthPass=self.__snmp_authpassword)
return session
def walk(self, attribute):
"""Fait un walk.
Exemple:
Si je demande hpSwitchPortFdbAddress, le retour contiendra
des entrées ayant pour tag hpSwitchPortFdbAddress, pour iid
une éventuelle valeur (si pertinent), et pour val la valeur
associée."""
self.__session = self.__get_session()
# Crée une variable netsnmp exploitable pour walk.
__varbind = netsnmp.Varbind(attribute)
# La stocke dans une liste.
__varlist = netsnmp.VarList(__varbind)
# __varlist est modifiée en place par la méthode walk.
_ = self.__session.walk(__varlist)
return [
{
'tag': ret.tag,
'iid': ret.iid,
'val': ret.val,
}
for ret in __varlist
]
def set(self, list_of_vars):
"""Met à jour un attribut"""
# On passe en SNMPv3
self.__session = self.__get_session(True)
# On construit la varlist à balancer en SNMP
__varlist = [
netsnmp.Varbind(
tag=res['tag'],
iid=res['iid'],
val=res['val']
)
for res in list_of_vars
]
# Oui, c'est moche
__varlist = netsnmp.VarList(*__varlist)
__ret = self.__session.set(__varlist)
return __ret

View file

@ -1,413 +0,0 @@
#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
"""Outils principaux pour la description d'un switch"""
import socket
import netaddr
from .port import HPSwitchPort
from .snmp import SNMPClient
from .mac import format_mac, MACFactory
from .defaults import OPERSTATUS, ADMINSTATUS, ETHSPEED, REV_ETHSPEED
class SwitchNotFound(Exception):
"""Erreur basique quand le switch n'est pas trouvé"""
pass
class HPSwitchFactory(object):
"""Factory stockant les switches"""
switches = {}
@classmethod
def get_switch(cls, switch):
"""Récupère un switch dans la factory"""
return cls.switches.get(switch, None)
@classmethod
def register_switch(cls, switch, switch_object):
"""Enregistre un switch dans la factory"""
cls.switches[switch] = switch_object
@classmethod
def get_switches(cls):
"""Récupère l'ensemble des switches dans la Factory"""
return cls.switches
class HPSwitch(object):
"""Classe décrivant un switch HP."""
def __new__(cls, switch):
"""Vérifie d'abord si un switch n'existe pas déjà.
L'idée est d'éviter de manipuler en parallèle des objets
dont les données deviendraient incohérentes. Donc,
lorsqu'on instancie un switch, celui-ci est référencé
par son hostname s'il existe, ou par le nom donné sinon."""
try:
__switch = socket.gethostbyname_ex(switch)[0]
except socket.gaierror:
try:
netaddr.IPAddress(switch)
except netaddr.AddrFormatError:
raise SwitchNotFound("Switch %r non trouvé." % (switch,))
switch_object = HPSwitchFactory.get_switch(__switch)
if switch_object is None:
switch_object = super(HPSwitch, cls).__new__(cls)
HPSwitchFactory.register_switch(switch, switch_object)
return switch_object
def __init__(self, switch):
"""Récupère le nom, et un client snmp"""
self.switch = switch
self.client = SNMPClient(self.switch)
self.ports = {}
self.__build_ports_list()
def version(self):
"""Retourne les données relatives à la version du firmware"""
return self.client.walk('sysDescr')[0]['val']
def name(self):
"""Retourne un nom "standardisé" du switch
(aka g0, c4, )"""
return self.switch.split(".", 1)[0].replace('bat', '').replace('-', '').lower()
def __build_ports_list(self):
"""Construit via une requête SNMP la liste des ports pour le switch."""
__ret = self.client.walk('hpSwitchPhysicalPortEntry.2')
for entry in __ret:
self.ports[int(entry['iid'])] = HPSwitchPort(int(entry['iid']), self, int(entry['val']))
def flush_port(self, num):
"""Vide un port de ses MACs"""
if self.ports.get(num, None) is not None:
self.ports[num].flush_macs()
def flush_ports(self):
"""Vide un port de ses MACs"""
for port in self.ports.itervalues():
port.flush_macs()
def fetch_all_ports(self):
"""Récupère les données depuis les ports du switch, et
renvoie ce qui a un intérêt"""
self.flush_ports()
__ret = self.client.walk('hpSwitchPortFdbAddress')
__ret = [
(int(ret['iid'].split('.')[0]), ret['val'])
for ret in __ret
]
return __ret
def __populate_port(self, num):
"""Peuple le port numéro num"""
if self.ports.get(num, None) is not None:
_ = self.ports[num].get_macs(update=True)
def __populate_all_ports(self):
"""Peuple tous les ports."""
__ret = self.fetch_all_ports()
for (iid, val) in __ret:
if self.ports.get(iid, None) is not None:
self.ports[iid].append_mac(val)
def show_port_macs(self, num, populate=False):
"""Affiche les macs d'un port donné.
Si populate vaut True, fait le populate et affiche le
bousin."""
if populate:
self.__populate_port(num)
return (
[
mac.value
for mac in self.ports[num].get_macs()
],
self.ports[num].name()
)
def show_ports_macs(self, populate=False):
"""Affiche les ports et macs associées.
Si populate vaut True, fait le populate et affiche le
bousin."""
if populate:
self.__populate_all_ports()
return {
port.name(): [
mac.value
for mac in port.get_macs()
]
for port in self.ports.itervalues()
}
def find_mac(self, mac, populate=False):
"""Cherche une mac sur le switch"""
mac = format_mac(mac)
if populate:
self.__populate_all_ports()
# On boucle sur les macs dans la factory
__mac = MACFactory.get_mac(mac)
if __mac is not None:
# On boucle sur les parents (des ports) à la recherche
# de ceux qui appartiennent au switch courant.
__parents = []
for parent in __mac.parents.itervalues():
if parent.parent == self:
__parents.append(parent)
# Si on en a trouvé, on les retourne avec la mac.
if __parents:
return (__mac, __parents)
return None
def __flush_multicast(self):
"""Vide les infos de multicast sur les ports"""
for port in self.ports.itervalues():
port.flush_multicast()
def __update_multicast(self):
"""Fait la mise à jour des infos de multicast sur chaque port"""
# On commence par vider.
self.__flush_multicast()
# On fait un walk.
data = self.client.walk('hpIgmpStatsPortIndex2')
# Le dico est du format standard. L'ip est au milieu du champ iid, et
# le port est dans val.
for data_dict in data:
# En gros, le champ iid ressemble à 1.239.255.255.255.6, où 1 est un truc
# que je connais pas, et 6 le numéro du port.
data_ip = ".".join(data_dict['iid'].split('.')[1:5])
igmp_ip, igmp_port = netaddr.IPAddress(data_ip), int(data_dict['val'])
# Y a plus qu'à stocker
if self.ports.get(igmp_port, None) is not None:
self.ports[igmp_port].append_multicast(igmp_ip)
def get_multicast(self, multi_ip=None, update=True):
"""Permet de récupérer les informations sur les ports
pour lesquels le multicast est actif."""
__output = {}
if multi_ip is not None:
multi_ip = netaddr.IPAddress(multi_ip)
# En cas d'update
if update:
self.__update_multicast()
# On construit le résultat de façon identique dans le cas
# update ou non.
for port in self.ports.itervalues():
for multicast_ip in port.multicast:
__output.setdefault(multicast_ip, []).append(port)
# On filtre par l'ip si besoin.
if multi_ip is not None:
__output = __output[multi_ip]
return __output
def nb_prises(self):
"""Retourne le nombre de prises du switch.
On pourrait aussi faire un self.client.walk('mib-2.17.1.2')
et récupérer la clef "val" du premier élément de la liste
retournée."""
return len(self.ports)
def __update_oper_status(self):
"""Récupère le statut des ports du switch."""
__oper = self.client.walk('ifOperStatus')
for dico in __oper:
port, state = dico['iid'], dico['val']
if self.ports.get(int(port), None) is not None:
self.ports[int(port)].oper = state
def __update_admin_status(self):
"""Récupère l'état d'un port du switch."""
__admin = self.client.walk('ifAdminStatus')
for dico in __admin:
port, state = dico['iid'], dico['val']
if self.ports.get(int(port), None) is not None:
self.ports[int(port)].admin = state
def is_enabled(self, prise, update=True):
"""Vérifie si la prise est activée"""
if update:
self.__update_admin_status()
if self.ports.get(prise, None) is not None:
return ADMINSTATUS[self.ports[prise].admin]
else:
return {
port : ADMINSTATUS[port.admin]
for port in self.ports.itervalues()
}
def is_up(self, prise, update=True):
"""Vérifie si la prise est allumée actuellement
(en gros, s'il y a une mac dessus)"""
if update:
self.__update_oper_status()
if self.ports.get(prise, None) is not None:
return OPERSTATUS[self.ports[prise].oper]
else:
return {
port : OPERSTATUS[port.oper]
for port in self.ports.itervalues()
}
def set_enabled(self, prise, enabled=True):
"""Met le port à enabled/disabled"""
if enabled:
val = '1'
else:
val = '2'
command_dict = {
'iid': str(prise),
'tag': 'ifAdminStatus',
'val': val,
}
return self.client.set([
command_dict
])
def toggle_enabled(self, prise):
"""Alterne up/down"""
if self.is_enabled(prise) == ADMINSTATUS[1]:
enabled = False
else:
enabled = True
return self.set_enabled(prise, enabled)
def get_port_alias(self, prise):
"""Retourne le nom du port"""
if self.ports.get(prise, None) is None:
return ""
return self.ports[prise].get_alias()
def update_ports_aliases(self):
"""Récupère les aliases des ports et les affecte"""
data = self.client.walk('ifAlias')
for data_dict in data:
if self.ports.get(int(data_dict['iid']), None) is not None:
self.ports[int(data_dict['iid'])].set_alias(data_dict['val'])
def set_port_alias(self, prise, alias):
"""Affecte un nom au port"""
if self.ports.get(prise, None) is not None:
self.client.set([
{
'iid': str(prise),
'tag': 'ifAlias',
'val': alias,
}
])
self.ports[prise].set_alias(alias)
def get_eth_speed(self, prise):
"""Retourne le nom du port"""
if self.ports.get(prise, None) is None:
return ""
return REV_ETHSPEED.get(self.ports[prise].get_eth(), 'unknown')
def update_eth_speed(self):
"""Met à jour la vitesse de tous les ports"""
data = self.client.walk('hpSwitchPortFastEtherMode')
for data_dict in data:
if self.ports.get(int(data_dict['iid']), None) is not None:
self.ports[int(data_dict['iid'])].set_eth(data_dict['val'])
def set_eth_speed(self, prise, rate=0, dtype='AUTO'):
"""Affecte un nom au port"""
# On affecte une config spécifique en vitesse
if self.ports.get(prise, None) is not None:
self.client.set([
{
'iid': str(prise),
'tag': 'hpSwitchPortFastEtherMode',
'val': ETHSPEED[dtype][int(rate)],
}
])
self.ports[prise].set_eth(ETHSPEED[dtype][int(rate)])
def get_vlans(self, prise=None, update=True):
"""Récupère les vlans actifs sur une prise"""
# Si mise à jour
if update:
# On fait le ménage
for port in self.ports.itervalues():
port.purge_vlans()
# Et on recommence
data = self.client.walk('enterprises.11.2.14.11.5.1.7.1.15.3.1.1')
for data_dict in data:
vlan, iid = [int(res) for res in data_dict['iid'].split('.')]
if self.ports.get(iid, None) is not None:
self.ports[iid].add_vlan(vlan)
# Si la prise vaut none, on file tout, sinon juste elle.
if prise is not None:
if self.ports.get(prise, None) is not None:
return self.ports[prise].get_vlans()
else:
return {
iid: port.get_vlans()
for (iid, port) in self.ports.iteritems()
}

View file

@ -1,144 +0,0 @@
#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
"""Fournit des outils et fonctions appelables au besoin"""
from gestion import annuaires_pg
from multiprocessing import Process, Manager
from .switch import HPSwitch
from .mac import MACFactory, format_mac
def filter_uplink(switch, stuff):
"""Filtre les prises uplink d'un retour.
stuff est une liste de la forme
[(port_id, mac), ...]"""
sortie = []
# Retourne "batg", "4.adm.crans.org", par exemple.
bat, num = switch.split('-', 1)
# On filtre ce qui n'est pas utile.
bat = bat[-1]
num = int(num[0])
# On récupère les infos utiles.
uplink = annuaires_pg.uplink_prises[bat]
gen_num_prise = 100 * num
for (port_id, mac) in stuff:
num_prise = gen_num_prise + port_id
if not num_prise in uplink:
sortie.append((port_id, mac))
return sortie
# +--------------------------------------------------------+
# | Mac Tracking Functions |
# +--------------------------------------------------------+
"""Ces fonctions servent à tracker une mac sur le réseau.
La fonction trace_mac s'occupe de ce travail. Elle utilise
la librairie multiprocessing pour spawner un process par
switch, et aller récupérer auprès de ceux-ci la liste des
MACs connectées, et les ports allant bien.
Multiprocessing ne mettant pas en place du partage de variable
par défaut, les objets retournés le sont via un Manager, dans
un dico, sans structure complexe.
Une solution dans laquelle les switches seraient renvoyés dans
leur structure python existe, mais elle est plus coûteuse,
et peu utile dans notre cas. (l'overhead engendré par la méthode
à base de dicos et régénération dans le processus parent est
epsilonesque)"""
def fetch_all_ports(switch, output):
"""Récupère l'ensemble des ports d'un switch, avec les MACS
dessus."""
sw = HPSwitch(switch)
# output est un Manager().dict()
__stuff = sw.fetch_all_ports()
__stuff = filter_uplink(switch, __stuff)
output[switch] = __stuff
def populate_all_switches(switches=None):
"""Remplit l'ensemble des switches avec les MACS qui sont
présentes sur leurs ports.
Peut également ne remplir qu'une liste spécifique si fournie
en argument."""
if switches == None:
switches = annuaires_pg.all_switchs()
hp_switches = {
switch : HPSwitch(switch)
for switch in switches
}
processes = {}
# La sortie des appels de fetch_all_ports sera écrite dans ce dico.
# On évitera la concurrence en utilisant le nom du switch comme
# séparateur
output = Manager().dict()
# Dans une première boucle, on crée les switches. Et on met
# les processes en mode actif.
for switch in switches:
hp_switches[switch].flush_ports()
processes[switch] = Process(target=fetch_all_ports, args=(switch, output), name=switch)
processes[switch].start()
# On fait la jointure des processes dans une seconde
# boucle, pour s'assurer que les processes ont bien
# tous été lancés avant de commencer à les sonder.
for switch in switches:
processes[switch].join()
for switch in switches:
if output[switch] is not None:
for (iid, val) in output[switch]:
if hp_switches[switch].ports.get(iid, None) is not None:
hp_switches[switch].ports[iid].append_mac(val)
else:
print "Output for switch %s is None." % (switch,)
def trace_mac(mac, in_all_switches=False):
"""Cherche une MAC. Si in_all_switches est à True, commence
par instancier tous les switches, et à les peupler.
Cette méthode est assez agressive, il faut l'utiliser avec
précaution."""
if in_all_switches:
populate_all_switches()
mac = format_mac(mac)
# On boucle sur les macs dans la factory
__mac = MACFactory.get_mac(mac)
if __mac is not None:
# On boucle sur les parents (des ports) à la recherche
# de ceux qui appartiennent au switch courant.
__parents = []
for parent in __mac.parents.itervalues():
__parents.append(parent)
# Si on en a trouvé, on les retourne avec la mac.
if __parents:
return (__mac, __parents)
return None

View file

@ -21,10 +21,10 @@
import sys import sys
import os, re, syslog, cPickle, socket import os, re, syslog, cPickle, socket
from ldap_crans import crans_ldap, hostname, generalizedTimeFormat from ldap_crans import crans_ldap, hostname
from commands import getstatusoutput from commands import getstatusoutput
from config import NETs, role, prefix, rid, output_file, filter_policy, rid_primaires from config import NETs, role, prefix, rid, output_file, filter_policy, rid_primaires
from config import blacklist_sanctions, blacklist_sanctions_soft, blacklist_bridage_upload, file_pickle, periode_transitoire, gtf_debut_periode_transitoire from config import blacklist_sanctions, blacklist_sanctions_soft, blacklist_bridage_upload, file_pickle, ann_scol, periode_transitoire
from iptools import AddrInNet from iptools import AddrInNet
from ridtools import Rid, find_rid_plage from ridtools import Rid, find_rid_plage
import subprocess import subprocess
@ -768,13 +768,9 @@ def blacklist(ipt):
if [x for x in sanctions if x in blacklist_sanctions_ipv6]: if [x for x in sanctions if x in blacklist_sanctions_ipv6]:
blcklst.extend(target.machines()) blcklst.extend(target.machines())
s = db.search('mblacklist=*&finConnexion>=%(fin)s&finAdhesion>=%(fin)s' % { s = db.search('mblacklist=*&paiement=%s' % ann_scol)
'fin': generalizedTimeFormat(),
})
if periode_transitoire: if periode_transitoire:
s['machine'].extend(db.search('mblacklist=*&finConnexion>=%(fin)s&finAdhsion>=%(fin)s' % { s['machine'].extend(db.search('mblacklist=*&paiement=%s' % (ann_scol-1))['machine'])
'fin': gtf_debut_periode_transitoire,
})['machine'])
for target in s['machine']: for target in s['machine']:
sanctions = target.blacklist_actif() sanctions = target.blacklist_actif()

View file

@ -26,7 +26,7 @@ import ldap.modlist
import ldap_passwd import ldap_passwd
import netaddr import netaddr
import traceback import traceback
import subprocess
import annuaires_pg as annuaires import annuaires_pg as annuaires
import config import config
import config.impression import config.impression
@ -41,22 +41,13 @@ from calendar import monthrange
from affich_tools import coul, prompt, cprint from affich_tools import coul, prompt, cprint
from email_tools import send_email from email_tools import send_email
from syslog import openlog, closelog, syslog from syslog import openlog, closelog, syslog
from numeros_disponibles import lister_ip_dispo
from unicodedata import normalize from unicodedata import normalize
import secrets_new as secrets import secrets_new as secrets
import ridtools import ridtools
from user_tests import isadm from user_tests import isadm
import getpass import getpass
try:
import pytz
except:
pytz = None
try:
import dateutil.tz
except:
dateutil = None
cur_user = os.getenv("SUDO_USER") or os.getenv("USER") or os.getenv("LOGNAME") or getpass.getuser() cur_user = os.getenv("SUDO_USER") or os.getenv("USER") or os.getenv("LOGNAME") or getpass.getuser()
date_format = '%d/%m/%Y %H:%M' date_format = '%d/%m/%Y %H:%M'
@ -72,17 +63,13 @@ random.seed() # On initialise le générateur aléatoire
test_hosts = tuple() test_hosts = tuple()
if os.getenv('DBG_LDAP', False): if os.getenv('DBG_LDAP', False):
if hostname != "vo":
raise ImportError, coul("La base de test n'est accessible que depuis vo !", "rouge")
# Utilisation de la base de données de test (tests, séminaire...) # Utilisation de la base de données de test (tests, séminaire...)
# Il faut au choix : # Il faut au choix :
# - faire un import crans_ldap_test # - faire un import crans_ldap_test
# ou - crans_ldap=test /le/script # ou - crans_ldap=test /le/script
host = os.getenv('DBG_LDAP') uri = ro_uri = 'ldapi://%2fvar%2frun%2fslapd%2fldapi/'
if host == '1':
if hostname != "vo":
raise ImportError("La base de test n'est accessible que depuis vo !")
host = 'localhost'
uri = ro_uri = 'ldap://%s/' % host
ldap_auth_dn = 'cn=admin,dc=crans,dc=org' ldap_auth_dn = 'cn=admin,dc=crans,dc=org'
ldap_password = '75bdb64f32' ldap_password = '75bdb64f32'
@ -119,7 +106,7 @@ blacklist_items = config.blacklist_items
### Droits possibles ### Droits possibles
droits_possibles = [u'Multimachines', u'Cableur', u'Imprimeur', u'Apprenti', droits_possibles = [u'Multimachines', u'Cableur', u'Imprimeur', u'Apprenti',
u'Webmaster', u'Moderateur', u'Webradio', u'Webmaster', u'Moderateur', u'Webradio',
u'Nounou', u'Tresorier', u'Bureau'] u'Nounou', u'Tresorier', u'Bureau', u'Troll']
################################################################################## ##################################################################################
### Droits critiques, ie que seules les nounous peuvent attribuer ### Droits critiques, ie que seules les nounous peuvent attribuer
@ -134,6 +121,7 @@ droits_vieux = [u'Nounou', u'Bureau']
### Variables internes diverses ### Variables internes diverses
#isadm = user_tests.isadm() #isadm = user_tests.isadm()
#isdeconnecteur = user_tests.isdeconnecteur() #isdeconnecteur = user_tests.isdeconnecteur()
ann_scol = config.ann_scol
#script_utilisateur = user_tests.getuser() #script_utilisateur = user_tests.getuser()
script_utilisateur = cur_user script_utilisateur = cur_user
@ -158,15 +146,12 @@ def tz(thetz):
else: else:
return "%s%04d" % ("+"*(thetz < 0) + "-"*(thetz > 0), abstz) return "%s%04d" % ("+"*(thetz < 0) + "-"*(thetz > 0), abstz)
def generalizedTimeFormat(stamp=None): def generalizedTimeFormat(stamp):
"""Converts a timestamp (local) in a generalized time format """Converts a timestamp (local) in a generalized time format
for LDAP for LDAP
""" """
if stamp is None:
stamp = time.time()
return "%s%s" % (time.strftime("%Y%m%d%H%M%S", time.localtime(stamp)), tz(time.altzone/3600)) return "%s%s" % (time.strftime("%Y%m%d%H%M%S", time.localtime(stamp)), tz(time.altzone/3600))
def fromGeneralizedTimeFormat(gtf): def fromGeneralizedTimeFormat(gtf):
@ -175,60 +160,6 @@ def fromGeneralizedTimeFormat(gtf):
""" """
return time.mktime(time.strptime(gtf.split("-", 1)[0].split("+", 1)[0].split('Z', 1)[0], "%Y%m%d%H%M%S")) return time.mktime(time.strptime(gtf.split("-", 1)[0].split("+", 1)[0].split('Z', 1)[0], "%Y%m%d%H%M%S"))
def datetimeFromGTF(gtf):
"""Returns a datetime from generalized time format
"""
if '-' in gtf or '+' in gtf:
date, tz = gtf[0:14], gtf[14:]
else:
date = gtf.replace("Z", '')
tz = '+0000'
return localizedDatetime(date, tz)
def datetimeToGTF(datetime_obj):
"""Transforms a datetime to a GTF"""
to_append = ""
if datetime_obj.utcoffset() is None:
if pytz is not None:
datetime_obj = pytz.utc.localize(datetime_obj)
else:
to_append = "Z"
mostly_gtf = datetime.datetime.strftime(datetime_obj, "%Y%m%d%H%M%S%z")
return mostly_gtf.replace('+0000', "Z") + to_append
def localizedDatetime(date=None, tz=None):
"""Génère un datetime localisé à partir d'une chaîne de la forme
%Y%m%d%H%M%S, et d'une chaîne tz de la forme +0200"""
_notz = (tz is None)
if date is not None:
the_date = datetime.datetime.strptime(date, "%Y%m%d%H%M%S")
else:
the_date = datetime.datetime.now()
# No timezone means we try to get from the system
# if we have dateutil, else, UTC.
if tz is None:
if dateutil is not None:
tz = datetime.datetime.now(dateutil.tz.tzlocal()).strftime("%z")
else:
tz = "+0000"
# No pytz means no timezoned datetime
if pytz is not None:
the_timezone = pytz.FixedOffset(int(tz[0:-2])*60 + int(tz[-2:]))
the_date = the_timezone.localize(the_date)
the_date = the_timezone.normalize(the_date)
else:
# Maybe we can do something
if dateutil is not None:
if _notz:
the_date.replace(tzinfo=dateutil.tz.tzlocal())
return the_date
def strip_accents(a, sois_un_porc_avec_les_espaces = True): def strip_accents(a, sois_un_porc_avec_les_espaces = True):
""" Supression des accents de la chaîne fournie """ """ Supression des accents de la chaîne fournie """
res = normalize('NFKD', decode(a)).encode('ASCII', 'ignore') res = normalize('NFKD', decode(a)).encode('ASCII', 'ignore')
@ -409,7 +340,7 @@ class Service:
starting = self.start starting = self.start
starting.sort() starting.sort()
dates = u' et '.join(map(lambda t: t < time.time() and \ dates = u' et '.join(map(lambda t: t < time.time() and \
u"maintenant" or time.strftime(date_format_new, u"maintenant" or time.strftime(date_format,
time.localtime(t)), time.localtime(t)),
self.start)) self.start))
dates = u" à partir d%s %s" % (dates.startswith(u"maintenant") and u"e" or u"u", dates = u" à partir d%s %s" % (dates.startswith(u"maintenant") and u"e" or u"u",
@ -804,7 +735,7 @@ class CransLdap:
result[i] = [] result[i] = []
# Fonction utile # Fonction utile
def build_filtre(champ, expr, neg=False, comp=''): def build_filtre(champ, expr, neg=False):
""" """
Retourne une chaine pour recherche dans la base LDAP Retourne une chaine pour recherche dans la base LDAP
du style (champ=expr) en adaptant les valeurs de expr au champ. du style (champ=expr) en adaptant les valeurs de expr au champ.
@ -835,20 +766,18 @@ class CransLdap:
# définifif (cf config.py). # définifif (cf config.py).
if config.periode_transitoire: if config.periode_transitoire:
# Pour la période transitoire année précédente ok # Pour la période transitoire année précédente ok
el = "(&(finAdhesion>=%(fin)s)(finConnexion>=%(fin)s))" % { el = "(|(paiement=%d)(paiement=%d)(finAdhesion>=%s))" % (config.ann_scol, config.ann_scol-1, generalizedTimeFormat(time.time()))
'fin': config.gtf_debut_periode_transitoire,
}
else: else:
el = "(&(finAdhesion>=%(fin)s)(finConnexion>=%(fin)s))" % { el = "(|(paiement=%s)(finAdhesion>=%s))" % (config.ann_scol, generalizedTimeFormat(time.time()))
'fin': generalizedTimeFormat(),
}
# Doit-on bloquer en cas de manque de la carte d'etudiant ? # Doit-on bloquer en cas de manque de la carte d'etudiant ?
# (si période transitoire on ne bloque dans aucun cas) # (si période transitoire on ne bloque dans aucun cas)
if config.bl_carte_et_definitif:
el = "(&(|(carteEtudiant=%d)(objectClass=club)(carteEtudiant=TRUE))%s)" % (config.ann_scol, el)
elif champ[1:] == 'blacklist': elif champ[1:] == 'blacklist':
el = '(blacklist=%s)' % expr el = '(blacklist=%s)' % expr
else: else:
# Cas général # Cas général
el = '(%s%s=%s)' % (champ, comp, expr) el = '(%s=%s)' % (champ, expr)
if neg: el = '(!%s)' % el if neg: el = '(!%s)' % el
return el return el
@ -871,16 +800,12 @@ class CransLdap:
# Test de l'expression de recherche et classement par filtres # Test de l'expression de recherche et classement par filtres
for cond in conds: for cond in conds:
neg = False neg = False
comp = ''
try: try:
champ, expr = cond.strip().split('=') champ, expr = cond.strip().split('=')
if champ[-1] == '!': if champ[-1] == '!':
# Négation pour ce champ # Négation pour ce champ
champ = champ[:-1] champ = champ[:-1]
neg = True neg = True
if champ[-1] in ['>', '<']:
comp = champ[-1]
champ = champ[0:-1]
except: except:
raise ValueError(u'Syntaxe de recherche invalide (%s)' % cond) raise ValueError(u'Syntaxe de recherche invalide (%s)' % cond)
@ -900,7 +825,7 @@ class CransLdap:
# Construction du filtre # Construction du filtre
for i in filtres: for i in filtres:
if champ in self.search_champs[i]: if champ in self.search_champs[i]:
filtre[i] += build_filtre(champ, expr, neg, comp) filtre[i] += build_filtre(champ, expr, neg)
ok = True ok = True
if champ not in self.auto_search_machines_champs \ if champ not in self.auto_search_machines_champs \
and champ not in self.non_auto_search_machines_champs: and champ not in self.non_auto_search_machines_champs:
@ -1022,6 +947,8 @@ class CransLdap:
if len(recherche['adherent']) > 0: if len(recherche['adherent']) > 0:
proprio = recherche['adherent'][0] proprio = recherche['adherent'][0]
if uid == "grosminet":
proprio = self.search("nom=grosminet", mode)['adherent'][0]
return proprio return proprio
__machines = () __machines = ()
@ -1060,34 +987,11 @@ class CransLdap:
class BaseClasseCrans(CransLdap): class BaseClasseCrans(CransLdap):
""" Méthodes de base des classes machines, et BaseProprietaire """ """ Méthodes de base des classes machines, et BaseProprietaire """
def proprietaire(self):
return None
def carteEtudiant(self, action=None):
return False
def adhesion(self, update=False, f=None):
return 0.0
def connexion(self, update=False, f=None):
return 0.0
def chbre(self, new=None):
return "????"
def __eq__(self, autre): def __eq__(self, autre):
""" Test d'égalité de deux instances de club/adhérent/machine, """ Test d'égalité de deux instances de club/adhérent/machine,
retourne True s'il s'agit du même club/adhérent/machine, False sinon """ retourne True s'il s'agit du même club/adhérent/machine, False sinon """
return self.__class__ == autre.__class__ and self.id() == autre.id() return self.__class__ == autre.__class__ and self.id() == autre.id()
def __enter__(self):
"""Dummy"""
pass
def __exit__(self, type, value, traceback):
"""Dummy"""
pass
def id(self): def id(self):
""" Retourne la valeur de l'attribut caractéristique de la classe (aid,mid,cid)""" """ Retourne la valeur de l'attribut caractéristique de la classe (aid,mid,cid)"""
try: try:
@ -1130,9 +1034,10 @@ class BaseClasseCrans(CransLdap):
# Il faut aussi regarder la blackliste du propriétaire # Il faut aussi regarder la blackliste du propriétaire
p = self.proprietaire() p = self.proprietaire()
bl_liste += p.blacklist() bl_liste += p.blacklist()
elif isinstance(self, Adherent) and (config.ann_scol in self.paiement() or (self.adhesion() > time.time() and self.connexion() > time.time())):
elif isinstance(self, Adherent) and (self.adhesion() > time.time() and self.connexion() > time.time()):
# blacklistes virtuelle si on est un adhérent pour carte étudiant et chambre invalides # blacklistes virtuelle si on est un adhérent pour carte étudiant et chambre invalides
if not config.periode_transitoire and config.bl_carte_et_actif and not bool(self.carteEtudiant()) and not self.sursis_carte():
actifs['carte_etudiant']=('-','-')
if self.chbre() == '????': if self.chbre() == '????':
actifs['chambre_invalide']=('-','-') actifs['chambre_invalide']=('-','-')
@ -1298,7 +1203,7 @@ class BaseClasseCrans(CransLdap):
# Cas spécial # Cas spécial
if "solde" in self.modifs: if "solde" in self.modifs:
diff = round(float(self._init_data.get('solde', [0])[0]) - float(self._data.get('solde', [0])[0]), 2) diff = float(self._init_data.get('solde', [0])[0]) - float(self._data.get('solde', [0])[0])
if diff > 0: if diff > 0:
modif['solde'] = "debit %s Euros" % str(diff) modif['solde'] = "debit %s Euros" % str(diff)
else: else:
@ -1327,7 +1232,7 @@ class BaseClasseCrans(CransLdap):
valeur_finale) valeur_finale)
# Formate les entrées de l'historique de la forme champ+diff-diff # Formate les entrées de l'historique de la forme champ+diff-diff
for champ in ['droits', 'controle', 'paiement', for champ in ['droits', 'controle', 'paiement', 'carteEtudiant',
'mailAlias', 'hostAlias', 'exempt', 'nvram', 'mailAlias', 'hostAlias', 'exempt', 'nvram',
'portTCPin', 'portTCPout', 'portUDPin', 'portUDPout', 'portTCPin', 'portTCPout', 'portUDPin', 'portUDPout',
'homepageAlias', 'imprimeurClub', 'gpgFingerprint', 'homepageAlias', 'imprimeurClub', 'gpgFingerprint',
@ -1360,7 +1265,7 @@ class BaseClasseCrans(CransLdap):
modif = ', '.join(liste_historique) modif = ', '.join(liste_historique)
timestamp = time.localtime() timestamp = time.localtime()
hist = "%s, %s" % ( time.strftime(date_format_new, timestamp), script_utilisateur ) hist = "%s, %s" % ( time.strftime(date_format, timestamp), script_utilisateur )
if self.modifs.has_key('derniereConnexion'): if self.modifs.has_key('derniereConnexion'):
# On nettoie l'historique pour ne garder que la dernière modification # On nettoie l'historique pour ne garder que la dernière modification
@ -1372,7 +1277,7 @@ class BaseClasseCrans(CransLdap):
# On loggue # On loggue
try: try:
fd = file('%s/%s_%s_%s' % ("%s/logs" % config.cimetiere, str(self.__class__).split('.')[-1], fd = file('%s/%s_%s_%s' % ("%s/logs" % config.cimetiere, str(self.__class__).split('.')[-1],
time.strftime('%Y-%m-%d-%H:%M:%S', timestamp), self.nom()), 'wb') time.strftime('%Y-%m-%d-%H:%M', timestamp), self.nom()), 'wb')
fd.write("%s\n" % self._data) fd.write("%s\n" % self._data)
fd.close() fd.close()
except: except:
@ -1463,12 +1368,12 @@ class BaseClasseCrans(CransLdap):
# Sauvegarde # Sauvegarde
t = str(self.__class__).split('.')[-1] t = str(self.__class__).split('.')[-1]
fd = open('%s/%s/%s_%s' % (config.cimetiere, t, fd = open('%s/%s/%s_%s' % (config.cimetiere, t,
time.strftime('%Y-%m-%d-%H:%M:%S'), time.strftime('%Y-%m-%d-%H:%M'),
self.nom()), 'wb') self.nom()), 'wb')
self.conn = None # Fermeture des connexions à la base sinon cPickle ne marchera pas self.conn = None # Fermeture des connexions à la base sinon cPickle ne marchera pas
cPickle.dump(self, fd, 2) cPickle.dump(self, fd, 2)
fd.close() fd.close()
index = u"%s, %s : %s %s # %s\n" % (time.strftime(date_format_new), index = u"%s, %s : %s %s # %s\n" % (time.strftime(date_format),
script_utilisateur, t, script_utilisateur, t,
self.Nom(), decode(comment)) self.Nom(), decode(comment))
@ -1615,6 +1520,7 @@ class BaseProprietaire(BaseClasseCrans):
finAdh.append(facture._data['finAdhesion'][0]) finAdh.append(facture._data['finAdhesion'][0])
self._set('debutAdhesion', debutAdh) self._set('debutAdhesion', debutAdh)
self._set('finAdhesion', finAdh) self._set('finAdhesion', finAdh)
self._save()
def droits(self, droits=None, light=False): def droits(self, droits=None, light=False):
""" Renvoie les droits courants. Non modifiable (sauf si surchargée dans classe enfant)""" """ Renvoie les droits courants. Non modifiable (sauf si surchargée dans classe enfant)"""
@ -1632,20 +1538,6 @@ class BaseProprietaire(BaseClasseCrans):
self._set('loginShell', [new]) self._set('loginShell', [new])
return new return new
def email_exterieur(self, new=None):
"""Retourne ou paramètre l'adresse mail extérieure de l'adhérent"""
if not new:
return decode(self._data.get('mailExt', [''])[0])
new = validate_mail(new)
self._set('mailExt', [new])
# On enlève les blacklistes mail_invalide
self.mail_invalide(False)
return new
def alias(self, new=None): def alias(self, new=None):
""" """
Création ou visualisation des alias mail Création ou visualisation des alias mail
@ -1840,24 +1732,12 @@ class BaseProprietaire(BaseClasseCrans):
return [] return []
def factures_adh(self): def factures_adh(self):
""" Retourne les factures pour adhésion valides.""" """ Retourne les factures pour adhésion """
factures_adh = self.factures("(debutAdhesion=*)") return self.factures("(debutAdhesion=*)")
return [
facture
for facture in factures_adh
if facture.controle() != "FALSE"
and facture.recuPaiement() is not None
]
def factures_conn(self): def factures_conn(self):
""" Retourne les factures pour connexion """ """ Retourne les factures pour connexion """
factures_conn = self.factures("(debutConnexion=*)") return self.factures("(debutConnexion=*)")
return [
facture
for facture in factures_conn
if facture.controle() != "FALSE"
and facture.recuPaiement() is not None
]
def solde(self, operation=None, comment=None): def solde(self, operation=None, comment=None):
""" Retourne ou modifie le solde d'un propriétaire """ Retourne ou modifie le solde d'un propriétaire
@ -2005,22 +1885,40 @@ class BaseProprietaire(BaseClasseCrans):
self._set('uidNumber', [new]) self._set('uidNumber', [new])
return new return new
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 sursis_carte(self):
if not config.bl_carte_et_actif:
return True
for h in self.historique()[::-1]:
x=re.match("(.*),.* : .*(paiement\+%s|inscription).*" % config.ann_scol,h)
if x != None:
return ((time.time()-time.mktime(time.strptime(x.group(1),'%d/%m/%Y %H:%M')))<=config.sursis_carte)
return False
def paiement_ok(self): def paiement_ok(self):
if isinstance(self, Adherent): if isinstance(self, Adherent):
m_paiement = min(self.adhesion(), self.connexion()) m_paiement = min(self.adhesion(), self.connexion())
else: else:
m_paiement = self.adhesion() m_paiement = self.adhesion()
if (m_paiement > time.time()) or (config.periode_transitoire and config.debut_periode_transitoire <= m_paiement <= config.fin_periode_transitoire): if config.ann_scol in self.paiement() or (config.periode_transitoire and (config.ann_scol-1) in self.paiement()) or (m_paiement > time.time()) or (config.periode_transitoire and config.debut_periode_transitoire <= m_paiement <= config.fin_periode_transitoire):
if config.periode_transitoire or not isinstance(self, Adherent) or not config.bl_carte_et_definitif or bool(self.carteEtudiant()):
return True return True
else:
return self.sursis_carte()
else: else:
return False return False
def delete(self, comment=''): def delete(self, comment=''):
"""Destruction du propriétaire""" """Destruction du propriétaire"""
if max(self.connexion(), self.adhesion()) + cotisation.del_post_adh >= time.time():
raise EnvironmentError("Vous ne pouvez supprimer un adhérent que %s jours après l'expiration de son adhésion et de sa connexion" % (cotisation.del_post_adh_jours,))
for m in self.machines(): for m in self.machines():
# Destruction machines # Destruction machines
m.delete(comment) m.delete(comment)
@ -2058,8 +1956,31 @@ class BaseProprietaire(BaseClasseCrans):
self.services_to_restart('bl_chbre_invalide') self.services_to_restart('bl_chbre_invalide')
if ('chbre' in self.modifs or 'paiement' in self.modifs) and self._data['chbre'][0] not in ("????", "EXT"): if ('chbre' in self.modifs or 'paiement' in self.modifs) and self._data['chbre'][0] not in ("????", "EXT"):
while True:
bat = self._data['chbre'][0][0] bat = self._data['chbre'][0][0]
ch = self._data['chbre'][0][1:] ch = self._data['chbre'][0][1:]
if annuaires.is_crans(bat, ch):
break
else:
r = prompt(u"La chambre %s est câblée sur le réseau CROUS. Est-ce *bien* la chambre de l'adhérent ? [O/N]" % self._data['chbre'][0], "O")
if r == 'O' or r == 'o':
annuaires.crous_to_crans(bat, ch)
else:
while True:
new_ch = prompt(u"Chambre de l'adhérent ?")
try:
self.chbre(new_ch)
except ValueError, c:
if len(c.args) == 2:
old_adh = c.args[1]
r = prompt(u"Changer %s de chambre ? [O/N]" % old_adh.Nom(), "n")
if r == 'O' or r == 'o':
old_adh.chbre('????')
old_adh.save()
self.chbre(new_ch)
break
else:
break
# Enregistrement # Enregistrement
self._save() self._save()
@ -2077,8 +1998,18 @@ class BaseProprietaire(BaseClasseCrans):
else: else:
ret += coul(u"Modification %s effectuée avec succès." % self.Nom(), 'vert') ret += coul(u"Modification %s effectuée avec succès." % self.Nom(), 'vert')
# Changements administratifs
test_carte = 'carteEtudiant' in self.modifs
if any([kw in self.modifs for kw in ['finConnexion', 'finAdhesion']]): if test_carte:
ret += coul('\n%s\n' % ('-'* 78), 'rouge')
ret += coul(u"Merci d'indiquer son aid (%s) en haut à gauche de la photocopie de la carte d'étudiant" % self.id(), 'gras')
ret += coul('\n%s\n' % ('-'* 78), 'rouge')
if test_carte and self.machines():
self.services_to_restart('bl_carte_etudiant')
if 'paiement' in self.modifs or (config.bl_carte_et_definitif and test_carte):
for m in self.machines(): for m in self.machines():
self.services_to_restart('macip', [m.ip()] ) self.services_to_restart('macip', [m.ip()] )
#self.services_to_restart('classify', [m.ip()] ) #self.services_to_restart('classify', [m.ip()] )
@ -2112,20 +2043,34 @@ class BaseProprietaire(BaseClasseCrans):
# Faut-il créer un compte sur vert ? # Faut-il créer un compte sur vert ?
if 'compte' in self.modifs: if 'compte' in self.modifs:
compte = self._data.get('uid', [''])[0]
if compte:
ret += u'\nUn compte a été créé :\n login : %s\n' % self.compte() ret += u'\nUn compte a été créé :\n login : %s\n' % self.compte()
r = prompt(u"Attribuer tout de suite un mot de passe, (A pour Automatique) ? [o/n/A]", "A") args = self._data['homeDirectory'][0] + ','
args+= self._data['uidNumber'][0] + ','
args+= self._data['uid'][0]
r = prompt(u"Attribuer tout de suite un mot de passe ? [O/N]", "O")
if r == 'O' or r == 'o': if r == 'O' or r == 'o':
change_password(login=self.compte()) change_password(login=self.compte())
if r == 'A' or r == 'a':
subprocess.call(['/usr/scripts/cransticket/dump_creds.py','--pass','uid=%s' % self.compte()])
else: else:
ret += coul(u' Il faudra penser à attribuer un mot de passe\n', 'jaune') ret += coul(u' Il faudra penser à attribuer un mot de passe\n', 'jaune')
# Le deuxième argument est le potentiel chemin de l'ancien compte
# s'il a jamais existé, ça permet de supprimer le home associé.
args = "%s,%s" % (compte, self.modifs.get('compte', ''))
r = prompt(u"Redirection mail ? [O/N]")
mail1 = mail2 = None
if r.lower().startswith('o'):
while True:
mail1 = prompt(u"Adresse mail ? (ANNUL pour annuler la redirection)")
if mail1 == "ANNUL":
mail1 = None
break
try:
validate_mail(mail1)
except ValueError, e:
print coul(e.message.encode('utf-8'), 'rouge')
continue
mail2 = prompt(u"Adresse mail (répéter) ?")
if mail1 == mail2 and mail1:
break
if mail1:
args += ',' + mail1
self.services_to_restart('home', [ args ]) self.services_to_restart('home', [ args ])
# Modif des droits ? # Modif des droits ?
@ -2354,6 +2299,20 @@ class Adherent(BaseProprietaire):
mail += '@crans.org' mail += '@crans.org'
return mail return mail
def email_exterieur(self, new=None):
"""Retourne ou paramètre l'adresse mail extérieure de l'adhérent"""
if not new:
return decode(self._data.get('mailExt', [''])[0])
new = validate_mail(new)
self._set('mailExt', [new])
# On enlève les blacklistes mail_invalide
self.mail_invalide(False)
return new
def mail_invalide(self, valeur=None): def mail_invalide(self, valeur=None):
""" """
L'adresse est invalide. L'adresse est invalide.
@ -2459,11 +2418,12 @@ class Adherent(BaseProprietaire):
debutConn = [] debutConn = []
finConn = [] finConn = []
for facture in self.factures_conn(): for facture in self.factures_adh():
debutConn.append(facture._data['debutConnexion'][0]) debutConn.append(facture._data['debutConnexion'][0])
finConn.append(facture._data['finConnexion'][0]) finConn.append(facture._data['finConnexion'][0])
self._set('debutConnexion', debutConn) self._set('debutConnexion', debutConn)
self._set('finConnexion', finConn) self._set('finConnexion', finConn)
self._save()
def adherentPayant(self, valeur = None): def adherentPayant(self, valeur = None):
""" """
@ -2486,15 +2446,11 @@ class Adherent(BaseProprietaire):
""" """
Supprime le compte sur zamok. Penser à définir l'adresse mail après. Supprime le compte sur zamok. Penser à définir l'adresse mail après.
""" """
if not self.compte():
return
self._set('mail', ['']) self._set('mail', [''])
if abs(self.solde()) >= 0.01: if abs(self.solde()) >= 0.01:
raise ValueError(u"Le solde d'un adhérent doit être nul pour supprimer son compte crans.") raise ValueError(u"Le solde d'un adhérent doit être nul pour supprimer son compte crans.")
self._data['objectClass'] = ['adherent'] self._data['objectClass'] = ['adherent']
if not 'compte' in self.modifs:
self.modifs['compte'] = "%s,%s" % (self.compte(), self.home())
for c in [ 'uid', 'cn', 'shadowLastChange', 'shadowMax', for c in [ 'uid', 'cn', 'shadowLastChange', 'shadowMax',
'shadowWarning', 'loginShell', 'userPassword', 'shadowWarning', 'loginShell', 'userPassword',
'uidNumber', 'gidNumber', 'homeDirectory', 'gecos', 'uidNumber', 'gidNumber', 'homeDirectory', 'gecos',
@ -2611,7 +2567,7 @@ class Adherent(BaseProprietaire):
self._data['mail'] = ["%s@crans.org" % (login)] self._data['mail'] = ["%s@crans.org" % (login)]
if not 'compte' in self.modifs: if not 'compte' in self.modifs:
self.modifs.setdefault('compte', ",") self.modifs.setdefault('compte', None)
# Création de l'alias canonique # Création de l'alias canonique
if self.nom() and self.prenom(): if self.nom() and self.prenom():
@ -2746,7 +2702,7 @@ class Adherent(BaseProprietaire):
if config.bl_vieux_cableurs: if config.bl_vieux_cableurs:
l = self.droits() l = self.droits()
if l != []: if l != []:
if not self.paiement_ok(): if config.ann_scol not in self.paiement():
reponse = True reponse = True
for d in droits_vieux: for d in droits_vieux:
if d in l: if d in l:
@ -2847,7 +2803,7 @@ class Club(BaseProprietaire):
return strip_accents(self.Nom()) return strip_accents(self.Nom())
def carteEtudiant(self, pd=None): def carteEtudiant(self, pd=None):
return True return [ config.ann_scol ]
def responsable(self, adher=None): def responsable(self, adher=None):
""" Responsable du club, adher doit être une instance de la classe adhérent """ """ Responsable du club, adher doit être une instance de la classe adhérent """
@ -2948,7 +2904,7 @@ class Club(BaseProprietaire):
self.lock('mail', login) self.lock('mail', login)
if not 'compte' in self.modifs: if not 'compte' in self.modifs:
self.modifs.setdefault('compte', ',') self.modifs.setdefault('compte', None)
self._data['objectClass'] = ['club', 'cransAccount', 'posixAccount', 'shadowAccount'] self._data['objectClass'] = ['club', 'cransAccount', 'posixAccount', 'shadowAccount']
self._data['uid'] = [ login ] self._data['uid'] = [ login ]
@ -2975,10 +2931,6 @@ class Club(BaseProprietaire):
""" Retourne l'adresse mail du responsable """ """ Retourne l'adresse mail du responsable """
return self.responsable().email() return self.responsable().email()
def update_connexion(self):
"""Dummy"""
pass
class Machine(BaseClasseCrans): class Machine(BaseClasseCrans):
""" Classe de définition d'une machine """ """ Classe de définition d'une machine """
idn = 'mid' idn = 'mid'
@ -3117,7 +3069,7 @@ Contactez nounou si la MAC est bien celle d'une carte.""", 3)
def __host_alias(self, champ, new): def __host_alias(self, champ, new):
""" Vérification de la validité d'un nom de machine """ """ Vérification de la validité d'un nom de machine """
# Supression des accents # Supression des accents
new = strip_accents(unicode(new, 'utf-8')) new = strip_accents(decode(new))
l, new = preattr(new) l, new = preattr(new)
new = new.lower() new = new.lower()
@ -3191,7 +3143,7 @@ Contactez nounou si la MAC est bien celle d'une carte.""", 3)
self._set('prise', []) self._set('prise', [])
return return
if not re.match('^[a-cg-jkmopv][0-7][0-5][0-9]$', new.lower()): if not re.match('^[a-cg-jmopv][0-7][0-5][0-9]$', new.lower()):
raise ValueError('Prise incorrecte') raise ValueError('Prise incorrecte')
self._set('prise', [new.upper()]) self._set('prise', [new.upper()])
@ -3267,7 +3219,7 @@ Contactez nounou si la MAC est bien celle d'une carte.""", 3)
self._set('sshFingerprint', liste) self._set('sshFingerprint', liste)
return liste return liste
def ip(self, ip=None, lock=True, force=False): def ip(self, ip=None, lock=True):
""" """
Défini ou retourne l'IP de la machine. 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. Les IP sont stoquées sous forme xxx.xxx.xxx.xxx et doivent être fournies ainsi.
@ -3284,111 +3236,74 @@ Contactez nounou si la MAC est bien celle d'une carte.""", 3)
l, ip = preattr(ip) l, ip = preattr(ip)
# Dans quel sous réseau se trouve le rid? # Dans quel réseau la machine doit-elle être placée ?
if isinstance(self, MachineWifi): if isinstance(self, MachineWifi):
mach_type = u'wifi-adh' net = config.NETs['wifi-adh']
plage_rid = config.rid_primaires['wifi-adh'] pool_ip = lister_ip_dispo('wifi-adh')
# Si on ajoute une bornv6, on prend dans la plage v6 only
elif isinstance(self, BorneWifi): elif isinstance(self, BorneWifi):
mach_type = u'bornes' net = config.NETs['bornes']
try: pool_ip = lister_ip_dispo('bornes')
if self.bornev6:
plage_rid = config.rid_primaires['bornes-v6']
except AttributeError:
plage_rid = config.rid_primaires['bornes']
elif isinstance(self.proprietaire(), AssociationCrans): elif isinstance(self.proprietaire(), AssociationCrans):
mach_type = u'crans' net = [ '0.0.0.0/0' ]
plage_rid = config.rid_primaires['serveurs'] pool_ip = lister_ip_dispo('all')
else: else:
proprio = self.proprietaire() proprio = self.proprietaire()
if proprio.etudes(0) == 'Personnel ENS': if proprio.etudes(0) == 'Personnel ENS':
mach_type = u'personnel-ens' net = config.NETs['personnel-ens']
plage_rid = config.rid_primaires['personnel-ens'] pool_ip = lister_ip_dispo('personnel-ens')
elif not isinstance(proprio, Adherent) or proprio.adherentPayant(): elif not isinstance(proprio, Adherent) or proprio.adherentPayant():
proprio_subnet = 'adherents' proprio_subnet = 'adherents'
mach_type = 'adherents'
try: try:
plage_rid = config.rid_primaires[proprio_subnet] net = config.NETs[proprio_subnet]
pool_ip = lister_ip_dispo(proprio_subnet)
except: except:
raise RuntimeError(u'Impossible de trouver le réseau où placer la machine.') raise RuntimeError(u'Impossible de trouver le réseau où placer la machine.')
# Le switch-bloc suivant modifie ip pour le faire correspondre à une
# vraie ip et le rid correspondant. Si ip='', cela signifie que l'on ne
# veut plus d'ipv4 (cela arrive si on a un rid ipv6 only par ex)
# Si <automatique> et self.rid(), recalcule l'ip à partir du rid
if ip == '<automatique>' and self.rid():
rid = int(self.rid())
rid_obj = ridtools.Rid(rid)
if rid_obj.ipv4_dispo:
ip = unicode(rid_obj.ipv4())
else: else:
ip = '' net = config.NETs["gratuit"]
#On essaye d'attribuer un rid si ip auto et pas encore de rid pool_ip = lister_ip_dispo("gratuit")
elif ip == '<automatique>':
for plage in plage_rid:
rid_pris = [
int(elem[1]['rid'][0])
for elem in self.conn.search_ext_s(self.base_dn, ldap.SCOPE_SUBTREE, "(&(rid>=%s)(rid<=%s))" % (plage[0], plage[1]))
]
# Par defaut la plage est pas v6 (cf bornes/bornes v6)
for rid in xrange(plage[0], plage[1] + 1):
if rid not in rid_pris:
# On verifie que l'ip se termine pas par 0 ou 255 et que il y a une ipv4 dispo (on sort si on est dans une plage v6)
if not ridtools.Rid(rid=rid).ipv4_dispo:
if not force:
raise ValueError(u"La machine sera v6 only", 1)
ip = ''
break
# On vire les ip qui se terminent par 0 ou 255 (toutes, thanks windows)
if rid % 256 != 0 and rid % 256 != 255:
ip = unicode(ridtools.Rid(rid=rid).ipv4())
break
# Si on a une ip, on s'en va
if ip != '<automatique>':
break
# Si après tout ca, on a encore auto, c'est qu'il n'y a plus d'ip
if ip == '<automatique>': if ip == '<automatique>':
raise RuntimeError(u"Plus d'IP (rid) libres dans %s." % mach_type) # On va prendre choisir une IP au hasard dans le pool des IP dispo
random.shuffle(pool_ip)
while len(pool_ip) > 0:
ip = pool_ip.pop() # On choisit une IP
if not self.exist('ipHostNumber=%s' % ip):
# On a trouvé la première ip libre
pool_ip.append(ip)
break
# Sinon, ip fournie, contrôle qu'elle est compatible avec le type machine if not len(pool_ip):
# et calcule le rid qui va bien avec raise RuntimeError(u"Plus d'IP libres dans %s." % ' et '.join(net))
elif ip != '':
elif ip == '':
self._set('ipHostNumber', [])
ip = None
else:
# L'ip est elle dans le bon sous-réseau ? # L'ip est elle dans le bon sous-réseau ?
# (accessoirement teste si l'IP est valide et ne correspond pas # (accessoirement teste si l'IP est valide et ne correspond pas
# à l'adresse de broadcast ou de réseau) # à l'adresse de broadcast ou de réseau)
rid = ridtools.Rid(ipv4=ip).rid if not iptools.AddrInNet(ip, net):
if not mach_type==u'crans' and not unicode(ridtools.find_rid_plage(rid)[0])==mach_type :
raise ValueError(u'IP invalide ou en dehors du sous-réseau alloué.', 1) raise ValueError(u'IP invalide ou en dehors du sous-réseau alloué.', 1)
# Reformatage # Reformatage
ip = iptools.DecToQuad(iptools.QuadToDec(ip)) ip = iptools.DecToQuad(iptools.QuadToDec(ip))
# L'ip est-elle déja allouée ? # L'ip est-elle déja allouée ?
if self.exist('ipHostNumber=%s' % ip): if self.exist('ipHostNumber=%s' % ip):
raise ValueError(u'IP déjà prise.') raise ValueError(u'IP déjà prise.')
else: # cas où ip == ''
if not self.rid():
raise ValueError(u'Vous devez indiquer une ip ou <automatique>')
rid = int(self.rid())
# Après ce bloc, rid et ip ont été fixés if ip != None:
# on calcule maintenant les modifs à lancer rid = ridtools.Rid(ipv4=ip)
old_ip = (self._data.get('ipHostNumber', None) or [''])[0] else:
rid = self.rid()
if rid == '':
pass
maj_ip = ip != old_ip
maj_rid = self.rid() != str(rid)
# Et on fait les modifs, ip d'abord, car self.rid() pourrait
# faire des tests supplémentaires
if maj_ip:
# Lock ip # Lock ip
if lock and ip: if lock and ip:
self.lock('ipHostNumber', ip) self.lock('ipHostNumber', ip)
self._set('ipHostNumber', [ip] if ip else []) self._set('ipHostNumber', [ip])
if maj_rid:
self.rid(str(rid), lock=lock)
self.rid("%d" % rid, lock=lock)
return ip return ip
def rid(self, rid=None, lock=True): def rid(self, rid=None, lock=True):
@ -3398,14 +3313,14 @@ Contactez nounou si la MAC est bien celle d'une carte.""", 3)
if rid == None: if rid == None:
return self._data.get('rid', [''])[0] return self._data.get('rid', [''])[0]
if self.ip() not in ['<automatique>', '']:
rid_t = ridtools.Rid(ipv4=self.ip()) rid_t = ridtools.Rid(ipv4=self.ip())
if int(rid_t) != int(rid): if int(rid_t) != int(rid):
raise ValueError('L\'ip et le rid doivent correspondre. rid : %s, ip : %s, rid_t : %s' % (int(rid), self.ip(), rid_t)) raise ValueError('L\'ip et le rid doivent correspondre. rid : %s, ip : %s, rid_t : %s' % (int(rid), self.ip(), rid_t))
if lock: if lock:
self.lock('rid', str(rid)) self.lock('rid', '%s' % rid)
self._set('rid', [str(rid)]) self._set('rid', ['%s' % rid])
def exempt(self, new=None): def exempt(self, new=None):
""" """
@ -4058,6 +3973,9 @@ class Facture(BaseClasseCrans):
# levera une exeption # levera une exeption
self._crediter() self._crediter()
# ajout des frais à la liste d'articles
self.ajoute(self._frais())
# modifie la base ldap # modifie la base ldap
self._set("recuPaiement", [new]) self._set("recuPaiement", [new])
elif new == False: elif new == False:
@ -4110,10 +4028,7 @@ class Facture(BaseClasseCrans):
# solde impression (on débite d'abord si jamais quelqu'un s'amuse à recharger son solde avec son solde) # solde impression (on débite d'abord si jamais quelqu'un s'amuse à recharger son solde avec son solde)
if art["code"] == "SOLDE": if art["code"] == "SOLDE":
proprio = self.proprietaire() proprio = self.proprietaire()
proprio.solde(operation=art['nombre']*art["pu"], proprio.solde(operation=art['nombre']*art["pu"], comment="Facture n°%s : %s" % (self.numero(), art['designation']))
comment="Facture n°%s : %s" %
(self.numero(),
art['designation'].encode(config.ldap_encoding, errors='ignore')))
proprio.save() proprio.save()
if self.modePaiement() == 'solde': if self.modePaiement() == 'solde':
proprio = self.proprietaire() proprio = self.proprietaire()
@ -4146,23 +4061,39 @@ class Facture(BaseClasseCrans):
def _frais(self): def _frais(self):
""" """
Retourne une liste d'articles correspondants aux divers frais Retourne une liste d'articles correspondants aux divers frais
Facturer les frais étant illicite (article L112-12 du code monétaire et financier),
on droppe cette fonctionnalité. La fonction ne reste que pour des raisons de rétro-compatibilité.
""" """
arts = []
# aucun frais pour une facture payée, ils sont intégrés aux articles
if self.recuPaiement():
return [] return []
# frais de paiement par paypal
if self.modePaiement() == 'paypal':
# 25 centimes pour le paiement paypal
s = 0.25
# et on ajoute 3.5% du montant
for art in self._articles():
s += 0.035 * art['nombre'] * art['pu']
# arrondissage-tronquage
s = float(int(s*100)/100.0)
# ajoute à la liste d'articles de frais
arts.append( {'code':'FRAIS', 'designation':'Frais de tansaction PayPal', 'nombre':1, 'pu':round(s, 2)} )
return arts
def _articles(self, arts = None): def _articles(self, arts = None):
"""Retourne ou modifie la liste des articles de la base""" """Retourne ou modifie la liste des articles de la base"""
# modifie la liste des articles # modifie la liste des articles
if arts != None: if arts != None:
self._set('article', self._set('article',
['%s~~%s~~%s~~%s' % ( ['%s~~%s~~%s~~%s' % (art['code'], art['designation'],
art['code'], str(art['nombre']), str(art['pu']))
art['designation'],
str(art['nombre']),
str(art['pu']))
for art in arts]) for art in arts])
# charge la liste des articles # charge la liste des articles
@ -4170,26 +4101,13 @@ class Facture(BaseClasseCrans):
for art in self._data.get("article", []): for art in self._data.get("article", []):
art = art.split('~~') art = art.split('~~')
art = { 'code' : art[0], art = { 'code' : art[0],
'designation' : art[1].decode(config.ldap_encoding, errors='replace'), 'designation' : art[1],
'nombre' : int(art[2]), 'nombre' : int(art[2]),
'pu' : float(art[3]) } 'pu' : float(art[3]) }
arts.append(art) arts.append(art)
return arts return arts
def remise(self, amount, reason):
"""Effectue une remise du montant annoncé. Cela crée un article
explicite annonçant qu'il y a une remise. Une remise ne peut
jamais excéder le montant total actuel de la facture."""
amount = min(amount, self.total())
self.ajoute({
'nombre': 1,
'code': 'REMISE',
'designation': u'Remise : %s' % (reason,),
'pu': -amount,
})
def ajoute(self, ajoute): def ajoute(self, ajoute):
"""Ajoute un/des article(s) à la facture """Ajoute un/des article(s) à la facture
ajoute est un article ou une liste d'articles ajoute est un article ou une liste d'articles
@ -4204,15 +4122,14 @@ class Facture(BaseClasseCrans):
# ajoute les articles # ajoute les articles
if type(ajoute)==dict: if type(ajoute)==dict:
ajoute = [ajoute] ajoute = [ajoute]
if type(ajoute)==list: if type(ajoute)==list:
for art in ajoute: for art in ajoute:
if int(art['nombre']) != float(art['nombre']): if int(art['nombre']) != float(art['nombre']):
raise ValueError, u'nombre doit être un entier' raise ValueError, u'nombre doit être un entier'
if round(art['pu'], 2) != art['pu']: if float(int(art['pu']*100)/100.0) != art['pu']:
raise ValueError, u'pu ne doit pas avoir plus de 2 chiffres apres la virgule' raise ValueError, u'pu ne doit pas avoir plus de 2 chiffres apres la virgule'
art['nombre'] = int(art['nombre']) art['nombre'] = int(art['nombre'])
if '~~' in art['designation']: if '~~' in ' '.join([str(x) for x in art.values()]):
raise ValueError, u'Ne pas mettre de ~~ dans les champs' raise ValueError, u'Ne pas mettre de ~~ dans les champs'
arts.append(art) arts.append(art)
@ -4248,15 +4165,15 @@ class Facture(BaseClasseCrans):
Retourne la liste des articles. Retourne la liste des articles.
Un article est un dictionnaire de la forme : Un article est un dictionnaire de la forme :
{ 'code' : string, { 'code' : string,
'designation' : unicode, 'designation' : string,
'nombre' : int, 'nombre' : int,
'pu' : int/float } 'pu' : int/float }
""" """
return self._articles() return self._articles() + self._frais()
def total(self): def total(self):
""" """
Calcule le total de la facture. Calcule le total de la facture, frais compris
""" """
s = 0 s = 0
for art in self.articles(): for art in self.articles():
@ -4286,7 +4203,7 @@ class Facture(BaseClasseCrans):
item_id = 0 item_id = 0
for item in self.articles(): for item in self.articles():
item_id += 1 item_id += 1
url += "&item_name_%d=%s" % (item_id, item['designation'].encode('ascii', errors='replace')) url += "&item_name_%d=%s" % (item_id, item['designation'])
url += "&amount_%d=%s" % (item_id, item['pu']) url += "&amount_%d=%s" % (item_id, item['pu'])
url += "&quantity_%d=%s" % (item_id, int(item['nombre'])) url += "&quantity_%d=%s" % (item_id, int(item['nombre']))
@ -4306,11 +4223,8 @@ class Facture(BaseClasseCrans):
def delete(self, comment=''): def delete(self, comment=''):
"""Suppression de la facture""" """Suppression de la facture"""
if self.controle(): if self.controle():
if max(self.proprietaire().connexion(), self.proprietaire().adhesion()) + cotisation.del_post_adh >= time.time(): raise EnvironmentError(u"La facture a déjà été controlée")
raise EnvironmentError(u"La facture a déjà été controlée, contacter trésorerie")
self.__proprietaire = None self.__proprietaire = None
self._delete(self.dn, comment) self._delete(self.dn, comment)
@ -4322,10 +4236,8 @@ class _FakeProprio(CransLdap):
if not self.conn: if not self.conn:
self.connect() self.connect()
self.dn = self.base_dn self.dn = self.base_dn
def id(self): def id(self):
return '' return ''
def blacklist(self, new=None): def blacklist(self, new=None):
if new is not None: if new is not None:
print >>sys.stderr, "Tentative d'ajout de blacklist à un propriétaire virtuel :" print >>sys.stderr, "Tentative d'ajout de blacklist à un propriétaire virtuel :"
@ -4337,26 +4249,22 @@ class _FakeProprio(CransLdap):
print >>sys.stderr, new print >>sys.stderr, new
raise ValueError("Impossible de blacklister %r" % str(self)) raise ValueError("Impossible de blacklister %r" % str(self))
return [] return []
def paiement(self):
return [ config.ann_scol ]
def carteEtudiant(self): def carteEtudiant(self):
return True return [ config.ann_scol ]
def blacklist_actif(self): def blacklist_actif(self):
return [] return []
def mail(self, new=None): def mail(self, new=None):
return 'roots@crans.org' return 'roots@crans.org'
def machines(self): def machines(self):
res = self.conn.search_s(self.dn, 1, Machine.filtre_idn) res = self.conn.search_s(self.dn, 1, Machine.filtre_idn)
m = [] m = []
for r in res: for r in res:
m.append(self.make(r)) m.append(self.make(r))
return m return m
def adhesion(self): def adhesion(self):
return time.time() + 86400 return time.time() + 86400
def connexion(self): def connexion(self):
return time.time() + 86400 return time.time() + 86400

Some files were not shown because too many files have changed in this diff Show more