/*
    Fichier envmail.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 envoie les fichiers mail en attente d'expédition

    Un fichier de configuration est utilisé pour se connecter au
    serveur smtp et pour fixer le répertoire racine du système
    de messagerie.

    Les mail à envoyer sont stockés dans le sous répertoire "sortie"
    de ce répertoire racine et se distinguent par leur numéro.

    Après envoi, ces fichiers sont déplacés dans le sous répertoire
    "envoyes" du système de messagerie
*/


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

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include "messages.h"
#include "buflect.h"
#include "ficonf.h"
#include "smtp.h"
#include "fmail.h"
#include "szchemin.h"
#include "encodage.h"
#include "genchampdate.h"


/* prototypes */
void envmail (char *fichmail);
int  start (char *motcle);
void env_adr (char *entete, char *donnees);
void env_dest (long poschamp);
void genfromlig ();
int  carspe_dans_ligne ();
void encode_ligne (int encodage);


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


char adr_mail [120];    // adresse mail de l'expéditeur
char direnv [20];       // répertoire des fichiers mail envoyés
struct dirent *entree;  // entrée de répertoire sur fichier mail courant
int  nbdest_ok;         // nombre de destinataires du mail acceptés par smtp


/* programme principal */

int main (int nbarg, char *varg[])
{
    char dirmails [szchemin]; // répertoire racine des fichiers mail
    char serv_smtp [120];  // nom du serveur smtp utilisé
    char mess_envoi [120]; // mémorise le texte à afficher à chaque envoi de mail
    FILE *fconf;           // descripteur fichier de configuration du compte mail
    DIR  *repert;          // structure pour explorer un répertoire
    int  numail;           // numéro d'ordre du fichier mail qu'on envoie
    int  i ;                // compteur


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

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

        if (fconf)
        {
            // on saute les 3 lignes concernant le serveur pop
            for (i = 0; i < 3; i++)
                while (fgetc (fconf) != '\n')
                    ;

            // recupérer le répertoire racine des fichiers mails
            fgets (dirmails, szchemin, fconf);
            dirmails [strlen (dirmails) - 1] = '\0';

            // vérifier son existence en s'y positionnant
            if (chdir (dirmails) < 0)
            {
                fclose (fconf);  // pour sortie proprement
                // "Répertoire de messagerie %s inexistant"
                errfatale ("REP_RACINE_ABSENT", dirmails);
            }

            // faire de même avec le répertoire d'envoi des mails
            if (chdir (ficdir ("DIR_SORTIE")) < 0)
            {
                fclose (fconf);  // pour sortie proprement
                // "Pas de répertoire d'envoi des mails"
                errfatale ("REP_ENVOI_ABSENT", "");
            }

            // créer si nécessaire le répertoire des mails envoyés
            sprintf (direnv, "../%s", ficdir ("DIR_ENVOYES"));
            mkdir (direnv, 0755);

            // récupérer la description de l'expéditeur des mails
            fgets (adr_mail, 120, fconf);
            adr_mail [strlen (adr_mail) - 1] = '\0';

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

            // on n'a plus besoin du fichier de configuration
            fclose (fconf);

            // si on peut se connecter au serveur smtp
            if (connect_smtp (serv_smtp))
            {
                // accéder au répertoire
                repert = opendir (".");

                // lire un nom de fichier
                entree = readdir (repert);

                // initialisation compteur
                numail = 1;

                // Mémorisation du teste à afficher à chaque envoi de mail
                // "\rEnvoi du mail n° %d"
                // afin d'éviter d'appeller la fonction message () chaque fois
                strcpy (mess_envoi, message ("ENVOI_MAIL"));

                // tant qu'il y a des noms de fichier à lire
                while (entree)
                {
                    // si le nom du fichier courant fait 8 caractères
                    if (strlen (entree->d_name) == 8)
                    {
                        // message de suivi du déroulement
                        // "\rEnvoi du mail n° %d"
                        printf (mess_envoi, numail++);
                        fflush (stdout);

                        // envoyer le mail qu'il contient
                        envmail (entree->d_name);
                    }

                    // et lire le nom du fichier suivant
                    entree = readdir (repert);
                }

                // pour l'affichage
                putchar ('\n');

                // fermeture de la connexion smtp
                deconnect_smtp ();
            }
        }
    }
    else
        // "Syntaxe : %s fichier_configuration"
        psyntaxe ("SYNT_GENE_FICONF");

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



