Majordomo-like (Java)
Présentation:

Majordomo est un gestionnaire de listes de diffusion très courant, à l’instar de listar et j’en passe. Le principe d’un gestionnaire de listes de diffusion est de gérer des listes (si si) sur lesquelles des utilisateurs vont ensuite pouvoir envoyer des messages, qui seront diffusés (si si) à tout les abonnés de cette liste.
Dans gestionnaire, il y a gestion, ce qui veut dire que l’ensemble est dynamique. En effet, des listes sont créées, des utilisateurs s’abonnent, se désabonnent. Pour corser le tout, on est en droit d’imaginer un certain nombre d’options dans les listes parmi lesquelles la possibilité d’administrer les listes (on ne va pas donner ça à tout le monde quand même), de ne recevoir qu’un seul message toutes les 24h (digest), etc…
Dans le fond il s’agit presque de gérer un service de courrier où toutes les adresses seraient en fait un groupe différent de personnes.

Mon approche, ou quelques commentaires pré techniques :

Java étant ce qu’il est (multi plateformes au détriment d’une grande vitesse), il est inconcevable d’imaginer un système qui se chargerait à reception d’un message, ceci pouvant vouloir dire charger des milliers de listes et d’utilisateurs. Il faut donc prendre la voie du daemon (processus qui tourne en permanence en arrière plan), ce qui n’a rien de statanique, d’ailleurs…
Le choix d’un serveur tournant en permanence n’est pas si innocent que ça… En effet, vu l’architecture de java, il serait facile de créer un client spécifique pour ce type de service, par exemple. Mais nous verrons cela un peu plus loin.
Il faut savoir que ce système, bien que quasiment complet, est toujours en développement, et ce parce que toutes les fonctionnalités ne sont pas encore implémentées : on ne gère pas encore les options de liste, par exemple. Mais pour l’heure le temps presse, et je n’ai pas eu le temps de les gérer. Ca sera pour un peu plus tard.
Le choix d’utiliser un serveur implique de créer un client. Ce client a pour vocation de faire le pont entre le service de messagerie traditionnel, et le serveur de diffusion. Pour l’instant, il est extrèmement minimal vu son utilisation : il récupère le message envoyé a telle ou telle liste et le transfert dans son intégralité au serveur de listes, qui le traite. Vue l’impossibilité de faire tourner le logiciel sur des machines de test dont on n’a pas le « contrôle », j’ai également intégré un client de test qui ne permet que d’envoyer des commandes au serveur (ie pas de message à destination des listes). Celui ci permet de montrer cet aspect « client » dont je parlais.
Pour les habitués, un petit coup de telnet sur le port du serveur avec le contenu du message (les champs utilisés sont décrits plus loin) et le tour est joué.

Petit apparté : ce logiciel n’est pour l’instant PAS en libre diffusion. Il peut être téléchargé, avec le code source, pour étude, mais ne devrait pas être utilisé. En effet j’ai l’intention de le fignoler, et de le distribuer ensuite, une fois que j’estimerai que c’est un logiciel prêt pour être réellement sollicité.

Description des packages / classes (voir aussi la javadoc) :

Package misc
Pourquoi misc ? Pourquoi pas… Après tout il s’agit d’un ensemble de classes qui n’a pas sa place dans l’arborescence traditionnelle java…

Package majordomo
Celui la parle tout seul

Package incoming :
Il s’agit du traitement des informations entrantes (Client, Commandes en attente, etc…)
- MajordomoClientHandler : gère la communication avec le client, demande au Parseur d’étudier le message puis transmet aux différents organes chargés d’éxécuter les instructions. C’est le superviseur du package.
- MajordomoCommand : éxécute de facto les commandes des utilisateurs.
- MajordomoParser : prend un email au format smtp standard avec les entêtes et crée un objet de type MajordomoEmail pour le système.
- MajordomoParserError Une erreur de lecture du message.
- MajordomoPendingCommands : gère les commandes en attente.

Package list :
- MajordomoEmail : un objet représentant un email dans le système.
- MajordomoList : un objet représentant une liste et ses abonnés.
- MajordomoUser : un objet représentant un utilisateur et ses listes.
- DoesNotExistException : exception standard pour le manager.
- ListNotSubscribedException : exception levée si un utilisateur essaie de faire quelque chose sur une liste à laquelle il n’est pas abonné.
- UserNotSubscribedException : exception levée si une liste essaie de faire quelque chose sur un utilisateur qui n’y est pas abonné.
- MajordomoListManager : le superviseur de ce package. Il gère les listes et les utilisateurs, et c’est par lui que devraient transiter le plus d’instructions possible.

