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


# Module pour la téléphonie
from gammu import smsd
from gammu.exception import GSMError

# Modules intrinsèques Python
import sys
import pwd
import os
import re
import socket
from email.message import Message
from email.parser import FeedParser

# Modules made in Cr@ns
from gestion.config import dns
from lc_ldap import shortcuts
import lc_ldap.filter2 as filter


#Exécuter le script sans essayer de contacter la clé 3G
if '--dry-run' in sys.argv[1:]:
    DRY_RUN = True
    print("Mode dry-run\n")
else:
    DRY_RUN = False

# Exécuter le script en mode débug
if '--debug' in sys.argv[1:]:
    DEBUG = True
    print("Mode débug\n")
else:
    DEBUG = False

#-------------------------------------------------------------------------------
# 0)              Initialisation de LDAP,objets + fcts utiles
#-------------------------------------------------------------------------------

# Taille maximale du corps d'un message SMS
if '--unicode' in sys.argv[1:]:
    MAX_CHAR = 70
else:
    MAX_CHAR = 160

# Connexion à la base LDAP
ldap = shortcuts.lc_ldap_admin()

# Serveur boite aux lettres
MAILBOX_SERVER = 'zamok.crans.org'

if DEBUG:
    log_file = open(os.getenv('HOME')+'/alertsms.log', mode='w')

# Serveurs pouvant distribuer les mails de manière légitime
TRUSTED_SERVERS = [ (
                        name.replace('freebox.crans.org','titanic.crans.org'),
                        name.replace('freebox.crans.org','titanic.crans.org').replace('.','.adm.',1),
                        { 'ipv4' : '', 'ipv6' : '' }
                    ) for name in dns.MXs.keys() ]

for _,server,ip_dict in TRUSTED_SERVERS:
    list_ip = socket.getaddrinfo(server,None,0,0,socket.IPPROTO_TCP)
    for _,_,_,_,ip in list_ip:
        if ':' in ip[0]:
            ip_dict['ipv6'] = ip[0]
        else:
            ip_dict['ipv4'] = ip[0]

# IP spéciales autorisées à envoyer des messages
SPECIAL_AUTH_IPv4 = []
SPECIAL_AUTH_IPv6 = []

# Expressions régulières pour le traitement du header des mails
catch_from_info = re.compile('from \[?(?P<sender>[a-zA-Z0-9.-]*)\]? \((?P<real_sender>[a-zA-Z0-9.-]*) ?\[((IPv6:(?P<ipv6>[0-9a-f:]*))|(?P<ipv4>[0-9.]*))\]\).*')
catch_by_info = re.compile('.*by (?P<who>[a-zA-Z0-9.]*) .*',flags=re.DOTALL)

def get_sender_info(line):
    """
        Extrait les infos d'un élément Received du header
        (prétendu nom de l'expéditeur, son nom d'après le DNS, son adresse IP)
    """
    m = catch_from_info.match(line)
    if m is None:
        return (None,None,None,None)

    return (m.group('sender'),m.group('real_sender'),m.group('ipv4'),m.group('ipv6'))

def get_all_sender_info(message):
    """
       Extrait les infos de tous les éléments Received du header d'un mail
       et en fait une liste (en conservant l'ordre des éléments)
    """
    return [ get_sender_info(line) for line in message.get_all('Received') ]

def get_by_info(line):
    """
       Extrait le nom du serveur ayant ajouté la ligne Received donnée
    """
    m = catch_by_info.match(line)
    if m is None:
        return None

    return m.group('who')

def get_all_by_info(message):
    """
       Extrait le nom de tous les serveurs ayant ajouté une ligne Received
       (en conservant l'ordre)
    """
    return [ get_by_info(line) for line in message.get_all('Received') ]

def get_trusted_servers_v4():
    """
        Renvoie la liste des serveurs de confiance avec leur IPv4
    """
    return [(name,adm_name,ip_dict['ipv4']) for name,adm_name,ip_dict in TRUSTED_SERVERS ]

