#!/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, lc_ldap_admin 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 from gestion.gen_confs.generate import trigger 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 # flemme donc je hardcode les MAC qui doivent toujours être placées sur le vlan v6only test_v6 = [ u'dc:9f:db:5c:c3:ea', # polynice-wlan0 #u'00:15:6d:ee:e8:f8', # atree-wlan0 u'00:26:c7:a6:9e:16', # cerveaulent ] 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 = with_ldap_conn(retries=2, delay=5, constructor=lc_ldap_admin) @use_ldap 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': 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 username (strip .wifi.crans.org) base = u'(objectclass=machine)' 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, mode='rw') 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 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 not None: mac = unicode(mac.lower()) machine['macAddress'] = mac machine.history_add(u'auth.py', u'macAddress ( %s )' % mac) machine.save() radiusd.radlog(radiusd.L_INFO, 'Mac set') trigger_generate('komaz', background=True) else: radiusd.radlog(radiusd.L_ERR, 'Cannot find MAC') @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) 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 (took first)') machine = items[0] if '' in machine['macAddress']: register_mac(auth_data, machine) 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): 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, (), ( ("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""" vlan_name = None reason = '' identity = "" #TODO prise = "" #TODO items = get_machines(auth_data) decision = 'adherent','' if not items: decision = 'accueil', 'Machine inconnue' machine = items[0] proprio = machine.proprio() if isinstance(machine, lc_ldap.objets.machineWifi): decision = 'wifi', '' 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'): # Cas des personnels logés dans les appartements de l'ENS decision = 'appts', 'Personnel ENS' for bl in machine.blacklist_actif(): if bl.value['type'] in bl_isolement: decision = 'isolement', unicode(bl) if bl.value['type'] in bl_accueil: 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 u': ' + reason).encode(u'utf-8')) ) # # # # 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_OK 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 def detach(p=None): """Appelé lors du déchargement du module (enfin, normalement)""" print "*** goodbye from example.py ***" return radiusd.RLM_MODULE_OK