Tutorat système & réseau : serveur de messagerie

Xavier Redon

1  Description générale du projet

1.1  Objectif

L’objectif est de concevoir et de réaliser des serveurs SMTP pour gérer un système de messagerie.

Ce sujet est l’extension d’un sujet demandant la conception et la réalisation de ces serveurs à partir de zéro. Dans l’extension, vous avez toujours le sujet original mais aussi les sources d’une version beta du projet : PSR-ReX-v6.tar.

Vous devrez donc aller plus loin que l’objectif du sujet original. Par exemple, vous implanterez une version sécurisé des serveurs, i.e. SMTPS. Mais plus généralement vous devez aboutir, non pas à un prototype fonctionnant sous des conditions drastiques, mais à un produit finalisé testé et pleinement opérationnel.

2  Spécifications techniques

2.1  Architecture générale

Le système de messagerie va être simplifié par rapport à un système de messagerie classique.

Un système de messagerie classique est composé de trois composants : les agents utilisateurs (MUA), les agents de transfert (MTA) et les agents de distribution (MDA). Un MUA va contacter son MTA qui lui-même va contacter le MTA du destinataire et enfin ce dernier contacte le MDA local pour stocker le message.

Vous allez développer deux serveurs SMTP :

Pour tester votre système vous pourrez utiliser un MUA compatible avec le format de stockage maildir, par exemple mutt.

2.2  Précisions

Quelques précisions concernant les serveurs SMTP :

Quelques précisions concernant le stockage des courriels :

2.3  Procédures de test

Un test de vos serveurs en grandeur nature est demandé :

Pour avoir accès à ces ressources vous utiliserez les machines virtuelles de vos ainés.

Pour les test :

3  Etapes

Il est fortement conseillé de suivre les étapes proposées ci-après pour réaliser le travail.

3.1  Organisation modulaire

Le projet doit être constitué des sources distribuées dans les répertoires suivants :

Communication -
pour les sources de la bibliothèque contenant les fonctions de gestion réseau libcom ;
Threads -
pour les sources de la bibliothèque contenant les fonctions de gestion des threads libthrd ;
SMTP -
pour les fonctions de gestion du protocole SMTP communes aux deux serveurs ;
MTAint -
pour les sources du serveur SMTP récupérant les courriels de l’utilisateur et les transférant sur Internet ;
MTAext -
pour les sources du serveur SMTP récupérant les courriels d’Internet pour les stocker sur disque.

Cette arborescence et quelques squelettes de fichiers sont disponibles sous forme d’un fichier au format tar et compressé à l’URL http://rex.plil.fr/Enseignement/Systeme/Tutorat.IMA2a4.Messagerie/messagerie.tgz. Transférez ce fichier dans votre compte Polytech’Lille et décompressez-le avec la commande tar xvf messagerie.tgz.

Le répertoire obtenu contient déjà un fichier Makefile global et des Makefile annexes dans chaque sous-répertoire. Vous avez un exemple de Makefile permettant de générer un exécutable et un exemple de Makefile permettant de construire une bibliothèque. Appuyez vous sur ces deux exemples pour compléter les autres Makefile. Le projet doit pouvoir être généré par la simple commande make exécutée dans le répertoire principal du projet. Il doit être aussi possible de régénérer complètement le projet par la commande make clean all lancée du même répertoire.

On prévoira de pouvoir compiler les différents sources avec un drapeau DEBUG (option -DDEBUG de gcc), permettant un affichage conditionnel d’informations de déverminage des programmes.

3.2  Sockets et serveur TCP

Il s’agit dans cette étape de réaliser un serveur TCP basique à l’aide de l’interface de programmation des sockets.

Ecrivez dans le module libcom.c (répertoire Communication), les deux fonctions suivantes :

Testez cette bibliothèque en écrivant un programme mta_int.c (répertoire MTAint) qui l’utilise et dont la fonction de traitement des connexions (celle appelée par boucleServeur) effectue juste une écriture de message de bienvenue dans la socket et clôt la connexion. Pour écrire, et plus tard lire, sur la connexion utilisez les fonctions classiques comme fgets ou fprintf. Pour y arriver vous devez transformer le descripteur de socket en une structure de fichier par la primitive fdopen.

Ce programme peut prendre des arguments : -p <port> ou --port <port> pour spécifier un numéro de port différent de celui par défaut (port 25). Pour traiter les arguments, utilisez la fonction getopt_long (voir la page de manuel correspondante). Si les arguments sont incorrects, on doit afficher un message qui précise la syntaxe. Pour plus de clarté, l’analyse des arguments et l’affichage de la syntaxe seront écrits dans des fonctions séparées.

