# -*- coding: utf-8 -*-
#
# IPT.PY -- Gestion du firewall
#
# Copyright (C) 2010 Olivier Huber
# Authors: Olivier Huber <huber@crans.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys
import os, re, syslog, cPickle, socket

from ldap_crans import crans_ldap, hostname
from commands import getstatusoutput
from config import NETs, role, prefix, rid, output_file, filter_policy, rid_primaires
from config import blacklist_sanctions, blacklist_sanctions_soft, blacklist_bridage_upload, file_pickle, ann_scol, periode_transitoire
from iptools import AddrInNet
from ridtools import Rid, find_rid_plage
import subprocess
import netaddr
import ip6tools

blacklist_sanctions_ipv6 = list(blacklist_sanctions)
blacklist_sanctions_ipv6.extend(blacklist_sanctions_soft)
blacklist_sanctions_ipv6.extend(blacklist_bridage_upload)

Mangle_policy = """
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
"""

Raw_policy = """
*raw
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
"""

Filter_policy_template = """
*filter
:INPUT %(policy_input)s [0:0]
:FORWARD %(policy_forward)s [0:0]
:OUTPUT %(policy_output)s [0:0]
"""

dprefix = { 'adherents': 'adherents', 'fil' : 'fil', 'adherents-v6' : 'fil', 'adm' : 'adm', 'wifi' : 'wifi',
        'wifi-adh-v6' : 'wifi','personnel-ens':'personnel-ens', 'serveurs':'serveurs', 'wifi-adh':'wifi',
        'bornes' : 'wifi', 'adm-v6':'adm', 'serveurs-v6':'serveurs'}

default_chains = [ 'PREROUTING', 'INPUT', 'FORWARD', 'OUTPUT', 'POSTROUTING' ]

# On ouvre une connexion avec la base
db = crans_ldap()

##############################################################################
#
# Déclaration des classes pour les règles du firewall
#
##############################################################################

class Chain(object):
    ''' Classe regroupant toutes les règles du firewall '''
    def __init__(self):
        self.items = []
    def __call__(self, cmd):
        self.items.append(cmd)

class Table(object):
    ''' Classe contenant toutes les chaînes du firewall '''
    # TODO voir si on peut pas créer dynamiquement les méthodes
    def __init__(self):
        self.prerouting = Chain()
        self.input = Chain()
        self.forward = Chain()
        self.output = Chain()
        self.postrouting = Chain()
        self.mac = Chain()
        self.macfil = Chain()
        self.macserveurs = Chain()
        self.macadherentsv6 = Chain()
        self.macwifiadhv6 = Chain()
        self.extadherentsv6 = Chain()
        self.extwifiadhv6 = Chain()
        self.cransadherentsv6 = Chain()
        self.cranswifiadhv6 = Chain()
        self.macfilv6 = Chain()
        self.macadm = Chain()
        self.macwifi = Chain()
        self.macwifiv6 = Chain()
        self.extfil = Chain()
        self.extfilv6 = Chain()
        self.extwifi = Chain()
        self.extwifiv6 = Chain()
        self.cransfil = Chain()
        self.cransfilv6 = Chain()
        self.cranswifi = Chain()
        self.cranswifiv6 = Chain()
        self.ieui64 = Chain()
        self.feui64 = Chain()
        self.blacklist_src = Chain()
        self.blacklist_dst = Chain()
        self.srv_out_adm = Chain()
        self.ingress_filtering = Chain()
        self.tracker_torrent = Chain()

class Ip6tables(object):
    ''' Classe pour '''
    def __init__(self):
        self.filter = Table()
        self.mangle = Table()
        self.raw = Table()

    def macip(self, mac, type_m):
        '''Fait la correspondance MAC-IP'''
        if '<automatique>' == mac:
            return
        tab = {'serveurs' : 'fil' }
        if type_m in tab.keys(): type_m = tab[type_m]
        type_mm = re.sub('-', '', type_m)
        getattr(self.filter,'mac' + type_mm)(" ".join(['-m mac --mac-source', mac,
            '-j RETURN']))
