diff --git a/freeradius/auth.py b/freeradius/auth.py index 159dfbd8..dc26b466 100644 --- a/freeradius/auth.py +++ b/freeradius/auth.py @@ -1,20 +1,21 @@ #!/bin/bash /usr/scripts/python.sh # ⁻*- coding: utf-8 -*- # -# Draft de fichier d'authentification -# # Ce fichier contient la définition de plusieurs fonctions d'interface à freeradius # qui peuvent être appelées (suivant les configurations) à certains moment de # l'éxécution. # + +import logging +import traceback +import netaddr +import radiusd # Module magique freeradius (radiusd.py is dummy) + 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.config.config import vlans from gestion.gen_confs.trigger import trigger_generate_cochon as trigger_generate import annuaires_pg @@ -27,13 +28,34 @@ test_v6 = [ USERNAME_SUFFIX = '.wifi.crans.org' +## -*- Logging -*- +# Initialisation d'un logger pour faire des stats etc +# pour l'instant, on centralise tout sur thot en mode debug +logger = logging.getLogger('auth.py') +logger.setLevel(logging.DEBUG) +formatter = logging.Formatter('%(name)s: [%(levelname)s] %(message)s') +handler = logging.handlers.SysLogHandler(address = '/dev/log') +try: + handler.addFormatter(formatter) +except AttributeError: + handler.formatter = formatter +logger.addHandler(handler) + +## -*- Types de blacklists -*- +#: reject tout de suite bl_reject = [u'bloq'] + +#: place sur le vlan isolement bl_isolement = [u'virus', u'autodisc_virus', u'autodisc_p2p', u'ipv6_ra'] + # TODO carte_etudiant: dépend si sursis ou non (regarder lc_ldap) # TODO LOGSSSSS + +#: place sur accueil bl_accueil = [u'carte_etudiant', u'chambre_invalide', u'paiement'] -# Decorateur utilisé plus tard (same connection) +## -*- Decorateurs -*- +# À appliquer sur les fonctions qui ont besoin d'une conn ldap 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, @@ -145,13 +167,6 @@ def get_prise_chbre(data): chbre = None return prise, chbre -def get_ap(data): - """Extrait la prise (wifi)""" - ## WiFi: NAS-Identifier => vide - ## Nas-Port => numéro sur l'interface - ## Nas-IP-Address => adresse IP de la borne - pass - @use_ldap_admin def register_mac(data, machine, conn): """Enregistre la mac actuelle sur une machine donnée.""" @@ -186,8 +201,7 @@ def instantiate(p, *conns): pass @radius_event -@use_ldap -def wifi_authorize(data, conn): +def authorize_wifi(data): """Section authorize pour le wifi (NB: le filaire est en accept pour tout le monde) Éxécuté avant l'authentification proprement dite. On peut ainsi remplir les @@ -233,38 +247,93 @@ def wifi_authorize(data, conn): ) @radius_event -@use_ldap -def post_auth(data, conn): +def authorize_fil(data): + """For now, do nothing. + TODO: check bl_reject. + TODO: check chap auth + """ + return radiusd.RLM_MODULE_OK + +@radius_event +def post_auth_wifi(data): """Appelé une fois que l'authentification est ok. On peut rajouter quelques éléments dans la réponse radius ici. Comme par exemple le vlan sur lequel placer le client""" - is_wifi = False - vlan_name = None - reason = '' - identity = "" #TODO - prise = "" - chbre = None - items = get_machines(data) - decision = 'adherent','' + port, vlan_name, reason = decide_vlan(data, True) + mac = data.get('Calling-Station-Id', None) + log_message = '(wifi) %s -> %s [%s%s]' % \ + (port, mac, vlan_name, (reason and u': ' + reason).encode(u'utf-8')) + logger.info(log_message) + radiusd.radlog(radiusd.L_AUTH, log_message) + + # Si NAS ayant des mapping particuliers, à signaler ici + vlan_id = vlans[vlan_name] + + # WiFi : Pour l'instant, on ne met pas d'infos de vlans dans la réponse + # les bornes wifi ont du mal avec cela + return radiusd.RLM_MODULE_OK + +@radius_event +def post_auth_fil(data): + """Idem, mais en filaire. + Pas testé.""" + + port, vlan_name, reason = decide_vlan(data, True) + mac = data.get('Calling-Station-Id', None) + + log_message = '(fil) %s -> %s [%s%s]' % \ + (port, mac, vlan_name, (reason and u': ' + reason).encode(u'utf-8')) + logger.info(log_message) + radiusd.radlog(radiusd.L_AUTH, log_message) + + # Si NAS ayant des mapping particuliers, à signaler ici + vlan_id = vlans[vlan_name] + + # Filaire + return (radiusd.RLM_MODULE_UPDATED, + ( + ("Tunnel-Type", "VLAN"), + ("Tunnel-Medium-Type", "IEEE-802"), + ("Tunnel-Private-Group-Id", '%d' % vlan), + ), + () + ) + +@use_ldap +def decide_vlan(data, is_wifi, conn): + """Décide du vlan non-taggué à assigner, et donne une raison + à ce choix. + Retourne un (port, vlan_name, reason) + où port = est une prise réseau / chambre (si filaire) + "wifi" si wifi + """ + + if is_wifi: + decision = 'wifi','' + port = data.get('Called-Station-Id', '?') + else: + decision = 'adherent','' + prise, chbre = get_prise_chbre(data) + port = "%s/%s" % (prise, chbre) + + items = get_machines(data) if not items: - decision = 'accueil', 'Machine inconnue' - return radiusd.RLM_MODULE_NOTFOUND # TODO faire un truc plus propre + return (port, 'accueil', 'Machine inconnue') machine = items[0] proprio = machine.proprio() - if isinstance(machine, lc_ldap.objets.machineWifi): - decision = 'wifi', '' - is_wifi = True - - if not machine['ipHostNumber'] or unicode(machine['macAddress'][0]) in test_v6: + if not machine['ipHostNumber']: decision = 'v6only', 'No IPv4' + elif unicode(machine['macAddress'][0]) in test_v6: + decision = 'v6only', 'Test machine v6' elif machine['ipHostNumber'][0].value in netaddr.IPNetwork('10.2.9.0/24'): # Cas des personnels logés dans les appartements de l'ENS decision = 'appts', 'Personnel ENS' + # Application des blacklists for bl in machine.blacklist_actif(): if bl.value['type'] in bl_isolement: decision = 'isolement', unicode(bl) @@ -275,43 +344,28 @@ def post_auth(data, conn): if not is_wifi: # Si l'adhérent n'est pas membre actif, il doit se brancher depuis la # prise d'un autre adhérent à jour de cotisation - prise, chbre = get_prise_chbre(data) - if proprio['droits']: - decision = decision[0], decision[1] + ' (force MA)' - elif chbre is None: + force_ma = False + if chbre is None and not proprio['droits']: decision = "accueil", "Chambre inconnue" - else: + elif chbre is not None: chbre = escape_ldap(chbre) hebergeurs = conn.search(u'(&(chambre=%s)(cid=*)(aid=*))' % chbre) for hebergeur in hebergeurs: if not hebergeur.blacklist_actif(): break else: - decision = "accueil", "Hébergeur blacklisté" + # Si tous les hebergeurs sont blacklistés, autorisé + # uniquement si MA + if not proprio['droits']: + decision = "accueil", "Hébergeur blacklisté" + else: + force_ma = True + else: + force_ma = True + if force_ma: + decision = decision[0], decision[1] + ' (force MA)' - # Unpack and log - if chbre is not None: - prise += '/' + chbre - - 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 u': ' + reason).encode(u'utf-8')) - ) - - # WiFi : Pour l'instant, on ne met pas d'infos de vlans dans la réponse - # les bornes wifi ont du mal avec cela - if is_wifi: - return radiusd.RLM_MODULE_OK - - return (radiusd.RLM_MODULE_UPDATED, - ( - ("Tunnel-Type", "VLAN"), - ("Tunnel-Medium-Type", "IEEE-802"), - ("Tunnel-Private-Group-Id", '%d' % vlan), - ), - () - ) + return (port,) + decision @radius_event def dummy_fun(p): diff --git a/freeradius/rlm_python_fil.conf b/freeradius/rlm_python_fil.conf new file mode 100644 index 00000000..2b365cf6 --- /dev/null +++ b/freeradius/rlm_python_fil.conf @@ -0,0 +1,37 @@ +# Configuration for the Python module. +# +# + +python crans_fil { + mod_instantiate = freeradius.auth + func_instantiate = instantiate + + # Spécifique au WiFi : rempli le mdp + mod_authorize = freeradius.auth + func_authorize = authorize_fil + + # Renseigne le vlan + # remplacer par dummy_fun pour ignorer le tagging de vlan + mod_post_auth = freeradius.auth + func_post_auth = post_auth_fil + + # Que faire avant de quitter + mod_detach = freeradius.auth + func_detach = detach + + # Le reste est dumb et inutile + mod_accounting = freeradius.auth + func_accounting = dummy_fun + + mod_pre_proxy = freeradius.auth + func_pre_proxy = dummy_fun + + mod_post_proxy = freeradius.auth + func_post_proxy = dummy_fun + + mod_recv_coa = freeradius.auth + func_recv_coa = dummy_fun + + mod_send_coa = freeradius.auth + func_send_coa = dummy_fun +} diff --git a/freeradius/rlm_python_wifi.conf b/freeradius/rlm_python_wifi.conf index a8b73e2b..8884ab42 100644 --- a/freeradius/rlm_python_wifi.conf +++ b/freeradius/rlm_python_wifi.conf @@ -8,12 +8,12 @@ python crans_wifi { # Spécifique au WiFi : rempli le mdp mod_authorize = freeradius.auth - func_authorize = wifi_authorize + func_authorize = authorize_wifi # Renseigne le vlan # remplacer par dummy_fun pour ignorer le tagging de vlan mod_post_auth = freeradius.auth - func_post_auth = post_auth + func_post_auth = post_auth_wifi # Que faire avant de quitter mod_detach = freeradius.auth diff --git a/freeradius/test.py b/freeradius/test.py index 4d5e7365..388221ce 100755 --- a/freeradius/test.py +++ b/freeradius/test.py @@ -17,14 +17,14 @@ delattr(sys, 'argv') auth.instantiate(()) p=( - ('Calling-Station-Id', 'ba:27:eb:3c:54:d5'), - ('User-Name', 'test18'), + ('Calling-Station-Id', 'b0:79:94:cf:d1:9a'), + ('User-Name', 'moo-torola'), ) -print repr(auth.wifi_authorize(p)) +print repr(auth.authorize_wifi(p)) print "wait for 3s, tu peux aller crasher le serveur pg ou ldap" sys.stdout.flush() time.sleep(3) -print repr(auth.post_auth(p)) +print repr(auth.post_auth_wifi(p))