def get_trusted_servers_v6():
    """
        Renvoie la liste des serveurs de confiance avec leur IPv6
    """
    return [(name,adm_name,ip_dict['ipv6']) for name,adm_name,ip_dict in TRUSTED_SERVERS ]

if DEBUG:
    log_file.writelines(['Phase 0 : OK\n'])

#-------------------------------------------------------------------------------
# 1)                      Lire le mail dans le flux stdin
#-------------------------------------------------------------------------------

# Ouverture de stdin
stream = sys.stdin

# Récupération du mail
mail = stream.readlines()

fp_mail = FeedParser()
fp_mail.feed("".join(mail))

msg = fp_mail.close()

# On le décortique
received_chain = get_all_sender_info(msg)
by_chain = get_all_by_info(msg)
mail_subject = msg['Subject']
mail_content = msg.get_payload().replace('\n',' ')

if DEBUG:
    log_file.writelines(['Phase 1 : OK\n'])

#-------------------------------------------------------------------------------
# 2)              Ce message est-il destiné à être envoyé par SMS ?
#-------------------------------------------------------------------------------

if '[SMS]' not in mail_subject:
    if DEBUG:
        log_file.writelines(["Message non destiné au service SMS"])
    sys.exit(200)

if DEBUG:
    log_file.writelines(['Phase 2 : OK\n'])

#-------------------------------------------------------------------------------
# 3)              Analyse de la chaine de réception du message
#-------------------------------------------------------------------------------
# Il faut qu'il y a au moins 2 serveurs dans la chaîne (boite aux lettres + serveur de mail)
# Sinon, cela signifie a priori que le message à été déposé directement dans la boite
# Il faut aussi vérifier qui a distribué le mail

if len(received_chain) < 2:
    if DEBUG:
        log_file.writelines(["La chaine de réception n'est pas assez longue"])
    sys.exit(300)

if by_chain[0] != MAILBOX_SERVER:
    if DEBUG:
        log_file.writelines(["La boîte aux lettres est suspecte :\
           {0} au lieu de {1}".format(repr(by_chain[0]),MAILBOX_SERVER)])
    sys.exit(301)

if DEBUG:
    log_file.writelines(["Boîte aux lettres : " + MAILBOX_SERVER])

trusted_server = False
sender,real_sender,ipv4,ipv6 = received_chain[0]

if ipv4:
    server = (sender,real_sender,ipv4)
    trusted_server = server in get_trusted_servers_v4()
elif ipv6:
    server = (sender,real_sender,ipv6)
    trusted_server = server in get_trusted_servers_v6()

if not trusted_server:
    if DEBUG:
        log_file.writelines(["Le serveur de distribution du courrier est suspect"])
    sys.exit(302)

if DEBUG:
    log_file.writelines(["Serveur de distribution : " + repr(server) + "\n"])

# On fait confiance à la boîte aux lettres et au serveur de distribution
# Le serveur de distribution est le premier serveur Crans à recevoir le mail
# Au-delà, on a aucun contrôle : On est obligé de faire confiance

    log_file.writelines(['Phase 3 : OK\n'])

#-------------------------------------------------------------------------------
# 4)                  Vérifier l'identité de l'expéditeur
#-------------------------------------------------------------------------------

# On récupère l'adresse IP de l'expéditeur
_,_,sender_ipv4,sender_ipv6 = received_chain.pop()

# S'agit-il d'un expéditeur spécial ?
special_auth = sender_ipv4 in SPECIAL_AUTH_IPv4 or sender_ipv6 in SPECIAL_AUTH_IPv6
pseudo = 'Special'

