/*
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);
}