/*
    Fichier filtradr.c
    Auteur Bernard Chardonneau

    Logiciel libre, droits d'utilisation précisés en français
    dans le fichier : licence-fr.txt

    Traductions des droits d'utilisation dans les fichiers :
    licence-de.txt , licence-en.txt , licence-es.txt ,
    licence-it.txt , licence-nl.txt , licence-pt.txt ,
    licence-eo.txt , licence-eo-utf.txt

    Droits d'utilisation également sur la page web :
    http://libremail.tuxfamily.org/voir.php?page=droits


    Ce programme détruit les mails du serveur ne comportant pas
    d'adresse d'expéditeur, ou provenant d'expéditeurs refusés

    La liste des adresses interdites est mémorisée dans un fichier
    qui peut contenir :
    - des adresses complètes
    - des adresses de fournisseurs d'accès
    - des adresses génériques contenant des parties variables

    Un fichier de configuration est utilisé pour se connecter à la
    boite aux lettres.
*/


#define appli   // pour la déclaration de variables globales à l'application

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "messages.h"
#include "buflect.h"
#include "ficonf.h"
#include "pop.h"
#include "trtentete.h"
#include "datecour.h"
#include "szchemin.h"


/* prototypes */
void charge_adr (FILE *fconf);
void testadr (int numes);
int  adr_generique (char *adresse);
int  adr_interdite (char *adresse);
int  adrgene_interdite (char *adresse);
int  test_adrgene (char *adresse, char *adrgene);


/* variables globales au source
   (pour éviter des tonnes de passages de paramètres) */


char **listadr;         // mémorise les adresses interdites
char **listadrgene;     // mémorise les adresses génériques interdites
int  sz_listadr;        // nombre d'adresses mémorisées dans listadr
int  sz_listadrgene;    // nombre d'adresses mémorisées dans listadrgene
FILE *ftrace;           // fichier contenant les expéditeurs des mails refusés
char datecour [10];     // date courante (pour le fichier des mails refusés)
int  conserves = 0;     // nombre de mails conservés
int  supprimes = 0;     // nombre de mails supprimés

// chaine de caractère mémorisée pour éviter appels répétitifs à message ()
char mess_analys [50];  // message signalant l'analyse d'un mail



/* programme principal */

int main (int nbarg, char *varg[])
{
    FILE *fconf;        // descripteur du fichier de configuration
    int  numes, nbmes;  // muméro du mail courant et nombre de mails


    // récupération du nom de l'exécutable
    memcom (*varg);

    // se positionner sur le premier argument de filtradr
    nbarg --;
    varg ++;

    // si le programme a été lancé avec l'option -t
    if (nbarg == 3 && strcmp (*varg, "-t") == 0)
    {
        // ouvrir le fichier trace en écriture fin de fichier
        ftrace = fopen (varg [1], "a");

        // si nom de fichier correct
        if (ftrace)
            // initialiser la date courante
            initdatecour (datecour);
        else
            // sinon, avertir l'utilisateur
            // "Impossible d'écrire dans le fichier %s"
            aff_err_arg ("IMPOS_ECR_FICH", varg [1]);

        // sauter les arguments traités
        nbarg -= 2;
        varg += 2;
    }
    // sinon, pas d'utilisation d'un fichier trace
    else
        ftrace = NULL;

    // controle du nombre d'arguments restants
    if (nbarg == 1)
    {
        // ouvrir le fichier de configuration
        fconf = ouvre_ficonf (*varg);

        if (fconf)
        {
            // connexion sur le compte mail du serveur pop
            if (connect_pop (fconf))
            {
                // charger en mémoire les adresses interdites
                charge_adr (fconf);

                // récupération du nombre de mails
                nbmes = nbmails ();

                // Initialisation du message à afficher à chaque analyse de mail
                // "\rAnalyse du mail n° %d"
                strcpy (mess_analys, message ("ANALYSE_MAIL"));

                // récupération de la liste des mails
                for (numes = 1; numes <= nbmes; numes++)
                    testadr (numes);

                // édition d'un récapitulatif
                // "\n%d messages conservés, %d supprimés\n"
                printf (message ("BILAN_FILTRAGE"), conserves, supprimes);

                // se déconnecter proprement du serveur pop
                deconnect_pop ();

                // si des mails ont été supprimés
                if (supprimes)
                    // attendre pour enregistrement correct des suppressions
                    // (évite problème si autre filtre appelé juste après)
                    sleep (2);
            }

            // on n'a plus besoin du fichier de configuration
            fclose (fconf);
        }
    }
    else
        // "Syntaxe : %s [-t fichier_trace] fich_configuration"
        psyntaxe ("SYNT_FILTRADR");

    return (0);
}



