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

import socket
import sys, re
import psycopg2
sys.path.append('/usr/scripts/gestion/')
from affich_tools import tableau

def stats(ip_crans=[], ip_ext=[],
        show=['ip_crans', 'ip_ext', 'port_crans', 'port_ext'],
        upload_mini=0, show_limit=10, begin_time=24, end_time=0,
        show_download=False,resolve_dns=True):
    """
    Retourne une chaine de caratères formatée avec le tableau de statistiques
    d'upload de l'ip fourni

    ip_crans     : ips des machines Cr@ns
                     chaine de caratère si il a qu'une machine
                     liste si il y a plusieurs machines
    ip_ext       : ips des machines extérieures
                     chaine de caratère si il a qu'une machine
                     liste si il y a plusieurs machines
    show         : liste des champs à afficher parmi :
                     ip_crans, ip_ext, port_crans, port_ext
    upload_mini  : n'affiche que les lignes avec plus d'upload que la valeur donnée
    show_limit   : nombre max de lignes à afficher
    begin_time   : date de départ d'analyse (en heure avant maintenant)
    end_time     : date de fin d'analyse (en heure avant maintenant)
    show_download: trie par rapport au download plutôt que l'upload
    """

    if type(ip_crans) == str:
        ip_crans = [ip_crans]

    if type(ip_ext) == str:
        ip_ext = [ip_ext]

    # variables pour la requete et l'affichage
    ##########################################

    select = []
    largeur = []
    titre = []
    format = []
    alignement = []

    if 'ip_crans' in show and len(ip_crans)!=1:
        select.append('ip_crans')
        largeur.append(13)
        titre.append('machine crans')
        format.append('s')
        alignement.append('c')

    if 'ip_ext' in show :
        select.append('ip_ext')
        largeur.append('*')
        titre.append('machine ext')
        format.append('s')
        alignement.append('c')

    if 'port_ext' in show :
        select.append('port_ext')
        largeur.append(10)
        titre.append('port ext')
        format.append('s')
        alignement.append('d')

    if 'port_crans' in show :
        select.append('port_crans')
        largeur.append(10)
        titre.append('port crans')
        format.append('s')
        alignement.append('d')

    select += ['sum(download) as download','sum(upload) as upload']
    largeur += [10,10]
    format += ['o','o']
    alignement += ['d','d']

    if show_download:
        sort_by = 'download'
        titre += ['*download*','upload']
    else:
        sort_by = 'upload'
        titre += ['download','*upload*']

    # requete dans la base
    ######################
    ip_crans = ' OR '.join([ "ip_crans='%s'"%x for x in ip_crans ])
    if not ip_crans: ip_crans='true'
    ip_ext = ' OR '.join([ "ip_ext='%s'"%x for x in ip_ext ])
    if not ip_ext: ip_ext='true'

    requete = """
        SELECT * FROM (
            (
                SELECT %(select)s FROM upload WHERE (%(ip_crans)s) AND (%(ip_ext)s)
                    AND (date > timestamp 'now' - interval '%(begin_time)d hours')
                    AND (date < timestamp 'now' - interval '%(end_time)d hours')
                    GROUP BY %(show)s
            )
        ) AS resultat_intemediaire
        WHERE %(sort_by)s >= '%(sort_mini)d'
        ORDER BY %(sort_by)s DESC
        LIMIT %(show_limit)d;""" % {
            'select': ','.join(select),
            'ip_crans': ip_crans,
            'ip_ext': ip_ext,
            'begin_time': begin_time,
            'end_time': end_time,
            'show': ','.join(show),
            'sort_by': sort_by,
            'sort_mini': upload_mini*1024*1024,
            'show_limit': show_limit }

    pgsql = psycopg2.connect(database="filtrage", user="crans")
    curseur = pgsql.cursor()
    curseur.execute(requete)
    results = curseur.fetchall()

    # on transforme tout en chaine
    results = [ [ str(x).decode("utf-8") for x in line ] for line in results ]
    headers = u""
    try:
        upload = 0
        download = 0
        for line in results:
            upload += int(line[4])
            download += int(line[3])
        headers += u"    upload: %sMo\n" % (upload/1024/1024)
        headers += u"    download: %sMo\n" % (download/1024/1024)
    except IndexError:
        pass

    # on modifie les ip en noms de machine et les ports en noms
    def nom_de_machine(ip):
        if not resolve_dns: return ip
        try:
            return socket.gethostbyaddr(ip)[0]
        except:
            return ip

    port_to_service = {}
    for service,port in [re.split('[ \t]+',
            x.strip().replace('/tcp','').replace('/udp',''))[:2]
            for x in open('/etc/services').readlines()
            if x[0] not in ['\n','#']]:
        port_to_service[port] = service

    for champ in select:
        if champ == 'ip_ext':
            col = select.index(champ)
            results = [x[:col] + [nom_de_machine(x[col])] + x[col+1:]
                       for x in results]

        elif champ == 'ip_crans':
            col = select.index(champ)
            results = [x[:col] + [nom_de_machine(x[col]).split('.')[0]] + x[col+1:]
                       for x in results]

        elif 'port' in champ:
            col = select.index(champ)
            results = [x[:col] + [port_to_service.get(x[col],x[col])] + x[col+1:]
                       for x in results]

    return headers + tableau(results, titre=titre, largeur=largeur,
            alignement=alignement, format=format)


