Introduction au protocole NNTP
Regroupés au sein de Usenet, les groupes de discussion gérés par le protocole NNTP (Networks News Transfer Protocol) ont encore de beaux jours devant eux malgré l'avancée des forums (PhpBB et consorts) sur les pages web. On peut s'y abonner, les consulter en tout temps et poster des messages. Nous allons utiliser Python pour réaliser un petit lecteur de nouvelles qui permettra de rapatrier les messages d'un groupe.
Introduction
modifierÀ la fin des années 70, les protocoles pour utiliser les réseaux se multiplient. En 1972, le courriel apparaît, suivi de TCP/IP une année plus tard. Si le courriel est intéressant pour quelques échanges, la solution apparaît moins commode pour établir des discussions entre plusieurs utilisateurs. Le concept de « mailing-list » est alors inventé pour permettre des envois à plusieurs correspondants abonnés. En 1979, des étudiants de la Duke University en Caroline du Nord, Tom Truscott et Jim Ellis planchent sur une innovation en la matière : réaliser un réseau qui se chargerait de stocker les messages qui pourraient être consultés à tout moment. C'est ainsi que débute l'histoire d'Usenet, contraction de « Unix User Network ». Aidés par d'autres étudiants, ils réalisent un réseau entre leur université et la University of North Carolina.
Un protocole est spécialement développé pour gérer la diffusion des nouvelles : UUCP. Le réseau de nouvelles se répand rapidement au travers d'ARPANET et d'autres types de connexions (BBS, Fidonet, X.25, etc.). En 1984, le nombre de serveurs UUCP se monte à plus de 900 mais devant un tel succès, des modifications doivent être envisagées. Une des plus importantes est le passage à TCP/IP qui le lie à Internet. De plus, UUCP constitue un gouffre à ressources : chaque serveur transmet un nouveau message à un autre serveur sans vérifier si ce dernier le possède déjà. Les serveurs croulent ainsi sous des messages inutiles qui saturent le réseau. Avec l'expansion de Usenet, la masse de messages devient rapidement ingérable.
Le protocole UUCP est alors abandonné au profit de NNTP (Networks News Transfer Protocol), présenté en février 1986 par Brian Kantor et Phil Lapsley dans la RFC-977 (puis mis à jour par C.Feather dans la RFC-3977 en Octobre 2006). NNTP résout ce problème en introduisant la notion d'interaction entre les serveurs. Un serveur peut demander la liste de groupes et d'articles d'un autre serveur et rapatrier les messages désirés. Le réseau peut aussi être fragmenté entre gros et petits serveurs, ces derniers s'occupant par exemple d'une liste plus restreinte de groupes ou faisant office de cache.
Plusieurs universités dont Berkeley travaillent sur les applications côté serveur en essayant d'optimiser la gestion des messages et le système est remodelé pour obtenir la structure que nous connaissons aujourd'hui. Usenet devient une vitrine de prédilection, des annonces importantes y sont faites et des groupes concernant les sujets les plus divers allant de l'anodin au sulfureux (alt.sex, groupes de secte, etc.) se répandent. Le 6 août 1991, Tim Berners-Lee annonce dans alt.hypertext la création du « WorldWideWeb project ». Dans l'ordre des choses, le spam y fait son apparition dès 1994. On attribue la paternité du spam sur Usenet à Clarence Thomas avec le message « Global Alert For All : Jesus is Coming Soon » (17 janvier 1994). Il sera suivi peu après par un spam sur le génocide arménien puis le premier véritable spam commercial sur Usenet orchestré par deux avocats américains.
schéma de fonctionnement général :
Aperçu du protocole NNTP
modifierLe protocole NNTP est basé sur une suite de commandes et de réponses en ASCII comme SMTP. Les communications se font en général via le port 119 du serveur. Les réponses retournées par un serveur sont divisées en deux catégories dans la RFC : réponses « textuelles » et réponses « états » sous la forme de nombres. Les indications d'états permettent de savoir si une commande envoyée par le client a été correctement traitée par le serveur. Des paramètres peuvent être joints à l'identifiant de l'état du serveur. Une certaine liberté est prévue au travers de messages de débogage. Une commande réussie retournera un code de type 2xx (par exemple, 211 pour la commande GROUP). Les codes 4xx et 5xx sont des messages d'erreurs.
Instructions complémentaires au standard RFC-977 (notamment amenées par la RFC-3977) :
- ARTICLE <ID> : permet de récupérer l'en-tête et le corps du message identifié par ID, cet identifiant prend la forme d'une adresse mail classique, par exemple <F4WdnSa6OKXF46TeRVn-ig@xyz.com>
- ARTICLE <n°> : comme avant, sauf que l'on utilise un numéro attribué selon un simple compteur propre à chaque groupe de discussions
- HEAD ou BODY : même syntaxe que pour ARTICLE sauf qu'elles permettent d'isoler l'en-tête ou le corps du message
- LIST : permet d'obtenir la liste des groupes disponibles sur le serveur avec des indications sur le nombre de messages et l'intervalle des numéros de messages
- GROUP <id> : permet de sélectionner le groupe de discussion désiré, par exemple « sci.crypt », la commande retourne « 211 » (en cas de réussite) suivi du nombre d'articles dans le groupe et les indices du premier et du dernier article.
- IHAVE <id> : permet d'informer le serveur que nous sommes en possession du message avec l'identifiant indiqué. Le serveur formule une demande s'il désire recevoir une copie.
- LAST : permet d'obtenir le dernier article stocké par le serveur
- NEXT : positionne le pointeur d’article interne sur l’article suivant
- HELP : Invite le serveur à envoyer un texte contenant la liste des instructions disponibles
- SLAVE : indique au serveur qu’il ne communique pas avec un utilisateur final mais avec un serveur
- STAT: place le pointeur d’article interne sur l’article défini
- NEWGROUPS date heure [GMT] : permet d'obtenir les nouveaux groupes apparus depuis la date indiquée, l'indication GMT permet d'indiquer si l'heure est donnée en fonction de celle du serveur ou celle du méridien de Greenwich. (format date : YYMMDD, heure : HHMMSS)
- NEWNEWS groupes date heure [GMT] : agit de la même manière que la commande précédente pour obtenir les messages postés après la date indiquée, le champ « groupes » accepte des jokers (wildcards) et des choix multiples comme par exemple « net.*.linux » ou encore « net.*,!net.unix.* » qui accepte tous les net sauf ceux qui se terminent par unix.*
- POST : permet d'envoyer un message. Si la procédure est autorisée, le code 340 est retourné et le format décrit dans la RFC 1036 (mise à jour de la RFC 850) doit être suivi. Nous allons y revenir plus tard.
- QUIT : stoppe la connexion
Grâce à un client Telnet, il est possible d'expérimenter ces commandes. Voici un exemple de session sur un serveur en lecture seule mais à l'accès libre :
En plus de ces commandes conformes à la RFC-977, quelques commandes ont été ajoutés au fil du temps pour combler quelques lacunes du protocole initial :
- XGTITLE : fournit l’intitulé d’un groupe de newsgroups
- XHDR : Retourne la valeur d’un champ cité provenant de l’entête d’un message seul ou d’un ensemble de messages
- XOVER : Réclame la transmission d’un fichier d’aperçu sur le message actuel
- AUTHINFO : permet au client de se déclarer auprès du serveur avec un nom d’utilisateur et d’un mot de passe.
- DATE : interroge la date sur le serveur actuel
- LISTGROUP : Liste les numéros des messages disponibles à l’intérieur d’un newsgroup.
- MODE : permet de basculer entre les modes « Transit » et « Lecteur »
Exemple simple de Dialogue NNTP pour le post d'un article par un client:
[C] POST (1) [S] 340 Input article; end with <CR-LF>.<CR-LF> (2) [C] From: "Macstras" mactras@ulp.u-strasbg.fr (3) [C] Newsgroups: misc.test (3) [C] Subject: Demo pour l’expose MGP2i (3) [C] Organization: Personnel (3) [C] (3) [C] Article de demonstration. (3) [C] . (3) [S] 240 Article received OK (4)
[C] : Client [S] : Serveur
(1) Le client envoi la commande Post pour notifier le serveur d’un envoi d’article.
(2) Le serveur répond au client qu’il est prêt à recevoir les éléments de l’article à poster et spécifie au client la commande de fin du message.
(3) Le client constitue le message en envoyant unitairement chaque champ (obligatoire ou optionnel) constitutif de l’article.
(4) Le serveur envoi le message d’état confirmant le succès de la prise en compte de l’article
$ telnet news.readfreenews.net 119 201 white.readfreenews.net NNRP Service Ready - news@readfreenews.net (no posting). LIST 215 Newsgroups in form "group high low flags". visi.help 0000058262 0000058263 y visi.stats 0000039101 0000039102 m visi.general 0000172353 0000172354 y visi.gripes 0000045288 0000045289 y visi.announce 0000012334 0000012335 m visi.test 0000023657 0000023605 y mn.k12.teacher.cue 0000010279 0000010271 y mn.personals 0000011316 0000011248 y alt.support.tourette 0000142902 0000140727 y alt.support.step-parents 0000184119 0000182728 y alt.support.diet 0000584275 0000567574 y
(...très longue liste... le premier nombre correspond à l'index du dernier message, le deuxième nombre à l'index du second message, le drapeau y/n/m indique si l'écriture sur le groupe est autorisée, interdite ou sous modération)
GROUP sci.crypt 211 18749 240359 259107 sci.crypt
(211 = OK, nous avons 18749 messages dans le groupe sci.crypt, le premier se trouve à l'index 240359 et le dernier à 259107)
ARTICLE 258000
(retourne l'en-tête et le corps du message)
BODY <1123545560.959696.302110@g49g2000cwa.googlegroups.com> HEAD <1123545560.959696.302110@g49g2000cwa.googlegroups.com>
(retourne le contenu et l'en-tête d'un message que l'on peut sans risque qualifier de spam)
HELP
(retourne la liste des commandes)
IHAVE 123456789 480 Transfer permission denied
(le serveur est en lecture seule et ne désire pas recevoir de messages)
NEWNEWS *.linux 050926 101010 230 New news follows. <43382240$0$49017$14726298@news.sunsite.dk> <e4egj1tki2tcvhf4mm3t2s9udl660ao2tv@4ax.com> <43386797$0$49009$14726298@news.sunsite.dk> <4339095e$0$49014$14726298@news.sunsite.dk> <433921c6$0$49010$14726298@news.sunsite.dk>
(longue liste, tous les messages postés dans les groupes terminant par .linux après le 26 septembre 2005 à 10h10)
N. B. : Ne semble plus fonctionner avec le serveur de l'exemple (décembre 2005).
Le format des messages sur USENET
modifierDécrit au départ dans la RFC 850, le format des messages fut mis à jour dans la RFC 1036. Plusieurs formats se sont cotoyés mais à notre connaissance, tous les messages sont désormais formatés selon les principes utilisés pour le courriel.
Un message comporte donc les inévitables champs “From”, “Subject”, etc. Les en-têtes peuvent être examinées grâce à la plupart des clients comme Thunderbird ou encore via Telnet au travers de la commande HEAD (voir l'exemple de session). Dans Thunderbird, en affichant l'intégralité du message, on peut voir les différents champs présents dans un message. On y retrouve des infos diverses comme le client utilisé pour l'envoi, la date d'envoi, le nom et le type du serveur, etc.
Les plus intéressants du point de vue du NNTP sont le champ « Message-ID », les champs « Path » et surtout « References ». L'identifiant est sous la forme d'une adresse mail, il est unique avec un contenu aléatoire. La spécification recommande de ne pas utiliser le même identifiant avant deux ans, on peut toutefois se demander si ce conseil est encore valable. Le champ « Path » résume le chemin emprunté par le message pour arriver depuis l'expéditeur jusque dans votre serveur de nouvelles. En général, il y a relativement peu d'intermédiaires.
Le champ « References » résume toute l'historique d'un message et permet de déterminer sa position dans l'arborescence d'un « thread » (discussion). La figure 1 montre comment le champ « References » évolue selon la hiérarchie du message : de droite à gauche dans le champ, on trouve l'identifiant du message parent, suivi du message grand-parent et ainsi de suite jusqu'à arriver au premier message de la discussion concernée. Il est ainsi possible d'afficher les messages dans une structure en arbre comme dans Thunderbird en analysant les identifiants et en demandant au serveur d'envoyer les messages correspondants. À noter que le premier message d'une discussion n'a pas de champ « References » car il n'a bien entendu pas de parent.
Mise en œuvre avec Python
modifierPython propose une librairie nommée nntplib qui gère les commandes NNTP. Réaliser une lecture automatique des messages d'un groupe donné est ainsi très facile, les noms des méthodes de la classe NNTP correspondent aux commandes en ASCII du protocole NNTP.
Après avoir importé nntplib, on construit une instance via nntplib.NNTP(nom_du_serveur), la connexion est automatiquement lancée. On ne manquera pas d'englober les différents appels de fonctions dans des blocs try/except pour prendre en charge les erreurs et autres exceptions. La méthode « group » permet d'indiquer le groupe désiré. En retour, on obtient un tuple avec les informations sur le groupe : nombre d'articles, premier article, dernier article.
On peut ensuite lancer une récupération du message en indiquant à la méthode « article » le numéro de l'article désiré. De la même manière que dans le protocole NNTP, l'en-tête peut être récupérée grâce à la méthode « head » et le contenu via « body ». Les tuples obtenus contiennent les champs du message. Le listing proposé récupère les 10 derniers messages dans le groupe « sci.crypt » et affiche leur contenu. Constituer une arborescence est un peu plus compliqué, il faut analyser le champ « References » et faire des appels à « article » avec les identifiants désirés, nous avons volontairement laissé de côté cette fonctionnalité.
Pour poster des messages, la démarche est un peu plus complexe car il faut respecter les champs indiqués dans la spécification (RFC 1036), la méthode « post » de la classe NNTP permet ensuite d'envoyer le message. À cet effet, plusieurs groupes de test existent si vous voulez essayer de programmer une fonction d'envoi (alt.test, de.test, it.test, etc.). Pour l'envoi, le mieux est de se connecter au serveur de nouvelles de son fournisseur d'accès plutôt qu'un serveur gratuit, le constructeur de la classe NNTP permet d'indiquer le mot de passe et le nom d'utilisateur.
Exemple complet
modifier# -*- coding: utf-8 -*- import nntplib, sys nomServeur = 'news.readfreenews.net' nomGroupe = 'sci.crypt' # connexion au serveur print '%s - connexion en cours' % nomServeur try: news = nntplib.NNTP(nomServeur) except: print 'connexion impossible' sys.exit() print 'connexion réussie : ' + news.getwelcome() # connexion au groupe print '%s - connexion au groupe' % nomGroupe try: groupe = news.group(nomGroupe) except: print 'impossible de se connecter au groupe' sys.exit() print 'connexion au groupe réussie : ' + groupe[0] # affichage des informations sur le groupe info = {} info["debut"] = groupe[2] info['fin'] = groupe[3] info['nombre'] = groupe[1] print "nombre d'articles : " + info['nombre'] print "identifiant du premier article : " + info['debut'] print "identifiant du dernier article : " + info['fin'] for i in range(int(info['fin'])-20, int(info['fin'])): article = news.article(str(i)) body = news.body(str(i)) print "="*80 print "Numéro: " + body[1] print "Message-ID: " + body[2] for j in article[3]: if ("References:" in j): print j print "-"*80 for i in body[3]: print i print "-"*80
Liens utiles (en anglais)
modifier- http://www.ietf.org/rfc/rfc0977.txt – description du protocole NNTP
- http://www.ietf.org/rfc/rfc3977.txt – Mise à jour du protocole NNTP
- http://www.ietf.org/rfc/rfc1036.txt - description du format USENET
- http://docs.python.org/lib/module-nntplib.html – référence de nntplib
- http://en.wikipedia.org/wiki/Usenet – article en anglais sur Usenet