Linux, langage C
Directory
liens
Client smtp
Overview
Les fonctions de communication de ce client sont sous la forme d’une machine d’état dont voici le diagramme:
Explications:
Avant de rentrer dans l’automate, les paramètres rentrés en ligne de commande sont transmis à la fonction initMD()
qui effectue l’initialiasation d’un objet de type MailData. Cet objet nous permet de facilement déplacer les parametres.
L’objet de type MailData est transmis à la fonction machinEtat()
qui crée d’abord une connexion (via la fonction connexion()
) au serveur spécifié en paramètre à la ligne de commande avant de rentrer dans la machine d’état. Nos données passent ensuite par plusieurs états qui permettent la détection d’erreurs et le bon fonctionnement d’un envoi d’email. Le passage d’un état à l’autre se fait en fonction du premier chiffre (poids fort) du code erreur renvoyé par le serveur.
Le logiciel est capable de détecter si l’envoie a été grey-listé. Si tel est le cas, il va fork()
le process et relancer une instance de la fonction machinEtat()
dans le process enfant pendant que le process parent est terminé. Cette manipulation permet d’entre autres récupérer le prompt après un avertissement. Après 20 minutes le process fils va faire une nouvelle tentative d’envoi de l’email.
Compilation
Elle se fait à l’aide d’un simple make dans le folder où se trouve le fichier .c
.
TARGET=clientSMTP
CFLAGS=-Wall -lpthread
all: $(TARGET)
clean:
rm -f $(TARGET)
Utilisation
Il suffit de lancer le client en remplaçant les paramètres par ceux désirés:
$ ./clientSMTP from subject filePath domainDNS destination [port]
Code source:
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<netdb.h>
#define PORT_SMTP 25 // SMTP: port 25, (587: auth, 465: ssl)
#define BUFFER_SIZE 1024
// etats (DECOMMENTER CES LIGNES POUR USAGE !)
// #define START 0
// #define HELO 1
// #define FROM 2
// #define TO 3
// #define DATA 4
// #define SUBJ 5
// #define BODY 6
// #define DOT 7
// #define QUIT 8
// #define ERROR4 9
// #define ERROR5 10
// machineEtat: on/off
#define OFF 0
#define ON 1
// struct parametres
typedef struct MailData MailData;
struct MailData {
const char *source;
const char *sujet;
const char *filePath;
const char *domainDns;
const char *destination;
int portno;
};
// initialisation des parametres
void initMD(MailData *md, char **arg) {
md->source = arg[1];
md->sujet = arg[2];
md->filePath = arg[3];
md->domainDns = arg[4];
md->destination = arg[5];
if(arg[6]!=NULL) {
md->portno = atoi(arg[6]);
}
else {
md->portno = PORT_SMTP;
}
}
// prototypes
int machineEtat(const MailData *args, const int sleepTime);
int connexion(const MailData *md);
void finConnexion(const int sock);
void errExit(char *err, const int sock);
// =================================================
// ========== Main ==========
// =================================================
int main(int argc, char **argv) {
//check args
if (argc < 6) {
printf("ERROR bad-args\nusage:\t%s source subject filePath domainDNS destination [port]\n", argv[0]);
exit(1);
}
// declaration & initialisation objet MailData
MailData md1;
initMD(&md1, argv);
// lancement machine d'etat
machineEtat(&md1,0);
return EXIT_SUCCESS;
}
// ========= End of Main ==========
// =================================================
// ========== Connexion ==========
// =================================================
int connexion(const MailData *md) {
// declaration des variables utiles
int sock;
struct sockaddr_in servAddr;
struct hostent *serv;
// creation socket
if (!(sock = socket(AF_INET, SOCK_STREAM, 0))) {
errExit("ERROR: error opening socket", sock);
}
// initialisation des attributs descripteurs socket
if (!(serv = gethostbyname(md->domainDns))) {
errExit("ERROR: no such host", sock);
}
bzero( ( char *) &servAddr, sizeof(servAddr));
servAddr.sin_family = AF_INET;
bcopy( (char *) serv->h_addr,
(char *) &servAddr.sin_addr.s_addr,
serv->h_length);
servAddr.sin_port = htons(md->portno);
// connexion au serveur
if (connect(sock,
(struct sockaddr*) &servAddr,
sizeof(servAddr)) < 0) {
errExit("ERROR: error connecting", sock);
}
printf("\nConnected to %s\n", md->domainDns);
return sock;
}
// ========= End of connexion ==========
// =================================================
// ========== connexionFin ==========
// =================================================
void finConnexion(const int sock) {
if (sock < 0) {
return;
}
printf("Closing connexion...\n");
close(sock);
}
// ========= End of connexionFin ==========
// =================================================
// ========== machineEtat ==========
// =================================================
int machineEtat(const MailData *args, int sleepTime) {
sleep(sleepTime);
int etat, sock, onOff, recup, pid;
char buffer[BUFFER_SIZE];
FILE *f = NULL;
FILE *txt = NULL;
onOff = ON;
etat = START;
sock = connexion(args);
if (!(f = fdopen(sock, "r+"))) {
errExit("ERROR: can't open socket", sock);
}
while(onOff) {
recup = 0;
switch(etat) {
case START:
printf("\nEtat: START: Initialisation de la communication\n");
break;
case HELO:
printf("\nEtat: HELO\n");
fprintf(f, "HELO host\r\n");
break;
case FROM:
printf("\nEtat: FROM\n");
fprintf(f,"MAIL FROM: <%s>\r\n",args->source);
printf("MAIL FROM: <%s>\r\n",args->source);
break;
case TO:
printf("\nEtat: TO\n");
fprintf(f,"RCPT TO: <%s>\r\n",args->destination);
printf("RCPT TO: <%s>\r\n",args->destination);
break;
case DATA:
printf("\nEtat: DATA\n");
fprintf(f,"DATA\r\n");
break;
case SUBJ:
printf("\nEtat: SUBJ\n");
fprintf(f,"Subject: %s\r\n",args->sujet);
printf("Subject: %s\r\n",args->sujet);
recup =1;
break;
case BODY:
printf("\nEtat: BODY\n");
if (!(txt = fopen(args->filePath, "r"))) { // DRY
errExit("ERROR: can't open mail file", sock);
}
while (fgets(buffer, sizeof(buffer), txt)) {
fputs(buffer,f);
printf("%s\n",buffer );
}
fclose(txt);
recup=1;
break;
case DOT:
printf("\nEtat: DOT\n");
fprintf(f,"\r\n.\r\n");
break;
case QUIT:
printf("\nEtat: QUIT\n");
fprintf(f,"QUIT\r\n");
onOff = OFF;
break;
case ERROR4:
printf("\nEtat: ERROR4\n");
fprintf(stderr, "ERROR %c%c%c: grey-listed\n", buffer[0], buffer[1], buffer[2]);
fprintf(stderr, "forking process...\n");
onOff = OFF;
pid = fork();
// process enfant
if (pid == 0) {
printf("Child process: retrying to send in 20'...\n");
machineEtat(args, 1200);
}
// process parent
if (pid > 0) {
printf("Exit parent process..\n");
fclose(f);
finConnexion(sock);
exit(0);
}
// echec forking
else {
errExit("ERROR: fork failed", sock);
}
break;
case ERROR5:
printf("\nEtat: ERROR5\n");
fprintf(stderr, "ERROR %c%c%c: final-error\n", buffer[0], buffer[1], buffer[2]);
finConnexion(sock);
fprintf(stderr, "exit...\n");
exit(1);
break;
default:
errExit("ERROR: unknown error", sock);
}
if(recup == 0) {
fgets(buffer, sizeof(buffer), f);
printf("%s", buffer);
if(buffer[0] == '2' || buffer[0] == '3') {
++etat;
}
else if(buffer[0] == '4') {
etat = ERROR4;
}
else if(buffer[0] == '5') {
etat = ERROR5;
}
else {
etat = -1;
}
}
else {
++etat;
}
}
fclose(f);
finConnexion(sock);
return 0;
}
// ========= End of machineEtat ==========
// =================================================
// ========== errExit ==========
// =================================================
void errExit(char *err, const int sock) {
fprintf(stderr, "%s\n", err);
finConnexion(sock);
fprintf(stderr, "exit...\n");
exit(-1);
}
// ========= End of errExit ==========
Tests
1. localhost
sol@debian:~/code/clientSMTP/SMTP/clientSMTP$ ./clientSMTP3 un@deux.com test mail.txt localhost un@trois.com
Trying localhost
Connected to localhost
Etat: START
220 debian.home ESMTP Exim 4.84_2 Sun, 18 Jun 2017 10:32:02 -0500
Etat: HELO
250 debian.home Hello localhost [127.0.0.1]
Etat: FROM
MAIL FROM: <un@deux.com>
250 OK
Etat: TO
RCPT TO: <un@trois.com>
250 Accepted
Etat: DATA
354 Enter message, ending with "." on a line by itself
Etat: SUBJ
Subject: test
Etat: BODY
C'est pour ce soir!
Etat: DOT
250 OK id=1dMcBG-000179-FR
Etat: QUIT
221 debian.home closing connection
sol@debian:~/code/clientSMTP/SMTP/clientSMTP$
2. serveur mail réel
sol@debian:~/code/clientSMTP/SMTP/clientSMTP$ ./clientSMTP3 sol.rosca@he-arc.ch test mail.txt smtprel.he-arc.ch steven.jeanneret@outlook.com
Trying smtprel.he-arc.ch
Connected to smtprel.he-arc.ch
Etat: START
220 ***************************************************
Etat: HELO
250 srv-smtpapp1.he-arc.ch
Etat: FROM
MAIL FROM: <sol.rosca@he-arc.ch>
250 2.1.0 Ok
Etat: TO
RCPT TO: <steven.jeanneret@outlook.com>
250 2.1.5 Ok
Etat: DATA
354 End data with <CR><LF>.<CR><LF>
Etat: SUBJ
Subject: test
Etat: BODY
C'est pour ce soir!
Etat: DOT
250 2.0.0 Ok: queued as 1F45B60DC2
Etat: QUIT
221 2.0.0 Bye
sol@debian:~/code/clientSMTP/SMTP/clientSMTP$
Ici, nous testons donc avec un serveur fonctionnel. Voici une copie d’écran du mail reçu
3. Erreur 4xx et fork()
sol@debian:~/code/clientSMTP/SMTP/clientSMTP$ ./clientSMTP3 nathan.latino@he-arc.ch test mail.txt smtp.alphanet.ch schaefer@alphanet.ch
Trying smtp.alphanet.ch
Connected to smtp.alphanet.ch
Etat: START
220 shakotay.alphanet.ch ESMTP Postfix (Debian/GNU)
Etat: HELO
250 shakotay.alphanet.ch
Etat: FROM
MAIL FROM: <nathan.latino@he-arc.ch>
250 2.1.0 Ok
Etat: TO
RCPT TO: <schaefer@alphanet.ch>
450 4.2.0 <schaefer@alphanet.ch>: Recipient address rejected: Greylisted, see http://postgrey.schweikert.ch/help/alphanet.ch.html
Etat: ERROR4
ERROR 450: grey-listed
Forking process...
Exit parent process..
Child process: retrying to send in 20'...
sol@debian:~/code/clientSMTP/SMTP/clientSMTP$ ls
total 28
-rwxr-xr-x 1 sol sol 12008 Jun 18 10:15 clientSMTP3
-rwxr-xr-x 1 sol sol 6886 Jun 18 10:15 clientSMTP3.c
-rwxr-xr-x 1 sol sol 20 Jun 18 09:23 mail.txt
-rwxr-xr-x 1 sol sol 84 Jun 18 09:23 makefile
sol@debian:~/code/clientSMTP/SMTP/clientSMTP$ Trying smtp.alphanet.ch
Connected to smtp.alphanet.ch
Etat: START
220 shakotay.alphanet.ch ESMTP Postfix (Debian/GNU)
Etat: HELO
250 shakotay.alphanet.ch
Etat: FROM
MAIL FROM: <nathan.latino@he-arc.ch>
250 2.1.0 Ok
Etat: TO
RCPT TO: <schaefer@alphanet.ch>
450 4.2.0 <schaefer@alphanet.ch>: Recipient address rejected: Greylisted, see http://postgrey.schweikert.ch/help/alphanet.ch.html
Etat: ERROR4
ERROR 450: grey-listed
Forking process...
Exit parent process..
Child process: retrying to send in 20'...
Si nous observons le timing des différentes entrées, nous voyons qu’elles correspondent au log précédent. Pour faciliter les tests du méchanisme de fork()
le temps de réémission a été réduit à 30 secondes (dans la version finale ce temps à été ajusté à 20 minutes). La partie du milieux ne nous interesse pas, c’est l’activité réseau normale.
Voici également une capture d’un track du process clientSMTP qui nous montre le process parent dans un premier temps et qui laisse sa place au process enfant (2e capture). Dans la pratique, au moment de l’arrêt du process parent, nous récupérons le prompts.
4. erreur 5xx
sol@debian:~/code/clientSMTP/SMTP/clientSMTP$ ./clientSMTP3 sol.rosca@gmail test mail.txt aspmx.l.google.com test@testt.ch
Trying aspmx.l.google.com
Connected to aspmx.l.google.com
Etat: START
220 mx.google.com ESMTP h32si6380560edc.376 - gsmtp
Etat: HELO
250 mx.google.com at your service
Etat: FROM
MAIL FROM: <sol.rosca@gmail>
250 2.1.0 OK h32si6380560edc.376 - gsmtp
Etat: TO
RCPT TO: <test@testt.ch>
550-5.1.1 The email account that you tried to reach does not exist. Please try
Etat: ERROR5
ERROR 550: final error
exit...
sol@debian:~/code/clientSMTP/SMTP/clientSMTP$
Pour forcer une erreur de type 5xx, nous avons volontairement entré une adresse de destination invalide pour ce serveur (gmail). Nous pouvons constater que malgré l’erreur, la fermeture de la communication se fait de façon gracieuse.