#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf8 -*-
#
# Sclaimer
# PEB : 01/02/2013 -> now()
#
# Blablabla. (cf mac_prise.py)

import psycopg2
import psycopg2.extras
import sys
import smtplib
import time

if '/usr/scripts' not in sys.path:
    sys.path.append('/usr/scripts')

from gestion.config import mac_prise
from gestion.affich_tools import tableau
import lc_ldap.shortcuts
import lc_ldap.objets
import collections
from email.header import Header

ldap = lc_ldap.shortcuts.lc_ldap_local()

conn = psycopg2.connect(user='crans', database='mac_prises')
conn.set_session(autocommit = True)
cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor)

membres_actifs = ldap.search(u'(|(droits=Cableur)(droits=Nounou)(droits=Apprenti)(droits=Bureau))')
chambres_ma = []
for membre_actif in membres_actifs:
    try:
        chambres_ma.append(str(membre_actif['chbre'][0]).lower())
    except:
        pass

clubs = ldap.search(u'cid=*')
chambres_clubs = []
for club in clubs:
    try:
        chambres_clubs.append(str(club['chbre'][0]).lower())
    except:
        pass

longueur = (24, 7)
titres = (u'mac', u'chambres')
alignements = ('c', 'c')

def lin(x, y, z):
    """
    Calcul linéaire d'un rapport
    """
    return (float(x)/float(y)-1.0)/(float(z)-1.0)

def genere_comptage(duree):
    """
    Grosse fonction de hack pour ne pas écrire six fois le même formatage de sortie bidon.
    Prend en argument :

        * duree, qui peut valoir 'instant', 'heuristique', ou 'journalier', permet d'importer
          les variables de config qui vont bien
    """
    global date
    global Logs
    pb_comptage_suspect = {}
    output = ""
    requete = """SELECT
    array_to_string(array_agg(DISTINCT date), ', ') AS dates,
    mac,
    array_to_string(array_agg(DISTINCT chambre), ', ') AS chambres,
    COUNT(DISTINCT chambre) AS nb_chambres_distinctes,
    COUNT(chambre) AS nb_chambres,
    COUNT(DISTINCT date) AS nb_dates_distinctes,
    COUNT(DISTINCT mac) AS nb_macs_distinctes
FROM
    correspondance
WHERE
    date >= timestamp 'now' - interval '%(delay)s'
GROUP BY
    mac;""" % {'delay': mac_prise.delay[duree]}
    cur.execute(requete)
    fetched = cur.fetchall()

    for entry in fetched:
        machines = ldap.search(u'(macAddress=%s)' % entry['mac'])
        if len(machines) > 0:
            if isinstance(machines[0], lc_ldap.objets.machineWifi):
                continue
        else:
            continue

        if entry['nb_chambres_distinctes'] >= mac_prise.suspect[duree]:
            Logs.append(u"Recherche par %s, entrée suspecte : %s -> %s : %s distinctes sur %s dates distinctes\n" % ('mac', entry['mac'], entry['chambres'], entry['nb_chambres'], entry['nb_dates_distinctes']))
            liste_chambres = entry['chambres'].split(', ')

            # On calcule la "probabilité" qu'un truc ne soit pas clair concernant la chambre/mac
            rapport = lin(entry['nb_chambres'], entry['nb_dates_distinctes'], float(entry['nb_chambres_distinctes']))
            if rapport >= mac_prise.rapport_suspect[duree]:
                Logs.append(u"Entrée ajoutée au tableau %s, car rapport supérieur au seuil.\n\n" % (duree))
                pb_comptage_suspect[entry['mac']] = (liste_chambres, rapport, mac_prise.rapport_suspect[duree])
            else:
                Logs.append(u"Entrée rejetée du tableau %s, car rapport inférieur au seuil : (%s).\n\n" % (duree, rapport))
        else:
            pass

    if len(pb_comptage_suspect) > 0:
        for clef, valeur in pb_comptage_suspect.items():
            requete = "INSERT INTO spotted (mac, type, chambre, date, rapport, seuil) VALUES(%s, %s, %s, %s, %s, %s);"
            cur.execute(requete, (clef, duree, ", ".join(valeur[0]), date, valeur[1], valeur[2]))
    return None