/* envoi du fichier mail passé en paramètre */

void envmail (char *fichmail)
{
    char nouvfic [18];  // chemin d'accès relatif au fichier mail après transfert
    char bufFrom [120]; // contenu du champ expéditeur du message
    char bufw [120];    // pour générer les lignes User-agent et peut être Date
    long pos_deblig;    // position dans le fichier en début de ligne
    long posTo, posCc, posBcc; // position champs des destinataires
    int  Useagent;      // simple indicateur
    int  Usedate;       // simple indicateur
    int  encodage;      // jeu de caractères d'une ligne d'entête


    // ouvrir le fichier mail à envoyer
    fmail = fopen (fichmail, "r");

    // si ouverture réussie
    if (fmail)
    {
        // initialisation
        *bufFrom = '\0';
        posTo    =  -1;
        posCc    =  -1;
        posBcc   =  -1;
        Useagent =   0;
        Usedate  =   0;

        // lecture de l'entête et mémorisation
        // de la position des champs importants
        do
        {
            // récupérer la position en début de ligne
            pos_deblig = ftell (fmail);

            // lire une ligne de l'entête du message
            lire_fmail ();

            // repérage des champs importants et mémorisation de leur position
            if (start ("From"))
            {
                buf_lect [120] = '\0';  // troncation si nécessaire
                strcpy (bufFrom, buf_lect);  // on mémorise le champ expéditeur
            }
            else if (start ("To"))
                posTo = pos_deblig;
            else if (start ("Cc"))
                posCc = pos_deblig;
            else if (start ("Bcc"))
                posBcc = pos_deblig;
            else if (start ("X-Mailer") || start ("User-Agent"))
                Useagent = 1;
            else if (start ("Date"))
                Usedate = 1;
        }
        while (buf_lect [0] != '\0');  // lecture entête terminée si ligne vide

        // expéditeur du message
        if (*bufFrom)
            env_adr ("MAIL FROM", bufFrom);
        else
            env_adr ("MAIL FROM", adr_mail);

        // destinataires du message
        // initialisation compteur
        nbdest_ok = 0;

        // on déclare les destinataires un par un
        if (posTo >= 0)
            env_dest (posTo);

        if (posCc >= 0)
            env_dest (posCc);

        if (posBcc >= 0)
            env_dest (posBcc);

        // si au moins un destinataire accepté
        if (nbdest_ok)
        {
            // préparation de l'envoi du message
            envoie_smtp ("DATA");
            lire_smtp ();

            // on revient au début de l'entête du mail
            rewind (fmail);

            // si expéditeur manquant, générer la ligne From
            if (*bufFrom == 0)
                genfromlig ();
            // sinon, lire la premère ligne de l'entête
            else
                lire_fmail ();

            // rajouter la date si elle n'était pas dans l'entête du mail
            if (! Usedate)
            {
                gen_champ_date (bufw);
                envoie_smtp (bufw);
            }

            // copie de l'entête
            while (buf_lect [0] != '\0')
            {
                // s'il y a des destinataires en copie cachée
                if (start ("Bcc"))
                {
                    // on le signale par un message générique
                    envoie_smtp ("Bcc: (...)");

                    // et on saute ces destinataires
                    do
                        lire_fmail ();
                    while (*buf_lect == ' ' || *buf_lect == '\t');
                }
                // cas général
                else
                {
                    // si la ligne contient des caractères spéciaux
                    encodage = carspe_dans_ligne ();

                    if (encodage)
                    {
                        // encoder cette ligne
                        encode_ligne (encodage);
                    }

                    // on envoie la ligne lue
                    envoie_smtp (buf_lect);

                    // et on lit la suivante
                    lire_fmail ();
                }
            }

            // si pas de nom de User-Agent dans l'entête envoyée
            if (! Useagent)
            {
                // le rajouter, en plus, ce n'est pas Outlook :-))
                // "libremail : logiciel libre multilingue"
                sprintf (bufw, "User-Agent: libremail : %s\n",
                                message ("QUALIF_LIBREMAIL"));
                envoie_smtp (bufw);
            }

            // recopier le message
            do
            {
                // si la ligne ne contient qu'un  .
                if (buf_lect [0] == '.' && ! buf_lect [1])
                    // le doubler pour ne pas faire croire à une fin de message
                    envoie_smtp ("..");
                else
                    // sinon, envoyer la ligne telle quelle
                    envoie_smtp (buf_lect);
            }
            while (lire_fmail ());  // on s'arrêtera en fin de fichier mail

            // message indiquant la fin du mail
            envoie_smtp (".");
        }
        // sinon
        else
            // annulation de l'envoi du message
            envoie_smtp ("RSET");

        // accusé de réception smtp
        lire_smtp ();

        // transfert du mail terminé
        fclose (fmail);

        // on peut déplacer ce fichier
        sprintf (nouvfic, "%s/%s", direnv, fichmail);
        rename (fichmail, nouvfic);
    }
    else
        // "\nImpossible de lire le fichier %s"
        aff_err_arg ("IMPOS_LECT_FICH", fichmail);
}



