Compare commits
1 commit
master
...
cleanup-ge
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1fc605c7e0 |
338 changed files with 24750 additions and 33474 deletions
11
.gitignore
vendored
11
.gitignore
vendored
|
@ -17,8 +17,17 @@
|
|||
# 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
|
||||
archive/gestion/clef-wifi*
|
||||
gestion/clef-wifi*
|
||||
|
||||
# Autres dépôts git
|
||||
gestion/logreader/
|
||||
|
|
31
README.md
31
README.md
|
@ -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 ! !!
|
|
@ -1,8 +1,7 @@
|
|||
#!/bin/bash /usr/scripts/python.sh
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) Stéphane Glondu, Alexandre Bos, Michel Blockelet
|
||||
# Remanié en 2015 par Gabriel Détraz
|
||||
# Licence : GPLv2
|
||||
|
||||
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
|
||||
sys.path.append('/usr/scripts/gestion')
|
||||
import config
|
||||
import config.mails
|
||||
from email_tools import send_email, parse_mail_template
|
||||
|
||||
# Fonctions d'affichage
|
||||
from gestion.affich_tools import coul, tableau, prompt, cprint
|
||||
|
||||
from utils.sendmail import actually_sendmail
|
||||
from gestion import mail
|
||||
from affich_tools import coul, tableau, prompt, cprint
|
||||
|
||||
# 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 !
|
||||
from socket import gethostname
|
||||
debug = False
|
||||
|
||||
# Conn à la db
|
||||
ldap = shortcuts.lc_ldap_admin()
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 3 and sys.argv[-2] == '--debug':
|
||||
debug = sys.argv[-1]
|
||||
|
@ -67,28 +65,33 @@ def _controle_interactif_adherents(liste):
|
|||
|
||||
nb = 0
|
||||
for a in liste:
|
||||
valeur = a['charteMA']
|
||||
valeur = a.charteMA()
|
||||
if valeur:
|
||||
suggestion = 'o'
|
||||
else:
|
||||
suggestion = 'n'
|
||||
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
|
||||
if ok == 'o':
|
||||
nb += 1
|
||||
if a['charteMA'] != True :
|
||||
modifiable = ldap.search(u'aid=%s' % a['aid'][0], mode='rw')
|
||||
try:
|
||||
with modifiable[0] as adh:
|
||||
adh['charteMA']=True
|
||||
adh.history_gen()
|
||||
adh.save()
|
||||
cprint(u'Controle OK')
|
||||
except:
|
||||
cprint(u'Adhérent %s locké, réessayer plus tard' % a['nom'][0], 'rouge')
|
||||
elif ok != 'n':
|
||||
cprint(u'Arrêt du contrôle des membres actifs', 'rouge')
|
||||
if a.charteMA() == False :
|
||||
modifiable = db.search('aid=%s' % a.id(), 'w')['adherent'][0]
|
||||
if modifiable._modifiable:
|
||||
modifiable.charteMA(True)
|
||||
cprint(modifiable.save())
|
||||
else:
|
||||
cprint(u'Adhérent %s locké, réessayer plus tard' % modifiable.Nom(), 'rouge')
|
||||
elif ok == 'n':
|
||||
if a.charteMA() == True:
|
||||
modifiable = db.search('aid=%s' % a.id(), 'w')['adherent'][0]
|
||||
if modifiable._modifiable:
|
||||
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
|
||||
|
||||
return nb, len(liste)-nb
|
||||
|
@ -96,12 +99,12 @@ def _controle_interactif_adherents(liste):
|
|||
|
||||
def liste_charte_nok():
|
||||
"""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 = []
|
||||
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
|
||||
and not adh['charteMA']):
|
||||
and not adh.charteMA()):
|
||||
liste_nok.append(adh)
|
||||
return liste_nok
|
||||
|
||||
|
@ -113,7 +116,7 @@ def controle_interactif():
|
|||
|
||||
# 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
|
||||
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 !
|
||||
ok, nok = _controle_interactif_adherents(todo_list)
|
||||
|
@ -129,18 +132,19 @@ def spammer():
|
|||
todo_list = liste_charte_nok()
|
||||
|
||||
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"
|
||||
|
||||
for adh in todo_list:
|
||||
to = adh['mail'][0]
|
||||
to = adh.email()
|
||||
print to
|
||||
if not debug:
|
||||
From = u"ca@crans.org"
|
||||
data=mail.generate('missing_charte_MA', {
|
||||
'To': unicode(to),
|
||||
'From': From,
|
||||
})
|
||||
actually_sendmail(u'ca@crans.org', (unicode(to),), data)
|
||||
data = config.mails.txt_charte_MA % {'From' : u"ca@crans.org", 'To' : to}
|
||||
connexion.sendmail("ca@crans.org",to,data.encode('utf-8'))
|
||||
|
||||
def __usage(message=None):
|
||||
""" Comment ça marche ? """
|
||||
|
@ -159,7 +163,7 @@ if __name__ == '__main__' :
|
|||
__usage(u'Mauvaise utilisation de liste')
|
||||
print "Liste des membres actifs n'ayant pas signé la charte :"
|
||||
for adh in liste_charte_nok():
|
||||
print unicode(adh['prenom'][0]) + u" " + unicode(adh['nom'][0])
|
||||
print adh.Nom()
|
||||
elif sys.argv[1] == 'modif':
|
||||
if len(sys.argv) != 2:
|
||||
__usage(u'Mauvaise utilisation de modif')
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash /usr/scripts/python.sh
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
|
@ -12,8 +12,9 @@ Licence : GPL v2
|
|||
|
||||
import os, sys, time
|
||||
import subprocess
|
||||
from lc_ldap import shortcuts
|
||||
from gestion.config import upload
|
||||
sys.path.append('/usr/scripts/gestion')
|
||||
from ldap_crans import crans_ldap
|
||||
from config import upload
|
||||
# logging tools
|
||||
import syslog
|
||||
def log(x):
|
||||
|
@ -29,8 +30,6 @@ import utils.exceptions
|
|||
import locale
|
||||
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.
|
||||
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):
|
||||
"""On génère la feuille d'avertissement et on retourne son emplacement."""
|
||||
barcode = "/usr/scripts/admin/mail_invalide/barcode.eps"
|
||||
name = unicode(proprio['prenom'][0]) + u" " + unicode(proprio['nom'][0])
|
||||
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 = '/usr/scripts/var/mails_invalides'
|
||||
|
||||
# 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(' ', '-'))
|
||||
|
||||
# Création du fichier tex
|
||||
format_date = '%A %d %B %Y'
|
||||
with open('%s/mail_invalide.tex' % os.path.dirname(__file__), 'r') as tempfile:
|
||||
template = tempfile.read()
|
||||
template = template.replace('~prenom~', proprio['prenom'][0].encode('utf-8'))
|
||||
template = template.replace('~nom~', proprio['nom'][0].encode('utf-8'))
|
||||
template = template.replace('~chambre~', proprio['chbre'][0].encode('utf-8'))
|
||||
template = template.replace('~prenom~', proprio.prenom().encode('utf-8'))
|
||||
template = template.replace('~nom~', proprio.nom().encode('utf-8'))
|
||||
template = template.replace('~chambre~', proprio.chbre().encode('utf-8'))
|
||||
template = template.replace('~mail~', mail.encode('utf-8').replace('_', '\\_'))
|
||||
template = template.replace('~fin~',
|
||||
time.strftime(format_date, time.localtime(time.time()+14*86400)))
|
||||
|
@ -85,37 +83,35 @@ def generate_ps(proprio, mail):
|
|||
except Exception, e:
|
||||
log('Erreur lors de la génération du ps : ')
|
||||
log(str(e))
|
||||
log("Values : adherent:%s" % name)
|
||||
log("Values : adherent:%s" % proprio.Nom())
|
||||
log(utils.exceptions.formatExc())
|
||||
raise
|
||||
|
||||
def set_mail_invalide(adherent, mail, a_verifier, a_imprimer):
|
||||
name = unicode(adherent['prenom'][0]) + u" " + unicode(adherent['nom'][0])
|
||||
if adherent['chbre'][0] in ['????', 'EXT']:
|
||||
print u"Chambre de %s : %s, générer la fiche ? [Yn]" % (name, adherent['chbre'][0])
|
||||
if adherent.chbre() in ['????', 'EXT']:
|
||||
print u"Chambre de %s : %s, générer la fiche ? [Yn]" % (adherent.Nom().encode('utf-8'), adherent.chbre())
|
||||
read = ''
|
||||
while read not in ['y', 'n']:
|
||||
read = raw_input().lower()
|
||||
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)
|
||||
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)
|
||||
print fiche
|
||||
a_imprimer.append(fiche)
|
||||
with adherent as adh:
|
||||
adh.blacklist('mail_invalide','Mail Invalide - Script',debut=int(time.time()) + DELAY * 24 * 3600)
|
||||
adh.history_gen()
|
||||
adh.save()
|
||||
adherent.blacklist([time.time() + 14 * 24 * 3600,
|
||||
'-', 'mail_invalide', "Mail invalide"])
|
||||
adherent.save()
|
||||
|
||||
if __name__ == "__main__":
|
||||
if '--help' in sys.argv or '-h' in sys.argv or len(sys.argv) < 2:
|
||||
print help
|
||||
sys.exit(0)
|
||||
|
||||
ldap = shortcuts.lc_ldap_admin()
|
||||
db = crans_ldap()
|
||||
|
||||
# On fait la liste des .forwards dans les homes
|
||||
print " * Lecture des .forward ..."
|
||||
|
@ -145,24 +141,24 @@ if __name__ == "__main__":
|
|||
# Est-ce un aid ?
|
||||
if adresse[0] == '-':
|
||||
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:
|
||||
print "*** Erreur : aucun résultat pour aid=%s" % adresse[1:]
|
||||
a_verifier.append(adresse)
|
||||
elif len(res) > 1:
|
||||
print "*** Erreur : plusieurs résultats pour aid=%s :" % adresse[1:]
|
||||
for adh in res:
|
||||
print unicode(adh['prenom'][0]) + u" " + unicode(adh['nom'][0])
|
||||
print adh.Nom()
|
||||
a_verifier.append(adresse)
|
||||
else:
|
||||
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
|
||||
|
||||
print " * Recherche de %s ..." % adresse
|
||||
# Est-ce un .forward ?
|
||||
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:
|
||||
print "*** Erreur : aucun résultat pour uid=%s" % forwards[adresse]
|
||||
a_verifier.append(adresse)
|
||||
|
@ -172,18 +168,18 @@ if __name__ == "__main__":
|
|||
continue
|
||||
|
||||
# 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:
|
||||
print "*** Erreur : aucun résultat pour %s" % adresse
|
||||
a_verifier.append(adresse)
|
||||
elif len(res) > 1:
|
||||
print "*** Erreur : plusieurs résultats pour %s :" % adresse
|
||||
for adh in res:
|
||||
print unicode(adh['prenom'][0]) + u" " + unicode(adh['nom'][0])
|
||||
print adh.Nom()
|
||||
a_verifier.append(adresse)
|
||||
else:
|
||||
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:
|
||||
print ''
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#!/bin/bash /usr/scripts/python.sh
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
|
||||
# Copyright (C) Stéphane Glondu, Alexandre Bos, et autres
|
||||
# Copyright (C) Stéphane Glondu, Alexandre Bos
|
||||
# Licence : GPLv2
|
||||
|
||||
__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 gestion.config
|
||||
sys.path.append('/usr/scripts/gestion')
|
||||
import config
|
||||
from email_tools import send_email, parse_mail_template
|
||||
|
||||
# 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
|
||||
from lc_ldap import shortcuts
|
||||
ldap = shortcuts.lc_ldap_admin()
|
||||
from ldap_crans import crans_ldap, ann_scol
|
||||
db = crans_ldap()
|
||||
|
||||
def _controle_interactif_adherents(liste):
|
||||
"""
|
||||
|
@ -47,28 +50,26 @@ def _controle_interactif_adherents(liste):
|
|||
nb = 0
|
||||
for a in liste:
|
||||
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
|
||||
if ok == 'o':
|
||||
modifiable = ldap.search(u'aid=%s' % a['aid'][0], mode='rw')[0]
|
||||
try:
|
||||
with modifiable as adh:
|
||||
adh['droits'].remove(u'Cableur')
|
||||
adh.history_gen()
|
||||
adh.save()
|
||||
cprint(u'Droits cableurs retirés', 'rouge')
|
||||
except:
|
||||
cprint(u'Adhérent %s locké, réessayer plus tard' % modifiable['nom'][0], 'rouge')
|
||||
modifiable = db.search('aid=%s' % a.id(), 'w')['adherent'][0]
|
||||
if modifiable._modifiable:
|
||||
modifiable.droits([])
|
||||
cprint(modifiable.save())
|
||||
else:
|
||||
cprint(u'Adhérent %s locké, réessayer plus tard' % modifiable.Nom(), 'rouge')
|
||||
elif ok != 'n':
|
||||
cprint(u'Arrêt du contrôle %s des membres actifs' % explicite, 'rouge')
|
||||
break
|
||||
|
||||
def candidats():
|
||||
todo_list1 = ldap.search(u'droits=cableur')
|
||||
todo_list1 = db.search('droits=*')['adherent']
|
||||
todo_list = []
|
||||
for adh in todo_list1:
|
||||
if not adh.paiement_ok():
|
||||
if adh.droitsGeles():
|
||||
todo_list.append(adh)
|
||||
todo_list.sort(lambda x, y: cmp((x.nom(), x.prenom()), (y.nom(), y.prenom())))
|
||||
return todo_list
|
||||
|
||||
def lister():
|
||||
|
@ -79,7 +80,7 @@ def lister():
|
|||
print "Liste des câbleur dont la cotisation n'est pas à jour."
|
||||
print
|
||||
for adh in todo_list:
|
||||
print unicode(adh['prenom'][0]) + u" " + unicode(adh['nom'][0])
|
||||
print adh.prenom() + " " + adh.nom()
|
||||
print
|
||||
print "total : " + str(len(todo_list))
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash /usr/scripts/python.sh
|
||||
#!/usr/bin/python
|
||||
# -*- mode: python; coding: utf-8 -*-
|
||||
#
|
||||
# total_impression.py
|
||||
|
@ -6,7 +6,6 @@
|
|||
#
|
||||
# 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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# 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."""
|
||||
|
||||
import sys
|
||||
from lc_ldap import shortcuts
|
||||
from gestion.affich_tools import cprint
|
||||
sys.path.append("/usr/scripts/gestion/")
|
||||
from ldap_crans import crans_ldap
|
||||
from config import ann_scol
|
||||
from affich_tools import cprint
|
||||
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):
|
||||
u""" Convertit une date en entier. """
|
||||
|
@ -50,7 +52,7 @@ def soldes_adherent(dlinf, dlsup, adherent, verbose):
|
|||
totaldebit = 0
|
||||
totalcredit = 0
|
||||
|
||||
for hist in adherent['historique']:
|
||||
for hist in adherent.historique():
|
||||
sep = ' '
|
||||
champ = hist.replace(',', '').replace(': ', '').split(sep)
|
||||
if datestrtoint(champ[0]) >= dlinf and (dlsup == 0 or datestrtoint(champ[0]) <= dlsup):
|
||||
|
@ -110,23 +112,19 @@ def calcul_soldes():
|
|||
totaldebit = 0
|
||||
totalcredit = 0
|
||||
|
||||
liste = ldap.search(u"uid=*",sizelimit=10000)
|
||||
liste = db.search("login=*")['adherent']
|
||||
|
||||
for adherent in liste:
|
||||
try:
|
||||
adhdebit, adhcredit = soldes_adherent(dlinf, dlsup, adherent, verbose)
|
||||
if adhdebit + adhcredit > 0 and adhdebit + adhcredit < 1000000: # On evite Toto Passoir
|
||||
if verbose >= 2:
|
||||
cprint('-' * 40, 'cyan')
|
||||
if verbose >= 1:
|
||||
name = unicode(adherent['prenom'][0]) + u" " + unicode(adherent['nom'][0])
|
||||
cprint(u'Debit total pour ' + name + u' : ' + unicode(adhdebit) + u' euros', 'rouge')
|
||||
cprint(u'Credit total pour ' + name + u' : ' + unicode(adhcredit) + u' euros', 'vert')
|
||||
cprint('=' * 40, 'bleu')
|
||||
totaldebit += adhdebit
|
||||
totalcredit += adhcredit
|
||||
except KeyError:
|
||||
pass
|
||||
adhdebit, adhcredit = soldes_adherent(dlinf, dlsup, adherent, verbose)
|
||||
if adhdebit + adhcredit > 0 and adhdebit + adhcredit < 1000000: # On evite Toto Passoir
|
||||
if verbose >= 2:
|
||||
cprint('-' * 40, 'cyan')
|
||||
if verbose >= 1:
|
||||
cprint('Debit total pour ' + adherent.Nom() + ' : ' + str(adhdebit) + ' euros', 'rouge')
|
||||
cprint('Credit total pour ' + adherent.Nom() + ' : ' + str(adhcredit) + ' euros', 'vert')
|
||||
cprint('=' * 40, 'bleu')
|
||||
totaldebit += adhdebit
|
||||
totalcredit += adhcredit
|
||||
if verbose >= 1:
|
||||
cprint('=' * 80, 'bleu')
|
||||
if dlinf == 0:
|
||||
|
|
|
@ -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, _)
|
|
@ -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}
|
||||
]
|
||||
|
|
@ -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"
|
|
@ -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)
|
|
@ -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)
|
|
@ -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")
|
|
@ -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)
|
||||
)
|
|
@ -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)
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/env python2.7
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Python plugin initializator for
|
||||
Bcfg2"""
|
||||
|
||||
from .PythonPlugin import Python
|
|
@ -29,21 +29,21 @@ device_map = {'block': stat.S_IFBLK,
|
|||
'fifo': stat.S_IFIFO}
|
||||
|
||||
|
||||
def calcMode(initial, mode):
|
||||
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}]
|
||||
tempmode = initial
|
||||
if len(mode) == 3:
|
||||
mode = '0%s' % (mode)
|
||||
pdigits = [int(mode[digit]) for digit in range(4)]
|
||||
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:
|
||||
tempmode |= perm
|
||||
return tempmode
|
||||
tempperms |= perm
|
||||
return tempperms
|
||||
|
||||
|
||||
def normGid(entry):
|
||||
|
@ -137,18 +137,18 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
entry.set('current_group', str(ondisk[stat.ST_GID]))
|
||||
except (OSError, KeyError):
|
||||
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):
|
||||
"""Verify Path type='directory' entry."""
|
||||
if entry.get('mode') == None or \
|
||||
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('mode', '')) < 4:
|
||||
entry.set('mode', '0' + entry.get('mode', ''))
|
||||
while len(entry.get('perms', '')) < 4:
|
||||
entry.set('perms', '0' + entry.get('perms', ''))
|
||||
try:
|
||||
ondisk = os.stat(entry.get('name'))
|
||||
except OSError:
|
||||
|
@ -165,14 +165,14 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
owner = 'root'
|
||||
group = '0'
|
||||
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':
|
||||
mtime = str(finfo[stat.ST_MTIME])
|
||||
else:
|
||||
mtime = '-1'
|
||||
pTrue = ((owner == str(normUid(entry))) and
|
||||
(group == str(normGid(entry))) and
|
||||
(mode == entry.get('mode')) and
|
||||
(perms == entry.get('perms')) and
|
||||
(mtime == entry.get('mtime', '-1')))
|
||||
|
||||
pruneTrue = True
|
||||
|
@ -217,19 +217,19 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
nqtext += "%s group is %s should be %s" % \
|
||||
(entry.get('name'), group, entry.get('group'))
|
||||
entry.set('qtext', nqtext)
|
||||
if mode != entry.get('mode'):
|
||||
entry.set('current_mode', mode)
|
||||
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'),
|
||||
mode,
|
||||
entry.get('mode')))
|
||||
perms,
|
||||
entry.get('perms')))
|
||||
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.get('name'),
|
||||
mode,
|
||||
entry.get('mode'))
|
||||
perms,
|
||||
entry.get('perms'))
|
||||
entry.set('qtext', nqtext)
|
||||
if mtime != entry.get('mtime', '-1'):
|
||||
entry.set('current_mtime', mtime)
|
||||
|
@ -249,7 +249,7 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
|
||||
def Installdirectory(self, 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('group') == None:
|
||||
self.logger.error('Entry %s not completely specified. '
|
||||
|
@ -547,7 +547,7 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
err = sys.exc_info()[1]
|
||||
self.logger.error("Could not chown %s: %s" % (newfile.name,
|
||||
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'))
|
||||
if entry.get('mtime', '-1') != '-1':
|
||||
try:
|
||||
|
@ -568,7 +568,7 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
|
||||
def Verifypermissions(self, 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('group') == None:
|
||||
self.logger.error('Entry %s not completely specified. '
|
||||
|
@ -637,7 +637,7 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
|
||||
def Installpermissions(self, entry):
|
||||
"""Install POSIX permissions"""
|
||||
if entry.get('mode') == None or \
|
||||
if entry.get('perms') == None or \
|
||||
entry.get('owner') == None or \
|
||||
entry.get('group') == None:
|
||||
self.logger.error('Entry %s not completely specified. '
|
||||
|
@ -659,7 +659,7 @@ class Python(Bcfg2.Client.Tools.Tool):
|
|||
try:
|
||||
for p in plist:
|
||||
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
|
||||
except (OSError, KeyError):
|
||||
self.logger.error('Permission fixup failed for %s' % \
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash /usr/scripts/python.sh
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" 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
|
||||
if "--mail" in sys.argv:
|
||||
if hosts != "":
|
||||
sys.path.append("/usr/scripts/")
|
||||
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)
|
||||
elif debug:
|
||||
|
|
|
@ -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 = ""
|
||||
}
|
||||
}'
|
|
@ -6,117 +6,38 @@
|
|||
# License : GPLv3
|
||||
# Date : 27/04/2014
|
||||
|
||||
import os
|
||||
import datetime
|
||||
import pytz
|
||||
import logging
|
||||
|
||||
TZ = pytz.timezone('Europe/Paris')
|
||||
LDIRPATH = os.getenv('DBG_CLOGGER_PATH', '/var/log/clogger')
|
||||
|
||||
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)
|
||||
"""
|
||||
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
|
||||
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.
|
||||
self.c_file_handler_level = getattr(logging, self.c_level.upper(), logging.INFO)
|
||||
self.c_file_handler.setLevel(self.c_file_handler_level)
|
||||
self.fhlevel = getattr(logging, level.upper(), logging.INFO)
|
||||
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
|
||||
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
|
||||
self.addHandler(self.c_file_handler)
|
||||
|
||||
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
|
||||
self.addHandler(self.fh)
|
||||
|
|
|
@ -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
|
||||
|
|
@ -1,22 +1,15 @@
|
|||
# ⁻*- 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 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/
|
||||
"""
|
||||
#
|
||||
# Ce fichier contient la définition de plusieurs fonctions d'interface à freeradius
|
||||
# qui peuvent être appelées (suivant les configurations) à certains moment de
|
||||
# l'éxécution.
|
||||
#
|
||||
|
||||
import logging
|
||||
import netaddr
|
||||
import radiusd # Module magique freeradius (radiusd.py is dummy)
|
||||
import ldap
|
||||
import os
|
||||
import binascii
|
||||
import hashlib
|
||||
|
||||
import lc_ldap.shortcuts
|
||||
from lc_ldap.crans_utils import escape as escape_ldap
|
||||
|
@ -25,63 +18,40 @@ import lc_ldap.objets
|
|||
import gestion.config.config as config
|
||||
from gestion.gen_confs.trigger import trigger_generate_cochon as trigger_generate
|
||||
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))
|
||||
|
||||
#: 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'
|
||||
|
||||
#: Suffixe à retirer du username si présent (filaire)
|
||||
USERNAME_SUFFIX_FIL = '.crans.org'
|
||||
|
||||
## -*- Logging -*-
|
||||
|
||||
class RadiusdHandler(logging.Handler):
|
||||
"""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é)
|
||||
# Initialisation d'un logger pour faire des stats etc
|
||||
# pour l'instant, on centralise tout sur thot en mode debug
|
||||
logger = logging.getLogger('auth.py')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
formatter = logging.Formatter('%(name)s: [%(levelname)s] %(message)s')
|
||||
handler = RadiusdHandler()
|
||||
handler.setFormatter(formatter)
|
||||
handler = logging.handlers.SysLogHandler(address = '/dev/log')
|
||||
try:
|
||||
handler.addFormatter(formatter)
|
||||
except AttributeError:
|
||||
handler.formatter = formatter
|
||||
logger.addHandler(handler)
|
||||
|
||||
## -*- Types de blacklists -*-
|
||||
#: reject tout de suite
|
||||
BL_REJECT = [u'bloq']
|
||||
bl_reject = [u'bloq']
|
||||
|
||||
#: 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
|
||||
BL_ACCUEIL = [u'paiement']
|
||||
bl_accueil = []
|
||||
|
||||
# À classer:
|
||||
# [u'carte_etudiant', u'chambre_invalide', ]
|
||||
# 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']
|
||||
# Ces blacklists ont des effets soft (portail captif port 80)
|
||||
#bl_accueil = [u'carte_etudiant', u'chambre_invalide', u'paiement']
|
||||
|
||||
## -*- Decorateurs -*-
|
||||
# À 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,
|
||||
constructor=lc_ldap.shortcuts.lc_ldap_anonymous)
|
||||
|
||||
def radius_event(fun):
|
||||
def radius_event(f):
|
||||
"""Décorateur pour les fonctions d'interfaces avec radius.
|
||||
Une telle fonction prend un uniquement argument, qui est une liste de tuples
|
||||
(clé, valeur) et renvoie un triplet dont les composantes sont :
|
||||
|
@ -99,23 +69,22 @@ def radius_event(fun):
|
|||
et autres trucs du genre)
|
||||
* un tuple de couples (clé, valeur) pour les valeurs internes à mettre à
|
||||
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
|
||||
tuples en entrée en un dictionnaire."""
|
||||
|
||||
def new_f(auth_data):
|
||||
if type(auth_data) == dict:
|
||||
data = auth_data
|
||||
else:
|
||||
data = dict()
|
||||
for (key, value) in auth_data or []:
|
||||
# Beware: les valeurs scalaires sont entre guillemets
|
||||
# Ex: Calling-Station-Id: "une_adresse_mac"
|
||||
data[key] = value.replace('"', '')
|
||||
data = dict()
|
||||
for (key, value) in auth_data or []:
|
||||
# Beware: les valeurs scalaires sont entre guillemets
|
||||
# Ex: Calling-Station-Id: "une_adresse_mac"
|
||||
data[key] = value.replace('"', '')
|
||||
try:
|
||||
return fun(data)
|
||||
except Exception as err:
|
||||
logger.error('Failed %r on data %r' % (err, auth_data))
|
||||
return f(data)
|
||||
except Exception as e:
|
||||
logger.error(repr(e) + ' on data ' + repr(auth_data))
|
||||
raise
|
||||
|
||||
return new_f
|
||||
|
@ -135,21 +104,18 @@ def get_machines(data, conn, is_wifi=True, proprio=None):
|
|||
try:
|
||||
mac = lc_ldap.crans_utils.format_mac(mac.decode('ascii', 'ignore'))
|
||||
except:
|
||||
logger.error('Cannot format MAC !')
|
||||
radiusd.radlog(radiusd.L_ERR, 'Cannot format MAC !')
|
||||
mac = None
|
||||
username = data.get('User-Name', None)
|
||||
if username:
|
||||
# Pour les requètes venant de federezwifi
|
||||
username = username.split('@', 1)[0]
|
||||
|
||||
username = escape_ldap(username.decode('ascii', 'ignore'))
|
||||
if username.endswith(suffix):
|
||||
username = username[:-len(suffix)]
|
||||
|
||||
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:
|
||||
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
|
||||
# ** 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))' %
|
||||
(base, username, suffix), **opt)
|
||||
|
||||
if TEST_SERVER:
|
||||
res += conn.search(u'(&%s(host=%s%s))' %
|
||||
(base, username, suffix), **opt)
|
||||
return res
|
||||
|
||||
def get_prise_chbre(data):
|
||||
|
@ -201,7 +164,7 @@ def get_prise_chbre(data):
|
|||
try:
|
||||
bat_name = nas[3].upper()
|
||||
bat_num = int(nas.split('-', 1)[1])
|
||||
except (IndexError, ValueError):
|
||||
except IndexError, ValueError:
|
||||
pass
|
||||
port = data.get('NAS-Port', None)
|
||||
if port:
|
||||
|
@ -219,7 +182,7 @@ def get_prise_chbre(data):
|
|||
def realm_of_machine(machine):
|
||||
"""Renvoie le `realm` d'une machine. Don't ask"""
|
||||
if isinstance(machine, lc_ldap.objets.machineFixe):
|
||||
return 'adherents'
|
||||
return 'fil'
|
||||
elif isinstance(machine, lc_ldap.objets.machineWifi):
|
||||
return 'wifi-adh'
|
||||
else:
|
||||
|
@ -227,29 +190,29 @@ def realm_of_machine(machine):
|
|||
|
||||
def get_fresh_rid(machine):
|
||||
"""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)
|
||||
try:
|
||||
return machine.conn._find_id('rid', realm, lock_id)
|
||||
return machine.conn._find_id('rid', realm, lockId)
|
||||
finally:
|
||||
machine.conn.lockholder.purge(lock_id)
|
||||
machine.conn.lockholder.purge(lockId)
|
||||
|
||||
@use_ldap_admin
|
||||
def register_machine(data, machine, conn):
|
||||
"""Enregistre la mac actuelle et/ou assigne le rid sur une machine donnée."""
|
||||
def register_mac(data, machine, conn):
|
||||
"""Enregistre la mac actuelle sur une machine donnée."""
|
||||
# TODO lc_ldap devrait posséder une fonction pour passer en rw depuis un ro
|
||||
if 'w' not in machine.mode:
|
||||
machine = conn.search(dn=machine.dn, scope=ldap.SCOPE_BASE, mode='rw')[0]
|
||||
|
||||
mac = data.get('Calling-Station-Id', None)
|
||||
if mac is None:
|
||||
logger.warn('Cannot find MAC for registration (aborting)')
|
||||
radiusd.radlog(radiusd.L_ERR, 'Cannot find MAC')
|
||||
return
|
||||
mac = mac.decode('ascii', 'ignore').replace('"','')
|
||||
try:
|
||||
mac = lc_ldap.crans_utils.format_mac(mac).lower()
|
||||
except Exception:
|
||||
logger.warn('Cannot format MAC for registration (aborting)')
|
||||
except:
|
||||
radiusd.radlog(radiusd.L_ERR, 'Cannot format MAC !')
|
||||
return
|
||||
|
||||
with machine:
|
||||
|
@ -270,25 +233,13 @@ def register_machine(data, machine, conn):
|
|||
@radius_event
|
||||
@use_ldap_admin
|
||||
@use_ldap
|
||||
def instantiate(*_):
|
||||
def instantiate(p, *conns):
|
||||
"""Utile pour initialiser les connexions ldap une première fois (otherwise,
|
||||
do nothing)"""
|
||||
logger.info('Instantiation')
|
||||
if TEST_SERVER:
|
||||
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
|
||||
def authorize_wifi(data):
|
||||
"""Section authorize pour le wifi
|
||||
|
@ -300,29 +251,26 @@ def authorize_wifi(data):
|
|||
items = get_machines(data)
|
||||
|
||||
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
|
||||
|
||||
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]
|
||||
|
||||
proprio = machine.proprio()
|
||||
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
|
||||
|
||||
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
|
||||
# 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):
|
||||
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
|
||||
|
||||
password = machine['ipsec'][0].value.encode('ascii', 'ignore')
|
||||
|
@ -337,124 +285,17 @@ def authorize_wifi(data):
|
|||
|
||||
@radius_event
|
||||
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,
|
||||
(),
|
||||
(
|
||||
("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
|
||||
def post_auth_wifi(data):
|
||||
"""Appelé une fois que l'authentification est ok.
|
||||
|
@ -467,13 +308,14 @@ def post_auth_wifi(data):
|
|||
log_message = '(wifi) %s -> %s [%s%s]' % \
|
||||
(port, mac, vlan_name, (reason and u': ' + reason).encode('utf-8'))
|
||||
logger.info(log_message)
|
||||
radiusd.radlog(radiusd.L_AUTH, log_message)
|
||||
|
||||
# Si NAS ayant des mapping particuliers, à signaler ici
|
||||
vlan_id = config.vlans[vlan_name]
|
||||
|
||||
# WiFi : Pour l'instant, on ne met pas d'infos de vlans dans la réponse
|
||||
# les bornes wifi ont du mal avec cela
|
||||
if WIFI_DYN_VLAN:
|
||||
if TEST_SERVER:
|
||||
return (radiusd.RLM_MODULE_UPDATED,
|
||||
(
|
||||
("Tunnel-Type", "VLAN"),
|
||||
|
@ -496,6 +338,7 @@ def post_auth_fil(data):
|
|||
log_message = '(fil) %s -> %s [%s%s]' % \
|
||||
(port, mac, vlan_name, (reason and u': ' + reason).encode('utf-8'))
|
||||
logger.info(log_message)
|
||||
radiusd.radlog(radiusd.L_AUTH, log_message)
|
||||
|
||||
# Si NAS ayant des mapping particuliers, à signaler ici
|
||||
vlan_id = config.vlans[vlan_name]
|
||||
|
@ -546,8 +389,8 @@ def decide_vlan(data, is_wifi, conn):
|
|||
proprio = machine.proprio()
|
||||
|
||||
# Avant de continuer, on assigne la mac à la machine candidat
|
||||
if '<automatique>' in machine['macAddress'] or not machine['rid']:
|
||||
register_machine(data, machine)
|
||||
if '<automatique>' in machine['macAddress']:
|
||||
register_mac(data, machine)
|
||||
|
||||
if not machine['ipHostNumber']:
|
||||
decision = 'v6only', u'No IPv4'
|
||||
|
@ -560,9 +403,9 @@ def decide_vlan(data, is_wifi, conn):
|
|||
|
||||
# Application des blacklists
|
||||
for bl in machine.blacklist_actif():
|
||||
if bl.value['type'] in BL_ISOLEMENT:
|
||||
if bl.value['type'] in bl_isolement:
|
||||
decision = 'isolement', unicode(bl)
|
||||
if bl.value['type'] in BL_ACCUEIL:
|
||||
if bl.value['type'] in bl_accueil:
|
||||
decision = 'accueil', unicode(bl)
|
||||
|
||||
# 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
|
||||
# (typiquement, les locaux sous digicode)
|
||||
decision = decision[0], decision[1] + u' (local club)'
|
||||
elif chbre in PUBLIC_CHBRE:
|
||||
decision = decision[0], decision[1] + u' (lieu de vie)'
|
||||
else:
|
||||
for hebergeur in hebergeurs:
|
||||
# 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
|
||||
|
||||
@radius_event
|
||||
def dummy_fun(_):
|
||||
"""Do nothing, successfully. (C'est pour avoir un truc à mettre)"""
|
||||
def dummy_fun(p):
|
||||
return radiusd.RLM_MODULE_OK
|
||||
|
||||
def detach(_=None):
|
||||
def detach(p=None):
|
||||
"""Appelé lors du déchargement du module (enfin, normalement)"""
|
||||
print "*** goodbye from auth.py ***"
|
||||
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
../rlm_python_unifie.conf
|
|
@ -2,10 +2,6 @@
|
|||
#
|
||||
# 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>
|
||||
#
|
||||
# This should only be used when testing modules.
|
37
freeradius/rlm_python_fil.conf
Normal file
37
freeradius/rlm_python_fil.conf
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
37
freeradius/rlm_python_wifi.conf
Normal file
37
freeradius/rlm_python_wifi.conf
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
######################################################################
|
||||
#
|
||||
# Authentification filaire du crans
|
||||
#
|
||||
######################################################################
|
||||
|
||||
server filaire {
|
||||
authorize{
|
||||
preprocess
|
||||
crans_unifie
|
||||
}
|
||||
|
||||
authenticate{
|
||||
crans_unifie
|
||||
}
|
||||
|
||||
post-auth{
|
||||
crans_unifie
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
# }
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -16,10 +16,9 @@ delattr(sys, 'argv')
|
|||
|
||||
auth.instantiate(())
|
||||
|
||||
# Test avec l'interface wifi d'apprentis
|
||||
p=(
|
||||
('Calling-Station-Id', '02:69:75:42:24:03'),
|
||||
('User-Name', 'apprentis-wifi'),
|
||||
('Calling-Station-Id', 'b0:79:94:cf:d1:9a'),
|
||||
('User-Name', 'moo-torola'),
|
||||
)
|
||||
|
||||
print repr(auth.authorize_wifi(p))
|
|
@ -1 +0,0 @@
|
|||
../auth.py
|
|
@ -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
|
|
@ -94,6 +94,10 @@ def dialog(backtitle, arg, dialogrc=''):
|
|||
|
||||
# Récupération du contenu du pipe
|
||||
_, 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()
|
||||
|
||||
# Récupération du code d'erreur
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
# Contenu :
|
||||
# ---------
|
||||
#
|
||||
# Décorateur :
|
||||
# static_var([(name, val)]), un décorateur pour créer des variables
|
||||
# statiques dans une fonction
|
||||
#
|
||||
# Fonctions :
|
||||
# getTerminalSize(), une fonction qui récupère le couple
|
||||
# largeur, hauteur du terminal courant.
|
||||
|
@ -35,13 +39,12 @@ import os
|
|||
import fcntl
|
||||
import termios
|
||||
import struct
|
||||
import functools
|
||||
import time
|
||||
import re
|
||||
|
||||
from locale import getpreferredencoding
|
||||
|
||||
from cranslib.decorators import static_var
|
||||
|
||||
OCT_NAMES = ["Pio", "Tio", "Gio", "Mio", "Kio"]
|
||||
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'
|
||||
|
@ -53,9 +56,7 @@ def try_decode(string):
|
|||
avoir en réception.
|
||||
|
||||
"""
|
||||
if isinstance(string, unicode):
|
||||
return string
|
||||
|
||||
unicode_str = ""
|
||||
try:
|
||||
return string.decode("UTF-8")
|
||||
except UnicodeDecodeError:
|
||||
|
@ -87,6 +88,21 @@ def guess_preferred_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():
|
||||
"""Dummy function to get term dimensions.
|
||||
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 "\033[1;0m"
|
||||
|
||||
@static_var(("styles", {}))
|
||||
@static_var([("styles", {})])
|
||||
def style(texte, what=None, dialog=False):
|
||||
"""Pretty text is pretty
|
||||
On peut appliquer plusieurs styles d'affilée, ils seront alors traités
|
||||
|
@ -558,26 +574,7 @@ if __name__ == "__main__":
|
|||
time.sleep(1)
|
||||
prettyDoin("Les carottes sont cuites." , "Ok")
|
||||
|
||||
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"
|
||||
]
|
||||
]
|
||||
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"]]
|
||||
titres = ("Nom", "Prénom", "Âge", "Adresse")
|
||||
longueurs = [25, 25, '*', '*']
|
||||
print tableau(data, titres, longueurs).encode(guess_preferred_encoding())
|
||||
|
|
|
@ -2,11 +2,8 @@
|
|||
|
||||
import os
|
||||
import psycopg2
|
||||
|
||||
from functools import wraps
|
||||
|
||||
import time
|
||||
import socket
|
||||
|
||||
conn = None
|
||||
# : é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.")
|
||||
attempts = 0
|
||||
while not conn or not attempts:
|
||||
host = os.getenv('DBG_ANNUAIRE', 'pgsql.v4.adm.crans.org')
|
||||
|
||||
# Test habituel sur vo:
|
||||
if host == '1' or __name__.endswith('annuaires_pg_test'):
|
||||
if __name__.endswith('annuaires_pg_test') or os.getenv('DBG_ANNUAIRE', False):
|
||||
host='localhost'
|
||||
|
||||
else:
|
||||
host='pgsql.v4.adm.crans.org'
|
||||
# "connecting …"
|
||||
try:
|
||||
if not conn:
|
||||
if attempts:
|
||||
# Attend un peu avant de reessayer
|
||||
time.sleep(delay)
|
||||
conn = psycopg2.connect(user='crans_ro', database='django',
|
||||
conn = psycopg2.connect(user='crans', database='switchs',
|
||||
host=host)
|
||||
return f(*args, **kwargs)
|
||||
except psycopg2.OperationalError:
|
||||
|
@ -47,8 +42,7 @@ def _need_conn(f):
|
|||
# backend pgsql. On utilise donc une exception plus standard
|
||||
return first_connect
|
||||
|
||||
# Le v est virtuel.
|
||||
bat_switchs = ["a", "b", "c", "g", "h", "i", "j", "m", "o", "p", "v"]
|
||||
bat_switchs = ["a", "b", "c", "g", "h", "i", "j", "m", "o", "p"]
|
||||
|
||||
class ChbreNotFound(ValueError):
|
||||
"""Lorsqu'une chambre n'existe pas"""
|
||||
|
@ -61,14 +55,14 @@ def chbre_prises(batiment, chambre = None):
|
|||
if chambre:
|
||||
chambre = chambre.lower()
|
||||
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:
|
||||
return "%03d" % cur.fetchone()[0]
|
||||
except TypeError:
|
||||
raise ChbreNotFound("Chambre inexistante bat %r, chbre %r" % (batiment, chambre))
|
||||
else:
|
||||
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 = {}
|
||||
for chambre, prise_crans in cur.fetchall():
|
||||
ret[chambre] = "%03d" % prise_crans
|
||||
|
@ -82,7 +76,7 @@ def chbre_commentaire(batiment, chambre):
|
|||
global conn
|
||||
batiment = batiment.lower()
|
||||
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:
|
||||
return cur.fetchone()[0]
|
||||
except TypeError:
|
||||
|
@ -94,14 +88,14 @@ def reverse(batiment, prise = None):
|
|||
batiment = batiment.lower()
|
||||
if prise:
|
||||
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:
|
||||
return [chbre for (chbre,) in cur.fetchall()]
|
||||
except TypeError:
|
||||
raise ValueError("Prise %s inexistante" % prise)
|
||||
else:
|
||||
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 = {}
|
||||
for chambre, prise_crans in cur.fetchall():
|
||||
try:
|
||||
|
@ -112,15 +106,37 @@ def reverse(batiment, prise = None):
|
|||
if not ret:
|
||||
raise ValueError("Batiment %s inexistant" % batiment)
|
||||
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
|
||||
def is_connected(batiment, chambre):
|
||||
"""Cablage physique effectue ?"""
|
||||
batiment = batiment.lower()
|
||||
chambre = chambre.lower()
|
||||
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]
|
||||
|
||||
@_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
|
||||
uplink_prises={ 'a' :
|
||||
{ 49 : 'uplink->bata-4', 50 : 'libre-service',
|
||||
|
@ -137,8 +153,7 @@ uplink_prises={ 'a' :
|
|||
349 : 'uplink->batb-4', 350 : 'libre-service',
|
||||
401 : 'uplink->batb-0', 402 : 'uplink->batb-1',
|
||||
403 : 'uplink->batb-2', 404 : 'uplink->batb-3',
|
||||
405 : 'uplink->backbone', 523 : 'uplink->batb-4',
|
||||
},
|
||||
405 : 'uplink->backbone' },
|
||||
'c' :
|
||||
{ 49 : 'uplink->batc-3', 50 : 'libre-service',
|
||||
149 : 'uplink->batc-3', 150 : 'libre-service',
|
||||
|
@ -271,38 +286,14 @@ uplink_prises={ 'a' :
|
|||
},
|
||||
}
|
||||
|
||||
_SPECIAL_SWITCHES = ['backbone.adm.crans.org',
|
||||
'multiprise-v6.adm.crans.org',
|
||||
'batk-0.crans.org',
|
||||
'minigiga.adm.crans.org',
|
||||
'batb-5.crans.org',
|
||||
]
|
||||
_HIDDEN_SWITCHES = [
|
||||
'batp-4.adm.crans.org',
|
||||
'batv-0.adm.crans.org',
|
||||
]
|
||||
_SPECIAL_SWITCHES=['backbone.adm.crans.org',
|
||||
'multiprise-v6.adm.crans.org',
|
||||
'batk-0.crans.org',
|
||||
'batp-4.adm.crans.org',
|
||||
'minigiga.adm.crans.org',
|
||||
]
|
||||
|
||||
def guess_switch_fqdn(switch_name):
|
||||
"""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):
|
||||
def all_switchs(bat=None, hide=_SPECIAL_SWITCHES):
|
||||
"""Retourne la liste des switchs pour un batiment.
|
||||
|
||||
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"""
|
||||
|
||||
if bat == None:
|
||||
bat = list(bat_switchs)
|
||||
bat = bat_switchs
|
||||
if type(bat) not in [ tuple, list ] :
|
||||
bat = [bat]
|
||||
switchs = []
|
||||
for b in bat:
|
||||
indexes = set(n/100 for n in uplink_prises[b])
|
||||
for i in indexes:
|
||||
switch_name = "bat%s-%s" % (b, i)
|
||||
try:
|
||||
hostname = guess_switch_fqdn(switch_name)
|
||||
except socket.gaierror:
|
||||
print "Le switch %s ne semble pas exister." % (switch_name,)
|
||||
continue
|
||||
hostname = "bat%s-%s.adm.crans.org" % (b, i)
|
||||
if hostname not in hide:
|
||||
switchs.append(hostname)
|
||||
# on ajoute quand-même le backbone et/ou multiprise-v6 si demandé
|
||||
|
|
|
@ -18,10 +18,11 @@ import re
|
|||
|
||||
import affichage
|
||||
import lc_ldap.shortcuts
|
||||
from lc_ldap.crans_utils import to_generalized_time_format as to_gtf
|
||||
|
||||
import mail as mail_module
|
||||
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, }
|
||||
DAY = datetime.timedelta(days=1)
|
||||
|
@ -71,28 +72,16 @@ def warn_or_delete(smtp, clandestin, fail, done):
|
|||
mail_addr = clandestin.get_mail()
|
||||
if not clandestin.machines() or not mail_addr:
|
||||
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 = {
|
||||
"from" : RESP,
|
||||
"chambre" : exchambre,
|
||||
"jours" : (date_suppr - now).days+1,
|
||||
"to" : mail_addr,
|
||||
"adh": clandestin,
|
||||
"chbre_url" : chbre_url,
|
||||
"chbre_url_error" : chbre_url_error,
|
||||
"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):
|
||||
"""Renvoie une ligne de tableau, pour une machine"""
|
||||
|
@ -112,7 +101,7 @@ if __name__ == '__main__':
|
|||
conn = lc_ldap.shortcuts.lc_ldap_admin()
|
||||
|
||||
if periode_transitoire:
|
||||
date = gtf_debut_periode_transitoire
|
||||
date = to_gtf(debut_periode_transitoire)
|
||||
else:
|
||||
date = now.strftime(FORMAT_LDAP) + 'Z'
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import lc_ldap.attributs
|
|||
import lc_ldap.objets
|
||||
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()
|
||||
|
||||
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')
|
||||
except UnicodeDecodeError:
|
||||
problem = True
|
||||
msg += u"Le mot de passe ne doit contenir que des caractères ascii.\n"
|
||||
|
||||
if len(password) >= 64:
|
||||
problem = True
|
||||
msg += u"Le mot de passe doit faire strictement moins de 64 caractères\n"
|
||||
if not dialog:
|
||||
affich_tools.cprint(u'Le mot de passe ne doit contenir que des caractères ascii.', "rouge")
|
||||
else:
|
||||
msg += affich_tools.coul(u'Le mot de passe ne doit contenir que des caractères ascii.\n', "rouge", dialog=dialog)
|
||||
|
||||
# Nounou mode
|
||||
if no_cracklib:
|
||||
if len(password) >= config.password.root_min_len:
|
||||
return True, msg
|
||||
return True
|
||||
else:
|
||||
upp = 0
|
||||
low = 0
|
||||
|
@ -67,22 +67,37 @@ def check_password(password, no_cracklib=False, dialog=False):
|
|||
|
||||
# Recherche de manque de caractères
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
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
|
||||
# la rigueur du test) ?
|
||||
password = cracklib.VeryFascistCheck(password)
|
||||
if dialog:
|
||||
msg = affich_tools.coul(msg, 'rouge', dialog=dialog)
|
||||
return True, msg
|
||||
except ValueError as e:
|
||||
msg += str(e).decode(config.in_encoding)
|
||||
|
||||
if dialog:
|
||||
msg = affich_tools.coul(msg, 'rouge', dialog=dialog)
|
||||
if not dialog:
|
||||
affich_tools.cprint(e.message, "rouge")
|
||||
else:
|
||||
msg += affich_tools.coul(str(e).decode(), "rouge", dialog=dialog)
|
||||
return False, msg
|
||||
else:
|
||||
if dialog:
|
||||
msg = affich_tools.coul(msg, 'rouge', dialog=dialog)
|
||||
return True, msg
|
||||
else:
|
||||
if dialog:
|
||||
msg = affich_tools.coul(msg, 'rouge', dialog=dialog)
|
||||
return False, msg
|
||||
|
||||
if dialog:
|
||||
msg = affich_tools.coul(msg, 'rouge', dialog=dialog)
|
||||
return False, msg
|
||||
|
||||
@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
|
||||
"""
|
||||
if login is None:
|
||||
login = current_user
|
||||
|
||||
if type(login) == str:
|
||||
login = login.decode(config.in_encoding)
|
||||
|
||||
if no_cracklib:
|
||||
if not lc_ldap.attributs.nounou in ldap.droits:
|
||||
no_cracklib = False
|
||||
|
||||
login = login.decode(encoding)
|
||||
login = lc_ldap.crans_utils.escape(login)
|
||||
query = ldap.search(u"(uid=%s)" % login, mode="w")
|
||||
|
||||
if not query:
|
||||
affich_tools.cprint('Utilisateur introuvable dans la base de données, modification de l\'utilisateur local.', "rouge")
|
||||
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.cancel()
|
||||
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
|
||||
From = 'roots@crans.org'
|
||||
|
@ -155,11 +155,11 @@ To: %s
|
|||
Subject: Tentative de changement de mot de passe !
|
||||
|
||||
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
|
||||
with mail_module.ServerConnection() as conn:
|
||||
conn.sendmail(From, To, mail)
|
||||
conn.sendmail(From, To , mail )
|
||||
sys.exit(1)
|
||||
|
||||
# On peut modifier le MDP
|
||||
|
@ -167,63 +167,54 @@ Tentative de changement du mot de passe de %s par %s.
|
|||
prenom = "Club"
|
||||
else:
|
||||
prenom = user['prenom'][0]
|
||||
affich_tools.cprint(
|
||||
"Changement du mot de passe de %s %s." % (
|
||||
prenom,
|
||||
user['nom'][0]
|
||||
),
|
||||
"vert",
|
||||
)
|
||||
affich_tools.cprint("Changement du mot de passe de %s %s." %
|
||||
(prenom, user['nom'][0]),
|
||||
"vert")
|
||||
|
||||
# Règles du jeu
|
||||
# (J'ai perdu)
|
||||
if verbose:
|
||||
affich_tools.cprint(
|
||||
u"""Règles :
|
||||
affich_tools.cprint(u"""Règles :
|
||||
Longueur standard : %s, root : %s,
|
||||
Minimums : 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,
|
||||
config.password.root_min_len,
|
||||
config.password.min_cif,
|
||||
config.password.min_low,
|
||||
config.password.min_upp,
|
||||
config.password.min_oth,
|
||||
config.password.cif_value,
|
||||
config.password.low_value,
|
||||
config.password.upp_value,
|
||||
config.password.oth_value,
|
||||
"Oui" * (not no_cracklib) + "Non" * (no_cracklib),
|
||||
),
|
||||
'jaune',
|
||||
)
|
||||
Cracklib : %s.""" % (
|
||||
config.password.min_len,
|
||||
config.password.root_min_len,
|
||||
config.password.min_cif,
|
||||
config.password.min_low,
|
||||
config.password.min_upp,
|
||||
config.password.min_oth,
|
||||
config.password.cif_value,
|
||||
config.password.low_value,
|
||||
config.password.upp_value,
|
||||
config.password.oth_value,
|
||||
"Oui" * (not no_cracklib) + "Non" * (no_cracklib)
|
||||
),
|
||||
'jaune')
|
||||
else:
|
||||
affich_tools.cprint(
|
||||
u"""Le nouveau mot de passe doit comporter au minimum %s caractères.
|
||||
affich_tools.cprint(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 doit contenir au moins %s chiffre(s), %s minuscule(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,
|
||||
config.password.min_cif,
|
||||
config.password.min_low,
|
||||
config.password.min_upp,
|
||||
config.password.min_oth
|
||||
),
|
||||
'jaune',
|
||||
)
|
||||
CTRL+D ou CTRL+C provoquent un abandon.""" %
|
||||
(
|
||||
config.password.min_len,
|
||||
config.password.min_cif,
|
||||
config.password.min_low,
|
||||
config.password.min_upp,
|
||||
config.password.min_oth
|
||||
), 'jaune')
|
||||
|
||||
try:
|
||||
while True:
|
||||
mdp = getpass.getpass("Nouveau mot de passe: ")
|
||||
(ret, msg) = check_password(mdp, no_cracklib)
|
||||
if ret:
|
||||
if check_password(mdp, no_cracklib)[0]:
|
||||
mdp2 = getpass.getpass("Retaper le mot de passe: ")
|
||||
if mdp != mdp2:
|
||||
affich_tools.cprint(u"Les deux mots de passe diffèrent.", "rouge")
|
||||
else:
|
||||
break
|
||||
else:
|
||||
affich_tools.cprint(msg, 'rouge')
|
||||
|
||||
except KeyboardInterrupt:
|
||||
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")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Recherche dans la base des adhérents",
|
||||
add_help=False,
|
||||
)
|
||||
parser.add_argument('-h',
|
||||
'--help',
|
||||
help="Affiche ce message et quitte.",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument('-n',
|
||||
'--no-cracklib',
|
||||
help="Permet de contourner les règles de choix du mot de passe" +
|
||||
"(réservé aux nounous).",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument('-v',
|
||||
'--verbose',
|
||||
help="Permet de contourner les règles de choix du mot de passe" +
|
||||
"(réservé aux nounous).",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument('login',
|
||||
type=str,
|
||||
nargs="?",
|
||||
help="L'utilisateur dont on veut changer le mot de passe.",
|
||||
)
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Recherche dans la base des adhérents",
|
||||
add_help=False)
|
||||
parser.add_argument('-h', '--help',
|
||||
help="Affiche ce message et quitte.",
|
||||
action="store_true")
|
||||
parser.add_argument('-n', '--no-cracklib',
|
||||
help="Permet de contourner les règles de choix du mot de passe" +
|
||||
"(réservé aux nounous).",
|
||||
action="store_true")
|
||||
parser.add_argument('-v', '--verbose',
|
||||
help="Permet de contourner les règles de choix du mot de passe" +
|
||||
"(réservé aux nounous).",
|
||||
action="store_true")
|
||||
parser.add_argument('login', type=str, nargs="?",
|
||||
help="L'utilisateur dont on veut changer le mot de passe.")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.help:
|
||||
parser.print_help()
|
||||
sys.exit(0)
|
||||
if args.no_cracklib:
|
||||
if not lc_ldap.attributs.nounou in ldap.droits:
|
||||
args.no_cracklib = False
|
||||
change_password(**vars(args))
|
||||
|
|
|
@ -10,28 +10,29 @@
|
|||
import os, sys
|
||||
|
||||
from gestion.affich_tools import prompt
|
||||
from gestion.ldap_crans import crans_ldap
|
||||
|
||||
from lc_ldap import shortcuts
|
||||
|
||||
ldap = shortcuts.lc_ldap_admin()
|
||||
db = crans_ldap()
|
||||
uid = os.getenv('SUDO_UID')
|
||||
if not uid :
|
||||
print "Impossible de déterminer l'utilisateur"
|
||||
sys.exit(1)
|
||||
|
||||
adh = ldap.search(u'uidNumber=%s' % uid,mode='w')
|
||||
|
||||
try:
|
||||
adh = adh[0]
|
||||
except IndexError:
|
||||
print 'Erreur fatale lors de la consultation de la base LDAP'
|
||||
sys.exit(3)
|
||||
s = db.search('uidNumber=%s' % os.getenv('SUDO_UID'),'w')
|
||||
|
||||
# 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'
|
||||
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 :')
|
||||
fd=open('/etc/shells')
|
||||
lines=fd.readlines()
|
||||
|
@ -44,9 +45,7 @@ if not shell in shells:
|
|||
print '\n'.join(shells)
|
||||
sys.exit(4)
|
||||
|
||||
with adh as ad:
|
||||
ad['loginShell']=shell
|
||||
ad.save()
|
||||
|
||||
adh.chsh(shell)
|
||||
adh.save()
|
||||
# A cause de nscd
|
||||
print "La modification sera prise en compte dans l'heure suivante."
|
||||
|
|
|
@ -4,4 +4,3 @@
|
|||
from config import *
|
||||
from encoding import *
|
||||
|
||||
import dns
|
||||
|
|
|
@ -9,35 +9,42 @@ import datetime
|
|||
# Fichier généré à partir de bcfg2
|
||||
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"
|
||||
##### Gestion des câblages
|
||||
# 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
|
||||
|
||||
# On récupère l'année scolaire à tout besoin
|
||||
__annee = time.localtime()[0]
|
||||
# Ne modifier que les dates !
|
||||
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
|
||||
gtf_debut_periode_transitoire = "%s0816000000+0200" % (__annee,)
|
||||
gtf_fin_periode_transitoire = "%s0930235959+0200" % (__annee,)
|
||||
debut_periode_transitoire = time.mktime(time.strptime("%s/08/16 00:00:00" % (ann_scol,), "%Y/%m/%d %H:%M:%S"))
|
||||
fin_periode_transitoire = time.mktime(time.strptime("%s/09/30 23:59:59" % (ann_scol,), "%Y/%m/%d %H:%M:%S"))
|
||||
|
||||
# Version timestampées timezone-naïves
|
||||
debut_periode_transitoire = time.mktime(time.strptime("%s/08/16 00:00:00" % (__annee,), "%Y/%m/%d %H:%M:%S"))
|
||||
fin_periode_transitoire = time.mktime(time.strptime("%s/09/30 23:59:59" % (__annee,), "%Y/%m/%d %H:%M:%S"))
|
||||
## Bloquage si carte d'étudiants manquante pour l'année en cours
|
||||
# /!\ Par sécurité, ces valeurs sont considérées comme False si
|
||||
# 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
|
||||
periode_transitoire = (debut_periode_transitoire <= time.time() <= fin_periode_transitoire)
|
||||
|
||||
ann_scol = __annee
|
||||
if time.time() <= debut_periode_transitoire:
|
||||
ann_scol -= 1
|
||||
#Sursis pour les inscription après le 1/11 pour fournir la carte étudiant
|
||||
sursis_carte=8*24*3600
|
||||
|
||||
# Gel des cableurs pas a jour de cotisation
|
||||
# Les droits ne sont pas retires mais il n'y a plus de sudo
|
||||
|
@ -55,210 +62,95 @@ quota_hard = 10000000
|
|||
fquota_soft = 0
|
||||
fquota_hard = 0
|
||||
# Shell
|
||||
login_shell = '/bin/zsh'
|
||||
club_login_shell = '/usr/bin/rssh'
|
||||
login_shell='/bin/zsh'
|
||||
club_login_shell='/usr/bin/rssh'
|
||||
# Longueur maximale d'un login
|
||||
maxlen_login = 25
|
||||
maxlen_login=25
|
||||
|
||||
shells_possibles = [
|
||||
u'/bin/csh',
|
||||
u'/bin/sh', # tout caca
|
||||
u'/bin/dash', # un bash light
|
||||
u'/usr/bin/rc',
|
||||
u'/usr/bin/ksh', # symlink vers zsh
|
||||
u'/bin/ksh', # symlink vers zsh
|
||||
u'/usr/bin/tcsh', # TENEX C Shell (csh++)
|
||||
u'/bin/tcsh', # TENEX C Shell (csh++)
|
||||
u'/bin/bash', # the Bourne-Again SHell
|
||||
u'/bin/zsh', # the Z shell
|
||||
u'/usr/bin/zsh', # the Z shell
|
||||
u'/usr/bin/screen',
|
||||
u'/bin/rbash', # Bash restreint
|
||||
u'/usr/bin/rssh', # restricted secure shell allowing only scp and/or sftp
|
||||
u'/usr/local/bin/badPassSh', # demande de changer de mot de passe
|
||||
u'/usr/bin/passwd', # idem
|
||||
u'/usr/local/bin/disconnect_shell', # déconnexion crans
|
||||
u'/usr/scripts/surveillance/disconnect_shell', # idem
|
||||
u'/usr/sbin/nologin', # This account is currently not available.
|
||||
u'/bin/false', # vraiement méchant
|
||||
u'/usr/bin/es', # n'exsite plus
|
||||
u'/usr/bin/esh', # n'existe plus
|
||||
u'', # le shell vide pour pouvoir les punis
|
||||
]
|
||||
|
||||
shells_gest_crans_order = [
|
||||
"zsh",
|
||||
"bash",
|
||||
"tcsh",
|
||||
"screen",
|
||||
"rbash",
|
||||
"rssh",
|
||||
"badPassSh",
|
||||
"disconnect_shell"
|
||||
shells_possibles = [u'/bin/csh',
|
||||
u'/bin/sh', # tout caca
|
||||
u'/bin/dash', # un bash light
|
||||
u'/usr/bin/rc',
|
||||
u'/usr/bin/ksh', # symlink vers zsh
|
||||
u'/bin/ksh', # symlink vers zsh
|
||||
u'/usr/bin/tcsh', # TENEX C Shell (csh++)
|
||||
u'/bin/tcsh', # TENEX C Shell (csh++)
|
||||
u'/bin/bash', # the Bourne-Again SHell
|
||||
u'/bin/zsh', # the Z shell
|
||||
u'/usr/bin/zsh', # the Z shell
|
||||
u'/usr/bin/screen',
|
||||
u'/bin/rbash', # Bash restreint
|
||||
u'/usr/bin/rssh', # restricted secure shell allowing only scp and/or sftp
|
||||
u'/usr/local/bin/badPassSh', # demande de changer de mot de passe
|
||||
u'/usr/bin/passwd', # idem
|
||||
u'/usr/local/bin/disconnect_shell', # déconnexion crans
|
||||
u'/usr/scripts/surveillance/disconnect_shell', # idem
|
||||
u'/usr/sbin/nologin', # This account is currently not available.
|
||||
u'/bin/false', # vraiement méchant
|
||||
u'/usr/bin/es', # n'exsite plus
|
||||
u'/usr/bin/esh', # n'existe plus
|
||||
u'', # le shell vide pour pouvoir les punis
|
||||
]
|
||||
|
||||
shells_gest_crans_order = ["zsh", "bash", "tcsh", "screen", "rbash", "rssh",
|
||||
"badPassSh", "disconnect_shell"]
|
||||
shells_gest_crans = {
|
||||
"zsh" : {
|
||||
"path" : "/bin/zsh",
|
||||
"desc" : "Le Z SHell, shell par defaut sur zamok"
|
||||
},
|
||||
"bash" : {
|
||||
"path" : "/bin/bash",
|
||||
"desc" : "Le Boune-Again SHell, shell par defaut de la plupart des linux"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"zsh": {"path":"/bin/zsh", "desc":"Le Z SHell, shell par defaut sur zamok"},
|
||||
"bash": {"path":"/bin/bash", "desc":"Le Boune-Again SHell, shell par defaut de la plupart des linux"},
|
||||
"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 ?
|
||||
droits_groupes = {
|
||||
'adm' : [
|
||||
u'Nounou',
|
||||
],
|
||||
'respbats' : [
|
||||
u'Imprimeur',
|
||||
u'Cableur',
|
||||
u'Nounou',
|
||||
],
|
||||
'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',
|
||||
],
|
||||
}
|
||||
droits_groupes = {'adm' : [u'Nounou'],
|
||||
'respbats' : [u'Imprimeur', u'Cableur', u'Nounou'],
|
||||
'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
|
||||
|
||||
modePaiement = [
|
||||
'liquide',
|
||||
'paypal',
|
||||
'solde',
|
||||
'cheque',
|
||||
'carte',
|
||||
'comnpay',
|
||||
'arbitraire',
|
||||
'note',
|
||||
]
|
||||
modePaiement = ['liquide', 'paypal', 'solde', 'cheque', 'carte']
|
||||
|
||||
####### Les ML
|
||||
# Le + devant un nom de ML indique une synchronisation
|
||||
# ML <-> fonction partielle : il n'y a pas d'effacement automatique
|
||||
# des abonnés si le droit est retiré
|
||||
droits_mailing_listes = {
|
||||
'roots' : [
|
||||
u'Nounou',
|
||||
u'Apprenti',
|
||||
],
|
||||
'mailman' : [
|
||||
u'Nounou',
|
||||
],
|
||||
'+nounou' : [
|
||||
u'Nounou',
|
||||
u'Apprenti',
|
||||
],
|
||||
'respbats' : [
|
||||
u'Cableur',
|
||||
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',
|
||||
],
|
||||
droits_mailing_listes = {'roots' : [ u'Nounou', u'Apprenti'],
|
||||
'mailman' : [ u'Nounou'],
|
||||
'+nounou' : [ u'Nounou', u'Apprenti'],
|
||||
'respbats' : [ u'Cableur', 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'],
|
||||
|
||||
# Correspondance partielle nécessaire... Des adresses non-crans sont inscrites à ces ML.
|
||||
'+dsi-crans' : [
|
||||
u'Nounou',
|
||||
u'Bureau',
|
||||
],
|
||||
'+crous-crans' : [
|
||||
u'Nounou',
|
||||
u'Bureau',
|
||||
],
|
||||
'+wrc' : [
|
||||
u'Webradio',
|
||||
],
|
||||
}
|
||||
'+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.
|
||||
'+dsi-crans' : [u'Nounou', u'Bureau'],
|
||||
'+crous-crans' : [u'Nounou', u'Bureau'],
|
||||
|
||||
'+wrc' : [u'Webradio'],
|
||||
}
|
||||
|
||||
#: Répertoire de stockage des objets détruits
|
||||
cimetiere = '/home/cimetiere'
|
||||
|
||||
#: Adresses mac utiles
|
||||
# Mac du routeur est la mac du routeur du crans (actuellement odlyd)
|
||||
# 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_komaz = 'a0:d3:c1:00:f4:04'
|
||||
mac_titanic = 'aa:73:65:63:6f:76'
|
||||
|
||||
#: Serveur principal de bcfg2
|
||||
|
@ -273,24 +165,24 @@ ISCSI_MAP_FILE = "/usr/scripts/var/iscsi_names_%s.py"
|
|||
# IANA_id correspond à l'entier attribué par l'IANA pour l'algorithm dans les champs DNS SSHFP
|
||||
# ssh_algo correspond a la première chaine de caractères donnant le nom de l'algorithme de chiffrement lorsque la clef ssh est dans le format openssh (algo key comment)
|
||||
sshfp_algo = {
|
||||
"rsa" : (1, "ssh-rsa"),
|
||||
"dsa" : (2, "ssh-dss"),
|
||||
"ecdsa-256" : (3, "ecdsa-sha2-nistp256"),
|
||||
"ecdsa-384" : (3, "ecdsa-sha2-nistp384"),
|
||||
"ecdsa-521" : (3, "ecdsa-sha2-nistp521"),
|
||||
"ecdsa" : (3, "ecdsa-sha2-nistp521"),
|
||||
}
|
||||
"rsa" : (1, "ssh-rsa"),
|
||||
"dsa" : (2, "ssh-dss"),
|
||||
"ecdsa-256" : (3, "ecdsa-sha2-nistp256"),
|
||||
"ecdsa-384" : (3, "ecdsa-sha2-nistp384"),
|
||||
"ecdsa-521" : (3, "ecdsa-sha2-nistp521"),
|
||||
"ecdsa" : (3, "ecdsa-sha2-nistp521"),
|
||||
}
|
||||
|
||||
sshfs_ralgo = {}
|
||||
for key, value in sshfp_algo.items():
|
||||
for key,value in sshfp_algo.items():
|
||||
sshfs_ralgo[value[1]] = (value[0], key)
|
||||
|
||||
sshfp_hash = {
|
||||
"sha1" : 1,
|
||||
"sha256" : 2,
|
||||
"sha1" : 1,
|
||||
"sha256" : 2,
|
||||
}
|
||||
|
||||
sshkey_max_age = int(9.869604401089358 * (365.25 * 24 * 3600))
|
||||
sshkey_max_age=2*(365.25*24*3600)
|
||||
|
||||
sshkey_size = {
|
||||
'rsa':4096,
|
||||
|
@ -325,90 +217,46 @@ plage_ens = '138.231.0.0/16'
|
|||
# clefs qui cassent la bijectivité, mais qui peuvent servir.
|
||||
# NETs est l'union des deux
|
||||
NETs_primaires = {
|
||||
'serveurs' : [
|
||||
'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.32/27',
|
||||
'138.231.148.64/26',
|
||||
'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'
|
||||
],
|
||||
}
|
||||
'serveurs' : ['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'],
|
||||
'bornes' : ['138.231.148.0/25'],
|
||||
'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'],
|
||||
'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 = {
|
||||
'all' : [
|
||||
'138.231.136.0/21',
|
||||
'138.231.144.0/21',
|
||||
],
|
||||
'wifi': [
|
||||
'138.231.144.0/21',
|
||||
],
|
||||
'fil' : [
|
||||
'138.231.136.0/21',
|
||||
],
|
||||
}
|
||||
'all' : ['138.231.136.0/21', '138.231.144.0/21'],
|
||||
'wifi': ['138.231.144.0/21'],
|
||||
'fil' : ['138.231.136.0/21'],
|
||||
}
|
||||
|
||||
NETs = {}
|
||||
NETs.update(NETs_primaires)
|
||||
NETs.update(NETs_secondaires)
|
||||
|
||||
NETs_regexp = {
|
||||
'all' : r'^138\.231\.1(3[6789]|4[0123456789]|5[01])\.\d+$'
|
||||
}
|
||||
NETs_regexp = { 'all' : '^138\.231\.1(3[6789]|4[0123456789]|5[01])\.\d+$' }
|
||||
|
||||
# Classes de rid
|
||||
# Merci d'essayer de les faire correspondre avec les réseaux
|
||||
# ci-dessus...
|
||||
# De même que pout NETs, primaires c'est pour la bijectivité, et secondaires
|
||||
# pour les trucs pratiques
|
||||
# https://wiki.crans.org/CransTechnique/PlanAdressage#Machines
|
||||
rid_primaires = {
|
||||
# Rid pour les serveurs
|
||||
'serveurs' : [(0, 255),],
|
||||
# Rid pour les machines fixes
|
||||
'adherents' : [(256, 2047),],
|
||||
# Rid pour les machines wifi
|
||||
'wifi-adh' : [(2048, 3071), (3104, 4095),],
|
||||
'wifi-adh' : [(2048, 3071), (3200, 4095),],
|
||||
# Rid pour les bornes
|
||||
'bornes' : [(3072, 3103), (34816, 35071),],
|
||||
'bornes' : [(3072, 3199),],
|
||||
# Rid pour machines spéciales
|
||||
'special' : [(4096, 6143),],
|
||||
# Rid pour les serveurs v6-only
|
||||
|
@ -417,8 +265,8 @@ rid_primaires = {
|
|||
'adherents-v6' : [(16384, 24575),],
|
||||
# Rid pour les wifi v6-only
|
||||
'wifi-adh-v6' : [(24576, 32767),],
|
||||
# Bornes-v6
|
||||
'bornes-v6' : [(34816, 35071),],
|
||||
# Bornes-v6 ?
|
||||
'bornes-v6' : [(32768, 33791),],
|
||||
# Rid pour les machines du vlan adm
|
||||
'adm-v6' : [(49152, 51199),],
|
||||
# Rid pour les machines du vlan adm
|
||||
|
@ -429,13 +277,13 @@ rid_primaires = {
|
|||
'personnel-ens' : [(55296, 55551),],
|
||||
# Un unique rid pour les machines multicast
|
||||
'multicast' : [(65535, 65535),],
|
||||
}
|
||||
}
|
||||
|
||||
rid_secondaires = {
|
||||
# Rid pour les machines filaire ipv4
|
||||
'fil' : [(0, 2047),],
|
||||
'wifi' : [(2048, 4095), (34816, 35071),],
|
||||
}
|
||||
'wifi' : [(2048, 4095),],
|
||||
}
|
||||
|
||||
rid = {}
|
||||
rid.update(rid_primaires)
|
||||
|
@ -461,59 +309,24 @@ ipv6_machines_speciales = {
|
|||
}
|
||||
|
||||
# Les préfixes ipv6 publics
|
||||
prefix = {
|
||||
'subnet' : [
|
||||
'2a01:240:fe3d::/48',
|
||||
],
|
||||
'serveurs' : [
|
||||
'2a01:240:fe3d:4::/64',
|
||||
],
|
||||
'adherents' : [
|
||||
'2a01:240:fe3d:4::/64',
|
||||
],
|
||||
'fil' : [
|
||||
'2a01:240:fe3d:4::/64',
|
||||
],
|
||||
'adm' : [
|
||||
'2a01:240:fe3d:c804::/64',
|
||||
],
|
||||
'adm-v6' : [
|
||||
'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',
|
||||
],
|
||||
}
|
||||
prefix = { 'subnet' : [ '2a01:240:fe3d::/48' ],
|
||||
'serveurs' : [ '2a01:240:fe3d:4::/64' ],
|
||||
'adherents' : [ '2a01:240:fe3d:4::/64' ],
|
||||
'fil' : [ '2a01:240:fe3d:4::/64' ],
|
||||
'adm' : [ '2a01:240:fe3d:c804::/64' ],
|
||||
'adm-v6' : [ '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)
|
||||
int_prefix = {
|
||||
|
@ -522,12 +335,10 @@ int_prefix = {
|
|||
}
|
||||
|
||||
# Domaines dans lesquels les machines sont placées suivant leur type
|
||||
domains = {
|
||||
'machineFixe': 'crans.org',
|
||||
'machineCrans': 'crans.org',
|
||||
'machineWifi': 'wifi.crans.org',
|
||||
'borneWifi': 'wifi.crans.org',
|
||||
}
|
||||
domains = { 'machineFixe': 'crans.org',
|
||||
'machineCrans': 'crans.org',
|
||||
'machineWifi': 'wifi.crans.org',
|
||||
'borneWifi': 'wifi.crans.org' }
|
||||
|
||||
# VLans
|
||||
vlans = {
|
||||
|
@ -547,10 +358,10 @@ vlans = {
|
|||
'v6only': 6,
|
||||
# Vlan isolement
|
||||
'isolement' : 9,
|
||||
# Vlan de tests de chiffrement DSI
|
||||
'chiffrement': 11,
|
||||
# VLan des appartements de l'ENS
|
||||
'appts': 21,
|
||||
# Vlan federez-wifi
|
||||
'federez': 22,
|
||||
# Vlan evenementiel (install-party, etc)
|
||||
'event': 10,
|
||||
# Vlan zone routeur ens (zrt)
|
||||
|
@ -559,100 +370,68 @@ vlans = {
|
|||
'iscsi': 42,
|
||||
# freebox (pour faire descendre la connexion au 0B)
|
||||
'freebox': 8,
|
||||
}
|
||||
|
||||
filter_policy = {
|
||||
'komaz' : {
|
||||
'policy_input' : 'ACCEPT',
|
||||
'policy_forward' : 'ACCEPT',
|
||||
'policy_output' : 'ACCEPT',
|
||||
},
|
||||
'zamok' : {
|
||||
'policy_input' : 'ACCEPT',
|
||||
'policy_forward' : 'DROP',
|
||||
'policy_output' : 'ACCEPT',
|
||||
},
|
||||
'default' : {
|
||||
'policy_input' : 'ACCEPT',
|
||||
'policy_forward' : 'ACCEPT',
|
||||
'policy_output' : 'ACCEPT',
|
||||
}
|
||||
}
|
||||
|
||||
filter_policy = { 'komaz' : { 'policy_input' : 'ACCEPT',
|
||||
'policy_forward' : 'ACCEPT',
|
||||
'policy_output' : 'ACCEPT'
|
||||
},
|
||||
'zamok' : { 'policy_input' : 'ACCEPT',
|
||||
'policy_forward' : 'DROP',
|
||||
'policy_output' : 'ACCEPT'
|
||||
},
|
||||
'default' : { 'policy_input' : 'ACCEPT',
|
||||
'policy_forward' : 'ACCEPT',
|
||||
'policy_output' : 'ACCEPT'
|
||||
}
|
||||
}
|
||||
|
||||
# Cf RFC 4890
|
||||
authorized_icmpv6 = [
|
||||
'echo-request',
|
||||
'echo-reply',
|
||||
'destination-unreachable',
|
||||
'packet-too-big',
|
||||
'ttl-zero-during-transit',
|
||||
'parameter-problem',
|
||||
]
|
||||
authorized_icmpv6 = ['echo-request', 'echo-reply', 'destination-unreachable',
|
||||
'packet-too-big', 'ttl-zero-during-transit', 'parameter-problem']
|
||||
|
||||
output_file = {
|
||||
4 : '/tmp/ipt_rules',
|
||||
6 : '/tmp/ip6t_rules',
|
||||
}
|
||||
output_file = { 4 : '/tmp/ipt_rules',
|
||||
6 : '/tmp/ip6t_rules'
|
||||
}
|
||||
|
||||
file_pickle = {
|
||||
4 : '/tmp/ipt_pickle',
|
||||
6 : '/tmp/ip6t_pickle',
|
||||
}
|
||||
file_pickle = { 4 : '/tmp/ipt_pickle',
|
||||
6 : '/tmp/ip6t_pickle'
|
||||
}
|
||||
|
||||
##################################################################################
|
||||
#: Items de la blackliste
|
||||
blacklist_items = {
|
||||
u'bloq': u'Blocage total de tous les services',
|
||||
u'paiement': u'Paiement manquant cette année',
|
||||
u'virus': u'Passage en VLAN isolement',
|
||||
u'upload': u"Bridage du débit montant vers l'extérieur",
|
||||
u'autodisc_upload': u'Autodisconnect pour upload',
|
||||
u'ipv6_ra': u'Isolement pour RA',
|
||||
u'mail_invalide': u'Blocage pour mail invalide',
|
||||
u'warez' : u"Présence de contenu violant de droit d'auteur sur zamok",
|
||||
}
|
||||
blacklist_items = { 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'virus': u'Passage en VLAN isolement',
|
||||
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_p2p': u'Autodisconnect pour P2P',
|
||||
u'ipv6_ra': u'Isolement pour RA',
|
||||
u'mail_invalide': u'Blocage pour mail invalide',
|
||||
u'warez' : u"Présence de contenu violant de droit d'auteur sur zamok",
|
||||
}
|
||||
|
||||
#: Blacklistes entrainant une déconnexion complète
|
||||
blacklist_sanctions = [
|
||||
'warez',
|
||||
'virus',
|
||||
'bloq',
|
||||
blacklist_sanctions = ['warez', 'p2p', 'autodisc_p2p','autodisc_virus','virus', 'bloq',
|
||||
'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)
|
||||
blacklist_sanctions_soft = [
|
||||
'ipv6_ra',
|
||||
'mail_invalide',
|
||||
'virus',
|
||||
'warez',
|
||||
'bloq',
|
||||
'chambre_invalide',
|
||||
]
|
||||
|
||||
blacklist_sanctions_soft = ['autodisc_virus','ipv6_ra','mail_invalide','virus',
|
||||
'warez', 'p2p', 'autodisc_p2p', 'bloq','carte_etudiant','chambre_invalide']
|
||||
#: Blacklistes entrainant un bridage de la connexion pour upload
|
||||
blacklist_bridage_upload = ['autodisc_upload', 'upload']
|
||||
|
||||
##################################################################################
|
||||
|
||||
adm_users = [
|
||||
'root',
|
||||
'identd',
|
||||
'daemon',
|
||||
'postfix',
|
||||
'freerad',
|
||||
'amavis',
|
||||
'nut',
|
||||
'respbats',
|
||||
'list',
|
||||
'sqlgrey',
|
||||
'ntpd',
|
||||
'lp',
|
||||
]
|
||||
adm_users = [ 'root', 'identd', 'daemon', 'postfix', 'freerad', 'amavis',
|
||||
'nut', 'respbats', 'list', 'sqlgrey', 'ntpd', 'lp' ]
|
||||
|
||||
open_ports = {
|
||||
'tcp' : '22',
|
||||
}
|
||||
open_ports = { 'tcp' : '22' }
|
||||
|
||||
# Debit max sur le vlan de la connexion gratuite
|
||||
debit_max_radin = 1000000
|
||||
|
@ -662,90 +441,13 @@ debit_max_gratuit = 1000000
|
|||
## Vlan accueil et isolement ##
|
||||
###############################
|
||||
accueil_route = {
|
||||
'138.231.136.1' : {
|
||||
'tcp' : [
|
||||
'80',
|
||||
'443',
|
||||
'22'
|
||||
],
|
||||
'hosts' : [
|
||||
'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',
|
||||
],
|
||||
},
|
||||
'138.231.136.1':{'tcp':['80','443', '22'],'hosts':['intranet.crans.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']},
|
||||
'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']
|
||||
|
||||
# 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']
|
||||
|
|
|
@ -10,17 +10,11 @@
|
|||
# Délai minimal avant de pouvoir réadhérer.
|
||||
# Ne tient pas compte de la période transitoire, qui est un confort
|
||||
# pour l'administration.
|
||||
delai_readh_jour = 32
|
||||
delai_readh_jour = 15
|
||||
delai_readh = delai_readh_jour * 86400
|
||||
|
||||
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 à
|
||||
# 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
|
||||
|
|
|
@ -3,10 +3,8 @@
|
|||
|
||||
""" Variables de configuration pour la gestion du DNS """
|
||||
|
||||
import os
|
||||
|
||||
# import des variables génériques
|
||||
import __init__ as config
|
||||
import config
|
||||
|
||||
#: ariane et ariane2 pour la zone parente
|
||||
parents = [
|
||||
|
@ -30,125 +28,38 @@ slaves_tv = slaves
|
|||
zone_tv = 'tv.crans.org'
|
||||
|
||||
#: DNS en connexion de secours
|
||||
secours_relay = '10.231.136.14';
|
||||
secours_relay='10.231.136.14';
|
||||
|
||||
#: Serveurs autoritaires pour les zones crans, le master doit être le premier
|
||||
DNSs = [
|
||||
'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,
|
||||
},
|
||||
}
|
||||
DNSs = ['sable.crans.org', 'freebox.crans.org', 'soyouz.crans.org']
|
||||
|
||||
#: Résolution DNS directe, liste de toutes les zones crans hors reverse
|
||||
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',
|
||||
]
|
||||
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' ]
|
||||
#: Les zones apparaissant dans des objets lc_ldap
|
||||
zones_ldap = [
|
||||
'crans.org',
|
||||
'crans.ens-cachan.fr',
|
||||
'wifi.crans.org',
|
||||
'clubs.ens-cachan.fr',
|
||||
'adm.crans.org',
|
||||
'tv.crans.org',
|
||||
]
|
||||
zones_ldap = [ '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_dnssec = [
|
||||
'crans.org',
|
||||
'wifi.crans.org',
|
||||
'adm.crans.org',
|
||||
'tv.crans.org',
|
||||
'crans.eu',
|
||||
]
|
||||
zones_dnssec = ['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
|
||||
zone_alias = {
|
||||
'crans.org' : [
|
||||
'crans.eu',
|
||||
],
|
||||
'crans.org' : ['crans.eu'],
|
||||
}
|
||||
|
||||
#: Résolution inverse v4
|
||||
zones_reverse = config.NETs["all"] + config.NETs["adm"] + config.NETs["personnel-ens"] + config.NETs['multicast']
|
||||
#: Résolution inverse v6
|
||||
zones_reverse_v6 = config.prefix['fil'] + config.prefix['wifi'] + config.prefix['adm'] + config.prefix['personnel-ens'] # à modifier aussi dans bind.py
|
||||
zones_reverse_v6 = config.prefix['fil'] + config.prefix['wifi'] + config.prefix ['adm'] + config.prefix['personnel-ens'] # à modifier aussi dans bind.py
|
||||
|
||||
#: Serveurs DNS récursifs :
|
||||
recursiv = {
|
||||
'fil' : [
|
||||
'138.231.136.98',
|
||||
'138.231.136.152',
|
||||
],
|
||||
'wifi' : [
|
||||
'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',
|
||||
],
|
||||
'fil' : ['138.231.136.98', '138.231.136.152'],
|
||||
'wifi' : ['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'],
|
||||
}
|
||||
|
||||
#: 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
|
||||
menteur_clients = [
|
||||
"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"
|
||||
menteur_clients = [ "138.231.136.210", "138.231.136.10" ] + config.prefix['evenementiel']
|
||||
|
|
|
@ -4,4 +4,3 @@ import sys
|
|||
|
||||
in_encoding = getattr(sys.stdin, 'encoding', None) or "UTF-8"
|
||||
out_encoding = getattr(sys.stdout, 'encoding', None) or "UTF-8"
|
||||
ldap_encoding = "UTF-8"
|
||||
|
|
|
@ -1,67 +1,10 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- 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
|
||||
ITEMS = {
|
||||
'CABLE': {
|
||||
'designation': u'Cable Ethernet 5m',
|
||||
'pu': 3.,
|
||||
},
|
||||
'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',
|
||||
items = {
|
||||
'CABLE' : {'designation': u'Cable Ethernet 5m', 'pu': 3, 'imprimeur': False},
|
||||
'ADAPTATEUR' : {'designation': u'Adaptateur Ethernet/USB', 'pu': 17, 'imprimeur': False},
|
||||
'RELIURE': {'designation': u'Reliure plastique', 'pu': 0.12, 'imprimeur': False},
|
||||
'SOLDE':{'designation': u'Rechargement du solde', 'pu':'*', 'imprimeur': False},
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ dev = {
|
|||
'wifi' : 'crans.3',
|
||||
'fil' : 'crans',
|
||||
'app' : 'crans.21',
|
||||
'federez' : 'crans.22',
|
||||
'adm' : 'crans.2',
|
||||
'tun-soyouz' : 'tun-soyouz'
|
||||
},
|
||||
|
@ -53,20 +52,12 @@ mask = [24]
|
|||
now=datetime.datetime.now()
|
||||
if now.hour >= 6 and now.hour < 19 and now.weekday() < 5 and not is_ferie():
|
||||
#: Débit maximal autorisé
|
||||
debit_max = { 'total' : 250,
|
||||
'out' : 250,
|
||||
'wifi' : 100,
|
||||
'fil' : 150 }
|
||||
# mbits per second en connexion de jour
|
||||
debit_max = 150 # mbits per second en connexion de jour
|
||||
#: Est-ce qu'on est en connexion de jour ou de nuit/week-end ?
|
||||
debit_jour = True
|
||||
else:
|
||||
#: Débit maximal autorisé
|
||||
debit_max = { 'total' : 600,
|
||||
'out' : 600,
|
||||
'wifi' : 150,
|
||||
'fil' : 450 }
|
||||
# mbits per second en conn de nuit et du week-end
|
||||
debit_max = 500 # 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 ?
|
||||
debit_jour = False
|
||||
|
||||
|
@ -76,12 +67,9 @@ bl_upload_debit_max = 60 #kbytes per second
|
|||
# Débit pour upload des gens en appartement ens
|
||||
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
|
||||
# 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
|
||||
reseaux_non_routables = [ '10.0.0.0/8', '172.16.0.0/12','198.18.0.0/15',
|
||||
|
|
32
gestion/config/mails/upload.py
Normal file
32
gestion/config/mails/upload.py
Normal 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"""
|
|
@ -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
1
gestion/config/services.py
Symbolic link
|
@ -0,0 +1 @@
|
|||
/etc/crans/services.py
|
|
@ -1 +0,0 @@
|
|||
/etc/crans/services.py
|
|
@ -1,10 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
PRELOAD_MIBS = (
|
||||
"STATISTICS-MIB",
|
||||
"SNMPv2-SMI",
|
||||
"SNMPv2-MIB",
|
||||
"IF-MIB",
|
||||
"CONFIG-MIB",
|
||||
)
|
|
@ -5,9 +5,8 @@
|
|||
# License : GPLv3
|
||||
|
||||
import itertools
|
||||
import os
|
||||
|
||||
debug = (int(os.environ.get('DBG_TRIGGER', 0)) == 1) or True
|
||||
debug = True
|
||||
log_level = "info"
|
||||
|
||||
# Serveur maître
|
||||
|
@ -16,30 +15,24 @@ user = "trigger"
|
|||
port = 5671
|
||||
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
|
||||
# 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
|
||||
services = {
|
||||
'civet' : ["event", "ack"],
|
||||
'civet' : ["event"],
|
||||
'dhcp' : ["dhcp"],
|
||||
'dyson' : ["autostatus"],
|
||||
'isc' : ["dhcp"],
|
||||
'odlyd' : ["firewall", "secours"],
|
||||
'owl' : ["users"],
|
||||
'redisdead' : ["mailman", "modif_ldap", "solde", "users", "secours"],
|
||||
'komaz' : ["firewall", "secours"],
|
||||
'owl' : ["userdel"],
|
||||
'redisdead' : ["mailman", "modif_ldap", "solde", "userdel", "secours"],
|
||||
'sable' : ["dns"],
|
||||
'titanic' : ["secours"],
|
||||
'zamok' : ["users"],
|
||||
'zbee' : ["users"],
|
||||
'zamok' : ["userdel"],
|
||||
'zbee' : ["useradd", "userdel"],
|
||||
}
|
||||
|
||||
# XXX - Uncomment this when in prod
|
||||
#all_services = set([service for service in itertools.chain(*services.values())])
|
||||
|
||||
all_services = ['dhcp', 'firewall', 'secours']
|
||||
all_services = ['dhcp', 'firewall']
|
||||
|
|
|
@ -3,22 +3,10 @@
|
|||
|
||||
""" Définitions des variables pour le contrôle d'upload. """
|
||||
|
||||
#: Intervalle en heures pour le comptage
|
||||
interval = 24
|
||||
|
||||
#: liste des exemptions générales
|
||||
exempt = [ ['138.231.136.0/21', '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
|
||||
soft = 1024 # Mio/24h glissantes
|
||||
|
||||
|
@ -28,6 +16,11 @@ hard = 8192 # Mio/24h glissantes
|
|||
#: max déconnexions
|
||||
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
|
||||
expediteur = "disconnect@crans.org"
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ if '/usr/scripts' not in sys.path:
|
|||
from pythondialog import Dialog as PythonDialog
|
||||
from pythondialog import DialogTerminatedBySignal, PythonDialogErrorBeforeExecInChildProcess
|
||||
from pythondialog import error as DialogError
|
||||
from gestion import affichage
|
||||
from gestion.affich_tools import get_screen_size, coul
|
||||
|
||||
debug_enable = False
|
||||
debugf = None
|
||||
|
@ -203,7 +203,7 @@ class Dialog(object):
|
|||
setattr(self, attr, ret)
|
||||
return ret
|
||||
|
||||
def __init__(self, debug_enable=False, dialogrc=False):
|
||||
def __init__(self, debug_enable=False):
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
|
||||
self.debug_enable = debug_enable
|
||||
|
@ -211,7 +211,6 @@ class Dialog(object):
|
|||
# On met un timeout à 10min d'innactivité sur dialog
|
||||
self.timeout = 600
|
||||
self.error_to_raise = (Continue, DialogError, ldap.SERVER_DOWN)
|
||||
self.dialogrc = dialogrc
|
||||
|
||||
_dialog = None
|
||||
@property
|
||||
|
@ -219,9 +218,7 @@ class Dialog(object):
|
|||
"""
|
||||
Renvois l'objet dialog.
|
||||
"""
|
||||
if self.dialogrc:
|
||||
self._dialog = PythonDialog(DIALOGRC=self.dialogrc)
|
||||
else:
|
||||
if self._dialog is None:
|
||||
self._dialog = PythonDialog()
|
||||
self.dialog_last_access = time.time()
|
||||
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
|
||||
"""
|
||||
(cols, lines) = affichage.getTerminalSize()
|
||||
(lines, cols) = get_screen_size()
|
||||
print "\033[48;5;17m"
|
||||
print " "*(lines * cols)
|
||||
cols = int(min(cols/2, 65))
|
||||
|
|
|
@ -9,8 +9,6 @@ Licence : GPLv3
|
|||
import sys
|
||||
import time
|
||||
import datetime
|
||||
import subprocess
|
||||
import pytz
|
||||
import dateutil.relativedelta
|
||||
if '/usr/scripts' not in sys.path:
|
||||
sys.path.append('/usr/scripts')
|
||||
|
@ -20,7 +18,6 @@ import config.cotisation
|
|||
import lc_ldap.objets as objets
|
||||
import lc_ldap.attributs as attributs
|
||||
from lc_ldap.attributs import UniquenessError
|
||||
from lc_ldap import crans_utils
|
||||
|
||||
import proprio
|
||||
from CPS import TailCall, tailcaller, Continue
|
||||
|
@ -48,20 +45,20 @@ class Dialog(proprio.Dialog):
|
|||
'GPGFingerprint' : [a.nounou, a.soi],
|
||||
'Remarques' : [a.cableur, a.nounou],
|
||||
'Droits':[a.nounou, a.bureau],
|
||||
'Blackliste':[a.bureau, a.nounou],
|
||||
'Blackliste':[a.cableur, a.nounou],
|
||||
'Vente':[a.cableur, a.nounou],
|
||||
'Supprimer':[a.nounou, a.bureau],
|
||||
}
|
||||
menu = {
|
||||
'Administratif' : {'text' : "Adhésion, chartes", "callback":self.adherent_administratif},
|
||||
'Personnel' : {'text' : "Nom, prénom, téléphone, et mail de contact", 'callback':self.adherent_personnel},
|
||||
'Administratif' : {'text' : "Adhésion, carte étudiant, chartes", "callback":self.adherent_administratif},
|
||||
'Personnel' : {'text' : "Nom, prénom, téléphone... (ajouter l'age ?)", 'callback':self.adherent_personnel},
|
||||
'Études' : {'text' : "Étude en cours", "callback":self.adherent_etudes},
|
||||
'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"},
|
||||
'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},
|
||||
'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},
|
||||
'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])]
|
||||
)
|
||||
|
||||
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):
|
||||
"""Menu de gestion du compte crans d'un proprio"""
|
||||
|
||||
|
@ -126,13 +119,17 @@ class Dialog(proprio.Dialog):
|
|||
"Adhésion": [a.cableur, a.nounou],
|
||||
'Connexion': [a.cableur, a.nounou],
|
||||
"Charte MA" : [a.nounou, a.bureau],
|
||||
"Carte Étudiant" : [a.nounou, a.cableur, a.tresorier],
|
||||
}
|
||||
menu = {
|
||||
"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},
|
||||
"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},
|
||||
}
|
||||
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")
|
||||
def box(default_item=None):
|
||||
return self.dialog.menu(
|
||||
|
@ -220,7 +217,8 @@ class Dialog(proprio.Dialog):
|
|||
|
||||
# Boite si on ne peux pas réahdérer
|
||||
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,
|
||||
height=0,
|
||||
timeout=self.timeout,
|
||||
|
@ -228,8 +226,9 @@ class Dialog(proprio.Dialog):
|
|||
|
||||
# Boite de confirmation à l'ahésion
|
||||
def box_adherer(end=None):
|
||||
if end != crans_utils.localized_datetime():
|
||||
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]))
|
||||
if end:
|
||||
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:
|
||||
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
|
||||
|
@ -243,8 +242,9 @@ class Dialog(proprio.Dialog):
|
|||
|
||||
# Génération de la facture pour adhésion
|
||||
def paiement(tag_paiement, adherent, finadhesion, comment, facture, cancel_cont, cont):
|
||||
now = crans_utils.localized_datetime()
|
||||
new_finadhesion = max(finadhesion, now).replace(year=max(finadhesion, now).year + 1)
|
||||
now = time.time()
|
||||
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
|
||||
if facture:
|
||||
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['info'] = unicode(comment, 'utf-8')
|
||||
facture['article'].append(config.cotisation.dico_adh)
|
||||
facture["finAdhesion"] = new_finadhesion
|
||||
facture["debutAdhesion"] = new_debutadhesion
|
||||
facture["finAdhesion"] = unicode(new_finadhesion)
|
||||
facture["debutAdhesion"] = unicode(new_debutadhesion)
|
||||
# On peut retarder le credit pour ajouter des contribution pour la connexion internet à la facture
|
||||
if crediter:
|
||||
if self.confirm_item(item=facture,
|
||||
|
@ -280,13 +280,9 @@ class Dialog(proprio.Dialog):
|
|||
raise Continue(cont(adherent=adherent))
|
||||
|
||||
|
||||
now = crans_utils.localized_datetime()
|
||||
try:
|
||||
finadhesion = adherent.fin_adhesion().value
|
||||
except AttributeError:
|
||||
finadhesion = now
|
||||
finadhesion = adherent.fin_adhesion()
|
||||
# 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)
|
||||
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
|
||||
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(
|
||||
"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,
|
||||
height=0,
|
||||
menu_height=0,
|
||||
|
@ -357,16 +353,17 @@ class Dialog(proprio.Dialog):
|
|||
|
||||
# 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):
|
||||
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)
|
||||
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:
|
||||
t_end_adh = finadhesion.value
|
||||
t_end_conn = finconnexion
|
||||
if (new_finconnexion - finadhesion.value).days > 30:
|
||||
if new_finconnexion > finadhesion:
|
||||
t_end_adh = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(finadhesion))
|
||||
t_end_conn = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(new_finconnexion))
|
||||
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))
|
||||
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" \
|
||||
|
@ -376,8 +373,8 @@ class Dialog(proprio.Dialog):
|
|||
if facture:
|
||||
with self.conn.search(dn=facture.dn, scope=0, mode='rw')[0] as facture:
|
||||
if mois:
|
||||
facture["finConnexion"] = new_finconnexion
|
||||
facture["debutConnexion"] = new_debutconnexion
|
||||
facture["finConnexion"] = unicode(new_finconnexion)
|
||||
facture["debutConnexion"] = unicode(new_debutconnexion)
|
||||
facture["article"].append(config.cotisation.dico_cotis(mois))
|
||||
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]),
|
||||
|
@ -400,8 +397,8 @@ class Dialog(proprio.Dialog):
|
|||
facture['modePaiement'] = unicode(tag_paiment, 'utf-8')
|
||||
facture['article'].append(config.cotisation.dico_cotis(mois))
|
||||
facture['info'] = unicode(comment, 'utf-8')
|
||||
facture["finConnexion"] = new_finconnexion
|
||||
facture["debutConnexion"] = new_debutconnexion
|
||||
facture["finConnexion"] = unicode(new_finconnexion)
|
||||
facture["debutConnexion"] = unicode(new_debutconnexion)
|
||||
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),
|
||||
title=u"Validation du paiement",
|
||||
|
@ -411,7 +408,7 @@ class Dialog(proprio.Dialog):
|
|||
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):
|
||||
raise Continue(cancel_cont)
|
||||
raise Continue(cont)
|
||||
raise Continue(cont(adherent=adherent))
|
||||
|
||||
def todo_mois(tag, self_cont):
|
||||
if tag == 'An':
|
||||
|
@ -433,18 +430,16 @@ class Dialog(proprio.Dialog):
|
|||
finconnexion = adherent.fin_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:
|
||||
t_end_adh = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(finadhesion))
|
||||
# Si l'adhésion est déjà fini
|
||||
if finadhesion <= crans_utils.localized_datetime():
|
||||
if finadhesion == datetime.datetime.fromtimestamp(0, tz=pytz.utc):
|
||||
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)
|
||||
if finadhesion <= time.time():
|
||||
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)
|
||||
# Sinon si elle fini avant la fin de la connexion courante
|
||||
elif finadhesion < finconnexion:
|
||||
t_end_conn = 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)
|
||||
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" % (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
|
||||
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 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):
|
||||
a = attributs
|
||||
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:
|
||||
for (key, values) in attrs.items():
|
||||
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.history_gen()
|
||||
adherent.save()
|
||||
|
@ -593,13 +649,6 @@ class Dialog(proprio.Dialog):
|
|||
if self.confirm_item(adherent, title="Créer l'adhérent suivant ?"):
|
||||
adherent.validate_changes()
|
||||
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:
|
||||
adherent = None
|
||||
return adherent
|
||||
|
@ -863,11 +912,13 @@ class Dialog(proprio.Dialog):
|
|||
"""Crée un adhérent et potentiellement son compte crans avec lui"""
|
||||
def mycont(adherent=None, **kwargs):
|
||||
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)
|
||||
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)
|
||||
carte_cont(cancel_cont=etude_cont)
|
||||
# Comme on crée une facture, pas de retour possible
|
||||
conn_cont(cancel_cont=conn_cont)
|
||||
raise Continue(etude_cont)
|
||||
|
|
|
@ -13,7 +13,7 @@ import traceback
|
|||
if '/usr/scripts' not in sys.path:
|
||||
sys.path.append('/usr/scripts')
|
||||
|
||||
from gestion import affichage
|
||||
from gestion.affich_tools import coul
|
||||
import gestion.config as config
|
||||
|
||||
import lc_ldap.objets as objets
|
||||
|
@ -37,14 +37,10 @@ class Dialog(lc.Dialog):
|
|||
index = 0
|
||||
for bl in obj['blacklist']:
|
||||
choices.append(
|
||||
(
|
||||
str(index),
|
||||
affichage.style(
|
||||
"%s [%s]" % (bl['type'], bl['comm']),
|
||||
'rouge' if bl['actif'] else None,
|
||||
dialog=True
|
||||
)
|
||||
)
|
||||
(str(index),
|
||||
coul("%s [%s]" % (bl['type'], bl['comm']), 'rouge' if bl['actif'] else None,
|
||||
dialog=True)
|
||||
)
|
||||
)
|
||||
index+=1
|
||||
return self.dialog.menu(
|
||||
|
@ -149,7 +145,7 @@ class Dialog(lc.Dialog):
|
|||
fin_tuple = self.get_timestamp(title=title, text="Choisir la date de fin :",
|
||||
cont=self_cont(bl=bl, tag=tag, bl_type=bl_type,
|
||||
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:
|
||||
fin = '-'
|
||||
bl['debut']=debut
|
||||
|
|
|
@ -6,18 +6,16 @@ Copyright (C) Valentin Samir
|
|||
Licence : GPLv3
|
||||
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import ldap
|
||||
import traceback
|
||||
import locale
|
||||
if '/usr/scripts' not in sys.path:
|
||||
sys.path.append('/usr/scripts')
|
||||
from pythondialog import Dialog
|
||||
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.objets as objets
|
||||
|
@ -29,17 +27,12 @@ from CPS import TailCall, tailcaller, Continue, TailCaller
|
|||
|
||||
class Dialog(CPS.Dialog):
|
||||
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
|
||||
printing.template(dialog=True)
|
||||
self.ldap_test = ldap_test
|
||||
if custom_user:
|
||||
custom_user = custom_user.decode(locale.getdefaultlocale()[1] or "ascii")
|
||||
self.custom_user = custom_user
|
||||
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):
|
||||
"""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
|
||||
del items[:] # On vide la liste pour la modifier en place
|
||||
items_id = {}
|
||||
(col, line) = affichage.getTerminalSize()
|
||||
(line, col) = get_screen_size()
|
||||
for c in classes:
|
||||
items.extend(olist[c])
|
||||
items_s = printing.sprint_list(olist[c], col-20).encode('utf-8').split('\n')
|
||||
|
|
|
@ -12,7 +12,6 @@ if '/usr/scripts' not in sys.path:
|
|||
|
||||
import lc_ldap.objets as objets
|
||||
import lc_ldap.attributs as attributs
|
||||
import subprocess
|
||||
|
||||
import certificat
|
||||
import blacklist
|
||||
|
@ -35,12 +34,10 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
|
|||
"""
|
||||
a = attributs
|
||||
# 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_port = [(a.portTCPout, 50), (a.portTCPin, 50), (a.portUDPout, 50),
|
||||
(a.portUDPin, 50)]
|
||||
|
||||
to_display_borne = [(a.canal, 10), (a.hotspot, 10), (a.puissance, 10), (a.positionBorne, 50), (a.nvram, 10)]
|
||||
to_display = [(a.host, 30), (a.macAddress, 17), (a.ipHostNumber, 15),
|
||||
(a.portTCPout, 50), (a.portTCPin, 50), (a.portUDPout, 50),
|
||||
(a.portUDPin, 50)
|
||||
]
|
||||
|
||||
# Quel séparateur on utilise pour les champs multivalué
|
||||
separateur = ' '
|
||||
|
@ -61,19 +58,15 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
|
|||
title="Paramètres machine",
|
||||
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
|
||||
if "machineWifi" == objectClass or 'borneWifi' == objectClass or realm == 'bornes':
|
||||
if "machineWifi" == objectClass or 'borneWifi' == objectClass:
|
||||
hostend = ".wifi.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"
|
||||
# Si l'object class est machineCrans, pas de vérification
|
||||
elif "machineCrans" == objectClass:
|
||||
if realm == 'adm':
|
||||
hostend = ".adm.crans.org"
|
||||
if not '.' in host:
|
||||
host = host + hostend
|
||||
return host
|
||||
# Sinon, libre à chachun d'ajouter d'autres objectClass ou de filtrer
|
||||
# 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)
|
||||
|
||||
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)]:
|
||||
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:
|
||||
|
@ -92,8 +85,7 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
|
|||
def modif_machine(machine, attrs):
|
||||
with self.conn.search(dn=machine.dn, scope=0, mode='rw')[0] as machine:
|
||||
for (key, values) in attrs.items():
|
||||
if values!=u'<automatique>' or key != 'ipHostNumber':
|
||||
machine[key]=values
|
||||
machine[key]=values
|
||||
machine.validate_changes()
|
||||
machine.history_gen()
|
||||
machine.save()
|
||||
|
@ -109,18 +101,13 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
|
|||
}
|
||||
with self.conn.newMachine(proprio.dn, realm, ldif) as machine:
|
||||
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:
|
||||
machine[attributs.ipsec.ldap_name]=attributs.ipsec.default
|
||||
machine.validate_changes()
|
||||
if self.confirm_item(machine, "Voulez vous vraiement créer cette machine ?"):
|
||||
machine.create()
|
||||
self.display_item(machine, "La machine a 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)
|
||||
self.display_item(machine, "La machine à bien été créée", ipsec=True)
|
||||
return machine
|
||||
else:
|
||||
raise Continue(cont)
|
||||
|
@ -136,7 +123,7 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
|
|||
values = [v for v in values.split(separateur) if v]
|
||||
# Pour host, on fait quelques vérification de syntaxe
|
||||
if a.ldap_name == 'host':
|
||||
attrs[a.ldap_name]=check_host(values, objectClass, realm)
|
||||
attrs[a.ldap_name]=check_host(values, objectClass)
|
||||
else:
|
||||
attrs[a.ldap_name]=values
|
||||
# Soit on édite une machine existante
|
||||
|
@ -147,16 +134,10 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
|
|||
machine = create_machine(proprio, realm, attrs)
|
||||
raise Continue(cont(machine=machine))
|
||||
|
||||
|
||||
if machine:
|
||||
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)
|
||||
|
||||
# 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"""
|
||||
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):
|
||||
"""Juste un raccourci vers edit_boolean_attributs spécifique aux machines"""
|
||||
a = attributs
|
||||
|
@ -207,8 +193,8 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
|
|||
menu_droits = {
|
||||
'Information' : [a.parent, a.cableur, a.nounou],
|
||||
'Autre': [a.parent, a.cableur, a.nounou],
|
||||
'Blackliste':[a.nounou],
|
||||
'Certificat': [a.parent, a.nounou],
|
||||
'Blackliste':[a.cableur, a.nounou],
|
||||
'Certificat': [a.parent, a.cableur, a.nounou],
|
||||
'Exemption' : [a.nounou],
|
||||
'Alias' : [a.parent, a.cableur, a.nounou],
|
||||
'Remarques' : [a.cableur, a.nounou],
|
||||
|
@ -270,41 +256,24 @@ class Dialog(certificat.Dialog, blacklist.Dialog):
|
|||
menu_droits = {
|
||||
'Fixe' : [a.soi, a.cableur, a.nounou],
|
||||
'Wifi' : [a.soi, a.cableur, a.nounou],
|
||||
'Appartements': [a.soi, a.cableur, a.nounou],
|
||||
}
|
||||
menu = {
|
||||
'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'},
|
||||
}
|
||||
menu_order = ['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
|
||||
|
||||
menu_order = ['Fixe', 'Wifi']
|
||||
if isinstance(proprio, objets.AssociationCrans):
|
||||
menu_droits.update({
|
||||
'Fixe' : [a.nounou],
|
||||
'Wifi' : [a.nounou],
|
||||
'Wifi-v6' : [a.nounou],
|
||||
'Adm' : [a.nounou],
|
||||
})
|
||||
menu.update({
|
||||
'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-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'},
|
||||
})
|
||||
menu_order += ['Adm', 'Wifi-v6']
|
||||
menu_order.append('Adm')
|
||||
def box(default_item=None):
|
||||
return self.dialog.menu(
|
||||
"Type de Machine ?",
|
||||
|
|
|
@ -85,10 +85,11 @@ class Dialog(machine.Dialog, blacklist.Dialog):
|
|||
|
||||
@tailcaller
|
||||
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]),
|
||||
timeout=self.timeout
|
||||
) == 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))
|
||||
if return_obj:
|
||||
return proprio
|
||||
|
@ -167,7 +168,7 @@ class Dialog(machine.Dialog, blacklist.Dialog):
|
|||
raise Continue(cont(proprio=proprio))
|
||||
#(code, passwords) = self.handle_dialog(cont, box)
|
||||
(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(
|
||||
code=code,
|
||||
output=passwords,
|
||||
|
@ -410,19 +411,14 @@ class Dialog(machine.Dialog, blacklist.Dialog):
|
|||
"cheque" : "Chèque",
|
||||
"carte" : "Par carte bancaire",
|
||||
"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):
|
||||
box_paiement_order = ["liquide", "cheque", "carte","note"]
|
||||
box_paiement_order = ["liquide", "cheque", "carte"]
|
||||
if "cransAccount" in proprio['objectClass']:
|
||||
if not "SOLDE" in [art['code'] for art in articles] and proprio["solde"]:
|
||||
box_paiement_order.append("solde")
|
||||
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 = []
|
||||
for key in box_paiement_order:
|
||||
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):
|
||||
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))
|
||||
return self.dialog.checklist(
|
||||
text="",
|
||||
|
@ -504,19 +499,11 @@ class Dialog(machine.Dialog, blacklist.Dialog):
|
|||
|
||||
def paiement(have_set, tag, proprio, comment, cancel_cont, cont):
|
||||
articles = copy.deepcopy(have_set)
|
||||
|
||||
# On formate les articles
|
||||
for article in articles:
|
||||
if article['pu'] == '*':
|
||||
article['pu'] = article['nombre']
|
||||
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:
|
||||
facture['modePaiement']=unicode(tag, 'utf-8')
|
||||
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']
|
||||
if arts:
|
||||
self.dialog.msgbox(
|
||||
text=u"Vous pouvez remettre à l'adherent les articles (si ce sont des articles) suivant :\n * %s" % '\n * '.join(arts),
|
||||
title=u"Vente terminée",
|
||||
width=0, height=0, timeout=self.timeout)
|
||||
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",
|
||||
width=0, height=0, timeout=self.timeout)
|
||||
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']:
|
||||
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:
|
||||
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)
|
||||
|
@ -572,11 +559,10 @@ class Dialog(machine.Dialog, blacklist.Dialog):
|
|||
else:
|
||||
(code, tags) = self.handle_dialog(cont, box_choose_item, tags)
|
||||
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(
|
||||
code=code,
|
||||
output=tags,
|
||||
cancel_cont=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])]
|
||||
)
|
||||
|
|
41629
gestion/ethercodes.dat
41629
gestion/ethercodes.dat
File diff suppressed because it is too large
Load diff
|
@ -25,7 +25,6 @@ except:
|
|||
# Machine sans mailman, les ML ne seront pas reconfigurées
|
||||
pass
|
||||
|
||||
CONN = crans_ldap()
|
||||
|
||||
class del_user:
|
||||
""" Suppression des fichiers d'un compte utilisateur """
|
||||
|
@ -115,28 +114,18 @@ class home:
|
|||
for args in self.args:
|
||||
anim('\t' + args)
|
||||
try:
|
||||
login, oldLogin, oldHome = args.split(",")
|
||||
if login:
|
||||
res = CONN.search("login=%s" % (login,))
|
||||
if res['adherent']:
|
||||
adh = res['adherent'][0]
|
||||
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()
|
||||
try:
|
||||
home, uid, login, mail_redirect = args.split(',')
|
||||
except ValueError:
|
||||
home, uid, login = args.split(',')
|
||||
mail_redirect = None
|
||||
homesplit = home.split("/")
|
||||
symlink = "/home-adh/%s" % (homesplit[-1],)
|
||||
### Home
|
||||
if not os.path.exists(home):
|
||||
# Le home n'existe pas
|
||||
os.mkdir(home, 0755)
|
||||
os.chown(home, int(uid), gid)
|
||||
os.chown(home, int(uid), config.gid)
|
||||
if homesplit[-2] != "club":
|
||||
if os.path.exists(symlink) and os.path.islink(symlink):
|
||||
os.unlink(symlink)
|
||||
|
@ -147,7 +136,7 @@ class home:
|
|||
# Il y un répertoire existant
|
||||
# Bon UID ?
|
||||
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
|
||||
raise OSError('home existant')
|
||||
if homesplit[-2] != "club":
|
||||
|
@ -169,29 +158,21 @@ class home:
|
|||
### Mail
|
||||
if not os.path.exists(home + '/Mail'):
|
||||
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):
|
||||
os.mkdir('/home-adh/mail/' + login, 0700)
|
||||
os.chown('/home-adh/mail/' + login, int(uid), 8)
|
||||
|
||||
### Redirection
|
||||
if mail_redirect:
|
||||
write_in_forward = True
|
||||
|
||||
# On vérifie s'il y a déjà un .forward
|
||||
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)
|
||||
file(home + '/.forward', 'w').write(mail_redirect + '\n')
|
||||
os.chown(home + '/.forward', int(uid), config.gid)
|
||||
os.chmod(home + '/.forward', 0604)
|
||||
### Owncloud dans le home
|
||||
if not os.path.exists(home + '/OwnCloud'):
|
||||
os.mkdir(home + '/OwnCloud')
|
||||
os.chown(home + '/OwnCloud', int(uid), grp.getgrnam('www-data').gr_gid)
|
||||
os.chmod(home + '/OwnCloud', 0770)
|
||||
os.chmod(home + '/OwnCloud',0770)
|
||||
except:
|
||||
print ERREUR
|
||||
if self.debug:
|
||||
|
|
|
@ -43,7 +43,6 @@ class autostatus(gen_config) :
|
|||
"obm.crans.org",
|
||||
"obm.adm.crans.org",
|
||||
"batv-3.adm.crans.org",
|
||||
"batv-1.adm.crans.org",
|
||||
|
||||
# Config par défaut
|
||||
"non-configure.wifi.crans.org",
|
||||
|
@ -71,7 +70,6 @@ class autostatus(gen_config) :
|
|||
"ragnarok.crans.org", # RIP contrôleur disque...
|
||||
"zamok.crans.org", # c'est en fait fx
|
||||
"bati-2.adm.crans.org", # N'est plus en place
|
||||
"batv-1.crans.org",
|
||||
|
||||
# Bornes wifi de test
|
||||
"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)
|
||||
|
||||
infos_routeurs = {}
|
||||
infos_routeurs [ '138.231.136.4' ] = ['Odlyd', u'Routeur principal du CRANS']
|
||||
infos_routeurs [ '138.231.136.3' ] = ['Komaz', u'Routeur secondaire du CRANS']
|
||||
infos_routeurs [ '138.231.136.4' ] = ['Komaz', u'Routeur principal du CRANS']
|
||||
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' ] = ['Pioneer2.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.176.1' ] = ['Pioneer', u'Routeur principal de l\'ENS']
|
||||
infos_routeurs [ '193.49.65.1' ] = ['RenaterCachan1' , 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 ['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 ['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
|
||||
contact = {}
|
||||
|
@ -295,14 +291,10 @@ class autostatus(gen_config) :
|
|||
# ajout du routeur
|
||||
|
||||
# ip
|
||||
try:
|
||||
tmp_ip = routeur.split(' ')[1]
|
||||
except IndexError:
|
||||
print "Skipping %r" % routeur
|
||||
continue
|
||||
tmp_ip = routeur.split(' ')[1]
|
||||
|
||||
# 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_desc = self.infos_routeurs[tmp_ip][1]
|
||||
else :
|
||||
|
|
|
@ -6,7 +6,6 @@ Copyright (C) Valentin Samir
|
|||
Licence : GPLv3
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import ssl
|
||||
import time
|
||||
|
@ -38,31 +37,24 @@ def short_name(fullhostname):
|
|||
return fullhostname.split(".")[0]
|
||||
|
||||
class ResourceRecord(object):
|
||||
"""Classe standard définissant une ressource DNS"""
|
||||
|
||||
def __init__(self, r_type, name, value, ttl=None):
|
||||
"""Affecte les valeurs de base de l'enregistrement"""
|
||||
self.r_type = r_type
|
||||
self.name = name
|
||||
self.value = value
|
||||
self._ttl = ttl
|
||||
def __init__(self, type, name, value, ttl=None):
|
||||
self._type=type
|
||||
self._name=name
|
||||
self._value=value
|
||||
self._ttl=ttl
|
||||
|
||||
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)
|
||||
return "%s\t%s\tIN\t%s\t%s" % (self._name, self._ttl, self._type, self._value)
|
||||
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):
|
||||
"""__repr__ == __str__"""
|
||||
return str(self)
|
||||
|
||||
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, r_format='pem', ttl=None):
|
||||
""" name: nom du domaine du certificat
|
||||
def __init__(self, name, port, proto, cert, certtype, reftype, selector=0, compat=True, format='pem', ttl=None):
|
||||
"""
|
||||
name: nom du domaine du certificat
|
||||
port: port où écoute le service utilisant le certificat
|
||||
proto: udp ou tcp
|
||||
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
|
||||
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")
|
||||
|
||||
if selector != 0:
|
||||
raise NotImplementedError("selector different form 0 not implemented")
|
||||
|
||||
if cert is None and proto == 'tcp' and name[-1] == '.':
|
||||
try:
|
||||
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)
|
||||
elif cert is None:
|
||||
raise ValueError("cert can only be retrive if proto is tcp and name fqdn")
|
||||
|
||||
if r_format is not 'der':
|
||||
if format is not 'der':
|
||||
dercert = ssl.PEM_cert_to_DER_cert(cert)
|
||||
else:
|
||||
dercert = cert
|
||||
|
||||
if not dercert:
|
||||
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))
|
||||
self.certhex = certhex
|
||||
if compat:
|
||||
super(TLSA, self).__init__(
|
||||
'TYPE52',
|
||||
|
@ -103,15 +87,15 @@ class TLSA(ResourceRecord):
|
|||
)
|
||||
else:
|
||||
super(TLSA, self).__init__(
|
||||
'TLSA',
|
||||
'_%s._%s%s' % (port, proto, '.' + name if name else ''),
|
||||
"%s %s %s %s"% (certtype, selector, reftype, certhex),
|
||||
ttl
|
||||
'TLSA',
|
||||
'_%s._%s%s' % (port, proto, '.' + name if name else ''),
|
||||
"%s %s %s %s"% (certtype, selector, reftype, certhex),
|
||||
ttl
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def hashCert(reftype, certblob):
|
||||
"""Retourne un hash d'un certif DER en MAJUSCULES.
|
||||
"""
|
||||
certblob: un certificat au format DER
|
||||
"""
|
||||
if reftype == 0:
|
||||
|
@ -127,170 +111,98 @@ class TLSA(ResourceRecord):
|
|||
return hashobj.hexdigest().upper()
|
||||
|
||||
class SOA(ResourceRecord):
|
||||
"""Ressource pour une entrée DNS SOA"""
|
||||
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))
|
||||
|
||||
class A(ResourceRecord):
|
||||
"""Entrée DNS pour une IPv4"""
|
||||
def __init__(self, name, value, ttl=None):
|
||||
super(A, self).__init__('A', name, value, ttl)
|
||||
|
||||
class DS(ResourceRecord):
|
||||
"""Entrée DNS pour l'empreinte d'une clef DNSSEC"""
|
||||
def __init__(self, name, value, ttl=None):
|
||||
super(DS, self).__init__('DS', name, value, ttl)
|
||||
|
||||
class PTR(ResourceRecord):
|
||||
"""Entrée DNS inverse (pour obtenir l'IP à partir du NDD"""
|
||||
def __init__(self, name, value, ttl=None):
|
||||
super(PTR, self).__init__('PTR', name, value, ttl)
|
||||
|
||||
class AAAA(ResourceRecord):
|
||||
"""Entrée DNS pour une IPv6"""
|
||||
def __init__(self, name, value, ttl=None):
|
||||
super(AAAA, self).__init__('AAAA', name, value, ttl)
|
||||
|
||||
class TXT(ResourceRecord):
|
||||
"""Entrée DNS pour un champ TXT"""
|
||||
def __init__(self, name, value, ttl=None):
|
||||
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):
|
||||
"""Entrée DNS pour un alias (toto -> redisdead)"""
|
||||
def __init__(self, name, value, ttl=None):
|
||||
super(CNAME, self).__init__('CNAME', name, value, ttl)
|
||||
|
||||
class DNAME(ResourceRecord):
|
||||
"""Entrée DNS pour un alias de domaine (crans.eu -> crans.org)"""
|
||||
def __init__(self, name, value, ttl=None):
|
||||
super(DNAME, self).__init__('DNAME', name, value, ttl)
|
||||
|
||||
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):
|
||||
super(MX, self).__init__('MX', name, '%s\t%s' % (priority, value), ttl)
|
||||
|
||||
class NS(ResourceRecord):
|
||||
"""Entrée DNS pour donner les serveurs autoritaires pour un nom de domaine"""
|
||||
def __init__(self, name, value, ttl=None):
|
||||
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):
|
||||
"""Entrée DNS pour les champs SRV"""
|
||||
def __init__(self, service, proto, priority, weight, port, target, ttl=None, subdomain=None):
|
||||
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)
|
||||
|
||||
def __init__(self, service, proto, priority, weight, port, target, ttl=None):
|
||||
super(SRV, self).__init__('SRV', '_%s._%s' % (service, proto), '%s\t%s\t%s\t%s' % (priority, weight, port, target), ttl)
|
||||
class NAPTR(ResourceRecord):
|
||||
"""Entrée DNS pour les NAPTR"""
|
||||
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)
|
||||
|
||||
class SSHFP(ResourceRecord):
|
||||
"""Entrée DNS stockant une fingerprint SSH"""
|
||||
def __init__(self, name, r_hash, algo, key, ttl=None):
|
||||
"""Vérifie que hash/algo sont supportés dans la config"""
|
||||
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())))
|
||||
|
||||
def __init__(self, name, hash, algo, key, ttl=None):
|
||||
if not hash in config.sshfp_hash.keys():
|
||||
raise ValueError('Hash %s invalid, valid hash are %s' % (hash, ', '.join(config.sshfp_host.keys())))
|
||||
if not algo in 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(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."""
|
||||
class ZoneBase(object):
|
||||
def __init__(self, zone_name):
|
||||
"""Affecte un nom de zone"""
|
||||
super(ZoneBase, self).__init__()
|
||||
self._rrlist=[]
|
||||
self.zone_name = zone_name
|
||||
self.ttl = 3600
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s %s>" % (self.__class__.__name__, self.zone_name)
|
||||
|
||||
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)
|
||||
for rr in self:
|
||||
_ret += "%s\n" % rr
|
||||
return _ret
|
||||
ret="%s\n$ORIGIN %s.\n$TTL %s\n" % (disclamer.replace('//', ';'), self.zone_name, self.ttl)
|
||||
for rr in self._rrlist:
|
||||
ret+="%s\n" % rr
|
||||
return ret
|
||||
|
||||
def add(self, rr):
|
||||
"""Ajout d'un enregistrement DNS"""
|
||||
if isinstance(rr, ResourceRecord):
|
||||
self.append(rr)
|
||||
self._rrlist.append(rr)
|
||||
else:
|
||||
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):
|
||||
"""Pour dumper le tout dans le fichier idoine."""
|
||||
with open(path, 'w') as f:
|
||||
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)
|
||||
self.zone_clone = zone_clone
|
||||
self.ttl = zone_clone.ttl
|
||||
|
||||
# On met un SOA custom.
|
||||
self.add(soa)
|
||||
|
||||
# On ajoute un DNAME, qui indique que la zone est un clone.
|
||||
self.add(DNAME('', "%s." % self.zone_clone.zone_name))
|
||||
|
||||
# Et on extrait les données nécessaires de la zone clônée
|
||||
# à 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 ['', '@']:
|
||||
for rr in self.zone_clone._rrlist[1:]:
|
||||
if rr._name in ['', '@']:
|
||||
self.add(rr)
|
||||
|
||||
# Si le nom de domaine concerné est celui de la zone clonée, pareil, on
|
||||
# "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))
|
||||
if rr._name in ["%s." % self.zone_clone.zone_name]:
|
||||
self.add(ResourceRecord(rr._type, "%s." % self.zone_name, rr._value))
|
||||
|
||||
|
||||
class Zone(ZoneBase):
|
||||
"""Une zone standard"""
|
||||
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 = []
|
||||
def __init__(self, zone_name, ttl, soa, ns_list, ipv6=True, ipv4=True, other_zones=[]):
|
||||
super(Zone, self).__init__(zone_name)
|
||||
self.ttl = ttl
|
||||
self.ipv4 = ipv4
|
||||
|
@ -303,26 +215,15 @@ class Zone(ZoneBase):
|
|||
self.add(NS('@', '%s.' % ns))
|
||||
|
||||
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:
|
||||
if str(hostname).endswith(".%s" % zone):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_name(self, hostname):
|
||||
"""Retourne la base du nom d'un hôte. Teste si celui-ci appartient bien
|
||||
à 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 où ce nom ne devrait pas être là, on retourne None.
|
||||
"""
|
||||
# le hostname fini bien par la zone courante, et il n'appartient pas à une sous-zone
|
||||
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 == "":
|
||||
return "@"
|
||||
else:
|
||||
|
@ -331,91 +232,65 @@ class Zone(ZoneBase):
|
|||
return None
|
||||
|
||||
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]:
|
||||
raise ValueError("i should be 4 or 6")
|
||||
if nom == '@':
|
||||
return 'v%s' % i
|
||||
# On considère que le "vrai" nom est la partie avant le premier .
|
||||
elif '.' in nom:
|
||||
nom_1, nom_2 = nom.split('.', 1)
|
||||
return "%s.v%s.%s" % (nom_1, i, nom_2)
|
||||
else:
|
||||
return "%s.v%s" % (nom, i)
|
||||
|
||||
def add_delegation(self, zone, server):
|
||||
"""Lorsqu'on veut offrir une délégation DNS à une machine
|
||||
pour un nom de domaine"""
|
||||
zone = self.get_name(zone)
|
||||
def add_delegation(zone, server):
|
||||
zone = self.het_name(zone)
|
||||
if zone:
|
||||
self.add(NS('@', '%s.' % server))
|
||||
|
||||
def add_a_record(self, nom, machine):
|
||||
"""Ajout d'une entrée A."""
|
||||
# Fait-on de l'IPv4 dans cette zone ?
|
||||
if self.ipv4:
|
||||
for ip in machine.get('ipHostNumber', []):
|
||||
self.add(A(nom, ip))
|
||||
# Fait-on aussi de l'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))
|
||||
|
||||
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:
|
||||
for ip in machine.get('ip6HostNumber', []):
|
||||
# Si dnsIpv6 est à True dans la base LDAP, on ajoute l'entrée.
|
||||
# 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:
|
||||
if machine.get('dnsIpv6', [True])[0]:
|
||||
self.add(AAAA(nom, ip))
|
||||
# Si on fait aussi de l'IPv4...
|
||||
if self.ipv4:
|
||||
self.add(AAAA(self.get_name_vi(nom, 6), ip))
|
||||
|
||||
def add_sshfp_record(self, nom, machine):
|
||||
"""Ajoute une fingerprint SSH"""
|
||||
for sshkey in machine.get('sshFingerprint', []):
|
||||
try:
|
||||
algo_txt, key = str(sshkey).split()[:2]
|
||||
algo = config.sshfs_ralgo[algo_txt][1]
|
||||
for r_hash in config.sshfp_hash.keys():
|
||||
self.add(SSHFP(nom, r_hash, algo, key))
|
||||
if self.ipv4:
|
||||
self.add(SSHFP(self.get_name_vi(nom, 4), r_hash, algo, key))
|
||||
if self.ipv6:
|
||||
self.add(SSHFP(self.get_name_vi(nom, 6), r_hash, algo, key))
|
||||
algo=config.sshfs_ralgo[algo_txt][1]
|
||||
for hash in config.sshfp_hash.keys():
|
||||
self.add(SSHFP(nom, hash, algo, key))
|
||||
if self.ipv4 and self.ipv6:
|
||||
self.add(SSHFP(self.get_name_vi(nom, 4), hash, algo, key))
|
||||
self.add(SSHFP(self.get_name_vi(nom, 6), hash, algo, key))
|
||||
# KeyError is l'algo dans ldap n'est pas connu
|
||||
# TypeError si la clef n'est pas bien en base64
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
|
||||
def add_tlsa_record(self, cert):
|
||||
"""Ajout d'un certif dans le DNS"""
|
||||
if 'TLSACert' in cert['objectClass']:
|
||||
if not cert.get('revocked', [False])[0]:
|
||||
for host in cert['hostCert']:
|
||||
nom = self.get_name(host)
|
||||
if nom is None: continue
|
||||
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'))
|
||||
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'))
|
||||
for host in cert['hostCert']:
|
||||
nom=self.get_name(host)
|
||||
if nom is None: continue
|
||||
for port in cert['portTCPin']:
|
||||
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']:
|
||||
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):
|
||||
"""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']:
|
||||
# Le nom peut être None (machine appartenant à une sous-zone, ou à une autre zone)
|
||||
nom = self.get_name(host)
|
||||
if nom is None:
|
||||
continue
|
||||
nom=self.get_name(host)
|
||||
if nom is None: continue
|
||||
|
||||
self.add_a_record(nom, machine)
|
||||
self.add_aaaa_record(nom, machine)
|
||||
|
@ -423,23 +298,14 @@ class Zone(ZoneBase):
|
|||
for cert in machine.certificats():
|
||||
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']:
|
||||
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:
|
||||
continue
|
||||
|
||||
alias = self.get_name(alias)
|
||||
if alias is None:
|
||||
continue
|
||||
|
||||
if alias is None: continue
|
||||
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]:
|
||||
self.add_a_record(alias, machine)
|
||||
self.add_aaaa_record(alias, machine)
|
||||
|
@ -449,34 +315,24 @@ class Zone(ZoneBase):
|
|||
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, 4), self.get_name_vi(to_nom, 4)))
|
||||
# Ne devrait pas arriver.
|
||||
else:
|
||||
self.add(CNAME(alias, "%s." % machine['host'][0]))
|
||||
|
||||
|
||||
class ZoneReverse(Zone):
|
||||
"""Zone inverse, listant des PTR (toto.crans.org IN PTR 138.231...)"""
|
||||
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)
|
||||
|
||||
self.net = net
|
||||
zone_name = ZoneReverse.reverse(net)[0]
|
||||
if '.' in net:
|
||||
ipv6 = False
|
||||
ipv4 = True
|
||||
ipv6=False
|
||||
ipv4=True
|
||||
elif ':' in net:
|
||||
ipv6 = True
|
||||
ipv4 = False
|
||||
ipv6=True
|
||||
ipv4=False
|
||||
else:
|
||||
raise ValueError("net should be an ipv4 ou ipv6 network")
|
||||
|
||||
super(ZoneReverse, self).__init__(zone_name, ttl, soa, ns_list, ipv6=ipv6, ipv4=ipv4)
|
||||
|
||||
@staticmethod
|
||||
|
@ -485,43 +341,29 @@ class ZoneReverse(Zone):
|
|||
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
|
||||
quoi sinon."""
|
||||
# Initialise la plage d'IP à partir de net
|
||||
_network = netaddr.IPNetwork(net)
|
||||
# Prend la première adresse ip de la plage, sauf si une est fournie
|
||||
_address = netaddr.IPAddress(ip if ip else _network.ip)
|
||||
# retourne le reverse splitté. (un reverse ressemble à 0.136.231.138.in-addr.arpa.)
|
||||
rev_dns_a = _address.reverse_dns.split('.')[:-1]
|
||||
|
||||
# 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:
|
||||
n = netaddr.IPNetwork(net)
|
||||
a = netaddr.IPAddress(ip if ip else n.ip)
|
||||
rev_dns_a = a.reverse_dns.split('.')[:-1]
|
||||
assert a in n
|
||||
if n.version == 4:
|
||||
if n.prefixlen == 8:
|
||||
return ('.'.join(rev_dns_a[3:]), 3)
|
||||
elif _network.prefixlen == 16:
|
||||
elif n.prefixlen == 16:
|
||||
return ('.'.join(rev_dns_a[2:]), 2)
|
||||
elif _network.prefixlen == 24:
|
||||
elif n.prefixlen == 24:
|
||||
return ('.'.join(rev_dns_a[1:]), 1)
|
||||
else:
|
||||
raise ValueError("Bad network %s" % _network)
|
||||
# En v6 c'est plus calme.
|
||||
# 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.
|
||||
# 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)
|
||||
raise ValueError("Bad network %s" % n)
|
||||
elif n.version == 6:
|
||||
return ('.'.join(rev_dns_a[(128-n.prefixlen)/4:]), (128-n.prefixlen)/4)
|
||||
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
une liste des plages en tenant compte de ce critère (donc de taille
|
||||
32/24/16/8)
|
||||
|
||||
Ne touche à rien pour l'IPv6.
|
||||
"""
|
||||
retourne une liste de reseaux ne contenant que
|
||||
des préfixes de taille 32, 24, 16 ou 8 en ipv4
|
||||
et laisse inchangé les réseaux ipv6.
|
||||
"""
|
||||
if not isinstance(nets, list):
|
||||
nets = [nets]
|
||||
|
@ -529,8 +371,6 @@ class ZoneReverse(Zone):
|
|||
for net in nets:
|
||||
if not isinstance(net, netaddr.IPNetwork):
|
||||
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.prefixlen > 24:
|
||||
subnets.extend(net.subnet(32))
|
||||
|
@ -540,13 +380,12 @@ class ZoneReverse(Zone):
|
|||
subnets.extend(net.subnet(16))
|
||||
else:
|
||||
subnets.extend(net.subnet(8))
|
||||
# En v6 c'est tout pété.
|
||||
elif net.version == 6:
|
||||
subnets.append(net)
|
||||
return subnets
|
||||
|
||||
|
||||
def add_machine(self, machine):
|
||||
"""Ajout d'un reverse pour une machine."""
|
||||
if machine['host']:
|
||||
if self.ipv4:
|
||||
attr = 'ipHostNumber'
|
||||
|
@ -554,42 +393,33 @@ class ZoneReverse(Zone):
|
|||
attr = 'ip6HostNumber'
|
||||
else:
|
||||
raise ValueError("A reverse zone should be ipv6 or ipv6")
|
||||
|
||||
for ip in machine[attr]:
|
||||
try:
|
||||
zone, length = ZoneReverse.reverse(self.net, str(ip))
|
||||
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:
|
||||
continue
|
||||
|
||||
if attr != 'ip6HostNumber' or machine.get('dnsIpv6', [True])[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.
|
||||
if attr != 'ip6HostNumber' or machine.get('dnsIpv6', [True])[0]: # Hack pour envoyer le reverse vers l'adresse .v6 dans le cas où dnsIpv6 = False
|
||||
self.add(PTR(nom, '%s.' % machine['host'][0]))
|
||||
else:
|
||||
rev_nom, rev_zone = str(machine['host'][0]).split('.', 1)
|
||||
self.add(PTR(nom, '%s.v6.%s.' % (rev_nom, rev_zone)))
|
||||
except AssertionError:
|
||||
# L'ip n'est pas dans la zone reverse, donc on continue silencieusement.
|
||||
pass
|
||||
|
||||
|
||||
class dns(gen_config):
|
||||
"""Classe de configuration du DNS (les services, generate, toussa)"""
|
||||
class dns(gen_config) :
|
||||
######################################PARTIE DE CONFIGURATION
|
||||
|
||||
### Fichiers à écrire
|
||||
# Répertoire d'écriture des fichiers de zone
|
||||
DNS_DIR = config.dns.DNS_DIR
|
||||
DNSSEC_DIR = config.dns.DNSSEC_DIR
|
||||
DNS_DIR = '/etc/bind/generated/' # Avec un / à la fin
|
||||
DNSSEC_DIR = '/etc/bind/signed/' # Avec un / à la fin
|
||||
# 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
|
||||
DNS_CONF_BCFG2 = config.dns.DNS_CONF_BCFG2
|
||||
DNS_CONF_BCFG2 = "/var/lib/bcfg2/Cfg/etc/bind/generated/zones_crans/zones_crans"
|
||||
|
||||
### Liste DNS
|
||||
# Le premier doit être le maitre
|
||||
|
@ -602,10 +432,13 @@ class dns(gen_config):
|
|||
|
||||
### Serveurs de mail
|
||||
# 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 = {
|
||||
'crans.org': [
|
||||
'crans.org': [
|
||||
SRV('jabber', 'tcp', 5, 0, 5269, 'xmpp'),
|
||||
SRV('xmpp-server', 'tcp', 5, 0, 5269, 'xmpp'),
|
||||
SRV('xmpp-client', 'tcp', 5, 0, 5222, 'xmpp'),
|
||||
|
@ -613,41 +446,14 @@ class dns(gen_config):
|
|||
SRV('sip', 'tcp', 5, 0, 5060, 'asterisk'),
|
||||
SRV('sips', 'tcp', 5, 0, 5061, '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"),
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
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' : [
|
||||
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('@', 15, 100, "S", "SIP+D2T", "", '_sip._tcp.crans.org.', ttl=86400),
|
||||
],
|
||||
NATPRs = {
|
||||
'crans.org' : [
|
||||
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('@', 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 ] }
|
||||
|
@ -655,10 +461,10 @@ class dns(gen_config):
|
|||
# /!\ Il faut faire attention au rollback des keys, il faudrait faire quelque chose d'automatique avec opendnssec
|
||||
DSs = {
|
||||
'crans.org': [
|
||||
DS('adm', '64649 8 2 9c45f0fef063672d96c983d5a3813a08a649c72d357f41ddece73ae8872d60cf'),
|
||||
DS('wifi', '5531 8 2 daf30a647566234edc1617546fd74abbbaf965b17389248f72fc66a33d6f5063'),
|
||||
DS('tv', '18199 8 2 d3cc2f5f81b830cbb8894ffd32c236e968edd3b0c0305112b6eb970aa763418e'),
|
||||
],
|
||||
DS('adm', '64649 8 2 9c45f0fef063672d96c983d5a3813a08a649c72d357f41ddece73ae8872d60cf'),
|
||||
DS('wifi', '5531 8 2 daf30a647566234edc1617546fd74abbbaf965b17389248f72fc66a33d6f5063'),
|
||||
DS('tv', '18199 8 2 d3cc2f5f81b830cbb8894ffd32c236e968edd3b0c0305112b6eb970aa763418e'),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
@ -666,29 +472,24 @@ class dns(gen_config):
|
|||
serial = int(time.time()) + 1000000000
|
||||
TTL = 3600
|
||||
|
||||
if hostname == short_name(config.dns.DNSs[0]):
|
||||
if hostname == short_name(config.dns.DNSs[0]):
|
||||
restart_cmd = '/usr/sbin/ods-signer sign --all && /etc/init.d/bind9 reload'
|
||||
else:
|
||||
restart_cmd = '/etc/init.d/bind9 reload'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Surcharge pour affecter EXTRAS"""
|
||||
self.EXTRAS = {}
|
||||
self.anim = None
|
||||
super(dns, self).__init__(*args, **kwargs)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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():
|
||||
# On met les mêmes MX pour toutes les zones.
|
||||
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.NAPTRs, self.DSs, self.EXTRAS, self.SPFs, self.NON_CLONABLE_SPFs, self.DKIM]:
|
||||
for rr_type in [self.SRVs, self.NATPRs, self.DSs, self.EXTRAS]:
|
||||
if zone.zone_name in rr_type.keys():
|
||||
zone.extend(rr_type[zone.zone_name])
|
||||
for m in machines:
|
||||
|
@ -697,43 +498,31 @@ class dns(gen_config):
|
|||
return zones
|
||||
|
||||
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:
|
||||
# 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
|
||||
|
||||
def gen_zones_reverse(self, ttl, ns_list, serial, zones={},
|
||||
zones_reverse_v4=config.dns.zones_reverse,
|
||||
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"""
|
||||
zones_reverse_v4=config.dns.zones_reverse, zones_reverse_v6=config.dns.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
|
||||
|
||||
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.iteritems():
|
||||
for zone_clone, zones_alias in config.dns.zone_alias.items():
|
||||
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))
|
||||
# Et on ajoute les enregistrements concernant la zone clône (pas la clônée, ça
|
||||
# a déjà été fait à l'init) à la main.
|
||||
for rr_type in [self.SRVs, self.NAPTRs, self.DSs, self.SPFs]:
|
||||
zones[zone]=ZoneClone(zone, zones[zone_clone], self.gen_soa(ns_list, serial, ttl))
|
||||
for rr_type in [self.SRVs, self.NATPRs, self.DSs]:
|
||||
if zones[zone].zone_name in rr_type.keys():
|
||||
zones[zone].extend(rr_type[zones[zone].zone_name])
|
||||
return zones
|
||||
|
||||
|
||||
def gen_zones(self, ttl, serial, ns_list, populate=True):
|
||||
"""On chaîne les différents gen_zones_*"""
|
||||
zones = {}
|
||||
self.gen_zones_ldap(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:
|
||||
conn = lc_ldap.shortcuts.lc_ldap_admin()
|
||||
machines = conn.search(u"mid=*", sizelimit=10000)
|
||||
|
@ -745,34 +534,30 @@ class dns(gen_config):
|
|||
self.gen_zones_clone(ttl, ns_list, serial, zones)
|
||||
return zones
|
||||
|
||||
|
||||
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')
|
||||
zones = {}
|
||||
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_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:
|
||||
conn = lc_ldap.shortcuts.lc_ldap_admin()
|
||||
machines = conn.machinesMulticast()
|
||||
machines=conn.machinesMulticast()
|
||||
machines.extend(conn.search(u'(|(host=%s)(host=*.%s)(hostAlias=%s)(hostAlias=*.%s))' % ((config.dns.zone_tv,)*4)))
|
||||
self.populate_zones(zones, machines)
|
||||
|
||||
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()
|
||||
print affich_tools.OK
|
||||
return zones
|
||||
|
||||
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
|
||||
zone_template = """
|
||||
zone_template="""
|
||||
zone "%(zone_name)s" {
|
||||
type master;
|
||||
file "%(zone_path)s";
|
||||
|
@ -782,17 +567,15 @@ zone "%(zone_name)s" {
|
|||
with open(self.DNS_CONF, 'w') as f:
|
||||
f.write(disclamer)
|
||||
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:
|
||||
zone_path = os.path.join(self.DNSSEC_DIR, 'db.%s' % (zone.zone_name,))
|
||||
zone_path = self.DNSSEC_DIR + 'db.' + zone.zone_name
|
||||
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})
|
||||
|
||||
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" {
|
||||
type slave;
|
||||
file "%(zone_path)s";
|
||||
|
@ -804,9 +587,9 @@ zone "%(zone_name)s" {
|
|||
f.write(disclamer)
|
||||
for zone in zones.values():
|
||||
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:
|
||||
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})
|
||||
|
||||
def _gen(self):
|
||||
|
@ -816,28 +599,28 @@ zone "%(zone_name)s" {
|
|||
return "DNS"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
HOSTNAME = short_name(gethostname())
|
||||
if HOSTNAME == short_name(config.bcfg2_main):
|
||||
if __name__ == '__main__' :
|
||||
hostname = short_name(gethostname())
|
||||
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)."
|
||||
CONFIG = dns()
|
||||
CONFIG.gen_slave()
|
||||
elif HOSTNAME == short_name(config.dns.DNSs[0]):
|
||||
c = dns()
|
||||
c.gen_slave()
|
||||
elif hostname == short_name(config.dns.DNSs[0]):
|
||||
print "Serveur maître :"
|
||||
CONFIG = dns()
|
||||
ZONES = CONFIG.gen_tv()
|
||||
c = dns()
|
||||
zones = c.gen_tv()
|
||||
import subprocess
|
||||
for ZONE in ZONES.values():
|
||||
if ZONE.zone_name in config.dns.zones_dnssec:
|
||||
ARGS = ("/usr/sbin/ods-signer sign %s" % ZONE.zone_name).split()
|
||||
PROCESS = subprocess.Popen(ARGS, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
RET = PROCESS.communicate()
|
||||
print RET[0].strip()
|
||||
if RET[1].strip():
|
||||
print RET[1].strip()
|
||||
for zone in zones.values():
|
||||
if zone.zone_name in config.dns.zones_dnssec:
|
||||
args=("/usr/sbin/ods-signer sign %s" % zone.zone_name).split()
|
||||
p=subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
|
||||
ret=p.communicate()
|
||||
print ret[0].strip()
|
||||
if 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."
|
||||
elif HOSTNAME in [short_name(FULLHOSTNAME) for FULLHOSTNAME in config.dns.DNSs[1:]]:
|
||||
print "Ce serveur est esclave! Lancez ce script sur %s, puis lancez bcfg2 ici" % (config.bcfg2_main,)
|
||||
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" % bcfg2_main
|
||||
else:
|
||||
print "Ce serveur ne correspond à rien pour la configuration DNS."
|
||||
|
||||
|
|
|
@ -48,15 +48,11 @@ class dydhcp:
|
|||
msg.obj.append((b"hardware-address", pack_mac(mac)))
|
||||
msg.obj.append((b"hardware-type", struct.pack("!I", 1)))
|
||||
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:
|
||||
statem = b'supersede host-name "%s";' % 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)
|
||||
response = conn.query_server(msg)
|
||||
# print response.dump() # DEBUG purpose (repr() marche po)
|
||||
conn.close()
|
||||
|
||||
def del_host(self, ip,mac):
|
||||
|
|
|
@ -45,13 +45,9 @@ class exemptions(gen_config):
|
|||
for machine in machines:
|
||||
for destination in machine["exempt"]:
|
||||
if destination.value.version == 4:
|
||||
if not machine['ipHostNumber']:
|
||||
continue
|
||||
source = str(machine["ipHostNumber"][0])
|
||||
requete = "INSERT INTO exemptes (ip_crans, ip_dest) VALUES ('%s','%s')" % (source, destination)
|
||||
else:
|
||||
if not machine['macAddress']:
|
||||
continue
|
||||
source = str(machine["macAddress"][0])
|
||||
requete = "INSERT INTO exemptes6 (mac_crans, ip_dest) VALUES ('%s','%s')" % (source, destination)
|
||||
# Si ip vide, passons au suivant
|
||||
|
@ -90,8 +86,7 @@ class machines(gen_config):
|
|||
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))
|
||||
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
|
||||
pgsql.commit()
|
||||
|
||||
|
|
|
@ -34,13 +34,13 @@ class firewall(utils.firewall_tools) :
|
|||
self.use_ipset = [self.blacklist_hard, self.test_mac_ip, self.blacklists]
|
||||
|
||||
self.ipset['mac_ip']={
|
||||
'adh' : Ipset("MAC-IP-ADH", "bitmap:ip,mac", "range 138.231.136.0-138.231.151.255"),
|
||||
'adm' : Ipset("MAC-IP-ADM", "bitmap:ip,mac", "range 10.231.136.0-10.231.136.255"),
|
||||
'app' : Ipset("MAC-IP-APP", "bitmap:ip,mac", "range 10.2.9.0-10.2.9.255"),
|
||||
'adh' : Ipset("MAC-IP-ADH","macipmap","--from 138.231.136.0 --to 138.231.151.255"),
|
||||
'adm' : Ipset("MAC-IP-ADM","macipmap","--from 10.231.136.0 --to 10.231.136.255"),
|
||||
'app' : Ipset("MAC-IP-APP","macipmap","--from 10.2.9.0 --to 10.2.9.255"),
|
||||
}
|
||||
|
||||
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:
|
||||
# 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'])
|
||||
self.ipset['blacklist']['hard'].restore(bl_hard_ips)
|
||||
print OK
|
||||
|
@ -131,7 +131,7 @@ class firewall(utils.firewall_tools) :
|
|||
|
||||
def mac_ip_remove(self, mac, 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):
|
||||
"""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
|
||||
if utils.AddrInNet(str(ip), config.NETs['wifi']):
|
||||
# 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']):
|
||||
func('adh', "%s,%s" % (ip, machine['macAddress'][0]))
|
||||
# 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
|
||||
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, '-j REJECT')
|
||||
|
@ -238,7 +238,7 @@ class firewall_wifionly(firewall):
|
|||
if utils.AddrInNet(str(ip), config.NETs['wifi']):
|
||||
func('adh', "%s,%s" % (ip, machine['macAddress'][0]))
|
||||
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
|
||||
elif utils.AddrInNet(str(ip), config.NETs['adm']):
|
||||
func('adm', "%s,%s" % (ip, machine['macAddress'][0]))
|
||||
|
|
|
@ -21,9 +21,8 @@ firewall = {
|
|||
'odlyd' : komaz.firewall,
|
||||
'zamok' : zamok.firewall,
|
||||
'routeur' : routeur.firewall,
|
||||
'gordon' : base.firewall_routeur,
|
||||
'eap' : base.firewall_wifionly,
|
||||
'pea' : base.firewall_wifionly,
|
||||
'radius' : base.firewall_wifionly
|
||||
}
|
||||
|
||||
if hostname in firewall.keys():
|
||||
|
|
|
@ -19,7 +19,6 @@ class firewall(base.firewall_routeur):
|
|||
'ssh_on_https' : self.ssh_on_https,
|
||||
'connexion_secours' : self.connexion_secours,
|
||||
'connexion_appartement' : self.connexion_appartement,
|
||||
'connexion_wififederez' : self.connexion_wififederez,
|
||||
'blacklist_soft' : self.blacklist_soft,
|
||||
'blacklist_upload' : self.blacklist_upload,
|
||||
'reseaux_non_routable' : self.reseaux_non_routable,
|
||||
|
@ -33,21 +32,18 @@ class firewall(base.firewall_routeur):
|
|||
self.use_tc.extend([self.limitation_debit])
|
||||
|
||||
self.ipset['reseaux_non_routable'] = {
|
||||
'deny' : base.Ipset("RESEAUX-NON-ROUTABLE-DENY", "hash:net"),
|
||||
'allow' : base.Ipset("RESEAUX-NON-ROUTABLE-ALLOW", "hash:net"),
|
||||
'deny' : base.Ipset("RESEAUX-NON-ROUTABLE-DENY","nethash"),
|
||||
'allow' : base.Ipset("RESEAUX-NON-ROUTABLE-ALLOW","nethash"),
|
||||
}
|
||||
|
||||
self.ipset['blacklist'].update({
|
||||
'soft' : base.Ipset("BLACKLIST-SOFT", "hash:ip"),
|
||||
'upload' : base.Ipset("BLACKLIST-UPLOAD", "hash:ip"),
|
||||
'soft' : base.Ipset("BLACKLIST-SOFT","ipmap","--from 138.231.136.0 --to 138.231.151.255"),
|
||||
'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
|
||||
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):
|
||||
"""Mise à jour des blacklistes"""
|
||||
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, '-m state --state RELATED,ESTABLISHED -j ACCEPT')
|
||||
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']:
|
||||
self.add(table, chain, '-s %s -j %s' % (net, mac_ip_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, '-j %s' % self.connexion_secours(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.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, '-o %s -j %s' % (dev['out'], self.filtrage_ports(table)))
|
||||
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.connexion_secours(table))
|
||||
self.add(table, chain, '-j %s' % self.blacklist_soft(table))
|
||||
self.add(table, chain, '-j %s' % self.blacklist_hard(table))
|
||||
|
||||
chain = 'POSTROUTING'
|
||||
self.add(table, chain, '-j %s' % self.connexion_wififederez(table))
|
||||
self.add(table, chain, '-j %s' % self.connexion_appartement(table))
|
||||
return
|
||||
|
||||
|
@ -155,13 +147,13 @@ class firewall(base.firewall_routeur):
|
|||
self.apply(table, chain)
|
||||
return chain
|
||||
|
||||
def limit_ssh_connexion(self, table=None, apply=False, ttl=120, counter_name="SSH"):
|
||||
chain = 'LIMIT-%s-CONNEXION' % (counter_name,)
|
||||
def limit_ssh_connexion(self, table=None, apply=False):
|
||||
chain = 'LIMIT-SSH-CONNEXION'
|
||||
|
||||
if table == 'filter':
|
||||
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 %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 --set' % dev['out'])
|
||||
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
|
||||
|
||||
if apply:
|
||||
|
@ -251,7 +243,6 @@ class firewall(base.firewall_routeur):
|
|||
if table == 'nat':
|
||||
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 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)
|
||||
print OK
|
||||
|
||||
|
@ -306,29 +297,6 @@ class firewall(base.firewall_routeur):
|
|||
self.apply(table, 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):
|
||||
self.blacklist_soft(fill_ipset=True)
|
||||
# for ip in ip_list:
|
||||
|
@ -347,7 +315,7 @@ class firewall(base.firewall_routeur):
|
|||
|
||||
if fill_ipset:
|
||||
# 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'])
|
||||
self.ipset['blacklist']['soft'].restore(bl_soft_ips)
|
||||
print OK
|
||||
|
@ -372,41 +340,6 @@ class firewall(base.firewall_routeur):
|
|||
self.apply(table, 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):
|
||||
self.blacklist_upload(fill_ipset=True)
|
||||
# for ip in ip_list:
|
||||
|
@ -425,7 +358,7 @@ class firewall(base.firewall_routeur):
|
|||
|
||||
if fill_ipset:
|
||||
# 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'])
|
||||
self.ipset['blacklist']['upload'].restore(bl_upload_ips)
|
||||
print OK
|
||||
|
@ -493,7 +426,6 @@ class firewall(base.firewall_routeur):
|
|||
|
||||
if table == 'filter':
|
||||
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 proto in base.config.firewall.srv_ports_default.keys():
|
||||
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
|
||||
bl_upload_debit_max = base.config.firewall.bl_upload_debit_max
|
||||
appt_upload_max = base.config.firewall.appt_upload_max
|
||||
federez_upload_max = base.config.firewall.federez_upload_max
|
||||
uplink_speed = '1024mbit'
|
||||
|
||||
|
||||
if table == 'mangle':
|
||||
pretty_print(table, chain)
|
||||
# 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 -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
|
||||
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')
|
||||
|
@ -584,17 +509,17 @@ class firewall(base.firewall_routeur):
|
|||
utils.tc("class add dev %s parent 1: classid 1:1 "
|
||||
"htb rate %s ceil %s" % (dev[int_key], uplink_speed, uplink_speed))
|
||||
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
|
||||
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 '
|
||||
'handle 10: sfq perturb 10' % dev[int_key])
|
||||
|
||||
# Classe par pour la voip
|
||||
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 '
|
||||
'handle 12: sfq perturb 10' % dev[int_key])
|
||||
|
||||
|
@ -622,34 +547,10 @@ class firewall(base.firewall_routeur):
|
|||
|
||||
# Classe pour le download des apparetments
|
||||
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 '
|
||||
'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
|
||||
|
||||
if apply:
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import sys
|
||||
import netaddr
|
||||
|
||||
if '/usr/scripts' not in sys.path:
|
||||
sys.path.append('/usr/scripts')
|
||||
if '/usr/scripts/' not in sys.path:
|
||||
sys.path.append('/usr/scripts/')
|
||||
|
||||
import syslog
|
||||
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"""
|
||||
|
||||
def machines(self):
|
||||
"""Renvoit la liste de toutes les machines"""
|
||||
"""Renvois la liste de toutes les machines"""
|
||||
if self._machines:
|
||||
return self._machines
|
||||
# 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
|
||||
# d'une machine
|
||||
anim('\tChargement des machines')
|
||||
# On prend toutes les machines y compris celles de ceux qui n'ont pas payé
|
||||
# elles seront ajoutées dans mac_ip mais blacklistées du fait du non paiement ensuite
|
||||
self._machines = self.conn.allMachines()
|
||||
|
||||
self._machines, self._adherents = self.conn.allMachinesAdherents()
|
||||
self._adherents = [ adh for adh in self._adherents if adh.paiement_ok() ]
|
||||
print OK
|
||||
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):
|
||||
"""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:
|
||||
return self._blacklisted_machines
|
||||
self._blacklisted_machines = [ machine for machine in self.machines() if machine.blacklist_actif() ]
|
||||
return self._blacklisted_machines
|
||||
|
||||
def blacklisted_ips(self, blacklist_sanctions=None):
|
||||
"""Renvoit l'ensemble des ips des machines ayant une blacklist dans blacklist_sanctions et étant dans nets si spécifié"""
|
||||
def blacklisted_ips(self, blacklist_sanctions=None, nets=None):
|
||||
"""Renvois l'ensemble des ips des machines ayant une blacklist dans blacklist_sanctions et étant dans nets si spécifié"""
|
||||
bl_ips = set()
|
||||
for machine in self.blacklisted_machines():
|
||||
if blacklist_sanctions is None or set(bl['type'] for bl in machine.blacklist_actif()).intersection(blacklist_sanctions):
|
||||
for ip in machine['ipHostNumber']:
|
||||
bl_ips.add(str(ip))
|
||||
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))
|
||||
return bl_ips
|
||||
|
||||
def blacklisted_adherents(self, excepts=[]):
|
||||
"""Renvoit 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()
|
||||
|
||||
"""Renvois la liste de tous les adhérents ayant une blackliste active en ignorant les blacklist de excepts"""
|
||||
if self._blacklisted_adherents and self._blacklisted_adherents_type == set(excepts):
|
||||
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)
|
||||
return self._blacklisted_adherents
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ class firewall(base.firewall):
|
|||
self.add(table, chain, '-d 127.0.0.1/8 -j RETURN')
|
||||
for net in base.config.NETs['all']:
|
||||
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:
|
||||
self.add(table, chain, '-m owner --uid-owner %s -j REJECT' % adh['uidNumber'][0])
|
||||
print OK
|
||||
|
|
|
@ -20,10 +20,7 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
import pwd
|
||||
import sys, re, os, pwd
|
||||
|
||||
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
|
||||
# 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 recent --name SSH2 --update --seconds 30 --hitcount 10 --rttl -j DROP' % dev_ip6)
|
||||
ip6tables.filter.input('-i %s -p tcp --dport ssh -m state --state NEW -m recent --name SSH --set ' % dev_ip6)
|
||||
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)
|
||||
#ip6tables.filter.forward('-i %s -p tcp --dport ssh -m state --state NEW -j ACCEPT' % dev_ip6)
|
||||
# ip6tables.filter.forward('-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 \
|
||||
#recent --name SSH --update --seconds 60 --hitcount 4 --rttl -j DROP' %
|
||||
# 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():
|
||||
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 )
|
||||
|
||||
# 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
|
||||
ip6tables.filter.forward('-o %s -d 2a01:240:fe3d:d2::/64 -j ACCEPT' % dev_crans)
|
||||
|
|
|
@ -45,13 +45,17 @@ class base_reconfigure:
|
|||
'macip': [ _s + '-macip' for _s in __firewalled_servers ],
|
||||
# 'droits': [ 'rouge-droits', 'ragnarok-droits' ],
|
||||
'blacklist': __blacklist_servers,
|
||||
'bl_carte_etudiant': __blacklist_servers,
|
||||
'bl_chbre_invalide': __blacklist_servers,
|
||||
'blacklist_mail_invalide': __blacklist_servers,
|
||||
'blacklist_virus': __blacklist_servers,
|
||||
'blacklist_warez': __blacklist_servers,
|
||||
'blacklist_ipv6_ra': __blacklist_servers,
|
||||
'blacklist_upload': __blacklist_servers,
|
||||
'blacklist_p2p': __blacklist_servers,
|
||||
'blacklist_autodisc_virus': __blacklist_servers,
|
||||
'blacklist_autodisc_upload': __blacklist_servers,
|
||||
'blacklist_autodisc_p2p': __blacklist_servers,
|
||||
'blacklist_bloq': __blacklist_servers,
|
||||
'del_user': [ 'zbee-del_user', 'owl-del_user', 'zamok-del_user' ],
|
||||
'port': ['%s-port' % _s for _s in __services.get('connection-main', [])],
|
||||
|
@ -64,7 +68,10 @@ class base_reconfigure:
|
|||
'warez':__service_develop['blacklist_warez'],
|
||||
'ipv6_ra':__service_develop['blacklist_ipv6_ra'],
|
||||
'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_p2p': __service_develop['blacklist_autodisc_p2p'],
|
||||
'bloq': __service_develop['blacklist_bloq'],
|
||||
})
|
||||
except ImportError:
|
||||
|
@ -229,7 +236,7 @@ class odlyd(base_reconfigure):
|
|||
|
||||
class zamok(base_reconfigure):
|
||||
def del_user(self, args):
|
||||
# Suppression des fichiers d'impression
|
||||
# Suppression des fichies d'impression
|
||||
from adherents import del_user
|
||||
self._do(del_user(args))
|
||||
|
||||
|
|
|
@ -16,37 +16,31 @@
|
|||
|
||||
|
||||
import sys
|
||||
if '/usr/scripts' not in sys.path:
|
||||
sys.path.append('/usr/scripts')
|
||||
sys.path.append('/usr/scripts/gestion')
|
||||
|
||||
import commands
|
||||
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):
|
||||
# Gestion des erreurs d'ipset
|
||||
def __init__(self, cmd, err_code, output):
|
||||
self.cmd = cmd
|
||||
self.err_code = err_code
|
||||
self.output = output
|
||||
def __init__(self,cmd,err_code,output):
|
||||
self.cmd=cmd
|
||||
self.err_code=err_code
|
||||
self.output=output
|
||||
def __str__(self):
|
||||
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):
|
||||
ipset = IPSET_PATH
|
||||
ipset="/usr/sbin/ipset"
|
||||
|
||||
def __str__(self):
|
||||
return self.set
|
||||
|
||||
def __init__(self, set, type, typeopt=''):
|
||||
self.set = set
|
||||
self.type = type
|
||||
self.typeopt = typeopt
|
||||
def __init__(self,set,type,typeopt=''):
|
||||
self.set=set
|
||||
self.type=type
|
||||
self.typeopt=typeopt
|
||||
self.squeeze = os.uname()[2] < '3'
|
||||
try:
|
||||
self.create()
|
||||
except IpsetError as error:
|
||||
|
@ -56,58 +50,62 @@ class Ipset(object):
|
|||
raise
|
||||
pass
|
||||
|
||||
def call(self, cmd, arg=''):
|
||||
def call(self,cmd,arg=''):
|
||||
"""Appel système à ipset"""
|
||||
cmd_line = "%s %s %s %s" % (self.ipset, cmd, self.set, arg)
|
||||
status, output = commands.getstatusoutput(cmd_line)
|
||||
cmd_line="%s %s %s %s" % (self.ipset,cmd,self.set,arg)
|
||||
status,output=commands.getstatusoutput(cmd_line)
|
||||
if status:
|
||||
raise IpsetError(cmd_line, status, output)
|
||||
raise IpsetError(cmd_line,status,output)
|
||||
return output
|
||||
|
||||
def create(self, opt=''):
|
||||
self.call("create", "%s %s" % (self.type, self.typeopt))
|
||||
def create(self,opt=''):
|
||||
self.call("-N","%s %s" % (self.type, self.typeopt))
|
||||
|
||||
def add(self, arg):
|
||||
self.call("add", arg)
|
||||
def add(self,arg):
|
||||
self.call("-A",arg)
|
||||
|
||||
def list(self):
|
||||
output = self.call("list").splitlines()
|
||||
list = []
|
||||
output=self.call("-L").splitlines()
|
||||
list=[]
|
||||
for line in output[6:]:
|
||||
if line == 'Bindings:':
|
||||
if line=='Bindings:':
|
||||
break
|
||||
list.append(line)
|
||||
return list
|
||||
|
||||
def delete(self, ip):
|
||||
def delete(self,ip):
|
||||
"""Delete an IP"""
|
||||
self.call("del", ip)
|
||||
self.call("-D",ip)
|
||||
|
||||
def restore(self, rules):
|
||||
def restore(self,rules):
|
||||
""" restore le set courrant"""
|
||||
rules_str = self.restore_format(rules)
|
||||
str = "%s\nCOMMIT\n" % rules_str
|
||||
path = '/tmp/ipset_%s' % self.set
|
||||
f = open(path, 'w+')
|
||||
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
|
||||
path='/tmp/ipset_%s' % self.set
|
||||
f=open(path, 'w+')
|
||||
f.write(str)
|
||||
f.close()
|
||||
try:
|
||||
self.flush()
|
||||
except IpsetError as error:
|
||||
sys.stderr.write("%s\n" % error)
|
||||
|
||||
cmd = "cat %s | %s -R" % (path, self.ipset)
|
||||
status, output = commands.getstatusoutput(cmd)
|
||||
if self.squeeze:
|
||||
self.destroy()
|
||||
except IpsetError as error: sys.stderr.write("%s\n" % error)
|
||||
cmd="cat %s | %s -R" % (path,self.ipset)
|
||||
status,output=commands.getstatusoutput(cmd)
|
||||
if status:
|
||||
raise IpsetError(cmd, status, output)
|
||||
raise IpsetError(cmd,status,output)
|
||||
return output
|
||||
|
||||
def flush(self):
|
||||
self.call("flush")
|
||||
self.call("-F")
|
||||
|
||||
def destroy(self):
|
||||
self.call("destroy")
|
||||
self.call("-X")
|
||||
|
||||
def restore_format(self, rules):
|
||||
return '\n'.join(["add %s %s" % (self.set, data) for data in rules])
|
||||
def restore_format(self,rules):
|
||||
return '\n'.join(["-A %s %s" % (self.set,data) for data in rules])
|
||||
|
||||
|
|
|
@ -31,11 +31,11 @@ DEBIAN_BACKPORT_ARCHS="i386 amd64"
|
|||
DEBIAN_BACKPORT_FTP="ftp://cdimage.debian.org/cdimage/unofficial/backports/"
|
||||
|
||||
# Définitions spécifiques à Ubuntu
|
||||
UBUNTU_DISTS="precise trusty utopic vivid"
|
||||
UBUNTU_DISTS="precise saucy trusty utopic"
|
||||
UBUNTU_ARCHS="i386 amd64"
|
||||
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
|
||||
# à /etc/exports) et mettre les images dans $ISODIR/ubuntu/ puis
|
||||
# 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"
|
||||
|
||||
# Définitions spécifiques à Fedora
|
||||
FEDORA_DISTS="20 21 22"
|
||||
FEDORA_DISTS="19 20"
|
||||
FEDORA_ARCHS="i386 x86_64"
|
||||
FEDORA_FTP="ftp://ftp.free.fr/mirrors/fedora.redhat.com/fedora/linux/"
|
||||
|
||||
|
|
|
@ -123,11 +123,11 @@ for dist in $DEBIAN_DISTS; do
|
|||
#~ 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/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
|
||||
#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/
|
||||
#mkdir -p $TFTPROOT/debian-$dist/kfreebsd-$arch/
|
||||
# cp -r $TMPDIR/netboot-debian-$dist-kfreebsd-$arch/* $TFTPROOT/debian-$dist/kfreebsd-$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
|
||||
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/
|
||||
mkdir -p $TFTPROOT/debian-$dist/kfreebsd-$arch/
|
||||
cp -r $TMPDIR/netboot-debian-$dist-kfreebsd-$arch/* $TFTPROOT/debian-$dist/kfreebsd-$arch/
|
||||
done
|
||||
done
|
||||
|
||||
|
@ -170,12 +170,12 @@ cat >> $TFTPROOT/boot-screens/menu.cfg << EOF
|
|||
menu end
|
||||
EOF
|
||||
done
|
||||
#for arch in $DEBIAN_ARCHS; do
|
||||
#cat >> $TFTPROOT/boot-screens/menu.cfg <<EOF
|
||||
# LABEL Debian $dist kfreebsd-$arch
|
||||
# kernel debian-$dist/kfreebsd-$arch/grub2pxe
|
||||
#EOF
|
||||
#done
|
||||
for arch in $DEBIAN_ARCHS; do
|
||||
cat >> $TFTPROOT/boot-screens/menu.cfg <<EOF
|
||||
LABEL Debian $dist kfreebsd-$arch
|
||||
kernel debian-$dist/kfreebsd-$arch/grub2pxe
|
||||
EOF
|
||||
done
|
||||
cat >> $TFTPROOT/boot-screens/menu.cfg << EOF
|
||||
menu end
|
||||
|
||||
|
@ -573,10 +573,8 @@ for dist in $FEDORA_DISTS; do
|
|||
for arch in $FEDORA_ARCHS; do
|
||||
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/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/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
|
||||
done
|
||||
done
|
||||
|
|
|
@ -28,7 +28,7 @@ console inactivity-timer 30
|
|||
logging {{ s }}
|
||||
{%- endfor %}
|
||||
;--- IP du switch ---
|
||||
ip default-gateway {{ gateway }}
|
||||
ip default-gateway 10.231.136.4
|
||||
{%- for vlan in vlans %}
|
||||
vlan {{ vlan.id }}
|
||||
name "{{ vlan.name|capitalize }}"
|
||||
|
@ -54,13 +54,12 @@ no web-management
|
|||
aaa authentication ssh login public-key none
|
||||
aaa authentication ssh enable public-key none
|
||||
ip ssh
|
||||
ip authorized-managers {{ network_id }} {{ subnet }}
|
||||
ip authorized-managers 10.231.136.0 255.255.255.0
|
||||
ip ssh filetransfer
|
||||
;--- Protection contre les boucles ---
|
||||
loop-protect disable-timer 30
|
||||
loop-protect transmit-interval 3
|
||||
loop-protect {{ non_trusted }}
|
||||
{%- if not public %}
|
||||
;--- Serveurs radius ---
|
||||
radius-server dead-time 2
|
||||
radius-server key {{ radius_key }}
|
||||
|
@ -69,26 +68,24 @@ radius-server host {{ s }}
|
|||
{%- endfor %}
|
||||
;--- Filtrage mac ---
|
||||
aaa port-access mac-based addr-format multi-colon
|
||||
{%- endif %}
|
||||
;--- Bricoles ---
|
||||
no cdp run
|
||||
no stack
|
||||
;--- DHCP Snooping ---
|
||||
{%- if dhcp_snooping_vlan_names %}
|
||||
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 %}
|
||||
dhcp-snooping authorized-server {{ s }}
|
||||
{%- endfor %}
|
||||
; Activation
|
||||
dhcp-snooping
|
||||
{%- endif %}
|
||||
{% if ra_filter %};--- RA guards ---
|
||||
ipv6 ra-guard ports {{ non_trusted }}
|
||||
no ipv6 ra-guard ports {{ trusted }}
|
||||
{% endif %}
|
||||
|
||||
;--- Config des prises ---
|
||||
{%- 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 }} addr-limit {{ port.num_mac() }}
|
||||
aaa port-access mac-based {{ port|int }} logoff-period 3600
|
||||
|
@ -98,9 +95,6 @@ interface {{ port|int }}
|
|||
enable
|
||||
name "{{ port }}"
|
||||
{{ port.flowcontrol() }}
|
||||
{%- if port.is_trusted() %}
|
||||
dhcp-snooping trust
|
||||
{%- endif %}
|
||||
{%- if gigabit %}
|
||||
{{ port.speed() }}
|
||||
{%- endif %}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
"""
|
||||
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 :
|
||||
* mot de passe admin (password manager user-name <username>)
|
||||
* activation du ssh (crypto key generate ssh)
|
||||
|
@ -51,7 +53,7 @@ V_NO = 3
|
|||
|
||||
# Vlans disponibles
|
||||
ENABLED_VLANS = ['adherent', 'adm', 'wifi', 'v6only', 'accueil', 'isolement',
|
||||
'appts', 'event', 'federez']
|
||||
'appts', 'event']
|
||||
|
||||
def vlan_id(name):
|
||||
"""Vlan id of a name (filtre jinja)"""
|
||||
|
@ -176,7 +178,7 @@ class Port(object):
|
|||
return V_NO
|
||||
elif self.bornes:
|
||||
if vlan in ['wifi', 'accueil', 'isolement', 'v6only', 'appts',
|
||||
'event', 'federez']:
|
||||
'event']:
|
||||
return V_TAGGED
|
||||
# Cas d'une borne dans une chambre: l'adherent doit pouvoir
|
||||
# se connecter
|
||||
|
@ -441,11 +443,7 @@ def format_prises_group(data, first, last):
|
|||
def pretty_print(hostname):
|
||||
"""Affiche joliement le plan de connexion d'un switch"""
|
||||
bat, sw_num = get_bat_num(hostname)
|
||||
|
||||
try:
|
||||
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]
|
||||
switch = ldap.search(u'host=bat%s-%d.adm.crans.org' % (bat, sw_num))[0]
|
||||
|
||||
port_dict = get_port_dict(switch)
|
||||
total = max(port_dict.keys())
|
||||
|
@ -465,11 +463,7 @@ def conf_switch(hostname):
|
|||
"""Affiche la configuration d'un switch"""
|
||||
bat, sw_num = get_bat_num(hostname)
|
||||
|
||||
try:
|
||||
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]
|
||||
|
||||
switch = ldap.search(u'host=bat%s-%d.adm.crans.org' % (bat, sw_num))[0]
|
||||
|
||||
tpl_env = jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
|
||||
##for info:
|
||||
|
@ -482,12 +476,12 @@ def conf_switch(hostname):
|
|||
|
||||
'date_gen': datetime.datetime.now(),
|
||||
# TODO fill that depuis bcfg2 ou whatever
|
||||
'radius_servers': [
|
||||
'10.231.136.72',
|
||||
'10.231.136.11',
|
||||
],
|
||||
'radius_servers': ['10.231.136.72', '10.231.136.9' ],
|
||||
'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_rid_servers': [34, 160],
|
||||
|
||||
|
@ -496,7 +490,7 @@ def conf_switch(hostname):
|
|||
|
||||
# réseaux où on fait du dhcp snooping (cf data.NETs)
|
||||
'dhcp_snooping_vlan_names': ['adherent', 'wifi', 'accueil',
|
||||
'isolement', 'v6only', 'appts', 'federez'],
|
||||
'isolement', 'v6only', 'appts'],
|
||||
}
|
||||
|
||||
for com in switch['info']:
|
||||
|
@ -523,30 +517,6 @@ def conf_switch(hostname):
|
|||
first = netaddr.IPNetwork(net_of_vlan_name(vname)[0]).first
|
||||
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
|
||||
if imodel in GIGABIT_MODELS:
|
||||
data['gigabit'] = True
|
||||
|
@ -569,14 +539,9 @@ def conf_switch(hostname):
|
|||
V_NO: 'no'}[assign]
|
||||
vlan.setdefault(attr, PortList())
|
||||
vlan[attr].extend(p)
|
||||
if name == 'adm' and not data['public']:
|
||||
if name == 'adm':
|
||||
vlan['ip_cfg'] = (gethostbyname(hostname), '255.255.255.0')
|
||||
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
|
||||
vlan['extra'] = 'ip igmp\nno ip igmp querier'
|
||||
vlans[name] = vlan
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -35,7 +35,7 @@ def handle_exit_code(d, code):
|
|||
os.system('clear')
|
||||
sys.exit(0)
|
||||
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 ?"
|
||||
if d.yesno(msg, width=60) == d.DIALOG_OK:
|
||||
os.system('clear')
|
||||
|
|
|
@ -25,7 +25,7 @@ import netsnmp
|
|||
if '/usr/scripts' not in sys.path:
|
||||
path.append('/usr/scripts')
|
||||
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
|
||||
|
||||
try:
|
||||
|
@ -390,27 +390,17 @@ class hpswitch :
|
|||
prise = prise.replace('-','')
|
||||
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
|
||||
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)
|
||||
return self.__is('IF-MIB::ifAdminStatus',prise)
|
||||
|
||||
def is_up(self,prise=0) :
|
||||
""" Retoune True ou False suivant si la prise est up
|
||||
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)
|
||||
return self.__is('IF-MIB::ifOperStatus', prise)
|
||||
return self.__is('IF-MIB::ifOperStatus',prise)
|
||||
|
||||
def nom(self,nom=None,prise=0) :
|
||||
""" Retourne ou attribue le nom à la prise fournie """
|
||||
|
@ -449,15 +439,13 @@ class hpswitch :
|
|||
|
||||
def vlans(self, prise = None):
|
||||
"""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:
|
||||
prise = self.prise
|
||||
prise = int(prise)
|
||||
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'
|
||||
oids = self.walk(oid_base)
|
||||
result = []
|
||||
for vlan_name, vlan in vlans.iteritems():
|
||||
if oid_format % {'vlan': vlan, 'prise': prise} in oids:
|
||||
result.append(vlan_name)
|
||||
|
|
|
@ -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])
|
|
@ -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',
|
||||
}
|
|
@ -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())
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
|
@ -21,10 +21,10 @@
|
|||
import sys
|
||||
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 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 ridtools import Rid, find_rid_plage
|
||||
import subprocess
|
||||
|
@ -768,13 +768,9 @@ def blacklist(ipt):
|
|||
if [x for x in sanctions if x in blacklist_sanctions_ipv6]:
|
||||
blcklst.extend(target.machines())
|
||||
|
||||
s = db.search('mblacklist=*&finConnexion>=%(fin)s&finAdhesion>=%(fin)s' % {
|
||||
'fin': generalizedTimeFormat(),
|
||||
})
|
||||
s = db.search('mblacklist=*&paiement=%s' % ann_scol)
|
||||
if periode_transitoire:
|
||||
s['machine'].extend(db.search('mblacklist=*&finConnexion>=%(fin)s&finAdhsion>=%(fin)s' % {
|
||||
'fin': gtf_debut_periode_transitoire,
|
||||
})['machine'])
|
||||
s['machine'].extend(db.search('mblacklist=*&paiement=%s' % (ann_scol-1))['machine'])
|
||||
|
||||
for target in s['machine']:
|
||||
sanctions = target.blacklist_actif()
|
||||
|
|
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue