/*
    Fichier supgrosmail.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 fichiers mail dépassant la taille
    limite passée en paramètre, sauf si le mail provient d'un
    expéditeur autorisé.

    La liste des expéditeurs autorisés pour envoyer de gros mails
    est mémorisée dans un fichier.

    Si un mail est refusé, un message d'avertissement est généré
    à l'intention de celui qui l'a expédié, et est envoyé en copie
    au destinataire qui ne reçoit pas ce message.

    Un fichier de configuration est utilisé pour se connecter à la
    boite aux lettres et pour sélectionner le serveur SMTP servant
    à l'envoi des messages.
*/


#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 "smtp.h"
#include "testchamp.h"
#include "trtentete.h"
#include "szchemin.h"
#include "mailrep.h"
#include "genchampdate.h"


/* constantes */

// nombre de mails qu'on peut détruire en une seule passe
#define szmaxliste    100

#define taillemin    2500  // taille max minimale acceptée sans confirmation


/* prototypes */
void listergrosmail (int numdepart);
void filtreliste ();
void testfiltre (int posliste);
int  avertir (int posliste);
void ajout_adr (char *dest, char *source);
void transf_ligne (char *message);


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


long taillemax;      // taille maximale des mails acceptés
char adr_mail [120]; // adresse mail de l'utilisateur
char **listexped;    // mémorise expéditeurs autorisés pour gros mails
int  nb_exped;       // nombre d'expéditeurs mémorisés dans listexped
int  listegros [szmaxliste];   // numéro des gros mails à tester
long taillegros [szmaxliste];  // taille des gros mails à tester
int  szlistegros;    // nombre d'éléments dans listegros
char *ficmailrep;    // fichier comptenant un mail pour réponse spécifique

/* buffers rappelant les caractéristiques du message */
char bufDate [120], bufFrom [120], bufTo [120], bufCc [120], bufSubject [120];
char adr_exped [120]; // adresse de l'expéditeur du message qu'on filtre

/* compteurs */
int  conserves;      // nombre de mails conservés
int  supprimes;      // nombre de mails supprimés
int  avertis;        // nombre d'expéditeurs de mails supprimés avertis
int  non_avertis;    // nombre d'expéditeurs de mails supprimés non avertis


/* programme principal */

