diff --git a/.gitignore b/.gitignore index 6b67b4cd..59081440 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,10 @@ var/ surveillance/mac_prises/output/ doc/build/ +# modules tier +pyasn1_modules/ +pythondialog/ + # etat_* de la connexion de secours secours/etat_* diff --git a/freeradius/auth.py b/freeradius/auth.py index 9a9b2d2d..a4f23a2f 100644 --- a/freeradius/auth.py +++ b/freeradius/auth.py @@ -19,12 +19,15 @@ # Voir des exemples plus complets ici: # https://github.com/FreeRADIUS/freeradius-server/blob/master/src/modules/rlm_python/ -from lc_ldap.shortcuts import with_ldap_conn, lc_ldap_anonymous +import lc_ldap.shortcuts from lc_ldap.crans_utils import escape as escape_ldap +import lc_ldap.crans_utils from gestion.config.config import vlans import lc_ldap.objets import radiusd import netaddr +import traceback +from gestion.gen_confs.trigger import trigger_generate_cochon as trigger_generate # Voilà, pour faire marcher le V6Only, il faut se retirer l'ipv4 de sa machine # ou en enregistrer une nouvelle (sans ipv4) avec une autre mac. Moi j'ai la @@ -35,12 +38,28 @@ u'dc:9f:db:5c:c3:ea', # polynice-wlan0 u'00:26:c7:a6:9e:16', # cerveaulent ] +# TODO (à metre dans bcfg2) +#setfacl -m u:freerad:rx /etc/crans/ +#setfacl -m u:freerad:rx /etc/crans/secrets +#setfacl -m u:freerad:r /etc/crans/secrets/dhcp.py +#setfacl -m u:freerad:r /etc/crans/secrets/secrets.py +#setfacl -m u:freerad:r /etc/crans/secrets/trigger-generate.pub +#setfacl -m m::r /etc/crans/secrets/trigger-generate +#setfacl -m u:freerad:r /etc/crans/secrets/trigger-generate + bl_reject = [u'bloq'] 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 bl_accueil = [u'carte_etudiant', u'chambre_invalide', u'paiement'] +# Decorateur utilisé plus tard (same connection) +use_ldap_admin = lc_ldap.shortcuts.with_ldap_conn(retries=2, delay=5, + constructor=lc_ldap.shortcuts.lc_ldap_admin) +use_ldap = lc_ldap.shortcuts.with_ldap_conn(retries=2, delay=5, + constructor=lc_ldap.shortcuts.lc_ldap_anonymous) + +@use_ldap def get_machines(auth_data, conn): mac = None username = None @@ -49,25 +68,100 @@ def get_machines(auth_data, conn): # Calling-Station-Id: "une_adresse_mac" for (key, value) in auth_data: if key == 'Calling-Station-Id': - mac = escape_ldap(value.decode('ascii', 'ignore').replace('"','')) + try: + value = value.decode('ascii', 'ignore').replace('"','') + mac = lc_ldap.crans_utils.format_mac(value) + except: + radiusd.radlog(radiusd.L_ERR, 'Cannot format MAC !') if key == 'User-Name': username = escape_ldap(value.decode('ascii', 'ignore').replace('"','')) + # TODO - # format mac (done by preprocess & hints) # format username (strip .wifi.crans.org) base = u'(objectclass=machine)' - # Search by reported mac, then (if failure) search by username (mac or host) - return conn.search(u'(&%s(macAddress=%s))' % (base, mac)) or \ - conn.search(u'(&%s(|(macAddress=%s)(host=%s.wifi.crans.org)))' % - (base, username, username)) -# Decorateur utilisé plus tard (same connection) -use_ldap = with_ldap_conn(retries=2, delay=5, constructor=lc_ldap_anonymous) + if mac is None: + radiusd.radlog(radiusd.L_ERR, 'Cannot read client MAC from AP !') + return [] + + mac = escape_ldap(mac) + username = escape_ldap(username) + search_strats = [ + # Case 1: Search by mac (reported by AP) + u'(&%s(macAddress=%s))' % (base, mac), + # Case 2: unregistered mac + u'(&%s(macAddress=)(host=%s.wifi.crans.org))' % + (base, username), + ] + + for filter_s in search_strats: + res = conn.search(filter_s) + if res: + break + + return res + +def get_prise(auth_data): + """Extrait la prise""" + ## Regarder dans + ## Filaire: NAS-Identifier => contient le nom du switch (batm-3.adm.crans.org) + ## Nas-Port => port du switch (ex 42) + ## WiFi: NAS-Identifier => vide + ## Nas-Port => numéro sur l'interface + ## Nas-IP-Address => adresse IP de la borne + is_wifi = True + bat_name = None + bat_num = None + port = None + + for (key, value) in auth_data: + if key == 'NAS-Identifier': + if value.startswith('bat'): + nas = value.split('.', 1)[0] + try: + bat_name = nas[3] + bat_num = nas.split('-', 1)[1] + except IndexError: + pass + + if key == 'Nas-Port': + port = int(value) + + if bat_num and bat_name and port: + return bat_name + "%01d%02d" % (bat_num, port) + + +@use_ldap_admin +def register_mac(auth_data, machine, conn): + for (key, value) in auth_data: + if key == 'Calling-Station-Id': + try: + value = value.decode('ascii', 'ignore').replace('"','') + mac = lc_ldap.crans_utils.format_mac(value) + except: + radiusd.radlog(radiusd.L_ERR, 'Cannot format MAC !') + + if mac is None: + radiusd.radlog(radiusd.L_ERR, 'Cannot find MAC') + return + mac = unicode(mac.lower()) + with conn.search(unicode(machine.dn.split(',',1)[0]), mode='rw')[0] as machine: + radiusd.radlog(radiusd.L_INFO, 'Registering mac %s' % mac) + machine['macAddress'] = mac + machine.history_add(u'auth.py', u'macAddress ( -> %s)' % mac) + machine.validate_changes() + machine.save() + radiusd.radlog(radiusd.L_INFO, 'Mac set') + radiusd.radlog(radiusd.L_INFO, 'Triggering komaz') + trigger_generate('komaz') + radiusd.radlog(radiusd.L_INFO, 'done ! (triggered komaz)') + +@use_ldap_admin @use_ldap -def instantiate(p, conn): - """Utile pour initialiser la connexion ldap une première fois (otherwise, +def instantiate(p, *conns): + """Utile pour initialiser les connexions ldap une première fois (otherwise, do nothing)""" pass @@ -79,15 +173,20 @@ def wifi_authorize(auth_data, conn): champs login et mot de passe qui serviront ensuite à l'authentification (MschapV2/PEAP ou MschapV2/TTLS)""" - items = get_machines(auth_data, conn) + items = get_machines(auth_data) if not items: radiusd.radlog(radiusd.L_ERR, 'lc_ldap: Nobody found') return radiusd.RLM_MODULE_NOTFOUND + if len(items) > 1: - radiusd.radlog(radiusd.L_ERR, 'lc_ldap: Too many results') + radiusd.radlog(radiusd.L_ERR, 'lc_ldap: Too many results (took first)') machine = items[0] + + if '' in machine['macAddress']: + register_mac(auth_data, machine) + proprio = machine.proprio() if isinstance(proprio, lc_ldap.objets.AssociationCrans): @@ -102,8 +201,10 @@ def wifi_authorize(auth_data, conn): 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') + # TODO: feed cert here return (radiusd.RLM_MODULE_UPDATED, (), ( @@ -121,7 +222,7 @@ def post_auth(auth_data, conn): reason = '' identity = "" #TODO prise = "" #TODO - items = get_machines(auth_data, conn) + items = get_machines(auth_data) decision = 'adherent','' if not items: @@ -133,7 +234,6 @@ def post_auth(auth_data, conn): if isinstance(machine, lc_ldap.objets.machineWifi): decision = 'wifi', '' - print machine['macAddress'][0].value if not machine['ipHostNumber'] or unicode(machine['macAddress'][0]) in test_v6: decision = 'v6only', 'No IPv4' elif machine['ipHostNumber'][0].value in netaddr.IPNetwork('10.2.9.0/24'): @@ -142,14 +242,14 @@ def post_auth(auth_data, conn): for bl in machine.blacklist_actif(): if bl.value['type'] in bl_isolement: - decision = 'isolement', unicode(bl).encode('utf-8') + decision = 'isolement', unicode(bl) if bl.value['type'] in bl_accueil: - decision = 'accueil', unicode(bl).encode('utf-8') + decision = 'accueil', unicode(bl) vlan_name, reason = decision vlan = vlans[vlan_name] radiusd.radlog(radiusd.L_INFO, 'auth.py: %s -> %s [%s%s]' % - (prise, identity, vlan_name, (reason and ': ' + reason)) + (prise, identity, vlan_name, (reason and u': ' + reason).encode(u'utf-8')) ) # @@ -169,7 +269,10 @@ def post_auth(auth_data, conn): # return (0, "Hébergeur non à jour", "accueil") # # + # Pour l'instant, on ne met pas d'infos de vlans dans la réponse + return radiusd.RLM_MODULE_OK + # This is dead code (for now) return (radiusd.RLM_MODULE_UPDATED, ( ("Tunnel-Type", "VLAN"), @@ -182,14 +285,6 @@ def post_auth(auth_data, conn): def dummy_fun(p): return radiusd.RLM_MODULE_OK -recv_coa = dummy_fun -send_coa = dummy_fun -preacct = dummy_fun -accounting = dummy_fun -pre_proxy = dummy_fun -post_proxy = dummy_fun - - def detach(p=None): """Appelé lors du déchargement du module (enfin, normalement)""" print "*** goodbye from example.py ***" diff --git a/freeradius/rlm_python_wifi.conf b/freeradius/rlm_python_wifi.conf index 297cf776..a8b73e2b 100644 --- a/freeradius/rlm_python_wifi.conf +++ b/freeradius/rlm_python_wifi.conf @@ -11,25 +11,27 @@ python crans_wifi { func_authorize = wifi_authorize # Renseigne le vlan + # remplacer par dummy_fun pour ignorer le tagging de vlan mod_post_auth = freeradius.auth func_post_auth = post_auth - # Le reste est dumb et inutile - mod_accounting = freeradius.auth - func_accounting = accounting - - mod_pre_proxy = freeradius.auth - func_pre_proxy = pre_proxy - - mod_post_proxy = freeradius.auth - func_post_proxy = post_proxy - - mod_recv_coa = freeradius.auth - func_recv_coa = recv_coa - - mod_send_coa = freeradius.auth - func_send_coa = send_coa - + # 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 } diff --git a/freeradius/test.py b/freeradius/test.py index 1cc5472f..4d5e7365 100755 --- a/freeradius/test.py +++ b/freeradius/test.py @@ -5,12 +5,21 @@ import auth import sys import time -if len(sys.argv) < 2: - print "Give me a mac !" - sys.exit(1) +delattr(sys, 'argv') + +#if len(sys.argv) < 2 and False: +# print "Give me a mac !" +# sys.exit(1) # Machine à s'authentifier (cerveaulent) -p=(('Calling-Station-Id', sys.argv[1]),) +#p=(('Calling-Station-Id', sys.argv[1]),) + +auth.instantiate(()) + +p=( + ('Calling-Station-Id', 'ba:27:eb:3c:54:d5'), + ('User-Name', 'test18'), +) print repr(auth.wifi_authorize(p)) print "wait for 3s, tu peux aller crasher le serveur pg ou ldap" diff --git a/gestion/affich_tools.py b/gestion/affich_tools.py index 2682e57d..d2652395 100755 --- a/gestion/affich_tools.py +++ b/gestion/affich_tools.py @@ -74,7 +74,7 @@ def dialog(backtitle,arg,dialogrc='') : elif not result : result=[''] return [ 0, result ] -def coul(txt, col=None): +def coul(txt, col=None, dialog=False): """ Retourne la chaine donnée encadrée des séquences qui vont bien pour obtenir la couleur souhaitée @@ -92,6 +92,21 @@ def coul(txt, col=None): 'cyan': 36, 'gris': 30, 'gras': 50 } + codecol_dialog = { + 'rouge': 1, + 'vert': 2, + 'jaune': 3, + 'bleu': 4, + 'violet': 5, + 'cyan': 6, + 'gris': 0, + 'gras': 'b' } + + if dialog: + try: + txt = "\Z%s%s\Zn" % (codecol_dialog[col], txt) + finally: + return txt try: if col[:2] == 'f_': add = 10 @@ -128,7 +143,7 @@ def cprint(txt, col='blanc', newline=True): else: print t, -def tableau(data, titre=None, largeur=None, alignement=None, format=None): +def tableau(data, titre=None, largeur=None, alignement=None, format=None, dialog=False): """ Retourne une chaine formatée repésentant un tableau. @@ -183,9 +198,11 @@ def tableau(data, titre=None, largeur=None, alignement=None, format=None): # Largeurs ########## if not largeur : - largeur = [ max([len(re.sub('\x1b\[1;([0-9]|[0-9][0-9])m','',ligne[i])) for ligne in data]) for i in range(nbcols) ] + largeur = [ max([len(re.sub('\\\Z.' if dialog else '\x1b\[1;([0-9]|[0-9][0-9])m','',ligne[i])) for ligne in data]) for i in range(nbcols) ] elif '*' in largeur: rows, cols = get_screen_size() + if dialog: + cols = cols - 6 for i in range(nbcols) : if largeur[i] in ['*',-1] : largeur[i] = max(cols - sum([l for l in largeur if l != '*']) - nbcols - 1, 3) @@ -198,12 +215,12 @@ def tableau(data, titre=None, largeur=None, alignement=None, format=None): def aligne (data, alignement, largeur) : # Longeur sans les chaines de formatage - l = len(re.sub('\x1b\[1;([0-9]|[0-9][0-9])m','',data)) + l = len(re.sub('\\\Z.' if dialog else '\x1b\[1;([0-9]|[0-9][0-9])m','',data)) # Alignement if l > largeur : # découpage d'une chaine trop longue - regexp = re.compile('\x1b\[1;([0-9]|[0-9][0-9])m') + regexp = re.compile('\\\Z.' if dialog else '\x1b\[1;([0-9]|[0-9][0-9])m') new_data = u'' new_len = 0 diff --git a/gestion/chambres_vides.py b/gestion/chambres_vides.py index 3b49dd57..1d568e3a 100755 --- a/gestion/chambres_vides.py +++ b/gestion/chambres_vides.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/bin/bash /usr/scripts/python.sh # -*- encoding: utf-8 -*- """ Pour détecter les gens en chambre invalide, les prévenir, et supprimer leurs machines @@ -12,8 +12,8 @@ import datetime import time import re -import ldap_crans -conn = ldap_crans.CransLdap() +import lc_ldap.shortcuts +conn = lc_ldap.shortcuts.lc_ldap_admin() import mail as mail_module import sys @@ -39,19 +39,20 @@ delai = config.demenagement_delai # On récupère ceux qui n'ont pas payé cette année if config.periode_transitoire: - bad_boys_e_s = conn.search('chbre=????&paiement=%d&paiement!=%d' % (year-1,year))['adherent'] + bad_boys_e_s = conn.search(u'(&(aid)*)(chbre=????)(paiement=%d)(!(paiement=%d)))' % (year-1,year)) else: - bad_boys_e_s = conn.search('chbre=????&paiement=%d' % year)['adherent'] + bad_boys_e_s = conn.search(u'(&(aid=*)(chbre=????)(paiement=%d))' % year) now = time.time() to_print = [] +to_error = [] for clandestin in bad_boys_e_s: # On cherche la dernière fois qu'il s'est retrouvé en chambre ???? - for l in clandestin.historique(): + for l in clandestin['historique'][::-1]: # On récupère la date du dernier changement de chambre # (l'historique est enregistré par ordre chronologique) - x = re.match("(.*),.* : chbre \((.*) -> \?\?\?\?\)",l) + x = re.match("(.*),.* : chbre \((.*) -> \?\?\?\?\)", str(l)) if x <> None: kickout_date = x.group(1) exchambre = x.group(2) @@ -64,9 +65,7 @@ for clandestin in bad_boys_e_s: if ttl > 0: if (sendmails and machine_liste != [] or DEBUG) and (ttl >= (delai - 1)*86400 or 0 < ttl <= 86400): # On lui envoie un mail pour le prévenir - to = clandestin.mail() - if not "@" in to: - to += "@crans.org" + to = clandestin['mail'][0] mail = mail_module.generate('demenagement', {"from" : "respbats@crans.org", "chambre" : exchambre, "jours" : int(ttl/86400) + 1, @@ -81,16 +80,19 @@ for clandestin in bad_boys_e_s: else: for m in machine_liste: - to_print.append( (clandestin.id(), m.ip(), m.id(), m.nom()) ) - m2 = conn.search('mid=%s' % m.id(),mode='w')['machine'][0] - m2.delete('Adherent sans chambre valide depuis %d jours' % delai) + try: + m2 = conn.search(u'mid=%s' % m['mid'][0],mode='w')[0] + m2.delete('Adherent sans chambre valide depuis %d jours' % delai) + to_print.append( (clandestin['aid'][0], m['ipHostNumber'][0], m['mid'][0], m['host'][0]) ) + except Exception as e: + to_error.append((clandestin['aid'][0], m['ipHostNumber'][0], m['mid'][0], m['host'][0], e)) +message = u"" if to_print != []: # Il s'est passé quelque chose, donc on envoie un mail # On regarde le plus grand hostname - hostnamemaxsize = max([len(i[3]) for i in to_print]) + hostnamemaxsize = max([len(str(i[3])) for i in to_print]) template = u"| %%4s | %%-15s | %%4s | %%-%ss |\n" % (hostnamemaxsize) - message = u"" message += u"\nListe des machines supprimées pour chambre invalide depuis plus de %s jours :\n" % delai tiret_line = u"+------+-----------------+------+-%s-+\n" % ("-" * hostnamemaxsize) message += tiret_line @@ -98,8 +100,24 @@ if to_print != []: message += tiret_line for aid, ip, mid, hostname in to_print: message += template % (aid, ip, mid, hostname) + message += tiret_line message += u"\nScore de cette nuit : %s" % (len(to_print)) + +if to_error != []: + hostnamemaxsize = max([len(str(i[3])) for i in to_error]) + errormaxsize = max([len(str(i[4])) for i in to_error]) + template = u"| %%4s | %%-15s | %%4s | %%-%ss | %%-%ss |\n" % (hostnamemaxsize, errormaxsize) + message += u"\n" + tiret_line = u"+------+-----------------+------+-%s-+-%s-+\n" % ("-" * hostnamemaxsize, "-" * errormaxsize) + message += u"\nListe des machines dont la supression à échoué :\n" + message += tiret_line + message += template % ("aid", " ip", "mid", (" " * (max((hostnamemaxsize-8)/2,0)) + "hostname"), (" " * (max((errormaxsize-6)/2,0)) + "erreur")) + for aid, ip, mid, hostname, error in to_error: + message += template % (aid, ip, mid, hostname, error) + message += tiret_line + +if to_print != [] or to_error != []: headers = u"From: respbats@crans.org\nSubject: %s\n" % Header("Machines supprimées pour chambre invalide", "utf8").encode() headers += u"Content-Type: text/plain; charset=UTF-8\n" headers += u"X-Mailer: /usr/scripts/gestion/chambres_vides.py\n" diff --git a/gestion/config/config_srv.py b/gestion/config/config_srv.py index 7b65dc1d..ba560fc9 100644 --- a/gestion/config/config_srv.py +++ b/gestion/config/config_srv.py @@ -5,4 +5,4 @@ adm_only = [] -role = {'zamok': ['adherents-server'], 'nat64': ['routeur-nat64'], 'komaz': ['wifi-router', 'appt-proxy', 'main-router'], 'dyson': ['sniffer'], 'isc': ['appt-proxy'], 'dhcp': ['appt-proxy'], 'ovh': ['externe'], 'routeur': ['appt-proxy']} +role = {'zamok': ['adherents-server'], 'nat64': ['routeur-nat64'], 'komaz': ['wifi-router', 'appt-proxy', 'main-router'], 'dyson': ['sniffer'], 'isc': ['appt-proxy'], 'dhcp': ['appt-proxy'], 'ovh': ['externe'], 'soyouz': ['externe'], 'routeur': ['appt-proxy']} diff --git a/gestion/config/dns.py b/gestion/config/dns.py index e92de589..8382d1ad 100644 --- a/gestion/config/dns.py +++ b/gestion/config/dns.py @@ -30,8 +30,8 @@ zone_tv = 'tv.crans.org' #: DNS en connexion de secours secours_relay='10.231.136.14'; -#: Serveurs authoritaires pour les zones crans, le master doit être le premier -DNSs = ['sable.crans.org', 'freebox.crans.org', 'ovh.crans.org'] +#: Serveurs autoritaires pour les zones crans, le master doit être le premier +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', 'ferme.crans.org' , 'clubs.ens-cachan.fr', 'adm.crans.org','crans.eu','wifi.crans.eu', 'tv.crans.org', 'ap.crans.org' ] diff --git a/gestion/config/services.py b/gestion/config/services.py new file mode 120000 index 00000000..d53deb22 --- /dev/null +++ b/gestion/config/services.py @@ -0,0 +1 @@ +/etc/crans/services.py \ No newline at end of file diff --git a/gestion/gen_confs/bind.py b/gestion/gen_confs/bind.py index 4335ba72..062b90a1 100755 --- a/gestion/gen_confs/bind.py +++ b/gestion/gen_confs/bind.py @@ -51,17 +51,18 @@ class ResourceRecord(object): return str(self) class TLSA(ResourceRecord): - def __init__(self, name, port, proto, cert, certtype, reftype, compat=True, ttl=None): + 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 pem (selector est donc toujours à 0) + cert: le certificat au format ``format`` (pem ou der) (selector est donc toujours à 0) certtype: type d'enregistrement 0 = CA pinning, 1 = cert pinning, 2 = self trusted CA, 3 = self trusted cert 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 """ - selector = 0 + if not format in ['pem', 'der']: + raise ValueError("format should be pem or der") 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') @@ -69,10 +70,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") - dercert = ssl.PEM_cert_to_DER_cert(cert) + 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, dercert) + certhex = TLSA.hashCert(reftype, str(dercert)) if compat: super(TLSA, self).__init__( 'TYPE52', @@ -156,7 +160,7 @@ class ZoneBase(object): def __init__(self, zone_name): self._rrlist=[] self.zone_name = zone_name - + def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.zone_name) @@ -194,8 +198,8 @@ class ZoneClone(ZoneBase): self.add(rr) if rr._name in ["%s." % self.zone_clone.zone_name]: self.add(ResourceRecord(rr._type, "%s." % self.zone_name, rr._value)) - - + + class Zone(ZoneBase): def __init__(self, zone_name, ttl, soa, ns_list, ipv6=True, ipv4=True, other_zones=[]): super(Zone, self).__init__(zone_name) @@ -254,17 +258,32 @@ class Zone(ZoneBase): def add_sshfp_record(self, nom, machine): for sshkey in machine.get('sshFingerprint', []): - algo_txt, key = str(sshkey).split()[:2] - 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: - if nom == '@': - self.add(SSHFP("v4", hash, algo, key)) - self.add(SSHFP("v6", hash, algo, key)) - else: - self.add(SSHFP("%s.v4" % nom, hash, algo, key)) - self.add(SSHFP("%s.v6" % nom, hash, algo, key)) + try: + algo_txt, key = str(sshkey).split()[:2] + 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: + if nom == '@': + self.add(SSHFP("v4", hash, algo, key)) + self.add(SSHFP("v6", hash, algo, key)) + else: + self.add(SSHFP("%s.v4" % nom, hash, algo, key)) + self.add(SSHFP("%s.v6" % nom, 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): + if 'TLSACert' in cert['objectClass']: + 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): for host in machine['host']: @@ -274,6 +293,8 @@ class Zone(ZoneBase): self.add_a_record(nom, machine) self.add_aaaa_record(nom, machine) self.add_sshfp_record(nom, machine) + for cert in machine.certificats(): + self.add_tlsa_record(cert) if machine['host']: @@ -286,7 +307,7 @@ class Zone(ZoneBase): if alias in ['@', '%s.' % self.zone_name]: self.add_a_record(alias, machine) self.add_aaaa_record(alias, machine) - self.add_sshfp_record(alias, machine) + self.add_sshfp_record(alias, machine) elif to_zone == self.zone_name: self.add(CNAME(alias, "%s" % to_nom)) if self.ipv4 and self.ipv6: @@ -294,7 +315,7 @@ class Zone(ZoneBase): self.add(CNAME("%s.v6" % alias, "%s.v6" % to_nom)) else: self.add(CNAME(alias, "%s." % machine['host'][0])) - + class ZoneReverse(Zone): def __init__(self, net, ttl, soa, ns_list): @@ -316,7 +337,7 @@ class ZoneReverse(Zone): def reverse(net, ip=None): """Renvoie la zone DNS inverse correspondant au réseau et à l'adresse donnés, ainsi que le nombre d'éléments de l'ip a - mettre dans le fichier de zone si elle est fournie, n'importe + mettre dans le fichier de zone si elle est fournie, n'importe quoi sinon.""" n = netaddr.IPNetwork(net) a = netaddr.IPAddress(ip if ip else n.ip) @@ -411,7 +432,7 @@ class dns(gen_config) : # format : [ priorité serveur , .... ] MXs = [ MX('@',10, 'redisdead.crans.org.'), - MX('@',20, 'ovh.crans.org.'), + MX('@',15, 'soyouz.crans.org.'), MX('@',25, 'freebox.crans.org.'), ] SRVs = { @@ -424,7 +445,7 @@ class dns(gen_config) : SRV('sips', 'tcp', 5, 0, 5061, 'asterisk'), ] } - NATPRs = { + 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), @@ -437,10 +458,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','565 8 2 498f6cd5bcf291aae4129700a7569fa6e9a86821185bd655f0b9efc6a3bf547e'), - DS('ferme','35156 8 2 b63a1443b3d7434429e879e046bc8ba89056cdcb4b9c3566853e64fd521895b8'), - DS('wifi','41320 8 2 024799c1d53f1e827f03d17bc96709b85ee1c05d77eb0ebeadcfbe207ee776a4'), - DS('tv','30910 8 2 3317f684081867ab94402804fbb3cd187e29655cc7f34cb92c938183fe0b71f5'), + DS('adm', '64649 8 2 9c45f0fef063672d96c983d5a3813a08a649c72d357f41ddece73ae8872d60cf'), + DS('ferme', '57424 8 2 2c21ec2a80a9ceb93fe085409ebdbab8d39145c18dc8ea8b23e9a38b5c414eb4'), + DS('wifi', '5531 8 2 daf30a647566234edc1617546fd74abbbaf965b17389248f72fc66a33d6f5063'), + DS('tv', '18199 8 2 d3cc2f5f81b830cbb8894ffd32c236e968edd3b0c0305112b6eb970aa763418e'), ], } @@ -473,6 +494,7 @@ class dns(gen_config) : TLSA('nagios.crans.org.', 443, 'tcp', None, 3, 2), TLSA('pad.crans.org.', 443, 'tcp', None, 3, 2), TLSA('news.crans.org.', 443, 'tcp', None, 3, 2), + TLSA('lists.crans.org.', 443, 'tcp', None, 3, 2), TLSA('asterisk.crans.org.', 5061, 'tcp', None, 3, 2), TLSA('smtp.crans.org.', 465, 'tcp', None, 3, 2), TLSA('imap.crans.org.', 993, 'tcp', None, 3, 2), diff --git a/gestion/gen_confs/dhcpd_new.py b/gestion/gen_confs/dhcpd_new.py index 9c24f367..b86b1479 100644 --- a/gestion/gen_confs/dhcpd_new.py +++ b/gestion/gen_confs/dhcpd_new.py @@ -36,6 +36,8 @@ class dydhcp: @raises OmapiError: @raises socket.error: """ + if '' in [ip, mac]: + return msg = OmapiMessage.open(b"host") msg.message.append((b"create", struct.pack("!I", 1))) msg.message.append((b"exclusive", struct.pack("!I", 1))) @@ -56,6 +58,8 @@ class dydhcp: @raises OmapiError: @raises socket.error: """ + if '' in [ip, mac]: + return msg = OmapiMessage.open(b"host") msg.obj.append((b"hardware-address", pack_mac(mac))) msg.obj.append((b"hardware-type", struct.pack("!I", 1))) @@ -147,7 +151,8 @@ class dhcp(gen_config) : for machine in self.machines : self.anim.cycle() for net in self.reseaux.keys() : - if machine.ip() != '' and AddrInNet(machine.ip(), net) : + if '' not in [machine.ip(), machine.mac()] and \ + AddrInNet(machine.ip(), net): host_template = self.host_template # variable pour remplir le template #d = { 'nom' : machine.nom().split('.')[0] , 'mac' : machine.mac() , 'ip' : machine.ip() } diff --git a/gestion/gen_confs/firewall4/base.py b/gestion/gen_confs/firewall4/base.py index d98f5f54..1844378c 100644 --- a/gestion/gen_confs/firewall4/base.py +++ b/gestion/gen_confs/firewall4/base.py @@ -3,7 +3,6 @@ import os import sys import socket -import netaddr import utils from utils import pretty_print, anim, OK, cprint @@ -25,7 +24,7 @@ class firewall(utils.firewall_tools) : def __init__(self): super(firewall, self).__init__() - + self.reloadable = { 'blacklist_hard' : self.blacklist_hard, 'test_mac_ip' : self.test_mac_ip, @@ -96,7 +95,7 @@ class firewall(utils.firewall_tools) : # for ip in ip_list: # machine = self.conn.search(u"ipHostNumber=%s" % ip) # # Est-ce qu'il y a des blacklists hard parmis les blacklists de la machine -# if machine and set([bl.value['type'] for bl in machine[0].blacklist_actif() ]).intersection(config.blacklist_sanctions): +# if machine and set([bl['type'] for bl in machine[0].blacklist_actif() ]).intersection(config.blacklist_sanctions): # try: self.ipset['blacklist']['hard'].add(ip) # except IpsetError: pass # else: @@ -110,17 +109,9 @@ class firewall(utils.firewall_tools) : chain = 'BLACKLIST_HARD' if fill_ipset: - anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['hard']) # On récupère la liste de toutes les ips blacklistés hard - bl_hard_ips = set( - str(ip) for ips in - [ - machine['ipHostNumber'] for machine in self.blacklisted_machines() if machine['ipHostNumber'] and reduce(lambda x,y: x or y, ( ip.value in netaddr.IPNetwork(n) for n in config.NETs['all'] for ip in machine['ipHostNumber'])) - if set([bl.value['type'] for bl in machine.blacklist_actif() ]).intersection(config.blacklist_sanctions) - ] - for ip in ips - ) - + 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 @@ -137,6 +128,8 @@ class firewall(utils.firewall_tools) : def test_mac_ip_dispatch(self, func, machine): """Détermine à quel set de mac-ip appliquer la fonction ``func`` (add, delete, append, ...)""" ips = machine['ipHostNumber'] + if '' in machine['macAddress'] : + return for ip in ips: # Si la machines est sur le réseau des adhérents if utils.AddrInNet(str(ip), config.NETs['wifi']): @@ -182,6 +175,11 @@ class firewall(utils.firewall_tools) : self.add(table, chain, '-m mac -s %s --mac-source %s -j RETURN' % (ip_ovh, config.mac_komaz)) self.add(table, chain, '-m mac -s %s --mac-source %s -j RETURN' % (ip_ovh, config.mac_titanic)) + # 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_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') print OK @@ -210,6 +208,8 @@ class firewall_routeur(firewall): def test_mac_ip_dispatch(self, func, machine): """Détermine à quel set de mac-ip appliquer la fonction func (add, delete, append, ...)""" ips = machine['ipHostNumber'] + if '' in machine['macAddress'] : + return for ip in ips: # Si la machines est sur le réseau des adhérents if utils.AddrInNet(str(ip), config.NETs['wifi']): @@ -228,6 +228,8 @@ class firewall_wifionly(firewall): def test_mac_ip_dispatch(self, func, machine): """Détermine à quel set de mac-ip appliquer la fonction func (add, delete, append, ...)""" ips = machine['ipHostNumber'] + if '' in machine['macAddress'] : + return for ip in ips: # Si la machines est sur le réseau des adhérents if utils.AddrInNet(str(ip), config.NETs['wifi']): diff --git a/gestion/gen_confs/firewall4/komaz.py b/gestion/gen_confs/firewall4/komaz.py index e1104e3d..184d6d41 100644 --- a/gestion/gen_confs/firewall4/komaz.py +++ b/gestion/gen_confs/firewall4/komaz.py @@ -298,7 +298,7 @@ class firewall(base.firewall_routeur): # for ip in ip_list: # machine = self.conn.search(u"ipHostNumber=%s" % ip) # # Est-ce qu'il y a des blacklists soft parmis les blacklists de la machine -# if machine and set([bl.value['type'] for bl in machine[0].blacklist_actif() ]).intersection(base.config.blacklist_sanctions_soft): +# if machine and set([bl['type'] for bl in machine[0].blacklist_actif() ]).intersection(base.config.blacklist_sanctions_soft): # try: self.ipset['blacklist']['soft'].add(ip) # except IpsetError: pass # else: @@ -310,17 +310,9 @@ class firewall(base.firewall_routeur): chain = 'BLACKLIST_SOFT' if fill_ipset: - anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['soft']) # On récupère la liste de toutes les ips blacklistés soft - bl_soft_ips = set( - str(ip) for ips in - [ - machine['ipHostNumber'] for machine in self.blacklisted_machines() if machine['ipHostNumber'] and reduce(lambda x,y: x or y, ( ip.value in base.netaddr.IPNetwork(n) for n in base.config.NETs['all'] for ip in machine['ipHostNumber'])) - if set([bl.value['type'] for bl in machine.blacklist_actif() ]).intersection(base.config.blacklist_sanctions_soft) - ] - for ip in ips - ) - + 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 @@ -348,7 +340,7 @@ class firewall(base.firewall_routeur): # for ip in ip_list: # machine = self.conn.search(u"ipHostNumber=%s" % ip) # # Est-ce qu'il y a des blacklists pour upload parmis les blacklists de la machine -# if machine and set([bl.value['type'] for bl in machine[0].blacklist_actif() ]).intersection(blacklist_bridage_upload): +# if machine and set([bl['type'] for bl in machine[0].blacklist_actif() ]).intersection(blacklist_bridage_upload): # try: self.ipset['blacklist']['upload'].add(ip) # except IpsetError: pass # else: @@ -360,17 +352,9 @@ class firewall(base.firewall_routeur): chain = 'BLACKLIST_UPLOAD' if fill_ipset: - anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['upload']) # On récupère la liste de toutes les ips blacklistés pour upload - bl_upload_ips = set( - str(ip) for ips in - [ - machine['ipHostNumber'] for machine in self.blacklisted_machines() - if set([bl.value['type'] for bl in machine.blacklist_actif() ]).intersection(base.config.blacklist_bridage_upload) - ] - for ip in ips - ) - + 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 @@ -446,13 +430,13 @@ class firewall(base.firewall_routeur): for machine in self.machines(): for ip in machine['ipHostNumber']: - if 'portTCPout' in machine.attrs.keys(): + if 'portTCPout' in machine: add_ports(ip, machine, 'tcp', 'out') - if 'portUDPout' in machine.attrs.keys(): + if 'portUDPout' in machine: add_ports(ip, machine, 'udp', 'out') - if 'portTCPin' in machine.attrs.keys(): + if 'portTCPin' in machine: add_ports(ip, machine, 'tcp', 'in') - if 'portUDPin' in machine.attrs.keys(): + if 'portUDPin' in machine: add_ports(ip, machine, 'udp', 'in') self.add(table, chain, '-j REJECT') diff --git a/gestion/gen_confs/firewall4/utils.py b/gestion/gen_confs/firewall4/utils.py index a2f84514..b0de0c2f 100644 --- a/gestion/gen_confs/firewall4/utils.py +++ b/gestion/gen_confs/firewall4/utils.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- import os import sys +import netaddr if '/usr/scripts/' not in sys.path: sys.path.append('/usr/scripts/') @@ -58,11 +59,21 @@ class firewall_tools(object) : """Renvois la liste de toutes les machines""" if self._machines: return self._machines + # On utilise allMachinesAdherents car on a besoin que + # les machine.proprio() soit déjà peuplés. En effet, on regarde + # les blacklistes d'un proprio lorsque l'on regarde les blacklistes + # d'une machine + anim('\tChargement des machines') 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""" + """ + 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() @@ -76,8 +87,22 @@ class firewall_tools(object) : self._blacklisted_machines = [ machine for machine in self.machines() if machine.blacklist_actif() ] return self._blacklisted_machines + 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']: + 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=[]): - """Renvois la liste de tous les adhérents ayant une blackliste active""" + """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()) @@ -218,10 +243,7 @@ class firewall_tools(object) : def start(self): """Démarre le pare-feu : génère les règles, puis les restore""" - anim('\tChargement des machines') self.machines() - self.blacklisted_machines() - print OK if squeeze: anim('\tVidage du pare-feu') diff --git a/gestion/gen_confs/firewall4/zamok.py b/gestion/gen_confs/firewall4/zamok.py index 1413602e..e3f0796d 100644 --- a/gestion/gen_confs/firewall4/zamok.py +++ b/gestion/gen_confs/firewall4/zamok.py @@ -102,8 +102,8 @@ 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(['paiement']): - if 'uidNumber' in adh.attrs.keys(): + 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 diff --git a/gestion/gen_confs/generate.py b/gestion/gen_confs/generate.py index 033614bd..5ae9bac7 100755 --- a/gestion/gen_confs/generate.py +++ b/gestion/gen_confs/generate.py @@ -15,7 +15,7 @@ arguments peuvent être founis pour une même option, les séparer par & """ import sys, signal, os, getopt - +from trigger import trigger_generate as trigger sys.path.append('/usr/scripts/gestion') from ldap_crans import crans_ldap, hostname @@ -28,8 +28,6 @@ from syslog import * import platform openlog("generate") -signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C - db = crans_ldap() make_lock('auto_generate', 'Big lock', nowait=1) @@ -205,15 +203,6 @@ class owl(base_reconfigure): from adherents import del_user self._do(del_user(args)) -class pgsql(base_reconfigure): - def surveillance_exemptions(self): - from gen_confs.surveillance import exemptions - self._do(exemptions()) - - def surveillance_machines(self): - from gen_confs.surveillance import machines - self._do(machines(), self._machines()) - class thot(base_reconfigure): def surveillance_exemptions(self): from gen_confs.surveillance import exemptions @@ -304,10 +293,10 @@ class gordon(base_reconfigure) : class titanic(base_reconfigure): pass -signal.signal(signal.SIGINT, signal.SIG_DFL) # Comportement normal de Ctrl-C remove_lock('auto_generate') if __name__ == '__main__': + signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C openlog('generate', LOG_PID) for x in db.services_to_restart(): try: @@ -362,15 +351,17 @@ if __name__ == '__main__': print 'Recherche des personnes en fin de sanction...' c = db.search('blacklist=*') services = [] + nom = time() hier = time() - 24*3600 + demain = time() + 24*3600 for a_reco in c['adherent'] + c['machine'] + c['club']: for bl in a_reco.blacklist(): fin, sanction = bl.split('$')[1:3] - if fin > hier and sanction not in services: - services.append(sanction) - for s in services: + if fin != '-' and fin <= demain and fin >= now and (sanction, fin) not in services: + services.append((sanction, fin)) + for s,f in services: print "Ajout de blacklist_%s pour reconfiguration" % s - db.services_to_restart('blacklist_%s' % s) + db.services_to_restart('blacklist_%s' % s, start=f) sys.exit(0) elif opt == '--add': @@ -397,3 +388,4 @@ if __name__ == '__main__': # On fait ce qu'il y a à faire classe(to_do) + signal.signal(signal.SIGINT, signal.SIG_DFL) # Comportement normal de Ctrl-C diff --git a/gestion/gen_confs/ipset.py b/gestion/gen_confs/ipset.py index 128db479..5a27b0cc 100644 --- a/gestion/gen_confs/ipset.py +++ b/gestion/gen_confs/ipset.py @@ -19,11 +19,8 @@ import sys sys.path.append('/usr/scripts/gestion') import commands -import lock import os - - class IpsetError(Exception): # Gestion des erreurs d'ipset def __init__(self,cmd,err_code,output): diff --git a/gestion/gen_confs/populate_sshFingerprint.py b/gestion/gen_confs/populate_sshFingerprint.py index ac146504..b3fb7042 100755 --- a/gestion/gen_confs/populate_sshFingerprint.py +++ b/gestion/gen_confs/populate_sshFingerprint.py @@ -57,10 +57,11 @@ def ssh_keygen(algo,size): print("Nouvelle clef %s générée" % key_path) def get_machines(): - machines=[] + filter="" for ip in set(ip4_addresses()): - machines.extend(conn.search(u'ipHostNumber=%s' % ip, mode='rw')) - return machines + filter+=u'(ipHostNumber=%s)' % ip + filter = u"(|%s)" % filter + return conn.search(filter, mode='rw') def check_keys_age(key_path,algo): age=time.time()-os.path.getmtime(key_path) @@ -74,22 +75,25 @@ def get_local_keys(): key_path='/etc/ssh/ssh_host_%s_key.pub' % algo if os.path.isfile(key_path): check_keys_age(key_path,algo) - keys[algo]=open(key_path).read() + keys[algo]=unicode(open(key_path).read().strip()) return keys def check_keys(keys): return dict([ (algo,key.split()[1] == ssh_keyscan('localhost',algo)) for algo,key in keys.items() ]) - -def publish_keys(): + +def valid_keys(): keys=get_local_keys() validation=check_keys(keys) - machines=get_machines() - for machine in machines: - sshkeys_old=[key.value for key in machine.get('sshFingerprint',[])] - sshkeys_new=[key.decode('UTF-8') for algo,key in keys.items() if validation[algo]] - if not set(sshkeys_old)==set(sshkeys_new): - machine['sshFingerprint']=sshkeys_new - machine.save() + return [key for algo,key in keys.items() if validation[algo]] + +def publish_keys(): + keys=valid_keys() + for machine in get_machines(): + with machine: + if machine['sshFingerprint'] != keys: + print "Key list changed" + machine['sshFingerprint'] = keys + machine.save() if __name__ == '__main__' : diff --git a/gestion/gen_confs/surveillance.py b/gestion/gen_confs/surveillance.py index 8f20498a..74ca7877 100644 --- a/gestion/gen_confs/surveillance.py +++ b/gestion/gen_confs/surveillance.py @@ -49,6 +49,9 @@ class exemptions(gen_config) : source = machine.ip() else: source = machine.ipv6() + # Si ip vide, passons au suivant + if not source: + continue requete="INSERT INTO exemptes (ip_crans,ip_dest) VALUES ('%s','%s')" % (source, destination) curseur.execute(requete) @@ -83,6 +86,10 @@ class machines(gen_config) : ipv6_vu={} def ipv6_already_set(ipv6): + # S'il ne s'agit pas d'une IP valide (vide ?) faisons comme si + # on l'avait déjà vue :p + if not ipv6: + return True ret = ipv6_vu.get(ipv6, False) ipv6_vu[ipv6] = True return ret diff --git a/gestion/gen_confs/trigger.py b/gestion/gen_confs/trigger.py new file mode 100755 index 00000000..8da9b110 --- /dev/null +++ b/gestion/gen_confs/trigger.py @@ -0,0 +1,37 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- + +import subprocess + +_options = ['PasswordAuthentication=no', 'ConnectTimeout=1', 'VerifyHostKeyDNS=yes', + 'BatchMode=yes', 'ServerAliveInterval=5', 'ServerAliveCountMax=1'] +_args = ["ssh", "-4", "-i", "/etc/crans/secrets/trigger-generate" ] + +def build_args(host): + if not 'adm.crans.org' in host: + host=host + '.adm.crans.org' + args = list(_args) + for opt in _options: + args.append('-o') + args.append(opt) + args.extend(["rpcssh@%s" % host, "generate"]) + return args + +def trigger_generate(host, background=False): + args = build_args(host) + if background: + subprocess.Popen(args) + else: + p=subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + if err: + raise Exception(err) + return out + +def trigger_generate_cochon(host): + """Ceci est une fonction crade qui permet de se débarraser du process enfant + que l'on aurait laissé en arrière plan""" + args = build_args(host) + p = subprocess.Popen(['/bin/bash'], + stdin=subprocess.PIPE, ) + p.communicate(' '.join(args) + ' &> /dev/null &') diff --git a/gestion/gest_crans.py b/gestion/gest_crans.py index 1427fa02..5883b98a 100755 --- a/gestion/gest_crans.py +++ b/gestion/gest_crans.py @@ -933,7 +933,7 @@ def __prompt_input_menu(method, titre, prompt): else: method([num-1, val]) - except ValueError, c: + except (EnvironmentError, ValueError) as c: arg = u'--title "%s" ' % titre arg += u'--msgbox "%s\n\n\n" 0 0' % to_unicode(c.args[0]) dialog(arg) @@ -1135,7 +1135,7 @@ def confirm(clas): return 1 try: res = clas.save() - except RuntimeError, c: + except (EnvironmentError, RuntimeError) as c: arg = u'--title "Enregistrement" ' arg += u'--msgbox "%s\n\n\n" 0 0' % to_unicode(c.args[0]) dialog(arg) diff --git a/gestion/gest_crans_lc.py b/gestion/gest_crans_lc.py new file mode 100755 index 00000000..0bf6bb84 --- /dev/null +++ b/gestion/gest_crans_lc.py @@ -0,0 +1,305 @@ +#!/bin/bash /usr/scripts/python.sh +# -*- coding: utf-8 -*- + +u""" +Interface utilisateur du système de gestion des machines +et adhérents du crans + +Copyright (C) Valentin Samir +Licence : GPLv3 + +""" +import os +import sys +import signal +from pythondialog import Dialog + +from gestion.affich_tools import get_screen_size, coul + +import lc_ldap.shortcuts +import lc_ldap.printing as printing + +def handle_exit_code(d, code): + if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): + if code == d.DIALOG_CANCEL: + msg = "Vous avez choisi Annuler dans la dernière fenêtre de dialogue.\n\n" \ + "Voulez vous quitter le programme ?" + os.system('clear') + sys.exit(0) + else: + msg = "Vous avez appuyer sur ESC 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') + sys.exit(0) + return False + else: + return True # code est d.DIALOG_OK + + + +def search(dialog, objectClassS, title, values={}): + """ + Rechercher des adhérents ou des machines dans la base ldap + retourne le tuple (code de retour dialog, valeurs entrée par l'utilisateur, liste d'objets trouvés) + La fonction est découpé en trois partie : + * affichage dialog et récupération du résultat + * construction de filtres de recherche ldap et recherches ldap + * filtre sur les résultats des recherches ldap + """ + select_dict = { + #label attribut ldap search for substring param dialog: line col valeur input-line icol len max-chars + 'Nom' : {'ldap':'nom', 'sub':True, 'params' : [ 1, 1, values.get('nom', ""), 1, 13, 20, 20]}, + 'Prenom' : {'ldap':'prenom', 'sub':True, 'params' : [ 2, 1, values.get('prenom', ""), 2, 13, 20, 20]}, + 'Téléphone' : {'ldap':'tel', 'sub':True, 'params' : [ 3, 1, values.get('tel', ""), 3, 13, 10, 00]}, + 'Chambre' : {'ldap':'chbre','sub':True, 'params' : [ 4, 1, values.get('chbre',""), 4, 13, 05, 00]}, + 'aid' : {'ldap' : 'aid', 'sub':False, 'params' : [ 5, 1, values.get('aid',""), 5, 13, 05, 05]}, + 'mail' : {'ldap' : 'mail', 'sub':True, 'params' : [ 6, 1, values.get('mail',""), 6, 13, 20, 00]}, + # seconde colone + 'Machine' : {'ldap' : '*', 'sub':True, 'params' : [1, 35, "", 1, 43, 0, 0]}, + 'Host' : {'ldap' : 'host', 'sub':True, 'params' : [2, 37, values.get('host',""), 2, 43, 17, 17]}, + 'Mac' : {'ldap' : 'macAddress', 'sub':False, 'params' : [3, 37, values.get('macAddress',""), 3, 43, 17, 17]}, + 'IP' : {'ldap' : 'ipHostNumber', 'sub':False,'params' : [4, 37, values.get('ipHostNumber',""), 4, 43, 15, 15]}, + 'mid' : {'ldap' : 'mid', 'sub':False, 'params' : [5, 37, values.get('mid',""), 5, 43, 5, 5]}, + } + # On a besoin de l'ordre pour récupérer les valeurs ensuite + select_adherent = ['Nom', 'Prenom', 'Téléphone', 'Chambre', 'aid', 'mail'] + select_machine = ['Host', 'Mac', 'IP', 'mid'] + def box(): + # On met les argument à dialog à la main ici, sinon, c'est difficile de choisir comment mettre une seconde colone + cmd = ["--form", "Entrez vos paramètres de recherche", '0', '0', '0'] + for key in select_adherent: + cmd.extend(['%s :' % key] + [str(e) for e in select_dict[key]['params']]) + cmd.extend(['Machine :'] + [str(e) for e in select_dict['Machine']['params']]) + for key in select_machine: + cmd.extend(['%s :' % key] + [str(e) for e in select_dict[key]['params']]) + cmd.extend(["Les champs vides sont ignorés.", '7', '1', "", '0', '0', '0', '0']) + # On utilise quand même la fonction de la bibliothèques pour passer les arguments + (code, output) = dialog._perform(*(cmd,), title=title, backtitle="Entrez vos paramètres de recherche") + if output: + return (code, output.split('\n')[:-1]) + else: # empty selection + return (code, []) + + (code, dialog_values) = box() + if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC): + return code, values, [] + else: + # Transformation de la liste des valeures entrée en dictionnnaire + dialog_values = dict(zip(select_adherent + select_machine, dialog_values)) + ldap_values = dict([(select_dict[key]['ldap'], value) for key, value in dialog_values.items()]) + + # Construction des filtres ldap pour les adhérents et les machines + filter_adherent = [] + filter_machine = [] + for (key, value) in dialog_values.items(): + if value: + if key in select_adherent: + filter_adherent.append((u"(%s=*%s*)" if select_dict[key]['sub'] else u"(%s=%s)") % (select_dict[key]['ldap'], unicode(value, 'utf-8'))) + elif key in select_machine: + filter_machine.append((u"(%s=*%s*)" if select_dict[key]['sub'] else u"(%s=%s)") % (select_dict[key]['ldap'], unicode(value, 'utf-8'))) + if filter_adherent: + filter_adherent=u"(&%s)" % "".join(filter_adherent) + if filter_machine: + filter_machine=u"(&%s)" % "".join(filter_machine) + + # Récupération des adhérents et des machines + adherents=conn.search(filter_adherent) if filter_adherent else [] + machines=conn.search(filter_machine) if filter_machine else [] + + # Filtrage des machines en fonction des adhérents + if filter_adherent: + if filter_machine: + # Si on filtre sur des adhérent et des machines, on calcule l'intersection + adherents_dn = set([a.dn for a in adherents]) + machines_f = [m for m in machines if m.parent_dn in adherents_dn] + else: + # Sinon on filtre seulement sur les adhérents, récupère les machines des adhérents trouvés + machines_f = [m for a in adherents for m in a.machines()] + else: + # Sinon si on filtre seulement sur des machines + machines_f = machines + + # Filtrage des adhérents en fonction des machines + if filter_machine: + if filter_adherent: + # Si on filtre sur des adhérents et des machines, on calcule l'intersection + machines_dn = set([m.parent_dn for m in machines]) + adherents_f = [a for a in adherents if a.dn in machines_dn] + else: + # Sinon on récupères les proprios des machines trouvées + adherents_f = [m.proprio() for m in machines] + else: + # Sinon si on filtre seulement sur des adhérents + adherents_f = adherents + + # On filtre sur les objectClassS + return code, ldap_values, [ o for objectClass in objectClassS for o in machines_f+adherents_f if objectClass in o['objectClass'] ] + +def select_one(dialog, items, default_item=None): + """Fait selectionner un item parmis une liste d'items à l'utisateur""" + ### TODO afficher correctement les objets dans items + choices=[] + count = 0 + default_tag = items.index(default_item) if default_item in items else default_item + for i in items: + choices.append((str(count), str(i), 1 if default_tag == count else 0)) + count+=1 + tag='' + while not tag: + (line, col) = get_screen_size() + (code, tag) = dialog.radiolist( + "Choisir", + choices=choices, + default_item=str(default_tag), + width=col-4, + height=line-3, + list_height=line-3, + scrollbar=True) + if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC): + return code, None + if not tag: + dialog.msgbox("Merci de choisir l'un des item de la liste ou d'annuler", title="Sélection", width=0, height=0) + return code, items[int(tag)] + +def select_confirm(dialog, item, title): + """Affiche un item et demande si c'est bien celui là que l'on veux (supprimer, éditer, créer,...)""" + return dialog.yesno(printing.sprint(item), no_collapse=True, colors=True, title=title, width=0, height=0, backtitle="Appuyez sur MAJ pour selectionner du texte") == dialog.DIALOG_OK + +def select(dialog, objectClassS, title, values={}): + """Permet de choisir un objet adhérent ou machine dans la base ldap""" + while True: + try: + # On fait effectuer une recherche à l'utilisateur + code, values, items = search(dialog, objectClassS, title, values) + # Si il a appuyé sur annuler ou sur escape, on annule + if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC): + return code, None + # S'il n'y a pas de résultas, on recommence + elif not items: + dialog.msgbox("Aucun Résultat", title="Recherche", width=0, height=0) + # S'il y a plusieurs résultats + elif len(items)>1: + item = None + while True: + # On en fait choisir un + code, item = select_one(dialog, items, item) + # Si il à appuyer sur annuler ou sur escape, on recommence la recherche + if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC): + break + # Sinon, on fait confirmer son choix à l'utilisateur + elif select_confirm(dialog, item, title): + return code, item + # S'il y a exactement 1 résultat à la recherche, on fait confirmer son choix à l'utilisateur + elif len(items) == 1 and select_confirm(dialog, items[0], title): + return code, items[0] + except Exception as e: + dialog.msgbox("%r" % e, title="Erreur rencontrée", width=0, height=0) + +def modif_adherent(dialog): + code, adherent = select(dialog, ["adherent"], "Recherche d'un adhérent") + if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC): + return code + dialog.msgbox("todo", width=0, height=0) + +def create_adherent(dialog): + dialog.msgbox("todo", width=0, height=0) + +def delete_adherent(dialog): + code, adherent = select(dialog, ["adherent"], "Recherche d'un adhérent") + if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC): + return code + dialog.msgbox("todo", width=0, height=0) + +def modif_machine(dialog): + code, machine = select(dialog, ["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine") + if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC): + return code + dialog.msgbox("todo", width=0, height=0) + +def create_machine_adherent(dialog): + dialog.msgbox("todo", width=0, height=0) + +def delete_machine(dialog): + code, machine = select(dialog, ["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine") + if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC): + return code + dialog.msgbox("todo", width=0, height=0) + + +def create_club(dialog): + dialog.msgbox("todo", width=0, height=0) + + +def delete_club(dialog): + dialog.msgbox("todo", width=0, height=0) + +def modif_club(dialog): + dialog.msgbox("todo", width=0, height=0) + +def create_machine_club(dialog): + dialog.msgbox("todo", width=0, height=0) + +def create_machine_crans(dialog): + dialog.msgbox("todo", width=0, height=0) + +def create_borne(dialog): + dialog.msgbox("todo", width=0, height=0) + +def menu_principal(dialog): + menu = { + 'aA' : {'text':"Inscrire un nouvel adhérent", 'callback': create_adherent}, + 'mA' : {'text':"Modifier l'inscription d'un adhérent", 'callback': modif_adherent, 'help':"Changer la chambre, la remarque, la section, la carte d'étudiant ou précâbler."}, + 'aMA': {'text':"Ajouter une machine à un adhérent", 'callback': create_machine_adherent}, + 'dA' : {'text':"Détruire un adhérent", 'callback': delete_adherent, 'help':"Suppression de l'adhérent ainsi que de ses machines"}, + 'mM' : {'text':"Modifier une machine existante", 'callback': modif_machine, 'help':"Changer le nom ou la MAC d'une machine."}, + 'dM' : {'text':"Détruire une machine", 'callback': delete_machine}, + 'aC' : {'text':"Inscrire un nouveau club", 'callback': create_club}, + 'mC' : {'text':"Modifier un club", 'callback': modif_club}, + 'aMC': {'text':"Ajouter une machine à un club", 'callback': create_machine_club}, + 'dC' : {'text':"Détruire un club", 'callback': delete_club}, + 'aKM': {'text':"Ajouter une machine à l'association", 'callback': create_machine_crans}, + 'aKB': {'text':"Ajouter une borne wifi", 'callback': create_borne}, + '' : {'text':"---------------------------------------",'callback': lambda x: x}, + } + ### Les clef qui n'existe pas sont toute renvoyé sur la clef '' + medu_order = ["aA", "mA", "aMA", "dA", "", "mM", "dM", " ", "aC", "mC", "aMC", "dC", " ", "aKM", "aKB"] + def box(default_item=None): + return dialog.menu( + "Que souhaitez vous faire ?", + width=55, + height=0, + menu_height=15, + item_help=1, + default_item=str(default_item), + title="Menu principal", + scrollbar=True, + cancel_label="Quitter", + backtitle=u"Vous êtes connecté en tant que %s" % conn.current_login, + choices=[(key, menu.get(key, menu[''])['text'], menu.get(key, menu['']).get('help', "")) for key in medu_order]) + + tag=None + while True: + (code, tag) = box(tag) + if handle_exit_code(dialog, code): + menu.get(tag, menu[''])['callback'](dialog) + + +if __name__ == '__main__': + signal.signal(signal.SIGINT, signal.SIG_IGN) + # On initialise le moteur de rendu en spécifiant qu'on va faire du dialog + printing.template(dialog=True) + + # On ouvre une connexion lc_ldap + conn = lc_ldap.shortcuts.lc_ldap_admin() + # On vérifie que l'utilisateur système existe dans ldap (pour la gestion des droits) + luser=conn.search(u"uid=%s" % conn.current_login) + if not luser: + sys.stderr.write("L'utilisateur %s n'existe pas dans la base de donnée" % conn.current_login) + sys.exit(1) + conn.droits = [str(d) for d in luser[0]['droits']] + conn.dn = luser[0].dn + + dialog = Dialog() + menu_principal(dialog) + os.system('clear') diff --git a/gestion/ip6tools.py b/gestion/ip6tools.py index 58575b9d..01c2db61 100644 --- a/gestion/ip6tools.py +++ b/gestion/ip6tools.py @@ -24,7 +24,11 @@ import netaddr def mac_to_ipv6(ipv6_prefix, mac_address): """Convert a MAC address (EUI48) to an IPv6 (prefix::EUI64).""" + if mac_address == '': + return '' + if type(mac_address) in [str, unicode]: + mac_address = netaddr.EUI(mac_address) addr = int(mac_address.bits(netaddr.mac_bare), 2) ip6addr = (((addr >> 24) ^ (1 << 17)) << 40) | (0xFFFE << 24) | (addr & 0xFFFFFF) n = netaddr.IPNetwork(ipv6_prefix) diff --git a/gestion/ipt.py b/gestion/ipt.py index 70ca4d7c..37c0a1c9 100644 --- a/gestion/ipt.py +++ b/gestion/ipt.py @@ -30,6 +30,7 @@ from iptools import AddrInNet from ridtools import Rid, find_rid_plage import subprocess import netaddr +import ip6tools blacklist_sanctions_ipv6 = list(blacklist_sanctions) blacklist_sanctions_ipv6.extend(blacklist_sanctions_soft) @@ -126,6 +127,8 @@ class Ip6tables(object): def macip(self, mac, type_m): '''Fait la correspondance MAC-IP''' + if '' == mac: + return tab = {'serveurs' : 'fil' } if type_m in tab.keys(): type_m = tab[type_m] type_mm = re.sub('-', '', type_m) @@ -141,6 +144,8 @@ class Ip6tables(object): 'wifi-adh-v6' : 'extwifiv6', 'serveurs':'extfil' } ip = ipv6_addr(mac, type_machine) + if not ip: + return for proto in ['tcp', 'udp']: for port in ports[proto]: if port != ':': @@ -157,6 +162,8 @@ ACCEPT' % (dev, proto, ip, port)) 'wifi-adh-v6' : 'cranswifiv6', 'serveurs':'cransfil' } ip = ipv6_addr(mac, type_machine) + if not ip: + return for proto in ['tcp', 'udp']: for port in ports[proto]: if port != ':': @@ -470,9 +477,7 @@ def check_ip_proto(ip_proto): def ipv6_addr(mac, net): ''' Renvoie l'adresse ipv6 d'auto-configuration de la mac sur le réseau ''' - mac_s = mac.split(':') - eui = hex(int(mac_s[0],16) ^ 0x02)[2:] + ':'.join(mac_s[1:3]) + 'ff:fe' + ':'.join(mac_s[3:5]) + mac_s[5] - return re.sub(':/64', eui , prefix[dprefix[net]][0]) + return ip6tools.mac_to_ipv6(prefix[dprefix[net]][0], mac) def mac_addr(ipv6): ''' Renvoie l'adresse mac de l'ipv6 d'auto-configuration ''' diff --git a/gestion/iscsi/nolslib.py b/gestion/iscsi/nolslib.py index f04440f7..f7677fc8 100644 --- a/gestion/iscsi/nolslib.py +++ b/gestion/iscsi/nolslib.py @@ -3,7 +3,7 @@ # baie_lib.py # ---------- -# Type à taper si ça chie : Pierre-Elliott Bécue +# Type à taper si ça chie : Pierre-Elliott Bécue # Licence : WTFPL '''Bibliothèque pour accéder à la baie de stockage nols, récupère les données @@ -113,7 +113,7 @@ class Nols(object): Objects = tree.findall("OBJECT") #Objects = tree.findall("OBJECT[@name='volume-view']") ## Fin cf juste en dessous - + for Object in Objects: name = None lun = None @@ -121,7 +121,7 @@ class Nols(object): # la merde que j'ai fait juste après. #name = Object.findall("PROPERTY[@name='volume-name']")[0].text #lun = Object.findall("OBJECT/PROPERTY[@name='lun']")[0].text - + ######## Début merde que j'ai faite juste après ########### if not (Object.attrib['name'] == "volume-view"): pass @@ -178,5 +178,5 @@ class Nols(object): def rename_volume(self, origin_name, new_name): '''Renomme un volume.''' - - self.cmd("set volume name %s %s" % (new_name, orig_name)) + + self.cmd("set volume name %s %s" % (new_name, origin_name)) diff --git a/gestion/ldap_crans.py b/gestion/ldap_crans.py index 236a2fe7..3d521fc5 100755 --- a/gestion/ldap_crans.py +++ b/gestion/ldap_crans.py @@ -223,6 +223,9 @@ def format_mac(mac): Le séparateur original peut être :, - ou rien Retourne la mac formatée. """ + mac = mac.strip() + if mac == '': + return mac l, mac = preattr(mac) mac = mac.strip().replace(' ','').replace("-", ":") if mac.count(":") == 5: @@ -2863,6 +2866,10 @@ class Machine(BaseClasseCrans): mac = format_mac(mac) + if mac == '': + multi_ok = True + lock = False + # La MAC serait-elle une MAC à la con ? if mac == "00:04:4b:80:80:03": raise ValueError(u"Il s'agit de l'unique adresse MAC achetée par nVidia pour ses cartes réseau. Il faut changer cette adresse.", 2) @@ -2898,7 +2905,7 @@ Contactez nounou si la MAC est bien celle d'une carte.""", 3) try: self._set('macAddress', [mac]) if net.size > 1: - self.ipv6(ip6tools.mac_to_ipv6(net, netaddr.EUI(mac)), lock) + self.ipv6(ip6tools.mac_to_ipv6(net, mac), lock) else: self.ipv6(config.ipv6_machines_speciales[int(self.rid())], lock) except Exception as e: @@ -3001,6 +3008,9 @@ Contactez nounou si la MAC est bien celle d'une carte.""", 3) if type(new) == list: # Modif + res = self.conn.search_s(self.dn, 1, "xid=*") + if res: + raise EnvironmentError("La machine possède des certificats, utilisez lc_ldap ou l'intranet2") index = new[0] new = new[1] if new == '': @@ -3214,6 +3224,12 @@ Contactez nounou si la MAC est bien celle d'une carte.""", 3) ret = '' + # test si la machine a des certificats + if 'host' in self.modifs: + res = self.conn.search_s(self.dn, 1, "xid=*") + if res: + raise EnvironmentError("La machine possède des certificats, utilisez lc_ldap ou l'intranet2") + # Besoin de redémarrer les firewalls ? if 'ipHostNumber' in self.modifs or 'macAddress' in self.modifs: reconf_ip = self._init_data.get('ipHostNumber', []) @@ -3294,6 +3310,9 @@ Contactez nounou si la MAC est bien celle d'une carte.""", 3) if self.proprietaire().__class__ == AssociationCrans and not isadm(): raise EnvironmentError(u'Il faut être administrateur pour effectuer cette opération.') + res = self.conn.search_s(self.dn, 1, "xid=*") + if res: + raise EnvironmentError("La machine possède des certificats, utilisez lc_ldap ou l'intranet2") self.proprio = self.__proprietaire.Nom() # On met dans un coin le nom du proprio self.__proprietaire = None # On oublie le propriétaire @@ -3390,7 +3409,14 @@ Contactez nounou si la MAC est bien celle d'une carte.""", 3) """Retourne l'adresse IPv6 correspondant à la machine""" if ipv6 == None: - return netaddr.IPAddress(self._data.get('ip6HostNumber', [''])[0]) + if self._data.get('ip6HostNumber', []) == []: + return None + else: + return netaddr.IPAddress(self._data.get('ip6HostNumber')[0]) + + if ipv6 == '': + self._set('ip6HostNumber', []) + return ipv6 = str(ipv6) net = self.netv6() diff --git a/gestion/mail/convocation_ago.py b/gestion/mail/convocation_ago.py new file mode 100755 index 00000000..e45cd5d5 --- /dev/null +++ b/gestion/mail/convocation_ago.py @@ -0,0 +1,52 @@ +#!/bin/bash /usr/scripts/python.sh +# -*- coding: utf-8 -*- + +import sys +import smtplib +from gestion import config +from gestion.affich_tools import cprint +from gestion import mail +import lc_ldap.shortcuts +from utils.sendmail import actually_sendmail + +# Attention, si à True envoie effectivement les mails +SEND=('--do-it' in sys.argv) +# Prévisualisation +PREV=('--prev' in sys.argv) + +ldap_filter=u"(&(paiement=%(annee)s)(aid=*))" % {'annee': config.ann_scol} +#ldap_filter=u"(|(uid=dstan)(uid=lasseri))" + +conn=lc_ldap.shortcuts.lc_ldap_readonly() +mailaddrs=set() +for adh in conn.search(ldap_filter, sizelimit=2000): + if 'canonicalAlias' in adh.attrs.keys(): + mailaddrs.add(str(adh['canonicalAlias'][0])) + elif 'mail' in adh.attrs.keys(): + mailaddrs.add(str(adh['mail'][0])) + elif 'uid' in adh.attrs.keys(): + mailaddrs.add(str(adh['uid'][0]) + '@crans.org') + else: + raise ValueError("%r has nor mail nor canonicalAlias, only %s" % (adh, adh.attrs.keys())) + +echecs=[] +From = 'ca@crans.org' +for To in mailaddrs: + cprint(u"Envoi du mail à %s" % To) + mailtxt=mail.generate('ago', {'To':To, 'From': From}) + if PREV: + print mailtxt.as_string() + try: + if SEND: + actually_sendmail('ca@crans.org', (To,), mailtxt) + cprint(" Envoyé !") + else: + cprint(" (simulé)") + except: + cprint(u"Erreur lors de l'envoi à %s " % To, "rouge") + echecs.append(To) + + +if echecs: + print "\nIl y a eu des erreurs :" + print echecs diff --git a/gestion/mail/mail.py b/gestion/mail/mail.py index 14676626..be8da1da 100644 --- a/gestion/mail/mail.py +++ b/gestion/mail/mail.py @@ -32,7 +32,7 @@ markup = { ### For an example: ### print generate('bienvenue', {'From':'respbats@crans.org', 'To':'admin@genua.fr', 'lang_info':'English version below'}).as_string() - +### or from a shell : python -c "import mail; print mail.generate('bienvenue', {'From':'respbats@crans.org', 'To':'admin@genua.fr', 'lang_info':'English version below'})" def submessage(playload, type, charset='utf-8'): """Renvois un sous message à mettre dans un message multipart""" diff --git a/gestion/mail/template/ago/From/fr b/gestion/mail/template/ago/From/fr new file mode 100644 index 00000000..2510ed39 --- /dev/null +++ b/gestion/mail/template/ago/From/fr @@ -0,0 +1 @@ +Le CA du Crans <{{From}}> diff --git a/gestion/mail/template/ago/Subject/fr b/gestion/mail/template/ago/Subject/fr new file mode 100644 index 00000000..00799112 --- /dev/null +++ b/gestion/mail/template/ago/Subject/fr @@ -0,0 +1 @@ +[Crans] Convocation à l'Assemblée Générale Ordinaire de l'association diff --git a/gestion/mail/template/ago/To/fr b/gestion/mail/template/ago/To/fr new file mode 100644 index 00000000..38476730 --- /dev/null +++ b/gestion/mail/template/ago/To/fr @@ -0,0 +1 @@ +{{To}} diff --git a/gestion/mail/template/ago/body/en b/gestion/mail/template/ago/body/en new file mode 100644 index 00000000..fc4acf67 --- /dev/null +++ b/gestion/mail/template/ago/body/en @@ -0,0 +1,37 @@ +Dear members, + +The Ordinary General Meeting of the Cr@ns will take place Thursday, March 6th, 2014 at 7:15pm in Pavillon des Jardins. +The present email serve as formal notice to attend. +The main goal of this meeting is the election of a new Board of Directors. + += Agenda = + +== New statutes and Internal regulation == +Both statutes and internal regulation have been modified. First drafts will soon be available on the wiki. +Final versions will be available Thursday, February 27th after the last meeting of the current members from the Board of Directors. New modifications will be submitted to the vote of all members present at the General Meeting. + +== Activity and financial report == +President Ariane Soret will present the activity report. +Treasurer Vincent Guiraud will present the financial report. +Both reports will be submitted to the vote of all present members. + +== Technical report == +Technical Manager Valentin Samir will present the technical report. + +== Elections == +As anounced by the President, the application phase [1] lasts until Thursday, February 27th. The elections shall be held during the Ordinary General Meeting. Execution modalities will be provided later. + +== Counting of votes == + +== Announcement of new Board of Directors == + +During the Ordinary General Meeting, internet connection may be altered. + +After the Ordinary General Meeting, the new Board of Directors will eventually have a meeting. + +Good evening, + +-- +Raphaël-David Lasseri +Secrétaire du Crans +Secretary of Crans Association diff --git a/gestion/mail/template/ago/body/fr b/gestion/mail/template/ago/body/fr new file mode 100644 index 00000000..04932302 --- /dev/null +++ b/gestion/mail/template/ago/body/fr @@ -0,0 +1,45 @@ +Chers adhérents, + +Le Jeudi 6 Mars 2014, à partir de 19h15 au Pavillon Des Jardins aura lieu +l'Assemblée Générale Ordinaire du Crans. Le présent mail tient lieu de +convocation. L'objet principal de cette assemblée sera l'élection du nouveau +Conseil d'Administration. + += Ordre du jour = + +== Nouveaux Status et Règlement Intérieur == +Les Statuts et le Règlement Intérieur ont été modifiés. +Leurs brouillons sont déja disponible sur la mailing list CA +et seront mis en ligne très prochainement sur le wiki. +Leurs versions finales seront disponible le jeudi 27 février à l'issue +du dernier Conseil d'Administration du bureau actuel. +Ces modifications seront soumises au vote des adhérents présents. + +== Bilans moral et financier == +Le président Ariane Soret présentera le bilan moral et le trésorier sortant +Vincent Guiraud présentera le bilan financier. Ces bilans seront soumis au vote +des adhérents présents. + +== Bilan technique == +Le responsable technique en chef Valentin Samir présentera le bilan technique. + +== Elections == +Comme précédemment annoncé par notre président. La phase de candidature [1] est +ouverte jusqu’au jeudi 27 février. Les élections se dérouleront le jour de +l'AGO; les modalités électorales seront communiqués ultérieurement. + +=== Dépouillement des votes === + +=== Annonce du nouveau bureau === + +Durant l'Assemblée Générale Ordinaire la connexion à Internet pourra être altérée. + + +À l'issue de l'Assemblée Générale Ordinaire le nouveau Conseil d'Administration +pourra éventuellement tenir une réunion. + + +Bonne soirée à tous ! + + [1]: https://wiki.crans.org/CransAdministratif/R%C3%A8glementInt%C3%A9rieur + diff --git a/gestion/restore.py b/gestion/restore.py index 39205926..7c701933 100755 --- a/gestion/restore.py +++ b/gestion/restore.py @@ -11,6 +11,10 @@ Attention, ce fichier est osbolète """ import cPickle, sys +if '/usr/scripts' not in sys.path: + sys.path.append('/usr/scripts') +from cranslib.deprecated import module as dep_module +dep_module('ressucite or ressucite_lc') import config from whos import aff diff --git a/gestion/secrets_new.py b/gestion/secrets_new.py index bb9961dc..1706db34 100644 --- a/gestion/secrets_new.py +++ b/gestion/secrets_new.py @@ -31,7 +31,7 @@ import getpass def get(secret): """ Recupere un secret. """ openlog('secrets_new') - prog = os.path.basename(sys.argv[0]) + prog = os.path.basename(getattr(sys, 'argv', ['undefined'])[0]) syslog('%s (in %s) asked for %s' % (getpass.getuser(), prog, secret)) try: f = open("/etc/crans/secrets/" + secret) diff --git a/gestion/tools/mkhome.py b/gestion/tools/mkhome.py index abc4af68..a348ff38 100755 --- a/gestion/tools/mkhome.py +++ b/gestion/tools/mkhome.py @@ -11,6 +11,9 @@ import services if not 'crans-nfs' in services.services or not socket.gethostname() in services.services['crans-nfs']: sys.stderr.write("Devrait être exécuté sur une machine du groupe 'crans-nfs'\n") exit(1) +if len(sys.argv) < 1: + sys.stderr.write("Usage : %s [user1] [user2] [user3] ...\n" % sys.argv[0]) + exit(1) import lc_ldap.shortcuts import gestion.gen_confs.adherents @@ -23,9 +26,6 @@ for user in sys.argv[1:]: if a and a[0]['homeDirectory'] and a[0]['uidNumber'] and a[0]['uid']: l.append("%s,%s,%s" % (a[0]['homeDirectory'][0], a[0]['uidNumber'][0], a[0]['uid'][0])) -else: - sys.stderr.write("Usage : %s [user1] [user2] [user3] ...\n" % sys.argv[0]) if l: h=gestion.gen_confs.adherents.home(l) h.reconfigure() - diff --git a/gestion/virtualisation/gen_net_udev.sh b/gestion/virtualisation/gen_net_udev.sh new file mode 100755 index 00000000..fd13679a --- /dev/null +++ b/gestion/virtualisation/gen_net_udev.sh @@ -0,0 +1,8 @@ +#!/bin/bash + + +# Sur certains virtualiseurs xen, les interfaces xen n'étaient pas dans udev. +# Voici un petit script qui écrit les règles correspondantes + +ip -o l show | sed 's/^[0-9]*: \(eth[^:]*\).*ether \([^ ]*\) .*$/SUBSYSTEM=="net", DRIVERS=="?*", ATTR{address}=="\2", NAME="\1"/; t; d' + diff --git a/gestion/virtualisation/vmid b/gestion/virtualisation/vmid new file mode 100755 index 00000000..863f0e2f --- /dev/null +++ b/gestion/virtualisation/vmid @@ -0,0 +1,46 @@ +#!/bin/bash + +VM_PATH=/etc/pve/qemu-server +PVE_PATH=/etc/pve +LOCAL_VM_PATH=/etc/pve/local/qemu-server +SERIAL_PATH=/var/run/qemu-server + +if [[ ! -d /etc/pve ]]; then + echo "Not a proxmox server !" + exit 1 +fi + +if [[ -z "$1" ]]; then + echo "Please give vmid or pve name" + exit 5 +fi + +if [[ "`whoami`" != "root" ]]; then + echo "You must probably be root" + exit 42 +fi + +if [[ $1 != *[!0-9]* ]]; then + vmid=$1 +else + echo "Looking for vmid of $1 ..." + for host in `ls $PVE_PATH/nodes`; do + p=$PVE_PATH/nodes/$host/qemu-server + for f in `ls $p`; do + grep "name: *$1" $p/$f -q && { + vmid=`echo $f | grep -o "[0-9]*"` + node=$host + echo "Found vmid $vmid" + break + } + done + done + if [[ -z "$node" ]]; then + echo "vmid not found" + exit 2 + fi + if [[ "$node" != "`hostname`" ]]; then + echo "Wrong node (go to $node)" + exit 3 + fi +fi diff --git a/gestion/whos.py b/gestion/whos.py index a03d141e..f6a6f5ec 100755 --- a/gestion/whos.py +++ b/gestion/whos.py @@ -670,7 +670,8 @@ def machine_details(machine) : f+= coul(u'IP : ','gras') + "%s\t\t" %machine.ip() f+= coul(u'MAC : ','gras') + "%s\n" %machine.mac() - f+= coul(u'IPv6 : ','gras') + "%s\n" %machine.ipv6() + if machine.ipv6() != None: + f+= coul(u'IPv6 : ','gras') + "%s\n" %machine.ipv6() if len(machine.sshFingerprint()) > 0 and aff_ssh: f += u"\n".join([coul(u'Fingerprint SSH : ', 'gras') + u"%s" % (i) for i in machine.sshFingerprint()])+"\n" diff --git a/gestion/whos_lc.py b/gestion/whos_lc.py index 7c5107ae..9ad86e50 100755 --- a/gestion/whos_lc.py +++ b/gestion/whos_lc.py @@ -9,7 +9,7 @@ import lc_ldap.filter2 as filter if __name__ == '__main__': if len(sys.argv) >1: conn=lc_ldap.shortcuts.lc_ldap_admin() - result=conn.search(filter.human_to_ldap(sys.argv[1]), sizelimit=4000) + result=conn.search(filter.human_to_ldap(sys.argv[1].decode('utf-8')), sizelimit=4000) if not result: print "rien trouvé" else: diff --git a/impression/digicode.py b/impression/digicode.py index 486ff6ef..0275496f 100644 --- a/impression/digicode.py +++ b/impression/digicode.py @@ -33,9 +33,10 @@ import commands import string import random import requests -sys.path.append("/usr/scripts/") +if not '/usr/scripts' in sys.path: + sys.path.append("/usr/scripts") import cranslib.utils.files -import secrets_new +import gestion.secrets_new as secrets_new digicode_pass = secrets_new.get("digicode_pass") # ############################################################# diff --git a/munin/scripts/link_plugins.py b/munin/scripts/link_plugins.py index 41626328..a79dd34a 100755 --- a/munin/scripts/link_plugins.py +++ b/munin/scripts/link_plugins.py @@ -22,6 +22,9 @@ import tempfile from hosts_plugins import hosts_plugins, general_plugins +from gestion.config.services import services + + # Plugins munin classiques à ignorer IGNORE_PLUGINS = ( 'apt_all', @@ -39,6 +42,13 @@ IGNORE_PLUGINS = ( 'vlan_', ) +if socket.gethostname() in services.services.get('iscsi', []): + IGNORE_PLUGINS += ( + 'diskstat', + 'diskstats', + 'diskstat_', + ) + # Chemin d'accès aux plugins munin MUNIN_PATH = "/usr/share/munin/plugins" diff --git a/tv/radio/config.py b/tv/radio/config.py index aa8346c9..5a05683f 100644 --- a/tv/radio/config.py +++ b/tv/radio/config.py @@ -7,7 +7,7 @@ def dir(path): return l multicast={ 'Radio': { - 'Armitunes': ('armitunes','239.231.140.162','1234',['http://149.255.33.76:8004/','http://95.31.11.136:9010/','http://95.31.3.225:9010/','http://hanyo.dyndns-server.com:9078/']), + 'Armitunes': ('armitunes','239.231.140.162','1234',['http://198.27.80.17:8000/','http://95.31.11.136:9010/','http://95.31.3.225:9010/']), 'Radio Classique': ('classique','239.231.140.163','1234',['http://broadcast.infomaniak.net:80/radioclassique-high.mp3']), 'France Inter': ('inter','239.231.140.164','1234',['http://mp3.live.tv-radio.com/franceinter/all/franceinterhautdebit.mp3']), 'France Info': ('info','239.231.140.165','1234',['http://mp3.live.tv-radio.com/franceinfo/all/franceinfo-32k.mp3']), @@ -16,7 +16,7 @@ multicast={ # 'Webradio Rock': ('rock','239.231.140.168','1234',['http://webradio.crans.org:8000/rock.mp3']), 'I.ACTIVE DANCE': ('iactive','239.231.140.170', '1234', ['http://serveur.wanastream.com:48700/']), 'Skyrock': ('skyrock', '239.231.140.171', '1234', ['http://95.81.146.6/3665/sky_122353.mp3']), - 'Rire et Chanson': ('rireetchanson', '239.231.140.172', '1234', ['http://95.81.146.2/rire_et_chansons/all/rir_124629.mp3']), + 'Rire et Chanson': ('rireetchanson', '239.231.140.172', '1234', ['http://95.81.146.10/5011/nrj_122230.mp3']), 'Europe 1': ('europe1', '239.231.140.173', '1234', ['http://vipicecast.yacast.net/europe1.mp3']), 'Chérie FM': ('cherie_fm', '239.231.140.174', '1234', ['http://95.81.146.2/cherie_fm/all/che_124310.mp3']), 'France Culture': ('culture', '239.231.140.175', '1234', ['http://95.81.147.3/franceculture/all/franceculturehautdebit.mp3']), diff --git a/utils/radius_auth.py b/utils/radius_auth.py index 4e636e18..885e8bcc 100755 --- a/utils/radius_auth.py +++ b/utils/radius_auth.py @@ -51,6 +51,12 @@ def do_auth(mac, prise): conn = crans_ldap(readonly=True) m = conn.search('mac=%s' % mac)['machine'] if len(m) == 0: + # Est-ce un ancien client de l'offre Crous ? + # on le met sur le vlan install-party où on aura activé un forwarding + # (uniquement en attendant qu'il soit inscrit proprement) + for chbre in annuaires_pg.reverse(prise[0], prise[1:]): + if not annuaires_pg.is_crans(prise[0], chbre): + return (0, "TMP: ancien client CROUS", 'event') return (0, "Mac inconnue", "accueil") elif len(m) > 1: return (-1, "Pb recherche mac (nb résultat %d!=1)" % len(m), "") diff --git a/wiki/macro/AllPagesWithACL.py b/wiki/macro/AllPagesWithACL.py new file mode 100644 index 00000000..8fc04e9f --- /dev/null +++ b/wiki/macro/AllPagesWithACL.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +""" + MoinMoin - AllPagesWithACL Macro + + @copyright: 2007 Alexander "Loki" Agibalov + @license: GNU GPL, see COPYING for details. + + changes: + 12.2007 - conversion to new syntax by BolesÅ‚aw KulbabiÅ„ski + + Modifié par Vincent Le Galli + (cf http://moinmo.in/MacroMarket/1.6_AdminTools) + * patch pour ne pas afficher les acl "not defined" + * Si on fournit le paramètre IncludeSystemPages, on a aussi les pages MoinMoin, + mais par défaut, non. +""" + +import os +import re +from MoinMoin.Page import Page +from MoinMoin import wikiutil + +def getAcl(request, pagename): + pg = Page(request, pagename) + pi = pg.get_pi() + ret = pi["acl"].getString() + if ret == '': + ret = "not defined" + return ret + + +def macro_AllPagesWithACL(macro, args): + html = "

