/*
    Fichier smtp.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


    Bibliothèque de fonctions permettant
    d'envoyer des mails au serveur smtp
*/


#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <time.h>
#include "buflect.h"
#include "messages.h"
#include "smtp.h"
#include "base64.h"


// #define DEBUG

#define defaut_port_smtp  25  // port utilisé par défaut par les serveurs smtp
#define timeout_connect    5  // attente maximum en secondes à la connexion

#define octet      unsigned char


/* prototypes de fonctions locales à ce source */

int connect_serv_smtp (char *serveur, int port_smtp);
int startligne (char *ligne, char *motcle);


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


static int smtp = -1;         // descripteur pour dialoguer avec le serveur



/* connexion au serveur smtp indiqué dans le fichier de configuration */

int connect_smtp (char *infoserveur)
{
    octet *carinfo;        // caractère de infoserveur
    char  serv_smtp [120]; // nom du serveur smtp utilisé
    int   port;            // numéro du port smtp utilisé
    FILE  *fich_auth;      // descripteur du fichier d'authentification smtp
    char  bufin [120];     // buffer pour lire le fichier d'authentification
    char  bufw [120];      // buffer d'envoi d'une ligne ou requête smtp
    int   caract;          // caractère du fichier d'authentification smtp
    int   retour;          // code de retour de la fonction


    // initialisation du numéro de port par défaut
    port = defaut_port_smtp;

    // si la ligne smtp de fichier de configuration fait
    // référence à un fichier d'authentification smtp
    if (*infoserveur == '>')
    {
        // se positionner sur le nom de ce fichier
        carinfo = infoserveur + 1;

        while (*carinfo == ' ' || *carinfo == '\t')
            carinfo ++;

        fich_auth = fopen (carinfo, "r");

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

            // mémoriser les autres lignes particulières du fichier
            // d'authentification smtp (on s'arrête sur une ligne vide)
            while (fgets (bufin, 120, fich_auth) && *bufin != '\n')
            {
                bufin [strlen (bufin) - 1] = '\0';

                // numéro de port spécifié ?
                if (startligne (bufin, "port"))
                {
                    // récupérer le numéro du port
                    carinfo = bufin + 4;

                    while (*carinfo == ' ' || *carinfo == '\t')
                        carinfo++;

                    port = atoi (carinfo);

                    // le vérifier sommairement
                    if (!port)
                    {
                        // "Numéro de port smtp invalide"
                        affiche_err ("PORT_SMTP_INVALIDE");
                        retour = 0;
                    }
                }
            }

            // tentative de connexion au serveur smtp
            if (port)
                retour = connect_serv_smtp (serv_smtp, port);

            // si la connexion s'est bien déroulée
            if (retour)
            {
                // se positionner sur la première ligne
                // du dialogue d'authentification
                while (fgets (bufin, 120, fich_auth) && *bufin == '\n')
                    ;

                // dialogue d'authentification
                do
                {
                    // mise en forme de la ligne lue
                    bufin [strlen (bufin) - 1] = '\0';

                    // si ligne à encoder base 64
                    if (startligne (bufin, "b64"))
                    {
                        // l'encoder avant de l'envoyer
                        encode64 (bufin + 4, bufw, strlen (bufin + 4));
#ifdef DEBUG
                        puts (bufin);
#endif
                        envoie_smtp (bufw);
                    }
                    // sinon, envoyer directement la ligne
                    else
                        envoie_smtp (bufin);

                    // et lire la réponse du serveur
                    lire_smtp ();
                }
                // passage à la ligne suivante
                while (fgets (bufin, 120, fich_auth));
            }

            // libérer le fichier d'authentification smtp
            fclose (fich_auth);
        }
        else
        {
            // message d'erreur
            // "Fichier %s non trouvé"
            aff_err_arg ("FICH_ABSENT", carinfo);
            retour = 0;
        }
    }
    // sinon simple connexion à un serveur smtp sans authentification
    else
    {
        // parcourir le nom du serveur smtp
        carinfo = infoserveur;

        while (*carinfo > ' ')
            carinfo ++;

        // si le nom du serveur smtp est suivi d'autres informations
        if (*carinfo)
        {
            // terminer le nom du serveur smtp
            *carinfo = '\0';

            // se positionner sur le numéro de port éventuel
            do
                carinfo++;
            while (*carinfo == ' ' || *carinfo == '\t');
        }

        // si un numéro de port semble présent
        if (*carinfo)
            // le récupérer
            port = atoi (carinfo);

        // si numéro de port trouvé ou si on a conservé le port par défaut
        if (port)
            // établir la connexion smtp avec le port choisi
            retour = connect_serv_smtp (infoserveur, port);

        // sinon message d'erreur
        else
        {
            // "Numéro de port smtp invalide"
            affiche_err ("PORT_SMTP_INVALIDE");
            retour = 0;
        }

        // si la connexion smtp s'est bien déroulée
        if (retour)
        {
            // générer le message  helo nom_de_calculateur
            strcpy (bufw, "HELO ");
            gethostname (bufw + 5, sizeof (bufw) - 5);

            // identification de l'ordinateur connecté
            envoie_smtp (bufw);
            lire_smtp ();
        }
    }

    // renvoyer le code de retour de la fonction
    return (retour);
}



