From eac95c11294eee8c95200649d5aea5455ed639c0 Mon Sep 17 00:00:00 2001 From: Ugo Date: Sat, 19 Jul 2025 17:11:34 +0100 Subject: [PATCH] fix: PROT and PBSZ protocol violation --- ftpCommandElaborate.c | 95 +++++++++++++++++++++++++++++++++++++------ ftpData.c | 2 +- ftpData.h | 1 + 3 files changed, 85 insertions(+), 13 deletions(-) diff --git a/ftpCommandElaborate.c b/ftpCommandElaborate.c index cedc321..5cce992 100755 --- a/ftpCommandElaborate.c +++ b/ftpCommandElaborate.c @@ -427,25 +427,37 @@ int parseCommandFeat(ftpDataType *data, int socketId) int parseCommandProt(ftpDataType *data, int socketId) { int returnCode; - char *theProtArg; - theProtArg = getFtpCommandArg("PROT", data->clients[socketId].theCommandReceived, 0); + char *theProtArg = getFtpCommandArg("PROT", data->clients[socketId].theCommandReceived, 0); + + // Check that PBSZ was previously set before allowing PROT + if (data->clients[socketId].pbszIsSet == 0) + { + returnCode = socketPrintf(data, socketId, "s", "503 PROT requires prior PBSZ command\r\n"); + if (returnCode <= 0) + { + LOG_ERROR("socketPrintfError"); + return FTP_COMMAND_PROCESSED_WRITE_ERROR; + } + return FTP_COMMAND_PROCESSED; + } if (theProtArg[0] == 'C' || theProtArg[0] == 'c') { - // Clear + // Clear data channel (no protection) my_printf("\n---------------- Set data channel to clear"); data->clients[socketId].dataChannelIsTls = 0; returnCode = socketPrintf(data, socketId, "scs", "200 PROT set to ", theProtArg[0], "\r\n"); } else if (theProtArg[0] == 'P' || theProtArg[0] == 'p') { - // Private + // Private (encrypted) data channel my_printf("\n---------------- Set data channel to private"); data->clients[socketId].dataChannelIsTls = 1; returnCode = socketPrintf(data, socketId, "scs", "200 PROT set to ", theProtArg[0], "\r\n"); } else { + // Unsupported protection level returnCode = socketPrintf(data, socketId, "scs", "502 Mode ", theProtArg[0], " is not implemented\r\n"); } @@ -458,6 +470,7 @@ int parseCommandProt(ftpDataType *data, int socketId) return FTP_COMMAND_PROCESSED; } + int parseCommandCcc(ftpDataType *data, int socketId) { int returnCode; @@ -498,22 +511,54 @@ int parseCommandCcc(ftpDataType *data, int socketId) int parseCommandPbsz(ftpDataType *data, int socketId) { int returnCode; - char *thePbszSize; - thePbszSize = getFtpCommandArg("PBSZ", data->clients[socketId].theCommandReceived, 0); + char *thePbszSize = getFtpCommandArg("PBSZ", data->clients[socketId].theCommandReceived, 0); - returnCode = socketPrintf(data, socketId, "sss", "200 PBSZ set to ", thePbszSize, "\r\n"); - - my_printf("\n 200 PBSZ set to %s", thePbszSize); - - if (returnCode <= 0) + // PBSZ requires TLS session active + if (data->clients[socketId].tlsIsEnabled == 0) { + returnCode = socketPrintf(data, socketId, "s", "503 PBSZ requires an active TLS session\r\n"); + if (returnCode <= 0) { + LOG_ERROR("socketPrintfError"); + return FTP_COMMAND_PROCESSED_WRITE_ERROR; + } + return FTP_COMMAND_PROCESSED; + } + + // PBSZ must be sent before login + if (data->clients[socketId].login.userLoggedIn == 1) + { + returnCode = socketPrintf(data, socketId, "s", "503 PBSZ must be sent before login\r\n"); + if (returnCode <= 0) { + LOG_ERROR("socketPrintfError"); + return FTP_COMMAND_PROCESSED_WRITE_ERROR; + } + return FTP_COMMAND_PROCESSED; + } + + // Only PBSZ 0 is supported + if (!thePbszSize || strcmp(thePbszSize, "0") != 0) + { + returnCode = socketPrintf(data, socketId, "s", "501 Only PBSZ 0 is supported\r\n"); + if (returnCode <= 0) { + LOG_ERROR("socketPrintfError"); + return FTP_COMMAND_PROCESSED_WRITE_ERROR; + } + return FTP_COMMAND_PROCESSED; + } + + // Successful PBSZ negotiation + returnCode = socketPrintf(data, socketId, "s", "200 PBSZ=0\r\n"); + if (returnCode <= 0) { LOG_ERROR("socketPrintfError"); return FTP_COMMAND_PROCESSED_WRITE_ERROR; } + + data->clients[socketId].pbszIsSet = 1; return FTP_COMMAND_PROCESSED; } + int parseCommandTypeA(ftpDataType *data, int socketId) { int returnCode; @@ -1605,7 +1650,32 @@ int parseCommandMkd(ftpDataType *data, int socketId) int parseCommandOpts(ftpDataType *data, int socketId) { int returnCode; - returnCode = socketPrintf(data, socketId, "s", "200 OK\r\n"); + char *optionString = getFtpCommandArg("OPTS", data->clients[socketId].theCommandReceived, 0); + + my_printf("\noptionString: %s", optionString); + + if (!optionString || strlen(optionString) == 0) + { + returnCode = socketPrintf(data, socketId, "s", "501 Syntax error in parameters or arguments.\r\n"); + if (returnCode <= 0) LOG_ERROR("socketPrintfError"); + return FTP_COMMAND_PROCESSED; + } + + // For example, parse only UTF8 ON/OFF options: + if (strncasecmp(optionString, "UTF8 ON", 7) == 0) + { + // Enable UTF8 (set a flag in client/session data if needed) + returnCode = socketPrintf(data, socketId, "s", "200 UTF8 mode enabled\r\n"); + } + else if (strncasecmp(optionString, "UTF8 OFF", 8) == 0) + { + // Disable UTF8 + returnCode = socketPrintf(data, socketId, "s", "200 UTF8 mode disabled\r\n"); + } + else + { + returnCode = socketPrintf(data, socketId, "s", "501 Option not supported.\r\n"); + } if (returnCode <= 0) { @@ -1616,6 +1686,7 @@ int parseCommandOpts(ftpDataType *data, int socketId) return FTP_COMMAND_PROCESSED; } + int parseCommandDele(ftpDataType *data, int socketId) { int functionReturnCode = 0; diff --git a/ftpData.c b/ftpData.c index bc254ef..41adcdf 100755 --- a/ftpData.c +++ b/ftpData.c @@ -816,7 +816,7 @@ void resetClientData(ftpDataType *data, int clientId, int isInitialization) data->clients[clientId].closeTheClient = 0; data->clients[clientId].sockaddr_in_size = sizeof(struct sockaddr_in); data->clients[clientId].sockaddr_in_server_size = sizeof(struct sockaddr_in); - + data->clients[clientId].pbszIsSet = 0; memset(&data->clients[clientId].client_sockaddr_in, 0, data->clients[clientId].sockaddr_in_size); memset(&data->clients[clientId].server_sockaddr_in, 0, data->clients[clientId].sockaddr_in_server_size); diff --git a/ftpData.h b/ftpData.h index eb62238..315c15d 100755 --- a/ftpData.h +++ b/ftpData.h @@ -195,6 +195,7 @@ struct clientData int isIpV6; int tlsIsEnabled; int tlsIsNegotiating; + int pbszIsSet; unsigned long long int tlsNegotiatingTimeStart; int dataChannelIsTls; pthread_mutex_t writeMutex;