Modifiez la fonction de traitement des connexions afin qu’elle ne ferme la connexion qu’après avoir lu une commande en provenance du client. Testez votre serveur avec plusieurs commandes nc simultanées. Conclusion ?

3.3  Serveurs SMTP à base de processus légers

Pour que vos serveurs SMTP puissent traiter plusieurs connexions simultanément, vous allez lancer un processus léger (thread) par connexion. Pour cela, implémentez la fonction publique de libthrd (répertoire Threads) :

int lanceThread(void (*)(void *),void *,int);

Cette fonction doit avoir comme action de lancer un thread dans le mode détaché. Ce thread doit exécuter la fonction passée en paramètre. La dite fonction prenant elle même comme paramètre le second paramètre de lanceThread dont la taille mémoire est donnée par le troisième paramètre. Pour éviter les problèmes mémoire, votre fonction doit effectuer une copie, sur le tas, du second paramètre. C’est cette copie qui doit être passée à pthread_create. Pour éviter les fuites mémoire, la copie doit être libérée en fin d’exécution du Thread. Pour ce faire, il vous est conseillé d’utiliser une fonction intermédiaire récupérant un pointeur vers une structure comprenant les deux premiers paramètres de lanceThread.

Testez votre fonction lanceThread en créant une nouvelle fonction dans mta_int.c qui appelle votre fonction de traitement des connexions via lanceThread. Utilisez la nouvelle fonction comme paramètre de boucleServeur.

Vérifiez que votre serveur est maintenant capable de gérer plusieurs connexions simultanément (toujours avec l’utilitaire nc).

3.4  Bibliothèque SMTP

Pour plus de clarté, il vous est demandé d’écrire les fonctions de traitement du protocole SMTP dans la bibliothèque libSMTP.a du répertoire SMTP.

Cette bibliothèque doit aussi définir une structure permettant de stocker tous les éléments d’un courriel : l’expéditeur et les destinataires de l’enveloppe ainsi que le texte du message.

Il est rappelé que vous devez implanter une fonction par commande SMTP. La ligne complète de la commande doit être passée à cette fonction ainsi que le FILE * de la connexion au client et la structure du courriel en cours de définition. La lecture d’informations supplémentaires (pour la commande SMTP data) et l’écriture de la ligne de réponse est à la charge de la fonction.

3.5  Stockage des courriels

Comme indiqué dans les spécifications techniques, un compteur global doit être géré pour la génération des noms uniques de fichiers.

Pour implanter ce compteur un sémaphore est nécessaire.

Implantez dans votre bibliothèque libthrd les deux fonctions publiques :

void P(int) ;
void V(int) ;

Ces fonctions cachent totalement le fait que vous utilisez des verrous d’exclusion mutuelle pour threads POSIX. En particulier, les verrous sont représentés par une constante.

Vous pouvez aussi utiliser un sémaphore pour réaliser une terminaison propre de vos serveurs SMTP (en attendant l’arrêt des Threads en cours).

3.6  Les serveurs SMTP

Pour compléter l’écriture du serveur SMTP sortant vous devrez :

Vous aurez aussi à ajouter des options (via getopt_long) pour préciser des paramètres nécessaires à vos serveurs :

Pour implanter des délais maximum dans l’attente ou l’envoi de commandes SMTP, vous pouvez regarder du côté de setsockopt et des options SO_RCVTIMEO et SO_SNDTIMEO.

3.7  Chiffrement

Vous avez un exemple d’utilisation de la bibliothèque libssl pour établir des communications chiffrées à l’adresse https://github.com/openssl/openssl/tree/master/demos/sslecho.

Il serait assez peu pratique d’adapter directement l’exemple aux sources fournies pour le projet. En particulier, l’obligation d’utiliser les fonctions SSL_read et SSL_write n’est pas très compatibles avec l’usage intensif des fonctions fgets et fprintf pour l’implantation du protocole SMTP.

Vous allez donc créer un nouveau sous-répertoire Chiffrement dans le projet, ce répertoire doit contenir les sources d’une bibliothèque de chiffrement. Cette bibliothèque doit, notablement, inclure une fonction socket2SSL définie ci-après.

La fonction socket2SSL a vocation à être appelée après connect dans les clients TCP et après accept dans les serveurs TCP. Le descripteur obtenu peut alors être passé dans fdopen pour permettre l’utilisation des fonctions fgets, fprintf, etc. Ce qui sera écrit via le FILE * obtenu par fdopen sera, dans un premier temps, chiffré par SSL_write puis envoyé, dans un second temps, sur la connexion TCP. Ce qui sera lu via le FILE * sera récupéré de la connexion TCP puis déchiffré via le SSL_read.

La même fonction peut être utilisée après la réception d’un ordre STARTTLS pour basculer la communication en mode chiffré.


Ce document a été traduit de LATEX par HEVEA