#        self.filter.mac(" ".join(['-m mac --mac-source', mac,
#        '-j RETURN']))

    def extcrans(self, type_machine, ports, mac, dev):
        '''Ouverture des ports de l'extérieur vers la zone crans'''
        tab = { 'fil' : 'extfil', 'adherents-v6' : 'extfilv6',
                'wifi' : 'extwifi',
                'wifi-adh-v6' : 'extwifiv6',
                'serveurs':'extfil' }
        ip = ipv6_addr(mac, type_machine)
        if not ip:
            return
        for proto in ['tcp', 'udp']:
            for port in ports[proto]:
                if port != ':':
                    getattr(self.filter,tab[type_machine])('-i %s -p %s -d %s --dport %s -j \
ACCEPT' % (dev, proto, ip, port))
                else:
                    getattr(self.filter,tab[type_machine])('-i %s -p %s -s %s -j ACCEPT' %
                        (dev, proto, ip))

    def cransext(self, type_machine, ports, mac, dev):
        '''Ouverture des ports de la zone crans vers l'extérieur'''
        tab = { 'fil' : 'cransfil', 'adherents-v6' :
        'cransfilv6', 'wifi' : 'cranswifi',
        'wifi-adh-v6' : 'cranswifiv6',
        'serveurs':'cransfil' }
        ip = ipv6_addr(mac, type_machine)
        if not ip:
            return
        for proto in ['tcp', 'udp']:
            for port in ports[proto]:
                if port != ':':
                    getattr(self.filter,tab[type_machine])('-i %s -p %s -s %s --sport %s -j \
ACCEPT' % (dev, proto, ip, port))
                else:
                    getattr(self.filter,tab[type_machine])('-i %s -p %s -s %s -j ACCEPT' %
                        (dev, proto, ip))

    def blacklist(self, machine):
        ''' Met des règles empêchant toute communication
        vers et à partir de la machine considérée '''
        if not machine.rid():
            return
        ident = int(machine.rid())
        ip = ""
        for type_m, plages in rid_primaires.iteritems():
            if type_m in ['special']:
                continue
            for plage in plages:
                if ident in range(plage[0], plage[1]):
                    ip = ipv6_addr(machine.mac(), type_m)
                    mac=machine.mac()
                    break

        if ip:
            self.filter.blacklist_dst('-d %s -j REJECT --reject-with icmp6-adm-prohibited' % ip)
        elif mac == '<automatique>':
            # else: si mac auto, c'est normal de pas avoir pu calculer l'ip
            return
        else:
            print (u"Ipv6 de la machine %s impossible à calculer" % machine.nom()).encode('utf-8')
        self.filter.blacklist_src('-m mac --mac-source %s -j REJECT --reject-with icmp6-port-unreachable' % mac)


    def version(self):
        ''' methode retournant la version du protocole ip
        pour l'instance de la classe '''
        return 6

