Merge branch 'master' of ssh://git.crans.org/git/usr-scripts

This commit is contained in:
Kévin "NeK" Moisy-Mabille 2014-03-03 12:07:49 +01:00
commit 30aab68b86
46 changed files with 1075 additions and 200 deletions

4
.gitignore vendored
View file

@ -45,6 +45,10 @@ var/
surveillance/mac_prises/output/
doc/build/
# modules tier
pyasn1_modules/
pythondialog/
# etat_* de la connexion de secours
secours/etat_*

View file

@ -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=<automatique>)(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 (<automatique> -> %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 '<automatique>' 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 ***"

View file

@ -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
}

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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']}

View file

@ -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' ]

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

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

View file

@ -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 é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),

View file

@ -36,6 +36,8 @@ class dydhcp:
@raises OmapiError:
@raises socket.error:
"""
if '<automatique>' 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 '<automatique>' 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() != '<automatique>' and AddrInNet(machine.ip(), net) :
if '<automatique>' 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() }

View file

@ -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 '<automatique>' 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 '<automatique>' 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 '<automatique>' 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']):

View file

@ -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')

View file

@ -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')

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -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__' :

View file

@ -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

37
gestion/gen_confs/trigger.py Executable file
View file

@ -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 &')

View file

@ -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)

305
gestion/gest_crans_lc.py Executable file
View file

@ -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')

View file

@ -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 == '<automatique>':
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)

View file

@ -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 '<automatique>' == 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 '''

View file

@ -3,7 +3,7 @@
# baie_lib.py
# ----------
# Type à taper si ça chie : Pierre-Elliott Bécue <peb@crans.org>
# Type à taper si ça chie : Pierre-Elliott Bécue <peb@crans.org>
# 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))

View file

@ -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 == '<automatique>':
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 == '<automatique>':
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()

52
gestion/mail/convocation_ago.py Executable file
View file

@ -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

View file

@ -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"""

View file

@ -0,0 +1 @@
Le CA du Crans <{{From}}>

View file

@ -0,0 +1 @@
[Crans] Convocation à l'Assemblée Générale Ordinaire de l'association

View file

@ -0,0 +1 @@
{{To}}

View file

@ -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

View file

@ -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 jusquau 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

View file

@ -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

View file

@ -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)

View file

@ -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()

View file

@ -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'

46
gestion/virtualisation/vmid Executable file
View file

@ -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

View file

@ -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"

View file

@ -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:

View file

@ -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")
# #############################################################

View file

@ -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"

View file

@ -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']),

View file

@ -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), "")

View file

@ -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 <legallic@crans.org>
(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 = "<p><b>All pages:</b><br>"
all = {}
pages = macro.request.rootpage.getPageList()
# pages = macro.request.rootpage.getPageList(filter = re.compile("^WikiSandBox").match)
html += "Total: %s pages </p>" % 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 += "<table>"
all1 = sorted(all.items())
for pg, ac in all1:
if ac != "not defined":
html += "<tr><td>%s</td>" % pg
html += "<td>%s</td></tr>" % ac
html += "</table>"
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(
"<<AllPagesWithACL: %s>>" % err.args[0])

View file

@ -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'<li class="%s">%s</li>'
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'''
<ul id="navibar">
%s
</ul>
''' % items
return html
def wikipanel(self, d):
""" Create wiki panel """
_ = self.request.getText