fix: REST and PASS security fixes

This commit is contained in:
Ugo
2025-07-21 20:35:48 +01:00
parent c4ab90b358
commit 4e889614eb
4 changed files with 144 additions and 62 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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'),