class Update(object):
    ''' Cette classe comprend toutes les méthodes nécessaires
    à une mise-à-jour ou un régénération partielle du firewall'''
    def ports(self, rids, ip_proto = 4):
        ''' Met à jour les autorisations par machine sur l'ouverture des ports.
        Prend en argument une liste de rid (entier(s)) de machines à changer'''
        check_ip_proto(ip_proto)
        ipt_p = open_pickle(ip_proto)
        dev_crans = iface6('fil')
        if ip_proto == 4:
            dev_ext = iface('ens')
        elif ip_proto == 6:
            dev_ext = iface6('sixxs2')
        net = ""
        for r in rids:
            if not r:
                continue
            net, _ = find_rid_plage(r)
            if net == "Inconnu":
                raise RidError("Il n'y a pas de réseau associé au rid %i" % m)
            if '-v6' in net and ip_proto == 4:
                raise MismatchRidIpProto(r, ip_proto, net)
            machine = db.search('rid=%i' % r)['machine']
            if len(machine) != 1:
                msg = "Il y a %i machines associée(s) au rid %i" % (
                    len(machine), r)
                raise RidError(msg)
            else:
                if ip_proto == 4:
                    ip = Rid(int(m)).to_ipv4()
                elif ip_proto == 6:
                    ip = ipv6_addr(machine[0].mac(), net)
            # On vérifie si la machine a déjà des entrées dans les chaînes
            # On est un peu sous optimal ici
            for sens in ['crans', 'ext']:
                items = getattr(ipt_p.filter,sens + re.sub('-', '',net)).items
                i = 0
                while i < len(items):
                    if ip in items[i] or 'REJECT' in items[i]:
                        del items[i]
                    else:
                        i = i + 1
            ports_io(ipt_p, machine[0], net, dev_ext, dev_crans)
            getattr(ipt_p.filter,'ext' + re.sub('-', '', net))('-j \
REJECT --reject-with icmp6-port-unreachable')
            getattr(ipt_p.filter,'crans' + re.sub('-', '', net))('-j \
REJECT --reject-with icmp6-port-unreachable')
        # On écrit et applique les règles
        write_rules(ipt_p)
        apply_rules(ip_proto)

        os.remove(file_pickle[ip_proto])

        # On sauve les chaînes
 
        save_pickle(ipt_p)
        return 0

    def blacklist(self, ip_proto = 4):
        ''' Permet de regénérer les diverses blacklistes '''
        #TODO vérifier que la fonctionn est _vraiement_ ipv4 aware
        check_ip_proto(ip_proto)

        # On supprime les anciennes règles si elles existent.
        try:
            os.remove(output_file[ip_proto])
        except:
            pass

        # On initialise un pare-feu vide
        ip6tables = Ip6tables()
        blacklist(ip6tables)

        # On écrit les données dans un fichier
        write_rules(ip6tables)

        #On vide les chaines des BLACKLIST_*
        flush_chain(ip_proto, ['BLACKLIST_DST', 'BLACKLIST_SRC'])
        # On les applique en concervant les chaines existantes
        apply_rules(ip_proto, no_flush=True)


        #### BACKWARD compatibilité
        ipt_p = open_pickle(ip_proto)
        blacklist(ipt_p)
        # On écrit et applique les règles <-- plus la peine de restorer tout, on ne traite que les chaines BLACKLIST_* ci dessus.
        # On pickel toujours pour la compatibilité avec les autres fonctions de la classe Update
        #write_rules(ipt_p)
        #apply_rules(ip_proto)

        os.remove(file_pickle[ip_proto])

        # On sauve les chaînes
        save_pickle(ipt_p)
        return 0

    def macs(self, macs, ip_proto = 4):
        ''' Méthode temporaire le temps de trouver une solution plus élégante
        l'interface devrait être la même que pour la fonction finale'''
        check_ip_proto(ip_proto)
        ipt_p = open_pickle(ip_proto)
        for type_m in ['fil', 'adherents-v6', 'adm', 'serveurs']:
            type_mm = re.sub('-', '', type_m)
            getattr(ipt_p.filter,'mac%s' % type_mm).items[:] = []
        machines = db.all_machines(graphic = True)
        macips(ipt_p, machines, ['fil', 'adherents-v6', 'adm', 'serveurs'])

        write_rules(ipt_p)
        apply_rules(ip_proto)
        os.remove(file_pickle[ip_proto])
        save_pickle(ipt_p)
        return 0
# Il faut voir comment passer les mac en argument
#    def macs(self, macs, ip_proto = 4):
#        ''' Fonction pour mettre à jour les adresses mac acceptées sur le réseau.
#        Prend comme argument un dictionnaire ayant pour clé 'remove' et 'add',
#        contenant respectivement les adresses mac a ôter et celles à ajouter'''
#        #TODO vérifier que la fonctionn est _vraiement_ ipv4 aware
#        ipt_p = open_pickle(ip_proto)
#        # On modifie la chaîne mac
#        nb = len(ipt_p.filter.mac.items)
#        for mac in macs['remove']:
#            for i in range(1, nb):
#                if mac in ipt_p.filter.mac.items[i]:
#                    ipt_p.filter.mac.items.remove[i]
#                    break
#            print "Erreur, la mac " + mac + " n'est pas dans la chaîne."
#    
#        for mac in macs['add']:
#            ipt_p.macip(mac)
#        
#        # On écrit et applique les règles
#        write_rules(ipt_p)
#        apply_rules(6)
#        
#        os.remove(file_pickle[ip_proto])
#    
#        # On sauve les chaînes
#        save_pickle(ipt_p)
#        return 0
#
##############################################################################
#
# Déclaration des exceptions
#
##############################################################################