/* charge en mémoire le fichier des adresses interdites */

void charge_adr (FILE *fconf)
{
    // fichier contenant les adresses refusées
    char ficadr_refus [szchemin + 10];
    char *car_ficadr_refus; // pointeur sur un caractère de ce fichier
    char adresse [120];     // une adresse de ce fichier
    char *nouvadr;          // adresse retournée allouée dynamiquement
    FILE *frefus;           // descripteur du fichier des adresses
    int  szlistes;          // taille des listes d'adresses allouées en mémoire
    int  i, j;              // compteurs


    // récupérer le nom du répertoire racine de la messagerie
    fgets (ficadr_refus, szchemin, fconf);

    // fabriquer le chemin d'accès au fichier contenant les adresses interdites
    // <racine>/refus_adr
    car_ficadr_refus = ficadr_refus + strlen (ficadr_refus);
    *(car_ficadr_refus - 1) = '/';
    strcpy (car_ficadr_refus, ficdir ("FIC_REFUS_ADR"));

    // initialisation : pas encore d'adresse mémorisée
    szlistes       = 0;
    sz_listadr     = 0;
    sz_listadrgene = 0;

    // accès au fichier des adresses refusées
    frefus = fopen (ficadr_refus, "r");

    // si ce fichier existe
    if (frefus)
    {
        // compter le nombre de lignes d'adresses qu'il contient
        while (fgets (adresse, sizeof (adresse), frefus))
            szlistes ++;

        // allouer les zones mémoire pour mémoriser les listes d'adresses

        // on préfère allouer systématiquement 2 fois trop d'espace mémoire
        // (4 octets par ligne du fichier de perdus) plutot que de fixer la
        // taille des 2 listes à la compilation ou d'analyser 2 fois le contenu
        // du fichier pour distinguer le nombre d'adresses classiques et
        // d'adresses génériques avant de relire le fichier pour les mémoriser
        listadr     = malloc (szlistes * sizeof (char *));
        listadrgene = malloc (szlistes * sizeof (char *));

        if (! listadrgene)
            // "Manque de place mémoire, l'application ne peut fonctionner"
            errfatale ("MANQUE_MEMOIRE", NULL);

        // on revient au début du fichier
        rewind (frefus);

        // récupérer et mémoriser les différentes adresses du fichier
        while (fgets (adresse, sizeof (adresse), frefus))
        {
            // allouer une variable pour mémoriser l'adresse lue
            nouvadr = (char *) malloc (strlen (adresse));

            if (! nouvadr)
                // "Manque de place mémoire, l'application ne peut fonctionner"
                errfatale ("MANQUE_MEMOIRE", NULL);

            // recopier l'adresse en la retournant
            // et en passant les caractères en minuscules
            i = strlen (adresse) - 2;
            j = 0;

            while (i >= 0)
                nouvadr [j++] = tolower (adresse [i--]);

            // terminer la chaine
            nouvadr [j] = 0;

            // mémoriser l'adresse dans la bonne liste ou message d'erreur
            switch (adr_generique (nouvadr))
            {
                          // adresse classique
                case  0 : listadr [sz_listadr ++] = nouvadr;
                          break;

                          // adresse générique valide
                case  1 : listadrgene [sz_listadrgene ++] = nouvadr;
                          break;

                          // "Adresse générique invalide : "
                default : affiche_msg_nocr ("ERR_ADRGENE");

                          // on affiche cette adresse dans le bon sens
                          while (--j >= 0)
                              putchar (nouvadr [j]);

                          putchar ('\n');

                          // et on peut libérer le tableau nouvadr
                          free (nouvadr);
            }
        }

        // terminé avec ce fichier
        fclose (frefus);
    }
    else
    {
     // "Fichier %s inexistant"
     // "seuls les mails avec une adresse d'expéditeur sans @ seront supprimés"
        aff_err_arg ("FICH_INEXISTANT", ficadr_refus);
        affiche_err ("FILTR_PARTIEL");
    }
}



/* lit l'entête d'un message, vérifie si l'adresse de l'expéditeur
   est autorisée, et supprime ce message si ce n'est pas le cas. */