def summary():
    """
    Fonction résumant les entrées "spotted" des dernières 24h
    """

    output = u""
    Logs = u""

    requete = """SELECT
    *
FROM
    spotted
WHERE
    date >= timestamp 'now' - interval '1 day'
ORDER BY
    date
ASC;"""
    cur.execute(requete)
    fetched = cur.fetchall()
    liste_triee = collections.defaultdict(dict)

    for entry in fetched:
        chbres = entry['chambre'].split(', ')
        chbres.sort()
        chbres = tuple(chbres)
        if liste_triee[entry['mac']].has_key(entry['type']):
            if liste_triee[entry['mac']][entry['type']].has_key(chbres):
                liste_triee[entry['mac']][entry['type']][chbres].append((entry['date'], entry['rapport'], entry['seuil']))
            else:
                liste_triee[entry['mac']][entry['type']][chbres] = []
                liste_triee[entry['mac']][entry['type']][chbres].append((entry['date'], entry['rapport'], entry['seuil']))
        else:
            liste_triee[entry['mac']][entry['type']] = {}
            liste_triee[entry['mac']][entry['type']][chbres] = []
            liste_triee[entry['mac']][entry['type']][chbres].append((entry['date'], entry['rapport'], entry['seuil']))

    for mac, ltype in liste_triee.items():
        data = []
        output += u"Il y a eu une activité suspecte concernant la mac %s :\n\n" % mac
        Logs += u"Activité pour %s :\n" % mac
        for dtype, dataset in ltype.items():
            number = 0
            chambres = set()
            for chbres, dataframe in dataset.items():
                chambres.update(chbres)
                number += len(dataframe)
                data += [[dtype, ", ".join(list(chbres)), donnee[0], donnee[1], donnee[2]] for donnee in dataframe]
            output += u'Sur le relevé %s, la mac est apparue %s fois dans de multiples chambres simultanément. Elle est apparue dans les chambres suivantes : %s.\n' % (dtype, number, ", ".join(list(chambres)))
        output += u'Consulter les logs (en PJ) pour plus d\'informations.\n\n'
        try:
            maxlen = max([len(a[1]) for a in data]) + 4
        except ValueError:
            maxlen = 4
        titres = (u'relevé', u'chambres', u'date', u'rapport', u'seuil')
        alignements = ('c', 'c', 'c', 'c', 'c')
        largeurs = (12, maxlen, 20, 10, 10)
        Logs += tableau(data, titres, largeurs, alignements)
        Logs += u"\n\n"

    return output, Logs

def reperage_mac_inconnue():
    """
    Fonction de repérage d'une mac qui ne devrait pas être
    dans telle chambre, sur une plage de 24h, suivant un
    paramètre de config pour le nombre d'occurrences.
    """

    output = u""
    probleme = {}
    requete = """SELECT
    chambre,
    mac,
    COUNT(mac) AS nb_min
FROM
    correspondance
WHERE
    date >= timestamp 'now' - interval '24 hours'
GROUP BY
    chambre,
    mac
ORDER BY
    chambre
ASC;"""
    cur.execute(requete)
    fetched = cur.fetchall()
    liste_parsee = collections.defaultdict(dict)

    for entry in fetched:
        liste_parsee[entry['chambre']][entry['mac']] = int(entry['nb_min'])

    for chambre in liste_parsee.keys():
        if chambre in chambres_ma + chambres_clubs:
            continue

        for mac in liste_parsee[chambre].keys():
            try:
                proprio_associe = ldap.search(u'macAddress=%s' % mac)[0].proprio()
                if str(proprio_associe['chbre'][0]).lower() == chambre.lower():
                    garbage = liste_parsee[chambre].pop(mac)
            except:
                pass
        number = sum(liste_parsee[chambre].values())

        if number >= mac_prise.max_inconnues_par_jour:
            probleme[chambre] = (liste_parsee[chambre].keys(), number)

    if len(probleme) > 0:
        output += mac_prise.titre_mac_inconnue+"\n"

        longueur_max = max([len(", ".join(a[0])) for a in probleme.values()] + [len("macs")]) + 2
        largeurs = (len('chambre') + 2, longueur_max, len('compteur') + 2, len('seuil') + 2)

        data = []
        clefs = probleme.keys()
        clefs.sort()
        for clef in clefs:
            data.append([clef, ", ".join(probleme[clef][0]), probleme[clef][1], mac_prise.max_inconnues_par_jour])

        output += tableau(data, ('chambre', 'macs', 'compteur', 'seuil'), largeurs, ('c', 'c', 'c', 'c'))
        output += u"\n\n\n"

    return output

