Journal Epeios Meta Mail User Agent : le protocole IMAP.

Posté par (page perso) . Licence CC by-sa
11
30
jan.
2017

Suite des aventures concernant le développement d'un MUA, dont le début vous est rapporté ici.

Après POP3 (RFC 1939) et Mail Internet Format (RFC 5322), voici le tour d'IMAP (RFC 3501 et 5530) d'être implémenté (partiellement pour le moment), et notamment la structure générale des messages qui sont échangés entre client et serveur IMAP, ce qui facilitera la future implémentation des commandes manquantes.

La prise en compte d'IMAP n'ayant qu'un impact réduit sur l'application en elle-même (ajout de la possibilité de définir un agent IMAP, en plus de POP3), telle qu'elle était présentée dans le journal ci-dessus, je vais en profiter pour m'attarder sur l'utilitaire mmuaq[.exe], que vous trouverez dans processing/mmuaq/ (le lien menant au téléchargement de l’application est donné tout en bas).

Cet utilitaire me sert à mettre au point le code avant qu'il ne soit mis en œuvre dans l'application proprement dite, ainsi qu'à dépister les éventuels bugs, compte tenu qu'un utilitaire en ligne de commande est plus facile à débuguer qu'un daemon.

Notez qu'il ne s'agit pas du client en ligne de commande du MUA ; ce rôle est dévolu à l'utilitaire mmuaqcli[.exe], qui se trouve dans frontend/CLI/.

Les commandes disponibles dans mmuaq correspondent aux tâches de base nécessaires à le gestion d'un MUA. Concernant IMAP, il y a deux types de commandes, d'une part les commandes bas-niveau, chacune de ces commandes correspondant à une commande du protocole IMAP (avec lancement de quelques commandes accessoires, comme celle consistant à se logger, sans quoi peu de commandes IMAP pourront s'exécuter sans erreur), et des commandes de plus haut niveau, qui seront les commandes appelées par le backend de l'application, et qui nécessitent l'appel à plusieurs commandes IMAP de base.

Vu ce pourquoi cet utilitaire a été conçu, il a une option --verbose qui affiche le contenu de ce qui transite entre le client et le serveur. Ça permet de voir où cela coince lorsqu'il y a un problème. Les informations affichées avec --verbose, ainsi que celles affichées sans --verbose pour les commandes bas-niveau, subissent un (léger) formatage pour faciliter leur lecture.

Ainsi, voici ce qu'un SELECT (avec un LOGIN et un LOGOUT) peut donner lorsqu'on se connecte avec telnet sur un serveur IMAP (-> signale les commandes qui ont été saisies dans la console) :

$ telnet <host> imap
Trying <ip>...
Connected to <host>.
Escape character is '^]'.
* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE STARTTLS AUTH=PLAIN] Dovecot ready.
-> 0 LOGIN <username> <password>
0 OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS SPECIAL-USE BINARY MOVE] Logged in
-> 1 SELECT inbox
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft \*)] Flags permitted.
* 661 EXISTS
* 0 RECENT
* OK [UNSEEN 14] First unseen.
* OK [UIDVALIDITY 1409248764] UIDs valid
* OK [UIDNEXT 15448] Predicted next UID
1 OK [READ-WRITE] Select completed (0.000 secs).
-> 2 LOGOUT
* BYE Logging out
2 OK Logout completed.
Connection closed by foreign host.

Et la même chose avec mmuaq (-> signale les commandes envoyées par le logiciel) :

$ mmuaq --imap-select inbox --verbose
Capability: IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE STARTTLS AUTH=PLAIN
OK: Dovecot ready.
-> 0 LOGIN <username> <password>
Capability: IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS SPECIAL-USE BINARY MOVE
OK: Logged in
-> 1 SELECT inbox
Flags: (\Answered \Flagged \Deleted \Seen \Draft)
PermanentFlags: (\Answered \Flagged \Deleted \Seen \Draft *)
OK: Flags permitted.
Exists: 661
Recent: 0
Unseen: 14
OK: First unseen.
UIDValidity: 1409248764
OK: UIDs valid
UIDNext: 15448
OK: Predicted next UID
Read-Write
OK: Select completed (0.000 secs).
-> 2 LOGOUT
Bye: Logging out
OK: Logout completed.

