From 3f777d0edfa0ea8413d2c83eacbbda135970cb3c Mon Sep 17 00:00:00 2001 From: Joel Sherrill Date: Fri, 12 Jan 2001 13:51:56 +0000 Subject: 2001-01-12 Sergei Organov * rtems_servers/ftpd.c, rtems_servers/ftpd.h: Major enhancements as listed below: - use pool of pre-created threads to handle sessions instead of creating/deleting threads on the fly - LIST output is now similar to what "/bin/ls -al" would output, thus FTP clients such Netscape are happy with it. - LIST NAME now works (both for files and directories) - added support for NLST, CDUP, and MDTM FTP commands to make more FTP clients happy - keep track of CWD for every session separately - ability to specify root directory name for FTPD in configuration table. FTPD will then create illusion for FTP clients that this is actually root directory. - ignore options sent in commands, thus LIST -al FILE works and doesn't try to list "-al" directory. - buffers are allocated on stack instead of heap where possible to eliminate malloc/free calls (avoid possible heap fragmentation troubles). - drop using of task notepad to pass parameters - use function arguments instead - use snprintf() instead of sprintf() as the latter is unsafe - use of PF_INET in socket() instead of AF_INET Here are ftp clients I've tried new FTPD with (all of them running on Debian GNU/Linux 2.2): Lftp 2.1.10 NcFTP 2.4.3 Netscape 4.75 ftp mc 4.5.49 --- c/src/libnetworking/rtems_servers/ftpd.c | 2090 +++++++++++++++++++----------- 1 file changed, 1358 insertions(+), 732 deletions(-) (limited to 'c/src/libnetworking/rtems_servers/ftpd.c') diff --git a/c/src/libnetworking/rtems_servers/ftpd.c b/c/src/libnetworking/rtems_servers/ftpd.c index aef88c82c7..28e803b628 100644 --- a/c/src/libnetworking/rtems_servers/ftpd.c +++ b/c/src/libnetworking/rtems_servers/ftpd.c @@ -1,12 +1,42 @@ /* FIXME: 1. Parse command is a hack. We can do better. - * 2. chdir is a hack. We can do better. - * 3. PWD doesn't work. - * 4. Some sort of access control? + * 2. Some sort of access control? + * 3. OSV: Timeouts on sockets. + * 4. OSV: hooks support seems to be a joke, as it requires storing of + * entire input file in memory. Seem to be better to change it to + * something more reasonable, like having + * 'hook_write(void const *buf, int count)' routine that will be + * called multiple times while file is being received. + * 5. OSV: Remove hack with "/dev/null"? + * 6. OSV: data_addr field of SessionInfo structure is not initialized. + * This will lead to undefined behavior if FTP client doesn't send + * PORT command. Seems we need to implement usage of default port + * (ftp port - 1). I didn't find any FTP client that doesn't send + * PORT command though. + * 7. OSV: while server claims TYPE ASCII command succeeds (to make + * happy some clients), ASCII mode isn't actually implemented. + * 8. Passive Mode? * - * FTP Server Daemon + * FTP Server Daemon * * Submitted by: Jake Janovetz * + * Changed by: Sergei Organov (OSV) + * Changes: + * - use pool of pre-created threads to handle sessions + * - LIST output now similar to what "/bin/ls -al" would output, thus + * FTP clients could parse it. + * - LIST NAME now works (both for files and directories) + * - keep track of CWD for every session separately + * - ability to specify root directory name in configuration table + * - options sent in commands are ignored, thus LIST -al FILE works + * - added support for NLST, CDUP and MDTM commands + * - buffers are allocated on stack instead of heap where possible + * - drop using of task notepad to pass parameters - use function + * arguments instead + * - various bug-fixes, e.g., use of PF_INET in socket() instead of + * AF_INET, use snprintf() instead of sprintf() everywhere for safety, + * etc. + * * $Id$ */ @@ -24,11 +54,12 @@ * * * Organization: * * * - * The FTP daemon is started upon boot. It runs all the time * - * and waits for connections on the known FTP port (21). When * - * a connection is made, it starts a 'session' task. That * + * The FTP daemon is started upon boot along with a (configurable) * + * number of tasks to handle sessions. It runs all the time and * + * waits for connections on the known FTP port (21). When * + * a connection is made, it wakeups a 'session' task. That * * session then interacts with the remote host. When the session * - * is complete, the session task deletes itself. The daemon still * + * is complete, the session task goes to sleep. The daemon still * * runs, however. * * * * * @@ -37,7 +68,7 @@ * RETR xxx - Sends a file from the client. * * STOR xxx - Receives a file from the client. xxx = filename. * * LIST xxx - Sends a file list to the client. * - * (LIST xxx isn't working yet...) * + * NLST xxx - Sends a file list to the client. * * USER - Does nothing. * * PASS - Does nothing. * * SYST - Replies with the system type (`RTEMS'). * @@ -46,29 +77,18 @@ * RMD xxx - Remove directory xxx. * * PWD - Print working directory. * * CWD xxx - Change working directory. * + * CDUP - Change to upper directory. * * SITE CHMOD xxx yyy - Change permissions on file yyy to xxx. * * PORT a,b,c,d,x,y - Setup for a data port to IP address a.b.c.d * * and port (x*256 + y). * + * MDTM xxx - Send date/time to the client. xxx = filename. * * * * * * The public routines contained in this file are: * * * - * rtems_initialize_ftpd_start - Starts the server daemon, then * - * returns to its caller. * - * * - * * - * The private routines contained in this file are: * + * rtems_initialize_ftpd - Initializes and starts the server daemon, * + * then returns to its caller. * * * - * rtems_ftpd_send_reply - Sends a reply code and text through the * - * control port. * - * rtems_ftpd_command_retrieve - Performs to "RETR" command. * - * rtems_ftpd_command_store - Performs the "STOR" command. * - * rtems_ftpd_command_list - Performs the "LIST" command. * - * rtems_ftpd_command_port - Opens a data port (the "PORT" command). * - * rtems_ftpd_parse_command - Parses an incoming command. * - * rtmes_ftpd_session - Begins a service session. * - * rtems_ftpd_daemon - Listens on the FTP port for service * - * requests. * *------------------------------------------------------------------------* * Jake Janovetz * * University of Illinois * @@ -76,31 +96,10 @@ * Urbana IL 61801 * ************************************************************************** * Change History: * - * 12/01/97 - Creation (JWJ) * + * 12/01/97 - Creation (JWJ) * + * 2001-01-08 - Changes by OSV * *************************************************************************/ -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "ftpd.h" - - -extern struct rtems_ftpd_configuration rtems_ftpd_configuration; - /************************************************************************** * Meanings of first and second digits of reply codes: * @@ -114,7 +113,7 @@ extern struct rtems_ftpd_configuration rtems_ftpd_configuration; * 4yz Transient negative completion reply. The requested action did * not take place, but the error condition is temporary so the * command can be reissued later. - * 5yz Permanent negative completion reply. The command was not + * 5yz Permanent negative completion reply. The command was not * accepted and should not be retried. *------------------------------------------------------------------------- * x0z Syntax errors. @@ -127,32 +126,515 @@ extern struct rtems_ftpd_configuration rtems_ftpd_configuration; * x5z Filesystem status. *************************************************************************/ +#include +#include +#include +#include +#include +#include +#include -/************************************************************************** +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ftpd.h" + + +#ifdef __GNUC__ +/* change to #if 1 to disable syslog entirely */ +#if 0 +#undef syslog +#define syslog(a, b, ...) while(0){} +#endif +#endif + +#define FTPD_SERVER_MESSAGE "RTEMS FTP server (Version 1.1-JWJ) ready." + +#define FTPD_SYSTYPE "RTEMS" + +/* Seem to be unused */ +#if 0 +#define FTPD_WELCOME_MESSAGE \ + "Welcome to the RTEMS FTP server.\n" \ + "\n" \ + "Login accepted.\n" +#endif + +/* Various buffer sizes */ +enum +{ + FTPD_BUFSIZE = 256, /* Size for temporary buffers */ + FTPD_DATASIZE = 1024, /* Size for file transfer buffers */ + FTPD_STACKSIZE = 8 * 1024, /* Tasks stack size */ +}; + +/* Event to be used by session tasks for waiting */ +enum +{ + FTPD_RTEMS_EVENT = RTEMS_EVENT_1 +}; + +/* Configuration table */ +extern struct rtems_ftpd_configuration rtems_ftpd_configuration; + +/* this is not prototyped in strict ansi mode */ +FILE *fdopen (int fildes, const char *mode); + +/* * SessionInfo structure. * - * The following structure is allocated for each session. The pointer - * to this structure is contained in the tasks notepad entry. - *************************************************************************/ + * The following structure is allocated for each session. + */ typedef struct { - struct sockaddr_in data_addr; /* Data address for PORT commands */ - FILE *ctrl_fp; /* File pointer for control connection */ - char cwd[255]; /* Current working directory */ - /* Login -- future use -- */ - int xfer_mode; /* Transfer mode (ASCII/binary) */ + struct sockaddr_in data_addr; /* Data address for PORT commands */ + FILE *ctrl_fp; /* File pointer for control connection */ + int socket; /* Socket descriptor for ctrl connection */ + char cwd[FTPD_BUFSIZE]; /* Current working directory */ + /* Login -- future use -- */ + int xfer_mode; /* Transfer mode (ASCII/binary) */ + rtems_id tid; /* Task id */ } FTPD_SessionInfo_t; -#define FTPD_SERVER_MESSAGE "RTEMS FTP server (Version 1.0-JWJ) ready." -#define FTPD_WELCOME_MESSAGE \ - "Welcome to the RTEMS FTP server.\n" \ - "\n" \ - "Login accepted.\n" +/* + * TaskPool structure. + */ +typedef struct +{ + FTPD_SessionInfo_t *info; + FTPD_SessionInfo_t **queue; + int count; + int head; + int tail; + rtems_id mutex; + rtems_id sem; +} FTPD_TaskPool_t; + +/* + * Task pool instance. + */ +static FTPD_TaskPool_t task_pool; + +/* + * Root node for FTPD without trailing slash. Even '/' node is denoted as + * empty string here. + */ +static char root[FTPD_BUFSIZE]; + + +/*PAGE + * + * serr + * + * Return errror string corresponding to current 'errno'. + * + */ +static char const* +serr(void) +{ + return strerror(errno); +} + + +/* + * Utility routines to manage root directory and session local + * current working directory. + */ + + +/*PAGE + * + * squeeze_path + * + * Squeezes path according to OS rules, i.e., eliminates /./, /../, and // + * from the path. Does nothing if the path is relative, i.e. doesn't begin + * with '/'. The trailing slash is always removed, even when alone, i.e. "/" + * will be "" after squeeze. + * + * Input parameters: + * path - the path to be squeezed + * + * Output parameters: + * path - squeezed path + * + */ +static void +squeeze_path(char* path) +{ + if(path[0] == '/') + { + char* e = path + 1; + int rest = strlen(e); + while(rest >= 0) + { + int len; + char* s = e; + e = strchr(s, '/'); + if(e) + ++e; + else + e = s + rest + 1; + len = e - s; + rest -= len; + if(len == 1 || (len == 2 && s[0] == '.')) + { + if(rest >= 0) + memmove(s, e, rest + 1); + else + *s++ = '\0'; + e = s; + } + else if(len == 3 && s[0] == '.' && s[1] == '.') + { + char* ps = s; + if(ps - 1 > path) { + do + --ps; + while(ps[-1] != '/'); + } + if(rest >= 0) + memmove(ps, e, rest + 1); + else + *ps++ = '\0'; + e = ps; + } + } + if(e[-2] == '/') + e[-2] = '\0'; + } +} + + +/*PAGE + * + * make_path + * + * Makes full path given file name, current working directory and root + * directory (file scope variable 'root'). + * + * Input parameters: + * cwd - current working directory + * name - file name + * root (file scope variable) - FTPD root directory + * + * Output parameters: + * buf - full path + * returns pointer to non-root part of the 'buf', i.e. to first character + * different from '/' after root part. + * + */ +static char const* +make_path(char* buf, char const* cwd, char const* name) +{ + char* res = NULL; + + int rlen = strlen(root); + int clen = strlen(cwd); + int nlen = strlen(name); + int len = rlen + nlen; + + if (name[0] != '/') + { + ++len; + if (clen > 0) + len += clen + 1; + } + + if (FTPD_BUFSIZE > len) + { + char* b = buf; + memcpy(b, root, rlen); b += rlen; + if (name[0] != '/') + { + *b++ = '/'; + if (clen > 0) + { + memcpy(b, cwd, clen); b += clen; + *b++ = '/'; + } + } + memcpy(b, name, nlen); b += nlen; + *b = '\0'; + + res = buf + rlen; + while(rlen-- > 0 && res[-1] == '/') + --res; + squeeze_path(res); + } + + return res; +} + +/* + * Task pool management routines + */ + + +/*PAGE + * + * task_pool_done + * + * Cleanup task pool. + * + * Input parameters: + * count - number of entries in task pool to cleanup + * + * Output parameters: + * NONE + * + */ +static void +task_pool_done(int count) +{ + int i; + for(i = 0; i < count; ++i) + rtems_task_delete(task_pool.info[i].tid); + if(task_pool.info) + free(task_pool.info); + if(task_pool.queue) + free(task_pool.queue); + if(task_pool.mutex != (rtems_id)-1) + rtems_semaphore_delete(task_pool.mutex); + if(task_pool.sem != (rtems_id)-1) + rtems_semaphore_delete(task_pool.sem); + task_pool.info = 0; + task_pool.queue = 0; + task_pool.count = 0; + task_pool.sem = -1; + task_pool.mutex = -1; +} + +/* Forward declare */ +static void session(rtems_task_argument arg); + +/*PAGE + * + * task_pool_init + * + * Initialize task pool. + * + * Input parameters: + * count - number of entries in task pool to create + * priority - priority tasks are started with + * + * Output parameters: + * returns 1 on success, 0 on failure. + * + */ +static int +task_pool_init(int count, rtems_task_priority priority) +{ + int i; + rtems_status_code sc; + char id = 'a'; + + task_pool.count = 0; + task_pool.head = task_pool.tail = 0; + task_pool.mutex = (rtems_id)-1; + task_pool.sem = (rtems_id)-1; + + sc = rtems_semaphore_create( + rtems_build_name('F', 'T', 'P', 'M'), + 1, + RTEMS_DEFAULT_ATTRIBUTES + | RTEMS_BINARY_SEMAPHORE + | RTEMS_INHERIT_PRIORITY + | RTEMS_PRIORITY, + RTEMS_NO_PRIORITY, + &task_pool.mutex); + + if(sc == RTEMS_SUCCESSFUL) + sc = rtems_semaphore_create( + rtems_build_name('F', 'T', 'P', 'S'), + count, + RTEMS_DEFAULT_ATTRIBUTES, + RTEMS_NO_PRIORITY, + &task_pool.sem); + + if(sc != RTEMS_SUCCESSFUL) { + task_pool_done(0); + syslog(LOG_ERR, "ftpd: Can not create semaphores"); + return 0; + } + + task_pool.info = (FTPD_SessionInfo_t*) + malloc(sizeof(FTPD_SessionInfo_t) * count); + task_pool.queue = (FTPD_SessionInfo_t**) + malloc(sizeof(FTPD_SessionInfo_t*) * count); + if (NULL == task_pool.info || NULL == task_pool.queue) + { + task_pool_done(0); + syslog(LOG_ERR, "ftpd: Not enough memory"); + return 0; + } + + for(i = 0; i < count; ++i) + { + FTPD_SessionInfo_t *info = &task_pool.info[i]; + sc = rtems_task_create(rtems_build_name('F', 'T', 'P', id), + priority, FTPD_STACKSIZE, + RTEMS_PREEMPT | RTEMS_NO_TIMESLICE | + RTEMS_NO_ASR | RTEMS_INTERRUPT_LEVEL(0), + RTEMS_NO_FLOATING_POINT | RTEMS_LOCAL, + &info->tid); + if (sc == RTEMS_SUCCESSFUL) + { + sc = rtems_task_start( + info->tid, session, (rtems_task_argument)info); + if (sc != RTEMS_SUCCESSFUL) + task_pool_done(i); + } + else + task_pool_done(i + 1); + if (sc != RTEMS_SUCCESSFUL) + { + syslog(LOG_ERR, "ftpd: Could not create/start FTPD session: %s", + rtems_status_text(sc)); + return 0; + } + task_pool.queue[i] = task_pool.info + i; + info->ctrl_fp = NULL; + info->socket = -1; + if (++id > 'z') + id = 'a'; + } + task_pool.count = count; + return 1; +} + +/*PAGE + * + * task_pool_obtain + * + * Obtain free task from task pool. + * + * Input parameters: + * NONE + * + * Output parameters: + * returns pointer to the corresponding SessionInfo structure on success, + * NULL if there are no free tasks in the pool. + * + */ +static FTPD_SessionInfo_t* +task_pool_obtain() +{ + FTPD_SessionInfo_t* info = 0; + rtems_status_code sc; + sc = rtems_semaphore_obtain(task_pool.sem, RTEMS_NO_WAIT, RTEMS_NO_TIMEOUT); + if (sc == RTEMS_SUCCESSFUL) + { + rtems_semaphore_obtain(task_pool.mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT); + info = task_pool.queue[task_pool.head]; + if(++task_pool.head >= task_pool.count) + task_pool.head = 0; + info->socket = -1; + info->ctrl_fp = NULL; + rtems_semaphore_release(task_pool.mutex); + } + return info; +} + +/*PAGE + * + * task_pool_release + * + * Return task obtained by 'obtain()' back to the task pool. + * + * Input parameters: + * info - pointer to corresponding SessionInfo structure. + * + * Output parameters: + * NONE + * + */ +static void +task_pool_release(FTPD_SessionInfo_t* info) +{ + rtems_semaphore_obtain(task_pool.mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT); + task_pool.queue[task_pool.tail] = info; + if(++task_pool.tail >= task_pool.count) + task_pool.tail = 0; + rtems_semaphore_release(task_pool.mutex); + rtems_semaphore_release(task_pool.sem); +} + +/* + * End of task pool routines + */ + +/*PAGE + * + * close_socket + * + * Close socket. + * + * Input parameters: + * s - socket descriptor to be closed. + * + * Output parameters: + * returns 1 on success, 0 on failure + * + */ +static int +close_socket(int s) +{ + if (0 <= s) + { + if (0 != close(s)) + { + shutdown(s, 2); + if (0 != close(s)) + return 0; + } + } + return 1; +} + +/*PAGE + * + * close_stream + * + * Close data stream of session. + * + * Input parameters: + * info - corresponding SessionInfo structure + * + * Output parameters: + * NONE + * + */ +static void +close_stream(FTPD_SessionInfo_t* info) +{ + if (NULL != info->ctrl_fp) + { + if (0 != fclose(info->ctrl_fp)) + { + syslog(LOG_ERR, "ftpd: Could not close control stream: %s", serr()); + } + else + info->socket = -1; + } + + if (!close_socket(info->socket)) + syslog(LOG_ERR, "ftpd: Could not close control socket: %s", serr()); + + info->ctrl_fp = NULL; + info->socket = -1; +} /************************************************************************** - * Function: rtems_ftpd_send_reply * + * Function: send_reply * ************************************************************************** * Description: * * * @@ -169,49 +651,87 @@ typedef struct * * * none * * * - ************************************************************************** - * Change History: * - * 12/01/97 - Creation (JWJ) * *************************************************************************/ static void -rtems_ftpd_send_reply(int code, char *text) +send_reply(FTPD_SessionInfo_t *info, int code, char *text) { - rtems_status_code sc; - FTPD_SessionInfo_t *info = NULL; - char str[80]; - + char const* s; + + + /*********************************************************************** + * code must be a 3-digit number. + **********************************************************************/ + if ((code < 100) || (code > 999)) + { + syslog(LOG_ERR, "ftpd: Code not 3-digits."); + return; + } + + /*********************************************************************** + * If a text reply exists, add it to the reply data. + **********************************************************************/ + s = (info->xfer_mode == TYPE_A) ? "\r" : ""; + if (text != NULL) + fprintf(info->ctrl_fp, "%d %.70s%s\n", code, text, s); + else + fprintf(info->ctrl_fp, "%d%s\n", code, s); + fflush(info->ctrl_fp); +} - sc = rtems_task_get_note(RTEMS_SELF, RTEMS_NOTEPAD_0, - (rtems_unsigned32 *)&info); - /*********************************************************************** - * code must be a 3-digit number. - **********************************************************************/ - if ((code < 100) || (code > 999)) - { - syslog(LOG_ERR, "ftpd: Code not 3-digits."); - return; - } - - /*********************************************************************** - * If a text reply exists, add it to the reply data. - **********************************************************************/ - if (text != NULL) - { - sprintf(str, "%d %.70s\r\n", code, text); - fprintf(info->ctrl_fp, "%d %.70s\r\n", code, text); - } - else - { - sprintf(str, "%d\r\n", code); - fprintf(info->ctrl_fp, "%d\r\n", code); - } - fflush(info->ctrl_fp); +/*PAGE + * + * send_mode_reply + * + * Sends BINARY/ASCII reply string depending on current transfer mode. + * + * Input parameters: + * info - corresponding SessionInfo structure + * + * Output parameters: + * NONE + * + */ +static void +send_mode_reply(FTPD_SessionInfo_t *info) +{ + if(info->xfer_mode == TYPE_I) + send_reply(info, 150, "Opening BINARY mode data connection."); + else + send_reply(info, 150, "Opening ASCII mode data connection."); } +/*PAGE + * + * data_socket + * + * Create data socket for session. + * + * Input parameters: + * info - corresponding SessionInfo structure + * + * Output parameters: + * returns socket descriptor, or -1 if failure + * + */ +static int +data_socket(FTPD_SessionInfo_t *info) +{ + int s = socket(PF_INET, SOCK_STREAM, 0); + if(0 > s) + send_reply(info, 420, "Server error - could not create socket."); + else if(0 > connect(s, (struct sockaddr *)&info->data_addr, + sizeof(struct sockaddr))) + { + send_reply(info, 420, "Server error - could not connect socket."); + close_socket(s); + s = -1; + } + return s; +} /************************************************************************** - * Function: rtems_ftpd_command_retrieve * + * Function: command_retrieve * ************************************************************************** * Description: * * * @@ -229,84 +749,67 @@ rtems_ftpd_send_reply(int code, char *text) * int - 0 for reply sent. * * 1 for no reply sent. * * * - ************************************************************************** - * Change History: * - * 04/29/98 - Creation (JWJ) * *************************************************************************/ static int -rtems_ftpd_command_retrieve(char *filename) +command_retrieve(FTPD_SessionInfo_t *info, char *filename) { - int s; - int n; - int fd; - unsigned char *bufr; - rtems_status_code sc; - FTPD_SessionInfo_t *info = NULL; + int s = -1; + int n; + int fd = -1; + char buf[FTPD_DATASIZE]; + int res = 0; + + char const* r = make_path(buf, info->cwd, filename); + + if (NULL == r || 0 > (fd = open(buf, O_RDONLY))) + { + send_reply(info, 450, "Error opening file."); + return(res); + } + + send_mode_reply(info); + + /*********************************************************************** + * Connect to the data connection (PORT made in an earlier PORT call). + **********************************************************************/ + s = data_socket(info); + if (0 <= s) + { + /*********************************************************************** + * Send the data over the ether. + **********************************************************************/ + while ((n = read(fd, buf, FTPD_DATASIZE)) > 0) + { + send(s, buf, n, 0); + } + + if (0 == n) + { + if (0 == close(fd)) + { + fd = -1; + res = 1; + } + } + } + if (0 == res) + send_reply(info, 450, "Retrieve failed."); + else + send_reply(info, 210, "File sent successfully."); - sc = rtems_task_get_note(RTEMS_SELF, RTEMS_NOTEPAD_0, - (rtems_unsigned32 *)&info); + if (-1 != fd) + close(fd); - if ((fd = open(filename, O_RDONLY)) == -1) - { - rtems_ftpd_send_reply(450, "Error opening file."); - return(0); - } + if (!close_socket(s)) + syslog(LOG_ERR, "ftpd: Error closing data socket"); - bufr = (unsigned char *)malloc(BUFSIZ); - if (bufr == NULL) - { - rtems_ftpd_send_reply(440, "Server error - malloc fail."); - close(fd); - return(0); - } - - /*********************************************************************** - * Connect to the data connection (PORT made in an earlier PORT call). - **********************************************************************/ - rtems_ftpd_send_reply(150, "BINARY data connection."); - s = socket(AF_INET, SOCK_STREAM, 0); - if (connect(s, (struct sockaddr *)&info->data_addr, - sizeof(struct sockaddr)) < 0) - { - rtems_ftpd_send_reply(420, "Server error - could not connect socket."); - free(bufr); - close(fd); - close(s); - return(1); - } - - /*********************************************************************** - * Send the data over the ether. - **********************************************************************/ - while ((n = read(fd, bufr, BUFSIZ)) > 0) - { - send(s, bufr, n, 0); - bufr[n-1] = '\0'; - } - - if (n == 0) - { - rtems_ftpd_send_reply(210, "File sent successfully."); - } - else - { - rtems_ftpd_send_reply(450, "Retrieve failed."); - } - - if (close(s) != 0) - { - syslog(LOG_ERR, "ftpd: Error closing data socket"); - } - - free(bufr); - close(fd); - return(0); + return(res); } /************************************************************************** - * Function: rtems_ftpd_command_store * + * Function: command_store * ************************************************************************** * Description: * * * @@ -324,159 +827,251 @@ rtems_ftpd_command_retrieve(char *filename) * int - 0 for success. * * 1 for failure. * * * - ************************************************************************** - * Change History: * - * 12/01/97 - Creation (JWJ) * *************************************************************************/ static int -rtems_ftpd_command_store(char *filename) +command_store(FTPD_SessionInfo_t *info, char *filename) { - char *bufr; - int s; - int n; - unsigned long size = 0; - rtems_status_code sc; - FTPD_SessionInfo_t *info = NULL; - struct rtems_ftpd_hook *usehook = NULL; - - - sc = rtems_task_get_note(RTEMS_SELF, RTEMS_NOTEPAD_0, - (rtems_unsigned32 *)&info); - - bufr = (char *)malloc(BUFSIZ * sizeof(char)); - if (bufr == NULL) - { - rtems_ftpd_send_reply(440, "Server error - malloc fail."); - return(1); - } - - rtems_ftpd_send_reply(150, "BINARY data connection."); - - s = socket(AF_INET, SOCK_STREAM, 0); - if (connect(s, (struct sockaddr *)&info->data_addr, - sizeof(struct sockaddr)) < 0) - { - free(bufr); - close(s); - return(1); - } - - - /*********************************************************************** - * File: "/dev/null" just throws the data away. - * Otherwise, search our list of hooks to see if we need to do something - * special. - **********************************************************************/ - if (!strncmp("/dev/null", filename, 9)) - { - while ((n = read(s, bufr, BUFSIZ)) > 0); - } - else if (rtems_ftpd_configuration.hooks != NULL) - { - struct rtems_ftpd_hook *hook; - int i; - - i = 0; - hook = &rtems_ftpd_configuration.hooks[i++]; - while (hook->filename != NULL) - { - if (!strcmp(hook->filename, filename)) - { - usehook = hook; - break; - } - hook = &rtems_ftpd_configuration.hooks[i++]; - } - } - - if (usehook != NULL) - { - char *bigBufr; - - /*********************************************************************** - * Allocate space for our "file". - **********************************************************************/ - bigBufr = (char *)malloc( - rtems_ftpd_configuration.max_hook_filesize * sizeof(char)); - if (bigBufr == NULL) + int s; + int n; + unsigned long size = 0; + struct rtems_ftpd_hook *usehook = NULL; + char buf[FTPD_DATASIZE]; + + int null = !strcmp("/dev/null", filename); + + if(!null) + { + if (NULL == make_path(buf, info->cwd, filename)) + { + send_reply(info, 450, "Error creating file."); + return(0); + } + } + + send_mode_reply(info); + + s = data_socket(info); + if(0 > s) + { + return(1); + } + + + /*********************************************************************** + * File: "/dev/null" just throws the data away. + * Otherwise, search our list of hooks to see if we need to do something + * special. + * OSV: FIXME: this is hack. Using /dev/null filesystem entry would be + * better. However, it's not clear how to handle root directory + * other than '/' then. + **********************************************************************/ + if (null) + { + while ((n = read(s, buf, FTPD_DATASIZE)) > 0); + } + else if (rtems_ftpd_configuration.hooks != NULL) + { + struct rtems_ftpd_hook *hook; + int i; + + i = 0; + hook = &rtems_ftpd_configuration.hooks[i++]; + while (hook->filename != NULL) + { + if (!strcmp(hook->filename, buf)) { - rtems_ftpd_send_reply(440, "Server error - malloc fail."); - free(bufr); - return(1); + usehook = hook; + break; } + hook = &rtems_ftpd_configuration.hooks[i++]; + } + } + + if (usehook != NULL) + { + /* + * OSV: FIXME: Small buffer could be used and hook routine + * called multiple times instead. Alternatively, the support could be + * removed entirely in favor of configuring RTEMS pseudo-device with + * given name. + */ + + char *bigBufr; + size_t filesize = rtems_ftpd_configuration.max_hook_filesize + 1; + + /*********************************************************************** + * Allocate space for our "file". + **********************************************************************/ + bigBufr = (char *)malloc(filesize); + if (bigBufr == NULL) + { + send_reply(info, 440, "Server error - malloc fail."); + return(1); + } + + /*********************************************************************** + * Retrieve the file into our buffer space. + **********************************************************************/ + size = 0; + while ((n = read(s, bigBufr + size, filesize - size)) > 0) + { + size += n; + } + if (size >= filesize) + { + send_reply(info, 440, "Server error - Buffer size exceeded."); + free(bigBufr); + close_socket(s); + return(1); + } + close_socket(s); + + /*********************************************************************** + * Call our hook. + **********************************************************************/ + if ((usehook->hook_function)(bigBufr, size) == 0) + { + send_reply(info, 210, "File transferred successfully."); + } + else + { + send_reply(info, 440, "File transfer failed."); + } + free(bigBufr); + } + else + { + int fd = + creat(buf, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + + if (0 > fd) + { + send_reply(info, 450, "Error creating file."); + close_socket(s); + return(0); + } - /*********************************************************************** - * Retrieve the file into our buffer space. - **********************************************************************/ - size = 0; - while ((n = read(s, bufr, BUFSIZ)) > 0) + while ((n = read(s, buf, FTPD_DATASIZE)) > 0) + { + if (0 > write(fd, buf, n)) { - if (size + n > - rtems_ftpd_configuration.max_hook_filesize * sizeof(char)) - { - rtems_ftpd_send_reply(440, "Server error - Buffer size exceeded."); - free(bufr); - free(bigBufr); - close(s); - return(1); - } - memcpy(&bigBufr[size], bufr, n); - size += n; + send_reply(info, 450, "Error during write."); + close(fd); + close_socket(s); + return(1); } - close(s); + } + if (0 > close(fd)) + { + send_reply(info, 450, "Error during write."); + close_socket(s); + return(1); + } + close_socket(s); + send_reply(info, 226, "Transfer complete."); + } + + return(0); +} - /*********************************************************************** - * Call our hook. - **********************************************************************/ - if ((usehook->hook_function)(bigBufr, size) == 0) + +/*PAGE + * + * send_dirline + * + * Sends one line of LIST command reply corresponding to single file. + * + * Input parameters: + * s - socket descriptor to send data to + * wide - if 0, send only file name. If not 0, send 'stat' info as well in + * "ls -l" format. + * curTime - current time + * path - path to be prepended to what is given by 'add' + * add - path to be appended to what is given by 'path', the resulting path + * is then passed to 'stat()' routine + * name - file name to be reported in output + * buf - buffer for temporary data + * + * Output parameters: + * NONE + * + */ +static void +send_dirline(int s, int wide, time_t curTime, char const* path, + char const* add, char const* fname, char* buf) +{ + if(wide) + { + struct stat stat_buf; + + int plen = strlen(path); + int alen = strlen(add); + if(plen == 0) + { + buf[plen++] = '/'; + buf[plen] = '\0'; + } + else + { + strcpy(buf, path); + if(alen > 0 && buf[plen - 1] != '/') { - rtems_ftpd_send_reply(210, "File transferred successfully."); + buf[plen++] = '/'; + if(plen >= FTPD_BUFSIZE) + return; + buf[plen] = '\0'; } + } + if(plen + alen >= FTPD_BUFSIZE) + return; + strcpy(buf + plen, add); + + if (stat(buf, &stat_buf) == 0) + { + int len; + struct tm bt; + time_t tf = stat_buf.st_mtime; + enum { SIZE = 80 }; + enum { SIX_MONTHS = 365*24*60*60/2 }; + char timeBuf[SIZE]; + gmtime_r(&tf, &bt); + if(curTime > tf + SIX_MONTHS || tf > curTime + SIX_MONTHS) + strftime (timeBuf, SIZE, "%b %d %Y", &bt); else - { - rtems_ftpd_send_reply(440, "File transfer failed."); - } - free(bigBufr); - } - else - { - int fd; - size_t written; - - fd = creat(filename, S_IRUSR | S_IWUSR | - S_IRGRP | S_IWGRP | - S_IROTH | S_IWOTH); - if (fd == -1) - { - rtems_ftpd_send_reply(450, "Could not open file."); - close(s); - free(bufr); - return(1); - } - while ((n = read(s, bufr, BUFSIZ)) > 0) - { - written = write(fd, bufr, n); - if (written == -1) - { - rtems_ftpd_send_reply(450, "Error during write."); - close(fd); - close(s); - free(bufr); - return(1); - } - } - close(fd); - close(s); - rtems_ftpd_send_reply(226, "Transfer complete."); - } - - free(bufr); - return(0); + strftime (timeBuf, SIZE, "%b %d %H:%M", &bt); + + len = snprintf(buf, FTPD_BUFSIZE, + "%c%c%c%c%c%c%c%c%c%c 1 %5d %5d %11u %s %s\r\n", + (S_ISLNK(stat_buf.st_mode)?('l'): + (S_ISDIR(stat_buf.st_mode)?('d'):('-'))), + (stat_buf.st_mode & S_IRUSR)?('r'):('-'), + (stat_buf.st_mode & S_IWUSR)?('w'):('-'), + (stat_buf.st_mode & S_IXUSR)?('x'):('-'), + (stat_buf.st_mode & S_IRGRP)?('r'):('-'), + (stat_buf.st_mode & S_IWGRP)?('w'):('-'), + (stat_buf.st_mode & S_IXGRP)?('x'):('-'), + (stat_buf.st_mode & S_IROTH)?('r'):('-'), + (stat_buf.st_mode & S_IWOTH)?('w'):('-'), + (stat_buf.st_mode & S_IXOTH)?('x'):('-'), + (int)stat_buf.st_uid, + (int)stat_buf.st_gid, + (int)stat_buf.st_size, + timeBuf, + fname + ); + + send(s, buf, len, 0); + } + } + else + { + int len = snprintf(buf, FTPD_BUFSIZE, "%s\r\n", fname); + send(s, buf, len, 0); + } } - /************************************************************************** - * Function: rtems_ftpd_command_list * + * Function: command_list * ************************************************************************** * Description: * * * @@ -492,101 +1087,146 @@ rtems_ftpd_command_store(char *filename) * * * none * * * - ************************************************************************** - * Change History: * - * 12/01/97 - Creation (JWJ) * *************************************************************************/ static void -rtems_ftpd_command_list(char *fname) +command_list(FTPD_SessionInfo_t *info, char const *fname, int wide) { - int s; - rtems_status_code sc; - FTPD_SessionInfo_t *info = NULL; - DIR *dirp; - struct dirent *dp; - char dirline[255]; - struct stat stat_buf; - - - sc = rtems_task_get_note(RTEMS_SELF, RTEMS_NOTEPAD_0, - (rtems_unsigned32 *)&info); - - rtems_ftpd_send_reply(150, "ASCII data connection for LIST."); - - s = socket(AF_INET, SOCK_STREAM, 0); - if (connect(s, (struct sockaddr *)&info->data_addr, - sizeof(struct sockaddr)) < 0) - { - syslog(LOG_ERR, "ftpd: Error connecting to data socket."); - return; - } - - if ((dirp = opendir(fname)) == NULL) - { - sprintf(dirline, "%s: No such file or directory.%s\n", - fname, (info->xfer_mode==TYPE_A)?("\r"):("")); - send(s, dirline, strlen(dirline), 0); - close(s); - rtems_ftpd_send_reply(226, "Transfer complete."); - return; - } - while ((dp = readdir(dirp)) != NULL) - { - if (stat(dp->d_name, &stat_buf) == 0) - { - sprintf(dirline, "%c%c%c%c%c%c%c%c%c%c %5d %5d %11d %s%s\n", - (S_ISLNK(stat_buf.st_mode)?('l'): - (S_ISDIR(stat_buf.st_mode)?('d'):('-'))), - (stat_buf.st_mode & S_IRUSR)?('r'):('-'), - (stat_buf.st_mode & S_IWUSR)?('w'):('-'), - (stat_buf.st_mode & S_IXUSR)?('x'):('-'), - (stat_buf.st_mode & S_IRGRP)?('r'):('-'), - (stat_buf.st_mode & S_IWGRP)?('w'):('-'), - (stat_buf.st_mode & S_IXGRP)?('x'):('-'), - (stat_buf.st_mode & S_IROTH)?('r'):('-'), - (stat_buf.st_mode & S_IWOTH)?('w'):('-'), - (stat_buf.st_mode & S_IXOTH)?('x'):('-'), - (int)stat_buf.st_uid, - (int)stat_buf.st_gid, - (int)stat_buf.st_size, - dp->d_name, - (info->xfer_mode==TYPE_A)?("\r"):("")); - send(s, dirline, strlen(dirline), 0); - } - } - closedir(dirp); - - close(s); - rtems_ftpd_send_reply(226, "Transfer complete."); + int s; + DIR *dirp = 0; + struct dirent *dp = 0; + struct stat stat_buf; + char buf[FTPD_BUFSIZE]; + char path[FTPD_BUFSIZE]; + time_t curTime; + char const* res; + char const* cwd = info->cwd; + + send_reply(info, 150, "Opening ASCII mode data connection for LIST."); + + s = data_socket(info); + if(0 > s) + { + syslog(LOG_ERR, "ftpd: Error connecting to data socket."); + return; + } + + if(fname[0] == '\0') + fname = "."; + + res = make_path(path, cwd, fname); + + if (NULL == res || 0 > stat(path, &stat_buf)) + { + snprintf(buf, FTPD_BUFSIZE, + "%s: No such file or directory.\r\n", fname); + send(s, buf, strlen(buf), 0); + } + else if (S_ISDIR(stat_buf.st_mode) && (NULL == (dirp = opendir(path)))) + { + snprintf(buf, FTPD_BUFSIZE, + "%s: Can not open directory.\r\n", fname); + send(s, buf, strlen(buf), 0); + } + else + { + time(&curTime); + if(!dirp) + send_dirline(s, wide, curTime, path, "", fname, buf); + else { + /* FIXME: need "." and ".." only when '-a' option is given */ + send_dirline(s, wide, curTime, path, "", ".", buf); + if(!strcmp(path, root)) + send_dirline(s, wide, curTime, path, "", "..", buf); + else + send_dirline(s, wide, curTime, path, "..", "..", buf); + while ((dp = readdir(dirp)) != NULL) + send_dirline(s, wide, curTime, path, dp->d_name, dp->d_name, buf); + } + } + + if(dirp) + closedir(dirp); + close_socket(s); + send_reply(info, 226, "Transfer complete."); } -/* - * Cheesy way to change directories +/*PAGE + * + * rtems_ftpd_cwd + * + * Change current working directory. We use 'chdir' here only to validate the + * new directory. We keep track of current working directory ourselves because + * current working directory in RTEMS isn't thread local, but we need it to be + * session local. + * + * Input parameters: + * info - corresponding SessionInfo structure + * dir - directory name passed in CWD command + * + * Output parameters: + * info->cwd is set to new CWD value. + * */ static void -rtems_ftpd_CWD(char *dir) +rtems_ftpd_cwd(FTPD_SessionInfo_t *info, char *dir) { - rtems_status_code sc; - FTPD_SessionInfo_t *info = NULL; + char buf[FTPD_BUFSIZE]; + char const* cwd = make_path(buf, info->cwd, dir); + if(cwd && chdir(buf) == 0) + { + send_reply(info, 250, "CWD command successful."); + strcpy(info->cwd, cwd); + } + else + { + send_reply(info, 550, "CWD command failed."); + } +} - sc = rtems_task_get_note(RTEMS_SELF, RTEMS_NOTEPAD_0, - (rtems_unsigned32 *)&info); - if (chdir(dir) == 0) - { - rtems_ftpd_send_reply(250, "CWD command successful."); - } - else - { - rtems_ftpd_send_reply(550, "CWD command failed."); - } +/*PAGE + * + * command_mdtm + * + * Handle FTP MDTM command + * + * Input parameters: + * info - corresponding SessionInfo structure + * fname - file name passed in MDTM command + * + * Output parameters: + * info->cwd is set to new CWD value. + * + */ +static void +command_mdtm(FTPD_SessionInfo_t *info, char const* fname) +{ + struct stat stbuf; + char buf[FTPD_BUFSIZE]; + + if(*fname == '\0') + fname = "."; + + if (stat(fname, &stbuf) < 0) + { + snprintf(buf, FTPD_BUFSIZE, "%s: %s.", fname, serr()); + send_reply(info, 550, buf); + } + else + { + struct tm *t = gmtime(&stbuf.st_mtime); + snprintf(buf, FTPD_BUFSIZE, "%04d%02d%02d%02d%02d%02d", + 1900 + t->tm_year, + t->tm_mon+1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + send_reply(info, 213, buf); + } } - /************************************************************************** - * Function: rtems_ftpd_command_port * + * Function: command_port * ************************************************************************** * Description: * * * @@ -604,38 +1244,65 @@ rtems_ftpd_CWD(char *dir) * * * none * * * - ************************************************************************** - * Change History: * - * 12/01/97 - Creation (JWJ) * *************************************************************************/ static void -rtems_ftpd_command_port(char *bufr) +command_port(FTPD_SessionInfo_t *info, char *bufr) { - char *ip; - char *port; - int ip0, ip1, ip2, ip3, port0, port1; - rtems_status_code sc; - FTPD_SessionInfo_t *info = NULL; - - - sc = rtems_task_get_note(RTEMS_SELF, RTEMS_NOTEPAD_0, - (rtems_unsigned32 *)&info); - - sscanf(bufr, "%d,%d,%d,%d,%d,%d", &ip0, &ip1, &ip2, &ip3, &port0, &port1); - ip = (char *)&(info->data_addr.sin_addr); - ip[0] = ip0 & 0xff; - ip[1] = ip1 & 0xff; - ip[2] = ip2 & 0xff; - ip[3] = ip3 & 0xff; - port = (char *)&(info->data_addr.sin_port); - port[0] = port0 & 0xff; - port[1] = port1 & 0xff; - info->data_addr.sin_family = AF_INET; + char *ip; + char *port; + int ip0, ip1, ip2, ip3, port0, port1; + + sscanf(bufr, "%d,%d,%d,%d,%d,%d", &ip0, &ip1, &ip2, &ip3, &port0, &port1); + ip = (char *)&(info->data_addr.sin_addr); + ip[0] = ip0 & 0xff; + ip[1] = ip1 & 0xff; + ip[2] = ip2 & 0xff; + ip[3] = ip3 & 0xff; + port = (char *)&(info->data_addr.sin_port); + port[0] = port0 & 0xff; + port[1] = port1 & 0xff; + info->data_addr.sin_family = AF_INET; } +/*PAGE + * + * skip_options + * + * Utility routine to skip options (if any) from input command. + * + * Input parameters: + * p - pointer to pointer to command + * + * Output parameters: + * p - is changed to point to first non-option argument + * + */ +static void +skip_options(char **p) +{ + char* buf = *p; + while(1) { + while(*buf == ' ') + ++buf; + if(*buf == '-') { + if(*++buf == '-') { /* `--' should terminate options */ + ++buf; + while(*buf == ' ') + ++buf; + break; + } + while(*buf != ' ' && *buf != '\0') + ++buf; + } + else + break; + } + *p = buf; +} + /************************************************************************** - * Function: rtems_ftpd_parse_command * + * Function: parse_command * ************************************************************************** * Description: * * * @@ -654,149 +1321,156 @@ rtems_ftpd_command_port(char *bufr) * * * none * * * - ************************************************************************** - * Change History: * - * 12/01/97 - Creation (JWJ) * *************************************************************************/ static void -rtems_ftpd_parse_command(char *bufr) +parse_command(FTPD_SessionInfo_t *info, char *bufr) { - char fname[255]; - rtems_status_code sc; - FTPD_SessionInfo_t *info = NULL; - - - sc = rtems_task_get_note(RTEMS_SELF, RTEMS_NOTEPAD_0, - (rtems_unsigned32 *)&info); - - if (!strncmp("PORT", bufr, 4)) - { - rtems_ftpd_send_reply(200, "PORT command successful."); - rtems_ftpd_command_port(&bufr[5]); - } - else if (!strncmp("RETR", bufr, 4)) - { - sscanf(&bufr[5], "%254s", fname); - rtems_ftpd_command_retrieve(fname); - } - else if (!strncmp("STOR", bufr, 4)) - { - sscanf(&bufr[5], "%254s", fname); - rtems_ftpd_command_store(fname); - } - else if (!strncmp("LIST", bufr, 4)) - { - if (bufr[5] == '\n') - { - rtems_ftpd_command_list("."); - } - else - { - sscanf(&bufr[5], "%254s", fname); - rtems_ftpd_command_list(fname); - } - } - else if (!strncmp("USER", bufr, 4)) - { - rtems_ftpd_send_reply(230, "User logged in."); - } - else if (!strncmp("SYST", bufr, 4)) - { - rtems_ftpd_send_reply(240, "RTEMS"); - } - else if (!strncmp("TYPE", bufr, 4)) - { - if (bufr[5] == 'I') - { - info->xfer_mode = TYPE_I; - rtems_ftpd_send_reply(200, "Type set to I."); - } - else if (bufr[5] == 'A') - { - info->xfer_mode = TYPE_A; - rtems_ftpd_send_reply(200, "Type set to A."); - } - else - { - info->xfer_mode = TYPE_I; - rtems_ftpd_send_reply(504, "Type not implemented. Set to I."); - } - } - else if (!strncmp("PASS", bufr, 4)) - { - rtems_ftpd_send_reply(230, "User logged in."); - } - else if (!strncmp("DELE", bufr, 4)) - { - sscanf(&bufr[4], "%254s", fname); - if (unlink(fname) == 0) - { - rtems_ftpd_send_reply(257, "DELE successful."); - } - else - { - rtems_ftpd_send_reply(550, "DELE failed."); - } - } - else if (!strncmp("SITE CHMOD", bufr, 10)) - { - int mask; - - sscanf(&bufr[11], "%o %254s", &mask, fname); - if (chmod(fname, (mode_t)mask) == 0) - { - rtems_ftpd_send_reply(257, "CHMOD successful."); - } - else - { - rtems_ftpd_send_reply(550, "CHMOD failed."); - } - } - else if (!strncmp("RMD", bufr, 3)) - { - sscanf(&bufr[4], "%254s", fname); - if (rmdir(fname) == 0) - { - rtems_ftpd_send_reply(257, "RMD successful."); - } - else - { - rtems_ftpd_send_reply(550, "RMD failed."); - } - } - else if (!strncmp("MKD", bufr, 3)) - { - sscanf(&bufr[4], "%254s", fname); - if (mkdir(fname, S_IRWXU | S_IRWXG | S_IRWXO) == 0) - { - rtems_ftpd_send_reply(257, "MKD successful."); - } - else - { - rtems_ftpd_send_reply(550, "MKD failed."); - } - } - else if (!strncmp("CWD", bufr, 3)) - { - sscanf(&bufr[4], "%254s", fname); - rtems_ftpd_CWD(fname); - } - else if (!strncmp("PWD", bufr, 3)) - { - char *cwd = getcwd(0, 0); - sprintf(bufr, "\"%s\" is the current directory.", cwd); - rtems_ftpd_send_reply(250, bufr); - free(cwd); - } - else - { - rtems_ftpd_send_reply(500, "Unrecognized/unsupported command."); - } + char fname[FTPD_BUFSIZE]; + + if (!strncmp("PORT", bufr, 4)) + { + send_reply(info, 200, "PORT command successful."); + command_port(info, &bufr[5]); + } + else if (!strncmp("RETR", bufr, 4)) + { + sscanf(&bufr[5], "%254s", fname); + command_retrieve(info, fname); + } + else if (!strncmp("STOR", bufr, 4)) + { + sscanf(&bufr[5], "%254s", fname); + command_store(info, fname); + } + else if (!strncmp("LIST", bufr, 4)) + { + bufr += 4; + skip_options(&bufr); + sscanf(bufr, "%254s", fname); + command_list(info, fname, 1); + } + else if (!strncmp("NLST", bufr, 4)) + { + bufr += 4; + skip_options(&bufr); + sscanf(bufr, "%254s", fname); + command_list(info, fname, 0); + } + else if (!strncmp("MDTM", bufr, 4)) + { + bufr += 4; + skip_options(&bufr); + sscanf(bufr, "%254s", fname); + command_mdtm(info, fname); + } + else if (!strncmp("USER", bufr, 4)) + { + send_reply(info, 230, "User logged in."); + } + else if (!strncmp("SYST", bufr, 4)) + { + send_reply(info, 240, FTPD_SYSTYPE); + } + else if (!strncmp("TYPE", bufr, 4)) + { + if (bufr[5] == 'I') + { + info->xfer_mode = TYPE_I; + send_reply(info, 200, "Type set to I."); + } + else if (bufr[5] == 'A') + { + /* FIXME: ASCII mode isn't actually supported yet. */ + info->xfer_mode = TYPE_A; + send_reply(info, 200, "Type set to A."); + } + else + { + info->xfer_mode = TYPE_I; + send_reply(info, 504, "Type not implemented. Set to I."); + } + } + else if (!strncmp("PASS", bufr, 4)) + { + send_reply(info, 230, "User logged in."); + } + else if (!strncmp("DELE", bufr, 4)) + { + sscanf(&bufr[4], "%254s", fname); + if (unlink(fname) == 0) + { + send_reply(info, 257, "DELE successful."); + } + else + { + send_reply(info, 550, "DELE failed."); + } + } + else if (!strncmp("SITE CHMOD", bufr, 10)) + { + int mask; + + sscanf(&bufr[11], "%o %254s", &mask, fname); + if (chmod(fname, (mode_t)mask) == 0) + { + send_reply(info, 257, "CHMOD successful."); + } + else + { + send_reply(info, 550, "CHMOD failed."); + } + } + else if (!strncmp("RMD", bufr, 3)) + { + sscanf(&bufr[4], "%254s", fname); + if (rmdir(fname) == 0) + { + send_reply(info, 257, "RMD successful."); + } + else + { + send_reply(info, 550, "RMD failed."); + } + } + else if (!strncmp("MKD", bufr, 3)) + { + sscanf(&bufr[4], "%254s", fname); + if (mkdir(fname, S_IRWXU | S_IRWXG | S_IRWXO) == 0) + { + send_reply(info, 257, "MKD successful."); + } + else + { + send_reply(info, 550, "MKD failed."); + } + } + else if (!strncmp("CWD", bufr, 3)) + { + sscanf(&bufr[4], "%254s", fname); + rtems_ftpd_cwd(info, fname); + } + else if (!strncmp("CDUP", bufr, 4)) + { + rtems_ftpd_cwd(info, ".."); + } + else if (!strncmp("PWD", bufr, 3)) + { + char const* cwd = "/"; + if(info->cwd[0]) + cwd = info->cwd; + snprintf(bufr, FTPD_BUFSIZE, + "\"%s\" is the current directory.", cwd); + send_reply(info, 250, bufr); + } + else + { + send_reply(info, 500, "Unrecognized/unsupported command."); + } } /************************************************************************** - * Function: rtems_ftpd_session * + * Function: session * ************************************************************************** * Description: * * * @@ -816,68 +1490,51 @@ rtems_ftpd_parse_command(char *bufr) * * * none * * * - ************************************************************************** - * Change History: * - * 12/01/97 - Creation (JWJ) * *************************************************************************/ static void -rtems_ftpd_session(rtems_task_argument arg) +session(rtems_task_argument arg) { - char cmd[256]; - rtems_status_code sc; - FTPD_SessionInfo_t *info = NULL; + char cmd[FTPD_BUFSIZE]; + FTPD_SessionInfo_t *info = (FTPD_SessionInfo_t *)arg; + rtems_event_set set; + while(1) { + rtems_event_receive(FTPD_RTEMS_EVENT, RTEMS_EVENT_ANY, RTEMS_NO_TIMEOUT, + &set); - sc = rtems_task_get_note(RTEMS_SELF, RTEMS_NOTEPAD_0, - (rtems_unsigned32 *)&info); + send_reply(info, 220, FTPD_SERVER_MESSAGE); - rtems_ftpd_send_reply(220, FTPD_SERVER_MESSAGE); + info->cwd[0] = 0; + info->xfer_mode = TYPE_I; - /*********************************************************************** - * Set initial directory to "/". - **********************************************************************/ - strcpy(info->cwd, "/"); - info->xfer_mode = TYPE_A; - while (1) - { - if (fgets(cmd, 256, info->ctrl_fp) == NULL) + while (1) + { + if (fgets(cmd, FTPD_BUFSIZE, info->ctrl_fp) == NULL) { - syslog(LOG_INFO, "ftpd: Connection aborted."); - break; + syslog(LOG_INFO, "ftpd: Connection aborted."); + break; } if (!strncmp("QUIT", cmd, 4)) { - rtems_ftpd_send_reply(221, "Goodbye."); - break; + send_reply(info, 221, "Goodbye."); + break; } else { - rtems_ftpd_parse_command(cmd); + parse_command(info, cmd); } - } - - if (fclose(info->ctrl_fp) != 0) - { - syslog(LOG_ERR, "ftpd: Could not close session."); - } - - - /* Least we can do is put the CWD back to /. */ - chdir("/"); + } - /*********************************************************************** - * Free up the allocated SessionInfo struct and exit. - **********************************************************************/ - free(info); - sc = rtems_task_delete(RTEMS_SELF); - syslog(LOG_ERR, "ftpd: Task deletion failed: %s", - rtems_status_text(sc)); + /* Close connection and put ourselves back into the task pool. */ + close_stream(info); + task_pool_release(info); + } } /************************************************************************** - * Function: rtems_ftpd_daemon * + * Function: daemon * ************************************************************************** * Description: * * * @@ -895,117 +1552,69 @@ rtems_ftpd_session(rtems_task_argument arg) * * * none * * * - ************************************************************************** - * Change History: * - * 12/01/97 - Creation (JWJ) * *************************************************************************/ - -/* this is not prototyped in strict ansi mode */ - -FILE *fdopen (int fildes, const char *mode); - static void -rtems_ftpd_daemon() +daemon() { - int s; - int s1; - int addrLen; - struct sockaddr_in remoteAddr; - struct sockaddr_in localAddr; - char sessionID; - rtems_task_priority priority; - rtems_status_code sc; - rtems_id tid; - FTPD_SessionInfo_t *info = NULL; - - - sessionID = 'a'; - - s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) - { - perror("Creating socket"); - } - - localAddr.sin_family = AF_INET; - localAddr.sin_port = htons(rtems_ftpd_configuration.port); - localAddr.sin_addr.s_addr = INADDR_ANY; - memset(localAddr.sin_zero, '\0', sizeof(localAddr.sin_zero)); - if (bind(s, (struct sockaddr *)&localAddr, - sizeof(localAddr)) < 0) - { - perror("Binding control socket"); - } - - if (listen(s, 2) < 0) - { - perror("Listening on control socket"); - } - - while (1) - { - /******************************************************************** - * Allocate a SessionInfo structure for the session task. - *******************************************************************/ - info = (FTPD_SessionInfo_t *)malloc(sizeof(FTPD_SessionInfo_t)); - if (info == NULL) - { - syslog(LOG_ERR, "ftpd: Could not allocate session info struct."); - rtems_panic("Malloc fail."); - } - - /******************************************************************** - * Accept on the socket and start the session task. - *******************************************************************/ - addrLen = sizeof(remoteAddr); - s1 = accept(s, (struct sockaddr *)&remoteAddr, &addrLen); - if (s1 < 0) - { - perror("Accepting control connection"); - } - - rtems_task_set_priority(RTEMS_SELF, RTEMS_CURRENT_PRIORITY, &priority); - sc = rtems_task_create(rtems_build_name('F', 'T', 'P', sessionID), - priority, 8*1024, - RTEMS_PREEMPT | RTEMS_NO_TIMESLICE | - RTEMS_NO_ASR | RTEMS_INTERRUPT_LEVEL(0), - RTEMS_NO_FLOATING_POINT | RTEMS_LOCAL, - &tid); - if (sc != RTEMS_SUCCESSFUL) - { - syslog(LOG_ERR, "ftpd: Could not create FTPD session: %s", - rtems_status_text(sc)); - } - - if (sessionID == 'z') + int s; + int addrLen; + struct sockaddr_in remoteAddr; + struct sockaddr_in localAddr; + char sessionID; + FTPD_SessionInfo_t *info = NULL; + + + sessionID = 'a'; + + s = socket(PF_INET, SOCK_STREAM, 0); + if (s < 0) + syslog(LOG_ERR, "ftpd: Error creating socket: %s", serr()); + + localAddr.sin_family = AF_INET; + localAddr.sin_port = htons(rtems_ftpd_configuration.port); + localAddr.sin_addr.s_addr = htonl(INADDR_ANY); + memset(localAddr.sin_zero, '\0', sizeof(localAddr.sin_zero)); + + if (0 > bind(s, (struct sockaddr *)&localAddr, sizeof(localAddr))) + syslog(LOG_ERR, "ftpd: Error binding control socket: %s", serr()); + + if (0 > listen(s, 1)) + syslog(LOG_ERR, "ftpd: Error listening on control socket: %s", serr()); + + while (1) + { + int ss; + addrLen = sizeof(remoteAddr); + ss = accept(s, (struct sockaddr *)&remoteAddr, &addrLen); + if (0 > ss) + { + syslog(LOG_ERR, "ftpd: Error accepting control connection: %s", serr()); + } + else + { + info = task_pool_obtain(); + if (NULL == info) { - sessionID = 'a'; + close_socket(ss); } else { - sessionID++; - } - - /******************************************************************** - * Send the socket on to the new session. - *******************************************************************/ - if ((info->ctrl_fp = fdopen(s1, "r+")) == NULL) - { - syslog(LOG_ERR, "ftpd: fdopen() on socket failed."); - close(s1); + info->socket = ss; + if ((info->ctrl_fp = fdopen(info->socket, "r+")) == NULL) + { + syslog(LOG_ERR, "ftpd: fdopen() on socket failed: %s", serr()); + close_stream(info); + task_pool_release(info); + } + else + { + /* Wakeup the session task. The task will call task_pool_release + after it closes connection. */ + rtems_event_send(info->tid, FTPD_RTEMS_EVENT); + } } - else - { - sc = rtems_task_set_note(tid, RTEMS_NOTEPAD_0, - (rtems_unsigned32)info); - sc = rtems_task_start(tid, rtems_ftpd_session, 0); - if (sc != RTEMS_SUCCESSFUL) - { - syslog(LOG_ERR, "ftpd: Could not start FTPD session: %s", - rtems_status_text(sc)); - } - } - } + } + } } @@ -1021,57 +1630,74 @@ rtems_ftpd_daemon() * * * Inputs: * * * - * rtems_task_priority priority - Priority to assign to this task. * - * * * Output: * * * * int - RTEMS_SUCCESSFUL on successful start of the daemon. * * * - ************************************************************************** - * Change History: * - * 12/01/97 - Creation (JWJ) * *************************************************************************/ int rtems_initialize_ftpd() { - rtems_status_code sc; - rtems_id tid; - - - if (rtems_ftpd_configuration.port == 0) - { - rtems_ftpd_configuration.port = FTPD_CONTROL_PORT; - } - - /*********************************************************************** - * Default FTPD priority. - **********************************************************************/ - if (rtems_ftpd_configuration.priority == 0) - { - rtems_ftpd_configuration.priority = 40; - } - sc = rtems_task_create(rtems_build_name('F', 'T', 'P', 'D'), - rtems_ftpd_configuration.priority, 8*1024, - RTEMS_PREEMPT | RTEMS_NO_TIMESLICE | RTEMS_NO_ASR | - RTEMS_INTERRUPT_LEVEL(0), - RTEMS_NO_FLOATING_POINT | RTEMS_LOCAL, - &tid); - if (sc != RTEMS_SUCCESSFUL) - { - syslog(LOG_ERR, "ftpd: Could not create FTP daemon: %s", - rtems_status_text(sc)); - return(RTEMS_UNSATISFIED); - } - - sc = rtems_task_start(tid, rtems_ftpd_daemon, 0); - if (sc != RTEMS_SUCCESSFUL) - { - syslog(LOG_ERR, "ftpd: Could not start FTP daemon: %s", - rtems_status_text(sc)); - return(RTEMS_UNSATISFIED); - } - - syslog(LOG_INFO, "ftpd: FTP daemon started."); - return(RTEMS_SUCCESSFUL); + rtems_status_code sc; + rtems_id tid; + rtems_task_priority priority; + int count; + + if (rtems_ftpd_configuration.port == 0) + { + rtems_ftpd_configuration.port = FTPD_CONTROL_PORT; + } + + if (rtems_ftpd_configuration.priority == 0) + { + rtems_ftpd_configuration.priority = 40; + } + priority = rtems_ftpd_configuration.priority; + + if (rtems_ftpd_configuration.tasks_count <= 0) + rtems_ftpd_configuration.tasks_count = 1; + count = rtems_ftpd_configuration.tasks_count; + + if (!task_pool_init(count, priority)) + { + syslog(LOG_ERR, "ftpd: Could not initialize task pool."); + return RTEMS_UNSATISFIED; + } + + sc = rtems_task_create(rtems_build_name('F', 'T', 'P', 'D'), + priority, FTPD_STACKSIZE, + RTEMS_PREEMPT | RTEMS_NO_TIMESLICE | RTEMS_NO_ASR | + RTEMS_INTERRUPT_LEVEL(0), + RTEMS_NO_FLOATING_POINT | RTEMS_LOCAL, + &tid); + + if (sc == RTEMS_SUCCESSFUL) + { + sc = rtems_task_start(tid, daemon, 0); + if (sc != RTEMS_SUCCESSFUL) + rtems_task_delete(tid); + } + + if (sc != RTEMS_SUCCESSFUL) + { + task_pool_done(count); + syslog(LOG_ERR, "ftpd: Could not create/start FTP daemon: %s", + rtems_status_text(sc)); + return RTEMS_UNSATISFIED; + } + + root[0] = '\0'; + if ( + rtems_ftpd_configuration.root && + strlen(rtems_ftpd_configuration.root) < FTPD_BUFSIZE && + rtems_ftpd_configuration.root[0] == '/') + { + strcpy(root, rtems_ftpd_configuration.root); + squeeze_path(root); + rtems_ftpd_configuration.root = root; + } + + syslog(LOG_INFO, "ftpd: FTP daemon started (%d session%s max)", + count, ((count > 1) ? "s" : "")); + return RTEMS_SUCCESSFUL; } - -- cgit v1.2.3