#! /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...