Et la même commande sans --verbose, ce qui permet d'obtenir juste les informations retournées par la commande demandée :

$ mmuaq --imap-select inbox
Flags: (\Answered \Flagged \Deleted \Seen \Draft)
PermanentFlags: (\Answered \Flagged \Deleted \Seen \Draft \*)
Exists: 661
Recent: 0
Unseen: 14
UIDValidity: 1409248764
UIDNext: 15448
Read-Write

Un exemple avec une commande de plus haut niveau (-> signale les commandes envoyées par le logiciel, les réponses étant encadrés par <- et --) :

$ mmuaq --imap-RFC822-text inbox 1 --verbose
<- * OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN] Dovecot ready.
--
-> 0 LOGIN <username> <password>
<- 0 OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS SPECIAL-USE BINARY MOVE] Logged in
--
-> 1 LIST "" ""
<- * LIST (\Noselect) "/" ""
1 OK List completed.
--
-> 2 SELECT inbox
<- * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft \*)] Flags permitted.
* 3 EXISTS
* 0 RECENT
* OK [UNSEEN 3] First unseen.
* OK [UIDVALIDITY 1483863075] UIDs valid
* OK [UIDNEXT 4] Predicted next UID
2 OK [READ-WRITE] Select completed (0.000 secs).
--
-> 3 FETCH 1 RFC822.TEXT
<- * 1 FETCH (RFC822.TEXT {15}
Body mail 1.1
)
3 OK Fetch completed.
--
Body mail 1.1

-> 4 LOGOUT
<- * BYE Logging out
4 OK Logout completed.
--

Pour pouvoir utiliser les commandes relatives à IMAP, il faut créer une section IMAP dans la section Parameters du fichier de configuration de l'utilitaire (mmuaq.xcfg) avec le contenu suivant :

 <IMAP HostPort="HOST_PORT">
  <Username>USERNAME</Username>
  <Password>PASSWORD</Password>
 </IMAP>

avec, pour HOST_PORT, l'adresse et le port du serveur IMAP, sous la forme addresse:port, et pour USERNAMEet PASSWORD, respectivement le login et le mot de passe d'un compte IMAP du serveur indiqué. À noter que les connexions sécurisées ne sont pas encore prises en charge, mais certains serveurs IMAP acceptent des connexions non sécurisées de localhost, et permettent facilement de définir une liste d'IPs desquelles ils acceptent des connexions non sécurisée.

Si vous souhaitez pouvoir vous connecter à plusieurs comptes IMAP différents, sans avoir à modifier à chaque fois le fichier de configuration, vous pouvez définir, dans le fichier de configuration, un Setup pour chaque compte IMAP, et l'activer en passant son identifiant à l'option -s|--setup, comme indiqué dans la page d'aide de l'utilitaire (mmuaq[.exe] --help). Pour en savoir plus à propos des setups, vous pouvez consulter cette page.

Cet utilitaire permet aussi de lancer des commandes (celles préfixées par --pop3-) sur un serveur POP3, sous réserve de créer une section POP3 dans la section Parameters du fichier de configuration, avec le contenu suivant :

 <POP3 HostPort="HOST_PORT">
  <Username>USERNAME</Username>
  <Password>PASSWORD</Password>
 </POP3>

HOST_PORT, USERNAMEet PASSWORD ont la même signification que pour la section IMAP, et vous pouvez naturellement également utiliser les setups pour cette section.

Voici la liste des commandes acceptées par mmuaq :

$ mmuaq --help --#Language=fr
Utilitaire d'aide au développement de l'application 'MMUAq'.

mmuaq --help
    Affiche cette page.
mmuaq --version
    Affiche la version du programme.
mmuaq --license
    Affiche la licence du programme.
mmuaq --b64-encode [<entrée> [<sortie>]]
    Encodage 'Base64'.
mmuaq --b64-decode [<entrée> [<sortie>]]
    Decodage 'Base64'.
mmuaq --next-tag [<étiquette>]
    Retourne l'étiquette 'IMAP' suivante.
mmuaq --pop3-list [-s|--setup=<setup>] [--verbose] [<courrier>]
    Affiche des informations sur les courriers d'un serveur 'POP3'.
