- utils: atoip(), lefttrim() - ARP: bunch of bugfixes - TCP, FTP: WIP - DHCP: mutex; query the FSM state
362 lines
11 KiB
C
362 lines
11 KiB
C
#include "ftp_server.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <etherlib/prefab/conn_blocks/tcp_connblock.h>
|
|
|
|
// ----------
|
|
|
|
/* FTP response messages */
|
|
|
|
static const char ftpOpeningDataConn[] = "150 Opening data connection.\r\n";
|
|
static const char ftpCmdOk[] = "200 Command OK.\r\n";
|
|
static const char ftpGreeting[] = "220 Service ready for new user.\r\n";
|
|
static const char ftpClosingDataConn[] = "226 Closing data connection.\r\n";
|
|
static const char ftpEnteringPassivePrefix[] = "227 Entering passive mode ";
|
|
static const char ftpLoginOK[] = "230 User logged in, proceed.\r\n";
|
|
static const char ftpFileActionCplt[] = "250 Requested file action complete.\r\n";
|
|
static const char ftpRootDirName[] = "257 \"/\" is the current directory\r\n";
|
|
static const char ftpSyntaxError[] = "501 Syntax error in command or parameters\r\n";
|
|
static const char ftpNotImplemented[] = "502 Command not implemented.\r\n";
|
|
static const char ftpCmdNotImplForParam[] = "504 Command not implemented for that parameter\r\n";
|
|
|
|
#define FTPS_DYNAMIC_RESPONSE_MAXLEN (63)
|
|
|
|
static char ftpsDynResp[FTPS_DYNAMIC_RESPONSE_MAXLEN + 1] = {0};
|
|
|
|
/* Implemented commands */
|
|
|
|
static char *ftpImplementedCmdsStr[] = {
|
|
// this is the smallest standard set of implemented FTP commands
|
|
"USER",
|
|
"QUIT",
|
|
"PORT",
|
|
"TYPE",
|
|
"MODE",
|
|
"STRU",
|
|
"RETR",
|
|
"STOR",
|
|
"NOOP",
|
|
|
|
// ------ additional commands
|
|
"PWD",
|
|
"CWD",
|
|
"PASV",
|
|
"LIST",
|
|
"\0" // SENTRY!
|
|
};
|
|
|
|
// THIS MUST REMAIN LOCAL!
|
|
typedef enum { // ordering must be the same as in the string version above
|
|
USER,
|
|
QUIT,
|
|
PORT,
|
|
TYPE,
|
|
MODE,
|
|
STRU,
|
|
RETR,
|
|
STOR,
|
|
NOOP,
|
|
|
|
// ------ additional commands
|
|
PWD,
|
|
CWD,
|
|
PASV,
|
|
LIST
|
|
} Ftp_ImplementedCmds;
|
|
|
|
// -----------
|
|
|
|
static Ftps_State s; // TODO: dynamically allocate this later
|
|
|
|
// TODO deinit!
|
|
// TODO ambiguous naming between server and client connections
|
|
|
|
int ftps_ctrl_recv(cbd d) {
|
|
uint32_t event = FTPEVT_CTRL_RECVD;
|
|
ETHLIB_OS_QUEUE_PUSH(s.eventQ, &event);
|
|
return 0;
|
|
}
|
|
|
|
int ftps_ctrl_accept_cb(cbd d) {
|
|
s.connCtrl = d;
|
|
uint32_t event = FTPEVT_NEW_CONNECTION;
|
|
ETHLIB_OS_QUEUE_PUSH(s.eventQ, &event);
|
|
return 0;
|
|
}
|
|
|
|
void ftps_thread(void *param);
|
|
|
|
void ftps_init() {
|
|
osThreadAttr_t attr;
|
|
memset(&attr, 0, sizeof(osThreadAttr_t));
|
|
attr.stack_size = 3072;
|
|
attr.priority = osPriorityNormal;
|
|
attr.name = "ftp";
|
|
osThreadNew(ftps_thread, NULL, &attr);
|
|
}
|
|
|
|
char *ftps_get_word(char *word, char *str, uint32_t maxLen) {
|
|
char *iter = str;
|
|
uint32_t len = 0;
|
|
|
|
// skip leading whitespaces
|
|
while ((*iter <= ' ') && (*iter != '\0')) {
|
|
iter++;
|
|
}
|
|
|
|
// look for end of word, and copy until copy size is reached
|
|
while (*iter > ' ') {
|
|
if (len < maxLen) {
|
|
word[len] = *iter;
|
|
}
|
|
|
|
len++;
|
|
iter++;
|
|
}
|
|
word[len] = '\0'; // insert null termination
|
|
|
|
// return the position of the next whitespace
|
|
return iter;
|
|
}
|
|
|
|
bool ftps_is_command_implemented(char *cmd, uint32_t len, uint32_t *cmdCode) {
|
|
char *iter = ftpImplementedCmdsStr[0];
|
|
bool ret = false;
|
|
uint8_t index = 0;
|
|
while (*iter != '\0') {
|
|
// compare passed command with the iterator value
|
|
if (len == 0) {
|
|
if (!strcmp(iter, cmd)) {
|
|
ret = true;
|
|
break;
|
|
}
|
|
} else {
|
|
if (!strncmp(iter, cmd, len)) {
|
|
ret = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// select next command
|
|
iter = ftpImplementedCmdsStr[++index];
|
|
}
|
|
|
|
// if cmdCode pointer is provided, then write back the command code
|
|
if (ret && (cmdCode != NULL)) {
|
|
*cmdCode = index;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void ftps_rtrim(char *str) {
|
|
char *iter = str + strlen(str) - 1;
|
|
while ((*iter <= ' ') && (iter >= str)) {
|
|
*iter = '\0';
|
|
iter--;
|
|
}
|
|
}
|
|
|
|
// data reception on data port
|
|
int ftps_data_recv(cbd d) {
|
|
return 0;
|
|
}
|
|
|
|
// accept new data connection
|
|
int ftps_data_accept_cb(cbd d) {
|
|
s.connData = d; // set data connection ID
|
|
tcp_debug(d, true);
|
|
ETHLIB_OS_SEM_POST(s.dataConnHasOpened); // release data connection semaphore
|
|
return 0;
|
|
}
|
|
|
|
#define FTPS_TYPE_ASCII 'A'
|
|
#define FTPS_TYPE_IMAGE 'I'
|
|
|
|
void ftps_process_event(uint32_t event) {
|
|
const char *resp = NULL; // response towards the FTP client
|
|
char *cmdArg = NULL; // command argument
|
|
int cmdCode = -1; // command code fetched from the string command
|
|
|
|
// is something is received, then read it
|
|
if (event == FTPEVT_CTRL_RECVD) {
|
|
// read into the linebuffer
|
|
uint32_t recvLen = tcp_recv(s.connCtrl, (uint8_t *)s.lineBuffer, FTPS_LINEBUF_LEN);
|
|
s.lineBuffer[recvLen] = '\0';
|
|
|
|
// if recv len is zero in dicating that the connection was closed, by the peer,
|
|
// then reset the server state
|
|
if (recvLen == 0) {
|
|
s.connCtrl = 0;
|
|
s.connState = FTPS_IDLE;
|
|
// TODO: close data connection as well
|
|
return;
|
|
}
|
|
|
|
// right trim the line
|
|
ftps_rtrim(s.lineBuffer);
|
|
|
|
// MSG("%s\n", s.lineBuffer);
|
|
|
|
// get first word: get pointer to first space and calculate compare length
|
|
char *spacePos = strchr(s.lineBuffer, ' ');
|
|
uint32_t compareLen = (spacePos == NULL) ? 0 : (spacePos - s.lineBuffer);
|
|
|
|
// determine if we support this command or not
|
|
bool cmdImpl = ftps_is_command_implemented(s.lineBuffer, compareLen, (uint32_t *)&cmdCode);
|
|
|
|
// if command is not implemented, then send the "not implemented" answer and return from this function
|
|
if (!cmdImpl) {
|
|
resp = ftpNotImplemented;
|
|
goto resp_and_return; // yes, goto, but it's efficient
|
|
}
|
|
|
|
// if command is implemented, then set the argument pointer
|
|
cmdArg = spacePos + 1; // the argument begins right after the first space
|
|
}
|
|
|
|
// ------
|
|
|
|
if (s.connState == FTPS_IDLE) { // IDLE...
|
|
if (event == FTPEVT_NEW_CONNECTION) { // ...and there's an incoming connection
|
|
// send greeting to the client
|
|
resp = ftpGreeting;
|
|
|
|
// set state to logging in
|
|
s.connState = FTPS_LOGGING_IN;
|
|
}
|
|
} else if (s.connState == FTPS_LOGGING_IN) {
|
|
if (cmdCode == USER) { // if user command is issued, the login, no username or password checking
|
|
// send login ok
|
|
resp = ftpLoginOK;
|
|
|
|
// switch to established state
|
|
s.connState = FTPS_ESTABLISHED;
|
|
}
|
|
} else if (s.connState == FTPS_ESTABLISHED) {
|
|
switch (cmdCode) {
|
|
case PWD: // print working directory name
|
|
resp = ftpRootDirName;
|
|
break;
|
|
case CWD:
|
|
resp = ftpFileActionCplt;
|
|
break;
|
|
case TYPE: { // set transfer format
|
|
char typeChar[2]; // get type character
|
|
ftps_get_word(typeChar, cmdArg, 1); // (first parameter)
|
|
|
|
// if no parameter was defined
|
|
if (typeChar[0] == '\0') {
|
|
goto syntax_error;
|
|
}
|
|
|
|
// if parameter is defined, then act accordingly
|
|
switch (typeChar[0]) {
|
|
case FTPS_TYPE_ASCII: // ASCII
|
|
case FTPS_TYPE_IMAGE: // Image (binary)
|
|
// I don't know if there's any difference between ASCII and Image transfers, since bytes are 8-bit long
|
|
goto cmd_ok;
|
|
break;
|
|
default: // other formats are not implemented
|
|
goto cmd_param_not_implemented;
|
|
break;
|
|
}
|
|
|
|
} break;
|
|
case PASV: { // enter passive mode
|
|
if (!s.passiveMode) {
|
|
// start data connection listener socket
|
|
uint16_t port;
|
|
do { // randomize a port number not in use currently
|
|
port = (rand() % (0xFFFF - 1024)) + 1024; // it's a nice practice if we pick a port number greater than 1024
|
|
s.connDataS = tcp_new_connblock(s.intf, IPv4_IF_ADDR, port, ftps_data_recv);
|
|
} while (s.connDataS == 0); // retry if connection block could not be opened
|
|
|
|
// convert to server socket
|
|
tcp_set_accept_callback(s.connDataS, ftps_data_accept_cb);
|
|
tcp_listen(s.connDataS);
|
|
|
|
MSG("port: %u\n", port);
|
|
|
|
// turn passive mode on and save passive mode port
|
|
s.passiveMode = true;
|
|
s.passiveModePort = port;
|
|
}
|
|
|
|
// compile response message
|
|
SNPRINTF(ftpsDynResp, FTPS_DYNAMIC_RESPONSE_MAXLEN, "%s (%u,%u,%u,%u,%u,%u)\r\n", // no space in the whole address-port compound
|
|
ftpEnteringPassivePrefix, EXPLODE_IPv4(s.intf->ip), (s.passiveModePort >> 8) & 0xFF, s.passiveModePort & 0xFF);
|
|
|
|
// set response
|
|
resp = ftpsDynResp;
|
|
} break;
|
|
case LIST: { // list files
|
|
ETHLIB_OS_SEM_WAIT(s.dataConnHasOpened); // wait for data connection to establish
|
|
tcp_send(s.connCtrl, (const uint8_t *)ftpOpeningDataConn, strlen(ftpOpeningDataConn)); // opening data connection
|
|
tcp_send(s.connData, (const uint8_t *)"\r\n", 2); // sending listing data
|
|
tcp_send(s.connCtrl, (const uint8_t *)ftpClosingDataConn, strlen(ftpClosingDataConn)); // closing data connection
|
|
close_connection(s.connData); // actually close connection
|
|
s.connData = 0;
|
|
resp = NULL; // clear data connection CBD
|
|
} break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ------
|
|
|
|
resp_and_return:
|
|
// send response if instructed
|
|
if (resp != NULL) {
|
|
tcp_send(s.connCtrl, (const uint8_t *)resp, strlen(resp));
|
|
}
|
|
return;
|
|
|
|
// -----
|
|
syntax_error:
|
|
resp = ftpSyntaxError;
|
|
goto resp_and_return;
|
|
|
|
cmd_param_not_implemented:
|
|
resp = ftpCmdNotImplForParam;
|
|
goto resp_and_return;
|
|
|
|
cmd_ok:
|
|
resp = ftpCmdOk;
|
|
goto resp_and_return;
|
|
}
|
|
|
|
void ftps_thread(void *param) {
|
|
// set interface
|
|
s.intf = get_default_interface();
|
|
|
|
// initialize event queue
|
|
s.eventQ = ETHLIB_OS_QUEUE_CREATE(FTPS_EVENT_QUEUE_LENGTH, uint32_t);
|
|
|
|
// initialize control connection
|
|
s.connCtrlS = tcp_new_connblock(s.intf, IPv4_IF_ADDR, FTPS_CTRL_PORT, ftps_ctrl_recv);
|
|
tcp_set_accept_callback(s.connCtrlS, ftps_ctrl_accept_cb); // set callback for accepting an incoming connection
|
|
tcp_listen(s.connCtrlS); // make it listen for incoming connections
|
|
|
|
// initialize connection state
|
|
s.connState = FTPS_IDLE;
|
|
s.passiveMode = false; // at the beginning we disable passive mode
|
|
s.passiveModePort = 0;
|
|
|
|
// initialize semaphore which synchronizes data connection state
|
|
s.dataConnHasOpened = ETHLIB_OS_SEM_CREATE(1);
|
|
|
|
// process loop
|
|
for (;;) {
|
|
// wait for some connection event
|
|
uint32_t event = FTPEVT_NONE;
|
|
ETHLIB_OS_QUEUE_POP(s.eventQ, &event);
|
|
|
|
// execute event process
|
|
ftps_process_event(event);
|
|
}
|
|
} |