scripts/freeradius/auth.py
2013-11-17 11:49:54 +01:00

196 lines
6.6 KiB
Python
Executable file

#!/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
# 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']
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, 'lc_ldap: Nobody found')
return radiusd.RLM_MODULE_NOTFOUND
if len(items) > 1:
radiusd.radlog(radiusd.L_ERR, 'lc_ldap: Too many results')
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):
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')
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, conn)
decision = 'adherent',''
if not items:
decision = 'accueil', 'Machine inconnue'
machine = items[0]
proprio = machine.proprio()
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'):
# 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).encode('utf-8')
if bl.value['type'] in bl_accueil:
decision = 'accueil', unicode(bl).encode('utf-8')
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))
)
#<!>
#
# # 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(p=None):
"""Appelé lors du déchargement du module (enfin, normalement)"""
print "*** goodbye from example.py ***"
return radiusd.RLM_MODULE_OK