# Si ce n'est pas une adresse spéciale, on fait une recherche LDAP
if not special_auth:

    if sender_ipv4:
        sender_ip = sender_ipv4
        f = filter.human_to_ldap('ipHostNumber=' + sender_ipv4)
    elif sender_ipv6:
        sender_ip = sender_ipv6
        f = filter.human_to_ldap('ip6HostNumber=' + sender_ipv6)
    else:
        if DEBUG:
            log_file.writelines(["Identification impossible : L'expéditeur n'a ni IPv4 ni IPv6"])
        sys.exit(402)

    sender = ldap.search(f)

    # --> Si il n'y a aucun résultat, on quitte le script
    if not sender:
        if DEBUG:
            log_file.writelines(['Aucun résultat pour l\'expéditeur dans la base LDAP'])
        sys.exit(400)

    sender = sender[0]

    # --> Dans le cas contraire, on regarde qui c'est
    if u'machineCrans' in [ objet.value for objet in sender['objectClass'] ]:
         is_authorized = True  # C'est une machine Crans
         pseudo = sender['host'][0].value.split('.',1)[0]

    elif u'machineFixe' in [ objet.value for objet in sender['objectClass'] ] \
    or u'machineWifi' in [ objet.value for objet in sender['objectClass'] ]:

        if u'Nounou' in [ droit.value for droit in sender.proprio()['droits'] ]:
            is_authorized = True   # C'est une nounou
            pseudo = sender.proprio()['uid'][0].value
        else:
            is_authorized = False

    else:
        is_authorized = False

    # On quitte le script si on a pas l'autorisation
    if not is_authorized:
        if DEBUG:
            log_file.writelines(["Impossible d'envoyer le SMS (Autorisation refusée)"])
        sys.exit(401)

if DEBUG:
    log_file.writelines(['Phase 4 : OK\n'])

#-------------------------------------------------------------------------------
# 5)                         Contacter le démon sms
#-------------------------------------------------------------------------------

if not DRY_RUN:
    try:
        # On essaie de contacter le démon SMS
        daemon = smsd.SMSD('/etc/gammu-smsdrc')

    except GSMError:
        # On quitte si l'initialisation a échoué
        if DEBUG:
            log_file.writelines(["Impossible de contacter le démon SMS"])
        sys.exit(500)

if DEBUG:
    log_file.writelines(['Phase 5 : OK\n'])
#-------------------------------------------------------------------------------
# 6)                            Ecrire le SMS
#-------------------------------------------------------------------------------

# On récupère l'utilisateur courant
user = pwd.getpwuid(os.getuid())[0]

# On trouve le numéro de l'adhérent associé dans la base LDAP
f = filter.human_to_ldap('uid='+user)

number = str(ldap.search(f)[0]['tel'][0])


# On fabrique une regex pour la forme du numéro de téléphone
tel_pattern = re.compile('^((336)|(337)|(06)|(07))[0-9]{8}$')

# On vérifie que ce soit un numéro de téléphone mobile valide
if not tel_pattern.match(number):
    if DEBUG:
        log_file.writelines(["Numéro du destinataire invalide"])
    sys.exit(600)

# Si le numéro commence par l'indicatif 33, on rajoute le +
if number.startswith('33'):
    number = '+' + number

# On écrit le message
text = (pseudo + u' : ' + mail_content)[0:MAX_CHAR-1]

message = {
              'Text' : text,
              'SMSC' : { 'Location' : 1 },
              'Number' : number,
          }

if DEBUG:
    log_file.writelines([u"Destinataire : " + user])
    log_file.writelines([u"Contenu du message : " + text + "\n"])

    log_file.writelines(['Phase 6 : OK\n'])
#-------------------------------------------------------------------------------
# 7)                              L'envoyer
#-------------------------------------------------------------------------------

if not DRY_RUN:
    try:
        # On envoie le message
        daemon.InjectSMS([message])

    except GSMError:
        if DEBUG:
            log_file.writelines(["Le message n'a pas pu être placé dans la file d'attente"])
        sys.exit(700)

if DEBUG:
    log_file.writelines(['Phase 7 : OK\n'])
