Files
rtems/cpukit/ftpd/ftpd.c
T
Sebastian Huber b4d322971c cpukit: Group the last ungrouped files
Seventy-three files had a Doxygen block but no @ingroup line.  Doxygen
documented them, but no group page listed them.  The PCI headers, the
driver manager headers and 46 headers under include/rtems were affected.

Give each file the group of the component it belongs to.  The seven
default file system files follow the table they fill.  A file which
provides an entry of rtems_filesystem_operations_table joins LibIOFSOps.
A file which provides an entry of rtems_filesystem_file_handlers_r joins
LibIOFSHandler.

Every file in cpukit with a Doxygen block names a group.  The files
malloc.h, todimpl.h and printk_plugin.c name two groups.  The other
files name one.

Updates #5717.

Assisted-by: Claude:claude-opus-5 claude-code
Signed-off-by: Sebastian Huber <sebastian.huber@embedded-brains.de>
2026-09-01 20:17:20 +00:00

2117 lines
55 KiB
C

/* SPDX-License-Identifier: BSD-2-Clause */
/**
* @file
*
* @ingroup RTEMSImplFTPD
*
* @brief
*/
/* 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. Seems 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 not 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.
*************************************************************************/
/*
* Copyright (C) 1999 Jake Janovetz <janovetz@tempest.ece.uiuc.edu>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef 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/error.h>
#include <rtems/ftpd.h>
#include <rtems/libio.h>
#include <rtems/thread.h>
#include <rtems/userenv.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/ftp.h>
#include <netinet/in.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"
/* Seems 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 user_buf[ 256 ]; /* user name buffer */
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_mutex mutex;
rtems_counting_semaphore 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;
static void yield( void )
{
/*
* If we do not build for the legacy network stack, then we use the libbsd. In
* the libbsd there is no global network stack semaphore which provides round
* robin fairness for threads of equal priority.
*/
#ifndef RTEMS_NETWORKING
sched_yield();
#endif
}
/*
* 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 );
}
free( task_pool.info );
free( task_pool.queue );
rtems_mutex_destroy( &task_pool.mutex );
rtems_counting_semaphore_destroy( &task_pool.sem );
task_pool.info = 0;
task_pool.queue = 0;
task_pool.count = 0;
}
/*
* 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;
rtems_mutex_init( &task_pool.mutex, "FTPD" );
rtems_counting_semaphore_init(
&task_pool.sem,
"FTPD",
(unsigned int) count
);
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_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_counting_semaphore_wait( &task_pool.sem );
rtems_mutex_lock( &task_pool.mutex );
info = task_pool.queue[ task_pool.head ];
if ( ++task_pool.head >= task_pool.count ) {
task_pool.head = 0;
}
rtems_mutex_unlock( &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_mutex_lock( &task_pool.mutex );
task_pool.queue[ task_pool.tail ] = info;
if ( ++task_pool.tail >= task_pool.count ) {
task_pool.tail = 0;
}
rtems_mutex_unlock( &task_pool.mutex );
rtems_counting_semaphore_post( &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, const 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 void set_socket_timeout( int s, int seconds )
{
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()
);
}
}
/*
* 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;
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;
}
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 );
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;
}
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 );
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 false on failure, true on success
*
*/
static bool send_dirline(
int s,
bool wide,
time_t curTime,
char const *path,
char const *add,
char const *fname,
char *buf
)
{
struct stat stat_buf;
size_t plen = strlen( path );
size_t alen = strlen( add );
int slen = 0;
if ( plen == 0 ) {
buf[ plen++ ] = '/';
buf[ plen ] = '\0';
} else {
buf = memcpy( buf, path, plen + 1 );
if ( alen > 0 && buf[ plen - 1 ] != '/' ) {
buf[ plen++ ] = '/';
if ( plen >= FTPD_BUFSIZE ) {
return 0;
}
buf[ plen ] = '\0';
}
}
if ( plen + alen >= FTPD_BUFSIZE ) {
return 0;
}
memcpy( buf + plen, add, alen + 1 );
if ( stat( buf, &stat_buf ) == 0 ) {
if ( wide ) {
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 );
}
slen = 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
);
} else {
slen = snprintf( buf, FTPD_BUFSIZE, "%s\r\n", fname );
}
} else {
slen = snprintf(
buf,
FTPD_BUFSIZE,
"%s: %s.\r\n",
fname,
strerror( errno )
);
}
if ( slen >= FTPD_BUFSIZE ) {
static const char dots[] = { '.', '.', '.', '\r', '\n' };
/*
* The file name exceeds the send buffer, indicate this with a ... at the
* end of the line.
*/
slen = FTPD_BUFSIZE - 1;
memcpy( &buf[ slen - sizeof( dots ) ], dots, sizeof( dots ) );
}
if ( slen > 0 && send( s, buf, (size_t) slen, 0 ) != slen ) {
return false;
}
return true;
}
/*
* 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,
bool wide
)
{
int s;
DIR *dirp = 0;
struct dirent *dp = 0;
char buf[ FTPD_BUFSIZE ];
time_t curTime;
bool ok = true;
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 = ".";
}
time( &curTime );
dirp = opendir( fname );
if ( dirp != NULL ) {
/* FIXME: need "." and ".." only when '-a' option is given */
ok = ok && send_dirline( s, wide, curTime, fname, "", ".", buf );
ok = ok && send_dirline(
s,
wide,
curTime,
fname,
( strcmp( fname, ftpd_root ) ? ".." : "" ),
"..",
buf
);
while ( ok && ( dp = readdir( dirp ) ) != NULL ) {
ok =
ok &&
send_dirline( s, wide, curTime, fname, dp->d_name, dp->d_name, buf );
}
closedir( dirp );
} else {
send_dirline( s, wide, curTime, fname, "", fname, buf );
}
close_data_socket( info );
if ( ok ) {
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, const 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 RFC959, we don't allow PORT command
* to specify IP address different from that 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() );
}
{
set_socket_timeout( s, info->idle );
if ( 0 == getsockname( s, (struct sockaddr *) &addr, &addrLen ) ) {
char buf[ FTPD_BUFSIZE ];
unsigned char const *ip, *p;
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;
}
} else {
syslog( LOG_ERR, "ftpd: Cannot get socket name: %s", serr() );
}
}
}
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 )
{
int wrong_command = 0;
if ( !strcmp( "PORT", cmd ) ) {
command_port( info, args );
} else if ( !strcmp( "PASV", cmd ) ) {
command_pasv( info );
} else if ( !strcmp( "RETR", cmd ) ) {
command_retrieve( info, args );
} else if ( !strcmp( "STOR", cmd ) ) {
command_store( info, args );
} else if ( !strcmp( "LIST", cmd ) ) {
command_list( info, args, true );
} else if ( !strcmp( "NLST", cmd ) ) {
command_list( info, args, false );
} else if ( !strcmp( "MDTM", cmd ) ) {
command_mdtm( info, args );
} else if ( !strcmp( "SIZE", cmd ) ) {
command_size( info, args );
} 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 ) ) {
strlcpy( info->user_buf, args, sizeof( info->user_buf ) );
info->user = info->user_buf;
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 ) ) {
if ( !info->user ) {
send_reply( info, 332, "Need account to log in" );
} else {
if ( ftpd_config->login && !ftpd_config->login( info->user, args ) ) {
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 ( unlink( args ) == 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 ) &&
( chmod( c + 1, (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 ( rmdir( args ) == 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 ( mkdir( args, 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 ) ) {
command_cwd( info, args );
} 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 woken 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;
bool chroot_made = false;
while ( 1 ) {
rtems_event_set set;
rtems_event_receive(
FTPD_RTEMS_EVENT,
RTEMS_EVENT_ANY,
RTEMS_NO_TIMEOUT,
&set
);
chroot_made = chroot_made ||
( rtems_libio_set_private_env() == RTEMS_SUCCESSFUL &&
chroot( ftpd_root ) == 0 );
/*
* The chdir() must immediately follow the chroot(), otherwise static
* analysis tools may complain about a security issue.
*/
if ( chroot_made && chdir( "/" ) == 0 ) {
send_reply( info, 220, FTPD_SERVER_MESSAGE );
while ( 1 ) {
char buf[ FTPD_BUFSIZE ];
char *cmd, *opts, *args;
size_t len;
if ( fgets( buf, FTPD_BUFSIZE, info->ctrl_fp ) == NULL ) {
syslog( LOG_INFO, "ftpd: Connection aborted." );
break;
}
len = strlen( buf );
if ( len == 0 ) {
continue;
}
if ( buf[ len - 1 ] != '\n' ) {
send_reply( info, 501, "Command line too long." );
/*
* We could also try to continue here; however, discarding the rest
* of the current command line and figuring out when the next command
* starts with fgets() is not that easy. It would be better to avoid
* the FILE stream and just use the socket directly with send() and
* recv().
*/
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 );
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 RTEMS_UNUSED )
{
int s;
socklen_t addrLen;
struct sockaddr_in addr;
memset( &addr, 0, sizeof( addr ) );
addr.sin_family = AF_INET;
addr.sin_port = htons( ftpd_config->port );
addr.sin_addr.s_addr = htonl( INADDR_ANY );
s = socket( PF_INET, SOCK_STREAM, 0 );
if ( s < 0 ) {
syslog( LOG_ERR, "ftpd: Error creating control socket: %s", serr() );
} else 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 {
FTPD_SessionInfo_t *info;
set_socket_timeout( ss, ftpd_timeout );
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;
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 );
}
}
}
}
}
}
close( s );
rtems_task_exit();
}
/*
* 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.
*/
rtems_status_code 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_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;
}