mmuaq --pop3-retrieve [-s|--setup=<setup>] [--verbose] <courrier>
    Récupère un courrier d'un serveur 'POP3'.
mmuaq --pop3-top [-s|--setup=<setup>] [--verbose] <courrier> <lignes>
    Affiche le début d'un courrier d'un serveur 'POP3'.
mmuaq --pop3-uidl [-s|--setup=<setup>] [--verbose] [<courrier>]
    Affiche l'identifiant unique des courriers d'un serveur 'POP3'.
mmuaq --imap-capability [-s|--setup=<setup>] [--verbose]
    Lance la commande 'CAPABILITY' du protocole 'IMAP'.
mmuaq --imap-list [-s|--setup=<setup>] [--verbose] <reference> <mailbox>
    Lance la commande 'LIST' du protocole 'IMAP'.
mmuaq --imap-lsub [-s|--setup=<setup>] [--verbose] <reference> <mailbox>
    Lance la commande 'LSUB' du protocole 'IMAP'.
mmuaq --imap-select [-s|--setup=<setup>] [--verbose] <mailbox>
    Lance la commande 'SELECT' du protocole 'IMAP'.
mmuaq --imap-fetch [-s|--setup=<setup>] [--verbose] [--uid] <mailbox> <sequence set> <items>
    Lance la commande 'FETCH' du protocole 'IMAP'.
mmuaq --imap-folders [-s|--setup=<setup>] [--verbose] [<dossier>]
    Affiche les sous-dossiers d'un dossier 'IMAP'.
mmuaq --imap-RFC822 [-s|--setup=<setup>] [--verbose] [--uid] <dossier> <courrier>
    Affiche le 'RFC822' d'un courrier d'un dossier 'IMAP'.
mmuaq --imap-RFC822-size [-s|--setup=<setup>] [--verbose] [--uid] <dossier> <courrier>
    Affiche le 'RFC822.SIZE' d'un courrier d'un dossier 'IMAP'.
mmuaq --imap-RFC822-header [-s|--setup=<setup>] [--verbose] [--uid] <dossier> <courrier>
    Affiche le 'RFC822.HEADER' d'un courrier d'un dossier 'IMAP'.
mmuaq --imap-RFC822-text [-s|--setup=<setup>] [--verbose] [--uid] <dossier> <courrier>
    Affiche le 'RFC822.TEXT' d'un courrier d'un dossier 'IMAP'.
mmuaq --imap-uid [-s|--setup=<setup>] [--verbose] <dossier> <courrier>
    Affiche l'UID' d'un courrier d'un dossier 'IMAP'.
mmuaq --imap-mail-amount [-s|--setup=<setup>] [--verbose] <dossier>
    Affiche le nombre de courriers d'un dossier 'IMAP'.
mmuaq --show-header [<entrée> [<sortie>]]
    Affiche l'entête d'un courrier.
mmuaq --get-field <champ> [<entrée> [<sortie>]]
    Affiche le contenu du champ d'un courrier.

--verbose ('Parameters/Verbose'='true'):
    Affiche les données échangées avec le serveur.
--uid ('Parameters/IMAP/UID'='true'):
    Lance le version 'UID' de la commande ('UID commande ...').
<setup> ('Parameters/@Setup'):
    Identifiant du 'setup' à utiliser.
<entrée> ('Parameters/Input'):
    Nom du fichier d'entrée (utilisation de l'entrée standard si absent).
<sortie> ('Parameters/Output'):
    Nom du fichier de sortie (utilisation de la sortie standard si absent).
<courrier> ('Parameters/MailID'):
    Identifiant de courrier (nombre, séquence, 'UID'... selon le contexte).
<lignes> ('Parameters/Lines'):
    Nombre de lignes.
<champ> ('Parameters/FieldName'):
    Nom du champ.
<étiquette> ('Parameters/Tag'):
    Étiquette 'IMAP'.
<reference> ('Parameters/IMAP/Reference'):
    'reference name' tel que défini pour le protocole 'IMAP'.
<mailbox> ('Parameters/IMAP/Mailbox'):
    'mailbox name' tel que défini pour le protocole 'IMAP'.