class NoIface(Exception):
    ''' Exception invoquée lorsqu'il n'y a pas
    d'interface associé à un réseau'''
    def __init__(self, net, msg):
        self.net = net
        self.msg = msg
        syslog.syslog(syslog.LOG_ERR,"Pas d'interface associé à %s"
                % self.net)
        syslog.syslog(syslog.LOG_ERR,"Résultat de la commande : \n %s"
                % self.msg)
    def __str__(self):
        txt = "Pas d'interface associé à %s \n \
              Résultat de la commande : \n %s" % (self.net, self.msg)
        return txt

class Iproute2Error(Exception):
    ''' Exception invoquée lorsqu'un appel à iproute2 renvoie une erreur'''
    def __init__(self, cmd, code, msg):
        self.cmd = cmd
        self.code = code
        self.msg = msg
        syslog.syslog(syslog.LOG_ERR,"%s : %s %s" % (self.cmd, self.code,
            self.msg))
    def __str__(self):
        return "%s: %s %s" % ( self.cmd, self.code, self.msg)

class NoRtTable(Exception):
    ''' Exception invoquée lorsqu'une table de routage n'existe pas'''
    def __init__(self, msg):
        self.msg = msg
        syslog.syslog(syslog.LOG_ERR,"La table donnée n'existe pas : %s" %
                self.msg)
    def __str__(self):
        return "La table donnée n'existe pas : %s" % self.msg

class ApplyRulesError(Exception):
    ''' Exception invoquée lorsque l'application des règles par
    ip(6|)tables-restore échoue '''
    def __init__(self, cmd, code, msg):
        self.cmd = cmd
        self.code = code
        self.msg = msg
        syslog.syslog(syslog.LOG_ERR,"%s : %s \n %s" %
                (self.cmd, self.code, self.msg))
    def __str__(self):
        return "%s : %s \n %s" % (self.cmd, self.code, self.msg)

class SysctlError(Exception):
    ''' Exception invoquée lorsque la modification d'un état dans /proc/sys
    échoue '''
    def __init__(self, cmd, code, msg):
        self.cmd = cmd
        self.code = code
        self.msg = msg
        syslog.syslog(syslog.LOG_ERR,"%s : %s %s" % (self.cmd, self.code,
            self.msg))
    def __str__(self):
        return "%s: %s %s" % ( self.cmd, self.code, self.msg)

class Ip6tablesError(Exception):
    def __init__(self, cmd, code, msg):
        self.cmd = cmd
        self.code = code
        self.msg = msg
        syslog.syslog(syslog.LOG_ERR,"%s : %s %s" % (self.cmd, self.code,
            self.msg))
    def __str__(self):
        return "%s: %s %s" % ( self.cmd, self.code, self.msg)

class RidError(Exception):
    ''' Exception invoquée lorsqu'il y a un soucis avec un rid fourni '''
    def __init__(self, msg):
        self.msg = msg
        syslog.syslog(syslog.LOG_ERR,"%s" % self.msg)
    def __str__(self):
        return "%s" % self.msg

class MismatchRidIpProto(Exception):
    ''' Exception invoquée lorsqu'il y a un défaut d'appariement entre le rid
    et le protocole ip concerné'''
    def __init__(self, mrid, ip_proto, net):
        self.mrid = mrid
        self.ip_proto = ip_proto
        self.net = net
        self.msg = "Défaut d'appariement entre le rid %i (%s) \
et le protocole ip %i" % (self.mrid, self.net, self.ip_proto)
        syslog.syslog(syslog.LOG_ERR, self.msg)
    def __str__(self):
        return self.msg

class IpProtoError(Exception):
    ''' Exception quand le protocole ip fourni est inconnu '''
    def __init__(self, proto):
        self.msg = "Le protocole ip %i est inconnu" % proto
        syslog.syslog(syslog.LOG_ERR,"%s" % self.msg)
    def __str__(self):
        return "%s" % self.msg

class UnknowUserError(Exception):
    ''' Exception quand l'utilisateur demandé n'existe pas sur la machine '''
    def __init__(self, user):
        self.msg = "L'utilisateur %s est inconnu" % user
        syslog.syslog(syslog.LOG_ERR,"%s" % self.msg)
    def __str__(self):
        return "%s" % self.msg