Package majordomod :
- MajordomoListfileFilter
- MajordomoUserfileFilter
Les filtres pour charger les bons fichiers

- MajordomoPrefsReader : lit les préférences et restaure le manager dans ses fonctions
- MajordomoPrefsWriter : enregistre les préférences.
- MajordomoPrefsReaderError The error raised when the prefs are not correct
- MajordomoPrefsWriterError The error raised when the prefs are not correct
Les exceptions associées

- MajordomoServer : le superviseur de cette classe. C’est lui qui gère les connections entrantes, les préférences, et le MajordomoListManager en charge.

Package server :
- MajordomoSMTP : la classe chargée d’envoyer les emails en passant par un serveur SMTP.
- MajordomoSMTPException : une exception générique de SMTP

A noter également, les classes « wrapper » :
- Majordomod : lit sur la ligne de commande UNIX les paramètres tels que chemin jusqu’aux préférences, email de l’admin en charge, serveur à utiliser, ou port à employer, puis lance le serveur.
- MajordomoClient : lit sur la ligne de commande UNIX le message au format SMTP standard et le redirige vers le serveur
- Majordomo : exemple de client orienté commandes en Swing.

Si on devait donner une hiérarchie de classes, (différente de celle des packages), on dirait :

MajordomoServer→MajordomoPrefsReader
MajordomoPrefsWriter
MajordomoClientHandler→ MajordomoParser
MajordomoCommand→ MajordomoPendingCommands
MajordomoListManager→ MajordomoList
MajordomoUser
MajordomoSMTP

Exemple de session de contrôle:

Session 1
DEBUG: sent a command
From: user1@domain.net
To: majordomo@majordomo.net
Subject: create toto

subscribe toto user2@domain.net

DEBUG: Le port a ete recupere: 12345
DEBUG: socket connecte
DEBUG: String envoyee
DEBUG: Tout va bien, BYE envoye
La session 1, c’est un admin qui cree une liste et demande l’inscription d’un utilisateur quelconque.
Les demandes de confirmation sont envoyées aux deux protagonistes.
Côté client on remarque la nécéssité de récupérer le port du serveur...
Etant donné qu’il n’y a pas de modification, pour l’instant, les messages du serveur sont inexistants.
Session1
DEBUG: Connection entrante
DEBUG: Chaine de commande lue
From: user1@domain.net
To: majordomo@majordomo.net
Subject: create toto

subscribe toto user2@domain.net
Session 2
DEBUG: sent a command
From: user1@domain.net
To: majordomo@majordomo.net
Subject: confirm 0

DEBUG: Le port a ete recupere: 12345
DEBUG: socket connecte
DEBUG: String envoyee
DEBUG: Tout va bien, BYE envoye
La session 2 c’est l’admin qui confirme sa demande de création de liste.
Il est alors abonné par défaut à cette liste.
Session 2
DEBUG: Connection entrante
DEBUG: Chaine de commande lue
From: user1@domain.net
To: majordomo@majordomo.net
Subject: confirm 0


***DEBUG: user1@domain.net has subscribed to toto
***DEBUG: 1 users are currently on this list
Session 3
DEBUG: sent a command
From: user2@domain.net
To: majordomo@majordomo.net
Subject: confirm 1



DEBUG: Le port a ete recupere: 12345
DEBUG: socket connecte
DEBUG: String envoyee
DEBUG: Tout va bien, BYE envoye
La session 3 c’est la confirmation de l’inscription du deuxième utilisateur.
On a deux utilisateurs, la discussion peut commencer.
Session 3
DEBUG: Connection entrante
DEBUG: Chaine de commande lue
From: user2@domain.net
To: majordomo@majordomo.net
Subject: confirm 1



***DEBUG: user2@domain.net has subscribed to toto
***DEBUG: 2 users are currently on this list


A l’issue de cet échange on a donc un admin et un utilisateur, sur une liste. Les envois de messages sur la liste seront détaillés plus loin.
Pour avoir une liste des commandes, il suffit de faire la même chose, avec « help » ou « commands » dans le sujet ou le corps du message.

Cette session illustre le fait qu’un client n’est pas bien compliqué. Mais que s’est il passé dans le serveur ?

