/* 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? * * FTP Server Daemon * * Submitted by: Jake Janovetz * * $Id$ */ /************************************************************************** * ftpd.c * ************************************************************************** * Description: * * * * This file contains the daemon which services requests that appear * * on the FTP port. This server is compatible with FTP, but it * * also provides 'hooks' to make it usable in situations where files * * are not used/necessary. Once the server is started, it runs * * forever. * * * * * * 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 * * session then interacts with the remote host. When the session * * is complete, the session task deletes itself. The daemon still * * runs, however. * * * * * * Supported commands are: * * * * 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...) * * USER - Does nothing. * * PASS - Does nothing. * * SYST - Replies with the system type (`RTEMS'). * * DELE xxx - Delete file xxx. * * MKD xxx - Create directory xxx. * * RMD xxx - Remove directory xxx. * * PWD - Print working directory. * * CWD xxx - Change working 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). * * * * * * 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_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 * * 1406 West Green Street * * Urbana IL 61801 * ************************************************************************** * Change History: * * 12/01/97 - Creation (JWJ) * *************************************************************************/ #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: * * Reply: Description: *-------- -------------- * 1yz Positive preliminary reply. The action is being started but * expect another reply before sending another command. * 2yz Positive completion reply. A new command can be sent. * 3yz Positive intermediate reply. The command has been accpeted * but another command must be sent. * 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 * accepted and should not be retried. *------------------------------------------------------------------------- * x0z Syntax errors. * x1z Information. * x2z Connections. Replies referring to the control or data * connections. * x3z Authentication and accounting. Replies for the login or * accounting commands. * x4z Unspecified. * x5z Filesystem status. *************************************************************************/ /************************************************************************** * SessionInfo structure. * * The following structure is allocated for each session. The pointer * to this structure is contained in the tasks notepad entry. *************************************************************************/ 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) */ } 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" /************************************************************************** * Function: rtems_ftpd_send_reply * ************************************************************************** * Description: * * * * This procedure sends a reply to the client via the control * * connection. * * * * * * Inputs: * * * * int code - The 3-digit reply code. * * char *text - Reply text. * * * * Output: * * * * none * * * ************************************************************************** * Change History: * * 12/01/97 - Creation (JWJ) * *************************************************************************/ static void rtems_ftpd_send_reply(int code, char *text) { rtems_status_code sc; FTPD_SessionInfo_t *info = NULL; char str[80]; 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); } /************************************************************************** * Function: rtems_ftpd_command_retrieve * ************************************************************************** * Description: * * * * This performs the "RETR" command. A data connection must already * * be open (via the "PORT" command.) Here, we send the data to the * * connection. * * * * * * Inputs: * * * * char *filename - Source filename. * * * * Output: * * * * 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) { int s; int n; int fd; unsigned char *bufr; rtems_status_code sc; FTPD_SessionInfo_t *info = NULL; sc = rtems_task_get_note(RTEMS_SELF, RTEMS_NOTEPAD_0, (rtems_unsigned32 *)&info); if ((fd = open(filename, O_RDONLY)) == -1) { rtems_ftpd_send_reply(450, "Error opening file."); return(0); } 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); } /************************************************************************** * Function: rtems_ftpd_command_store * ************************************************************************** * Description: * * * * This performs the "STOR" command. A data connection must already * * be open (via the "PORT" command.) Here, we get the data from the * * connection and figure out what to do with it. * * * * * * Inputs: * * * * char *filename - Destination filename. * * * * Output: * * * * int - 0 for success. * * 1 for failure. * * * ************************************************************************** * Change History: * * 12/01/97 - Creation (JWJ) * *************************************************************************/ static int rtems_ftpd_command_store(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) { rtems_ftpd_send_reply(440, "Server error - malloc fail."); free(bufr); return(1); } /*********************************************************************** * Retrieve the file into our buffer space. **********************************************************************/ size = 0; while ((n = read(s, bufr, BUFSIZ)) > 0) { 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; } close(s); /*********************************************************************** * Call our hook. **********************************************************************/ if ((usehook->hook_function)(bigBufr, size) == 0) { rtems_ftpd_send_reply(210, "File transferred successfully."); } 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); } /************************************************************************** * Function: rtems_ftpd_command_list * ************************************************************************** * Description: * * * * Sends a file list through a data connection. The data * * connection must have already been opened with the "PORT" command. * * * * * * Inputs: * * * * char *fname - File (or directory) to list. * * * * Output: * * * * none * * * ************************************************************************** * Change History: * * 12/01/97 - Creation (JWJ) * *************************************************************************/ static void rtems_ftpd_command_list(char *fname) { 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."); } /* * Cheesy way to change directories */ static void rtems_ftpd_CWD(char *dir) { rtems_status_code sc; FTPD_SessionInfo_t *info = NULL; 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."); } } /************************************************************************** * Function: rtems_ftpd_command_port * ************************************************************************** * Description: * * * * This procedure opens up a data port given the IP address of the * * remote machine and the port on the remote machine. This connection * * will then be used to transfer data between the hosts. * * * * * * Inputs: * * * * char *bufr - Arguments to the "PORT" command. * * * * * * Output: * * * * none * * * ************************************************************************** * Change History: * * 12/01/97 - Creation (JWJ) * *************************************************************************/ static void rtems_ftpd_command_port(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; } /************************************************************************** * Function: rtems_ftpd_parse_command * ************************************************************************** * Description: * * * * Here, we parse the commands that have come through the control * * connection. * * * * FIXME: This section is somewhat of a hack. We should have a better * * way to parse commands. * * * * Inputs: * * * * char *bufr - Pointer to the buffer which contains the command * * text. * * * * Output: * * * * none * * * ************************************************************************** * Change History: * * 12/01/97 - Creation (JWJ) * *************************************************************************/ static void rtems_ftpd_parse_command(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."); } } /************************************************************************** * Function: rtems_ftpd_session * ************************************************************************** * Description: * * * * This task is started when the FTP daemon gets a service request * * from a remote machine. Here, we watch for commands that will * * come through the "control" connection. These commands are then * * parsed and executed until the connection is closed, either * * unintentionally or intentionally with the "QUIT" command. * * * * * * Inputs: * * * * rtems_task_argument arg - The daemon task passes the socket * * which serves as the control connection. * * * * Output: * * * * none * * * ************************************************************************** * Change History: * * 12/01/97 - Creation (JWJ) * *************************************************************************/ static void rtems_ftpd_session(rtems_task_argument arg) { char cmd[256]; rtems_status_code sc; FTPD_SessionInfo_t *info = NULL; sc = rtems_task_get_note(RTEMS_SELF, RTEMS_NOTEPAD_0, (rtems_unsigned32 *)&info); rtems_ftpd_send_reply(220, FTPD_SERVER_MESSAGE); /*********************************************************************** * Set initial directory to "/". **********************************************************************/ strcpy(info->cwd, "/"); info->xfer_mode = TYPE_A; while (1) { if (fgets(cmd, 256, info->ctrl_fp) == NULL) { syslog(LOG_INFO, "ftpd: Connection aborted."); break; } if (!strncmp("QUIT", cmd, 4)) { rtems_ftpd_send_reply(221, "Goodbye."); break; } else { rtems_ftpd_parse_command(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)); } /************************************************************************** * Function: rtems_ftpd_daemon * ************************************************************************** * Description: * * * * This task runs in the background forever. It waits for service * * requests on the FTP port (port 21). When a request is received, * * it opens a new session to handle those requests until the * * connection is closed. * * * * * * Inputs: * * * * none * * * * Output: * * * * none * * * ************************************************************************** * Change History: * * 12/01/97 - Creation (JWJ) * *************************************************************************/ static void rtems_ftpd_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') { sessionID = 'a'; } 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); } 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)); } } } } /************************************************************************** * Function: rtems_ftpd_start * ************************************************************************** * Description: * * * * Here, we start the FTPD task which waits for FTP requests and * * services them. This procedure returns to its caller once the * * task is started. * * * * * * 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); }