##############################################################################
#
# Déclaration des fonctions 
#
##############################################################################

def gethostbyname(hostname):
    hosts4=[]
    hosts6=[]
    try : 
        for host in socket.getaddrinfo(hostname,None,socket.AF_INET,socket.IPPROTO_IP,socket.AI_CANONNAME):
            hosts4.append(host[4][0])
    except(socket.gaierror): pass
    try : 
        for host in socket.getaddrinfo(hostname,None,socket.AF_INET6,socket.IPPROTO_IP,socket.AI_CANONNAME):
            hosts6.append(host[4][0])
    except(socket.gaierror): pass
    return (hosts4,hosts6)

def check_ip_proto(ip_proto):
    ''' Vérifie que le protocole ip fourni est valide '''
    if ip_proto != 4 and ip_proto != 6:
        raise IpProtoError(ip_proto)

def ipv6_addr(mac, net):
    ''' Renvoie l'adresse ipv6 d'auto-configuration de la mac sur le réseau '''
    return ip6tools.mac_to_ipv6(prefix[dprefix[net]][0], mac)

def mac_addr(ipv6):
    ''' Renvoie l'adresse mac de l'ipv6 d'auto-configuration '''
    ipv6_s= ipv6.split(':')[4:]
    mac=''
    if ipv6_s[1].endswith('ff') and ipv6_s[2].startswith('fe'):
       elt = "%04x" % int(ipv6_s[0], 16)
       mac += "%02x" % (int(elt[0:2],16) ^ 0x02) + ':' + elt[2:]
       elt = "%04x" % int(ipv6_s[1], 16)
       mac += ':' + elt[0:2]
       elt = "%04x" % int(ipv6_s[2], 16)
       mac += ':' + elt[2:]
       elt = "%04x" % int(ipv6_s[3], 16)
       mac += ':' + elt[0:2] + ':' + elt[2:]
       return mac
    return None

# TODO Fusionner les deux fonctions.
def iface(net):
    '''Retourne l'interface réseau associée à un certain type de réseau
    Pour l'instant on se base sur l'ipv4 pour identifier l'interface'''
    if net == 'adherents-v6':
        net = 'fil'
    if net == 'wifi-adh-v6':
        net = 'wifi'
    cmd = "ip a show| egrep 'inet[^6]'"
    code, msg = getstatusoutput(cmd)
    if code:
        raise Iproute2Error(cmd, code, msg)
    output = msg.splitlines()
    for line in output:
        if AddrInNet(line.split()[1].split('/')[0], NETs[net]):
            return line.split()[-1]

    raise NoIface(net, msg)

def iface6(net):
    '''Retourne l'interface réseau associée à
    un certain type de réseau ipv6'''
    cmd = "ip -6 a show"
    code, msg = getstatusoutput(cmd)
    if code:
        raise Iproute2Error(cmd, code, msg)
    output = msg.splitlines()
    subnet = netaddr.IPNetwork(prefix[net][0])
    i = 0
    while i < len(output):
        if re.match('^[0-9]+:', output[i]):
            dev = re.sub('(@.*|:)', '', output[i].split()[1])
            i = i + 1
            while (i < len(output)) and ('inet6' in output[i]):
                if  netaddr.IPNetwork(output[i].split()[1]) == subnet:
                    return dev
                else:
                    i = i + 2
        else:
            i = i + 1

    raise NoIface(net, msg)

 
def check_table(table):
    ''' Vérifie que la table existe bien '''
    ctables = open('/etc/iproute2/rt_tables', 'r')
    rt_tables = ctables.readlines()
    tables = [item for item in rt_tables if not re.match('#', item)]
    if any(re.search(table, elt) for elt in tables):
        return 0 
    else:
        raise NoRtTable(table)

def custom_default_route(table, default, net, ip_proto = 4):
    ''' Permet de créer une route par default personnalisée'''
    check_ip_proto(ip_proto)
    check_table(table)
    dev = iface(net)
    cmd = "ip -%i route add table %s default via %s dev %s" % (ip_proto, table,
            default, dev)
    code, msg = getstatusoutput(cmd)
    if code:
        raise Iproute2Error(cmd, code, msg)