Le serveur recoit 2 commandes, dans le premier échange :
create toto
subscribe toto user2@domain.net
Il met ces commandes en attente et demande confirmation. Les tickets de confirmation sont attribués dans l’ordre.
User1@domain.net reçoit donc une demande de confirmation pour la création, avec le ticket 0, et user2@domain.net reçoit une demande de confirmation pour son inscription, qu’il n’a pas lui même demandée (il aurait reçu exactement la même s’il avait été à l’origine de la demande), avec le ticket 1.
Après un temps indéterminé, user1@domain.net confirme sa commnde, et le serveur crée alors la liste et y abonne l’admin, par l’intermédiaire des fonctions du listmanager.
Enfin, user2@domain.net confirme lui aussi sa commande, et le listmanager l’ajoute aux abonnés de la liste.

Cela se passe comme ça pour quasiment toutes les commandes qui demandent une confirmation.

La diffusion de messages :

Tout ce qui n’est pas adressé à majordomo est envoyé au superviseur de listes. Celui ci détermine si la liste existe, si l’utilisateur a le droit de poster dessus, et fait l’envoi à tout les abonnés le cas échéant. Techniquement, il suffit de prendre toutes les adresses email enregistrées et de leur envoyer une copie du message.
Mais la liste de diffusion ça n’est pas seulement ça. On doit pouvoir réclamer des « digests », des compilations plus digestes, ou partir en vacances, etc...
Pour ce genre de cas, on dispose de deux fanions (options) qui permettent de basculer d’un mode à l’autre :
- DIGEST : on recoit tout les mails en un seul, une fois par jour.
- VACATION : on ne recoit plus les mails, on les recevra en une seule fois lorsqu’on ne sera plus en VACATION.

Les messages sont alors stockés temporairement dans un coin, en attendant une demande.
A noter quand même qu’avant d’envoyer un mail il faut impérativement utiliser la méthode normalizeBody, qui met des CRLF partout (\r\n), ce qui est la norme pour le smtp.


L'email:

Les messages sont traités et envoyés par le système: ils obéissent à un canevas relativement simple pour des raisons évidentes. Il gère ou s'intéresse à quelques champs très particuliers:
"To: " -> la liste destinataire ou majordomo dans un sens, les utilisateurs dans l'autre.
"From: " -> la liste destinataire ou majordomo dans un sens, les utilisateurs dans l'autre.
"Delivered-To: " -> utilisé pour la lecture du mail, parce qu'il arrive que les utilisateurs ne l'adressent pas directement à la liste, mais le mettent en CC, ou pire en BCC. Dans ce cas, la plupart des serveurs de mails rajoutent l'entête "Delivered-To:"
"Subject: " -> le sujet du message, dans les deux sens.
"Date: " -> en envoi seulement, donne la date à laquelle le système croit avoir traité le message.

Considérations diverses :
- Les serveurs de mail sendmail, postfix et qmail utilisent un système de pipes pour rediriger les flux vers des programmes. Autrement dit, il est impossible de faire marcher les classes java telles que.
L’idéal est de faire un petit script executable qui ressemble à :

# majordomo.sh
# !/bin/sh
java –classpath <chemin d’acces à majordomo.jar ou MajordomoClient.class> MajordomoClient $*

Il faut ensuite rajouter dans les alias (/etc/aliases) du serveur smtp un lien qui a cette allure la :
majordomo : | <chemin d’accès à majordomo.sh>
liste1 : | <chemin d’accès à majordomo.sh>
liste2 : | <chemin d’accès à majordomo.sh>
etc...

Dès lors le serveur smtp entrant est capable de gérer les listes de diffusion.
Attention toutefois. Dans certains cas, le script shell ne redirige pas tout a fait bien le flux. Il faut alors mettre dans les alias «majordomo : | java –classpath (...) MajordomoClient ». Ca marche très bien mais c’est un peu lourd à mon avis.

Sur mes machines de test, les emails « anonymes » (ie qui n’ont pas de destinataire reconnu) sont tous systématiquement redirigés sur majordomo.

- Il faut bien évidemment que le serveur de messagerie accepte de relayer les emails envoyés par la machine hébergeant majordomo et ce quasiment sans vérification... Sinon les emails avec une adresse de retour « non valides » risquent d’etre bloqués.

- Le système est en mode debug. Il arrivera un jour ou je mettrai ce debug en option mais pour l’instant, les informations envoyées sur la console sont utiles.

Nicolas Zinovieff