From 4e889614eb17961cfad0b92c9dd2545add668b52 Mon Sep 17 00:00:00 2001 From: Ugo Date: Mon, 21 Jul 2025 20:35:48 +0100 Subject: [PATCH] fix: REST and PASS security fixes --- controlChannel/controlChannel.c | 30 +++++-- ftpCommandElaborate.c | 33 +++++++- ftpCommandsElaborate.h | 1 + test/integration.py | 142 +++++++++++++++++++++----------- 4 files changed, 144 insertions(+), 62 deletions(-) diff --git a/controlChannel/controlChannel.c b/controlChannel/controlChannel.c index 67ce90f..40efc66 100644 --- a/controlChannel/controlChannel.c +++ b/controlChannel/controlChannel.c @@ -305,9 +305,28 @@ static int processCommand(int processingElement, ftpDataType *ftpData) #define COMMAND_MAP_SIZE (sizeof(commandMap) / sizeof(CommandMapEntry)) int toReturn = 0; + int commandIndex = -1; + //printTimeStamp(); my_printf("\nCommand received from (%d): %s", processingElement, ftpData->clients[processingElement].theCommandReceived); + for (int i = 0; i < COMMAND_MAP_SIZE; ++i) + { + if (IS_CMD(ftpData->clients[processingElement].theCommandReceived, commandMap[i].command)) + { + commandIndex = i; + break; + } + } + + if (commandIndex == -1) + { + toReturn = invalidCommandResponse(ftpData, processingElement); + ftpData->clients[processingElement].commandIndex = 0; + memset(ftpData->clients[processingElement].theCommandReceived, 0, CLIENT_COMMAND_STRING_SIZE+1); + return toReturn; + } + if(!isTransferCommand(processingElement, ftpData) && ftpData->clients[processingElement].workerData.retrRestartAtByte != 0) { @@ -332,15 +351,8 @@ static int processCommand(int processingElement, ftpDataType *ftpData) return 1; } - for (int i = 0; i < COMMAND_MAP_SIZE; ++i) - { - if (IS_CMD(ftpData->clients[processingElement].theCommandReceived, commandMap[i].command)) - { - my_printf("\n%s COMMAND RECEIVED", commandMap[i].command); - toReturn = commandMap[i].handler(ftpData, processingElement); - break; - } - } + my_printf("\n%s COMMAND RECEIVED", commandMap[commandIndex].command); + toReturn = commandMap[commandIndex].handler(ftpData, processingElement); ftpData->clients[processingElement].commandIndex = 0; memset(ftpData->clients[processingElement].theCommandReceived, 0, CLIENT_COMMAND_STRING_SIZE+1); diff --git a/ftpCommandElaborate.c b/ftpCommandElaborate.c index b4f968d..3d2c214 100755 --- a/ftpCommandElaborate.c +++ b/ftpCommandElaborate.c @@ -167,6 +167,19 @@ int parseCommandPass(ftpDataType *data, int socketId) loginFailsDataType element; int searchPosition = -1; + if (data->clients[socketId].login.name.textLen <= 0) + { + returnCode = socketPrintf(data, socketId, "s", "503 Login with USER first.\r\n"); + + if (returnCode <= 0) + { + LOG_ERROR("socketPrintfError"); + return FTP_COMMAND_PROCESSED_WRITE_ERROR; + } + + return FTP_COMMAND_PROCESSED; + } + thePass = getFtpCommandArg("PASS", data->clients[socketId].theCommandReceived, 0); strncpy(element.ipAddress, data->clients[socketId].clientIpAddress, INET_ADDRSTRLEN); @@ -1597,9 +1610,8 @@ int parseCommandMkd(ftpDataType *data, int socketId) { returnStatus = FILE_doChownFromUidGid(mkdFileName.text, data->clients[socketId].login.ownerShip.uid, data->clients[socketId].login.ownerShip.gid); } - - returnCode = socketPrintf(data, socketId, "sss", "257 \"", theDirectoryFilename, "\" : The directory was successfully created\r\n"); - + returnCode = socketPrintf(data, socketId, "sss", "257 \"", theDirectoryFilename, "\" The directory was successfully created\r\n"); + my_printf("\n\n ------------------ Directory created"); if (returnCode <= 0) { LOG_ERROR("socketPrintfError"); @@ -1882,6 +1894,21 @@ int notLoggedInMessage(ftpDataType *data, int socketId) return FTP_COMMAND_PROCESSED; } +int invalidCommandResponse(ftpDataType *data, int socketId) +{ + int returnCode; + returnCode = socketPrintf(data, socketId, "s", "500 Syntax error, command unrecognized.\r\n"); + + if (returnCode <= 0) + { + LOG_ERROR("socketPrintfError"); + return FTP_COMMAND_PROCESSED_WRITE_ERROR; + } + + return FTP_COMMAND_PROCESSED; +} + + int parseCommandQuit(ftpDataType *data, int socketId) { int returnCode; diff --git a/ftpCommandsElaborate.h b/ftpCommandsElaborate.h index 8c8cf92..b4a0130 100755 --- a/ftpCommandsElaborate.h +++ b/ftpCommandsElaborate.h @@ -76,6 +76,7 @@ int parseCommandRetr(ftpDataType * data, int socketId); int parseCommandMkd(ftpDataType * data, int socketId); int parseCommandNoop(ftpDataType * data, int socketId); int notLoggedInMessage(ftpDataType * data, int socketId); +int invalidCommandResponse(ftpDataType *data, int socketId); int parseCommandRmd(ftpDataType * data, int socketId); int parseCommandQuit(ftpDataType * data, int socketId); int parseCommandSize(ftpDataType * data, int socketId); diff --git a/test/integration.py b/test/integration.py index 955a367..12a0640 100644 --- a/test/integration.py +++ b/test/integration.py @@ -6,6 +6,7 @@ import time import ssl import socket import ftplib +import re FTP_HOST = '127.0.0.1' FTP_PORT = 21 @@ -115,32 +116,103 @@ class FTPServerRFCComplianceTests(unittest.TestCase): self.assertTrue(pwd.startswith('/'), f"PWD should return directory path, got: {pwd}") resp = self.ftp.cwd('/') self.assertTrue(resp.startswith('250'), f"CWD should succeed with 250 response, got: {resp}") - - def Disabledtest_utf8_mkd(self): - """Verify server accepts MKD command with UTF-8 directory names.""" + + + def Disabledtest_mkdr(self): test_dirs = [ - "Café", - "测试", - "директория", - "データ", - "résumé" + "dir1", + "dir2", + "dir3" ] - + + self.utf8_mkd(test_dirs) + + + + def test_utf8_mkd(self): + test_dirs = ["директория", "データ", "résumé"] + with ftplib.FTP() as ftp: ftp.connect(FTP_HOST, FTP_PORT, timeout=5) ftp.login(FTP_USER, FTP_PASS) - + ftp.encoding = 'utf-8' + + # Enable UTF-8 option + try: + ftp.sendcmd("OPTS UTF8 ON") + except Exception: + pass + + for d in test_dirs: + # Try to remove if exists + try: + ftp.rmd(d) + except ftplib.error_perm: + pass + + response = ftp.mkd(d) + + # Match response code and directory name more flexibly + m = re.match(r'257\s+"?(.+?)"?\s', response) + assert m is not None, f"MKD response format incorrect: {response}" + dir_in_response = m.group(1) + assert dir_in_response == d, f"Directory name mismatch: expected {d}, got {dir_in_response}" + + # Cleanup + ftp.rmd(d) + + + def utf8_mkd(self, test_dirs): + #test_dirs = [ + # "Café", + # "测试", + # "директория", + # "データ", + # "résumé" + #] + + with ftplib.FTP() as ftp: + ftp.connect(FTP_HOST, FTP_PORT, timeout=5) + ftp.login(FTP_USER, FTP_PASS) + + ftp.encoding = 'utf-8' + try: + print("C: OPTS UTF8 ON") + response = ftp.sendcmd("OPTS UTF8 ON") + print(f"S: {repr(response)}") + except Exception as e: + print(f"Warning: OPTS UTF8 ON failed: {e}") + for dir_name_utf8 in test_dirs: with self.subTest(dir=dir_name_utf8): - print(f"C: MKD {dir_name_utf8}") - response = ftp.mkd(dir_name_utf8) - print(f"S: {response}") - - self.assertTrue(response.startswith('257'), f"MKD failed for directory '{dir_name_utf8}'") - - # Clean up after test - ftp.rmd(dir_name_utf8) - print(f"Removed directory: {dir_name_utf8}") + try: + # Delete if exists + try: + ftp.cwd(dir_name_utf8) + ftp.cwd("..") + ftp.rmd(dir_name_utf8) + print(f"Deleted existing dir: {dir_name_utf8}") + except ftplib.error_perm: + pass + + # Create directory + print(f"C: MKD {dir_name_utf8}") + response = ftp.mkd(dir_name_utf8) + print(f"S: {repr(response)}") + + self.assertTrue(response.startswith('257'), + f"MKD failed: Server responded with: {repr(response)}") + + # Verify directory is accessible by changing to it + ftp.cwd(dir_name_utf8) + ftp.cwd("..") + + # Cleanup + ftp.rmd(dir_name_utf8) + print(f"Removed directory: {dir_name_utf8}") + + except Exception as e: + self.fail(f"Exception for directory '{dir_name_utf8}': {e}") def test_pbsz_prot_violations(self): @@ -193,36 +265,6 @@ class FTPServerRFCComplianceTests(unittest.TestCase): finally: ssock.close() - - - def Disabtest_utf8_mkd_with_opts(self): - dir_name_utf8 = "rés" - try: - with ftplib.FTP() as ftp: - ftp.connect(FTP_HOST, FTP_PORT, timeout=5) - ftp.login(FTP_USER, FTP_PASS) - - # Enable UTF-8 mode on the server - print("C: OPTS UTF8 ON") - response = ftp.sendcmd("OPTS UTF8 ON") - print(f"S: {response}") - - if not response.startswith('200'): - print("Warning: OPTS UTF8 not supported or rejected by server.") - - # Now try to create directory with UTF-8 name - print(f"C: MKD {dir_name_utf8}") - response = ftp.mkd(dir_name_utf8) - print(f"S: {response}") - - print(f"Successfully created UTF-8 directory: {dir_name_utf8}") - - except ftplib.error_perm as e: - print(f"FTP permission error creating directory '{dir_name_utf8}': {e}") - except Exception as e: - print(f"Error: {e}") - - def test_feat_space_indent(self): """ @@ -630,7 +672,7 @@ class FTPServerRFCComplianceTests(unittest.TestCase): resp = self.ftp.quit() self.assertTrue(resp.startswith('221'), f"QUIT should respond with 221, got: {resp}") - def Disabledtest_invalid_command(self): + def test_invalid_command(self): try: resp = self.ftp.sendcmd('FOOBAR') self.assertTrue(resp.startswith('500') or resp.startswith('502'),