#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-

"""
Script de déconnexion automatique des machines du crans pour les raisons :
    - upload

Copyright (C) Xavier Pessoles, Étienne Chové, Vincent Bernat, Nicolas Salles
Licence : GPL v2
"""

################################################################################
#                          Import des commandes                                #
################################################################################

import sys
import psycopg2
import smtplib
from time import *

from gestion.config import NETs, plage_ens, prefix
from gestion.config import upload as upload
import gestion.config.encoding
import gestion.config.mails.upload as mails_upload
from gestion.ldap_crans import crans_ldap
from gestion.ldap_crans import MachineFixe

from surveillance.fiche_deconnexion.generate import generate_ps
from gestion.affich_tools import tableau
import surveillance.analyse2 as analyse
import gestion.mail as mail_module

# ldap
ldap = crans_ldap()

# Variables utiles
##################

# Création d'une chaine qui ressemble à :
# (ip_src<<=inet('138.231.136.0/21') or ip_src<<=inet('138.231.148.0/22'))
ip_src_in_crans = '(%s)' % ' or '.join([ "ip_src<<=inet('%s')" % net for net in NETs['all'] ])

# Connections :
###############

# Connection à la base sql via pgsql
pgsql = psycopg2.connect(database='filtrage', user='crans')
pgsql.set_session(autocommit=True)
curseur = pgsql.cursor()

# Le smtp est assez capricieux
def connectsmtp():
    for i in range(5):
        try:
            mail = smtplib.SMTP('localhost')
        except:
            sleep(5)
            if i == 4:
                print "Impossible de se connecter au SMTP"
                sys.exit(1)
    return mail

# Pour trouver la chambre où était la machine que l'on déconnecte.
# TODO: mettre ça dans annuaires_pg
def reperage_chambre(mac):
    pgsql = psycopg2.connect(host="thot.adm.crans.org", database='mac_prises', user='crans')
    # A priori, pas besoin, on ne fait que des select
    pgsql.set_session(autocommit=True)
    curseur = pgsql.cursor()
    requete = "SELECT date, chambre FROM correspondance WHERE mac=%s ORDER BY date DESC LIMIT 1;"
    curseur.execute(requete, (mac,))
    result = curseur.fetchall()
    if result:
        return result[0][0], result[0][1]
    else:
        return "Inconnue", "Inconnue"

################################################################################
#                         Vérification de l'upload                             #
################################################################################

# upload par entité (adhérent/club/machine crans)
requete="""WITH
    machines_sans_doublon
AS
    (
        SELECT DISTINCT ON(mac_addr)
            *
        FROM
            machines
    )
SELECT
    SUM(agregat.total) as tot_upload, machines_sans_doublon.type, machines_sans_doublon.id
FROM (
    SELECT
        'upload', sum(bytes)/1024/1024 AS total, mac_src
    FROM
        upload
    WHERE
        stamp_updated > now() - interval '1 day'
        AND stamp_updated < now()
        AND NOT (ip_dst <<= inet%(plage_ens)s OR ip_dst <<= inet%(plage_ipv6)s OR ip_dst <<= inet%(appt)s OR ip_src <<= inet%(ipv6_local)s OR ip_src=inet'0.0.0.0' OR ip_src <<= inet%(plage_adm)s OR ip_dst <<= inet%(plage_adm)s)
        AND (ip_src <<= inet%(allone)s OR ip_src <<= inet%(alltwo)s OR ip_src <<= inet%(plage_ipv6)s OR ip_src <<= inet%(appt)s)
        AND NOT EXISTS
        (
            SELECT 1
            FROM exemptes
            WHERE upload.ip_src <<= exemptes.ip_crans
            AND upload.ip_dst <<= exemptes.ip_dest
        )
        AND NOT EXISTS
        (
            SELECT 1
            FROM exemptes6
            WHERE upload.mac_src = exemptes6.mac_crans
            AND upload.ip_dst <<= exemptes6.ip_dest
        )
    GROUP BY
        mac_src
    ) AS agregat
INNER JOIN
    machines_sans_doublon
ON
    machines_sans_doublon.mac_addr = agregat.mac_src
GROUP BY
    machines_sans_doublon.type, machines_sans_doublon.id
ORDER BY
    tot_upload;
"""
curseur.execute(requete, {'plage_ens': plage_ens, 'allone': NETs['all'][0], 'alltwo': NETs['all'][1], 'plage_ipv6': prefix['subnet'][0], 'appt': NETs['personnel-ens'][0], 'ipv6_local': 'fe80::/8', 'plage_adm': NETs['adm'][0]})
uploadeurs = curseur.fetchall()



