diff --git a/apps/ftp_server.c b/apps/ftp_server.c new file mode 100644 index 0000000..66bb0d8 --- /dev/null +++ b/apps/ftp_server.c @@ -0,0 +1,213 @@ +#include "ftp_server.h" + +#include + +#include + +// ---------- + +/* 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); + } +} \ No newline at end of file diff --git a/apps/ftp_server.h b/apps/ftp_server.h new file mode 100644 index 0000000..987135a --- /dev/null +++ b/apps/ftp_server.h @@ -0,0 +1,45 @@ +#ifndef APPS_FTP_SERVER +#define APPS_FTP_SERVER + +#include +#include + +#define FTPS_CTRL_PORT (21) // FTP server control connection port + +#define FTPS_EVENT_QUEUE_LENGTH (8) // internal event queue length + +#define FTPS_LINEBUF_LEN (63) + +#define FTPS_ROOT_DIR_NAME "ftp_root" + +/** + * FTP connection state machine states. +*/ +typedef enum { + FTPS_IDLE, // the server is IDLE + FTPS_LOGGING_IN, // the client is logging in + FTPS_ESTABLISHED // the connection is established +} Ftps_ConnState; + +/** + * FTP connection events. +*/ +typedef enum { + FTPEVT_NONE, // no action + FTPEVT_NEW_CONNECTION, // a new connection was recently initiated + FTPEVT_CTRL_RECVD, // a control message has been received +} Ftps_ConnEvents; + +typedef struct { + cbd connCtrlS; // control connection server + cbd connCtrl; // established control connection + cbd connDataS; // data connection server + cbd connData; // established data connection + Ftps_ConnState connState; // connection state + ETHLIB_OS_QUEUE_TYPE eventQ; // event queue + char lineBuffer[FTPS_LINEBUF_LEN + 1]; // line buffer +} Ftps_State; + +void ftps_init(); + +#endif /* APPS_FTP_SERVER */