void testadr (int numes)
{
    char bufw [120];     // buffer d'envoi d'une requête
    char bufFrom [120];  // buffer contenant le nom et l'adresse de l'expéditeur
    char bufadr [120];   // buffer contenant l'adresse de l'expéditeur retournée
    int  i, j;           // simples compteurs


    // "\rAnalyse du mail n° %d"
    printf (mess_analys, numes);
    fflush (stdout);

    // initialisation
    bufFrom [0] = '\0';
    bufadr  [0] = '\0';

    // demande de lecture de l'entête du message
    sprintf (bufw, "TOP %d 1", numes);
    env_pop (bufw);

    // lire la première ligne de l'entête du message
    lire_pop ();

    // terminé pour ce mail si erreur d'envoi coté serveur
    if (memcmp (buf_lect, "-ERR ", 5) == 0)
    {
        // Erreur serveur pour l'accès au mail
        aff_err_argnum ("ERREUR_SERVEUR", numes);
        conserves ++;
        return;
    }

    // lecture entête du message et mémorisation de l'adresse de l'expéditeur
    do
    {
        // si on a trouvé le champ expéditeur
        if (start ("From"))
        {
            // initialisations
            i = 5;
            j = 0;

            // recherche du @ dans l'adresse
            while (buf_lect [i] != '@' && buf_lect [i] != '\0')
                i++;

            // si l'adresse ne contient pas de @
            if (buf_lect [i] != '@')
            {
                // on va tester la ligne suivante
                lire_pop ();

                i = 1;

                while (buf_lect [i] != '@' && buf_lect [i] != '\0')
                    i++;
            }

            // une adresse valide devra contenir le @
            if (buf_lect [i] == '@')
            {
                // positionnement en fin d'adresse
                while (buf_lect [i] != '>' && buf_lect [i] != ' '
                                           && buf_lect [i] != '\0')
                    i++;

                i--;

                // recopie de l'adresse (dans l'ordre inverse des caractères)
                while (i > 0 && buf_lect [i] != '<' && buf_lect [i] != ' ')
                    bufadr [j++] = tolower (buf_lect [i--]);
            }

            // terminaison de la chaine
            bufadr [j] = 0;

            // si utilisation d'un fichier trace
            if (ftrace)
            {
                // convertir les caractères spéciaux de la ligne
                majlignentete ();

                // initialisation
                i = 5;

                // recherche du début de l'expéditeur
                while (buf_lect [i] == ' ')
                    i++;

                // tronquer si nécessaire l'expéditeur (pour éviter un plantage)
                if (strlen (buf_lect + i) > sizeof (bufFrom))
                     buf_lect [i + sizeof (bufFrom) - 1] = '\0';

                // mémorisation de l'expéditeur
                strcpy (bufFrom, buf_lect + i);
            }
        }

        // lire la ligne suivante de l'entête du message
        lire_pop ();
    }
    // lecture terminée si ligne limitée à un .
    while (buf_lect [0] != '.' || buf_lect [1] != '\0');

    // si le mail contient une adresse d'expéditeur invalide ou interdite
    if (bufadr [0] == 0 || adr_interdite (bufadr) || adrgene_interdite (bufadr))
    {
        // demande de destruction du mail
        sprintf (bufw, "DELE %d", numes);
        env_pop (bufw);
        lire_pop ();
        supprimes ++;

        // si utilisation d'un fichier trace
        if (ftrace)
            // mémoriser l'adresse de l'expéditeur de ce mail
            fprintf (ftrace, "%s%s\n", datecour, bufFrom);
    }
    else
        conserves ++;
}



/*  cherche si l'adresse passée en paramètre est une
    adresse classique ou une adresse générique valide */


int adr_generique (char *adresse)
{
    int retour = 0;


    // on teste tous les caractères de l'adresse
    // pour détecter les métas caractères des adresses génériques
    while (*adresse)
    {
        // cas de 0, 1 ou plusieurs occurences d'un type de caractères
        if (*adresse == '+' || *adresse == '*')
        {
            // tester le métacaractère suivant
            adresse ++;

            if (*adresse == 'a' || *adresse == '9' || *adresse == 'x')
                retour = 1;   // peut être une adresse générique valide
            else
                return (-1);  // adresse générique invalide
        }

        // cas d'une suite de caractères variable de longueur fixe
        else if (*adresse == ']')
        {
            retour = 1;   // peut être une adresse générique valide

            // analyse des méta caractères de l'intervalle
            do
                adresse++;
            while (*adresse == 'a' || *adresse == '9' || *adresse == 'x');

            // adresse générique invalide si caractère non autorisé
            if (*adresse != '[')
                return (-1);
        }

        // passage au caractère suivant
        adresse ++;
    }

    // analyse de l'adresse terminée
    return (retour);
}



/* recherche si l'adresse de l'expéditeur est une adresse interdite */