/* cherche si la dernière ligne lue commence par un mot clé particulier */

int start (char *motcle)
{
    int i;

    i = 0;

    // on teste caractère par caractère en ignorant la casse
    while (motcle [i] && tolower (buf_lect [i]) == tolower (motcle [i]))
        i++;

    // le mot clé doit être suivi de  :  dans la ligne lue
    return (buf_lect [i] == ':');
}



/* génère une ligne déclarant l'expéditeur ou des destinataires du message */

void env_adr (char *entete, char *ligne)
{
    char bufw [sz_buflect];  // buffer d'envoi d'une commande smtp
    int  i, j;  // compteurs


    // initialisation : positionnement en début de ligne
    i = 0;

    // rechercher l'adresse dans la ligne
    while (ligne [i] && ligne [i] != '<' && ligne [i] != '@')
    {
        if (! ligne [++i])
        {
            // "Adresse %s erronée"
            aff_err_arg ("ERR_ADR_DEST", ligne);
            return;
        }
    }

    // se positionner au début de l'adresse
    while (i >= 0 && ligne [i] != '<' && ligne [i] != ' ' && ligne [i] != '\t')
        i--;

    i++;   // on sera sur le 1er caractère de l'adresse


    // début de la commande smtp à envoyer
    sprintf (bufw, "%s: <", entete);
    j = strlen (bufw);

    // copie de l'adresse
    do
        bufw [j++] = ligne [i++];
    while (ligne [i] && ligne [i] != '>' && ligne [i] != ','
                     && ligne [i] != ' ' && ligne [i] != '\t');

    // terminaison de la chaine à envoyer
    bufw [j++] = '>';
    bufw [j]   = '\0';

    // envoi de la commande smtp
    envoie_smtp (bufw);

    // lire l'accusé de réception
    lire_smtp ();

    // si on traite les destinataires du mail
    if (*entete == 'R')
    {
        // si destinataire accepté
        if (buf_lect [0] == '2' && buf_lect [1] == '5')
            // le comptabiliser
            nbdest_ok ++;
        else
        {
            // sinon, affichage de l'adresse à problème
            // "\nFichier %s, "
            // "Destinataire %s refusé\n"
            printf (message ("NOM_FICH"), entree->d_name);
            printf (message ("REFUS_DEST"), bufw + 9);
            puts (buf_lect);
        }

        // dans les 2 cas, passer à l'adresse suivante
        if (ligne [i] == '>')
            i++;

        while (ligne [i] == ' ' || ligne [i] == ',')
            i++;

        // et traiter les autres adresses de la ligne
        if (ligne [i])
            env_adr (entete, ligne + i);
    }
}



