Un bug amusant de l'outil GNU mailutils
Outil mailutils et TP de réseau
J'ai un TP d'introduction aux réseaux qui porte sur les protocoles de
la messagerie électronique SMTP et POP. Ce sont des protocoles assez
simples et au format texte, ce qui permet d'observer facilement une
conversation complète avec Follow TCP Stream dans wireshark, et
aussi de "jouer" au client ou au serveur avec l'outil netcat.
Dans ce TP j'utilise aussi (pour tester que les étudiants arrivent
bien à "jouer" un serveur SMTP avec netcat) l'outil mailutils send
du paquet logiciel mailutils et ai remarqué un comportement bizarre.
En cherchant à comprendre ce qui se passait, j'ai été amené à
éclaircir ma compréhension de certains aspects de la spécification et
explorer l'implémentation de l'outil mailutils pour y déceler une
bogue amusante (et en pratique totalement inoffensive, sauf peut être
pour un TP d'introduction aux réseaux…)
Mais commençons par le commencement !
Le protocole SMTP
Le format typique d'une conversation SMTP est le suivant (S: indique
un message émis par le serveur C: par le client) :
S: 220 smtp.example.com Welcome C: HELO client.example.com S: 250 smtp.example.com C: MAIL FROM:<lulu@example.com> S: 250 OK C: RCPT TO:<jp@example.com> S: 250 OK C: DATA S: 354 go on please! C: From: lulu@example.com C: To: jp@example.com C: Subject: yay! C: C: What's up doc? C: . S: 250 OK C: QUIT S: 221 Bye
On voit sur cet exemple qu'une fois le client connecté (ce qui n'est
pas montré ici car cela se passe à la couche transport), le serveur
commence par le saluer avec un message 220 suivi de son nom de
domaine et d'un texte optionnel, puis le client envoie des requêtes
auxquelles le serveur répond. HELO est la salutation du client
(le client y indique son nom de domaine), MAIL indique l'expéditeur
d'un message, RCPT son (un) destinataire, DATA la demande de
transmettre le mail proprement dit. Après la réponse 354 le client
envoie le contenu du mail, terminé par une ligne ne contenant qu'un
point.1 Enfin QUIT indique que le client a terminé.
Comme on le voit, les requêtes sont des mots de quatre lettres suivis d'un espace et d'un texte, les réponses des nombres sur trois chiffres (le premier indiquant la catégorie de la réponse, 2 = tout va bien, 3 = j'attends la suite…) suivi d'un espace et de texte.
On n'en voit pas ici, mais il est aussi possible d'avoir des réponses multi-lignes. Dans ce cas, le nombre est suivi d'un tiret au lieu d'un espace, indiquant que la réponse se poursuit sur la ligne suivante. Exemple extrait du RFC821 :
123-First line
123-Second line
123-234 text beginning with numbers
123 The last line
Le mécanisme d'extension SMTP
L'exemple ci-dessus correspond à la version originale du protocole, telle que définie dans le rfc821. Le rfc1869 a défini un "modèle d'extension", permettant au serveur d'informer le client des extensions au protocole qu'il sait gérer.2
Pour cela le client envoie une requête EHLO (extended helo) au
lieu de HELO et le serveur y répond avec une réponse multi-ligne, la
première indiquant (comme pour HELO) son nom de domaine, les
suivantes chacune un mot-clé identifiant une extension supportée.
In addition to opening the session, use of EHLO indicates that the client is able to process service extensions and requests that server provide a list of the extensions it supports.
Normally, the response to EHLO will be a multiline reply. Each line of the response contains a keyword and, optionally, one or more parameters. Following the normal syntax for multiline replies, these keywords follow the code (250) and a hyphen for all but the last line, and the code and a space for the last line.
Voici un exemple tiré du rfc2821, où le serveur répond au EHLO en
indiquant qu'il gère les extensions 8BITMIME, SIZE, DSN et HELP :
S: 220 foo.com Simple Mail Transfer Service Ready C: EHLO bar.com S: 250-foo.com greets bar.com S: 250-8BITMIME S: 250-SIZE S: 250-DSN S: 250 HELP
Si le serveur est une ancienne version ne connaissant pas le mécanisme
d'extension, il ne comprendra pas le EHLO, le client peut alors reprendre par un HELO.
Contemporary SMTP implementations MUST support the basic extension mechanisms. For instance, servers MUST support the EHLO command even if they do not implement any specific extensions and clients SHOULD preferentially utilize EHLO rather than HELO. (However, for compatibility with older conforming implementations, SMTP clients and servers MUST support the original HELO mechanisms as a fallback.)
Ce qui donnerait
S: 220 smtp.example.com running antique software C: EHLO client.example.com S: 502 Don't know that C: HELO client.example.com S: 250 smtp.example.com
L'extrait précédent indique qu'un serveur moderne, même s'il ne
supporte aucune extension, doit comprendre la requête EHLO. Il me
semble clair que dans ce cas de figure, il doit fournir une réponse
sur une seule ligne (c'est à dire qu'il fournit une liste de 0
extension), ce qui donnerait :
S: 220 smtp.example.com I'm modern but don't support any extension C: EHLO client.example.com S: 250 smtp.example.com
À noter la formulation suivante :
If the SMTP server, in violation of this specification, does not support any SMTP service extensions, it will generate an error response.
Cela me semble être une erreur, car contradictoire avec ce qui
précède. Je pense que ce que l'auteur voulait dire, c'est que si le
serveur SMTP ne connait pas le mécanisme d'extension (ce qui est rendu
obligatoire par la spécification actuelle), il répondra une erreur,
puisqu'il ne comprendra pas le message EHLO.
L'outil mailutils du paquet GNU mailutils
GNU mailutils est un ensemble de bibliothèques et d'utilitaires pour
traiter le courrier électronique. Il contient entre autre un outil
multi usage nommé… mailutils.
Cet outil permet de manipuler facilement les protocoles de la
messagerie dans un shell. Par exemple mailutils send prend en
arguments l'adresse IP d'un serveur SMTP et un fichier (censé contenir
un mail), contacte le serveur SMTP et met en oeuvre l'envoi du mail à
ce serveur.
mailutils send
Simulons un serveur SMTP : on lance netcat dans un terminal pour
attendre une connexion sur le port TCP 25 :
# netcat -l -p 25 -v listening on [any] 25 ...
Et dans un autre terminal on utilise mailutils send pour envoyer un
mail à notre "serveur" :
$ mailutils send 127.0.0.1 mail.msg
Voici l'échange vu sur le premier terminal (la première ligne est
affichée par netcat pour indiquer qu'un client vient de se connecter,
les lignes précédées par * sont tapées dans le terminal et donc
envoyées au client, les autres sont ce que nous recevons du client) :
connect to [127.0.0.1] from localhost [127.0.0.1] 56584 * 220 hello EHLO LC1758 * 250 ok EHLO LC1758 * 250 ? EHLO LC1758 * 250 excellence ? EHLO LC1758 * 250 innovation ! MAIL FROM:<lulu@example.com> * 250 ok RCPT TO:<jp@example.com> * 250 ok DATA * 354 vas y toto From: lulu@example.com To: jp@example.com Subject: yay! What's up doc? . * 250 QUIT * 221
On répond un 250 ok à la requête EHLO, ce qui semble ne pas
satisfaire le client, puisqu'il répète la requête. On choisit de
persister avec la même réponse, et cela finit par fonctionner puisque
la réponse est acceptée la quatrième fois, permettant à la suite de se
dérouler normalement. Ceci est quand même très bizarre et, surtout,
embêtant pour un TP où les étudiants doivent simuler avec netcat un
serveur SMTP crédible pour mailutils send.
mailutils smtp
Pour essayer de comprendre ce qui se passe, nous allons utiliser la
commande mailutils smtp, qui fournit un shell client smtp permettant
de déclencher manuellement les différentes opérations. On l'utilise
ici pour se connecter à notre "serveur smtp" afin d'examiner plus
précisément les choses. Ci dessous le client est à gauche, le serveur
à droite, les lignes précédées d'un * sont tapées le reste n'est
qu'affiché. On voit que la réponse 250 smtp.example.com est bien
traitée comme une erreur.
* $ mailutils smtp * $ netcat -l -v -p 2220 * smtp> connect 127.0.0.1 2222 Connection received ... 220 smtp.example.com * 220 smtp.example.com * smtp> ehlo client.example.com EHLO client.example.com Error: Invalid argument * 250 smtp.example.com smtp>
On imagine donc que c'est le cas aussi pour mailutils send, et qu'il
décide de réessayer, mais on ne voit pas du tout pourquoi cette
erreur, à la quatrième occurence, est finalement considérée comme non
problématique…
Le code source
Je vous préviens, le code source de mailutils est un peu touffu,
respirons un grand coup et soyons attentif…
Le shell
Le main et les commandes du shell smtp sont dans le fichier
mu/libexec/smtp.c. Le main appelle mutool_shell dans
mu/libexec/shell.c dont voici un extrait :
while (!done)
{
char *s, *line = input_line ();
[...]
/* Remove leading and trailing whitespace from the line.
Then, if there is anything left, add it to the history list
and execute it. */
s = mu_str_stripws (line);
if (*s)
{
[...]
status = execute_line (s);
if (status != 0)
mu_error ("Error: %s", mu_strerror (status));
}
free (line);
}
On a donc une boucle où on lit une ligne, l'exécute, le status
retourné pouvant indiquer la présence d'une erreur. execute_line
parse la ligne puis appelle la fonction C correspondant à la commande.
État courant
Ces fonctions sont définies dans smtp.c et utilisent une variable
globale smtp stockant l'état courant de la conversation :
static mu_smtp_t smtp;
avec (include/mailutils/smtp.h) :
typedef struct _mu_smtp *mu_smtp_t;
[...]
struct _mu_smtp
{
int flags;
[...]
mu_list_t capa;
[...]
/* I/O buffers */
char replcode[4];
char *replptr;
char *rdbuf;
[...]
mu_list_t mlrepl;
[...]
};
On y trouve tout un tas d'informations dont la dernière réponse reçue
(rdbuf), son code de réponse (replcode, code à 3 chiffres plus
l'octet zéro de fin de chaîne) et deux listes chaînées mlrepl (la
dernière réponse multi-ligne reçue) et capa (pour capabilities, les
extensions annoncées par le serveur).
Listes chaînées
Le type mu_list_t est une liste doublement chaînée :
include/mailutils/types.h
typedef struct _mu_list *mu_list_t;
include/mailutils/sys/list.h
struct list_data
{
void *item;
struct list_data *next;
struct list_data *prev;
};
struct _mu_list
{
struct list_data head;
[...]
};
Je cache presque tout pour ne pas nous noyer dans les détails (!) mais
l'important pour la suite est qu'une liste est un pointeur vers une
structure struct _mu_list.
connect
La première commande sera connect, exécutée par la fonction
com_connect qui établit la connexion TCP et initialise la variable
globale smtp
status = mu_smtp_create (&smtp);
libproto/mailer/smtp_create.c
smtp = calloc (1, sizeof (*smtp));
On sait que calloc initialise le contenu du bloc de mémoire alloué
avec des zéros, ce qui veut dire que mlrepl et capa ne sont alors
pas des listes vides mais des pointeurs nuls.
ehlo
La commande ehlo sera exécutée par com_ehlo (dans
mu/libexec/smtp.c) :
static int
com_ehlo (int argc, char **argv)
{
if (argc == 1)
{
if (mu_smtp_test_param (smtp, MU_SMTP_PARAM_DOMAIN))
{
mu_error (_("no domain set"));
return 0;
}
}
else
mu_smtp_set_param (smtp, MU_SMTP_PARAM_DOMAIN, argv[1]);
return com_capa (1, argv);
}
On voit qu'elle ne fait pas grand chose, essentiellement elle appelle
com_capa qui a pour but d'afficher la liste des capabilities du
serveur (c'est à dire le contenu de capa).
capa
Si capa n'est pas encore initialisée elle commencera par récupérer
cette liste, en envoyant un message EHLO.3
static int
com_capa (int argc, char **argv)
{
mu_iterator_t iterator = NULL;
int status = 0;
[...]
status = mu_smtp_capa_iterator (smtp, &iterator);
if (status == 0)
{
[...]
mu_iterator_destroy (&iterator);
}
}
return status;
}
Donc, mu_smtp_capa_iterator est appelée et échoue (retourne un
status non nul). Elle est définie dans
libproto/mailer/smtp_capa_itr.c :
int
mu_smtp_capa_iterator (mu_smtp_t smtp, mu_iterator_t *itr)
{
if (!smtp || !itr)
return EINVAL;
if (MU_SMTP_FISSET (smtp, _MU_SMTP_ERR))
return MU_ERR_FAILURE;
if (!smtp->capa)
{
int rc = mu_smtp_ehlo (smtp);
if (rc)
return rc;
}
if (!MU_SMTP_FISSET (smtp, _MU_SMTP_ESMTP))
return MU_ERR_FAILURE;
return mu_list_get_iterator (smtp->capa, itr);
}ehlo pour récupérer les capabilities
Comme on l'a vu, smtp->capa est une liste chaînée (mu_list_t)
contenant la liste des capabilities annoncées par le serveur. Si
c'est un pointeur nul, la liste n'a pas encore été collectée, on
essaie de le faire en appelant mu_smtp_ehlo (dans
libproto/mailer/smtp_ehlo.c), dont voici un extrait :
int
mu_smtp_ehlo (mu_smtp_t smtp)
{
int status;
[...]
status = mu_smtp_write (smtp, "EHLO %s\r\n",
smtp->param[MU_SMTP_PARAM_DOMAIN]);
MU_SMTP_CHECK_ERROR (smtp, status);
status = mu_smtp_response (smtp);
MU_SMTP_CHECK_ERROR (smtp, status);
if (smtp->replcode[0] == '2')
{
smtp->flags |= _MU_SMTP_ESMTP;
smtp->capa = smtp->mlrepl;
smtp->mlrepl = NULL;
mu_list_set_comparator (smtp->capa, capa_comp);
}
else if (smtp->replcode[0] == '4')
return MU_ERR_REPLY;
else
{
status = mu_smtp_write (smtp, "HELO %s\r\n",
smtp->param[MU_SMTP_PARAM_DOMAIN]);
MU_SMTP_CHECK_ERROR (smtp, status);
status = mu_smtp_response (smtp);
MU_SMTP_CHECK_ERROR (smtp, status);
smtp->flags &= ~_MU_SMTP_ESMTP;
if (smtp->replcode[0] != '2')
return MU_ERR_REPLY;
}
if (smtp->state == MU_SMTP_EHLO)
smtp->state = MU_SMTP_MAIL;
return 0;
On voit qu'on émet la requête EHLO et récupère la réponse (fonction
mu_smtp_response). Si le code de réponse est 2xx, on fixe
smtp->capa à smtp_mlrepl, s'il est 4xx on retourne une erreur,
sinon en essaie un HELO, conformément à la spécification
(rfc5321-2.2.1+10).
Dans notre exemple, nous répondions 250, on devrait donc initialiser smtp->capa (c'est parce qu'il était nul qu'on a appelé mu_smtp_ehlo).
Analyse d'une réponse
Voyons la fonction mu_smtp_response (fichier
libproto/mailer/smtp_io.c). La fonction commence par lire une ligne
et vérifie qu'elle est bien formée :
int
mu_smtp_response (mu_smtp_t smtp)
{
int rc;
size_t n;
rc = mu_stream_getline (smtp->carrier, &smtp->rdbuf, &smtp->rdsize, &n);
MU_SMTP_CHECK_ERROR (smtp, rc);
if (n == 0)
MU_SMTP_CHECK_ERROR (smtp, EIO);
n = mu_rtrim_class (smtp->rdbuf, MU_CTYPE_ENDLN);
if (n < 3 || !mu_isdigit (smtp->rdbuf[0]))
{
mu_diag_output (MU_DIAG_NOTICE,
"received invalid reply from SMTP server");
MU_SMTP_CHECK_ERROR (smtp, MU_ERR_BADREPLY);
}
Elle copie le code numérique dans smtp->replcode et teste si la
réponse est multi-ligne (smtp->rdbuf[3] == '-'). Si c'est le cas,
le drapeau _MU_SMTP_MLREPL est levé :
memcpy (smtp->replcode, smtp->rdbuf, 3);
smtp->replcode[3] = 0;
if (smtp->rdbuf[3] == '-')
{
smtp->flags |= _MU_SMTP_MLREPL;
[...]
puis une boucle permet d'accumuler le texte de toutes les lignes
suivantes dans la liste chaînée smtp->mlrepl :
rc = _mu_smtp_init_mlist (smtp); (ref:init)
MU_SMTP_CHECK_ERROR (smtp, rc);
do
{
char *p;
rc = mu_stream_getline (smtp->carrier, &smtp->rdbuf, &smtp->rdsize,
&n);
MU_SMTP_CHECK_ERROR (smtp, rc);
if (n == 0)
MU_SMTP_CHECK_ERROR (smtp, EIO);
n = mu_rtrim_class (smtp->rdbuf, MU_CTYPE_ENDLN);
if (n < 3 || memcmp (smtp->rdbuf, smtp->replcode, 3))
{
mu_diag_output (MU_DIAG_NOTICE,
"received invalid reply from SMTP server");
MU_SMTP_CHECK_ERROR (smtp, MU_ERR_BADREPLY);
}
p = strdup (smtp->rdbuf + 4);
if (!p)
MU_SMTP_CHECK_ERROR (smtp, ENOMEM);
mu_list_append (smtp->mlrepl, p); (ref:append)
}
while (smtp->rdbuf[3] == '-');
}
Ici _mu_smtp_init_mlist(smtp) (libproto/mailer/smtp.io.c) fixe
smtp->mlrepl à la liste vide, et mu_list_append
(libmailutils/list/append.c) y ajoute un élément.
Au final on récupère donc bien la liste des capabilities dans
smtp->mlrepl, qui sera ensuite affectée à smtp->capa par
mu_smtp_ehlo. Mais le cas qui nous occupe est celui où la réponse
n'est pas multi-ligne, et dans ce cas le drapeau _MU_SMTP_MLREPL est
baissé et smtp->mlrepl n'est pas modifiée :
else
{
smtp->flags &= ~_MU_SMTP_MLREPL;
smtp->replptr = smtp->rdbuf + 4;
}
return 0;
}Et donc
C'est donc un pointeur nul que mu_smtp_ehlo copie dans smtp->capa,
et c'est pour cela que mu_smtp_capa_iterator retourne une valeur non
nulle, le résultat de mu_list_get_iterator appliqué à un pointeur
nul !
Résumons. Quand on tape la commande ehlo (je note -> !0 pour
"retourne une valeur différente de zéro")
-
execute_line-> !0 car-
appelle
com_ehlo-> !0 car-
appelle
com_capa-> !0 car-
appelle
mu_smtp_capa_iterator-> !0 car-
appelle
mu_smtp_ehlo- appelle
mu_smtp_responsequi laissesmtp->mlreplà nul si pas multi-ligne - met donc
smtp->capaà nul
- appelle
- appelle
mu_list_get_iterationsur nul qui -> !0
-
-
-
-
Correction
Le problème est donc dans la fonction mu_smtp_response : si la
réponse est multi-ligne, le contenu est placé sous forme d'une liste
dans smtp->mlrepl, mais si ce n'est pas le cas, smtp->mlrepl reste
indéterminé, alors qu'il devrait devenir la liste vide !
On peut corriger facilement ceci en plaçant l'initialisation à la
liste vide avant le if pour qu'elle soit effectuée que la réponse
soit multi-ligne ou pas :
smtp->replcode[3] = 0;
rc = _mu_smtp_init_mlist (smtp); (====)
MU_SMTP_CHECK_ERROR (smtp, rc);
if (smtp->rdbuf[3] == '-')
Après ce déplacement de deux lignes et compilation, une réponse
simple-ligne est acceptée pour le EHLO :
* $ mailutils smtp * $ netcat -l -v -p 2220 * smtp> connect 127.0.0.1 2222 Connection received ... 220 smtp.example.com * 220 smtp.example.com * smtp> ehlo client.example.com EHLO client.example.com smtp> * 250 smtp.example.com
Et l'envoi d'un message par mailutils send se fait donc maintenant
sans EHLO répétés :
$ mailutils send 127.0.0.1 mail.msg * # netcat -v -l -p 25
listening on [any] 25 ...
connect to [127.0.0.1] from localhost [127.0.0.1] 52090
* 220 salut
EHLO LC1758
* 250 ok
MAIL FROM:<jean.pierre@sender>
* 250 ok
RCPT TO:<lulu@receiver>
* 250 ok
DATA
* 354 go
From: jean.pierre@sender
To: lulu@receiver
Subject: that one
yolo
.
* 250 ok
QUIT
* 221 ok
Ok, mais pourquoi quatre fois ?
On a corrigé le problème, mais tout n'est pas encore vraiment
éclairci : dans le cas de mailutils send, pourquoi la requête EHLO
est-elle répétée et la réponse acceptée la quatrième fois qu'elle est
fournie !?
Cet outil n'utilise pas les fonctions com_ehlo etc, qui sont les
commandes du shell smtp. Il est défini dans mu/libexec/send.c. Au
cours de la conversation, le client a besoin de tester le support
d'une extension par le serveur. Pour cela il peut consulter sa liste
smtp->capa. Ceci est réalisé par la fonction mu_smtp_capa_test :
libproto/mailer/smtp_capa.c
int
mu_smtp_capa_test (mu_smtp_t smtp, const char *name, const char **pret)
{
if (!smtp || !name)
return EINVAL;
if (MU_SMTP_FISSET (smtp, _MU_SMTP_ERR))
return MU_ERR_FAILURE;
if (!smtp->capa)
{
int rc = mu_smtp_ehlo (smtp);
if (rc)
return rc;
}
if (!MU_SMTP_FISSET (smtp, _MU_SMTP_ESMTP))
return MU_ERR_FAILURE;
return mu_list_locate (smtp->capa, (void*) name, (void**)pret);
}
Comme on le voit, si smtp->capa est nul, la fonction mu_smtp_ehlo
est appelée pour l'initialiser. La fonction mu_list_locate teste si
l'élément name est présent dans la liste smtp->capa, si c'est le
cas elle retourne 0 et place un pointeur sur l'élément dans
*pret.4 Mais on a vu qu'avec une réponse simple-ligne,
smtp->capa n'était pas modifié, un argument nul sera passé à la
fonction mu_list_locate qui retournera une valeur d'erreur (non
nulle). Des appels répétés à mu_smtp_capa_test pour tester
différentes extensions vont engendrer des appels répétés à
mu_smtp_ehlo et donc l'envoi à chaque fois d'une requête EHLO.
Ci dessous la séquence des appels de fonctions au cours de l'exécution complète :
-
main(mu/libexec/send.c)mu_mailer_create_from_url-
mu_mailer_open=libproto/mailer/smtp.c:smtp_openmailer_smtp_init_late-
mu_smtp_open(libproto/mailer/smtp_open.c)- check
smtp->state == MU_SMTP_INIT mu_smtp_response– check 2xxsmtp->state = MU_SMTP_EHLO
- check
mu_smtp_ehlo# le premier EHLO-
mu_smtp_capa_testSTARTTLSmu_smtp_ehlo# déclenché par le test sur STARTTLS
-
mu_smtp_capa_testAUTHmu_smtp_ehlo# déclenché par le test sur AUTH
-
mu_mailer_send_messagesmtp_send_message(libproto/mailer/smtp.c)-
mu_smtp_capa_testSIZEmu_smtp_ehlo# le test sur SIZE
mu_mailer_closemu_mailer_destroy
Après l'ouverture de la session, le EHLO initial est envoyé. Le
client souhaite utiliser l'extension STARTTLS5, il teste donc sa
présence dans sa liste capa, ce qui déclenche un appel à
mu_smtp_ehlo. Il teste ensuite l'extension AUTH pour
l'authentification (rfc4954), et enfin l'extension SIZE permettant
d'annoncer la taille d'un message avant son envoi (rfc1870).
Évidemment aucune de ces extensions n'est utilisée.
Et le HELO ?
Supposons maintenant que notre serveur est ancien et ne comprend pas
les requêtes EHLO, il répondra donc par une erreur (par exemple 502
Command not implemented), et le client est censé retenter avec HELO
(comme mentionné plus haut, code de mu_smtp_ehlo).
Voici ce qui se passe avec un serveur qui répond 502 à EHLO et
250 OK (simple ligne évidemment) à HELO :
root@LC1758:~# netcat -l -v -p 25 listening on [any] 25 ... connect to [127.0.0.1] from localhost [127.0.0.1] 58162 220 EHLO LC1758 502 HELO LC1758 250 EHLO LC1758 502 HELO LC1758 250 EHLO LC1758 502 HELO LC1758 250 EHLO LC1758 502 HELO LC1758 250 MAIL FROM:<lulu@example.com>
Le problème est toujours le même. Après une réponse 5xx au EHLO
le client essaie avec HELO mais smtp->capa reste égal à NULL ce
qui entraîne une tentative de récupérer la liste des capabilities (et
donc un appel à mu_smtp_ehlo pour chaque capa que le client aimerait
bien utiliser, voir le code en #get-capa).
Puisque notre modification précédente met smtp->mlrepl à la liste
vide dans tous les cas, on peut l'utiliser pour fixer la valeur de
smtp->capa :
libproto/mailer/smtp_ehlo.c/mu_smtp_ehlo
status = mu_smtp_write (smtp, "HELO %s\r\n",
smtp->param[MU_SMTP_PARAM_DOMAIN]);
MU_SMTP_CHECK_ERROR (smtp, status);
status = mu_smtp_response (smtp);
MU_SMTP_CHECK_ERROR (smtp, status);
smtp->flags &= ~_MU_SMTP_ESMTP;
smtp->capa = smtp->mlrepl; /* this must be the empty list */ (ligne ajoutée) (====)
if (smtp->replcode[0] != '2')
return MU_ERR_REPLY;
}Et bien voilà, tout fonctionne maintenant normalement !
Note : le bug a depuis été corrigé, mais pas avec le patch montré ici, voir https://savannah.gnu.org/patch/?10439.
Une chose qui peut étonner est que l'on indique les émetteur et
destinataire à la fois par des messages SMTP (dans MAIL et RCPT)
et dans le mail lui même. Les adresses transmises à SMTP sont celles
"de l'enveloppe" (c'est à dire utilisées par SMTP en tant que service
d'acheminement du mail) alors que celles présentes dans le mail font
partie du mail. Vu de SMTP le mail est juste un fichier et il ne se
préoccupe pas de son contenu. Les From et To dans le mail sont un
peu comme les lignes en tête d'une lettre, et on ne s'attend pas à ce
que le facteur les utilise !
Comme on l'a vu, dans SMTP la requête EHLO établit la
conversation avec le serveur et récupère la liste des
capabilities. Le shell mailutils distingue les deux aspects qui
sont intrinsèquement liés dans le protocole, mais d'autres protocoles
séparent les deux aspects : POP3 a une requête CAPA (rfc2449), IMAP4
une requête CAPABILITY (rfc9051).
Le test de présence est fait avec une fonction de comparaison qui n'est pas nécessairement l'égalité, d'où l'intérêt de fournir un pointeur sur l'élément trouvé (conformément au nom "locate"…)
Définie dans le rfc3207, cette extension offre un mécanisme (supporté par beaucoup de protocoles) permettant de passer la connexion en TLS avant de poursuivre les échanges. Ce n'est pas le mécanisme recommandé de nos jours, on préfère le "TLS implicite" où on utilise un port spécifique et la session TLS est établie directement avant tout échange à la couche application (rfc8314).