diff --git a/freeradius/auth.py b/freeradius/auth.py new file mode 100755 index 00000000..c9c447d6 --- /dev/null +++ b/freeradius/auth.py @@ -0,0 +1,174 @@ +#!/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. +# +# Une telle fonction prend un uniquement argument, qui est une liste de tuples +# (clé, valeur) +# et renvoie un triplet dont les composantes sont : +# * le code de retour (voir radiusd.RLM_MODULE_* ) +# * un tuple de couples (clé, valeur) pour les valeurs de réponse +# (access ok et autres trucs du genre) +# * un tuple de couples (clé, valeur) pour les valeurs internes à mettre à jour +# (mot de passe par exemple) +# +# Voir des exemples plus complets ici: +# https://github.com/FreeRADIUS/freeradius-server/blob/master/src/modules/rlm_python/ + +from lc_ldap.shortcuts import with_ldap_conn +from lc_ldap.crans_utils import escape as escape_ldap +from gestion.config.config import vlans +import lc_ldap.objets +import radiusd +import netaddr + +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'] + +def get_machines(auth_data, conn): + mac = None + username = None + + # Beware: les valeurs scalaires sont entre guillemets + # 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('"','')) + 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) + +@use_ldap +def instantiate(p, conn): + """Utile pour initialiser la connexion ldap une première fois (otherwise, + do nothing)""" + pass + +@use_ldap +def wifi_authorize(auth_data, conn): + """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 + champs login et mot de passe qui serviront ensuite à l'authentification + (MschapV2/PEAP ou MschapV2/TTLS)""" + + items = get_machines(auth_data, conn) + + if not items: + radiusd.radlog(radiusd.L_ERR, 'Nobody found :(') + return radiusd.RLM_MODULE_NOTFOUND + if len(items) > 1: + radiusd.radlog(radiusd.L_ERR, 'Too much results from lc_ldap !') + + machine = items[0] + + proprio = machine.proprio() + if isinstance(proprio, lc_ldap.objets.AssociationCrans): + radiusd.radlog(radiusd.L_ERR, 'Crans machine trying to authenticate !') + return radiusd.RLM_MODULE_INVALID + + for bl in machine.blacklist_actif(): + if bl.value['type'] in bl_reject: + return radiusd.RLM_MODULE_REJECT + + if not machine.get('ipsec', False): + return radiusd.RLM_MODULE_REJECT + password = machine['ipsec'][0].value.encode('ascii', 'ignore') + + return (radiusd.RLM_MODULE_UPDATED, + (), + ( + ("Cleartext-Password", password), + ), + ) + +@use_ldap +def post_auth(auth_data, conn): + """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""" + + items = get_machines(auth_data, conn) + + if not items: + return radiusd.RLM_MODULE_NOTFOUND + machine = items[0] + proprio = machine.proprio() + + vlan = vlans['adherent'] + if isinstance(machine, lc_ldap.objets.machineWifi): + vlan = vlans['wifi'] + + if not machine['ipHostNumber']: + # No IP => vlan v6only + vlan = vlans['v6only'] + elif machine['ipHostNumber'][0].value in netaddr.IPNetwork('10.2.9.0/24'): + # Cas des personnels logés dans les appartements de l'ENS + vlan = vlans['appts'] + + for bl in machine.blacklist_actif(): + if bl in bl_isolement: + vlan = vlans['isolement'] + if bl in bl_accueil: + vlan = vlans['accueil'] + +# +# +# # Si l'adhérent n'est pas membre actif, il doit se brancher depuis la prise +# # d'un autre adhérent à jour de cotisation +# if not proprio.droits(): +# try: +# chbre = prise[0] + annuaires_pg.reverse(prise[0], prise[1:])[0] +# except IndexError: +# return (0, "Chambre inconnue", "accueil") +# hebergeurs = conn.search('chambre=' + chbre) +# for hebergeur in hebergeurs['adherent'] + hebergeurs['club']: +# if paiement_ok(hebergeur): +# break +# else: +# return (0, "Hébergeur non à jour", "accueil") +# +# + + return (radiusd.RLM_MODULE_UPDATED, + ( + ("Tunnel-Type", "VLAN"), + ("Tunnel-Medium-Type", "IEEE-802"), + ("Tunnel-Private-Group-Id", '%d' % vlan), + ), + () + ) + +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(): + """Appelé lors du déchargement du module (enfin, normalement)""" + print "*** goodbye from example.py ***" + return radiusd.RLM_MODULE_OK diff --git a/freeradius/rlm_python_wifi.conf b/freeradius/rlm_python_wifi.conf new file mode 100644 index 00000000..297cf776 --- /dev/null +++ b/freeradius/rlm_python_wifi.conf @@ -0,0 +1,35 @@ +# Configuration for the Python module. +# +# + +python crans_wifi { + mod_instantiate = freeradius.auth + func_instantiate = instantiate + + # Spécifique au WiFi : rempli le mdp + mod_authorize = freeradius.auth + func_authorize = wifi_authorize + + # Renseigne le 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 + + mod_detach = freeradius.auth + func_detach = detach +} diff --git a/freeradius/test.py b/freeradius/test.py new file mode 100755 index 00000000..1cc5472f --- /dev/null +++ b/freeradius/test.py @@ -0,0 +1,21 @@ +#!/bin/bash /usr/scripts/python.sh +# -*- coding: utf-8 -*- + +import auth +import sys +import time + +if len(sys.argv) < 2: + print "Give me a mac !" + sys.exit(1) + +# Machine à s'authentifier (cerveaulent) +p=(('Calling-Station-Id', sys.argv[1]),) + +print repr(auth.wifi_authorize(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))