/* connexion au serveur smtp */

int connect_serv_smtp (char *serveur, int port_smtp)
{
    struct hostent *HostEnt;      // description du host serveur
    struct sockaddr_in serv_addr; // addresse du serveur
    struct timeval st_timeout;    // structure pour fixer timeout à la connexion
    time_t avant, apres;          // pour détection sortie sur timeout


#ifdef DEBUG
    printf ("Appel de connect_serv_smtp (%s, %d)\n", serveur, port_smtp);
#endif

    // récupération adresse du serveur à partir de son nom
    HostEnt = gethostbyname (serveur);

    if (HostEnt == NULL)
    {
        // "Serveur smtp non trouvé"
        affiche_err ("SERV_SMTP_ABSENT");
        return (0);
    }

    // initialisation structure serv_adr
    memset (&serv_addr, 0, sizeof (serv_addr)); // init serv_addr

    memcpy (&serv_addr.sin_addr, HostEnt->h_addr, HostEnt->h_length);

    serv_addr.sin_port = htons (port_smtp);     // port smtp utilisé
    serv_addr.sin_family = AF_INET;             // AF_*** : INET=internet

    // création de la socket
    if ((smtp = socket (AF_INET, SOCK_STREAM, 0)) < 0)
    {
        // "Problème création socket client smtp"
        affiche_err ("CREAT_SOCKET_SMTP");
        return (0);
    }

    // on fixe un temps de réponse maximum à la connexion
    st_timeout.tv_sec  = timeout_connect;
    st_timeout.tv_usec = 0;

    if (setsockopt (smtp, SOL_SOCKET, SO_SNDTIMEO,
                          &st_timeout, sizeof (st_timeout)) < 0)
    {
        // "Problème pour fixer un timeout à la connexion : attente bloquante"
        affiche_err ("PB_FIX_TIMEOUT");
    }

    // on va mesurer le temps nécessaire pour se connecter
    time (&avant);

    // requète de connexion
    if (connect (smtp, (struct sockaddr*) &serv_addr, sizeof (serv_addr)) < 0)
    {
        time (&apres);

        // afficher un message adapté
        if (apres - avant >= timeout_connect)
            // "Problème demande de connexion : délai dépassé"
            affiche_err ("PB_TIMEOUT_SERVEUR");
        else
        {
            // "Problème demande de connexion"
            // "peut être due à une connexion Internet par proxy"
            affiche_err ("PB_ACCES_SERV-1");
            affiche_err ("PB_ACCES_SERV-2");
        }

        return (0);
    }
    else
    {
        // attendre la réponse du serveur smtp
        lire_smtp (smtp);
        return (1);
    }
}



/* teste si la ligne passée en paramètre commence par un mot clé particulier */

int startligne (char *ligne, char *motcle)
{
    int i;

    i = 0;

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

    // le mot clé doit être suivi d'un blanc dans la ligne lue
    return (ligne [i] == ' ');
}



/* Ecriture de données
   envoie la chaine passée en paramètre suivie de \r\n
*/


void envoie_smtp (char *buffer)
{
#ifdef DEBUG
    putchar ('>');
    puts (buffer);
    fflush (stdout);
#endif

    write (smtp, buffer, strlen (buffer));
    write (smtp, "\r\n", 2);
}



/* Lecture d'une ligne de données
   la lecture s'arrête sur un caractère de passage à la ligne
*/


void lire_smtp ()
{
    int posbuf;


    // initialisation
    posbuf = 0;

    // lecture
    do
        read (smtp, buf_lect + posbuf, 1);
    while (buf_lect [posbuf++] != '\n' && posbuf < sz_buflect);

#ifdef DEBUG
    buf_lect [posbuf - 1] = '\0';
    putchar ('<');
    puts (buf_lect);

    // si encodage base64, on affiche aussi la donnée en clair
    if (startligne (buf_lect, "334"))
    {
        decode64 (buf_lect + 4);
        puts (buf_lect);
    }

    fflush (stdout);
#endif
}



/* déconnexion propre du serveur smtp */

void deconnect_smtp ()
{
    // envoi du message de deconnexion
    envoie_smtp ("QUIT");

    // lecture de l'acquittement
    lire_smtp ();

    // fermeture de la connexion
    shutdown (smtp, 2);
    close (smtp);
}