Tutorat système & réseau : serveur de messagerieXavier Redon |
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.
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.
Quelques précisions concernant les serveurs SMTP :
Quelques précisions concernant le stockage des courriels :
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 :
Il est fortement conseillé de suivre les étapes proposées ci-après pour réaliser le travail.
Le projet doit être constitué des sources distribuées dans les répertoires suivants :
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.
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 ?
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).
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.
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).
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.
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