All pages:
" + all = {} + pages = macro.request.rootpage.getPageList() +# pages = macro.request.rootpage.getPageList(filter = re.compile("^WikiSandBox").match) + html += "Total: %s pages

" % str(len(pages)) + + for pagename in pages: + if Page(macro.request,pagename).isStandardPage() or (args != None and "IncludeSystemPages" in args): + all[Page(macro.request, pagename).link_to(macro.request)] = getAcl(macro.request, pagename) + + html += "" + all1 = sorted(all.items()) + for pg, ac in all1: + if ac != "not defined": + html += "" % pg + html += "" % ac + html += "
%s%s
" + + return macro.formatter.rawHTML(html) + + +def execute(macro, args): + try: + return wikiutil.invoke_extension_function( + macro.request, macro_AllPagesWithACL, args, [macro]) + except ValueError, err: + return macro.request.formatter.text( + "<>" % err.args[0]) + diff --git a/wiki/theme/crans.py b/wiki/theme/crans.py index 31a6b0cd..6bd3e02c 100644 --- a/wiki/theme/crans.py +++ b/wiki/theme/crans.py @@ -101,6 +101,49 @@ class Theme(ThemeBase): '{o}': ("{o}", "star_off.png", 16, 16), } + def navibar(self, d): + """ Assemble the navibar + + @param d: parameter dictionary + @rtype: unicode + @return: navibar html + """ + request = self.request + found = {} # pages we found. prevent duplicates + items = [] # navibar items + item = u'
  • %s
  • ' + current = d['page_name'] + + # Process config navi_bar + if request.cfg.navi_bar: + for text in request.cfg.navi_bar: + pagename, link = self.splitNavilink(text) + if pagename == current: + cls = 'wikilink current' + else: + cls = 'wikilink' + items.append(item % (cls, link)) + found[pagename] = 1 + + # Add current page at end of local pages + if not current in found: + title = d['page'].split_title() + title = self.shortenPagename(title) + link = d['page'].link_to(request, title) + cls = 'current' + items.append(item % (cls, link)) + + # Assemble html + items = u''.join(items) + html = u''' + +''' % items + return html + + + def wikipanel(self, d): """ Create wiki panel """ _ = self.request.getText