// déclare les destinataires du message contenu dans un champ
// To: , Cc ou Bcc .

void env_dest (long poschamp)
{
    char lignedest [sz_buflect];  // ligne contenant des destinataires


    // se positionner sur la première ligne du champ destinataires à analyser
    fseek (fmail, poschamp, SEEK_SET);
    lire_fmail ();

    // tant qu'on n'a pas traité toutes les lignes du champ
    do
    {
        // copie nécessaire car la réponse à une requête smtp écrasera la
        // valeur de buf_lect qui pouvait contenir plusieurs destinataires
        strcpy (lignedest, buf_lect);

        // déclarer les adresses de destinataires contenus dans la ligne lue
        env_adr ("RCPT TO", lignedest);

        // passer à la ligne suivante
        lire_fmail ();
    }
    while (*buf_lect == ' ' || *buf_lect == '\t');
}



/* génère la ligne From de l'entête du mail à envoyer
   si elle ne figure pas dans le fichier mail */


void genfromlig ()
{
    int  i, j;  // compteurs


    // initialisation
    strcpy (buf_lect, "From: ");
    i = 0;
    j = 6;

    // recopier le début de la description de l'expéditeur
    while (adr_mail [i] && adr_mail [i] != '<' && adr_mail [i] != '@')
    {
        buf_lect [j++] = adr_mail [i];

        // terminé si pas de @ dans l'adresse de l'expéditeur
        if (! adr_mail [i++])
        {
            // "Adresse Email erronée dans le fichier de configuration"
            affiche_msg ("ERR_ADR_MAIL");
            return;
        }
    }

    // se positionner au début de l'adresse
    while (i >= 0 && adr_mail [i] != '<' && adr_mail [i] != ' '
                                        && adr_mail [i] != '\t')
    {
        i--;
        j--;
    }

    // générer un espace si nécessaire
    if (adr_mail [i] != '<')
        buf_lect [j++] = ' ';

    // et le < de début d'adresse
    buf_lect [j++] = '<';

    // aller sur le 1er caractère de l'adresse
    i++;

    // recopier l'adresse
    do
        buf_lect [j++] = adr_mail [i++];
    while (adr_mail [i] && adr_mail [i] != '>' && adr_mail [i] != ' '
                                               && adr_mail [i] != '\t');

    // terminaison de l'adresse
    buf_lect [j++] = '>';
    buf_lect [j]  = '\0';
}



/* vérifie s'il y a au moins un caractère spécial à encoder
   dans la ligne d'entête et détermine le mode d'encodage */


int carspe_dans_ligne ()
{
    int i;  // compteur


    // initialisation
    i = 0;

    // recherche du premier caractère spécial
    while (buf_lect [i])
    {
        // si caractère spécial trouvé
        if (buf_lect [i++] & 0x80)
        {
            // détermination du jeu de caractères utilisé

            // séquence UTF-8 de 2 caractères
            // on refuse les séquences commençant par 0xC0 ou 0xC1
            // interdites par UTF-8 et qui ont posé des problèmes de sécurité
            if (0xC2 <= buf_lect [i-1] && buf_lect [i-1] <= 0xDF
             && 0x80 <= buf_lect [i]   && buf_lect [i]   <= 0xBF)
                // on a trouvé une séquence de caractères encodée UTF-8
                return (Utf8);

            // séquence UTF-8 de 3 caractères
            // même remarque pour les séquences commençant par 0xE0
            else if (0xE1 <= buf_lect [i-1] // && buf_lect [i-1] <= 0xFF
                  && 0x80 <= buf_lect [i]   && buf_lect [i]   <= 0xBF
                  && 0x80 <= buf_lect [i+1] && buf_lect [i+1] <= 0xBF)
                // on a trouvé une séquence de caractères encodée UTF-8
                return (Utf8);

            // on pourrait détecter de même des séquences
            // UTF-8 de 4 caractères mais est-ce bien utile ?

            else
                // on considère que le jeu de caractères utilisé est ISO-8859-n
                return (Iso8859);
        }
    }

    // aucun caractère spécial trouvé
    return (0);
}