int adr_interdite (char *adresse)
{
    int i, j;

    // on compare l'adresse expéditeur avec toutes les adresses à refuser
    for (i = 0; i < sz_listadr; i++)
    {
        j = 0;

        // comparaison caractère par caractère
        while (adresse [j] == listadr [i][j])
        {
            if (adresse [j])
                j++;   // passage au caractère suivant
            else
                return (1); // trouvé adresse refusée
        }

        if (listadr [i][j] == 0 && listadr [i][j-1] == '@')
            return (1);     // on refuse toutes les adresses de cet hébergeur
    }

    // l'adresse du mail ne fait pas partie des adresses interdites
    return (0);
}



/* recherche si l'adresse de l'expéditeur correspond
   à une adresses générique interdite */


int adrgene_interdite (char *adresse)
{
    int i;  // compteur


    // on compare l'adresse expéditeur avec toutes les adresses à refuser
    for (i = 0; i < sz_listadrgene; i++)
    {
        if (test_adrgene (adresse, listadrgene [i]))
            return (1); // trouvé adresse refusée
    }

    // l'adresse du mail ne correspond pas à une adresse générique interdite
    return (0);
}



/* comparaison entre l'adresse de l'expéditeur et une adresse
   générique interdite (ou un morceau de ces 2 adresses) */


int test_adrgene (char *adresse, char *adrgene)
{
    // tant que l'adresse générique n'a pas été analysée entièrement
    while (*adrgene)
    {
        // traitement distinct en fonction du
        // caractère courant de l'adresse générique
        switch (*adrgene)
        {
            // traitement suite de caractères variables de longueur fixe
            case ']' : adrgene++;

                       // cette suite est délimitée par des [ ]
                       while (*adrgene != '[')
                       {
                           // terminé si fin prématurée de l'adresse à comparer
                           if (! *adresse)
                               return 0;

                           switch (*adrgene)
                           {
                               // il faut trouver une lettre dans l'adresse
                               case 'a' : if (*adresse < 'a' || *adresse > 'z')
                                              return 0;
                                          break;

                               // il faut trouver un chiffer dans l'adresse
                               case '9' : if (*adresse < '0' || *adresse > '9')
                                              return 0;

                               // caractère quelconque accepté dans l'adresse
                               default  : break;
                           }

                           // continuer l'analyse avec les caractères suivants
                           adresse ++;
                           adrgene ++;
                       }

                       // sauter le '[' de l'adresse générique
                       adrgene ++;
                       break;

            // traitement de 1 ou plusieurs caractères d'un type particulier
            case '+' : if (! *adresse)
                           return 0;   // terminé si adresse trop courte
                       else
                       {
                           // test du caractère courant de l'adresse à comparer
                           switch (adrgene [1])
                           {
                               // cas où il faut que ce soit une lettre
                               case 'a' : if (*adresse < 'a' || *adresse > 'z')
                                              return 0;
                                          break;

                               // cas où il faut que ce soit un chiffre
                               case '9' : if (*adresse < '0' || *adresse > '9')
                                              return 0;

                               // cas général :n'importe quel caractère accepté
                               default  : break;
                           }

                           // passer caractère suivant de l'adresse à comparer
                           adresse ++;
                       }

            // traitement de 0, 1 ou plusieurs caractères d'un type particulier
            case '*' : adrgene ++;  // récupérer le type du caractère

                       // on va utiliser un appel récursif pour
                       // faire une analyse déterministe
                       switch (*adrgene)
                       {
                           // présence de 0, une ou plusieurs lettres avant
                           // la prochaine partie fixe de l'adresse
                           case 'a' : while ('a' <= *adresse && *adresse <= 'z')
                                      {
                                          if (test_adrgene (adresse, adrgene+1))
                                              return (1);

                                          adresse ++;
                                      }

                                      break;

                           // présence de 0, un ou plusieurs chiffres avant
                           // la prochaine partie fixe de l'adresse
                           case '9' : while ('0' <= *adresse && *adresse <= '9')
                                      {
                                          if (test_adrgene (adresse, adrgene+1))
                                              return (1);

                                          adresse ++;
                                      }

                                      break;

                           // caractères quelconques avant la
                           // prochaine partie fixe de l'adresse
                           default  : while (*adresse)
                                      {
                                          if (test_adrgene (adresse, adrgene+1))
                                              return (1);

                                          adresse ++;
                                      }
                       }

                       // dernier essai, celui qui a le
                       // plus de chances de réussir
                       return (test_adrgene (adresse, adrgene+1));
                       break;

            // cas général, comparaison de 2 caractères classiques
            default  : if (*adresse == *adrgene)   // si identiques
                       {
                           // continuer le test avec caractères suivants
                           adresse ++;
                           adrgene ++;
                       }
                       else
                           // sinon échec d'analyse
                           return (0);
        }
    }

    // succès si l'analyse des 2 adresses se termine simultanément
    return (*adresse == 0);
}