<sequence set> ('Parameters/IMAP/SequenceSet'):
    'sequence set' tel que défini pour le protocole 'IMAP'.
<items> ('Parameters/IMAP/Items'):
    'message data item names or macro' tel que défini pour le protocole 'IMAP'.
<dossier> ('Parameters/IMAP/Folder'):
    Dossier 'IMAP' ; dossier racine si absent.

Comme l'utilitaire est publié sous licence AGPL, les sources sont bien entendu consultables. Pour ceux que cela intéresse, voici en particulier ceux qui prennent en charge les différents protocoles :
- RFC 3522 (Internet Message Format) : muaimf.h muaimf.cpp,
- RFC 3501 et 5530 (IMAP) muaimabs.h muaimabs.cpp: muaima.h muaima.cpp,
- RFC 1939 (POP3) : muapo3.h muapo3.cpp,

et les sources de l'utilitaire proprement dit, qui met en œuvre ces différents sources : mmuaq.

Pour la documentation, les sources, les binaires… de l'application en général (qui inclut l'utilitaire détaillé dans ce journal), c'est par ici.

  • # RFC 5322 et ... ?

    Posté par (page perso) . Évalué à 4.

    J'aurais tout autant aimé que l'e-mail soit décrit sur une seule et unique RFC mais c'est sans compter le legacy avec la RFC 822/2822, le MIME avec la RFC 2045 (2046 .. 2049), les patches comme la RFC 6532 ainsi que quelques arcanes de la RFC 5321 (SMTP) - je me suis amusé1 à faire un tri topologique des dépendances entres les RFCs d'ailleurs.

    Et encore, ce n'est qu'une partie de l'iceberg. Le problème devient plus épineux avec l'encoding (Base64, Quoted-Printable, UTF-{7,8}, ISO …) où tu peux prier que le mail spécifie la langue dans laquelle il te parle en lieu place de définir un standard partagé entre les développeurs-réptiliens s'échangeant en secret des règles tacites de ce que devrait être un e-mail en faisant fît du véritable standard (et le gagnant reste Outlook).

    Bref, tu t'attaques à un gros morceau pour le coup et il existe déjà, je pense (je ne suis pas adepte du milieu CPP), des librairies qui font ça très bien, qui se sont posé les bonnes questions et qui connaissent ce qui révèle du cas particulier à une résilience assumé face aux erreurs possibles que tu peux retrouver dans les e-mails.

    La question est clairement pas simple car, au delà du fait qu'il faut avoir une connaissance assez large de se que tu peux retrouver dans l'intermail (et donc avoir une bonne base, j'ai eu la chance d'avoir ~ 2 000 000 d'e-mails à ma disposition), tu dois aussi connaître au minimum 7 RFC différentes2 pour avoir une vision globale et te poser des vraies questions sur l'API, sur ce qui est acceptable (mais pas autorisé par le standard) et sur ce qui ne l'est pas et trouver un juste milieu qui ne dépends que d'une étude empirique de ce qu'est, dans la vraie vie, l'e-mail (donc au delà du standard).

    Je continuerais toujours à dissocier le traitement automatique de l'e-mail du MUA d'ailleurs considérant que ces 2 parties sont déjà assez complexes intrinsèquement qu'une fusion nécessaire des deux demanderait un travail d'autant plus important. Pour le coup, je remets pas tellement en cause l'idée de refaire un MUA (même si d'autres diront que ça existe déjà) mais le traitement automatique d'un e-mail est un travail beaucoup trop conséquent qui fait toujours frétiller les développeurs qui ont été face aux dates de la RFC 822 ou encore à l'adresse e-mail en général1. Une question que je ne peux que te conseiller d'éviter donc.


    1. En passant, je me fais un peu de pub pour un projet que je viens de finir il y a quelques mois. 

    2. Je ne sais jamais si RFC est féminin ou masculin 

    • [^] # Re: RFC 5322 et ... ?

      Posté par . Évalué à 5. Dernière modification le 31/01/17 à 13:23.

      En passant, je me fais un peu de pub pour un projet que je viens de finir il y a quelques mois.

      Je ne t'accuse pas, c'est pas vraiment ta faute, mais ça a déclenché une hémorragie dans mes oreilles ton accent français.

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.