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