def custom_subnet_route(table, subnet, net, ip_proto = 4):
    ''' Permet de créer une route personnalisée'''
    check_ip_proto(ip_proto)
    check_table(table)
    dev = iface(net)
    cmd = "ip -%i route add table %s from %s dev %s" % (ip_proto, table, subnet,
        dev)
    code, msg = getstatusoutput(cmd)
    if code:
        raise Iproute2Error(cmd, code, msg)

def custom_subnet_via_route(table, subnet, net, via, ip_proto = 4):
    ''' Permet de créer une route personnalisée'''
    check_ip_proto(ip_proto)
    check_table(table)
    dev = iface(net)
    cmd = "ip -%i route add table %s from %s via %s dev %s" % (ip_proto, table,
    subnet, via, dev)
    code, msg = getstatusoutput(cmd)
    if code:
        raise Iproute2Error(cmd, code, msg)

def custom_ip_rule(table, subnet, ip_proto = 4):
    ''' Ajout d'un règle de routage personnalisée basée
    sur un sous-reseaux de provenance'''
    check_ip_proto(ip_proto)
    check_table(table)
    cmd = "ip -%i rule add from %s table %s" % (ip_proto, subnet, table)
    code, msg = getstatusoutput(cmd)
    if code:
        raise Iproute2Error(cmd, code, msg)


def custom_mark_rule(table, fw_mark, ip_proto = 4):
    ''' Ajout d'une règle de routage personnalisée basée sur une marque'''
    check_ip_proto(ip_proto)
    check_table(table)
    cmd = "ip -%i rule add fwmark %s table %s" % (ip_proto, fw_mark, table)
    code, msg = getstatusoutput(cmd)
    if code:
        raise Iproute2Error(cmd, code, msg)

def srv(attribute):
    ''' Renvoie le (ou les) serveur(s) qui possède l'attribut '''
    return [ server for server, attr in role.iteritems() if (attribute in attr)]

def ip_attribut(attribute, cl, ip_proto = 4):
    ''' Renvoie l'ip du serveur qui possède l'attribut '''
    check_ip_proto(ip_proto)
    host = srv(attribute)[0]
    #Gestion erreur ?
    # Autre net que fil ?
    if ip_proto == 4:
        return cl.search('host=%s.crans.org' % host)['machine'][0].ip()
    elif ip_proto == 6:
        mac = cl.search('host=%s.crans.org' % host)['machine'][0].mac()
        if mac:
            return ipv6_addr(mac, 'fil')
        else:
            print "Error" + " host=%s.crans.org" % name

def flush_chain(ip_proto = 4, chain_list = []):
    check_ip_proto(ip_proto)
    f = open(output_file[ip_proto], 'r')
    if ip_proto == 4:
        cmd = '/sbin/iptables'
    elif ip_proto == 6:
        cmd = '/sbin/ip6tables'

    for chain in chain_list:
        run = [cmd, '-F', chain]
        try:
            p1 = subprocess.Popen(run, stdin=f, stdout=subprocess.PIPE)
        except OSError:
            syslog.syslog(syslog.LOG_ERR,"La chaine %s n'existe pas" % chain)
            print "La chaine %s n'existe pas" % chain
        stdout, stderr = p1.communicate()

def apply_rules(ip_proto = 4, no_flush=False):
    ''' Applique les règles fournies dans le fichier passé en arguement '''
    check_ip_proto(ip_proto)
    f = open(output_file[ip_proto], 'r')
    if ip_proto == 4:
        cmd = '/sbin/iptables-restore'
    elif ip_proto == 6:
        cmd = '/sbin/ip6tables-restore'
    if no_flush:
        cmd = [cmd, '--noflush']
    try:
        p1 = subprocess.Popen(cmd, stdin=f, stdout=subprocess.PIPE)
    except OSError:
        syslog.syslog(syslog.LOG_ERR,"Le fichier %s n'existe pas" % f.name)
        print "Le fichier %s n'existe pas" % f.name
    stdout, stderr = p1.communicate()
    if p1.returncode < 0:
        raise ApplyRulesError(cmd, -p1.returncode, 'STDOUT\n' + stdout +
                'STDERR\n' + stderr )

