#! /usr/bin/env python
# -*- coding: utf-8 -*-

# Serveur SSL qui renvoie la MAC correspondant à une IP
# Ce serveur est utilisé sur Nectaris par les bornes wifi pour faire
# les requêtes ARP.

# Lancement : twistd -n -y arp.py --pidfile=/var/run/arp.pid
# Pas de -n pour qu'il passe en fond

# On utilise twisted
# http://twistedmatrix.com/documents/current/howto/tutorial/intro
from twisted.internet import protocol, reactor, defer, utils
from twisted.internet.ssl import ContextFactory
from twisted.protocols import basic
from twisted.application import internet, service

import sys
sys.path.append('/usr/scripts/gestion')

# LDAP
from ldap_crans import crans_ldap

# Divers
from iptools import AddrInNet
from OpenSSL import SSL
import re

class ServerContextFactory(ContextFactory):

    def getContext(self):
        """Création d'un contexte SSL côté serveur."""
        ctx = SSL.Context(SSL.SSLv23_METHOD)
        ctx.use_certificate_file('/etc/ssl/certs/nectaris.pem')
        ctx.use_privatekey_file('/etc/ssl/private/nectaris.pem')
        return ctx

class ARPProtocol(basic.LineReceiver):
    """Protocole de communication pour récupérer une adresse MAC.

    Chaque lignee reçue contient une adresse IP et le serveur répond
    avec l'adresse MAC correspondante ou avec `unknown' si celle-ci
    est inconnue (ou non autorisée).
    """
    def lineReceived(self, IP):
        self.factory.getMac(IP
                     ).addErrback(lambda _: "Internal error in server"
                     ).addCallback(lambda m:
                                   (self.answerMac(m)))

    def answerMac(self, m):
        """Renvoie au client la bonne adresse MAC.

        `m' peut être None.
        """
        if m:
            return self.transport.write(str(m) + "\r\n")
        else:
            return self.transport.write("unknown\r\n")

class ARPFactory(protocol.ServerFactory):
    """Backend du serveur. Effectue la résolution IP/MAC."""
    protocol = ARPProtocol

    def __init__(self, network='0.0.0.0/0'):
        self.ldap = crans_ldap()
        self.network = network

    def getMac(self, IP):
        return defer.succeed(self._getMac(IP))

    def _getMac(self, IP):
        """Fonction réelle, synchrone, qui obtient l'adresse MAC."""
        try:
            if AddrInNet(IP, self.network):
                results = self.ldap.search('ip=%s' % IP)['machine']
                if results:
                    # On a au moins un résultat, on retourne le premier
                    return results[0].mac().strip()
                else:
                    # On a rien
                    return None
            else:
                # L'IP n'est pas autorisée
                return None
        except ValueError:
            # A priori, ce n'était pas une IP
            return None

# Corps du programme
# On écoute sur le port 5243 et on ne répond qu'aux requêtes concernant le
#  sous-réseau wifi
application = service.Application('arp')
factory = ARPFactory('138.231.148.0/21')
# Remplacer SSL par TCP pour ne pas utiliser SSL et virer ServerContextFactory
internet.SSLServer(5243, factory,
                   ServerContextFactory()).setServiceParent(service.IServiceCollection(application))