/* encode une ligne d'entête contenant des caractères spéciaux */

void encode_ligne (int encodage)
{
    unsigned char lignecopie [sz_buflect];  // copie de buf_lect avant encodage
    int  dans_encode; // indicateur : encodage du mot courant
    int  i, j;        // compteurs


    // se positionner après le premier blanc ou la première tabulation
    j = 0;

    while (buf_lect [j] != ' ' && buf_lect [j] != '\t')
        j++;

    j++;

    // recopier le reste de la ligne
    strcpy (lignecopie, buf_lect + j);

    // si ligne Subject:
    if (start ("Subject"))
    {
        // l'encodage va porter sur tout le reste de la ligne
        if (encodage == Iso8859)
            strcpy (buf_lect + j, "=?ISO-8859-15?Q?");
        else
            strcpy (buf_lect + j, "=?UTF-8?Q?");

        // j indique le premier caractère libre de buf_lect
        j = strlen (buf_lect);

        for (i = 0; lignecopie [i] != '\0'; i++)
        {
            // les blancs et caractères de controle sont remplacés par des _
            if (lignecopie [i] <= ' ')
                buf_lect [j++] = '_';

            // encodage des caractères spéciaux
            else if (lignecopie [i] >= 0x7E || lignecopie [i] == '='
                  || lignecopie [i] == '?' || lignecopie [i] == '_')
            {
                sprintf (buf_lect + j, "=%02X", lignecopie [i]);

                // tenir compte du nombre de caractères rajoutés
                j = j + 3;
            }
            // et simple copie des autres caractères
            else
                buf_lect [j++] = lignecopie [i];
        }

        // on termine la ligne
        strcpy (buf_lect + j, "?=");
    }
    // sinon, on encodera par mot à partir du premier caractère spécial
    else
    {
        // initialisation
        dans_encode = 0;

        for (i = 0; lignecopie [i] != '\0'; i++)
        {
            // si fin de mot ou caractère de controle (indésirable)
            if (lignecopie [i] <= ' ')
            {
                // si encodage en cours
                if (dans_encode)
                {
                    // terminer l'encodage
                    buf_lect [j++] = '?';
                    buf_lect [j++] = '=';
                    dans_encode = 0;
                }

                // recopier le ' '
                // les caractères de controle sont aussi remplacés par des ' '
                buf_lect [j++] = ' ';
            }

            // sinon, si caractère spécial à encoder
            else if (lignecopie [i] >= 0x7E || lignecopie [i] == '='
                  || lignecopie [i] == '?' || lignecopie [i] == '_')
            {
                // si pas d'encodage en cours
                if (! dans_encode)
                {
                    // le démarrer
                    if (encodage == Iso8859)
                        strcpy (buf_lect + j, "=?ISO-8859-15?Q?");
                    else
                        strcpy (buf_lect + j, "=?UTF-8?Q?");

                    j = strlen (buf_lect);
                    dans_encode = 1;
                }

                // encoder le caractère
                sprintf (buf_lect + j, "=%02X", lignecopie [i]);

                // tenir compte du nombre de caractères rajoutés
                j = j + 3;
            }

            // simple copie des autres caractères
            else
                buf_lect [j++] = lignecopie [i];
        }

        // si encodage en cours
        if (dans_encode)
        {
            // le terminer
            buf_lect [j++] = '?';
            buf_lect [j++] = '=';
        }

        // terminer la ligne
        buf_lect [j] = '\0';
    }
}