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_response
qui laissesmtp->mlrepl
à nul si pas multi-ligne - met donc
smtp->capa
à nul
- appelle
- appelle
mu_list_get_iteration
sur 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_open
mailer_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_test
STARTTLSmu_smtp_ehlo
# déclenché par le test sur STARTTLS
-
mu_smtp_capa_test
AUTHmu_smtp_ehlo
# déclenché par le test sur AUTH
-
mu_mailer_send_message
smtp_send_message
(libproto/mailer/smtp.c
)-
mu_smtp_capa_test
SIZEmu_smtp_ehlo
# le test sur SIZE
mu_mailer_close
mu_mailer_destroy
Après l'ouverture de la session, le EHLO
initial est envoyé. Le
client souhaite utiliser l'extension STARTTLS
5, 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).