EtherLib/apps/ftp_server.c
2024-04-23 23:31:41 +02:00

213 lines
5.7 KiB
C

#include "ftp_server.h"
#include <stdbool.h>
#include <etherlib/prefab/conn_blocks/tcp_connblock.h>
// ----------
/* FTP response messages */
static char ftpGreeting[] = "220 Service ready for new user.\r\n";
static char ftpNotImplemented[] = "502 Command not implemented.\r\n";
static char ftpLoginOK[] = "230 User logged in, proceed.\r\n";
static char ftpRootDirName[] = "257 \"/\" is the current directory\r\n";
/* 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",
"\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
} 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_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 = 2048;
attr.priority = osPriorityNormal;
attr.name = "ftp";
osThreadNew(ftps_thread, NULL, &attr);
}
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--;
}
}
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';
// 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 TYPE: //
default:
break;
}
}
// ------
resp_and_return:
// send response if instructed
if (resp != NULL) {
tcp_send(s.connCtrl, (const uint8_t *)resp, strlen(resp));
}
}
void ftps_thread(void *param) {
// initialize event queue
s.eventQ = ETHLIB_OS_QUEUE_CREATE(FTPS_EVENT_QUEUE_LENGTH, uint32_t);
// initialize control connection
s.connCtrlS = tcp_new_connblock(get_default_interface(), IPv4_IF_ADDR, FTPS_CTRL_PORT, ftps_ctrl_recv);
tcp_listen(s.connCtrlS); // make it listen for incoming connections
tcp_set_accept_callback(s.connCtrlS, ftps_accept_cb); // set callback for accepting an incoming connection
// initialize connection state
s.connState = FTPS_IDLE;
// 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);
}
}