uFTP/controlChannel/controlChannel.c
2025-07-21 20:35:48 +01:00

360 lines
15 KiB
C

/*
* The MIT License
*
* Copyright 2018 Ugo Cirmignani.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <netdb.h>
#include <errno.h>
/* FTP LIBS */
#include "library/fileManagement.h"
#include "library/configRead.h"
#include "library/signals.h"
#include "library/openSsl.h"
#include "library/connection.h"
#include "library/dynamicMemory.h"
#include "library/errorHandling.h"
#include "library/daemon.h"
#include "library/log.h"
#include "ftpServer.h"
#include "ftpData.h"
#include "ftpCommandsElaborate.h"
#include "debugHelper.h"
#include "controlChannel.h"
/* Private function definition */
static int processCommand(int processingElement, ftpDataType *ftpData);
static void memoryDebug(ftpDataType *ftpData);
static int isTransferCommand(int processingElement, ftpDataType *ftpData);
void evaluateControlChannel(ftpDataType *ftpData)
{
int returnCode = 0;
//Update watchdog timer
updateWatchDogTime((int)time(NULL));
//debug memory usage
memoryDebug(ftpData);
/* waits for socket activity, if no activity then checks for client socket timeouts */
if (selectWait(ftpData) == 0)
{
checkClientConnectionTimeout(ftpData);
flushLoginWrongTriesData(ftpData);
}
/*Main loop handle client commands */
for (int processingSock = 0; processingSock < ftpData->ftpParameters.maxClients; processingSock++)
{
/* close the connection if quit flag has been set */
if (ftpData->clients[processingSock].closeTheClient == 1)
{
closeClient(ftpData, processingSock);
continue;
}
/* Check if there are client pending connections, accept the connection if possible otherwise reject */
if ((returnCode = evaluateClientSocketConnection(ftpData)) == 1)
{
break;
}
/* no data to check client is not connected, continue to check other clients */
if (isClientConnected(ftpData, processingSock) == 0)
{
/* socket is not conneted */
continue;
}
if (FD_ISSET(ftpData->clients[processingSock].socketDescriptor, &ftpData->connectionData.rset) ||
FD_ISSET(ftpData->clients[processingSock].socketDescriptor, &ftpData->connectionData.eset))
{
#ifdef OPENSSL_ENABLED
if (ftpData->clients[processingSock].tlsIsNegotiating == 1)
{
returnCode = SSL_accept(ftpData->clients[processingSock].ssl);
if (returnCode <= 0)
{
//my_printf("\nSSL NOT YET ACCEPTED: %d", returnCode);
ftpData->clients[processingSock].tlsIsEnabled = 0;
ftpData->clients[processingSock].tlsIsNegotiating = 1;
if ( ((int)time(NULL) - ftpData->clients[processingSock].tlsNegotiatingTimeStart) > TLS_NEGOTIATING_TIMEOUT )
{
ftpData->clients[processingSock].closeTheClient = 1;
LOGF("%sTLS timeout closing the client time:%lld, start time: %lld..", LOG_DEBUG_PREFIX, (int)time(NULL), ftpData->clients[processingSock].tlsNegotiatingTimeStart);
//my_printf("\nTLS timeout closing the client time:%lld, start time: %lld..", (int)time(NULL), ftpData->clients[processingSock].tlsNegotiatingTimeStart);
}
}
else
{
//my_printf("\nSSL ACCEPTED");
ftpData->clients[processingSock].tlsIsEnabled = 1;
ftpData->clients[processingSock].tlsIsNegotiating = 0;
}
continue;
}
#endif
if (ftpData->clients[processingSock].tlsIsEnabled == 1)
{
#ifdef OPENSSL_ENABLED
ftpData->clients[processingSock].bufferIndex = SSL_read(ftpData->clients[processingSock].ssl, ftpData->clients[processingSock].buffer, CLIENT_BUFFER_STRING_SIZE);
#endif
}
else
{
ftpData->clients[processingSock].bufferIndex = read(ftpData->clients[processingSock].socketDescriptor, ftpData->clients[processingSock].buffer, CLIENT_BUFFER_STRING_SIZE);
}
//The client is not connected anymore
if ((ftpData->clients[processingSock].bufferIndex) == 0)
{
closeClient(ftpData, processingSock);
}
//Some commands has been received
if (ftpData->clients[processingSock].bufferIndex > 0)
{
int i = 0;
int commandProcessStatus = 0;
for (i = 0; i < ftpData->clients[processingSock].bufferIndex; i++)
{
if (ftpData->clients[processingSock].commandIndex < CLIENT_COMMAND_STRING_SIZE)
{
if (ftpData->clients[processingSock].buffer[i] != '\r' && ftpData->clients[processingSock].buffer[i] != '\n')
{
ftpData->clients[processingSock].theCommandReceived[ftpData->clients[processingSock].commandIndex++] = ftpData->clients[processingSock].buffer[i];
}
if (ftpData->clients[processingSock].buffer[i] == '\n')
{
ftpData->clients[processingSock].socketCommandReceived = 1;
//my_printf("\n Processing the command: %s", ftpData->clients[processingSock].theCommandReceived);
commandProcessStatus = processCommand(processingSock, ftpData);
//Echo unrecognized commands
if (commandProcessStatus == FTP_COMMAND_NOT_RECONIZED)
{
int returnCode = 0;
returnCode = socketPrintf(ftpData, processingSock, "s", "500 Unknown command\r\n");
if (returnCode < 0)
{
ftpData->clients[processingSock].closeTheClient = 1;
LOG_ERROR("socketPrintf");
}
my_printf("\n COMMAND NOT SUPPORTED ********* %s", ftpData->clients[processingSock].buffer);
LOGF("%sCommand not supported: %s", LOG_DEBUG_PREFIX, ftpData->clients[processingSock].buffer);
}
else if (commandProcessStatus == FTP_COMMAND_PROCESSED)
{
ftpData->clients[processingSock].lastActivityTimeStamp = (int)time(NULL);
}
else if (commandProcessStatus == FTP_COMMAND_PROCESSED_WRITE_ERROR)
{
ftpData->clients[processingSock].closeTheClient = 1;
LOG_ERROR("ftp command processed error");
my_printf("\n Write error WARNING!");
}
}
}
else
{
//Command overflow can't be processed
int returnCode;
ftpData->clients[processingSock].commandIndex = 0;
memset(ftpData->clients[processingSock].theCommandReceived, 0, CLIENT_COMMAND_STRING_SIZE+1);
returnCode = socketPrintf(ftpData, processingSock, "s", "500 Unknown command\r\n");
if (returnCode <= 0)
{
ftpData->clients[processingSock].closeTheClient = 1;
LOG_ERROR("socketPrintf");
}
my_printf("\n Command too long closing the client.");
break;
}
}
memset(ftpData->clients[processingSock].buffer, 0, CLIENT_BUFFER_STRING_SIZE+1);
}
}
}
}
/* Private static functions */
static void memoryDebug(ftpDataType *ftpData)
{
my_printf("\nUsed memory : %lld", DYNMEM_GetTotalMemory());
for (int memCount = 0; memCount < ftpData->ftpParameters.maxClients; memCount++)
{
if (ftpData->clients[memCount].memoryTable != NULL)
{
my_printf("\nftpData->clients[%d].memoryTable = %s", memCount, ftpData->clients[memCount].memoryTable->theName);
}
if (ftpData->clients[memCount].workerData.memoryTable != NULL)
{
my_printf("\nftpData->clients[%d].workerData.memoryTable = %s", memCount, ftpData->clients[memCount].workerData.memoryTable->theName);
}
if (ftpData->clients[memCount].workerData.directoryInfo.memoryTable != NULL)
{
my_printf("\nftpData->clients[%d].workerData.directoryInfo.memoryTable = %s", memCount, ftpData->clients[memCount].workerData.directoryInfo.memoryTable->theName);
}
}
}
static int isTransferCommand(int processingElement, ftpDataType *ftpData)
{
if (IS_CMD(ftpData->clients[processingElement].theCommandReceived, "RETR") ||
IS_CMD(ftpData->clients[processingElement].theCommandReceived, "STOR") ||
IS_CMD(ftpData->clients[processingElement].theCommandReceived, "PASV") ||
IS_CMD(ftpData->clients[processingElement].theCommandReceived, "TYPE I") ||
IS_CMD(ftpData->clients[processingElement].theCommandReceived, "TYPE A") ||
IS_CMD(ftpData->clients[processingElement].theCommandReceived, "TYPE F") ||
IS_CMD(ftpData->clients[processingElement].theCommandReceived, "APPE"))
{
return 1;
}
return 0;
}
static int processCommand(int processingElement, ftpDataType *ftpData)
{
//command handler structure
static CommandMapEntry commandMap[] = {
{"USER", parseCommandUser},
{"PASS", parseCommandPass},
{"SITE", parseCommandSite},
{"AUTH TLS", parseCommandAuth},
{"PROT", parseCommandProt},
{"PBSZ", parseCommandPbsz},
{"CCC", parseCommandCcc},
{"PWD", parseCommandPwd},
{"XPWD", parseCommandPwd},
{"SYST", parseCommandSyst},
{"FEAT", parseCommandFeat},
{"TYPE I", parseCommandTypeI},
{"TYPE A", parseCommandTypeI},
{"STRU F", parseCommandStruF},
{"MODE S", parseCommandModeS},
{"EPSV", parseCommandEpsv},
{"PASV", parseCommandPasv},
{"PORT", parseCommandPort},
{"EPRT", parseCommandEprt},
{"LIST", parseCommandList},
{"STAT", parseCommandStat},
{"CDUP", parseCommandCdup},
{"XCUP", parseCommandCdup},
{"CWD ..", parseCommandCdup},
{"CWD", parseCommandCwd},
{"REST", parseCommandRest},
{"RETR", parseCommandRetr},
{"STOR", parseCommandStor},
{"MKD", parseCommandMkd},
{"XMKD", parseCommandMkd},
{"ABOR", parseCommandAbor},
{"DELE", parseCommandDele},
{"OPTS", parseCommandOpts},
{"MDTM", parseCommandMdtm},
{"NLST", parseCommandNlst},
{"QUIT", parseCommandQuit},
{"RMD", parseCommandRmd},
{"XRMD", parseCommandRmd},
{"RNFR", parseCommandRnfr},
{"RNTO", parseCommandRnto},
{"SIZE", parseCommandSize},
{"APPE", parseCommandAppe},
{"NOOP", parseCommandNoop},
{"ACCT", parseCommandAcct}
};
#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)
{
my_printf("Reset: retrRestartAtByte");
ftpData->clients[processingElement].workerData.retrRestartAtByte = 0;
}
cleanDynamicStringDataType(&ftpData->clients[processingElement].ftpCommand.commandArgs, 0, ftpData->clients[processingElement].memoryTable);
cleanDynamicStringDataType(&ftpData->clients[processingElement].ftpCommand.commandOps, 0, ftpData->clients[processingElement].memoryTable);
if (ftpData->clients[processingElement].login.userLoggedIn == 0 &&
(IS_NOT_CMD(ftpData->clients[processingElement].theCommandReceived, "USER") &&
IS_NOT_CMD(ftpData->clients[processingElement].theCommandReceived, "PASS") &&
IS_NOT_CMD(ftpData->clients[processingElement].theCommandReceived, "QUIT") &&
IS_NOT_CMD(ftpData->clients[processingElement].theCommandReceived, "PBSZ") &&
IS_NOT_CMD(ftpData->clients[processingElement].theCommandReceived, "PROT") &&
IS_NOT_CMD(ftpData->clients[processingElement].theCommandReceived, "AUTH TLS")))
{
toReturn = notLoggedInMessage(ftpData, processingElement);
ftpData->clients[processingElement].commandIndex = 0;
memset(ftpData->clients[processingElement].theCommandReceived, 0, CLIENT_COMMAND_STRING_SIZE+1);
return 1;
}
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);
return toReturn;
}