--------------------------------------------------------------------------------------- IX. C.A.P.R.U, librairie de classe de gestion Client/Socket* Marcel --------------------------------------------------------------------------------------- * : article également disponnible en version html à cette adresse : http://www.rootshell.be/~ioc/mag/issue5/capru.html /*************/ (vous pouvez si vous voulez inclure mes sites, http://www.emulators.fr.fm et http://www.hackside.fr.fm) /*************/ [ Introduction ] Désireux d'apporter ma faible contribution à IOC, je tenterai de vous expliquer dans ce bref cours la méthode que j'utilise pour créer des applications Client/Serveur en C++. J'ai créé une librairie de classes, pas encore totalement terminée, qui permet à tout programmeur qui les utilise de créer des applications en utilisant les sockets d'une manière simple, dans des programmes graphiques ou non (avec QT, GTK, ...). Qui dit simple pour l'utilisateur, dit compliqué pour le développeur ;-) Cette librairie se nomme "C.A.P.R.U" pour "Classes d'Applications et de Protocoles Réseaux sous Unix". Elle n'est donc qu'en version de développement et non accessible sur le Net, toutefois si un lecteur est désireux de les voir, en tant qu'extension de ce cours, je serai ravi de lui faire parvenir, à condition de ne pas les redistribuer, pas tant que ce ne sera une version finie (ie stable). Il va de soi que ces classes sont sous licence GNU/GPL. Pour me joindre, mailez-moi par *** emulators@fr.fm *** Ou bien laissez un message sur l'un de mes sites. Je vais donc essayer de vous montrer la partie la moins simple qui consiste à la mise en oeuvre des classes et à leur programmation, dans un contexte de réutilisation maximale du code, et de robustesse. J'ai voulu utiliser le C++ pour la mise en oeuvre de la programmation objet, ce qui permet une modularité et une meilleure gestion des erreurs. De même ma politique a été de rendre ces classes les plus polyvalentes possibles, afin de pouvoir être utilisées par un maximum de personnes. Dans ce cours je vais expliquer pas à pas la méthode que j'ai utilisé pour créer cette librairie, afin que vous puissiez vous en inspirer pour créer votre propre librairie ! N'hésitez surtout pas à me mailer pour me demander des détails supplémentaires. A noter qu'entre le moment où vous lirez ce tutorial et celui où la librairie sortira officiellement, elle pourra avoir beaucoup changé. Ce cours est une exclusivité pour IOC, il sera par la suite disponible sur mes sites. Si vous avez la possibilité, lisez ce texte avec la coloration syntaxique du C++ Sommaire : __________ 1. L'architecture : Dans un premier temps je vais vous parler de tous les alentours de la programmation socket c'est-à-dire l'architecture des classes et la gestion des erreurs. 2. Client/Serveur Universels : Ensuite nous verrons la mise en oeuvre des sockets pour créer un serveur ou un client universels. 3. Programmes et Protocoles : Puis nous verrons de quelle manière utiliser ces classes pour en créer d'autres afin de gérer différents protocoles comme POP3, SMTP, ... ou bien créer des applications telles que des sniffers, des nukers des scanners de ports, ect ... I. L'Architecture : ___________________ J'ai créé une classe de base, CBazSocket, qui est composée de quelques fonctions membres qui lui sont propres (classes d'affichage sur stdout), mais aussi de toutes les fonctions virtuelles, qui sont aussi redéfinies dans chaque classe fille. Voici le fichier header de la classe de base : /*-----------8<-------------8<--------------8<---------------8<-----------------8<-------------*/ namespace client_serveur { class erreur { }; class CBazSocket { public : /*Fonctions annexes*/ void AfficherHeure(); void AfficherASCII(char *msg, long int taille); void AfficherHexa(char *msg, long int taille); /*Entrez une chaine et sa taille en paramètre, et je l'affiche à la manière d'un éditeur hexa (hexa + ascii)*/ void EnregDansFichier(char *msg, long int taille, char *chemin, bool ajout = true); /*Ces 4 classes vont souvent êtr eutilisées pour l'affichage ou l'enregistrement dans un fichier texte*/ /*Fonctions virtuelles pour CServeur et CClient*/ virtual void Creer(); virtual void Options(int niveau, int option, char *valeur); virtual void Options(int niveau, int option, int valeur); virtual void Connecter(); virtual void Envoyer(char *msg, long int taille); virtual void Recevoir(long int taillemin, long int taillemax); virtual void Deconnecter(); char *buffsrv; long int taillebuffsrv; char *buffcli; long int taillebuffcli; /*Fonctions virtuelles pour CInformations*/ virtual void Serveur(char *nomserveur); virtual void Protocole(char *nomproto); virtual void Service(char *nomservice, char *nomproto); virtual void Reseau(char *nomserveur, unsigned short int type); /*Structure pour stocker un hote, un protocole, un service, ou un nom de réseau*/ struct hostent *he; /*Modifié par CInformations::Serveur(), par CInformations::CInformations(), CClient::CClient(), et par CClient::Creer()*/ struct protoent *prtcl; /*Modifié par CInformations::Protocole(), et par CInformations::CInformations()*/ struct servent *srvc; /*Modifié par CInformations::Service(), et par CInformations::CInformations()*/ struct netent *ntnt; /*Modifié par CInformations::Reseau(), et par CInformations::CInformations()*/ /*Fonctions virtuelles pour CApplications*/ virtual void ScannerMonoIP(char *nomserveur); virtual void ScannerMultiIP(char *nomserveurdep, char *nomserveurfin, unsigned short int port = 1025); virtual void Sniffer(unsigned short int adrssge); virtual void Nuker(char *nomserveur, char *msg, unsigned short int port = 139, unsigned long int nbrfois = 100); char *buffappl; long int taillebuffappl; /*Fonctions virtuelles pour CPOP3*/ virtual void ClientPOP3_Connecter(char *nomserveur); virtual void ClientPOP3_UserPass(char *user, char *pass); virtual void ClientPOP3_Stats(); virtual void ClientPOP3_Lister(unsigned long int numero = 0); virtual void ClientPOP3_VoirEnTete(unsigned short int numero = 1, unsigned long int nbrlignes = 0); virtual void ClientPOP3_AfficherMail(unsigned short int numero = 1, long int taillemin = 10, long int taillemax = 10); virtual void ClientPOP3_EffacerMail(unsigned short int numero = 0); virtual void ClientPOP3_RestaurerTous(); virtual void ClientPOP3_Quitter(); virtual void ServeurPOP3(); char *buffpop3; long int taillebuffpop3; /*Entiers stockant en fait des sockets*/ long int srv; /*socket serveur local, modifié par CServeur::Creer();*/ long int srv_cli_cnt; /*socket utilisé lorsqu'un client distant se connecte au serveur local, modifié par CServeur::Connecter()*/ long int cli; /*socket client local, modifié par CClient::Creer()*/ /*Errorlevel*/ unsigned short int possible[10]; /*Paramètres du socket (entiers stockant les port, port d'envoi, domaine, type, et protocole des sockets, chaîne pour stocker un nom de serveur, ...)*/ unsigned short int adrssge; /*Doit contenir 4 pour IP4 et 6 pour IP6*/ char *nomserveur; /*Modifié par CClient::CClient()*/ unsigned short int port, domaine, type, protocole; /*Modifiés par CServeur::CServeur() et CClient::CClient()*/ unsigned short int portdenvoi; /*Modifié par CClient::CClient();*/ bool verbose; /*Si verbose vaut 'true', beaucoup de messages sont affichés, sinon aucun*/ short int affichage; /*Détermine le mode d'affichage des résultats des fonctions membres*/ bool ajout; char *chemin; /********************************/ /* Jusqu'à la fin de la classe, */ /* */ /*Données de stockage temporaire*/ /* utilisées par les classes */ /* CServeur et CClient */ /********************************/ /*Structures stockant l'identité des sockets*/ struct sockaddr_in sonin, monin; /*Pour la famille PF_INET*/ struct sockaddr_in6 sonin6, monin6; /*Pour la famille PF_INET6*/ struct sockaddr_ax25 *sonax25, *monax25; /*Pour la famille PF_AX25*/ struct sockaddr_ipx *sonipx, *monipx; /*Pour la famille PF_IPX*/ struct sockaddr_un *sonun, *monun; /*Pour la famille PF_UNIX,PF_LOCAL*/ /*Données membres statiques*/ static socklen_t sockaddr_in_len; /*vaut sizeof(struct sockaddr_in)*/ static socklen_t sockaddr_in6_len; /*vaut sizeof(struct sockaddr_in6)*/ static socklen_t sockaddr_ax25_len; /*vaut sizeof(struct sockaddr_ax25)*/ static socklen_t sockaddr_ipx_len; /*vaut sizeof(struct sockaddr_ipx)*/ static socklen_t sockaddr_un_len; /*vaut sizeof(struct sockaddr_un)*/ }; } /*Fin du namespace client_serveur*/ /*-----------8<-------------8<--------------8<---------------8<-----------------8<-------------*/ Voici les 4 fonctions membres d'affichage : void CBazSocket::AfficherHeure() { time_t sectmp; struct tm *stmtmp = new tm; time(§mp); stmtmp = localtime(§mp); printf("%2d h. %2d m. %2d s.", stmtmp->tm_hour, stmtmp->tm_min, stmtmp->tm_sec); } /*---------------------------------------------------------------------------*/ void CBazSocket::AfficherASCII(char *msg, long int taille) { long int i; printf("%d octets à ", (int)taille); AfficherHeure(); printf("\n"); for (i=0; i < taille; i++) printf("%c", msg[i]); printf("\n"); } /*---------------------------------------------------------------------------*/ void CBazSocket::AfficherHexa(char *msg, long int taille) { long int i, j; unsigned short int a; char *b = new char[5]; printf("%d octets à ", (int)taille); AfficherHeure(); printf("\n"); for (i=0; i < taille; i++) { a = msg[i]; sprintf (b, "%04X ", a); /*Affiche 16 caractères en hexa*/ printf ("%c%c ", b[2], b[3]); if (! ((i+1)%16)) /*Affiche 16 caractères en ASCII*/ { cout << "\t"; for (j=i-15; j <= i; j++) { a = msg[j]; if (a < 0x18 || a == 0x19 || ((a > 0x1A) && (a < 0x20)) || ((a >= 0x7F) && (a < 0xA0))) a = 0x2E; printf("%c ", a); } printf("\n"); } } if (taille%16) { for (i=0; i < 16-(taille%16); i++) printf(" "); cout << "\t"; for (j=(taille-(taille%16)); j < taille; j++) { a = msg[j]; if (a < 0x18 || a == 0x19 || ((a > 0x1A) && (a < 0x20)) || ((a >= 0x7F) && (a < 0xA0))) a = 0x2E; printf("%c ", a); } } printf("\n"); } /*---------------------------------------------------------------------------*/ void CBazSocket::EnregDansFichier(char *msg, long int taille, char *chemin, bool ajout) { FILE *fp; if (ajout) fp = fopen(chemin, "a"); else fp = fopen(chemin, "w"); if (fp == NULL) { if (verbose) perror("\tImpossible de créer ou d'ouvrir le fichier "); } else { if (fwrite(msg, sizeof(char), taille, fp) != (unsigned long int)taille) { if (verbose) perror("\tImpossible d'écrire dans le fichier (exemple, disque plein) "); } if (fclose(fp) == EOF) { if (verbose) perror ("\tImpossible de fermer le fichier "); } } } Vous remarquerez aussi que j'ai créé un namespace, afin d'être sûr de ne pas entrer en conflit avec d'autres fonctions d'autres librairies. Chaque classe à un nom de la forme Cxxx. la classe de base se nomme CBazSocket, et la classe fille Serveur se nomme CServeur par exemple. Vous pouvez donc aperçevoir 8 classes filles, à savoir : CServeur /*Serveur universel*/ CClient /*Client universel*/ CApplications /*Diverses applications telles que sniffer, nuker, ...*/ CInformations /*Informations sur un serveur, sur un protocole, ...*/ CPOP3 /*Protocole POP3*/ CFTP /*Protocole FTP*/ CHTTP /*Protocole HTTP*/ CSMTP /*Protocole SMTP*/ J'ai jugé utile d'utiliser le polymorphisme, notamment pour l'utilisation de listes chaînées d'objets ! De même il est à savoir que les classes d'applications et de protocoles sont une surcouche des classes CServeur et CClient, en les réutilisant. [ L'Errorlevel ] Il faut laisser le pouvoir au développeur utilisateur des classes de récupérer et intercépter les erreurs afin que le puisse les afficher à sa manière ( par exemple dans une boîte de dialogue visuelle ). Pour cela il m'a semblé nécéssaire de créer des Errorlevel. J'ai donc défini un tableau d'entiers courts, nommé 'possible', dont chaque élément correspond à une opération élémentaire d'une fonction membre d'une classe. Par exemple si la fonction Creer() ( fontion 1, donc indice 0 du tableau) de la classe CServeur a 5 opérations élémentaires, il faut que possible[0] = 5. Ceci n'est pas d'une originalité frappante, vous devez donc connaître le principe. Maintenant que l'architecture a été mise en place, passons aux choses intéressantes :) II. Client/Serveur Universels : ________________________________ Les 2 classes filles sans conteste les plus importantes sont bien sûr CServeur et CClient. Ces classes comportent chacunes les même fonctions membres (d'où l'utilité du polymorphisme), à savoir : virtual void Creer(); virtual void Options(int niveau, int option, char *valeur); virtual void Options(int niveau, int option, int valeur); virtual void Connecter(); virtual void Envoyer(char *msg, long int taille); virtual void Recevoir(long int taillemin, long int taillemax); virtual void Deconnecter(); Ces fonctions sont à la base de tout, et contiennent chacune les vérifications nécéssaires pour se permettre de les appeler dans n'importe quel ordre, jusqu'à ce que le bon ordre soit créé afin de créé le client ou le serveur voulu. Bien sûr c'est inutile de les appeler dans le désordre, mais je dois pouvoir tout prévoir. Là encore rien de spécatculaire dans ces vérifications d'erreurs ! A savoir que le constructeur reçoit divers paramètre nécessaires à toutes les méthodes de la classe. Etudions sans plus attendre la fonction Creer(), qui se contente de créer le socket : 1- Dans le cas du client : void CClient::Creer() { char *a = new char[256]; possible[0] = 0; if ((cli = socket(domaine, type, protocole)) == -1) /*---- Début Opération élémentaire 1 ----*/ { if (verbose) perror("\tClient : Erreur de socket "); } else { possible[0]++; if (verbose) { AfficherHeure(); cout << " : Client : Socket valide\n"; } if((he = gethostbyname(nomserveur)) == NULL) /*---- Début Opération élémentaire 2 ----*/ { if (verbose) perror("\tClient : Erreur d'obtention des infos sur le serveur "); } else { possible[0]++; if (verbose) { AfficherHeure(); if (domaine == PF_INET) { if (inet_ntop(PF_INET, (in_addr *)he->h_addr_list[0], a, 256) == NULL) fprintf(stderr, " : Client : Impossible de déterminer l'IP du serveur (distant) ?!?!\n"); else cout << " : Client : Infos sur " << nomserveur << " (" << a << ") trouvées\n"; } /*else { if (inet_ntop(PF_INET6, (in6_addr *)he->h_addr_list[0], a, 256) == NULL) cout << " : Client : Impossible de déterminer l'IP du serveur (distant) ?!?!\n"; else cout << " : Client : Infos sur " << nomserveur << " (" << a << ") trouvées\n"; }*/ } if (domaine == PF_INET) { memset(&sonin, 0, sockaddr_in_len); sonin.sin_family = domaine; sonin.sin_port = htons(port); sonin.sin_addr.s_addr = ((in_addr *)he->h_addr_list[0])->s_addr; /*ou bien (*(in_addr_t *)he->h_addr_list[0]) ...*/ } /*else { memset(&sonin6, 0, sockaddr_in6_len); sonin6.sin_family = domaine; sonin6.sin_port = htons(port); sonin6.sin_addr.s_addr = ((in6_addr *)he->h_addr_list[0])->s_addr; }*/ if (sonin.sin_addr.s_addr == INADDR_NONE /*|| sonin6.sin_addr.s_addr == INADDR_NONE*/) /*---- Début Opération élémentaire 3 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client : Impossible de trouver le serveur %s\n", nomserveur); } } else { possible[0]++; if (verbose) { AfficherHeure(); cout << " : Client : Hôte " << nomserveur << " trouvé\n"; } } /*---- Fin Opération élémentaire 3 ----*/ } /*---- Fin Opération élémentaire 2 ----*/ } /*---- Fin Opération élémentaire 1 ----*/ delete[] a; } Vous remarquerez que j'ai bien indiqué les opérations élémentaires, ici possible[0] doit valoir 3 pour être certain que tout a bien fonctionné. 2- Dans le cas du serveur : void CServeur::Creer() { possible[0] = 0; if (port < 1) /*---- Début Opération élémentaire 1 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Serveur : Impossible de se travailler car le numéro de port local(%d) est invalide\n\tChoisissez entre 1 et 65535\n", port); } } else { possible[0]++; if ((srv = socket(domaine, type, protocole)) == -1) /*---- Début Opération élémentaire 2 ----*/ { if (verbose) perror("\tServeur : Erreur de socket "); } else { possible[0]++; if (verbose) { AfficherHeure(); cout << " : Serveur : Socket valide\n"; } } /*---- Fin Opération élémentaire 2 ----*/ } /*---- Fin Opération élémentaire 1 ----*/ } Si vous êtes familier des sockets, vous pourrez constater que la plupart du code est dédié à la gestion des erreurs. J'ai beaucoup insisté sur ce dernier point afin de créer des classes robustes. Voilà maintenant le code de CClient::Connecter() : void CClient::Connecter() { long int i = 0; char *a = new char[256]; possible[1] = 0; if (possible[0] == 3) { if (domaine == PF_INET) /*Ne fait pas partie d'une opération élémentaire*/ { memset(&monin, 0, sockaddr_in_len); monin.sin_family = domaine; monin.sin_port = htons(portdenvoi); monin.sin_addr.s_addr = htonl(INADDR_ANY); i = bind(cli, (struct sockaddr *)&monin, sockaddr_in_len); } /*else { memset(&monin6, 0, sockaddr_in6_len); monin6.sin_family = domaine; monin6.sin_port = htons(portdenvoi); monin6.sin_addr.s_addr = htonl(INADDR_ANY); i = bind(cli, (struct sockaddr *)&monin6, sockaddr_in6_len); }*/ if (i == -1) { if (verbose) perror("\tClient : Impossible d'associer la socket à une addresse "); close(cli); } else { if (verbose) { AfficherHeure(); cout << " : Client : Socket associé à une adresse\n"; } } } /*Ne fait pas partie d'une opération élémentaire*/ if (possible[0] != 3 || type != SOCK_STREAM) /*---- Début Opération élémentaire 1 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client : Impossible de se connecter, raisons possibles :\n\t* Le socket n'a pas été créé\n\t* Il s'agit d'un socket qui ne nécessite pas de connexion (SOCK_DGRAM, ...)\n"); } } else { possible[1]++; if (port < 1 || port > 65535) /*---- Début Opération élémentaire 2 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client : Impossible de travailler car le numéro de port distant (%d) est invalide\n\tChoisissez entre 1 et 65535\n", port); } } else { possible[1]++; if (domaine == PF_INET) i = connect(cli, (struct sockaddr *)&sonin, sockaddr_in_len); /*else i = connect(cli, (struct sockaddr *)&sonin6, sockaddr_in6_len);*/ if (i == -1) /*---- Début Opération élémentaire 3 ----*/ { if (verbose) perror("\tClient : Impossible de se connecter au socket "); close(cli); } else { possible[1]++; if (verbose) { AfficherHeure(); cout << " : Client : Connecté au serveur distant " << nomserveur << endl; } memset(&monin, 0, sockaddr_in_len); memset(&monin6, 0, sockaddr_in6_len); if (domaine == PF_INET) i = getsockname(cli, (struct sockaddr *)&monin, &sockaddr_in_len); /*else i = getsockname(srv_cli_cnt, (struct sockaddr *)&monin6, &sockaddr_in6_len);*/ if (i == -1) /*---- Début Opération élémentaire 4 ----*/ { if (verbose) perror("\tClient : Impossible d'obtenir des infos sur le client "); } else { possible[1]++; if (verbose) { if (domaine == PF_INET) { cout << "\t\tFamille du socket local : " << monin.sin_family << endl; cout << "\t\tPort du client (local) ouvert : " << ntohs(monin.sin_port) << endl; if (inet_ntop(PF_INET, &monin.sin_addr, a, 256) == NULL) cout << "\t\tImpossible de déterminer l'IP du client (local) ?!?!\n"; else cout << "\t\tIP du client (local) : " << a << endl; } /*else { cout << "\t\tFamille du socket local : " << monin6.sin_family << endl; cout << "\t\tPort du client (local) ouvert : " << ntohs(monin6.sin_port) << endl; if (inet_ntop(PF_INET6, &monin6.sin_addr, a, 256) == NULL) cout << "\t\tImpossible de déterminer l'IP du client (local) ?!?!\n"; else cout << "\t\tIP du client (local) : " << a << endl; }*/ } memset(&sonin, 0, sockaddr_in_len); memset(&sonin6, 0, sockaddr_in6_len); if (domaine == PF_INET) i = getpeername(cli, (struct sockaddr *)&sonin, &sockaddr_in_len); /*else i = getpeername(srv_cli_cnt, (struct sockaddr *)&sonin6, &sockaddr_in6_len);*/ if (i == -1) /*---- Début Opération élémentaire 5 ----*/ { if (verbose) perror("\tClient : Impossible d'obtenir des infos sur le serveur "); } else { possible[1]++; if (verbose) { if (domaine == PF_INET) { cout << "\t\tFamille du socket distant : " << sonin.sin_family << endl; cout << "\t\tPort du serveur (distant) ouvert : " << ntohs(sonin.sin_port) << endl; if (inet_ntop(PF_INET, &sonin.sin_addr, a, 256) == NULL) cout << "\t\tImpossible de déterminer l'IP du serveur (distant) ?!?!\n"; else cout << "\t\tIP du serveur (distant) : " << a << endl; } /*else { cout << "\t\tFamille du socket distant : " << sonin6.sin_family << endl; cout << "\t\tPort du serveur (distant) ouvert : " << ntohs(sonin6.sin_port) << endl; if (inet_ntop(PF_INET6, &sonin6.sin_addr, a, 256) == NULL) cout << "\t\tImpossible de déterminer l'IP du serveur (distant) ?!?!\n"; else cout << "\t\tIP du serveur (distant) : " << a << endl; }*/ } } /*---- Fin Opération élémentaire 5 ----*/ } /*---- Fin Opération élémentaire 4 ----*/ } /*---- Fin Opération élémentaire 3 ----*/ } /*---- Fin Opération élémentaire 2 ----*/ } /*---- Fin Opération élémentaire 1 ----*/ delete[] a; } Vous pouvez lire le manuel ("man commande") des fonctions getpeername et getsockname. Analysez bien les 5 opérations élémentaires. Vous remarquerez que je connecte le socket au serveur distant qui se nomme 'nomserveur', initialisé lors de l'appel au constructeur, après avoir bindé le socket à un port local (opération souvent inutile dans le cas d'un client. Les fonctions surchargées Options() peuvent ne pas être utilisées, je ne expliquerai pas ici ! Voici le code de CServeur::Connecter() void CServeur::Connecter() { long int i = 0; char *a = new char[256]; possible[1] = 0; if (possible[0] == 2) /*Ne fait pas partie d'une opération élémentaire*/ { if (domaine == PF_INET) { memset(&monin, 0, sockaddr_in_len); monin.sin_family = domaine; monin.sin_port = htons(port); monin.sin_addr.s_addr = htonl(INADDR_ANY); i = bind(srv, (struct sockaddr *)&monin, sockaddr_in_len); } /*else { memset(&monin6, 0, sockaddr_in6_len); monin6.sin_family = domaine; monin6.sin_port = htons(port); monin6.sin_addr.s_addr = htonl(INADDR_ANY); i = bind(srv, (struct sockaddr *)&monin6, sockaddr_in6_len); }*/ if (i == -1) { if (verbose) perror("\tServeur : Impossible d'associer la socket à une addresse "); close(srv); } else { if (verbose) { AfficherHeure(); cout << " : Serveur : Socket associé à une adresse\n"; } } } /*Ne fait pas partie d'une opération élémentaire*/ if (possible[0] != 2 || type != SOCK_STREAM) /*---- Début Opération élémentaire 1 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Serveur : Impossible de se connecter, raisons possibles :\n\t* Le socket n'a pas été créé\n\t* Il s'agit d'un socket qui ne nécessite pas de connexion (SOCK_DGRAM, ...)\n"); } } else { possible[1]++; if (listen(srv, 1) == -1) /*---- Début Opération élémentaire 2 ----*/ { if (verbose) perror("\tServeur : Erreur sur listen avec la socket "); close(srv); } else { possible[1]++; if (verbose) { AfficherHeure(); cout << " : Serveur : Listen correctement réalisé sur le port " << port << "\n\tAttente de connexion d'un client ... ... ...\n"; } memset(&sonin, 0, sockaddr_in_len); memset(&sonin6, 0, sockaddr_in6_len); while (true) { if (domaine == PF_INET) srv_cli_cnt = accept(srv, (struct sockaddr *)&sonin, &sockaddr_in_len); /*else srv_cli_cnt = accept(srv, (struct sockaddr *)&sonin6, &sockaddr_in6_len);*/ if (srv_cli_cnt == -1) /*---- Début Opération élémentaire 3 ----*/ { if (verbose) perror("\tServeur : Erreur de socket "); close(srv); } else { possible[1]++; if (verbose) { AfficherHeure(); cout << " : Serveur : Un client distant est connecté\n"; } memset(&monin, 0, sockaddr_in_len); memset(&monin6, 0, sockaddr_in6_len); if (domaine == PF_INET) i = getsockname(srv_cli_cnt, (struct sockaddr *)&monin, &sockaddr_in_len); /*else i = getsockname(srv_cli_cnt, (struct sockaddr *)&monin6, &sockaddr_in6_len);*/ if (i == -1) /*---- Début Opération élémentaire 4 ----*/ { if (verbose) perror("\tServeur : Impossible d'obtenir des infos sur le serveur "); } else { possible[1]++; if (verbose) { if (domaine == PF_INET) { cout << "\t\tFamille du socket local : " << monin.sin_family << endl; cout << "\t\tPort du serveur (local) ouvert : " << ntohs(monin.sin_port) << endl; if (inet_ntop(PF_INET, &monin.sin_addr, a, 256) == NULL) cout << "\t\tImpossible de déterminer l'IP du serveur (local) ?!?!\n"; else cout << "\t\tIP du serveur (local) : " << a << endl; } /*else { cout << "\t\tFamille du socket local : " << monin6.sin_family << endl; cout << "\t\tPort du serveur (local) ouvert : " << ntohs(monin6.sin_port) << endl; if (inet_ntop(PF_INET6, &monin6.sin_addr, a, 256) == NULL) cout << "\t\tImpossible de déterminer l'IP du serveur (local) ?!?!\n"; else cout << "\t\tIP du serveur (local) : " << a << endl; }*/ } memset(&sonin, 0, sockaddr_in_len); memset(&sonin6, 0, sockaddr_in6_len); if (domaine == PF_INET) i = getpeername(srv_cli_cnt, (struct sockaddr *)&sonin, &sockaddr_in_len); /*else i = getpeername(srv_cli_cnt, (struct sockaddr *)&sonin6, &sockaddr_in6_len);*/ if (i == -1) /*---- Début Opération élémentaire 5 ----*/ { if (verbose) perror("\tServeur : Impossible d'obtenir des infos sur le client "); } else { possible[1]++; if (verbose) { if (domaine == PF_INET) { cout << "\t\tFamille du socket distant : " << sonin.sin_family << endl; cout << "\t\tPort du client (distant) ouvert : " << ntohs(sonin.sin_port) << endl; if (inet_ntop(PF_INET, &sonin.sin_addr, a, 256) == NULL) cout << "\t\tImpossible de déterminer l'IP du client (distant) ?!?!\n"; else cout << "\t\tIP du client (distant) : " << a << endl; } /*else { cout << "\t\tFamille du socket distant : " << sonin6.sin_family << endl; cout << "\t\tPort du client (distant) ouvert : " << ntohs(sonin6.sin_port) << endl; if (inet_ntop(PF_INET6, &sonin6.sin_addr, a, 256) == NULL) cout << "\t\tImpossible de déterminer l'IP du client (distant) ?!?!\n"; else cout << "\t\tIP du client (distant) : " << a << endl; }*/ } close (srv); break; /*TRES IMPORTANT, sinon continue la boucle même si un client s'est connecté*/ } /*---- Fin Opération élémentaire 5 ----*/ } /*---- Fin Opération élémentaire 4 ----*/ } /*---- Fin Opération élémentaire 3 ----*/ } /*Fin while(1)*/ } /*---- Fin Opération élémentaire 2 ----*/ } /*---- Fin Opération élémentaire 1 ----*/ delete[] a; } La grande différence avec le méthode du Client, est l'attente (listen puis une boucle infinie avec connect). Ne surtout pas oublier l'instruction break à la fin de la boucle infinie, pour qu'il ne continue pas à attendre un client lorsqu'il en a trouvé un ! A partir de maintenant, les fonctions seront presqu'identiques entre CClient et CServeur, je ne montrarai donc que celles de Client. Voici CClient::Envoyer() void CClient::Envoyer(char *msg, long int taille) { possible[2] = 0; if (possible[0] != 3 || (type == SOCK_STREAM && possible[1] != 5)) /*---- Début Opération élémentaire 1 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client : Impossible d'envoyer, raisons possibles :\n\t* Le socket n'a pas été créé\n\t* Le socket a été créé mais n'est pas connecté\n"); } taillebuffcli = 0; } else { possible[2]++; if (type != SOCK_STREAM) { if (domaine == PF_INET) taillebuffcli = sendto(cli, msg, taille, 0, (struct sockaddr *)&sonin, sockaddr_in_len); /*else *taille = sendto(cli, msg, strlen(msg), 0, (struct sockaddr *)&sonin6, sockaddr_in6_len);*/ } else taillebuffcli = send(cli, msg, taille, 0); if (taillebuffcli < 1) /*---- Début Opération élémentaire 2 ----*/ { if (verbose) perror("\tClient : Erreur d'envoi "); } else { possible[2]++; /*---- Fin Opération élémentaire 2 ----*/ if (affichage == 1) AfficherASCII(msg, taillebuffcli); if (affichage == 2) AfficherHexa(msg, taillebuffcli); if (affichage == 3) EnregDansFichier(msg, taillebuffcli, chemin, ajout); } } /*---- Fin Opération élémentaire 1 ----*/ } C'est ici d'une simplicité élémentaire (oui OK j'exagère :) , je vous laisse le soin de regarder ce court code. Voici CClient::Recevoir(), très simple aussi : void CClient::Recevoir(long int taillemin, long int taillemax) { long int i = 0; if (taillemin > taillemax) taillemin = taillemax; buffcli = new char[taillemax]; for (i=0; iCreer(); /*Optionnel, seulement si on veut configurer les options du socket*/ if (type == SOCK_STREAM) o_baz->Options(SOL_SOCKET, SO_REUSEADDR, 1); else if (type == SOCK_DGRAM) o_baz->Options(SOL_SOCKET, IP_ADD_MEMBERSHIP, "224.0.0.0"); /*Connexion si nécéssaire*/ o_baz->Connecter(); /*Réception de données*/ o_baz->AfficherHeure(); cout << " : Serveur : Ecoute\nSaisir le mot-clé de votre choix, qui permet, lorsqu'il est reçu, d'arrêter l'écoute\n"; cin >> msgquit; cout << "Ecoute activée ... ... (elle sera terminée à la réception du mot '" << msgquit << "')\n\n"; do { o_baz->Recevoir(0, 102400); } while (strcmp(o_baz->buffsrv, msgquit) && strcmp(o_baz->buffsrv, strcat(msgquit, "\n")) && o_baz->possible[3] == 2); /*Envoi de données si possible*/ o_baz->AfficherHeure(); cout << " : Serveur : Envoi\nSaisir le mot-clé de votre choix, qui permet, lorsqu'il est tapé, d'arrêter l'envoi\n"; cin >> msgquit; cout << "\t\tTapez maintenant les phrases à envoyer, validez par [Entrée]\n\t\tflèche droite pour les espaces, '" << msgquit << "' pour quitter\n\n"; do { fgets(msg, 102400, stdin); o_baz->Envoyer(msg, strlen(msg)); } while (strcmp(msg, msgquit) && o_baz->possible[2] == 2); /*Déconnexion si nécéssaire*/ o_baz->Deconnecter(); Etudions maintenant des Classes utilisant CClient et CServeur III. Programmes et Protocoles : _______________________________ Nous étudierons une méthode de CApplications, et une de classe de protocole ( je n'ai implémenté que le protocole POP3 pour l'instant ). Chaque protocole a une classe qui lui est dédiée, contrairement aux applications qui n'ont qu'une fonction membre. Etudions la fonction membre Nuker de la classe CApplications : void CApplications::Nuker(char *nomserveur, char *msg, unsigned short int port, unsigned long int nbrfois) { unsigned long int i = 0; long int j = 0; char *a = new char[256]; possible[0] = 0; o_baz = new CInformations(verbose, 1); o_baz->Serveur(nomserveur); if (o_baz->he == NULL) /*---- Début Opération élémentaire 1 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Nuker : Impossible d'obtenir des infos sur l'hôte à scanner\n"); } } else { possible[0]++; if (inet_ntop(PF_INET, (in_addr *)o_baz->he->h_addr_list[0], a, 256) == NULL) /*---- Début Opération élémentaire 2 ----*/ { /*if (inet_ntop(PF_INET6, (in6_addr *)o_baz->he->h_addr_list[0], a, 256) != NULL) { possible[0]++; o_baz = new CClient(a, port, 0, PF_INET6, SOCK_STREAM, 0, verbose, 1); }*/ } else { possible[0]++; o_baz = new CClient(a, port, 0, PF_INET, SOCK_STREAM, 0, verbose, 1); } /*---- Fin Opération élémentaire 2 ----*/ o_baz->Creer(); o_baz->Connecter(); if (o_baz->possible[1] != 5) /*---- Début Opération élémentaire 3 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Nuker : Impossible de se connecter\n"); } } else { possible[0]++; for (i=0; ipossible[2] == 2; i++) /*---- Début Opération élémentaire 4 ----*/ { o_baz->Envoyer(msg, strlen(msg)); if (o_baz->taillebuffcli > 0 && verbose) { AfficherHeure(); cout << "(Envoi n°" << i+1 << ")\n"; } else { if (verbose) fprintf(stderr, "erreur d'envoi\n"); } } o_baz->Deconnecter(); if (verbose) { AfficherHeure(); cout << " : Nuker : Nuke de la machine " << nomserveur << " réussi\n"; } possible[0]++; /*---- Fin Opération élémentaire 4 ----*/ } /*---- Fin Opération élémentaire 3 ----*/ } /*---- Fin Opération élémentaire 1 ----*/ delete[] a; } Il s'agit d'un bête nuker pas spécialement optimisé (pour celà il faut le faire en C), mais qui présente l'avantage d'une réelle simplicité. En effet on peut créer un nuker en 1 ligne de code en utilisant cette classe : CBazSocket *o_baz = new CApplications(true, 2); o_baz->Nuker(156.123.12.76, "hihihihihi", 139, 10000); Enverra 10000 fois le message "hihihihihi" à 156.123.12.76 sur son port 139. Facile non ? Maintenant étudions la mise en oeuvre du protocole POP3, que j'ai réalisé en lisant la RFC 1939. Voici la définition de la classe CPOP3 : class CPOP3 : public CBazSocket { public : CPOP3(bool verbose = true, short int affichage = 1); virtual ~CPOP3(); virtual void ClientPOP3_Connecter(char *nomserveur); /*possible[0] // 4 opérations élémentaires*/ virtual void ClientPOP3_UserPass(char *user, char *pass); /*possible[1] // 3 opérations élémentaires*/ //virtual void ClientPOP3_APOP(char *user, char *pass); /*possible[1] // x opérations élémentaires*/ virtual void ClientPOP3_Stats(); /*possible[2] // x opérations élémentaires*/ virtual void ClientPOP3_Lister(unsigned long int numero = 0); /*possible[2] // x opérations élémentaires*/ /*Mettez 0 comme argument (ou bien aucun argument) pour tout lister*/ virtual void ClientPOP3_VoirEnTete(unsigned short int numero = 1, unsigned long int nbrlignes = 0); /*possible[2] // x opérations élémentaires*/ virtual void ClientPOP3_AfficherMail(unsigned short int numero = 1, long int taillemin = 10, long int taillemax = 10); /*possible[2] // x opérations élémentaires*/ virtual void ClientPOP3_EffacerMail(unsigned short int numero = 0); /*possible[2] // x opérations élémentaires*/ virtual void ClientPOP3_RestaurerTous(); /*possible[2] // x opérations élémentaires*/ virtual void ClientPOP3_Quitter(); /*possible[2] // x opérations élémentaires*/ virtual void ServeurPOP3(); private : CBazSocket *o_baz; bool ClientPOP3_Recevoir(long int taillemin, long int taillemax); struct listage { short int nbrmails; short int *taillesmails; }; }; Vous remarquerez le membre private *o_baz, qui est un pointeur vers un objet de la classe de base, afin d'utiliser CClient. Nous allons nous intéresser au client POP3, qui doit se connecter à un serveur POP3. Pour tester mon client POP3 sans être constamment sur le Net, j'ai aussi créé le serveur POP3, qui est donc un environnement de test pour le client, mais qui ne nous intéressera pas ici. A noter que comme pour les Réseau de Neurones, l'environnement de test est aussi dur à réaliser que le Réseau de Neurones en lui-même (bon c'était pas tout à fait le cas ici malgré tout ;) Etudions la première fonction, Connecter() : void CPOP3::ClientPOP3_Connecter(char *nomserveur) { buffpop3 = new char[256]; possible[0] = 0; o_baz = new CInformations(verbose); o_baz->Serveur(nomserveur); if (o_baz->he == NULL) /*---- Début Opération élémentaire 1 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3 : Impossible d'obtenir des infos sur le serveur POP3\n"); } } else { possible[0]++; if (inet_ntop(PF_INET, (in_addr *)o_baz->he->h_addr_list[0], buffpop3, 256) == NULL) /*---- Début Opération élémentaire 2 ----*/ { /*if (inet_ntop(PF_INET6, (in6_addr *)o_baz->he->h_addr_list[0], buffpop3, 256) != NULL) { possible[0]++; o_baz = new CClient(a, 110, 0, PF_INET6, SOCK_STREAM, 0, false, 2); }*/ } else { possible[0]++; o_baz = new CClient(buffpop3, 110, 0, PF_INET, SOCK_STREAM, 0, verbose, 2); } /*---- Fin Opération élémentaire 2 ----*/ o_baz->Creer(); o_baz->Options(SOL_SOCKET, SO_RCVBUF, 102400); o_baz->Connecter(); if (o_baz->possible[1] != 5) /*---- Début Opération élémentaire 3 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3 : Impossible de se connecter sur %s\n", nomserveur); } } else { possible[0]++; if (verbose) { AfficherHeure(); cout << " : Client POP3 : Connexion réussie sur " << nomserveur << endl; } if (! ClientPOP3_Recevoir(0, 256)) /*---- Début Opération élémentaire 4 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3 : Erreur\n\t"); } } else { possible[0]++; if (verbose) { AfficherHeure(); cout << " : Client POP3 : +OK\n\t"; } } /*---- Fin Opération élémentaire 4 ----*/ } /*---- Fin Opération élémentaire 3 ----*/ } /*---- Fin Opération élémentaire 1 ----*/ } On recherche donc d'abord le serveur grâce à la classe CInformations, et plus précisément sa fonction membre Serveur() (informations sur un serveur). Ensuite on réutilise le pointeur de la classe de base pour stocker un objet de type CClient. Puis ce client se connecte tout simplement au serveur POP3 ! Voici maintenant la méthode permettant de s'identifier : void CPOP3::ClientPOP3_UserPass(char *user, char *pass) { buffpop3 = new char[256]; short int afftmp = o_baz->affichage; possible[1] = 0; if (possible[0] != 4) /*---- Début Opération élémentaire 1 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3: Impossible de s'identifier, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n"); } } else { possible[1]++; if (verbose) { printf("\n"); AfficherHeure(); cout << " : Soumission du nom d'utilisateur " << user << endl; } sprintf(buffpop3, "USER %s\r\n", user); o_baz->Envoyer(buffpop3, strlen(buffpop3)); if (! ClientPOP3_Recevoir(0, 256)) /*---- Début Opération élémentaire 2 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3 : L'user %s est inconnu\n\t", user); } } else { possible[1]++; if (verbose) { AfficherHeure(); cout << " : Client POP3 : User OK\n\t"; } if (verbose) { printf("\n"); AfficherHeure(); cout << " : Soumission du mot de passe " << endl; } sprintf(buffpop3, "PASS %s\r\n", pass); o_baz->affichage = 0; /*Ne pas afficher le pass envoyer*/ o_baz->Envoyer(buffpop3, strlen(buffpop3)); o_baz->affichage = afftmp; if (! ClientPOP3_Recevoir(0, 256)) /*---- Début Opération élémentaire 3 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3 : Le password est mauvais\n\t"); } } else { possible[1]++; if (verbose) { AfficherHeure(); cout << " : Client POP3 : Password OK\n\t"; } CPOP3::ClientPOP3_Lister(); } /*---- Fin Opération élémentaire 3 ----*/ } /*---- Fin Opération élémentaire 2 ----*/ } /*---- Fin Opération élémentaire 1 ----*/ memset(pass, 0, strlen(pass)+1); /*Effacement total du mot de passe*/ memset(buffpop3, 0, strlen(buffpop3)+1); /*Effacement total du mot de passe*/ } La RFC indique que le client doit envoyer la commande USER nomutilisateur\r\n puis PASS password\r\n, ce que fait donc cette méthode en utilisant ses arguments. void CPOP3::ClientPOP3_Stats() { buffpop3 = new char[256]; possible[2] = 0; if (possible[0] != 4 || possible[1] != 3) /*---- Début Opération élémentaire 1 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3: Impossible d'afficher les statistiques de votre compte, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n\t* Le nom d'utilisateur est érroné\n\t* Le mot de passe est érroné\n"); } } else { possible[2]++; if (verbose) { printf("\n"); AfficherHeure(); cout << " : Soumission de la requête de statistiques du compte\n"; } sprintf(buffpop3, "STAT\r\n"); o_baz->Envoyer(buffpop3, strlen(buffpop3)); if (! ClientPOP3_Recevoir(0, 10240)) /*---- Début Opération élémentaire 2 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3 : Impossible de reçevoir les statistiques du compte\n\t"); } } else { possible[2]++; if (verbose) { AfficherHeure(); cout << " : Client POP3 : Statistiques reçues\n\t"; } if (verbose) { printf("\n"); AfficherHeure(); cout << " : Soumission de la requête de statistiques des messages\n"; } sprintf(buffpop3, "UIDL\r\n"); o_baz->Envoyer(buffpop3, strlen(buffpop3)); if (! ClientPOP3_Recevoir(0, 10240)) /*---- Début Opération élémentaire 3 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3 : Impossible de reçevoir les staistiques des messages\n\t"); } } else { possible[2]++; if (verbose) { AfficherHeure(); cout << " : Client POP3 : Statistiques reçues\n\t"; } } /*---- Fin Opération élémentaire 3 ----*/ } /*---- Fin Opération élémentaire 2 ----*/ } /*---- Fin Opération élémentaire 1 ----*/ } Les commandes STAT\r\n et UIDL\r\n font partie de la RFC, et permettent d'obtenir des informations à propos des messages ou du compte. Maintenant listons nos mails, avec la commande LIST de la RFC : void CPOP3::ClientPOP3_Lister(unsigned long int numero) { buffpop3 = new char[256]; possible[2] = 0; if (possible[0] != 4 || possible[1] != 3) /*---- Début Opération élémentaire 1 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3: Impossible de lister vos mails, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n\t* Le nom d'utilisateur est érroné\n\t* Le mot de passe est érroné\n"); } } else { possible[2]++; if (verbose) { printf("\n"); AfficherHeure(); cout << " : Soumission de la requête de listage\n"; } if (numero > 0) sprintf(buffpop3, "LIST %d\r\n", numero); else sprintf(buffpop3, "LIST\r\n"); o_baz->Envoyer(buffpop3, strlen(buffpop3)); if (! ClientPOP3_Recevoir(0, 10240)) /*---- Début Opération élémentaire 2 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3 : Impossible de lister les mails\n\t"); } } else { possible[2]++; if (verbose) { AfficherHeure(); cout << " : Client POP3 : Listage OK\n\t"; } } /*---- Fin Opération élémentaire 2 ----*/ } /*---- Fin Opération élémentaire 1 ----*/ ; } Puis voyons l'en-tête d'un mail donné avec la commande optionnelle de la RFC, mais en générale implémentée dans les serveurs, à savoir TOP : void CPOP3::ClientPOP3_VoirEnTete(unsigned short int numero, unsigned long int nbrlignes) { buffpop3 = new char[256]; possible[2] = 0; if (possible[0] != 4 || possible[1] != 3) /*---- Début Opération élémentaire 1 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3: Impossible d'afficher l'en-tête du mail n° %d, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n\t* Le nom d'utilisateur est érroné\n\t* Le mot de passe est érroné\n", numero); } } else { possible[2]++; if (verbose) { printf("\n"); AfficherHeure(); cout << " : Soumission de la requête pour voir l'en-tête du mail " << numero << "\n"; } sprintf(buffpop3, "TOP %d %d\r\n", numero, nbrlignes); o_baz->Envoyer(buffpop3, strlen(buffpop3)); if (! ClientPOP3_Recevoir(0, 102400)) /*---- Début Opération élémentaire 2 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3 : Impossible de voir l'en-tête\n\t"); } } else { possible[2]++; if (verbose) { AfficherHeure(); cout << " : Client POP3 : En-tête du mail n° " << numero << " reçue\n\t"; } } /*---- Fin Opération élémentaire 2 ----*/ } /*---- Fin Opération élémentaire 1 ----*/ ; } Et la commande la plus importante, AfficherMail, grâce à RETR. Analysez, vous verrez que c'est facile ! void CPOP3::ClientPOP3_AfficherMail(unsigned short int numero, long int taillemin, long int taillemax) { buffpop3 = new char[256]; possible[2] = 0; if (possible[0] != 4 || possible[1] != 3 || taillemax < 4) /*---- Début Opération élémentaire 1 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3: Impossible d'afficher le mail n° %d, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n\t* Le nom d'utilisateur est érroné\n\t* Le mot de passe est érroné\n\t* Moins de 4 octets seront reçus !", numero); } } else { possible[2]++; if (verbose) { printf("\n"); AfficherHeure(); cout << " : Soumission de la requête pour afficher le mail " << numero << "\n"; } sprintf(buffpop3, "RETR %d 0\r\n", numero); o_baz->Envoyer(buffpop3, strlen(buffpop3)); if (! ClientPOP3_Recevoir(taillemin, taillemax)) /*---- Début Opération élémentaire 2 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3 : Impossible d'afficher ce mail\n\t"); } } else { possible[2]++; if (verbose) { AfficherHeure(); cout << " : Client POP3 : Mail n° " << numero << " reçu\n\t"; } } /*---- Fin Opération élémentaire 2 ----*/ } /*---- Fin Opération élémentaire 1 ----*/ } La fonction qui va suivre permet d'effacer un mail de la méoire, puis du disque du serveur si vous le quittez proprement (commande DELE) : void CPOP3::ClientPOP3_EffacerMail(unsigned short int numero) { buffpop3 = new char[256]; possible[2] = 0; if (possible[0] != 4 || possible[1] != 3) /*---- Début Opération élémentaire 1 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3: Impossible d'effacer vos mails, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n\t* Le nom d'utilisateur est érroné\n\t* Le mot de passe est érroné\n"); } } else { possible[2]++; if (verbose) { printf("\n"); AfficherHeure(); cout << " : Soumission de la requête d'effacement du mail " << numero << endl; } sprintf(buffpop3, "DELE %d\r\n", numero); o_baz->Envoyer(buffpop3, strlen(buffpop3)); if (! ClientPOP3_Recevoir(0, 10240)) /*---- Début Opération élémentaire 2 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3 : Impossible d'effacer le mail n°%d\n\t", numero); } } else { possible[2]++; if (verbose) { AfficherHeure(); cout << " : Client POP3 : Le mail n°" << numero << " sera bien effacé lorsque vous quitterez proprement le serveur\n\t"; } } /*---- Fin Opération élémentaire 2 ----*/ } /*---- Fin Opération élémentaire 1 ----*/ } Cette fonction RestaurerTous annule tous les appels à la précédente (commande RSET) : void CPOP3::ClientPOP3_RestaurerTous() { buffpop3 = new char[256]; possible[2] = 0; if (possible[0] != 4 || possible[1] != 3) /*---- Début Opération élémentaire 1 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3: Impossible de restaurer vos mails, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n\t* Le nom d'utilisateur est érroné\n\t* Le mot de passe est érroné\n"); } } else { possible[2]++; if (verbose) { printf("\n"); AfficherHeure(); cout << " : Soumission de la requête de récupération de tous les mails\n"; } sprintf(buffpop3, "RSET\r\n"); o_baz->Envoyer(buffpop3, strlen(buffpop3)); if (! ClientPOP3_Recevoir(0, 256)) /*---- Début Opération élémentaire 2 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3 : Impossible de restaurer vos mails\n\t"); } } else { possible[2]++; if (verbose) { AfficherHeure(); cout << " : Client POP3 : Tous vos mails ont été restaurés et ne seront pas effacés lorsque vous quitterez\n\t"; } } /*---- Fin Opération élémentaire 2 ----*/ } /*---- Fin Opération élémentaire 1 ----*/ } Et enfinl acommande QUIT\r\n qui permet de quitter proprement un serveur POP3 : void CPOP3::ClientPOP3_Quitter() { buffpop3 = new char[256]; possible[2] = 0; if (possible[0] != 4 || possible[1] != 3) /*---- Début Opération élémentaire 1 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3: Impossible de quitter votre compte, raisons possibles\n\t* Vous n'êtes pas connecté au serveur POP3\n\t* Le nom d'utilisateur est érroné\n\t* Le mot de passe est érroné\n"); } } else { possible[2]++; if (verbose) { printf("\n"); AfficherHeure(); cout << " : Soumission de la requête pour quitter\n"; } sprintf(buffpop3, "QUIT\r\n"); o_baz->Envoyer(buffpop3, strlen(buffpop3)); if (! ClientPOP3_Recevoir(0, 256)) /*---- Début Opération élémentaire 2 ----*/ { if (verbose) { AfficherHeure(); fprintf(stderr, " : Client POP3 : Impossible de quitter proprement le serveur\n\t"); } } else { possible[2]++; if (verbose) { AfficherHeure(); cout << " : Client POP3 : Serveur quitté proprement\n\t"; } } /*---- Fin Opération élémentaire 2 ----*/ o_baz->Deconnecter(); } /*---- Fin Opération élémentaire 1 ----*/ } Et voici la fonction private utilisée en tant que surcouche de CClient::Recevoir() : bool CPOP3::ClientPOP3_Recevoir(long int taillemin, long int taillemax) { do { o_baz->Recevoir(taillemin, taillemax); if (o_baz->taillebuffcli > 0) { if (! strncmp(o_baz->buffcli, "+OK", 3)) return true; if (! strncmp(o_baz->buffcli, "-ERR", 4)) return false; } else return false; } while (1); } /*---------------------------------------------------------------------------*/ Cette fonction tient compte du résultat du serveur, qui est soit +OK xxxxx, soit -ERR xxxxxxx Je vous conseille, pour mieux comprendre, de lire la RFC 1939, qui peut se trouver sur "http://www.armware.dk/RFC" Conclusion : ____________ Voilà j'ai terminé mon petit tutorial, qui était en fait un survol de mes classes pour vous montrer ma méthodologie de travail, dont vous pourrez vous inspirer pour faire vos propres classes. Vous pouvez attendre la sortie officielle de CAPRU, ou me la demander par mail (en version de développement) si ce cours vous a vraiment intéressé. Je remercie les membres de IOC pour avoir publié ces lignes :)