#! /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 import re from syslog import openlog, syslog, LOG_ERR, LOG_LOCAL4, LOG_INFO, LOG_LOCAL3, LOG_PERROR, LOG_PID, LOG_CRIT 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) # Initialisation de la socket self._dontclean = False try: os.unlink(socket) except: pass os.mkfifo(socket, 0600) os.chown(socket, 67, -1) self.socket = file(socket, "a+") 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_LOCAL4, "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_LOCAL4, "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 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""" syslog(LOG_INFO | LOG_LOCAL4, "Demarrage du demon hotspot") 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 syslog(LOG_INFO | LOG_LOCAL4, "Nouvelle requete") ip = self.socket.readline().strip() syslog(LOG_INFO | LOG_LOCAL4, "Nouvelle requete pour l'IP : %s" % ip) if re.match("^10\.231.*", ip): self.add_ip(ip) # Dans tous les cas, on vire les IP inutiles self.prune_ips() if __name__ == "__main__": s = Server("/var/www/hotspot.socket") s.daemonize() while True: try: s.start() except: import traceback from cStringIO import StringIO ss = StringIO() sys.stderr = ss traceback.print_exc() sys.stderr = sys.__stderr__ syslog(LOG_CRIT | LOG_LOCAL4, "Erreur importante :\n%s" % ss.getvalue()) time.sleep(60) # Pour ne pas flooder...