int main (int nbarg, char *varg[])
{
    char serv_smtp [120]; // nom du serveur smtp utilisé
    FILE *fconf;         // descripteur du fichier de configuration
    // fichier contenant les adresses d'expéditeurs à tester
    char ficadr_OK [szchemin + 11];
    char *car_ficadr_OK; // pointeur sur un caractère de ce fichier


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

    // nombre d'arguments passés en paramètre
    nbarg--;

    // si utilisation d'un mail de réponse spécifique
    if (nbarg == 4 && strcmp (varg [1], "-m") == 0)
    {
        // mémoriser le nom du fichier contenant le mail de réponse
        ficmailrep = varg [2];

        // si ce fichier n'existe pas
        if (access (ficmailrep, 0) != 0)
            errfatale ("MANQUE_FICMAILREP", ficmailrep);

        // sauter les argument traités
        varg = varg + 2;
        nbarg = nbarg - 2;
    }
    // sinon, mail de réponse standard
    else
        ficmailrep = NULL;

    // controle du nombre d'arguments restants
    if (nbarg == 2)
    {
        // récupérer la taille maximale des mails
        // acceptés pour n'importe quel expéditeur
        taillemax = atol (varg [1]);

        // erreur de syntaxe si la taille maximale est nulle
        if (! taillemax)
        {
            // "Paramètre taille_max incorrect ou mal placé"
            affiche_err ("ERR_TAILLEMAX");
            return (0);
        }

        // on peut mettre un - devant le paramètre taille
        if (taillemax < 0)
            taillemax = -taillemax;

        // cette taille peut être exprimée en koctets
        if (tolower (varg [1][strlen (varg [1]) - 1]) == 'k')
            taillemax *= 1024;

        // controle de sécurité :
        // si cette taille est très faible, elle doit être confirmée par un !
        if (taillemax < taillemin && varg [1][strlen (varg [1]) - 1] != '!')
        {
            //"Une taille maximale de %ld octets est très faible pour un mail\n"
            //"Veuillez la confirmer en la faisant suivre d'un !"
            fprintf (stderr, message ("TAILLEMAX_FAIBLE"), taillemax);
            affiche_err ("CONFIRM_TAILLE");

            return (0);
        }

        // ouvrir le fichier de configuration
        fconf = ouvre_ficonf (varg [2]);

        if (fconf)
        {
            // connexion sur le compte mail du serveur pop
            if (connect_pop (fconf))
            {
                // récupérer la liste des gros mails
                listergrosmail (0);

                // s'il risque d'y avoir des mails à supprimer
                if (szlistegros)
                {
                    // récupérer le nom du répertoire racine de la messagerie
                    fgets (ficadr_OK, szchemin, fconf);

                    // fabriquer le chemin d'accès au fichier contenant les
                    // adresses d'expéditeurs de mails en copie cachée autorisés
                    // <racine>/accept_gros
                    car_ficadr_OK = ficadr_OK + strlen (ficadr_OK);
                    *(car_ficadr_OK - 1) = '/';
                    strcpy (car_ficadr_OK, ficdir ("FIC_ACCEPT_GROS"));

                    // si ce fichier existe
                    if (access (ficadr_OK, 0) == 0)
                        // charger en mémoire la liste de ces expéditeurs
                        listexped = charge_valchamp (ficadr_OK, &nb_exped);
                    else
                        // sinon liste vide
                        nb_exped = 0;

                    // recupérer adresse email utilisateur
                    fgets (buf_lect, sizeof (buf_lect), fconf);
                    buf_lect [strlen (buf_lect) - 1] = '\0';

                    // et la mémoriser
                    ajout_adr (adr_mail, buf_lect);

                    // recupérer le nom du serveur smtp
                    fgets (serv_smtp, 120, fconf);
                    serv_smtp [strlen (serv_smtp) - 1] = '\0';

                    // connexion au serveur smtp pour l'envoi des avertissements
                    if (connect_smtp (serv_smtp))
                    {
                        // filtrage des gros mails
                        filtreliste ();

                        // si la liste des gros mails avait atteint sa
                        // taille maximale, mais qu'on a pu en supprimer
                        // on relance une ou plusieurs étape(s) de filtrage
                        while (szlistegros == szmaxliste)
                        {
                            // récupérer la liste des gros mails restants
                            listergrosmail (listegros [szlistegros - 1]);

                            // s'il en reste
                            if (szlistegros)
                            {
                                // les filtrer
                                // "Nouvelle étape de filtrage"
                                affiche_msg ("NOUV_ETAPE");
                                filtreliste ();
                            }
                        }

                        // fermeture de la connexion smtp
                        deconnect_smtp ();
                    }
                }
                else
                    // "Aucun mail ne dépasse la taille autorisée"
                    affiche_msg ("AUCUN_GROSMAIL");

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

            // on n'a plus besoin du fichier de configuration
            fclose (fconf);
        }
    }
    else
      // Syntaxe : %s [-m fichier_mail_réponse] taille_max fichier_configuration
        psyntaxe ("SYNT_SUPGROSMAIL");

    // pour faire plaisir à gcc qui veut une fonction main de type int
    return (0);
}



/* mémorise les numéros des mails dépassant la
   taille limite et dont le numéro est > numdepart */


void listergrosmail (int numdepart)
{
    long numail;     // numéro du mail dont on liste la taille
    long taillemail; // taille de ce mail


    // initialisation
    szlistegros = 0;

    // mémoriser les tailles des mails
    env_pop ("LIST");

    // sauter le message d'entête de la liste
    lire_pop ();

    // lire la taille d'un mail
    lire_pop ();

    // tantque des mails restent à analyser
    while (buf_lect [0] != '.')
    {
        // récupérer la taille du mail
        sscanf (buf_lect, "%ld%ld", &numail, &taillemail);

        // si ce mail est trop gros, n'a pas été encore mémorisé comme mail
        // à supprimer, et que la liste des mails à supprimer n'est pas pleine
        if (taillemail > taillemax && numail > numdepart
                                   && szlistegros < szmaxliste)
        {
            // mémoriser le numéro et la taille de ce mail
            listegros  [szlistegros] = numail;
            taillegros [szlistegros++] = taillemail;
        }

        // lire la taille du mail suivant
        lire_pop ();
    }
}



/* supprime les gros mails ne provenant pas d'expéditeurs autorisés */

void filtreliste ()
{
    int i;  // compteur


    // initialisation des compteurs de mail traités
    conserves = 0;
    supprimes = 0;
    avertis   = 0;
    non_avertis = 0;

    // test des gros mails et suppression éventuelle
    for (i = 0; i < szlistegros; i++)
        testfiltre (i);

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

    if (supprimes)
        // "%d expéditeurs avertis, %d non avertis\n"
        printf (message ("BILAN_AVERTIS"), avertis, non_avertis);
}



/* vérifie si un gros mail provient d'un expéditeur autorisé et si
   ce n'est pas le cas, le détruit cas en avertissant son expéditeur */


void testfiltre (int posliste)
{
    char bufw [120]; // buffer d'envoi d'une requête
    int  numes;      // numéro du mail traité


    // initialisation
    bufDate [0]    = '\0';
    bufFrom [0]    = '\0';
    bufTo [0]      = '\0';
    bufCc [0]      = '\0';
    bufSubject [0] = '\0';
    numes = listegros [posliste];

    // message de suivi de déroulement
    // "\rControle expéditeur du message : %d"
    printf (message ("TEST_EXPED"), numes);
    fflush (stdout);

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

    // lecture et mémorisation des caractéristiques du message
    do
    {
        // lire une ligne de l'entête du message
        lire_pop ();

        // convertir les caractères spéciaux
        majlignentete ();

        // mémorisation des caractéristiques importantes
        // pour cette fonction, on n'a besoin que de la ligne From:
        // mais en mémorisant le reste, on évite de relire l'entête
        // du mail dans la fonction avertir
        if (start ("Date"))
            membuf (bufDate);
        else if (start ("From"))
            membuf (bufFrom);
        else if (start ("To"))
            membuf (bufTo);
        else if (start ("Cc"))
            membuf (bufCc);
        else if (start ("Subject"))
            membuf (bufSubject);
    }
    // lecture terminée si ligne limitée à un .
    while (buf_lect [0] != '.' || buf_lect [1] != '\0');

    // extraire l'adresse de l'expéditeur
    ajout_adr (adr_exped, bufFrom + 5);

    // si le mail ne provient pas d'un expéditeur autorisé
    if ((! *adr_exped) || (! trouve_valchamp (adr_exped, listexped, nb_exped)))
    {
        // envoyer un mail à l'expéditeur du message
        if (bufFrom [0])
            if (avertir (posliste))
                avertis ++;
            else
                non_avertis ++;

        // demande de destruction du message
        sprintf (bufw, "DELE %d", numes);
        env_pop (bufw);
        lire_pop ();
        supprimes ++;
    }
    else
        conserves ++;
}



/* avertit l'expéditeur d'un gros mail que son message est refusé */

int avertir (int posliste)
{
    char bufw [120];  // buffer d'envoi d'une ligne ou d'une requête smtp


    // l'envoi du mail d'avertissement suppose l'accès à un fichier de données
    if (! acces_mailrep (ficmailrep, "supgros"))
        return (0);

    // expéditeur du message
    sprintf (bufw, "MAIL FROM: <%s>", adr_mail);
    envoie_smtp (bufw);
    lire_smtp ();

    // destinataire principal du message
    sprintf (bufw, "RCPT TO: <%s>", adr_exped);
    envoie_smtp (bufw);
    lire_smtp ();

    // si destinataire refusé, on le signale
    if (buf_lect [0] != '2')
    {
        // Affichage de l'adresse à problème
        // "Destinataire %s refusé\n"
        printf (message ("REFUS_DEST"), adr_exped);
        puts (buf_lect);

        // et on n'envoie aucun mail d'avertissement
        envoie_smtp ("RSET");
        lire_smtp ();
        return 0;  // mail non envoyé
    }

    // l'expéditeur du message le recevra en copie
    sprintf (bufw, "RCPT TO: <%s>", adr_mail);
    envoie_smtp (bufw);
    lire_smtp ();

    // préparation de l'envoi du message
    envoie_smtp ("DATA");
    lire_smtp ();

    // génération de l'entête du message
    gen_champ_date (bufw);
    envoie_smtp (bufw);
    sprintf (bufw, "From: <%s>", adr_mail);
    envoie_smtp (bufw);
    sprintf (bufw, "To%s", bufFrom + 4);
    envoie_smtp (bufw);
    sprintf (bufw, "Cc: <%s>", adr_mail);
    envoie_smtp (bufw);

    // "Subject: Votre message du ..."
    sprintf (bufw, "Subject: %s %s", chaine_mailrep ("SUJET"), bufDate + 4);
    envoie_smtp (bufw);

    // "Content-Type: text/plain; charset=..."
    sprintf (bufw, "Content-Type: text/plain; charset=%s",
                                  chaine_mailrep ("CHARSET"));
    envoie_smtp (bufw);

    envoie_smtp ("Content-Transfer-Encoding: 8bit");

    // filtre supgrosmail : logiciel libre multilingue"
    sprintf (bufw, "User-Agent: filtre %s : %s\n", nomcom (),
                                message ("QUALIF_LIBREMAIL"));
    envoie_smtp (bufw);
    envoie_smtp ("");

    // "Vous m'avez envoyé un mail ayant les caractéristiques suivantes :"
    envoie_smtp (chaine_mailrep ("ANNONCE"));
    envoie_smtp ("");

    // rappel du message concerné
    transf_ligne (bufDate);
    transf_ligne (bufFrom);
    transf_ligne (bufTo);
    transf_ligne (bufCc);
    transf_ligne (bufSubject);

    // Taille: %ld octets
    sprintf (bufw, chaine_mailrep ("TAILLE"), taillegros [posliste]);
    transf_ligne (bufw);
    envoie_smtp ("");

    // envoi du message explicatif
    envoi_mailrep ();

    // indicateur de fin du message
    envoie_smtp (".");
    lire_smtp ();

    return 1;   // bon déroulement
}



/* récupère l'adresse mail entre les < > s'ils
   existent et la rajoute au buffer destination */


void ajout_adr (char *dest, char *source)
{
    int orig, debut, posdest;

    orig = 0;

    // chercher le @
    while (source [orig] != '@' && source [orig])
        orig ++;

    // remonter au debut de l'adresse
    while (orig > 0 && source [orig] != ' ' && source [orig] != '<')
        orig--;

    // l'adresse commence sur un caractère significatif
    if (source [orig] == '<' || source [orig] == ' ')
        debut = orig + 1;
    else
        debut = orig;

    // copie de l'adresse
    posdest = 0;

    // on s'arrête si l'on rencontre un  > , un blanc ou une fin de chaine
    while (source [debut] != '>' && source [debut] & 0xDF)
        // l'adresse est mémorisée en minuscules
        dest [posdest++] = tolower (source [debut++]);

    // terminer l'expression de l'adresse
    dest [posdest] = '\0';
}



/* copie dans le mail à envoyer d'une ligne du mail reçu */

void transf_ligne (char *message)
{
    char bufw [82];  // buffer contenant la ligne à envoyer

    if (*message)
    {
        strcpy (bufw, "> ");
        strcpy (bufw + 2, message);
        envoie_smtp (bufw);
    }
}