def stats_fork(stub='', ip_crans=[],
        upload_mini=0, show_limit=10, begin_time=24, end_time=0,
        show_download=False,resolve_dns=False):
        if '/' in stub:
            return
        import subprocess
        args=[]
        if show_download:
            args.append('--show-download')
        args.extend(['--show-limit','%s' % show_limit])
        args.extend(['--begin-time', '%s' % begin_time])
        args.extend(['--end-time', '%s' % end_time])
        if not resolve_dns:
            args.append('-n')
        for ip in ip_crans:
            subprocess.Popen(
             ["/usr/scripts/surveillance/analyse.py",
              "--file","/usr/scripts/var/analyse/%s%s.txt" % (ip,stub),
              "--ip-crans", "%s" % ip
             ] + args)

if __name__ == '__main__' :

    help = """Statistiques d'upload d'une machine du crans
usage: analyse.py [option]...
Option fait partie des options suivantes :
  -d ou --show-download
      trier par download plutôt que par upload
  --ip-crans [ip]
      ip de la machine crans
  --ip-ext [ip]
      ip de la machine extérieure
  --show [champ]
      champs à afficher (parmi ip_crans, ip_ext, port_crans, port_ext)
  --upload-mini [n Mo]
      upload mini des lignes à afficher
  --show-limit [n]
      nombre maximum de lignes à afficher
  --begin-time [n heures]
      heure de départ de l'analyse (en heures depuis maintenant)
  --end-time [n heures]
      heure de fin de l'analyse (en heures depuis maintenant)
  -n
      Ne pas résoudre les adresses ip

Exemple :
    sudo /usr/scripts/surveillance/analyse.py bilou.crans.org"""

    headers=""

    # import des modules
    import getopt

    # aide
    ######
    if '-h' in sys.argv or '--help' in sys.argv or len(sys.argv)==1:
        print help
        sys.exit(0)

    # parsage des arguments
    #######################
    try :
        opts, args = getopt.getopt(sys.argv[1:], 'dn', ['show-download',
                'ip-crans=', 'ip-ext=', 'show=', 'upload-mini=',
                'begin-time=', 'end-time=', 'show-limit=', 'file='])
    except getopt.GetoptError,message :
        print help
        sys.exit(4)

    # affichage des stats de download
    #################################
    show_download = False
    for key,value in opts:
        if key == '-d' or key == '--show-download':
            show_download = True
    if show_download:
        headers += u"Statistiques de download\n"
    else:
        headers += u"Statistiques d'upload\n"

    # recherche de la machine crans
    ###############################
    ip_crans = []
    ip_crans_nom = []    
    for key,value in opts :
        if key == '--ip-crans' :
            try :
                ip_crans.append(socket.gethostbyaddr(value)[2][0])
                ip_crans_nom.append(socket.gethostbyaddr(value)[0])
            except socket.gaierror :
                print "Hôte %s inconnu" % value
                sys.exit(5)
            except socket.herror:
                print "Hôte %s inconnu" % value
                ip_crans.append(value)
    if len(ip_crans_nom)==1:
        headers += u'    depuis la machine %s\n' % ip_crans_nom[0]
    elif ip_crans_nom:
        headers += u'    depuis les machines %s\n' % ', '.join(ip_crans_nom)

    # recherche de la machine ext
    #############################
    ip_ext = []
    ip_ext_nom = []    
    for key,value in opts :
        if key == '--ip-ext' :
            # recherche de l'ip de la machine extérieur
            try:
                ip_ext.append(socket.gethostbyaddr(value)[2][0])
            except socket.herror:
                ip_ext.append(value)
            except socket.gaierror :
                print "Hôte %s inconnu" % value
                sys.exit(5)

            # recherche du nom d'hote
            try :
                ip_ext_nom.append(socket.gethostbyaddr(value)[0])
            except socket.herror:
                ip_ext_nom.append(ip_ext[-1])
            except socket.gaierror :
                ip_ext_nom.append(ip_ext[-1])
    if len(ip_ext_nom)==1:
        headers += u'    vers la machine extérieure %s\n' % ip_ext_nom[0]
    elif ip_ext_nom:
        headers += u'    vers les machines extérieures %s\n' % ', '.join(ip_ext_nom)

    # limite d'affichage
    ####################
    show = [x[1] for x in opts
            if x[0] == '--show'
            and x[1] in ['ip_crans', 'ip_ext', 'port_crans', 'port_ext']]
    if not show :
        show = ['ip_crans', 'ip_ext', 'port_crans', 'port_ext']
    else :
        headers += u'    affichage de %s\n' % ', '.join(show)

    # upload mini à afficher
    ########################
    upload_mini = 0
    for key,value in opts :
        if key == '--upload-mini' :
            try :
                upload_mini = int(value)
            except :
                print 'L\'upload mini doit être un entier (en MO)'
                sys.exit(4)
            break
    if upload_mini:
        headers += u'    pour les traffics supérieurs à %d Mo\n' % upload_mini

    # nombre limite d'enregristrements
    ##################################
    limit = 10
    for key,value in opts :
        if key == '--show-limit' :
            try :
                limit = int(value)
                headers += u'    affichage des %d premiers résultats\n' % limit
            except :
                print 'Le nombre limite n\'est pas un entier'
                sys.exit(3)
            break

    # Résolution dns
    ################
    resolve_dns = '-n' not in [key for (key,value) in opts]

    # début de l'analyse
    ####################
    begin_time = 24
    for key,value in opts :
        if key == '--begin-time' :
            try :
                begin_time = int(value)
            except :
                print 'Le nombre d\'heures doit être un entier'
                sys.exit(4)
            break

    # fin de l'analyse
    ##################
    end_time = 0
    for key,value in opts :
        if key == '--end-time' :
            try :
                end_time = int(value)
            except :
                print 'Le nombre d\'heures doit être un entier'
                sys.exit(4)
            break
    if begin_time != 24 or end_time:
        headers += u'    entre il y a %d heure(s) et il y a %d heure(s)\n' % (begin_time,end_time)

    # fichier de sortie
    ###################
    file = None
    for key,value in opts :
        if key == '--file' :
            file = value


    # affichage du résultat
    #######################
    out = stats(ip_crans, ip_ext, show, upload_mini, limit, begin_time,
            end_time, show_download,resolve_dns)
    out = headers + out
    if not file:
        print out.encode("utf-8")
    else:
        with open(file, 'w') as f:
            f.write(out.encode('utf-8'))

