From 28103022cd03411eaa5f5cfc27a5228a9ed0da35 Mon Sep 17 00:00:00 2001 From: anschrammh Date: Thu, 31 Oct 2019 21:39:04 +0100 Subject: [PATCH] Added checks to see if the client is logged in or not and other improvements in order to increase responsiveness --- src/app/FTPServer.h | 445 ++++++++++++++++++++++++++++++++------------ 1 file changed, 324 insertions(+), 121 deletions(-) diff --git a/src/app/FTPServer.h b/src/app/FTPServer.h index aba40b1..f4fcf8b 100644 --- a/src/app/FTPServer.h +++ b/src/app/FTPServer.h @@ -5,7 +5,7 @@ #include "SDCardManager.h" #include "definition.h" #include "Dictionary.h" -#define DEBUG_FTPS +//#define DEBUG_FTPS #define READ_BUFFER_SIZE 2500 //2500 is max to read sd card, more will crash template @@ -13,9 +13,10 @@ class FTPServer : public TCPServer { public: enum FTPClientState {INIT, WAITING_FOR_COMMANDS}; - enum FTPClientDataTransfer {NONE, LIST_DF, NLST_DF, RETR_DF, STOR_DF}; + enum FTPClientDataTransfer {NONE = 0, LIST_DF, NLST_DF, RETR_DF, STOR_DF, APPE_DF}; enum FileTransferStatus {OK, NOT_FOUND, NO_FILE_NAME}; enum BinaryFlag {OFF = 0, ON}; + enum FtpMsgCode {_150, _200, _215, _220, _221, _230, _226, _227, _250, _257, _331, _350, _451, _5_502, _504, _530, _550 }; FTPServer(unsigned int port = 21, SDCardManager *sdCardManager = NULL, const char *login = NULL, const char *password = NULL, uint8_t maxClient = MAX_CLIENT, uint16_t clientCommandDataBufferSize = 255) : TCPServer(port, maxClient, clientCommandDataBufferSize), _login(NULL), @@ -41,7 +42,6 @@ class FTPServer : public TCPServer strcpy(_password, password); } } - _dataServer.begin(_dataPort); } @@ -68,17 +68,20 @@ class FTPServer : public TCPServer client->_clientState = TCPClient::HANDLED; } - virtual void processClientData(T *client) + ICACHE_RAM_ATTR virtual void processClientData(T *client) { - if (client->_waitingForDataConnection) + /*if (client->_waitingForDataConnection) { //#ifdef DEBUG_FTPS //Serial.println("Listening for new data client"); //#endif WiFiClient dataClient = _dataServer.available(); - if (dataClient) + if(dataClient) { + #ifdef DEBUG_FTPS + Serial.println("Data client returns true"); + #endif if (dataClient.connected()) { client->_waitingForDataConnection = false; @@ -90,72 +93,68 @@ class FTPServer : public TCPServer else dataClient.stop(); } - } - + }*/ + switch(client->_dataTransferPending) { - case LIST_DF: //We list the files of the current directory - //We check if the dataConnection is established: - if (client->_dataClient.connected()) - { - client->_client.println("150 File status okay;"); - if(sendFSTree(client)) + case LIST_DF: + if (client->_dataClient.connected()) { - client->_client.println("226 Closing data connection."); + #ifdef DEBUG_FTPS + Serial.println("Listing started"); + #endif + + client->_client.println("150 File status okay."); + if(sendFSTree(client)) + { + client->_client.println("226 Closing data connection."); + } + else + { + client->_client.println("451 Requested action aborted."); + } + + client->closeDataConnection(); + client->_dataTransferPending = NONE; } - else - { - client->_client.println("451 Requested action aborted."); - } - - client->_dataClient.stop(); - client->_dataTransferPending = NONE; - } - //A timeout should be added - /*else - { - client->_client.println("425 Can't open data connection."); - }*/ break; case RETR_DF: if (client->_dataClient.connected()) { if(client->_fileSentBytes == 0) - client->_client.println("150 File status okay;"); - + client->_client.println("150 File status okay."); + FileTransferStatus fts; if(!sendFile(client,&fts))//File was sent or error occured { //we check the return code - switch(fts) + if(fts == OK) { - case OK: - client->_client.println("226 Closing data connection."); - client->_dataClient.stop(); - client->_dataTransferPending = NONE; - break; - case NOT_FOUND: - client->_client.println("451 File not found."); - client->_dataClient.stop(); - client->_dataTransferPending = NONE; - break; + client->_client.println("226 Closing data connection."); + client->closeDataConnection(); + client->_dataTransferPending = NONE; + } + else if(fts == NOT_FOUND) + { + client->_client.println("451 File not found."); + client->closeDataConnection(); + client->_dataTransferPending = NONE; } } } - //A timeout should be added - /*else + else if(client->_fileSentBytes != 0) + { + client->_client.println("426 Connection closed; transfer aborted."); + client->closeDataConnection(); + client->_dataTransferPending = NONE; + } + break; + case STOR_DF: + if (client->_dataClient.connected() || client->_dataClient.available())//Here we need to check if client has some data available for reading. IMPORTANT { - client->_client.println("425 Can't open data connection."); - }*/ - break; - case STOR_DF : - if (client->_dataClient.connected() || client->_dataClient.available()) - { - if(client->_dataClient.connected()) - client->_fileIsBeeingReceived = true; - if(client->_dataClient.available()) { + client->_fileIsBeeingReceived = true; #ifdef DEBUG_FTPS Serial.printf("receiving file %s\n", client->_currentFile); #endif @@ -171,18 +170,19 @@ class FTPServer : public TCPServer { client->_client.println("451 Requested action aborted: local error in processing."); } - client->_dataClient.stop(); + client->closeDataConnection(); client->_fileIsBeeingReceived = false; client->_dataTransferPending = NONE; } } } - else if(client->_fileIsBeeingReceived) + //If no data connection exists and no error was raised during writting, then it could be just a file creation with no data connection opened + else if(client->_fileIsBeeingReceived || (client->_dataClientConnected || millis() - client->_actionTimeout > 5000)) { if(client->_fileRecvBytes == 0) //File was just created empty { if(_sdCardManager->exists(client->_currentFile)) - _sdCardManager->remove(client->_currentFile); + _sdCardManager->remove(client->_currentFile); File file2create = _sdCardManager->open(client->_currentFile, FILE_WRITE); @@ -201,13 +201,55 @@ class FTPServer : public TCPServer client->_client.println("226 Closing data connection."); client->_fileIsBeeingReceived = false; - client->_dataClient.stop(); + client->closeDataConnection(); client->_dataTransferPending = NONE; client->_fileRecvBytes = 0; } - break; + break; + case APPE_DF: + if (client->_dataClient.connected() || client->_dataClient.available())//Here we need to check if client has some data for reading. IMPORTANT + { + if(client->_dataClient.available()) + { + client->_fileIsBeeingReceived = true; + #ifdef DEBUG_FTPS + Serial.printf("appending to file %s\n", client->_currentFile); + #endif + FileTransferStatus fts = OK; + + if(!writeToSdCard(client, &fts, true)) //An error occured + { + if(fts == NO_FILE_NAME) + { + client->_client.println("501 No file name given."); + } + else + { + client->_client.println("451 Requested action aborted: local error in processing."); + } + client->closeDataConnection(); + client->_fileIsBeeingReceived = false; + client->_dataTransferPending = NONE; + } + } + } + //If the connection is closed and data has been received, then we got the whole file + else if(client->_fileIsBeeingReceived) + { + #ifdef DEBUG_FTPS + Serial.println("Whole file received"); + #endif + + client->_client.println("226 Closing data connection."); + + client->_fileIsBeeingReceived = false; + client->closeDataConnection(); + client->_dataTransferPending = NONE; + client->_fileRecvBytes = 0; + } + break; } - + #ifdef DEBUG_FTPS if (client->_newDataAvailable) { @@ -215,17 +257,17 @@ class FTPServer : public TCPServer } #endif - if (client->_dataSize > 0) + if(client->_dataSize > 0) { switch (client->_ftpClientState) { + case WAITING_FOR_COMMANDS: + processCommands(client); + break; case INIT: client->setCurrentDirectory(FTP_DIR); client->_ftpClientState = WAITING_FOR_COMMANDS; break; - case WAITING_FOR_COMMANDS: - processCommands(client); - break; } } } @@ -259,7 +301,7 @@ class FTPServer : public TCPServer client->setUsername(client->_cmdParameters->getAt(0)->getString()); } else - client->_client.println("530 Username required."); + sendInfoResponse(_530, client);//client->_client.println("530 Username required."); } else //The ftp access is open { @@ -279,35 +321,47 @@ class FTPServer : public TCPServer } else { - client->_client.println("530 Wrong username or password !."); + sendInfoResponse(_530, client, "Wrong username or password !");//client->_client.println("530 Wrong username or password !."); } } else { - client->_client.println("530 Password required."); + sendInfoResponse(_530, client,"");//client->_client.println("530 Password required."); } } else if (strcmp(client->_ftpCommand, "PWD") == 0) //We set the default directory { + if(!client->_loggedIn) + { + sendInfoResponse(_530, client); + return; + } + client->_client.printf("257 \"%s\"\r\n", client->_currentDirectory); } else if (strcmp(client->_ftpCommand, "TYPE") == 0) { + if(!client->_loggedIn) + { + sendInfoResponse(_530, client); + return; + } + if (client->_cmdParameters->count() > 0) { switch (client->_cmdParameters->getAt(0)->getString()[0]) { case 'I': client->_binaryFlag = ON; - client->_client.println("200 Command okay."); + sendInfoResponse(_200, client,"");//client->_client.println("200 Command okay."); break; case 'L': client->_binaryFlag = ON; - client->_client.println("200 Command okay."); + sendInfoResponse(_200, client,"");//client->_client.println("200 Command okay."); break; case 'A': client->_binaryFlag = OFF; - client->_client.println("200 Command okay."); + sendInfoResponse(_200, client,"");//client->_client.println("200 Command okay."); break; default: client->_client.println("504 Command not implemented for TYPE."); @@ -320,19 +374,80 @@ class FTPServer : public TCPServer } else if (strcmp(client->_ftpCommand, "PASV") == 0) { - client->_client.printf("227 Entering Passive Mode (%u,%u,%u,%u,%d,%d).\r\n", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3], _dataPort / 256, _dataPort % 256); - client->_waitingForDataConnection = true; + if(!client->_loggedIn) + { + sendInfoResponse(_530, client); + return; + } + + //We need to test if we are in softAP or STA to chose the right IP + uint8_t addr[4] = {0}; + if(WiFi.status() == WL_CONNECTED) + { + addr[0] = WiFi.localIP()[0]; + addr[1] = WiFi.localIP()[1]; + addr[2] = WiFi.localIP()[2]; + addr[3] = WiFi.localIP()[3]; + }else + { + addr[0] = WiFi.softAPIP()[0]; + addr[1] = WiFi.softAPIP()[1]; + addr[2] = WiFi.softAPIP()[2]; + addr[3] = WiFi.softAPIP()[3]; + } + + client->_client.printf("227 Entering Passive Mode (%u,%u,%u,%u,%d,%d).\r\n", addr[0], addr[1], addr[2], addr[3], _dataPort / 256, _dataPort % 256); #ifdef DEBUG_FTPS Serial.println("Opening data server for new data client"); #endif + //We listen for 100 ms directly to accept the client + uint64_t timeOut(millis()); + while(true) + { + WiFiClient dataClient = _dataServer.available(); + //Serial.printf("Client state : %d\n", dataClient.status()); + if (dataClient) + { + //Serial.printf("Data client is true , available : %d\n", dataClient.available()); + if (dataClient.connected())//Connected returns true if the status() is ESTABLISHED or available() returns true + { + client->setDataClient(dataClient); + #ifdef DEBUG_FTPS + Serial.println("Data client accepted from loop"); + #endif + break; + } + else + dataClient.stop(); + } + + if(millis() - timeOut > 100) + { + client->_waitingForDataConnection = true; + break; + } + yield(); + } } else if (strcmp(client->_ftpCommand, "LIST") == 0) { + if(!client->_loggedIn) + { + sendInfoResponse(_530, client); + return; + } + //We inform that a data transfer is pending client->_dataTransferPending = LIST_DF; } else if (strcmp(client->_ftpCommand, "CWD") == 0) { + if(!client->_loggedIn) + { + sendInfoResponse(_530, client); + return; + } + if (client->_cmdParameters->count() > 0) { //Go back one level @@ -389,6 +504,12 @@ class FTPServer : public TCPServer } else if(strcmp(client->_ftpCommand, "RETR") == 0) { + if(!client->_loggedIn) + { + sendInfoResponse(_530, client); + return; + } + if (client->_cmdParameters->count() > 0) { //We save the file path to be sent @@ -408,6 +529,12 @@ class FTPServer : public TCPServer } else if(strcmp(client->_ftpCommand, "MKD") == 0) { + if(!client->_loggedIn) + { + sendInfoResponse(_530, client); + return; + } + if (client->_cmdParameters->count() > 0) { char *dirNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters); @@ -429,16 +556,22 @@ class FTPServer : public TCPServer } else { - client->_client.println("550 Requested action not taken."); + sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken."); } } else { - client->_client.println("550 Requested action not taken."); + sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken."); } } else if(strcmp(client->_ftpCommand, "RMD") == 0) { + if(!client->_loggedIn) + { + sendInfoResponse(_530, client); + return; + } + if (client->_cmdParameters->count() > 0) { //We have the dir name, we need to append the current directory... @@ -454,27 +587,36 @@ class FTPServer : public TCPServer } else { - client->_client.println("550 Requested action not taken."); + sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken."); } free(dirNameWithPath); } else { - client->_client.println("550 Requested action not taken."); + sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken."); } } else if(strcmp(client->_ftpCommand, "STOR") == 0) { + if(!client->_loggedIn) + { + sendInfoResponse(_530, client); + return; + } + if (client->_cmdParameters->count() > 0) { + client->_client.printf_P(PSTR("150 File status okay; about to open data connection.\r\n")); //We save the file path to be sent - char *fileNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters); + char *fileNameWithPath(NULL); - client->_client.println("150 File status okay; about to open data connection."); + if(client->_cmdParameters->getAt(0)->getString()[0] == '/') + fileNameWithPath = constructFileNameWithPath("", client->_cmdParameters); + else + fileNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters); client->setCurrentFile(fileNameWithPath); - #ifdef DEBUG_FTPS Serial.printf("File to store : #%s#\n", client->_currentFile); #endif @@ -483,12 +625,39 @@ class FTPServer : public TCPServer client->startTimeout(); } } + else if(strcmp(client->_ftpCommand, "APPE") == 0) + { + if(!client->_loggedIn) + { + sendInfoResponse(_530, client); + return; + } + + if (client->_cmdParameters->count() > 0) + { + client->_client.printf_P(PSTR("150 File status okay; about to open data connection.\r\n")); + //We save the file path to be sent + char *fileNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters); + client->setCurrentFile(fileNameWithPath); + #ifdef DEBUG_FTPS + Serial.printf("File to append : #%s#\n", client->_currentFile); + #endif + free(fileNameWithPath); + client->_dataTransferPending = APPE_DF; + } + } else if(strcmp(client->_ftpCommand, "SYST") == 0) { client->_client.println("215 UNIX Type: L8"); } else if(strcmp(client->_ftpCommand, "DELE") == 0) { + if(!client->_loggedIn) + { + sendInfoResponse(_530, client); + return; + } + if (client->_cmdParameters->count() > 0) { //We have the file name, we need to append the current directory... @@ -504,22 +673,33 @@ class FTPServer : public TCPServer } else { - client->_client.println("550 Requested action not taken."); + sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken."); } free(file2deleteNameWithPath); } else { - client->_client.println("550 Requested action not taken."); + sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken."); } } else if(strcmp(client->_ftpCommand, "RNFR") == 0) { + if(!client->_loggedIn) + { + sendInfoResponse(_530, client); + return; + } + if (client->_cmdParameters->count() > 0) { //We have the file name, we need to append the current directory... - char *fileNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters); + char *fileNameWithPath(NULL); + + if(client->_cmdParameters->getAt(0)->getString()[0] == '/') + fileNameWithPath = constructFileNameWithPath("", client->_cmdParameters); + else + fileNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters); #ifdef DEBUG_FTPS Serial.printf("file to rename : #%s#\n",fileNameWithPath); @@ -532,15 +712,26 @@ class FTPServer : public TCPServer } else { - client->_client.println("550 Requested action not taken."); + sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken."); } } else if(strcmp(client->_ftpCommand, "RNTO") == 0) { - if (client->_cmdParameters->count() > 0) + if(!client->_loggedIn) + { + sendInfoResponse(_530, client); + return; + } + + if (client->_cmdParameters->count() > 0) //If the name starts with a /, we do not need to prepend the directory { //Here we rename the file - char *file2RenameNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters); + char *file2RenameNameWithPath(NULL); + if(client->_cmdParameters->getAt(0)->getString()[0] == '/') + file2RenameNameWithPath = constructFileNameWithPath("", client->_cmdParameters); + else + file2RenameNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters); + #ifdef DEBUG_FTPS Serial.printf("file to rename to : #%s#\n",file2RenameNameWithPath); Serial.printf("Old name : %s --> %s\n",client->_currentFile,file2RenameNameWithPath); @@ -550,21 +741,45 @@ class FTPServer : public TCPServer { client->_client.println("250 Requested file action okay."); }else - client->_client.println("550 Requested action not taken."); + sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken."); free(file2RenameNameWithPath); } } + else if(strcmp(client->_ftpCommand, "QUIT") == 0) + { + if(client->_dataTransferPending == NONE)//If no transfers are in progress + { + sendInfoResponse(_221, client); + + client->_client.stop(); + client->_clientState = TCPClient::ClientState::DISCARDED; + } + else + sendInfoResponse(_221, client); + } + /*else if(strcmp(client->_ftpCommand, "SIZE") == 0) + { + if (client->_cmdParameters->count() > 0) + { + client->_client.println("213 15224"); + } + }*/ else + { client->_client.println("502 Command not implemented."); + #ifdef DEBUG_FTPS + Serial.println("Command not implemented"); + #endif + } } //Here we write the received file to the sd card - boolean writeToSdCard(T *client, FileTransferStatus *fts) + ICACHE_RAM_ATTR inline boolean writeToSdCard(T *client, FileTransferStatus *fts, boolean append = false) { if (client->_currentFile != NULL) { - if(_sdCardManager->exists(client->_currentFile) && client->_fileRecvBytes == 0) + if(_sdCardManager->exists(client->_currentFile) && client->_fileRecvBytes == 0 && !append) _sdCardManager->remove(client->_currentFile); File fileBeeingReceived = _sdCardManager->open(client->_currentFile, FILE_WRITE); @@ -598,7 +813,7 @@ class FTPServer : public TCPServer } //Here we send the fs tree to the ftp client - boolean sendFSTree(T *client) + ICACHE_RAM_ATTR inline boolean sendFSTree(T *client) { if (client->_currentDirectory != NULL) { @@ -642,7 +857,7 @@ class FTPServer : public TCPServer } //The binary flag needs to be taken into consideration - boolean sendFile(T *client, FileTransferStatus *fts) + ICACHE_RAM_ATTR inline boolean sendFile(T *client, FileTransferStatus *fts) { if (client->_currentFile != NULL) { @@ -690,39 +905,7 @@ class FTPServer : public TCPServer *fts = NOT_FOUND; return false; } - - static char *_83FileNameFormat(char *filename) - { - char *buffer = (char *)malloc((sizeof(char) * strlen(filename)) + 1); - strcpy(buffer, filename); - char *delimiter = strchr(buffer, '.'); - if(delimiter != NULL) - { - *delimiter = '\0'; - if(strlen(buffer) > 8) - buffer[8] = '\0'; - - strcpy(filename, buffer); - - if(strlen(delimiter + 1) > 3) - *(delimiter + 1 + 3) = '\0'; - - strcat(filename, "."); - strcat(filename, delimiter + 1); - } - else - { - if(strlen(buffer) > 8) - { - buffer[8] = '\0'; - } - strcpy(filename, buffer); - } - free(buffer); - return filename; - } - //This functions construct the full file path. //ie : if the current directory is : "/somedir/subdir" and the received file name is : "my file .txt" //then it will return : "/somedir/subdir/my file .txt" @@ -763,10 +946,30 @@ class FTPServer : public TCPServer return fileNameWithPath; } + //Error code functions + void sendInfoResponse(FtpMsgCode code, T *client, const char *msg = "") + { + switch(code) + { + case _200: + client->_client.printf_P(PSTR("200 Command okay. %s\r\n"), msg); + break; + case _221: + client->_client.printf_P(PSTR("221 Service closing control connection. %s\r\n")); + break; + case _530: + client->_client.printf_P(PSTR("530 Password required. %s\r\n"), msg); + break; + case _550: + client->_client.printf_P(PSTR("550 Requested action not taken. %s\r\n"), msg); + break; + } + } + char *_login; char *_password; unsigned int _dataPort; - + WiFiServer _dataServer; //In passive mode, the FTP server opens two different ports (one for the commands and the other for the data stream) SDCardManager *_sdCardManager; };