From 552d7673e5af2f7d1531e023423feb5286bfc364 Mon Sep 17 00:00:00 2001 From: bernat Date: Wed, 9 Nov 2005 11:22:26 +0100 Subject: [PATCH] Gestion du hotspot, import initial darcs-hash:20051109102226-d1718-26be50de87fd13d1c0ea11772f39b8d0921669ed.gz --- wifi/hotspot.py | 138 ++++++++++++++++++++++++++++++++++++++++++++++++ wifi/lease.py | 59 +++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100755 wifi/hotspot.py create mode 100755 wifi/lease.py diff --git a/wifi/hotspot.py b/wifi/hotspot.py new file mode 100755 index 00000000..e6aba22b --- /dev/null +++ b/wifi/hotspot.py @@ -0,0 +1,138 @@ +#! /usr/bin/env python +# -*- encoding: iso-8859-15 -*- + +# Gestion du portail captif : +# - lecture d'une socket pour les IP à ajouter +# - lecture du fichier de lease DHCP pour les expirer + +import os +from popen2 import Popen4 +import sys +import time +from syslog import openlog, syslog, LOG_ERR, LOG_LOCAL4, LOG_INFO, LOG_LOCAL3, LOG_PERROR, LOG_PID +from lease import Leases +from select import select + +class Server: + """Serveur (sur socket) gérant le hotspot""" + + def __init__(self, socket): + """Initialise le serveur. + + Il écoutera sur le pipe nommé `socket'.""" + # syslog + openlog("hotspot.py", LOG_PERROR | LOG_PID) + syslog(LOG_INFO | LOG_LOCAL4, "Demarrage du demon hotspot") + # Initialisation de la socket + self._socket_name = socket + self._dontclean = False + os.mkfifo(socket, 0600) + self.socket = file(socket, "a+") + + def __del__(self): + try: + self.socket.close() + except: + pass + if not self._dontclean: + os.unlink(self._socket_name) + + def execute(self,cmd): + """Exécute une commande. + + Gère les erreurs par log dans syslog.""" + p = Popen4(cmd) + p.tochild.close() + out = p.fromchild.read() + p.fromchild.close() + exit = os.WEXITSTATUS(p.wait()) + if exit: + # Une erreur a eu lieu + syslog(LOG_ERR | LOG_LOCAL4, + "Error %d executing `%s' : %s" % (exit, cmd.strip(), out.strip())) + else: + return out + + def add_ip(self,ip): + """Ajout d'une IP au firewall""" + cmd = "pfctl -t clients_hotspot_autorises -Tadd %s" % ip + if self.execute(cmd): + syslog(LOG_INFO | LOG_LOCAL3, "Nouveau client hotspots: %s" % ip) + + + def del_ip(self,ip): + """Retire une règle du firewall""" + cmd = "pfctl -t clients_hotspot_autorises -Tdelete %s" % ip + if self.execute(cmd): + syslog(LOG_INFO | LOG_LOCAL3, "Suppression du client: %s" % ip) + + def list_ips(self): + """Liste les IP actuellement dans le firewall""" + cmd = "pfctl -t clients_hotspot_autorises -Tshow" + ips = self.execute(cmd) + if not ips: + return [] + return filter(lambda y: y.strip() != '', + map(lambda x: x.strip(), ips.split("\n"))) + + def prune_ips(self): + """Vire les IP qui ne sont plus utilisées""" + l = Leases().leases + deleted = False + for ip in self.list_ips(): + match = filter(lambda x: x.ip == ip, l) + if not match: + # Plus de bail du tout + self.del_ip(ip) + deleted = True + else: + # On vérifie si le bail n'a pas expiré depuis plus de une minute + if match[0].end < time.gmtime(time.time()-60): + self.del_ip(ip) + deleted=True + if deleted: + syslog(LOG_INFO | LOG_LOCAL3, "Clients hotspots: %s" % (", ".join(self.list_ips())) or "aucun") + + def daemonize(self): + """Passe en arrière plan""" + # Fork + if os.fork() > 0: + self._dontclean = True + sys.exit(0) + # Bon répertoire + os.chdir("/") + # Masque indépendant de l'utilisateur + os.umask(022) + # Chef de file + os.setsid() + # Second fork pour avoir son groupe + if os.fork() > 0: + self._dontclean = True + sys.exit(0) + # Redirection des E/S + si = file('/dev/null', 'r') + so = file('/dev/null', 'a+') + se = file('/dev/null', 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + def start(self): + """Boucle principale""" + s = self.socket.fileno() + while True: + # On attend du monde sur la socket + result = select([s],[],[],60)[0] + if result: + # On a quelqu'un, on lit une ligne + ip = self.socket.readline().strip() + self.add_ip(ip) + # Dans tous les cas, on vire les IP inutiles + self.prune_ips() + + +if __name__ == "__main__": + s = Server("/tmp/hotspot.socket") + s.daemonize() + s.start() + diff --git a/wifi/lease.py b/wifi/lease.py new file mode 100755 index 00000000..62c3c63d --- /dev/null +++ b/wifi/lease.py @@ -0,0 +1,59 @@ +#! /usr/bin/env python +# -*- encoding: iso-8859-15 -*- + +# Gestion en lecture d'un fichier lease (issu du DHCPD d'Open) + +import re +import time + +class Lease: + """Classe représentant un bail (lease).""" + + def __init__(self, texte): + """Instanciation d'un bail + + L'instanciation se fait en fournissant `texte' qui est la + forme textuelle du bail qui se trouve dans + /var/db/dhcpd.leases de la forme : + + lease 10.231.149.89 { + starts 3 2005/11/09 07:58:52; + ends 3 2005/11/09 08:13:52; + hardware ethernet 00:0c:f1:37:54:2c; + uid 01:00:0c:f1:37:54:2c; + client-hostname "FCDVIRELY"; + } + """ + mo = re.search("\\blease ([0-9\\.]*) \\{", texte) + if not mo: + raise ValueError, u"Pas de bail trouvé" + self.ip = mo.group(1) + + mo = re.search("\\bstarts [0-7] ([0-9/]* [0-9:]*);", texte) + if not mo: + raise ValueError, u"Pas de date de début de bail" + self.start = time.strptime(mo.group(1), "%Y/%m/%d %H:%M:%S") + + mo = re.search("\\bends [0-7] ([0-9/]* [0-9:]*);", texte) + if not mo: + raise ValueError, u"Pas de date de fin de bail" + self.end = time.strptime(mo.group(1), "%Y/%m/%d %H:%M:%S") + + mo = re.search("\\bhardware ethernet ([0-9a-f:]*);", texte) + if not mo: + raise ValueError, u"Pas d'adresse Ethernet pour le bail" + self.mac = mo.group(1) + + # On n'est pas intéressé par le reste + +class Leases: + """Classe représentant un ensemble de bails.""" + + def __init__(self, fichier="/var/db/dhcpd.leases"): + """Instanciation à partir du fichier de leases.""" + self.leases = [] + # Méthode rapide : + for lease in " ".join(file(fichier).readlines()).split("}")[:-1]: + self.leases.append(Lease(lease)) + +