From 9e9f09d796e8b35d275e3861865b110adee0b04a Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Thu, 17 Dec 2015 00:19:34 +0100 Subject: [PATCH] MULTITHREAD OK --- dep/server.c | 112 ++++++++++++++++++++++++++------------------------ dep/server.h | 72 ++++++++++++++++++++++++-------- dep/utility.c | 12 +++--- proxy_ftp.c | 77 +++++++++++++++++++++++++++------- proxy_ftp.h | 16 ++++---- test | Bin 22778 -> 23034 bytes 6 files changed, 191 insertions(+), 98 deletions(-) diff --git a/dep/server.c b/dep/server.c index ce3f000..7ccc6eb 100644 --- a/dep/server.c +++ b/dep/server.c @@ -6,7 +6,7 @@ -void DROP_SERVER(char* serverHost, char** givenPort, int* listenSocket){ +void DROP_SERVER(const char* serverHost, char** givenPort, int* listenSocket){ if( DEBUGMOD&HDR ) printf("====== DROP_SERVER(%s, %s, %d) ======\n\n", serverHost, *givenPort, *listenSocket); // FONCTIONNEMENT @@ -124,17 +124,17 @@ void DROP_SERVER(char* serverHost, char** givenPort, int* listenSocket){ -// CMD: CLIENT (SRV) PROXY (CLT) FTP -// DTA: CLIENT (CLT) PROXY (CLT) FTP -// + + + void MANAGE_REQUEST(char* pRequest, int* USER_SOCKET, int* FTP_SOCKET, int* DUSER_SOCKET, int* DFTP_SOCKET){ if( DEBUGMOD&HDR ) printf("====== MANAGE_REQUEST(%s, %d, %d, %d, %d) ======\n\n", pRequest, *USER_SOCKET, *FTP_SOCKET, *DUSER_SOCKET, *DFTP_SOCKET); - char response[maxBuffLen]; // contiendra la réponse - int nbSend; // contiendra le nombre de données envoyées - char rCommand[5]; // contiendra les commandes (1ère partie) - char rContent[maxBuffLen]; // contiendra le contenu associé à la commande (2ème partie) + char response[maxBuffLen]; // contiendra la réponse (2*taille buffer pour strcat(BUFLEN, BUFLEN)) + int nbSend; // contiendra le nombre de données envoyées + char rCommand[5]; // contiendra les commandes (1ère partie) + char rContent[maxBuffLen]; // contiendra le contenu associé à la commande (2ème partie) // on vide les buffers memset(&rCommand, '\0', sizeof(rCommand)); @@ -153,40 +153,40 @@ void MANAGE_REQUEST(char* pRequest, int* USER_SOCKET, int* FTP_SOCKET, int* DUSE /* (1) USER username@serveur => connection FTP (commande) --------------------------------------------*/ if( strcmp(rCommand, "USER") == 0 && indexOf(rContent, '@') >= 0 ){ - char userName[maxBuffLen] = {0}; + char userName[100] = {0}; char hostName[maxHostLen] = {0}; + // pour l'envoi de la séquence d'initialisation + char* ftp_response; ftp_response = malloc(maxBuffLen); + memset(ftp_response, 0, maxBuffLen); + /* 1. On extrait @username et @hostname */ sscanf(rContent, "%[^@]@%s\r\n", userName, hostName); - if( DEBUGMOD&BUF ) printf("USERNAME [[%s]]\n", userName); if( DEBUGMOD&BUF ) printf("HOSTNAME [[%s]]\n", hostName); - /* On se connecte au serveur FTP (commandes) */ + + /* 2. On se connecte au serveur FTP (commandes) */ CONNECT_CLIENT(hostName, FTP_PORT, FTP_SOCKET); if( *FTP_SOCKET == -1 ) printf("CAN'T CONNECT TO GIVEN SERVER!!\n"); if( DEBUGMOD&SCK ) printf("FTP SOCKET CONNECTED AT %s:%s\n", hostName, FTP_PORT); - /* On envoie la séquence d'initialisation */ - char BUFFER[maxBuffLen] = {0}; - char* ftp_response; ftp_response = malloc(maxBuffLen); - memset(ftp_response, 0, maxBuffLen); - strcpy(BUFFER, "\r\n"); - CLIENT_SEND(FTP_SOCKET, BUFFER, &ftp_response); + /* 3. On envoie la séquence d'initialisation */ + CLIENT_SEND(FTP_SOCKET, "\r\n", &ftp_response); - if( DEBUGMOD&CMD ) xPrint("P->F: %s\n\n", BUFFER); + if( DEBUGMOD&CMD ) xPrint("P->F: %s\n\n", "\r\n"); if( DEBUGMOD&CMD ) xPrint("F->P: %s\n", ftp_response); - - // char BUFF[maxBuffLen] = {0}; - // strcpy(BUFF, ""); - // swrite(FTP_SOCKET, BUFF); - // sread(FTP_SOCKET, BUFF); - // swrite(USER_SOCKET, strcat(BUFF, "\r\n")); + + /* 4. On envoie la requête USER au serveur FTP auquel on vient de se connecter */ + char newRequest[maxBuffLen]; + strcat(newRequest, "USER "); + strcat(newRequest, userName); + strcat(newRequest, "\r\n"); + printf("NEW REQ: [[%s]]\n", newRequest); strcpy(response, "USER xdrm\r\n"); - printf("RESP: [[%s]]\n", response); } /* (2) PORT a1,a2,a3,a4,p1,p2 => utilisateur (actif) @@ -199,6 +199,7 @@ void MANAGE_REQUEST(char* pRequest, int* USER_SOCKET, int* FTP_SOCKET, int* DUSE char serverPort[maxPortLen] = {0}; char BUFFER[maxBuffLen] = {0}; + /* 1. On récupère l'ip et le port de la réponse */ sscanf(pRequest, "PORT %d,%d,%d,%d,%d,%d", &a1, &a2, &a3, &a4, &p1, &p2); // on récupère l'adresse en sprintf(serverHost, "%d.%d.%d.%d", a1, a2, a3, a4); @@ -206,12 +207,12 @@ void MANAGE_REQUEST(char* pRequest, int* USER_SOCKET, int* FTP_SOCKET, int* DUSE sprintf(serverPort, "%d", p1*256+p2); - // on se connecte au client + /* 2. On se connecte au client */ if( DEBUGMOD&SCK ) printf("CONNECTING TO CLIENT %s:%s\n", serverHost, serverPort); CONNECT_CLIENT(serverHost, serverPort, DUSER_SOCKET); if( DEBUGMOD&SCK ) printf("CONNECTED TO CLIENT %s:%s\n", serverHost, serverPort); - // on envoie PASV car on veut être en mode passif entre le proxy et le serveur FTP + /* 3. Envoi de "PASV" car on veut être en mode passif entre le proxy et le serveur FTP */ strcpy(response, "PASV\r\n"); } @@ -251,44 +252,48 @@ void MANAGE_RESPONSE(char* pAnswer, int* USER_SOCKET, int* FTP_SOCKET, int* DUSE /* [2] Selection en fonction de @ftpCode ================================================*/ - - /* (2) Demande d'username + switch(ftpCode){ + /* (1) Demande d'username --------------------------------------------*/ if( ftpCode == 220 ) strcpy(response, WLCM_MSG); - /* (3) username OK -> demande MDP + /* (2) username OK -> demande MDP --------------------------------------------*/ - else if( ftpCode == 331 ) + case 331: strcpy(response, USER_MSG); + break; - /* (4) Bon mdp -> connection + /* (3) Bon mdp -> connection --------------------------------------------*/ - else if( ftpCode == 230 ) + case 230: strcpy(response, PASS_BON_MSG); - + break; - - /* (5) Mauvais mdp -> connection + /* (4) Mauvais mdp -> message erreur --------------------------------------------*/ - else if( ftpCode == 530 ) + case 530: strcpy(response, PASS_BAD_MSG); + break; - /* (6) Info SYST + /* (5) Info SYST --------------------------------------------*/ - // else if( ftpCode == 215 ) + // case 215: // strcpy(response, "bla\n"); - - /* (7) LOGOUT => EXIT + // break; + + + /* (6) LOGOUT => EXIT --------------------------------------------*/ - else if( ftpCode == 221 ) + case 221: strcpy(response, EXIT_MSG); + break; - /* (8) Mode passif => On lance les SOCKETS du BUS DE DONNEES + /* (7) Mode passif => On lance les SOCKETS du BUS DE DONNEES --------------------------------------------*/ - else if( ftpCode == 227 ){ // on lance la SOCKET FTP du BUS DE DONNEES + case 227: // on lance la SOCKET FTP du BUS DE DONNEES int a1, a2, a3, a4 = 0; int p1, p2 = 0; @@ -306,12 +311,12 @@ void MANAGE_RESPONSE(char* pAnswer, int* USER_SOCKET, int* FTP_SOCKET, int* DUSE if( DEBUGMOD&SCK ) printf("CONNECTED TO FTP %s:%s\n", serverHost, serverPort); strcpy(response, LIST_DAT_MSG); - } + break; - /* (9) On lit la SOCKET FTP du BUS DE DONNEES + /* (8) On lit la SOCKET FTP du BUS DE DONNEES --------------------------------------------*/ - else if( ftpCode == 150 ){ // on lit la SOCKET FTP du BUS DE DONNEES + case 150: char BUFFER[maxBuffLen] = {0}; @@ -355,23 +360,24 @@ void MANAGE_RESPONSE(char* pAnswer, int* USER_SOCKET, int* FTP_SOCKET, int* DUSE // fin de la transaction strcpy(response, STOP_DAT_MSG); - } + break; - /* (10) Fin de transfert de données + /* (9) Fin de transfert de données (fermeture SOCKETS du BUS DE DONNEES) --------------------------------------------*/ - else if( ftpCode == 226 ){ // on ferme les SOCKETS du BUS DE DONNEES + case 226: // strcpy(response, "226 Fin de la transaction!\r\n"); strcpy(response, "\r\n"); close(*DUSER_SOCKET); close(*DFTP_SOCKET); - } + break; /* (n) Commande inconnue --------------------------------------------*/ - else + default: strcpy(response, pAnswer); - + break; + } /* [3] Retour de la réponse ================================================*/ diff --git a/dep/server.h b/dep/server.h index 980dbf4..8050bda 100644 --- a/dep/server.h +++ b/dep/server.h @@ -1,27 +1,58 @@ /* Créé et met un serveur sur écoute * +* ==IN== * @serverHost Nom de l'hôte local (localhost) -* -* @givenPort Pointeur sur le à remplir => contiendra le port donné par le système +* @givenPort Si renseigné, définit le port à utiliser +* +* ==OUT== +* @givenPort Pointeur sur le à remplir => contiendra le port donné par le système (si pas renseigné en entrée) * @listenSocket Pointeur sur le à remplir => contiendra un pointeur sur la socket d'écoute +* * +* +* @history +* [1] On définit le filtre/format +* [2] On récupère les infos (adresse, port) +* Note: Gestion IPv4/IPv6 +* [3] Création de la socket +* [4] On publie la SOCKET (bind) +* [5] On récupère les informations du serveur (host/port) +* [6] On met la socket sur écoute (listen) +* [7] On envoie les données par référence +* */ -void DROP_SERVER(char* serverHost, char** givenPort, int* listenSocket); +void DROP_SERVER(const char* serverHost, char** givenPort, int* listenSocket); /* Gestion de la requête du client -* +* ==IN/OUT== * @pBuffer Requête en question * @USER_SOCKET Pointeur sur la SOCKET du BUS DE COMMANDES utilisateur * @FTP_SOCKET Pointeur sur la SOCKET du BUS DE COMMANDES FTP * @DUSER_SOCKET Pointeur sur la SOCKET du BUS DE DONNEES utilisateur * @DFTP_SOCKET Pointeur sur la SOCKET du BUS DE DONNEES FTP * +* +* * @history -* [1] si "PORT a1,a2,a3,a4,p1,p2" on démarre la SOCKET CLIENT du BUS DE DONNEES -* [2] -* [3] +* [1] On découpe la requête en 2 parties (rCommand, rContent) +* [2] Selection en fonction de @rCommand +* +* (1) USER username@serveur => connection FTP (commande) +* 1. On extrait @username et @hostname +* 2. On se connecte au serveur FTP (commandes) +* 3. On envoie la séquence d'initialisation +* 4. On envoie la requête USER au serveur FTP auquel on vient de se connecter +* +* (2) PORT a1,a2,a3,a4,p1,p2 => utilisateur (actif) +* 1. On récupère l'ip et le port de la réponse +* 2. On se connecte au client +* 3. Envoi de "PASV" car on veut être en mode passif entre le proxy et le serveur FTP +* (n) Si aucun traitement on recopie la requête telquel +* +* [3] Retour de la réponse +* */ void MANAGE_REQUEST(char* pRequest, int* USER_SOCKET, int* FTP_SOCKET, int* DUSER_SOCKET, int* DFTP_SOCKET); @@ -35,17 +66,24 @@ void MANAGE_REQUEST(char* pRequest, int* USER_SOCKET, int* FTP_SOCKET, int* DUSE * @DUSER_SOCKET Pointeur sur la SOCKET du BUS DE DONNEES utilisateur * @DFTP_SOCKET Pointeur sur la SOCKET du BUS DE DONNEES FTP * -* @history -* [1] SI commande sans transfert nécessaire, on modifie la réponse - -* [2] SI on a besoin d'un transfert de données -* (1) On initialise les SOCKETS avec la valeur de PORT a,b,c,d,p1,p2 -* (2) a.b.c.d (adresse ip) + p1*256+p2 (port) -* (3) UTILISATEUR (ACIF) PROXY (PASSIF) SRV_FTP -* -* [3] SI 227, on lance la SOCKET FTP du BUS DE DONNEES (mode passif) -* [4] SI 226, on ferme les SOCKETS du BUS DE DONNEES * +* +* @history +* [1] On découpe la requête en 2 parties (ftpCode, ftpText) +* [2] Selection en fonction de @ftpCode +* +* (1) 220- Demande d'username +* (2) 331- username OK -> demande MDP +* (3) 230- Bon mdp -> connection +* (4) 530- Mauvais mdp -> message erreur +* (5) 215- Info SYST [COMMENTÉ] +* (6) 221- LOGOUT => EXIT (commande QUIT) +* (7) 227- Mode passif => On lance les SOCKETS du BUS DE DONNEES +* (8) 150- On lit la SOCKET FTP du BUS DE DONNEES +* (9) 226- Fin de transfert de données (fermeture SOCKETS du BUS DE DONNEES) +* (n) xxx- Commande inconnue +* +* [3] Retour de la réponse */ void MANAGE_RESPONSE(char* pAnswer, int* USER_SOCKET, int* FTP_SOCKET, int* DUSER_SOCKET, int* DFTP_SOCKET); diff --git a/dep/utility.c b/dep/utility.c index fa6c205..9e8ddfe 100644 --- a/dep/utility.c +++ b/dep/utility.c @@ -19,9 +19,9 @@ void splitFtpRequest(char* pRequest, char* pCommand, char* pContent){ for( i = 0 ; i < strlen(pRequest) && pRequest[i] != '\0' ; i++ ){ if( i < firstSpaceIndex ) // première partie (pCommand) - strcpy( pCommand, strcat(pCommand, (char[2]) { (char) pRequest[i], '\0' }) ); + strcat( pCommand, (char[2]){(char) pRequest[i], '\0'}); if( i > firstSpaceIndex ) // seconde partie (pContent) - strcpy( pContent, strcat(pContent, (char[2]) { (char) pRequest[i], '\0' }) ); + strcat( pContent, (char[2]){(char) pRequest[i], '\0'}); } @@ -42,9 +42,9 @@ void splitFtpResponse(char* pAnswer, char* ftpCode, char* ftpText){ for( i = 0 ; i < strlen(pAnswer) && pAnswer[i] != '\0' ; i++ ){ if( i < codeLength ) // première partie (ftpCode) - strcpy( ftpCode, strcat(ftpCode, (char[2]) { (char) pAnswer[i], '\0' }) ); + strcat(ftpCode, (char[2]){(char)pAnswer[i], '\0' }); if( i > codeLength ) // seconde partie (ftpText) - strcpy( ftpText, strcat(ftpText, (char[2]) { (char) pAnswer[i], '\0' }) ); + strcat(ftpText, (char[2]){(char)pAnswer[i], '\0' }); } @@ -86,7 +86,7 @@ void formatBuffer(char* pBuffer){ revealString(pBuffer); /* [2] On ajoute "\r\n" à la fin ============================================================*/ - strcpy(pBuffer, strcat(pBuffer, "\r\n")); + strcat(pBuffer, "\r\n\0"); if( DEBUGMOD&BUF ) printf( "BUFLEN (aft): %lu\n", strlen(pBuffer) ); } @@ -102,7 +102,7 @@ void read_stdin(char* pBuffer, unsigned long pLength){ while( pBuffer[strlen(pBuffer)-1] == '\r' || pBuffer[strlen(pBuffer)-1] == '\n' ) pBuffer[strlen(pBuffer)-1] = '\0'; - strcpy(pBuffer, strcat(pBuffer, "\r\n\0")); + strcat(pBuffer, "\r\n\0"); } diff --git a/proxy_ftp.c b/proxy_ftp.c index 0f79349..f20f3c9 100644 --- a/proxy_ftp.c +++ b/proxy_ftp.c @@ -2,10 +2,11 @@ // DECLARATIONS static pthread_t managers[maxListLen]; +static int activeManagers = 0x00; /* headers */ static void* testServer(); - +void* manageConnection(void* THREADABLE_SOCKET); @@ -40,21 +41,14 @@ int main(int argc, char* argv[]){ static void* testServer(char* localPort){ - int USER_SOCKET = -1; // contiendra le BUS DE COMMANDE utilisateur - int FTP_SOCKET = -1; // contiendra le BUS DE COMMANDE FTP - int DUSER_SOCKET = -1; // contiendra le BUS DE DONNES utilisateur - int DFTP_SOCKET = -1; // contiendra le BUS DE DONNEES FTP - - char BUFFER[maxBuffLen]; // contiendra le BUFFER + int THREADABLE_SOCKET = -1; // contiendra le BUS DE COMMANDE utilisateur à envoyer à un THREAD struct sockaddr_storage clientInfo; // contiendra les infos client char repeat; // sert à sortir de la boucle - int nbReceived, nbSend; // contiendra les nb reçu && envoyé socklen_t len = sizeof(struct sockaddr_storage); // retour de @DROP_SERVER char* serverPort; // contiendra le port int LISTENSOCK; // contiendra la socket d'écoute - char* ftp_response; /* [0] On démarre le SERVEUR + le CLIENT ==========================================================*/ @@ -71,11 +65,64 @@ static void* testServer(char* localPort){ /* [1] Attente d'une demande de connection, puis création d'une socket ============================================================================*/ - USER_SOCKET = accept(LISTENSOCK, (struct sockaddr *) &clientInfo, &len); + int index = 0; + while( index <= maxListLen ){ + THREADABLE_SOCKET = -1; // on initialise la SOCKET en attendant la connexion + THREADABLE_SOCKET = accept(LISTENSOCK, (struct sockaddr *) &clientInfo, &len); -// static void* manageConnection(int* USER_SOCKET){ + // on lance un thread pour le traitement + pthread_create(&managers[index], NULL, manageConnection, (void*)(intptr_t) THREADABLE_SOCKET); + index++; + } + + int i; + for( i = 0 ; i < maxListLen ; i++ ) + pthread_join(managers[i], NULL); + printf("FERMETURE DE TOUTES LES CONNECTIONS!\n"); + close(LISTENSOCK); +} + + + + + + + + + + + +/* Gestion d'une connexion client +* +* @THREADABLE_SOCKET SOCKET de la connexion client +* +* @history +* [1] Initialisation des variables +* [2] Envoi de la séquence de bienvenue +* [3] Attente des données reçues et reception (CLIENT_FTP->PROXY) +* [4] Traitement de la requête client +* [5] Redirection de la requête vers le serveur FTP (PROXY->SERVER_FTP) +* [6] Traitement de la reponse du serveur FTP +* [7] Redirection de la réponse vers le client (PROXY->CLIENT_FTP) +* +* +*/ +void* manageConnection(void* THREADABLE_SOCKET){ + int USER_SOCKET = (intptr_t) THREADABLE_SOCKET; + + /* [1] Variables + ============================================================================*/ + // int USER_SOCKET = -1; // contiendra le BUS DE COMMANDE utilisateur + int FTP_SOCKET = -1; // contiendra le BUS DE COMMANDE FTP + int DUSER_SOCKET = -1; // contiendra le BUS DE DONNES utilisateur + int DFTP_SOCKET = -1; // contiendra le BUS DE DONNEES FTP + + char BUFFER[maxBuffLen]; // contiendra le BUFFER + char* ftp_response; // contiendra le BUFFER de réponse du serveur FTP (commandes) + int nbReceived, nbSend; // contiendra les nb reçu && envoyé + /* [2] Envoi de la séquence de Bienvenue ============================================================================*/ swrite(&USER_SOCKET, WLCM_MSG); @@ -117,8 +164,8 @@ static void* testServer(char* localPort){ /* [8] On vide les buffers ============================================================================*/ - // memset(BUFFER, '\0', maxBuffLen); // on vide le buffer - // memset(ftp_response, '\0', maxBuffLen); + memset(BUFFER, '\0', maxBuffLen); // on vide le buffer + memset(ftp_response, '\0', maxBuffLen); } /* [9] Fermeture des connections (SOCKETS) @@ -126,7 +173,9 @@ static void* testServer(char* localPort){ printf("CLOSING CONNECTIONS\n"); close(USER_SOCKET); close(FTP_SOCKET); - close(LISTENSOCK); + + // on arrête le THREAD + pthread_exit(NULL); } diff --git a/proxy_ftp.h b/proxy_ftp.h index b76a557..0799275 100644 --- a/proxy_ftp.h +++ b/proxy_ftp.h @@ -1,6 +1,7 @@ /* global */ #include #include +#include #include #include @@ -15,12 +16,12 @@ /* debug */ -#define SOCKETS 0x01 // debug RESEAU -#define COMMAND 0x02 // debug COMMANDES -#define DATA 0x04 // debug DONNEES -#define REVEALS 0x08 // debug EXPLICITATION des strings -#define BUFFERS 0x10 // debug des BUFFERS -#define HEADERS 0x20 // debug des HEADERS de fonctions +#define SOCKETS 0x01 // debug RESEAU +#define COMMANDS 0x02 // debug COMMANDES +#define DATA 0x04 // debug DONNEES +#define REVEALS 0x08 // debug EXPLICITATION des strings +#define BUFFERS 0x10 // debug des BUFFERS +#define HEADERS 0x20 // debug des HEADERS de fonctions #define SCK 0x01 // FILTRE pour ONLY_SOCKET #define CMD 0x02 // FILTRE pour ONLY_COMMAND @@ -30,8 +31,7 @@ #define HDR 0x20 // FILTRE pour ONLY_HEADERS // possibilité de cumuler les DEBUGMODES -/* DEBUGMOD ONLY_COMMAND + ONLY_DATA + .. */ -#define DEBUGMOD COMMAND // REVEALS + HEADER +#define DEBUGMOD COMMANDS // REVEALS + HEADER /* vars */ diff --git a/test b/test index b8d2a49ee96311a7b12968fa2c3dd1045f3ae2f8..1a8eec9040d40477b929574a3d9c32619b349747 100755 GIT binary patch delta 8237 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 V(k5Zq;h>a0&W?~CI=L)U`hW9(bld;{ delta 7497 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 Zo>>KLGk>$sH*1cvZT9|sCua{;{tw|dhfDwf