if __name__ == '__main__':
    date = time.strftime('%F %T')
    if len(sys.argv) >= 2:
        output = u''
        Logs = u''
        if '--summary' in sys.argv or 'summary' in sys.argv:
            # On envoie le résumé
            output += u'+---------------------------------------------------------------+\n'
            output += u'|  Résumé des apparitions de mac dans plusieurs chambres        |\n'
            output += u'+---------------------------------------------------------------+\n\n'
            texte, Logs = summary()
            output += texte
            output += u"\n\n"
        if '--reperage' in sys.argv or 'reperage' in sys.argv:
            # On envoie le repérage
            output += u'+---------------------------------------------------------------+\n'
            output += u'|  Repérage de spoof potentiel par comptage                     |\n'
            output += u'+---------------------------------------------------------------+\n\n'
            output += u"""Attention, cette fonctionnalité est plus une forme de stalking que très utile.\n"
Elle visait initialement à repérer les machines qui étaient trop longtemps dans
une chambre qui n'est pas celle où elles devraient être, cependant, les divers
tests menés n'ont pas prouvé une grande efficacité de cette méthode.\n\n"""
            output += reperage_mac_inconnue()

        Logs = Logs.encode('UTF-8')
        output = output.encode('UTF-8')
        if "--debug" in sys.argv or "debug" in sys.argv:
            print output
            sys.exit(0)
        message = """From: %(from)s
To: %(to)s
Subject: %(subject)s
Content-Type: multipart/mixed; boundary="_424234545aaff-ffca234efff-556adceff5646_"

--_424234545aaff-ffca234efff-556adceff5646_
Content-Type: text/plain; charset="UTF-8"

%(contenu)s

--_424234545aaff-ffca234efff-556adceff5646_
Content-Type: text/plain; charset="UTF-8"
Content-Disposition: attachment; filename="logs_analyse"

%(logs)s

--_424234545aaff-ffca234efff-556adceff5646_--
"""

        corps = message % { 'from': 'Spoofing watcher <disconnect@lists.crans.org>',
                            'to': mac_prise.mail_to,
                            'subject': Header(u"Résumé quotidien des correspondances mac_prises.", "UTF-8"),
                            'contenu': output,
                            'logs': Logs,
                          }

        mail = smtplib.SMTP('localhost')
        mailfrom = 'disconnect@lists.crans.org'
        mailto = mac_prise.mail_to
        mail.sendmail(mailfrom, mailto, corps)
    else:
        Logs = [u"Logs du script %s\n\n\n" % sys.argv[0]]
        genere_comptage('instantanne')
        genere_comptage('moyen')
        genere_comptage('journalier')

        if mac_prise.hargneux:
            hargneux = True
        else:
            hargneux = False

        if hargneux:
            message = """From: %(from)s
To: %(to)s
Subject: %(subject)s
Content-Type: text/plain; charset="UTF-8"

%(logs)s
"""

            corps = message % { 'from': 'Spoofing watcher <disconnect@lists.crans.org>',
                                'to': mac_prise.mail_to,
                                'subject': 'Logs de %s' % sys.argv[0],
                                'logs': "".join(Logs),
                              }

            mail = smtplib.SMTP('localhost')
            mailfrom = 'disconnect@lists.crans.org'
            mailto = mac_prise.mail_to
            mail.sendmail(mailfrom, mailto, corps.encode('utf-8'))