def enable_forwarding(ip_proto = 4):
    ''' Autorise le forwarding sur la machine considérée '''
    check_ip_proto(ip_proto)
    cmd = 'echo 1 > /proc/sys/net/ipv%i/conf/all/forwarding' % ip_proto
    code, msg = getstatusoutput(cmd)
    if code:
        raise SysctlError(cmd, code, msg)

def disable_forwarding(ip_proto = 4):
    ''' Désactive le forwarding sur la machine considérée '''
    check_ip_proto(ip_proto)
    cmd = 'echo 0 > /proc/sys/net/ipv%i/conf/all/forwarding' % ip_proto
    code, msg = getstatusoutput(cmd)
    if code:
        raise SysctlError(cmd, code, msg)


def not_private(arg):
    ''' Retourne un boolén suivant que la fonction passé en argument 
    est privée ou non'''
    if re.match('^_.*', str(arg)):
        return False
    else:
        return True

def open_pickle(ip_proto = 4):
    ''' Cette fonction ouvre un fichier contenant un dump d'une classe
    Ip(|6)tables'''
    check_ip_proto(ip_proto)
    try:
        f = open(file_pickle[ip_proto], 'r')
    except OSError:
        syslog.syslog(syslog.LOG_ERR,"Le fichier %s n'existe pas" %
                file_pickle[ip_proto])
        print "Le fichier %s n'existe pas" % file_pickle[ip_proto]
    #XXX gestion des erreurs
    return cPickle.load(f)

def save_pickle(ipt):
    ''' Cette fonction sauvegarde une instance de Ip(|6)tables'''
    fout = open(file_pickle[ipt.version()], 'w')
    cPickle.dump(ipt, fout)
    fout.close()

def write_rules(ipt):
    ''' Cette fonction écrit les règles dans un fichier '''
    # TODO vérifier que la fonctionn est _vraiement_ ipv4 aware
    ip_proto = ipt.version()
    fout = open(output_file[ip_proto], 'w')

    # On recherche si la machine a des politiques particulières
    if hostname in filter_policy.keys():
        key = hostname
    else:
        key = 'default'

    # On applique les politiques par défaut sur les chaînes canoniques
    output = { 'filter' : Filter_policy_template % filter_policy[key],
            'mangle' : Mangle_policy,
            'raw' : Raw_policy
            }

    # On parcours une première fois l'instance pour initialiser correctement
    # nos chaînes 
    for itables in ['filter', 'mangle', 'raw']:
        for ichain in filter(not_private,
                dir(ipt.__getattribute__(itables))):
            if ichain.upper() not in default_chains:
                if hasattr(ipt.__getattribute__(itables).__getattribute__(ichain),'items') and ipt.__getattribute__(itables).__getattribute__(ichain).items:
                    output[itables] += ":%s - [0:0]\n" % ichain.upper()

    # On ajoute maintenant les règles
    for itables in ['filter', 'mangle', 'raw']:
        for ichain in filter(not_private,
                dir(ipt.__getattribute__(itables))):
            for rule in ipt.__getattribute__(itables).__getattribute__(ichain).items:
                output[itables] += '-A ' + ichain.upper() + ' ' + rule + '\n'
    output['filter'] += '-A FORWARD -j REJECT\n'

    # Ecriture dans le fichier
    fout.writelines(output['filter'])
    fout.write('COMMIT\n')
    fout.write('')
    fout.writelines(output['mangle'])
    fout.write('COMMIT\n')
    fout.write('')
    fout.writelines(output['raw'])
    fout.write('COMMIT\n')

    fout.close()


