213 lines
5.7 KiB
C
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);
|
|
}
|
|
} |