# Table des avertis
###################

# Avertis upload hard
requete = "SELECT type,id FROM avertis_upload_hard where date>timestamp 'now' - interval '1 day'"
curseur.execute(requete)
avertis_upload_hard = curseur.fetchall()

# Avertis upload soft
requete = "SELECT type,id FROM avertis_upload_soft where date>timestamp 'now' - interval '1 day'"
curseur.execute(requete)
avertis_upload_soft = curseur.fetchall()

# Vérification :
################

for elupload, eltype, elid in uploadeurs:
    if elupload >= upload.hard:
        # L'adhérent a t il été blacklisté ?
        ####################################
        if (eltype, elid) in avertis_upload_hard:
            continue

        # Propriétaire issu de LDAP
        ###########################
        if eltype == 'club':
            proprio = ldap.search('cid=%d'%elid, 'w')['club']
            self_call_type = "cid"
        elif eltype == 'adherent':
            proprio = ldap.search('aid=%d'%elid, 'w')['adherent']
            self_call_type = "aid"
        else:
            continue


        if len(proprio) != 1:
            print 'Erreur : Proprio non trouvé (%s) %d'%(eltype, elid)
            continue
        proprio = proprio[0]

        # On cherche à savoir où et quand on
        # a vu les machines du proprio pour la dernière fois
        ####################################################
        machines = proprio.machines()

        macs_dates_chambres = []
        for machine in machines:
            if isinstance(machine, MachineFixe):
               mac = machine.mac()
               date, chambre = reperage_chambre(mac)
               macs_dates_chambres.append([mac, date, chambre])

        mdcf = tableau(macs_dates_chambres, ('mac', 'date', 'chambre'), (20, 21, 7), ('c', 'c', 'c'))

        # Début de remplissage du mail
        ##############################
        if eltype == "club":
            theid = "cid="
        else:
            theid = "aid="
        theid += proprio.id()

        # Test: validation_url('upload')
        try:
            data = {'dn': theid,
                    'blid': len(proprio.blacklist())}
            reco_url = mail_module.validation_url('upload', data)
            reco_url_error = u""
        except Exception as e:
            reco_url_error = u"[[erreur de génération: %r]]" % e
            reco_url = u""

        mail_data = {
            'from': upload.expediteur,
            'to': proprio.email(),
            'upload': "%.2f" % (elupload,),
            'proprio': proprio.Nom(),
            'lang_info':'English version below',
            'mdc': mdcf,
            'chambre': proprio.chbre(),
            'id': theid,
            'reco_url': reco_url,
            'reco_url_error': reco_url_error,
        }

        # On sanctionne
        ###############
        debut = int(time())
        # On ne reconnecte pas auto si url dispo
        if reco_url:
            fin = "-"
        else:
            fin = debut + 24*3600
        # Pour l'analyse
        orig = strftime("%Y/%m/%d %H:%M:%S", localtime(debut - 86400))
        end = strftime("%Y/%m/%d %H:%M:%S", localtime(debut))

        try:
            proprio.blacklist([debut, fin, 'autodisc_upload', "Déconn auto. %s Mo" % elupload])
            proprio.save()
            # On inscrit l'instance dans la table des avertis_hard
            ######################################################
            curseur.execute("INSERT INTO avertis_upload_hard (type,id,date) VALUES ('%s','%d','now')"%(eltype,elid))
            analyse.self_call(["--%s" % (self_call_type), "%s" % (elid), "--dns", "--begin", "%s" % (orig), "--end", "%s" % (end), "--limit", "1000", "--fichier", "/usr/scripts/var/analyse/%s_%s_%s.txt" % (end.replace("/", "_").replace(":", "_").replace(" ", "_"), self_call_type, elid)])
        except Exception as error:
            sys.stderr.write("Blacklist de id=%s pour %s Mo échoué, %s\n" % (proprio.id(), elupload, error))
            continue

        # On envoie un mail à l'adhérent
        ################################
        mail = connectsmtp()

        corps = mail_module.generate('upload_hard', mail_data).as_string()
        mail.sendmail(upload.expediteur, proprio.email(), corps)

        # On envoie un mail à disconnect
        ################################
        mail_data['to'] = upload.expediteur
        corps = mail_module.generate('upload_notif', mail_data).as_string()
        mail.sendmail(upload.expediteur, upload.expediteur, corps)

        # Vérification du nombre de déconnexions
        #########################################
        nb_decos = len([ x for x in proprio.blacklist() if int(x.split('$')[0]) > time()-30*24*3600 and x.split('$')[2] == 'autodisc_upload' ])
        if nb_decos >= upload.max_decos:

            # Génération du fichier postscript
            try:
                fichier_ps = generate_ps('upload', proprio, ldap)
            except:
                fichier_ps = ("ERREUR lors de la génération. Merci de regénérer manuellement la fiche avec la commande :\n"
                    + "/usr/scripts/surveillance/fiche_deconnexion/generate.py --upload aid=%d" % int(proprio.id()))

            # Envoi du mail à disconnect
            corps = mails_upload.message_disconnect_multi % {'from': upload.expediteur, 'to': upload.expediteur, 'nbdeco': nb_decos, 'proprio': proprio.Nom(), 'ps': fichier_ps}
            corps = corps.encode('utf-8')
            mail.sendmail(upload.expediteur, upload.expediteur, corps)

    elif elupload >= upload.soft:
        # L'adhérent a t il été averti ou est déjà déco ?
        #################################################
        if (eltype, elid) in avertis_upload_soft or (eltype, elid) in avertis_upload_hard:
            continue

        # Objets LDAP
        #############
        if eltype == 'club':
            proprio = ldap.search('cid=%d'%elid)['club']
        elif eltype == 'adherent':
            proprio = ldap.search('aid=%d'%elid)['adherent']
        else:
            continue

        if len(proprio) != 1:
            print 'Proprio non trouvé (%s) %d'%(eltype, elid)
            continue
        proprio = proprio[0]

        # On inscrit l'ip dans la table des avertis soft
        ################################################
        curseur.execute("INSERT INTO avertis_upload_soft (type,id,date) VALUES ('%s','%d','now')"%(eltype, elid))

        # On envoie un mail à l'adhérent
        ################################
        mail = connectsmtp()

        corps = mail_module.generate('upload_soft', {'from': upload.expediteur, 'to': proprio.email(), 'upload': "%.2f" % (elupload,), 'proprio': proprio.Nom(), 'lang_info':'English version below', 'limite_soft': upload.soft, 'limite_hard': upload.hard}).as_string()
        mail.sendmail(upload.expediteur, proprio.email(), corps)

        # On envoie un mail à disconnect
        ################################
        if upload.disconnect_mail_soft:
            corps = mails_upload.message_disconnect_soft % {'from': upload.expediteur, 'to': upload.expediteur, 'upload': "%.2f" % (elupload,), 'proprio': proprio.Nom()}
            corps = corps.encode('utf-8')
            mail.sendmail(upload.expediteur, upload.expediteur, corps)

# On supprime les vieux avertisements
curseur.execute("DELETE FROM avertis_upload_hard WHERE date < timestamp 'now' - interval '85200 seconds'") # 23h et 40min pour prolonger les blacklists toujours au dessus de la limite
curseur.execute("DELETE FROM avertis_upload_soft WHERE date < timestamp 'now' - interval '1 day'")

