2174 lines
52 KiB
C

/* FIXME: 1. Parse command is a hack. We can do better.
* 2. OSV: hooks support seems to be bad, 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.
* 3. OSV: Remove hack with "/dev/null"?
*
* FTP Server Daemon
*
* Submitted by: Jake Janovetz <janovetz@tempest.ece.uiuc.edu>
*
* Changed by: Sergei Organov <osv@javad.ru> (OSV)
* Arnout Vandecappelle <arnout@mind.be> (AV)
* Sebastien Bourdeauducq <sebastien@milkymist.org> (MM)
*
*
* Changes:
*
* 2010-12-02 Sebastien Bourdeauducq <sebastien@milkymist.org>
*
* * Support spaces in filenames
*
* 2010-04-29 Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>
*
* * Added USER/PASS authentication.
*
* 2001-01-31 Sergei Organov <osv@javad.ru>
*
* * Hacks with current dir and root dir removed in favor of new libio
* support for task-local current and root directories.
*
* 2001-01-30 Sergei Organov <osv@javad.ru>
*
* * Bug in `close_data_socket()' introduced by previous change fixed.
* * `command_pasv()' changed to set timeout on socket we are listening on
* and code fixed to don't close socket twice on error.
* * `serr()' changed to clear `errno'.
* * `data_socket()' changed to clear `errno' before `bind()'.
* * `session()' changed to clear `errno' before processing session.
*
* 2001-01-29 Sergei Organov <osv@javad.ru>
*
* * `close_data_socket()' fixed to close both active and passive sockets
* * Initialize info->data_socket to -1 in `daemon()'
* * Initialize `fname' to empty string in `exec_command()'
*
* 2001-01-22 Sergei Organov <osv@javad.ru>
*
* * Timeouts on sockets implemented. 'idle' field added to
* configuration. No timeout by default to keep backward compatibility.
* Note: SITE IDLE command not implemented yet.
* * Basic global access control implemented. 'access' field added to
* configuration. No access limitations by default to keep backward
* compatibility.
*
* 2001-01-17 Sergei Organov <osv@javad.ru>
*
* * Anchor data socket for active mode (using self IP and port 20.)
* * Fixed default data port support (still not tested).
* * Don't allow IP address different from originating host in
* PORT command to improve security.
* * Fixed bug in MDTM command.
* * Check for correctness of parsing of argument in command_port().
* * Fixed squeeze_path() to don't allow names like 'NAME/smth' where
* 'NAME' is not a directory.
* * Command parsing a little bit improved: command names are now
* converted to upper-case to be more compatible with RFC (command
* names are not case-sensitive.)
* * Reformat comments so that they have RTEMS look-and-feel.
*
* 2001-01-16 Sergei Organov <osv@javad.ru>
*
* * Fixed DELE, SITE CHMOD, RMD, MKD broken by previous changes
* * True ASCII mode implemented (doesn't work for hooks and /dev/null)
* * Passive mode implemented, PASV command added.
* * Default port for data connection could be used (untested, can't find
* ftp client that doesn't send PORT command)
* * SYST reply changed to UNIX, as former RTEMS isn't registered name.
* * Reply codes reviewed and fixed.
*
* 2001-01-08 Sergei Organov <osv@javad.ru>
*
* * 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.
*/
/*************************************************************************
* 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 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 wakes-up a 'session' task. That
* session then interacts with the remote host. When the session
* is complete, the session task goes to sleep. 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.
* NLST xxx - Sends a file list to the client.
* 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.
* 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 file modification date/time to the client.
* xxx = filename.
* PASV - Use passive mode data connection.
*
*
* The public routines contained in this file are:
*
* rtems_initialize_ftpd - Initializes and starts the server daemon,
* then returns to its caller.
*
*------------------------------------------------------------------------
* Jake Janovetz
* University of Illinois
* 1406 West Green Street
* Urbana IL 61801
*************************************************************************
* Change History:
* 12/01/97 - Creation (JWJ)
* 2001-01-08 - Changes by OSV
* 2010-04-29 - Authentication added by AV
*************************************************************************/
/*************************************************************************
* 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 accepted
* 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.
*************************************************************************/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <ctype.h>
#include <inttypes.h>
#include <sched.h>
#include <rtems.h>
#include <rtems/rtems_bsdnet.h>
#include <rtems/error.h>
#include <rtems/libio.h>
#include <rtems/userenv.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/ftp.h>
#include <netinet/in.h>
#include <rtems/ftpd.h>
#include <machine/rtems-bsd-printf-to-iprintf.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 "UNIX Type: L8"
/* Seem to be unused */
#if 0
#define FTPD_WELCOME_MESSAGE \
"Welcome to the RTEMS FTP server.\n" \
"\n" \
"Login accepted.\n"
#endif
/* Event to be used by session tasks for waiting */
enum
{
FTPD_RTEMS_EVENT = RTEMS_EVENT_1
};
/* Configuration table */
static struct rtems_ftpd_configuration* ftpd_config;
/* 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.
*/
typedef struct
{
struct sockaddr_in ctrl_addr; /* Control connection self address */
struct sockaddr_in data_addr; /* Data address set by PORT command */
struct sockaddr_in def_addr; /* Default address for data */
int use_default; /* 1 - use default address for data */
FILE *ctrl_fp; /* File pointer for control connection */
int ctrl_socket; /* Socket for ctrl connection */
int pasv_socket; /* Socket for PASV connection */
int data_socket; /* Socket for data connection */
int idle; /* Timeout in seconds */
int xfer_mode; /* Transfer mode (ASCII/binary) */
rtems_id tid; /* Task id */
char *user; /* user name (0 if not supplied) */
char *pass; /* password (0 if not supplied) */
bool auth; /* true if user/pass was valid, false if not or not supplied */
} FTPD_SessionInfo_t;
/*
* 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 directory
*/
static char const* ftpd_root = "/";
/*
* Default idle timeout for sockets in seconds.
*/
static int ftpd_timeout = 0;
/*
* Global access flags.
*/
static int ftpd_access = 0;
/*
* serr
*
* Return error string corresponding to current 'errno'.
*
*/
static char const*
serr(void)
{
int err = errno;
errno = 0;
return strerror(err);
}
/*
* Utility routines for access control.
*
*/
static int
can_read(void)
{
return (ftpd_access & FTPD_NO_READ) == 0;
}
static int
can_write(void)
{
return (ftpd_access & FTPD_NO_WRITE) == 0;
}
/*
* Task pool management routines
*
*/
/*
* 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;
}
/*
* 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 void session(rtems_task_argument arg); /* Forward declare */
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;
if (++id > 'z')
id = 'a';
}
task_pool.count = count;
return 1;
}
/*
* 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(void)
{
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;
rtems_semaphore_release(task_pool.mutex);
}
return info;
}
/*
* 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
*/
/*
* Function: send_reply
*
*
* This procedure sends a reply to the client via the control
* connection.
*
*
* Input parameters:
* code - 3-digit reply code.
* text - Reply text.
*
* Output parameters:
* NONE
*/
static void
send_reply(FTPD_SessionInfo_t *info, int code, char *text)
{
text = text != NULL ? text : "";
fprintf(info->ctrl_fp, "%d %.70s\r\n", code, text);
fflush(info->ctrl_fp);
}
/*
* close_socket
*
* Close socket.
*
* Input parameters:
* s - socket descriptor.
* seconds - number of seconds the timeout should be,
* if >= 0 - infinite timeout (no timeout).
*
* Output parameters:
* returns 1 on success, 0 on failure.
*/
static int
set_socket_timeout(int s, int seconds)
{
int res = 0;
struct timeval tv;
int len = sizeof(tv);
if(seconds < 0)
seconds = 0;
tv.tv_usec = 0;
tv.tv_sec = seconds;
if(0 != setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, len))
syslog(LOG_ERR, "ftpd: Can't set send timeout on socket: %s.", serr());
else if(0 != setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, len))
syslog(LOG_ERR, "ftpd: Can't set receive timeout on socket: %s.", serr());
else
res = 1;
return res;
}
/*
* 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;
}
/*
* 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 = info->pasv_socket;
if(0 > s)
{
int on = 1;
s = socket(PF_INET, SOCK_STREAM, 0);
if(0 > s)
send_reply(info, 425, "Can't create data socket.");
else if(0 > setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
{
close_socket(s);
s = -1;
}
else
{
struct sockaddr_in data_source;
int tries;
/* anchor socket to avoid multi-homing problems */
data_source = info->ctrl_addr;
data_source.sin_port = htons(20); /* ftp-data port */
for(tries = 1; tries < 10; ++tries)
{
errno = 0;
if(bind(s, (struct sockaddr *)&data_source, sizeof(data_source)) >= 0)
break;
if (errno != EADDRINUSE)
tries = 10;
else
rtems_task_wake_after(tries * 10);
}
if(tries >= 10)
{
send_reply(info, 425, "Can't bind data socket.");
close_socket(s);
s = -1;
}
else
{
struct sockaddr_in *data_dest =
(info->use_default) ? &info->def_addr : &info->data_addr;
if(0 > connect(s, (struct sockaddr *)data_dest, sizeof(*data_dest)))
{
send_reply(info, 425, "Can't connect data socket.");
close_socket(s);
s = -1;
}
}
}
}
info->data_socket = s;
info->use_default = 1;
if(s >= 0)
set_socket_timeout(s, info->idle);
return s;
}
/*
* close_data_socket
*
* Close data socket for session.
*
* Input parameters:
* info - corresponding SessionInfo structure
*
* Output parameters:
* NONE
*
*/
static void
close_data_socket(FTPD_SessionInfo_t *info)
{
/* As at most one data socket could be open simultaneously and in some cases
data_socket == pasv_socket, we select socket to close, then close it. */
int s = info->data_socket;
if(0 > s)
s = info->pasv_socket;
if(!close_socket(s))
syslog(LOG_ERR, "ftpd: Error closing data socket.");
info->data_socket = -1;
info->pasv_socket = -1;
info->use_default = 1;
}
/*
* close_stream
*
* Close control 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->ctrl_socket = -1;
}
if (!close_socket(info->ctrl_socket))
syslog(LOG_ERR, "ftpd: Could not close control socket: %s", serr());
info->ctrl_fp = NULL;
info->ctrl_socket = -1;
}
/*
* 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.");
}
/*
* command_retrieve
*
* Perform the "RETR" command (send file to client).
*
* Input parameters:
* info - corresponding SessionInfo structure
* char *filename - source filename.
*
* Output parameters:
* NONE
*
*/
static void
command_retrieve(FTPD_SessionInfo_t *info, char const *filename)
{
int s = -1;
int fd = -1;
char buf[FTPD_DATASIZE];
struct stat stat_buf;
int res = 0;
if(!can_read() || !info->auth)
{
send_reply(info, 550, "Access denied.");
return;
}
if (0 > (fd = open(filename, O_RDONLY)))
{
send_reply(info, 550, "Error opening file.");
return;
}
if (fstat(fd, &stat_buf) == 0 && S_ISDIR(stat_buf.st_mode))
{
if (-1 != fd)
close(fd);
send_reply(info, 550, "Is a directory.");
return;
}
send_mode_reply(info);
s = data_socket(info);
if (0 <= s)
{
int n = -1;
if(info->xfer_mode == TYPE_I)
{
while ((n = read(fd, buf, FTPD_DATASIZE)) > 0)
{
if(send(s, buf, n, 0) != n)
break;
sched_yield();
}
}
else if (info->xfer_mode == TYPE_A)
{
int rest = 0;
while (rest == 0 && (n = read(fd, buf, FTPD_DATASIZE)) > 0)
{
char const* e = buf;
char const* b;
int i;
rest = n;
do
{
char lf = '\0';
b = e;
for(i = 0; i < rest; ++i, ++e)
{
if(*e == '\n')
{
lf = '\n';
break;
}
}
if(send(s, b, i, 0) != i)
break;
if(lf == '\n')
{
if(send(s, "\r\n", 2, 0) != 2)
break;
++e;
++i;
}
}
while((rest -= i) > 0);
sched_yield();
}
}
if (0 == n)
{
if (0 == close(fd))
{
fd = -1;
res = 1;
}
}
}
if (-1 != fd)
close(fd);
if (0 == res)
send_reply(info, 451, "File read error.");
else
send_reply(info, 226, "Transfer complete.");
close_data_socket(info);
return;
}
/*
* discard
*
* Analog of `write' routine that just discards passed data
*
* Input parameters:
* fd - file descriptor (ignored)
* buf - data to write (ignored)
* count - number of bytes in `buf'
*
* Output parameters:
* returns `count'
*
*/
static ssize_t
discard(int fd, void const* buf, size_t count)
{
(void)fd;
(void)buf;
return count;
}
/*
* command_store
*
* Performs the "STOR" command (receive data from client).
*
* Input parameters:
* info - corresponding SessionInfo structure
* char *filename - Destination filename.
*
* Output parameters:
* NONE
*/
static void
command_store(FTPD_SessionInfo_t *info, char const *filename)
{
int s;
int n;
unsigned long size = 0;
struct rtems_ftpd_hook *usehook = NULL;
char buf[FTPD_DATASIZE];
int res = 1;
int bare_lfs = 0;
int null = 0;
typedef ssize_t (*WriteProc)(int, void const*, size_t);
WriteProc wrt = &write;
if(!can_write() || !info->auth)
{
send_reply(info, 550, "Access denied.");
return;
}
send_mode_reply(info);
s = data_socket(info);
if(0 > s)
return;
null = !strcmp("/dev/null", filename);
if (null)
{
/* File "/dev/null" just throws data away.
* FIXME: this is hack. Using `/dev/null' filesystem entry would be
* better.
*/
wrt = &discard;
}
if (!null && ftpd_config->hooks != NULL)
{
/* Search our list of hooks to see if we need to do something special. */
struct rtems_ftpd_hook *hook;
int i;
i = 0;
hook = &ftpd_config->hooks[i++];
while (hook->filename != NULL)
{
if (!strcmp(hook->filename, filename))
{
usehook = hook;
break;
}
hook = &ftpd_config->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 = ftpd_config->max_hook_filesize + 1;
/*
* Allocate space for our "file".
*/
bigBufr = (char *)malloc(filesize);
if (bigBufr == NULL)
{
send_reply(info, 451, "Local resource failure: malloc.");
close_data_socket(info);
return;
}
/*
* Retrieve the file into our buffer space.
*/
size = 0;
while ((n = recv(s, bigBufr + size, filesize - size, 0)) > 0)
{
size += n;
}
if (size >= filesize)
{
send_reply(info, 451, "File too long: buffer size exceeded.");
free(bigBufr);
close_data_socket(info);
return;
}
/*
* Call our hook.
*/
res = (usehook->hook_function)(bigBufr, size) == 0;
free(bigBufr);
if(!res)
{
send_reply(info, 451, "File processing failed.");
close_data_socket(info);
return;
}
}
else
{
/* Data transfer to regular file or /dev/null. */
int fd = 0;
if(!null)
fd = creat(filename,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (0 > fd)
{
send_reply(info, 550, "Error creating file.");
close_data_socket(info);
return;
}
if(info->xfer_mode == TYPE_I)
{
while ((n = recv(s, buf, FTPD_DATASIZE, 0)) > 0)
{
if (wrt(fd, buf, n) != n)
{
res = 0;
break;
}
sched_yield();
}
}
else if(info->xfer_mode == TYPE_A)
{
int rest = 0;
int pended_cr = 0;
while (res && rest == 0 && (n = recv(s, buf, FTPD_DATASIZE, 0)) > 0)
{
char const* e = buf;
char const* b;
int i;
rest = n;
if(pended_cr && *e != '\n')
{
char const lf = '\r';
pended_cr = 0;
if(wrt(fd, &lf, 1) != 1)
{
res = 0;
break;
}
}
do
{
int count;
int sub = 0;
b = e;
for(i = 0; i < rest; ++i, ++e)
{
int pcr = pended_cr;
pended_cr = 0;
if(*e == '\r')
{
pended_cr = 1;
}
else if(*e == '\n')
{
if(pcr)
{
sub = 2;
++i;
++e;
break;
}
++bare_lfs;
}
}
if(res == 0)
break;
count = i - sub - pended_cr;
if(count > 0 && wrt(fd, b, count) != count)
{
res = 0;
break;
}
if(sub == 2 && wrt(fd, e - 1, 1) != 1)
res = 0;
}
while((rest -= i) > 0);
sched_yield();
}
}
if (0 > close(fd) || res == 0)
{
send_reply(info, 452, "Error writing file.");
close_data_socket(info);
return;
}
}
if (bare_lfs > 0)
{
snprintf(buf, FTPD_BUFSIZE,
"Transfer complete. WARNING! %d bare linefeeds received in ASCII mode.",
bare_lfs);
send_reply(info, 226, buf);
}
else
send_reply(info, 226, "Transfer complete.");
close_data_socket(info);
}
/*
* 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:
* returns 0 on failure, 1 on success
*
*/
static int
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] != '/')
{
buf[plen++] = '/';
if(plen >= FTPD_BUFSIZE)
return 0;
buf[plen] = '\0';
}
}
if(plen + alen >= FTPD_BUFSIZE)
return 0;
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 };
time_t SIX_MONTHS = (365L*24L*60L*60L)/2L;
char timeBuf[SIZE];
gmtime_r(&tf, &bt);
if(curTime > tf + SIX_MONTHS || tf > curTime + SIX_MONTHS)
strftime (timeBuf, SIZE, "%b %d %Y", &bt);
else
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
);
if(send(s, buf, len, 0) != len)
return 0;
}
}
else
{
int len = snprintf(buf, FTPD_BUFSIZE, "%s\r\n", fname);
if(send(s, buf, len, 0) != len)
return 0;
}
return 1;
}
/*
* command_list
*
* Send file list to client.
*
* Input parameters:
* info - corresponding SessionInfo structure
* char *fname - File (or directory) to list.
*
* Output parameters:
* NONE
*/
static void
command_list(FTPD_SessionInfo_t *info, char const *fname, int wide)
{
int s;
DIR *dirp = 0;
struct dirent *dp = 0;
struct stat stat_buf;
char buf[FTPD_BUFSIZE];
time_t curTime;
int sc = 1;
if(!info->auth)
{
send_reply(info, 550, "Access denied.");
return;
}
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 = ".";
if (0 > stat(fname, &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(fname))))
{
snprintf(buf, FTPD_BUFSIZE,
"%s: Can not open directory.\r\n", fname);
send(s, buf, strlen(buf), 0);
}
else
{
time(&curTime);
if(!dirp && *fname)
sc = sc && send_dirline(s, wide, curTime, fname, "", fname, buf);
else {
/* FIXME: need "." and ".." only when '-a' option is given */
sc = sc && send_dirline(s, wide, curTime, fname, "", ".", buf);
sc = sc && send_dirline(s, wide, curTime, fname,
(strcmp(fname, ftpd_root) ? ".." : ""), "..", buf);
while (sc && (dp = readdir(dirp)) != NULL)
sc = sc &&
send_dirline(s, wide, curTime, fname, dp->d_name, dp->d_name, buf);
}
}
if(dirp)
closedir(dirp);
close_data_socket(info);
if(sc)
send_reply(info, 226, "Transfer complete.");
else
send_reply(info, 426, "Connection aborted.");
}
/*
* command_cwd
*
* Change current working directory.
*
* Input parameters:
* info - corresponding SessionInfo structure
* dir - directory name passed in CWD command
*
* Output parameters:
* NONE
*
*/
static void
command_cwd(FTPD_SessionInfo_t *info, char *dir)
{
if(!info->auth)
{
send_reply(info, 550, "Access denied.");
return;
}
if(chdir(dir) == 0)
send_reply(info, 250, "CWD command successful.");
else
send_reply(info, 550, "CWD command failed.");
}
/*
* command_pwd
*
* Send current working directory to client.
*
* Input parameters:
* info - corresponding SessionInfo structure
*
* Output parameters:
* NONE
*/
static void
command_pwd(FTPD_SessionInfo_t *info)
{
char buf[FTPD_BUFSIZE];
char const* cwd;
errno = 0;
buf[0] = '"';
if(!info->auth)
{
send_reply(info, 550, "Access denied.");
return;
}
cwd = getcwd(buf + 1, FTPD_BUFSIZE - 4);
if(cwd)
{
int len = strlen(cwd);
static char const txt[] = "\" is the current directory.";
int size = sizeof(txt);
if(len + size + 1 >= FTPD_BUFSIZE)
size = FTPD_BUFSIZE - len - 2;
memcpy(buf + len + 1, txt, size);
buf[len + size] = '\0';
send_reply(info, 250, buf);
}
else {
snprintf(buf, FTPD_BUFSIZE, "Error: %s.", serr());
send_reply(info, 452, buf);
}
}
/*
* command_mdtm
*
* Handle FTP MDTM command (send file modification time to client)/
*
* 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(!info->auth)
{
send_reply(info, 550, "Access denied.");
return;
}
if (0 > stat(fname, &stbuf))
{
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);
}
}
static void
command_size(FTPD_SessionInfo_t *info, char const* fname)
{
struct stat stbuf;
char buf[FTPD_BUFSIZE];
if(!info->auth)
{
send_reply(info, 550, "Access denied.");
return;
}
if (info->xfer_mode != TYPE_I || 0 > stat(fname, &stbuf) || stbuf.st_size < 0)
{
send_reply(info, 550, "Could not get file size.");
}
else
{
snprintf(buf, FTPD_BUFSIZE, "%" PRIuMAX, (uintmax_t) stbuf.st_size);
send_reply(info, 213, buf);
}
}
/*
* command_port
*
* This procedure fills address for data connection given the IP address and
* port of the remote machine.
*
* Input parameters:
* info - corresponding SessionInfo structure
* args - arguments to the "PORT" command.
*
* Output parameters:
* info->data_addr is set according to arguments of the PORT command.
* info->use_default is set to 0 on success, 1 on failure.
*/
static void
command_port(FTPD_SessionInfo_t *info, char const *args)
{
enum { NUM_FIELDS = 6 };
unsigned int a[NUM_FIELDS];
int n;
close_data_socket(info);
n = sscanf(args, "%u,%u,%u,%u,%u,%u", a+0, a+1, a+2, a+3, a+4, a+5);
if(NUM_FIELDS == n)
{
int i;
union {
uint8_t b[NUM_FIELDS];
struct {
uint32_t ip;
uint16_t port;
} u ;
} ip_info;
for(i = 0; i < NUM_FIELDS; ++i)
{
if(a[i] > 255)
break;
ip_info.b[i] = (uint8_t)a[i];
}
if(i == NUM_FIELDS)
{
/* Note: while it contradicts with RFC959, we don't allow PORT command
* to specify IP address different than those of the originating client
* for the sake of safety. */
if (ip_info.u.ip == info->def_addr.sin_addr.s_addr)
{
info->data_addr.sin_addr.s_addr = ip_info.u.ip;
info->data_addr.sin_port = ip_info.u.port;
info->data_addr.sin_family = AF_INET;
memset(info->data_addr.sin_zero, 0, sizeof(info->data_addr.sin_zero));
info->use_default = 0;
send_reply(info, 200, "PORT command successful.");
return; /* success */
}
else
{
send_reply(info, 425, "Address doesn't match peer's IP.");
return;
}
}
}
send_reply(info, 501, "Syntax error.");
}
/*
* command_pasv
*
* Handle FTP PASV command.
* Open socket, listen for and accept connection on it.
*
* Input parameters:
* info - corresponding SessionInfo structure
*
* Output parameters:
* info->pasv_socket is set to the descriptor of the data socket
*/
static void
command_pasv(FTPD_SessionInfo_t *info)
{
int s = -1;
int err = 1;
close_data_socket(info);
s = socket(PF_INET, SOCK_STREAM, 0);
if (s < 0)
syslog(LOG_ERR, "ftpd: Error creating PASV socket: %s", serr());
else
{
struct sockaddr_in addr;
socklen_t addrLen = sizeof(addr);
addr = info->ctrl_addr;
addr.sin_port = htons(0);
if (0 > bind(s, (struct sockaddr *)&addr, addrLen))
syslog(LOG_ERR, "ftpd: Error binding PASV socket: %s", serr());
else if (0 > listen(s, 1))
syslog(LOG_ERR, "ftpd: Error listening on PASV socket: %s", serr());
else if(set_socket_timeout(s, info->idle))
{
char buf[FTPD_BUFSIZE];
unsigned char const *ip, *p;
getsockname(s, (struct sockaddr *)&addr, &addrLen);
ip = (unsigned char const*)&(addr.sin_addr);
p = (unsigned char const*)&(addr.sin_port);
snprintf(buf, FTPD_BUFSIZE, "Entering passive mode (%u,%u,%u,%u,%u,%u).",
ip[0], ip[1], ip[2], ip[3], p[0], p[1]);
send_reply(info, 227, buf);
info->pasv_socket = accept(s, (struct sockaddr *)&addr, &addrLen);
if (0 > info->pasv_socket)
syslog(LOG_ERR, "ftpd: Error accepting PASV connection: %s", serr());
else
{
close_socket(s);
s = -1;
err = 0;
}
}
}
if(err)
{
/* (OSV) The note is from FreeBSD FTPD.
* Note: a response of 425 is not mentioned as a possible response to
* the PASV command in RFC959. However, it has been blessed as a
* legitimate response by Jon Postel in a telephone conversation
* with Rick Adams on 25 Jan 89. */
send_reply(info, 425, "Can't open passive connection.");
close_socket(s);
}
}
/*
* 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;
char* last = NULL;
while(1) {
while(isspace((unsigned char)*buf))
++buf;
if(*buf == '-') {
if(*++buf == '-') { /* `--' should terminate options */
if(isspace((unsigned char)*++buf)) {
last = buf;
do ++buf;
while(isspace((unsigned char)*buf));
break;
}
}
while(*buf && !isspace((unsigned char)*buf))
++buf;
last = buf;
}
else
break;
}
if(last)
*last = '\0';
*p = buf;
}
/*
* split_command
*
* Split command into command itself, options, and arguments. Command itself
* is converted to upper case.
*
* Input parameters:
* buf - initial command string
*
* Output parameter:
* buf - is modified by inserting '\0' at ends of split entities
* cmd - upper-cased command code
* opts - string containing all the options
* args - string containing all the arguments
*/
static void
split_command(char *buf, char **cmd, char **opts, char **args)
{
char* eoc;
char* p = buf;
while(isspace((unsigned char)*p))
++p;
*cmd = p;
while(*p && !isspace((unsigned char)*p))
{
*p = toupper((unsigned char)*p);
++p;
}
eoc = p;
if(*p)
*p++ = '\0';
while(isspace((unsigned char)*p))
++p;
*opts = p;
skip_options(&p);
*args = p;
if(*opts == p)
*opts = eoc;
while(*p && *p != '\r' && *p != '\n')
++p;
if(*p)
*p++ = '\0';
}
/*
* exec_command
*
* Parse and execute FTP command.
*
* FIXME: This section is somewhat of a hack. We should have a better
* way to parse commands.
*
* Input parameters:
* info - corresponding SessionInfo structure
* cmd - command to be executed (upper-case)
* args - arguments of the command
*
* Output parameters:
* NONE
*/
static void
exec_command(FTPD_SessionInfo_t *info, char* cmd, char* args)
{
char fname[FTPD_BUFSIZE];
int wrong_command = 0;
fname[0] = '\0';
if (!strcmp("PORT", cmd))
{
command_port(info, args);
}
else if (!strcmp("PASV", cmd))
{
command_pasv(info);
}
else if (!strcmp("RETR", cmd))
{
strncpy(fname, args, 254);
command_retrieve(info, fname);
}
else if (!strcmp("STOR", cmd))
{
strncpy(fname, args, 254);
command_store(info, fname);
}
else if (!strcmp("LIST", cmd))
{
strncpy(fname, args, 254);
command_list(info, fname, 1);
}
else if (!strcmp("NLST", cmd))
{
strncpy(fname, args, 254);
command_list(info, fname, 0);
}
else if (!strcmp("MDTM", cmd))
{
strncpy(fname, args, 254);
command_mdtm(info, fname);
}
else if (!strcmp("SIZE", cmd))
{
strncpy(fname, args, 254);
command_size(info, fname);
}
else if (!strcmp("SYST", cmd))
{
send_reply(info, 215, FTPD_SYSTYPE);
}
else if (!strcmp("TYPE", cmd))
{
if (args[0] == 'I')
{
info->xfer_mode = TYPE_I;
send_reply(info, 200, "Type set to I.");
}
else if (args[0] == 'A')
{
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 (!strcmp("USER", cmd))
{
sscanf(args, "%254s", fname);
if (info->user)
free(info->user);
if (info->pass)
free(info->pass);
info->pass = NULL;
info->user = strdup(fname);
if (ftpd_config->login &&
!ftpd_config->login(info->user, NULL)) {
info->auth = false;
send_reply(info, 331, "User name okay, need password.");
} else {
info->auth = true;
send_reply(info, 230, "User logged in.");
}
}
else if (!strcmp("PASS", cmd))
{
sscanf(args, "%254s", fname);
if (info->pass)
free(info->pass);
info->pass = strdup(fname);
if (!info->user) {
send_reply(info, 332, "Need account to log in");
} else {
if (ftpd_config->login &&
!ftpd_config->login(info->user, info->pass)) {
info->auth = false;
send_reply(info, 530, "Not logged in.");
} else {
info->auth = true;
send_reply(info, 230, "User logged in.");
}
}
}
else if (!strcmp("DELE", cmd))
{
if(!can_write() || !info->auth)
{
send_reply(info, 550, "Access denied.");
}
else if (
strncpy(fname, args, 254) &&
unlink(fname) == 0)
{
send_reply(info, 257, "DELE successful.");
}
else
{
send_reply(info, 550, "DELE failed.");
}
}
else if (!strcmp("SITE", cmd))
{
char* opts;
split_command(args, &cmd, &opts, &args);
if(!strcmp("CHMOD", cmd))
{
int mask;
if(!can_write() || !info->auth)
{
send_reply(info, 550, "Access denied.");
}
else {
char *c;
c = strchr(args, ' ');
if((c != NULL) && (sscanf(args, "%o", &mask) == 1) && strncpy(fname, c+1, 254)
&& (chmod(fname, (mode_t)mask) == 0))
send_reply(info, 257, "CHMOD successful.");
else
send_reply(info, 550, "CHMOD failed.");
}
}
else
wrong_command = 1;
}
else if (!strcmp("RMD", cmd))
{
if(!can_write() || !info->auth)
{
send_reply(info, 550, "Access denied.");
}
else if (
strncpy(fname, args, 254) &&
rmdir(fname) == 0)
{
send_reply(info, 257, "RMD successful.");
}
else
{
send_reply(info, 550, "RMD failed.");
}
}
else if (!strcmp("MKD", cmd))
{
if(!can_write() || !info->auth)
{
send_reply(info, 550, "Access denied.");
}
else if (
strncpy(fname, args, 254) &&
mkdir(fname, S_IRWXU | S_IRWXG | S_IRWXO) == 0)
{
send_reply(info, 257, "MKD successful.");
}
else
{
send_reply(info, 550, "MKD failed.");
}
}
else if (!strcmp("CWD", cmd))
{
strncpy(fname, args, 254);
command_cwd(info, fname);
}
else if (!strcmp("CDUP", cmd))
{
command_cwd(info, "..");
}
else if (!strcmp("PWD", cmd))
{
command_pwd(info);
}
else
wrong_command = 1;
if(wrong_command)
send_reply(info, 500, "Command not understood.");
}
/*
* session
*
* This task handles single session. It is waked up 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.
*
* Input parameters:
* arg - pointer to corresponding SessionInfo.
*
* Output parameters:
* NONE
*/
static void
session(rtems_task_argument arg)
{
FTPD_SessionInfo_t *const info = (FTPD_SessionInfo_t *)arg;
int chroot_made = 0;
rtems_libio_set_private_env();
/* chroot() can fail here because the directory may not exist yet. */
chroot_made = chroot(ftpd_root) == 0;
while(1)
{
rtems_event_set set;
int rv;
rtems_event_receive(FTPD_RTEMS_EVENT, RTEMS_EVENT_ANY, RTEMS_NO_TIMEOUT,
&set);
chroot_made = chroot_made || chroot(ftpd_root) == 0;
rv = chroot_made ? chdir("/") : -1;
errno = 0;
if (rv == 0)
{
send_reply(info, 220, FTPD_SERVER_MESSAGE);
while (1)
{
char buf[FTPD_BUFSIZE];
char *cmd, *opts, *args;
if (fgets(buf, FTPD_BUFSIZE, info->ctrl_fp) == NULL)
{
syslog(LOG_INFO, "ftpd: Connection aborted.");
break;
}
split_command(buf, &cmd, &opts, &args);
if (!strcmp("QUIT", cmd))
{
send_reply(info, 221, "Goodbye.");
break;
}
else
{
exec_command(info, cmd, args);
}
}
}
else
{
send_reply(info, 421, "Service not available, closing control connection.");
}
/*
* Go back to the root directory. A use case is to release a current
* directory in a mounted file system on dynamic media, e.g. USB stick.
* The return value can be ignored since the next session will try do the
* operation again and an error check is performed in this case.
*/
chdir("/");
/* Close connection and put ourselves back into the task pool. */
close_data_socket(info);
close_stream(info);
free(info->user);
free(info->pass);
task_pool_release(info);
}
}
/*
* ftpd_daemon
*
* This task runs forever. It waits for service requests on the FTP port
* (port 21 by default). When a request is received, it opens a new session
* to handle those requests until the connection is closed.
*
* Input parameters:
* NONE
*
* Output parameters:
* NONE
*/
static void
ftpd_daemon(rtems_task_argument args __attribute__((unused)))
{
int s;
socklen_t addrLen;
struct sockaddr_in addr;
FTPD_SessionInfo_t *info = NULL;
s = socket(PF_INET, SOCK_STREAM, 0);
if (s < 0)
syslog(LOG_ERR, "ftpd: Error creating socket: %s", serr());
addr.sin_family = AF_INET;
addr.sin_port = htons(ftpd_config->port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
if (0 > bind(s, (struct sockaddr *)&addr, sizeof(addr)))
syslog(LOG_ERR, "ftpd: Error binding control socket: %s", serr());
else if (0 > listen(s, 1))
syslog(LOG_ERR, "ftpd: Error listening on control socket: %s", serr());
else while (1)
{
int ss;
addrLen = sizeof(addr);
ss = accept(s, (struct sockaddr *)&addr, &addrLen);
if (0 > ss)
syslog(LOG_ERR, "ftpd: Error accepting control connection: %s", serr());
else if(!set_socket_timeout(ss, ftpd_timeout))
close_socket(ss);
else
{
info = task_pool_obtain();
if (NULL == info)
{
close_socket(ss);
}
else
{
info->ctrl_socket = ss;
if ((info->ctrl_fp = fdopen(info->ctrl_socket, "r+")) == NULL)
{
syslog(LOG_ERR, "ftpd: fdopen() on socket failed: %s", serr());
close_stream(info);
task_pool_release(info);
}
else
{
/* Initialize corresponding SessionInfo structure */
info->def_addr = addr;
if(0 > getsockname(ss, (struct sockaddr *)&addr, &addrLen))
{
syslog(LOG_ERR, "ftpd: getsockname(): %s", serr());
close_stream(info);
task_pool_release(info);
}
else
{
info->use_default = 1;
info->ctrl_addr = addr;
info->pasv_socket = -1;
info->data_socket = -1;
info->xfer_mode = TYPE_A;
info->data_addr.sin_port =
htons(ntohs(info->ctrl_addr.sin_port) - 1);
info->idle = ftpd_timeout;
info->user = NULL;
info->pass = NULL;
if (ftpd_config->login)
info->auth = false;
else
info->auth = true;
/* Wakeup the session task. The task will call task_pool_release
after it closes connection. */
rtems_event_send(info->tid, FTPD_RTEMS_EVENT);
}
}
}
}
}
rtems_task_delete(RTEMS_SELF);
}
/*
* rtems_ftpd_start
*
* 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.
*
*
* Input parameters:
* config: constant initial setup.
* Output parameters:
* returns RTEMS_SUCCESSFUL on successful start of the daemon.
*/
int
rtems_ftpd_start(const struct rtems_ftpd_configuration* config)
{
rtems_status_code sc;
rtems_id tid;
rtems_task_priority priority;
int count;
if (ftpd_config != NULL)
return RTEMS_RESOURCE_IN_USE;
ftpd_config = malloc(sizeof(*ftpd_config));
if (ftpd_config == NULL)
return RTEMS_NO_MEMORY;
*ftpd_config = *config;
if (ftpd_config->port == 0)
{
ftpd_config->port = FTPD_CONTROL_PORT;
}
if (ftpd_config->priority == 0)
{
ftpd_config->priority = 40;
}
priority = ftpd_config->priority;
ftpd_timeout = ftpd_config->idle;
if (ftpd_timeout < 0)
ftpd_timeout = 0;
ftpd_config->idle = ftpd_timeout;
ftpd_access = ftpd_config->access;
ftpd_root = "/";
if (ftpd_config->root && ftpd_config->root[0] == '/' )
ftpd_root = ftpd_config->root;
ftpd_config->root = ftpd_root;
if (ftpd_config->tasks_count <= 0)
ftpd_config->tasks_count = 1;
count = ftpd_config->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, RTEMS_MINIMUM_STACK_SIZE,
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, ftpd_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;
}
if (ftpd_config->verbose)
syslog(LOG_INFO, "ftpd: FTP daemon started (%d session%s max)",
count, ((count > 1) ? "s" : ""));
return RTEMS_SUCCESSFUL;
}