def blacklist(ipt):
    ''' Permet de peupler les chaînes concernant les diverses blacklistes '''

    blcklst = []

    s = db.search('nom=*')

    for target in s['adherent'] + s['club']:
        sanctions = target.blacklist_actif()
        if [x for x in sanctions if x in blacklist_sanctions_ipv6]:
            blcklst.extend(target.machines())

    s = db.search('mblacklist=*&paiement=%s' % ann_scol)
    if periode_transitoire:
        s['machine'].extend(db.search('mblacklist=*&paiement=%s' % (ann_scol-1))['machine'])
            
    for target in s['machine']:
        sanctions = target.blacklist_actif()
        if [x for x in sanctions if x in blacklist_sanctions_ipv6]:
            blcklst.append(target)

    if ipt.filter.blacklist_src.items:
        ipt.filter.blacklist_src.items[:] = []
    if ipt.filter.blacklist_dst.items:
        ipt.filter.blacklist_dst.items[:] = []

    print "%s machines blacklistées" % len(blcklst)
    for machine in blcklst:
        ipt.blacklist(machine)

    return 0

def ports_io(ipt, machine, type_machine, dev_ext, dev_crans):
    ''' Fonction ouvrant les ports d'une machine selon le contenu de la base
    LDAP'''
    ports_in = { 'tcp' : machine.portTCPin(),
        'udp' : machine.portUDPin() }
    ports_out = { 'tcp' : machine.portTCPout(),
        'udp' : machine.portUDPout() }
    if ports_in.values() != [[], []]:
        ipt.extcrans(type_machine, ports_in, machine.mac(),
            dev_ext)
    if ports_out.values() != [[], []]:
        ipt.cransext(type_machine, ports_out, machine.mac(),
            dev_crans)

def mac_ip(ipt, machines, types_machines):
    '''Réalise une correspondance MAC-IP pour un réseau considéré
    On vérifie d'abord que la MAC est connue et ensuite on n'accepte que les
    adresses en eui64'''
    tab = {'serveurs' : 'fil' }
    macips(ipt, machines, types_machines)
    for type_m in types_machines:
        if not '-v6' in type_m and not type_m in tab.keys():
            try:
                dev = iface6(type_m)
                ipt.filter.input('-i %s -s %s -j %s' % (dev, prefix[type_m][0],
                    'MAC' + type_m.upper()))
                ipt.filter.input('-i %s -j IEUI64' % dev)
            except NoIface as e:
                sys.stderr.write("NoIface: %s" % e)

    # On active les extensions de vie privée
    for net in prefix['subnet']:
       ipt.filter.ieui64('-s %s -j RETURN' % net)
    # Pour le lien local, on n'accepte que les eui64
    ipt.filter.ieui64('-s fe80::/64 -m eui64 -j RETURN')
    ipt.filter.ieui64('-j REJECT')

def macips(ipt, machines, types_machines):
    ''' Construit la chaîne MAC '''
    tab = {'serveurs' : 'fil' }

    for machine in machines:
        if not machine.rid():
            continue
        for type_m in types_machines:
            for plage in rid[type_m]:
                if int(machine.rid()) in range(plage[0], plage[1]):
                    ipt.macip(machine.mac(), type_m)
                    break
 
    for type_m in types_machines:
        if not type_m in tab.keys():
            type_mm = re.sub('-', '', type_m)
            getattr(ipt.filter,'mac' + type_mm)('-j REJECT')
            #eval('ipt.filter.mac' + type_mm)('-j REJECT')
    return 0

def ingress_filtering(ipt):
    ''' Réalise un filtre sur les plages d'IP susceptibles d'être routées '''
    ip_proto = ipt.version()
    if ip_proto == 6:
        dev_ext = iface6('sixxs2')
        # d'abord sur l'interface sur le réseau Cr@ns, on ne route que les
        # paquet dans le bon subnet.
        ipt.filter.ingress_filtering('-o %s -s %s -j RETURN' % (dev_ext,
            prefix['subnet'][0]))
        ipt.filter.ingress_filtering('-o %s -j LOG --log-prefix "BAD ROUTE "' %
                dev_ext)
        ipt.filter.ingress_filtering('-o %s -j REJECT' % dev_ext)
        # de l'extérieur, on ne veut que des paquet ne provenant pas de notre
        # réseau à destination de notre réseau
        ipt.filter.ingress_filtering('-i %s ! -s %s -d %s -j RETURN' %
                (dev_ext, prefix['subnet'][0], prefix['subnet'][0]))
        ipt.filter.ingress_filtering('-i %s -j LOG --log-prefix "BAD SRC "' %
                dev_ext)
        ipt.filter.ingress_filtering('-i %s -j REJECT' % dev_ext)
