Back to home page

LXR

 
 

    


File indexing completed on 2025-05-11 08:24:11

0001 /* SPDX-License-Identifier: BSD-2-Clause */
0002 
0003 /**
0004  * @file
0005  *
0006  * @brief
0007  */
0008 
0009 /* FIXME: 1. Parse command is a hack.  We can do better.
0010  *        2. OSV: hooks support seems to be bad, as it requires storing of
0011  *           entire input file in memory.  Seems to be better to change it to
0012  *           something more reasonable, like having
0013  *           'hook_write(void const *buf, int count)' routine that will be
0014  *           called multiple times while file is being received.
0015  *        3. OSV: Remove hack with "/dev/null"?
0016  *
0017  *  FTP Server Daemon
0018  *
0019  *  Submitted by: Jake Janovetz <janovetz@tempest.ece.uiuc.edu>
0020  *
0021  *  Changed by:   Sergei Organov <osv@javad.ru> (OSV)
0022  *                Arnout Vandecappelle <arnout@mind.be> (AV)
0023  *                Sebastien Bourdeauducq <sebastien@milkymist.org> (MM)
0024  *                
0025  *
0026  *  Changes:
0027  *
0028  *    2010-12-02        Sebastien Bourdeauducq <sebastien@milkymist.org>
0029  *
0030  *      * Support spaces in filenames
0031  * 
0032  *    2010-04-29        Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>
0033  * 
0034  *      * Added USER/PASS authentication.
0035  * 
0036  *    2001-01-31        Sergei Organov <osv@javad.ru>
0037  *
0038  *      * Hacks with current dir and root dir removed in favor of new libio
0039  *        support for task-local current and root directories.
0040  *
0041  *    2001-01-30        Sergei Organov <osv@javad.ru>
0042  *
0043  *      * Bug in `close_data_socket()' introduced by previous change fixed.
0044  *      * `command_pasv()' changed to set timeout on socket we are listening on
0045  *        and code fixed to not close socket twice on error.
0046  *      * `serr()' changed to clear `errno'.
0047  *      * `data_socket()' changed to clear `errno' before `bind()'.
0048  *      * `session()' changed to clear `errno' before processing session.
0049  *
0050  *    2001-01-29        Sergei Organov <osv@javad.ru>
0051  *
0052  *      * `close_data_socket()' fixed to close both active and passive sockets
0053  *      * Initialize info->data_socket to -1 in `daemon()'
0054  *      * Initialize `fname' to empty string  in `exec_command()'
0055  *
0056  *    2001-01-22        Sergei Organov <osv@javad.ru>
0057  *
0058  *      * Timeouts on sockets implemented. 'idle' field added to
0059  *        configuration. No timeout by default to keep backward compatibility.
0060  *        Note: SITE IDLE command not implemented yet.
0061  *      * Basic global access control implemented. 'access' field added to
0062  *        configuration. No access limitations by default to keep backward
0063  *        compatibility.
0064  *
0065  *    2001-01-17        Sergei Organov <osv@javad.ru>
0066  *
0067  *      * Anchor data socket for active mode (using self IP and port 20.)
0068  *      * Fixed default data port support (still not tested).
0069  *      * Don't allow IP address different from originating host in
0070  *        PORT command to improve security.
0071  *      * Fixed bug in MDTM command.
0072  *      * Check for correctness of parsing of argument in command_port().
0073  *      * Fixed squeeze_path() to don't allow names like 'NAME/smth' where
0074  *        'NAME' is not a directory.
0075  *      * Command parsing a little bit improved: command names are now
0076  *        converted to upper-case to be more compatible with RFC (command
0077  *        names are not case-sensitive.)
0078  *      * Reformat comments so that they have RTEMS look and feel.
0079  *
0080  *    2001-01-16        Sergei Organov <osv@javad.ru>
0081  *
0082  *      * Fixed DELE, SITE CHMOD, RMD, MKD broken by previous changes
0083  *      * True ASCII mode implemented (doesn't work for hooks and /dev/null)
0084  *      * Passive mode implemented, PASV command added.
0085  *      * Default port for data connection could be used (untested, can't find
0086  *        ftp client that doesn't send PORT command)
0087  *      * SYST reply changed to UNIX, as former RTEMS isn't registered name.
0088  *      * Reply codes reviewed and fixed.
0089  *
0090  *    2001-01-08        Sergei Organov <osv@javad.ru>
0091  *
0092  *      * use pool of pre-created threads to handle sessions
0093  *      * LIST output now similar to what "/bin/ls -al" would output, thus
0094  *        FTP clients could parse it.
0095  *      * LIST NAME now works (both for files and directories)
0096  *      * keep track of CWD for every session separately
0097  *      * ability to specify root directory name in configuration table
0098  *      * options sent in commands are ignored, thus LIST -al FILE works
0099  *      * added support for NLST, CDUP and MDTM commands
0100  *      * buffers are allocated on stack instead of heap where possible
0101  *      * drop using of task notepad to pass parameters - use function
0102  *        arguments instead
0103  *      * various bug-fixes, e.g., use of PF_INET in socket() instead of
0104  *        AF_INET, use snprintf() instead of sprintf() everywhere for safety,
0105  *        etc.
0106  */
0107 
0108 /*************************************************************************
0109  *                                 ftpd.c
0110  *************************************************************************
0111  * Description:
0112  *
0113  *    This file contains the daemon which services requests that appear
0114  *    on the FTP port.  This server is compatible with FTP, but it
0115  *    also provides 'hooks' to make it usable in situations where files
0116  *    are not used/necessary.  Once the server is started, it runs
0117  *    forever.
0118  *
0119  *
0120  *    Organization:
0121  *
0122  *       The FTP daemon is started upon boot along with a (configurable)
0123  *       number of tasks to handle sessions.  It runs all the time and
0124  *       waits for connections on the known FTP port (21).  When
0125  *       a connection is made, it wakes-up a 'session' task.  That
0126  *       session then interacts with the remote host.  When the session
0127  *       is complete, the session task goes to sleep.  The daemon still
0128  *       runs, however.
0129  *
0130  *
0131  * Supported commands are:
0132  *
0133  * RETR xxx     - Sends a file from the client.
0134  * STOR xxx     - Receives a file from the client.  xxx = filename.
0135  * LIST xxx     - Sends a file list to the client.
0136  * NLST xxx     - Sends a file list to the client.
0137  * USER         - Does nothing.
0138  * PASS         - Does nothing.
0139  * SYST         - Replies with the system type (`RTEMS').
0140  * DELE xxx     - Delete file xxx.
0141  * MKD xxx      - Create directory xxx.
0142  * RMD xxx      - Remove directory xxx.
0143  * PWD          - Print working directory.
0144  * CWD xxx      - Change working directory.
0145  * CDUP         - Change to upper directory.
0146  * SITE CHMOD xxx yyy - Change permissions on file yyy to xxx.
0147  * PORT a,b,c,d,x,y   - Setup for a data port to IP address a.b.c.d
0148  *                      and port (x*256 + y).
0149  * MDTM xxx     - Send file modification date/time to the client.
0150  *                xxx = filename.
0151  * PASV         - Use passive mode data connection.
0152  *
0153  *
0154  * The public routines contained in this file are:
0155  *
0156  *    rtems_initialize_ftpd - Initializes and starts the server daemon,
0157  *                            then returns to its caller.
0158  *
0159  *------------------------------------------------------------------------
0160  * Jake Janovetz
0161  * University of Illinois
0162  * 1406 West Green Street
0163  * Urbana IL  61801
0164  *************************************************************************
0165  * Change History:
0166  *  12/01/97   - Creation (JWJ)
0167  *  2001-01-08 - Changes by OSV
0168  *  2010-04-29 - Authentication added by AV
0169  *************************************************************************/
0170 
0171 /*************************************************************************
0172  * Meanings of first and second digits of reply codes:
0173  *
0174  * Reply:  Description:
0175  *-------- --------------
0176  *  1yz    Positive preliminary reply.  The action is being started but
0177  *         expect another reply before sending another command.
0178  *  2yz    Positive completion reply.  A new command can be sent.
0179  *  3yz    Positive intermediate reply.  The command has been accepted
0180  *         but another command must be sent.
0181  *  4yz    Transient negative completion reply.  The requested action did
0182  *         not take place, but the error condition is temporary so the
0183  *         command can be reissued later.
0184  *  5yz    Permanent negative completion reply.  The command was not
0185  *         accepted and should not be retried.
0186  *-------------------------------------------------------------------------
0187  *  x0z    Syntax errors.
0188  *  x1z    Information.
0189  *  x2z    Connections.  Replies referring to the control or data
0190  *         connections.
0191  *  x3z    Authentication and accounting.  Replies for the login or
0192  *         accounting commands.
0193  *  x4z    Unspecified.
0194  *  x5z    Filesystem status.
0195  *************************************************************************/
0196 
0197 /*
0198  * Copyright (C) 1999 Jake Janovetz <janovetz@tempest.ece.uiuc.edu>
0199  *
0200  * Redistribution and use in source and binary forms, with or without
0201  * modification, are permitted provided that the following conditions
0202  * are met:
0203  * 1. Redistributions of source code must retain the above copyright
0204  *    notice, this list of conditions and the following disclaimer.
0205  * 2. Redistributions in binary form must reproduce the above copyright
0206  *    notice, this list of conditions and the following disclaimer in the
0207  *    documentation and/or other materials provided with the distribution.
0208  *
0209  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0210  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0211  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0212  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
0213  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
0214  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
0215  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
0216  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
0217  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
0218  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
0219  * POSSIBILITY OF SUCH DAMAGE.
0220  */
0221 
0222 #ifdef HAVE_CONFIG_H
0223 #include "config.h"
0224 #endif
0225 
0226 #include <stdio.h>
0227 #include <stdlib.h>
0228 #include <string.h>
0229 #include <unistd.h>
0230 #include <fcntl.h>
0231 #include <dirent.h>
0232 #include <errno.h>
0233 #include <ctype.h>
0234 #include <inttypes.h>
0235 #include <sched.h>
0236 
0237 #include <rtems.h>
0238 #include <rtems/error.h>
0239 #include <rtems/ftpd.h>
0240 #include <rtems/libio.h>
0241 #include <rtems/thread.h>
0242 #include <rtems/userenv.h>
0243 #include <syslog.h>
0244 
0245 #include <sys/types.h>
0246 #include <sys/socket.h>
0247 #include <arpa/ftp.h>
0248 #include <netinet/in.h>
0249 
0250 
0251 #ifdef __GNUC__
0252 /* change to #if 1 to disable syslog entirely */
0253 #if 0
0254 #undef  syslog
0255 #define syslog(a, b, ...) while(0){}
0256 #endif
0257 #endif
0258 
0259 #define FTPD_SERVER_MESSAGE  "RTEMS FTP server (Version 1.1-JWJ) ready."
0260 
0261 #define FTPD_SYSTYPE "UNIX Type: L8"
0262 
0263 /* Seems to be unused */
0264 #if 0
0265 #define FTPD_WELCOME_MESSAGE \
0266    "Welcome to the RTEMS FTP server.\n" \
0267    "\n" \
0268    "Login accepted.\n"
0269 #endif
0270 
0271 /* Event to be used by session tasks for waiting */
0272 enum
0273 {
0274   FTPD_RTEMS_EVENT = RTEMS_EVENT_1
0275 };
0276 
0277 /* Configuration table */
0278 static struct rtems_ftpd_configuration *ftpd_config;
0279 
0280 /* this is not prototyped in strict ansi mode */
0281 FILE *fdopen (int fildes, const char *mode);
0282 
0283 /*SessionInfo structure.
0284  *
0285  * The following structure is allocated for each session.
0286  */
0287 typedef struct
0288 {
0289   struct sockaddr_in  ctrl_addr;   /* Control connection self address */
0290   struct sockaddr_in  data_addr;   /* Data address set by PORT command */
0291   struct sockaddr_in  def_addr;    /* Default address for data */
0292   int                 use_default; /* 1 - use default address for data */
0293   FILE                *ctrl_fp;    /* File pointer for control connection */
0294   int                 ctrl_socket; /* Socket for ctrl connection */
0295   int                 pasv_socket; /* Socket for PASV connection */
0296   int                 data_socket; /* Socket for data connection */
0297   int                 idle;        /* Timeout in seconds */
0298   int                 xfer_mode;   /* Transfer mode (ASCII/binary) */
0299   rtems_id            tid;         /* Task id */
0300   char                *user;       /* user name (0 if not supplied) */
0301   char                user_buf[256]; /* user name buffer */
0302   bool                auth;        /* true if user/pass was valid, false if not or not supplied */
0303 } FTPD_SessionInfo_t;
0304 
0305 
0306 /*
0307  * TaskPool structure.
0308  */
0309 typedef struct
0310 {
0311   FTPD_SessionInfo_t       *info;
0312   FTPD_SessionInfo_t       **queue;
0313   int                      count;
0314   int                      head;
0315   int                      tail;
0316   rtems_mutex              mutex;
0317   rtems_counting_semaphore sem;
0318 } FTPD_TaskPool_t;
0319 
0320 /*
0321  * Task pool instance.
0322  */
0323 static FTPD_TaskPool_t task_pool;
0324 
0325 /*
0326  * Root directory
0327  */
0328 
0329 static char const* ftpd_root = "/";
0330 
0331 /*
0332  * Default idle timeout for sockets in seconds.
0333  */
0334 static int ftpd_timeout = 0;
0335 
0336 /*
0337  * Global access flags.
0338  */
0339 static int ftpd_access = 0;
0340 
0341 static void
0342 yield(void)
0343 {
0344 /*
0345  * If we do not build for the legacy network stack, then we use the libbsd.  In
0346  * the libbsd there is no global network stack semaphore which provides round
0347  * robin fairness for threads of equal priority.
0348  */
0349 #ifndef RTEMS_NETWORKING
0350   sched_yield();
0351 #endif
0352 }
0353 
0354 /*
0355  * serr
0356  *
0357  * Return error string corresponding to current 'errno'.
0358  *
0359  */
0360 static char const*
0361 serr(void)
0362 {
0363   int err = errno;
0364   errno = 0;
0365   return strerror(err);
0366 }
0367 
0368 /*
0369  * Utility routines for access control.
0370  *
0371  */
0372 
0373 static int
0374 can_read(void)
0375 {
0376   return (ftpd_access & FTPD_NO_READ) == 0;
0377 }
0378 
0379 static int
0380 can_write(void)
0381 {
0382   return (ftpd_access & FTPD_NO_WRITE) == 0;
0383 }
0384 
0385 /*
0386  * Task pool management routines
0387  *
0388  */
0389 
0390 
0391 /*
0392  * task_pool_done
0393  *
0394  * Cleanup task pool.
0395  *
0396  * Input parameters:
0397  *   count - number of entries in task pool to cleanup
0398  *
0399  * Output parameters:
0400  *   NONE
0401  *
0402  */
0403 static void
0404 task_pool_done(int count)
0405 {
0406   int i;
0407   for(i = 0; i < count; ++i)
0408     rtems_task_delete(task_pool.info[i].tid);
0409   free(task_pool.info);
0410   free(task_pool.queue);
0411   rtems_mutex_destroy(&task_pool.mutex);
0412   rtems_counting_semaphore_destroy(&task_pool.sem);
0413   task_pool.info = 0;
0414   task_pool.queue = 0;
0415   task_pool.count = 0;
0416 }
0417 
0418 /*
0419  * task_pool_init
0420  *
0421  * Initialize task pool.
0422  *
0423  * Input parameters:
0424  *   count    - number of entries in task pool to create
0425  *   priority - priority tasks are started with
0426  *
0427  * Output parameters:
0428  *   returns 1 on success, 0 on failure.
0429  *
0430  */
0431 static void session(rtems_task_argument arg); /* Forward declare */
0432 
0433 static int
0434 task_pool_init(int count, rtems_task_priority priority)
0435 {
0436   int i;
0437   rtems_status_code sc;
0438   char id = 'a';
0439 
0440   task_pool.count = 0;
0441   task_pool.head = task_pool.tail = 0;
0442 
0443   rtems_mutex_init(&task_pool.mutex, "FTPD");
0444   rtems_counting_semaphore_init(&task_pool.sem, "FTPD", (unsigned int) count);
0445 
0446   task_pool.info = (FTPD_SessionInfo_t*)
0447     malloc(sizeof(FTPD_SessionInfo_t) * count);
0448   task_pool.queue = (FTPD_SessionInfo_t**)
0449     malloc(sizeof(FTPD_SessionInfo_t*) * count);
0450   if (NULL == task_pool.info || NULL == task_pool.queue)
0451   {
0452     task_pool_done(0);
0453     syslog(LOG_ERR, "ftpd: Not enough memory");
0454     return 0;
0455   }
0456 
0457   for(i = 0; i < count; ++i)
0458   {
0459     FTPD_SessionInfo_t *info = &task_pool.info[i];
0460     sc = rtems_task_create(rtems_build_name('F', 'T', 'P', id),
0461       priority, FTPD_STACKSIZE,
0462       RTEMS_PREEMPT | RTEMS_NO_TIMESLICE |
0463       RTEMS_NO_ASR | RTEMS_INTERRUPT_LEVEL(0),
0464       RTEMS_FLOATING_POINT | RTEMS_LOCAL,
0465       &info->tid);
0466     if (sc == RTEMS_SUCCESSFUL)
0467     {
0468       sc = rtems_task_start(
0469         info->tid, session, (rtems_task_argument)info);
0470       if (sc != RTEMS_SUCCESSFUL)
0471         task_pool_done(i);
0472     }
0473     else
0474       task_pool_done(i + 1);
0475     if (sc != RTEMS_SUCCESSFUL)
0476     {
0477       syslog(LOG_ERR, "ftpd: Could not create/start FTPD session: %s",
0478         rtems_status_text(sc));
0479       return 0;
0480     }
0481     task_pool.queue[i] = task_pool.info + i;
0482     if (++id > 'z')
0483       id = 'a';
0484   }
0485   task_pool.count = count;
0486   return 1;
0487 }
0488 
0489 /*
0490  * task_pool_obtain
0491  *
0492  * Obtain free task from task pool.
0493  *
0494  * Input parameters:
0495  *   NONE
0496  *
0497  * Output parameters:
0498  *   returns pointer to the corresponding SessionInfo structure on success,
0499  *           NULL if there are no free tasks in the pool.
0500  *
0501  */
0502 static FTPD_SessionInfo_t*
0503 task_pool_obtain(void)
0504 {
0505   FTPD_SessionInfo_t* info = 0;
0506   rtems_counting_semaphore_wait(&task_pool.sem);
0507   rtems_mutex_lock(&task_pool.mutex);
0508   info = task_pool.queue[task_pool.head];
0509   if(++task_pool.head >= task_pool.count)
0510     task_pool.head = 0;
0511   rtems_mutex_unlock(&task_pool.mutex);
0512   return info;
0513 }
0514 
0515 /*
0516  * task_pool_release
0517  *
0518  * Return task obtained by 'obtain()' back to the task pool.
0519  *
0520  * Input parameters:
0521  *   info  - pointer to corresponding SessionInfo structure.
0522  *
0523  * Output parameters:
0524  *   NONE
0525  *
0526  */
0527 static void
0528 task_pool_release(FTPD_SessionInfo_t* info)
0529 {
0530   rtems_mutex_lock(&task_pool.mutex);
0531   task_pool.queue[task_pool.tail] = info;
0532   if(++task_pool.tail >= task_pool.count)
0533     task_pool.tail = 0;
0534   rtems_mutex_unlock(&task_pool.mutex);
0535   rtems_counting_semaphore_post(&task_pool.sem);
0536 }
0537 
0538 /*
0539  * End of task pool routines
0540  */
0541 
0542 /*
0543  * Function: send_reply
0544  *
0545  *
0546  *    This procedure sends a reply to the client via the control
0547  *    connection.
0548  *
0549  *
0550  * Input parameters:
0551  *   code  - 3-digit reply code.
0552  *   text  - Reply text.
0553  *
0554  * Output parameters:
0555  *   NONE
0556  */
0557 static void
0558 send_reply(FTPD_SessionInfo_t  *info, int code, const char *text)
0559 {
0560   text = text != NULL ? text : "";
0561   fprintf(info->ctrl_fp, "%d %.70s\r\n", code, text);
0562   fflush(info->ctrl_fp);
0563 }
0564 
0565 
0566 /*
0567  * close_socket
0568  *
0569  * Close socket.
0570  *
0571  * Input parameters:
0572  *   s - socket descriptor.
0573  *   seconds - number of seconds the timeout should be,
0574  *             if >= 0 - infinite timeout (no timeout).
0575  *
0576  * Output parameters:
0577  *   returns 1 on success, 0 on failure.
0578  */
0579 static void
0580 set_socket_timeout(int s, int seconds)
0581 {
0582   struct timeval tv;
0583   int len = sizeof(tv);
0584 
0585   if(seconds < 0)
0586     seconds = 0;
0587   tv.tv_usec = 0;
0588   tv.tv_sec  = seconds;
0589   if(0 != setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, len))
0590     syslog(LOG_ERR, "ftpd: Can't set send timeout on socket: %s.", serr());
0591   else if(0 != setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, len))
0592     syslog(LOG_ERR, "ftpd: Can't set receive timeout on socket: %s.", serr());
0593 }
0594 
0595 /*
0596  * close_socket
0597  *
0598  * Close socket.
0599  *
0600  * Input parameters:
0601  *   s - socket descriptor to be closed.
0602  *
0603  * Output parameters:
0604  *   returns 1 on success, 0 on failure
0605  */
0606 static int
0607 close_socket(int s)
0608 {
0609   if (0 <= s)
0610   {
0611     if (0 != close(s))
0612     {
0613       shutdown(s, 2);
0614       if (0 != close(s))
0615         return 0;
0616     }
0617   }
0618   return 1;
0619 }
0620 
0621 /*
0622  * data_socket
0623  *
0624  * Create data socket for session.
0625  *
0626  * Input parameters:
0627  *   info - corresponding SessionInfo structure
0628  *
0629  * Output parameters:
0630  *   returns socket descriptor, or -1 if failure
0631  *
0632  */
0633 static int
0634 data_socket(FTPD_SessionInfo_t *info)
0635 {
0636   int s = info->pasv_socket;
0637   if(0 > s)
0638   {
0639     int on = 1;
0640     s = socket(PF_INET, SOCK_STREAM, 0);
0641     if(0 > s)
0642       send_reply(info, 425, "Can't create data socket.");
0643     else if(0 > setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
0644     {
0645       close_socket(s);
0646       s = -1;
0647     }
0648     else
0649     {
0650       struct sockaddr_in data_source;
0651       int tries;
0652 
0653       /* anchor socket to avoid multi-homing problems */
0654       data_source = info->ctrl_addr;
0655       data_source.sin_port = htons(20); /* ftp-data port */
0656       for(tries = 1; tries < 10; ++tries)
0657       {
0658         errno = 0;
0659         if(bind(s, (struct sockaddr *)&data_source, sizeof(data_source)) >= 0)
0660           break;
0661         if (errno != EADDRINUSE)
0662           tries = 10;
0663         else
0664           rtems_task_wake_after(tries * 10);
0665       }
0666       if(tries >= 10)
0667       {
0668         send_reply(info, 425, "Can't bind data socket.");
0669         close_socket(s);
0670         s = -1;
0671       }
0672       else
0673       {
0674         struct sockaddr_in *data_dest =
0675           (info->use_default) ? &info->def_addr : &info->data_addr;
0676         if(0 > connect(s, (struct sockaddr *)data_dest, sizeof(*data_dest)))
0677         {
0678           send_reply(info, 425, "Can't connect data socket.");
0679           close_socket(s);
0680           s = -1;
0681         }
0682       }
0683     }
0684   }
0685   info->data_socket = s;
0686   info->use_default = 1;
0687   set_socket_timeout(s, info->idle);
0688   return s;
0689 }
0690 
0691 /*
0692  * close_data_socket
0693  *
0694  * Close data socket for session.
0695  *
0696  * Input parameters:
0697  *   info - corresponding SessionInfo structure
0698  *
0699  * Output parameters:
0700  *   NONE
0701  *
0702  */
0703 static void
0704 close_data_socket(FTPD_SessionInfo_t *info)
0705 {
0706   /* As at most one data socket could be open simultaneously and in some cases
0707      data_socket == pasv_socket, we select socket to close, then close it. */
0708   int s = info->data_socket;
0709   if(0 > s)
0710     s = info->pasv_socket;
0711   if(!close_socket(s))
0712     syslog(LOG_ERR, "ftpd: Error closing data socket.");
0713   info->data_socket = -1;
0714   info->pasv_socket = -1;
0715   info->use_default = 1;
0716 }
0717 
0718 /*
0719  * close_stream
0720  *
0721  * Close control stream of session.
0722  *
0723  * Input parameters:
0724  *   info - corresponding SessionInfo structure
0725  *
0726  * Output parameters:
0727  *   NONE
0728  *
0729  */
0730 static void
0731 close_stream(FTPD_SessionInfo_t* info)
0732 {
0733   if (NULL != info->ctrl_fp)
0734   {
0735     if (0 != fclose(info->ctrl_fp))
0736     {
0737       syslog(LOG_ERR, "ftpd: Could not close control stream: %s", serr());
0738     }
0739     else
0740       info->ctrl_socket = -1;
0741   }
0742 
0743   if (!close_socket(info->ctrl_socket))
0744     syslog(LOG_ERR, "ftpd: Could not close control socket: %s", serr());
0745 
0746   info->ctrl_fp = NULL;
0747   info->ctrl_socket = -1;
0748 }
0749 
0750 
0751 /*
0752  * send_mode_reply
0753  *
0754  * Sends BINARY/ASCII reply string depending on current transfer mode.
0755  *
0756  * Input parameters:
0757  *   info - corresponding SessionInfo structure
0758  *
0759  * Output parameters:
0760  *   NONE
0761  *
0762  */
0763 static void
0764 send_mode_reply(FTPD_SessionInfo_t *info)
0765 {
0766   if(info->xfer_mode == TYPE_I)
0767     send_reply(info, 150, "Opening BINARY mode data connection.");
0768   else
0769     send_reply(info, 150, "Opening ASCII mode data connection.");
0770 }
0771 
0772 /*
0773  * command_retrieve
0774  *
0775  * Perform the "RETR" command (send file to client).
0776  *
0777  * Input parameters:
0778  *   info - corresponding SessionInfo structure
0779  *   char *filename  - source filename.
0780  *
0781  * Output parameters:
0782  *   NONE
0783  *
0784  */
0785 static void
0786 command_retrieve(FTPD_SessionInfo_t  *info, char const *filename)
0787 {
0788   int                 s = -1;
0789   int                 fd = -1;
0790   char                buf[FTPD_DATASIZE];
0791   struct stat         stat_buf;
0792   int                 res = 0;
0793 
0794   if(!can_read() || !info->auth)
0795   {
0796     send_reply(info, 550, "Access denied.");
0797     return;
0798   }
0799 
0800   if (0 > (fd = open(filename, O_RDONLY)))
0801   {
0802     send_reply(info, 550, "Error opening file.");
0803     return;
0804   }
0805 
0806   if (fstat(fd, &stat_buf) == 0 && S_ISDIR(stat_buf.st_mode))
0807   {
0808     if (-1 != fd)
0809       close(fd);
0810     send_reply(info, 550, "Is a directory.");
0811     return;
0812   }
0813 
0814   send_mode_reply(info);
0815 
0816   s = data_socket(info);
0817 
0818   if (0 <= s)
0819   {
0820     int n = -1;
0821 
0822     if(info->xfer_mode == TYPE_I)
0823     {
0824       while ((n = read(fd, buf, FTPD_DATASIZE)) > 0)
0825       {
0826         if(send(s, buf, n, 0) != n)
0827           break;
0828         yield();
0829       }
0830     }
0831     else if (info->xfer_mode == TYPE_A)
0832     {
0833       int rest = 0;
0834       while (rest == 0 && (n = read(fd, buf, FTPD_DATASIZE)) > 0)
0835       {
0836         char const* e = buf;
0837         char const* b;
0838         int i;
0839         rest = n;
0840         do
0841         {
0842           char lf = '\0';
0843 
0844           b = e;
0845           for(i = 0; i < rest; ++i, ++e)
0846           {
0847             if(*e == '\n')
0848             {
0849               lf = '\n';
0850               break;
0851             }
0852           }
0853           if(send(s, b, i, 0) != i)
0854             break;
0855           if(lf == '\n')
0856           {
0857             if(send(s, "\r\n", 2, 0) != 2)
0858               break;
0859             ++e;
0860             ++i;
0861           }
0862         }
0863         while((rest -= i) > 0);
0864         yield();
0865       }
0866     }
0867 
0868     if (0 == n)
0869     {
0870       if (0 == close(fd))
0871       {
0872         fd = -1;
0873         res = 1;
0874       }
0875     }
0876   }
0877 
0878   if (-1 != fd)
0879     close(fd);
0880 
0881   if (0 == res)
0882     send_reply(info, 451, "File read error.");
0883   else
0884     send_reply(info, 226, "Transfer complete.");
0885 
0886   close_data_socket(info);
0887 
0888   return;
0889 }
0890 
0891 
0892 /*
0893  * discard
0894  *
0895  * Analog of `write' routine that just discards passed data
0896  *
0897  * Input parameters:
0898  *   fd    - file descriptor (ignored)
0899  *   buf   - data to write (ignored)
0900  *   count - number of bytes in `buf'
0901  *
0902  * Output parameters:
0903  *   returns `count'
0904  *
0905  */
0906 static ssize_t
0907 discard(int fd, void const* buf, size_t count)
0908 {
0909   (void)fd;
0910   (void)buf;
0911   return count;
0912 }
0913 
0914 /*
0915  * command_store
0916  *
0917  * Performs the "STOR" command (receive data from client).
0918  *
0919  * Input parameters:
0920  *   info - corresponding SessionInfo structure
0921  *   char *filename   - Destination filename.
0922  *
0923  * Output parameters:
0924  *   NONE
0925  */
0926 static void
0927 command_store(FTPD_SessionInfo_t *info, char const *filename)
0928 {
0929   int                    s;
0930   int                    n;
0931   unsigned long          size = 0;
0932   struct rtems_ftpd_hook *usehook = NULL;
0933   char                   buf[FTPD_DATASIZE];
0934   int                    res = 1;
0935   int                    bare_lfs = 0;
0936   int                    null = 0;
0937   typedef ssize_t (*WriteProc)(int, void const*, size_t);
0938   WriteProc              wrt = &write;
0939 
0940   if(!can_write() || !info->auth)
0941   {
0942     send_reply(info, 550, "Access denied.");
0943     return;
0944   }
0945 
0946   send_mode_reply(info);
0947 
0948   s = data_socket(info);
0949   if(0 > s)
0950     return;
0951 
0952   null = !strcmp("/dev/null", filename);
0953   if (null)
0954   {
0955     /* File "/dev/null" just throws data away.
0956      *  FIXME: this is hack.  Using `/dev/null' filesystem entry would be
0957      *  better.
0958      */
0959     wrt = &discard;
0960   }
0961 
0962   if (!null && ftpd_config->hooks != NULL)
0963   {
0964 
0965     /* Search our list of hooks to see if we need to do something special. */
0966     struct rtems_ftpd_hook *hook;
0967     int i;
0968 
0969     i = 0;
0970     hook = &ftpd_config->hooks[i++];
0971     while (hook->filename != NULL)
0972     {
0973       if (!strcmp(hook->filename, filename))
0974       {
0975         usehook = hook;
0976         break;
0977       }
0978       hook = &ftpd_config->hooks[i++];
0979     }
0980   }
0981 
0982   if (usehook != NULL)
0983   {
0984     /*
0985      * OSV: FIXME: Small buffer could be used and hook routine
0986      * called multiple times instead.  Alternatively, the support could be
0987      * removed entirely in favor of configuring RTEMS pseudo-device with
0988      * given name.
0989      */
0990 
0991     char   *bigBufr;
0992     size_t  filesize = ftpd_config->max_hook_filesize + 1;
0993 
0994     /*
0995      * Allocate space for our "file".
0996      */
0997     bigBufr = (char *)malloc(filesize);
0998     if (bigBufr == NULL)
0999     {
1000       send_reply(info, 451, "Local resource failure: malloc.");
1001       close_data_socket(info);
1002       return;
1003     }
1004 
1005     /*
1006      * Retrieve the file into our buffer space.
1007      */
1008     size = 0;
1009     while ((n = recv(s, bigBufr + size, filesize - size, 0)) > 0)
1010     {
1011       size += n;
1012     }
1013     if (size >= filesize)
1014     {
1015       send_reply(info, 451, "File too long: buffer size exceeded.");
1016       free(bigBufr);
1017       close_data_socket(info);
1018       return;
1019     }
1020 
1021     /*
1022      * Call our hook.
1023      */
1024     res = (usehook->hook_function)(bigBufr, size) == 0;
1025     free(bigBufr);
1026     if(!res)
1027     {
1028       send_reply(info, 451, "File processing failed.");
1029       close_data_socket(info);
1030       return;
1031     }
1032   }
1033   else
1034   {
1035     /* Data transfer to regular file or /dev/null. */
1036     int fd = 0;
1037 
1038     if(!null)
1039       fd = creat(filename,
1040         S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1041 
1042     if (0 > fd)
1043     {
1044       send_reply(info, 550, "Error creating file.");
1045       close_data_socket(info);
1046       return;
1047     }
1048 
1049     if(info->xfer_mode == TYPE_I)
1050     {
1051       while ((n = recv(s, buf, FTPD_DATASIZE, 0)) > 0)
1052       {
1053         if (wrt(fd, buf, n) != n)
1054         {
1055           res = 0;
1056           break;
1057         }
1058         yield();
1059       }
1060     }
1061     else if(info->xfer_mode == TYPE_A)
1062     {
1063       int rest = 0;
1064       int pended_cr = 0;
1065       while (res && rest == 0 && (n = recv(s, buf, FTPD_DATASIZE, 0)) > 0)
1066       {
1067         char const* e = buf;
1068         char const* b;
1069         int i;
1070 
1071         rest = n;
1072         if(pended_cr && *e != '\n')
1073         {
1074           char const lf = '\r';
1075           pended_cr = 0;
1076           if(wrt(fd, &lf, 1) != 1)
1077           {
1078             res = 0;
1079             break;
1080           }
1081         }
1082         do
1083         {
1084           int count;
1085           int sub = 0;
1086 
1087           b = e;
1088           for(i = 0; i < rest; ++i, ++e)
1089           {
1090             int pcr = pended_cr;
1091             pended_cr = 0;
1092             if(*e == '\r')
1093             {
1094               pended_cr = 1;
1095             }
1096             else if(*e == '\n')
1097             {
1098               if(pcr)
1099               {
1100                 sub = 2;
1101                 ++i;
1102                 ++e;
1103                 break;
1104               }
1105               ++bare_lfs;
1106             }
1107           }
1108           if(res == 0)
1109             break;
1110           count = i - sub - pended_cr;
1111           if(count > 0 && wrt(fd, b, count) != count)
1112           {
1113             res = 0;
1114             break;
1115           }
1116           if(sub == 2 && wrt(fd, e - 1, 1) != 1)
1117             res = 0;
1118         }
1119         while((rest -= i) > 0);
1120         yield();
1121       }
1122     }
1123 
1124     if (0 > close(fd) || res == 0)
1125     {
1126       send_reply(info, 452, "Error writing file.");
1127       close_data_socket(info);
1128       return;
1129     }
1130   }
1131 
1132   if (bare_lfs > 0)
1133   {
1134     snprintf(buf, FTPD_BUFSIZE,
1135       "Transfer complete. WARNING! %d bare linefeeds received in ASCII mode.",
1136       bare_lfs);
1137     send_reply(info, 226, buf);
1138   }
1139   else
1140     send_reply(info, 226, "Transfer complete.");
1141   close_data_socket(info);
1142 
1143 }
1144 
1145 
1146 /*
1147  * send_dirline
1148  *
1149  * Sends one line of LIST command reply corresponding to single file.
1150  *
1151  * Input parameters:
1152  *   s - socket descriptor to send data to
1153  *   wide - if 0, send only file name.  If not 0, send 'stat' info as well in
1154  *          "ls -l" format.
1155  *   curTime - current time
1156  *   path - path to be prepended to what is given by 'add'
1157  *   add  - path to be appended to what is given by 'path', the resulting path
1158  *          is then passed to 'stat()' routine
1159  *   name - file name to be reported in output
1160  *   buf  - buffer for temporary data
1161  *
1162  * Output parameters:
1163  *   returns false on failure, true on success
1164  *
1165  */
1166 static bool
1167 send_dirline(int s, bool wide, time_t curTime, char const* path,
1168   char const* add, char const* fname, char* buf)
1169 {
1170   struct stat stat_buf;
1171   size_t plen = strlen(path);
1172   size_t alen = strlen(add);
1173   int slen = 0;
1174 
1175   if(plen == 0)
1176   {
1177     buf[plen++] = '/';
1178     buf[plen] = '\0';
1179   }
1180   else
1181   {
1182     buf = memcpy(buf, path, plen + 1);
1183     if(alen > 0 && buf[plen - 1] != '/')
1184     {
1185       buf[plen++] = '/';
1186       if(plen >= FTPD_BUFSIZE)
1187         return 0;
1188       buf[plen] = '\0';
1189     }
1190   }
1191   if(plen + alen >= FTPD_BUFSIZE)
1192     return 0;
1193   memcpy(buf + plen, add, alen + 1);
1194 
1195   if (stat(buf, &stat_buf) == 0)
1196   {
1197     if (wide)
1198     {
1199       struct tm bt;
1200       time_t tf = stat_buf.st_mtime;
1201       enum { SIZE = 80 };
1202       time_t SIX_MONTHS = (365L*24L*60L*60L)/2L;
1203       char timeBuf[SIZE];
1204       gmtime_r(&tf, &bt);
1205       if(curTime > tf + SIX_MONTHS || tf > curTime + SIX_MONTHS)
1206         strftime (timeBuf, SIZE, "%b %d  %Y", &bt);
1207       else
1208         strftime (timeBuf, SIZE, "%b %d %H:%M", &bt);
1209 
1210       slen = snprintf(buf, FTPD_BUFSIZE,
1211         "%c%c%c%c%c%c%c%c%c%c  1 %5d %5d %11u %s %s\r\n",
1212         (S_ISLNK(stat_buf.st_mode)?('l'):
1213           (S_ISDIR(stat_buf.st_mode)?('d'):('-'))),
1214         (stat_buf.st_mode & S_IRUSR)?('r'):('-'),
1215         (stat_buf.st_mode & S_IWUSR)?('w'):('-'),
1216         (stat_buf.st_mode & S_IXUSR)?('x'):('-'),
1217         (stat_buf.st_mode & S_IRGRP)?('r'):('-'),
1218         (stat_buf.st_mode & S_IWGRP)?('w'):('-'),
1219         (stat_buf.st_mode & S_IXGRP)?('x'):('-'),
1220         (stat_buf.st_mode & S_IROTH)?('r'):('-'),
1221         (stat_buf.st_mode & S_IWOTH)?('w'):('-'),
1222         (stat_buf.st_mode & S_IXOTH)?('x'):('-'),
1223         (int)stat_buf.st_uid,
1224         (int)stat_buf.st_gid,
1225         (int)stat_buf.st_size,
1226         timeBuf,
1227         fname
1228       );
1229     }
1230     else
1231     {
1232       slen = snprintf(buf, FTPD_BUFSIZE, "%s\r\n", fname);
1233     }
1234   }
1235   else
1236   {
1237     slen = snprintf(buf, FTPD_BUFSIZE, "%s: %s.\r\n", fname, strerror(errno));
1238   }
1239 
1240   if (slen >= FTPD_BUFSIZE)
1241   {
1242     static const char dots[] = { '.', '.', '.', '\r', '\n' };
1243 
1244     /*
1245      * The file name exceeds the send buffer, indicate this with a ... at the
1246      * end of the line.
1247      */
1248     slen = FTPD_BUFSIZE - 1;
1249     memcpy(&buf[slen - sizeof(dots)], dots, sizeof(dots));
1250   }
1251 
1252   if (slen > 0 && send(s, buf, (size_t) slen, 0) != slen)
1253     return false;
1254 
1255   return true;
1256 }
1257 
1258 /*
1259  * command_list
1260  *
1261  * Send file list to client.
1262  *
1263  * Input parameters:
1264  *   info - corresponding SessionInfo structure
1265  *   char *fname  - File (or directory) to list.
1266  *
1267  * Output parameters:
1268  *   NONE
1269  */
1270 static void
1271 command_list(FTPD_SessionInfo_t *info, char const *fname, bool wide)
1272 {
1273   int                 s;
1274   DIR                 *dirp = 0;
1275   struct dirent       *dp = 0;
1276   char                buf[FTPD_BUFSIZE];
1277   time_t curTime;
1278   bool ok = true;
1279 
1280   if(!info->auth)
1281   {
1282     send_reply(info, 550, "Access denied.");
1283     return;
1284   }
1285 
1286   send_reply(info, 150, "Opening ASCII mode data connection for LIST.");
1287 
1288   s = data_socket(info);
1289   if(0 > s)
1290   {
1291     syslog(LOG_ERR, "ftpd: Error connecting to data socket.");
1292     return;
1293   }
1294 
1295   if (fname[0] == '\0')
1296     fname = ".";
1297 
1298   time(&curTime);
1299   dirp = opendir(fname);
1300   if (dirp != NULL)
1301   {
1302     /* FIXME: need "." and ".." only when '-a' option is given */
1303     ok = ok && send_dirline(s, wide, curTime, fname, "", ".", buf);
1304     ok = ok && send_dirline(s, wide, curTime, fname,
1305       (strcmp(fname, ftpd_root) ? ".." : ""), "..", buf);
1306 
1307     while (ok && (dp = readdir(dirp)) != NULL)
1308       ok = ok &&
1309         send_dirline(s, wide, curTime, fname, dp->d_name, dp->d_name, buf);
1310 
1311     closedir(dirp);
1312   }
1313   else
1314   {
1315     send_dirline(s, wide, curTime, fname, "", fname, buf);
1316   }
1317 
1318   close_data_socket(info);
1319 
1320   if (ok)
1321     send_reply(info, 226, "Transfer complete.");
1322   else
1323     send_reply(info, 426, "Connection aborted.");
1324 }
1325 
1326 
1327 /*
1328  * command_cwd
1329  *
1330  * Change current working directory.
1331  *
1332  * Input parameters:
1333  *   info - corresponding SessionInfo structure
1334  *   dir  - directory name passed in CWD command
1335  *
1336  * Output parameters:
1337  *   NONE
1338  *
1339  */
1340 static void
1341 command_cwd(FTPD_SessionInfo_t  *info, const char *dir)
1342 {
1343   if(!info->auth)
1344   {
1345     send_reply(info, 550, "Access denied.");
1346     return;
1347   }
1348 
1349   if(chdir(dir) == 0)
1350     send_reply(info, 250, "CWD command successful.");
1351   else
1352     send_reply(info, 550, "CWD command failed.");
1353 }
1354 
1355 
1356 /*
1357  * command_pwd
1358  *
1359  * Send current working directory to client.
1360  *
1361  * Input parameters:
1362  *   info - corresponding SessionInfo structure
1363  *
1364  * Output parameters:
1365  *   NONE
1366  */
1367 static void
1368 command_pwd(FTPD_SessionInfo_t  *info)
1369 {
1370   char buf[FTPD_BUFSIZE];
1371   char const* cwd;
1372   errno = 0;
1373   buf[0] = '"';
1374 
1375   if(!info->auth)
1376   {
1377     send_reply(info, 550, "Access denied.");
1378     return;
1379   }
1380 
1381   cwd = getcwd(buf + 1, FTPD_BUFSIZE - 4);
1382   if(cwd)
1383   {
1384     int len = strlen(cwd);
1385     static char const txt[] = "\" is the current directory.";
1386     int size = sizeof(txt);
1387     if(len + size + 1 >= FTPD_BUFSIZE)
1388       size = FTPD_BUFSIZE - len - 2;
1389     memcpy(buf + len + 1, txt, size);
1390     buf[len + size] = '\0';
1391     send_reply(info, 250, buf);
1392   }
1393   else {
1394     snprintf(buf, FTPD_BUFSIZE, "Error: %s.", serr());
1395     send_reply(info, 452, buf);
1396   }
1397 }
1398 
1399 /*
1400  * command_mdtm
1401  *
1402  * Handle FTP MDTM command (send file modification time to client)/
1403  *
1404  * Input parameters:
1405  *   info - corresponding SessionInfo structure
1406  *   fname - file name passed in MDTM command
1407  *
1408  * Output parameters:
1409  *   info->cwd is set to new CWD value.
1410  */
1411 static void
1412 command_mdtm(FTPD_SessionInfo_t  *info, char const* fname)
1413 {
1414   struct stat stbuf;
1415   char buf[FTPD_BUFSIZE];
1416 
1417   if(!info->auth)
1418   {
1419     send_reply(info, 550, "Access denied.");
1420     return;
1421   }
1422 
1423   if (0 > stat(fname, &stbuf))
1424   {
1425     snprintf(buf, FTPD_BUFSIZE, "%s: %s.", fname, serr());
1426     send_reply(info, 550, buf);
1427   }
1428   else
1429   {
1430     struct tm *t = gmtime(&stbuf.st_mtime);
1431     snprintf(buf, FTPD_BUFSIZE, "%04d%02d%02d%02d%02d%02d",
1432       1900 + t->tm_year,
1433       t->tm_mon+1, t->tm_mday,
1434       t->tm_hour, t->tm_min, t->tm_sec);
1435     send_reply(info, 213, buf);
1436   }
1437 }
1438 
1439 static void
1440 command_size(FTPD_SessionInfo_t *info, char const* fname)
1441 {
1442   struct stat stbuf;
1443   char buf[FTPD_BUFSIZE];
1444 
1445   if(!info->auth)
1446   {
1447     send_reply(info, 550, "Access denied.");
1448     return;
1449   }
1450 
1451   if (info->xfer_mode != TYPE_I || 0 > stat(fname, &stbuf) || stbuf.st_size < 0)
1452   {
1453     send_reply(info, 550, "Could not get file size.");
1454   }
1455   else
1456   {
1457     snprintf(buf, FTPD_BUFSIZE, "%" PRIuMAX, (uintmax_t) stbuf.st_size);
1458     send_reply(info, 213, buf);
1459   }
1460 }
1461 
1462 /*
1463  * command_port
1464  *
1465  * This procedure fills address for data connection given the IP address and
1466  * port of the remote machine.
1467  *
1468  * Input parameters:
1469  *   info - corresponding SessionInfo structure
1470  *   args - arguments to the "PORT" command.
1471  *
1472  * Output parameters:
1473  *   info->data_addr is set according to arguments of the PORT command.
1474  *   info->use_default is set to 0 on success, 1 on failure.
1475  */
1476 static void
1477 command_port(FTPD_SessionInfo_t *info, char const *args)
1478 {
1479   enum { NUM_FIELDS = 6 };
1480   unsigned int a[NUM_FIELDS];
1481   int n;
1482 
1483   close_data_socket(info);
1484 
1485   n = sscanf(args, "%u,%u,%u,%u,%u,%u", a+0, a+1, a+2, a+3, a+4, a+5);
1486   if(NUM_FIELDS == n)
1487   {
1488     int i;
1489     union {
1490       uint8_t b[NUM_FIELDS];
1491       struct {
1492         uint32_t ip;
1493         uint16_t port;
1494       } u ;
1495     } ip_info;
1496 
1497     for(i = 0; i < NUM_FIELDS; ++i)
1498     {
1499       if(a[i] > 255)
1500         break;
1501       ip_info.b[i] = (uint8_t)a[i];
1502     }
1503 
1504     if(i == NUM_FIELDS)
1505     {
1506       /* Note: while it contradicts RFC959, we don't allow PORT command
1507        * to specify IP address different from that of the originating client
1508        * for the sake of safety. */
1509       if (ip_info.u.ip == info->def_addr.sin_addr.s_addr)
1510       {
1511         info->data_addr.sin_addr.s_addr = ip_info.u.ip;
1512         info->data_addr.sin_port        = ip_info.u.port;
1513         info->data_addr.sin_family      = AF_INET;
1514         memset(info->data_addr.sin_zero, 0, sizeof(info->data_addr.sin_zero));
1515 
1516         info->use_default = 0;
1517         send_reply(info, 200, "PORT command successful.");
1518         return; /* success */
1519       }
1520       else
1521       {
1522         send_reply(info, 425, "Address doesn't match peer's IP.");
1523         return;
1524       }
1525     }
1526   }
1527   send_reply(info, 501, "Syntax error.");
1528 }
1529 
1530 
1531 /*
1532  * command_pasv
1533  *
1534  * Handle FTP PASV command.
1535  * Open socket, listen for and accept connection on it.
1536  *
1537  * Input parameters:
1538  *   info - corresponding SessionInfo structure
1539  *
1540  * Output parameters:
1541  *   info->pasv_socket is set to the descriptor of the data socket
1542  */
1543 static void
1544 command_pasv(FTPD_SessionInfo_t *info)
1545 {
1546   int s = -1;
1547   int err = 1;
1548 
1549   close_data_socket(info);
1550 
1551   s = socket(PF_INET, SOCK_STREAM, 0);
1552   if (s < 0)
1553     syslog(LOG_ERR, "ftpd: Error creating PASV socket: %s", serr());
1554   else
1555   {
1556     struct sockaddr_in addr;
1557     socklen_t addrLen = sizeof(addr);
1558 
1559     addr = info->ctrl_addr;
1560     addr.sin_port = htons(0);
1561 
1562     if (0 > bind(s, (struct sockaddr *)&addr, addrLen))
1563       syslog(LOG_ERR, "ftpd: Error binding PASV socket: %s", serr());
1564     else if (0 > listen(s, 1))
1565       syslog(LOG_ERR, "ftpd: Error listening on PASV socket: %s", serr());
1566     {
1567       set_socket_timeout(s, info->idle);
1568       if (0 == getsockname(s, (struct sockaddr *)&addr, &addrLen))
1569       {
1570         char buf[FTPD_BUFSIZE];
1571         unsigned char const *ip, *p;
1572 
1573         ip = (unsigned char const*)&(addr.sin_addr);
1574         p  = (unsigned char const*)&(addr.sin_port);
1575         snprintf(buf, FTPD_BUFSIZE, "Entering passive mode (%u,%u,%u,%u,%u,%u).",
1576           ip[0], ip[1], ip[2], ip[3], p[0], p[1]);
1577         send_reply(info, 227, buf);
1578 
1579         info->pasv_socket = accept(s, (struct sockaddr *)&addr, &addrLen);
1580         if (0 > info->pasv_socket)
1581           syslog(LOG_ERR, "ftpd: Error accepting PASV connection: %s", serr());
1582         else
1583         {
1584           close_socket(s);
1585           s = -1;
1586           err = 0;
1587         }
1588       }
1589       else
1590         syslog(LOG_ERR, "ftpd: Cannot get socket name: %s", serr());
1591     }
1592   }
1593   if(err)
1594   {
1595     /* (OSV) The note is from FreeBSD FTPD.
1596      * Note: a response of 425 is not mentioned as a possible response to
1597      * the PASV command in RFC959.  However, it has been blessed as a
1598      * legitimate response by Jon Postel in a telephone conversation
1599      * with Rick Adams on 25 Jan 89. */
1600     send_reply(info, 425, "Can't open passive connection.");
1601     close_socket(s);
1602   }
1603 }
1604 
1605 
1606 /*
1607  * skip_options
1608  *
1609  * Utility routine to skip options (if any) from input command.
1610  *
1611  * Input parameters:
1612  *   p  - pointer to pointer to command
1613  *
1614  * Output parameters:
1615  *   p  - is changed to point to first non-option argument
1616  */
1617 static void
1618 skip_options(char **p)
1619 {
1620   char* buf = *p;
1621   char* last = NULL;
1622   while(1) {
1623     while(isspace((unsigned char)*buf))
1624       ++buf;
1625     if(*buf == '-') {
1626       if(*++buf == '-') { /* `--' should terminate options */
1627         if(isspace((unsigned char)*++buf)) {
1628           last = buf;
1629           do ++buf;
1630           while(isspace((unsigned char)*buf));
1631           break;
1632         }
1633       }
1634       while(*buf && !isspace((unsigned char)*buf))
1635         ++buf;
1636       last = buf;
1637     }
1638     else
1639       break;
1640   }
1641   if(last)
1642     *last = '\0';
1643   *p = buf;
1644 }
1645 
1646 /*
1647  * split_command
1648  *
1649  * Split command into command itself, options, and arguments. Command itself
1650  * is converted to upper case.
1651  *
1652  * Input parameters:
1653  *   buf - initial command string
1654  *
1655  * Output parameter:
1656  *   buf  - is modified by inserting '\0' at ends of split entities
1657  *   cmd  - upper-cased command code
1658  *   opts - string containing all the options
1659  *   args - string containing all the arguments
1660  */
1661 static void
1662 split_command(char *buf, char **cmd, char **opts, char **args)
1663 {
1664   char* eoc;
1665   char* p = buf;
1666   while(isspace((unsigned char)*p))
1667     ++p;
1668   *cmd = p;
1669   while(*p && !isspace((unsigned char)*p))
1670   {
1671     *p = toupper((unsigned char)*p);
1672     ++p;
1673   }
1674   eoc = p;
1675   if(*p)
1676     *p++ = '\0';
1677   while(isspace((unsigned char)*p))
1678     ++p;
1679   *opts = p;
1680   skip_options(&p);
1681   *args = p;
1682   if(*opts == p)
1683     *opts = eoc;
1684   while(*p && *p != '\r' && *p != '\n')
1685     ++p;
1686   if(*p)
1687     *p++ = '\0';
1688 }
1689 
1690 /*
1691  * exec_command
1692  *
1693  * Parse and execute FTP command.
1694  *
1695  * FIXME: This section is somewhat of a hack.  We should have a better
1696  *        way to parse commands.
1697  *
1698  * Input parameters:
1699  *   info - corresponding SessionInfo structure
1700  *   cmd  - command to be executed (upper-case)
1701  *   args - arguments of the command
1702  *
1703  * Output parameters:
1704  *    NONE
1705  */
1706 static void
1707 exec_command(FTPD_SessionInfo_t *info, char *cmd, char *args)
1708 {
1709   int wrong_command = 0;
1710 
1711   if (!strcmp("PORT", cmd))
1712   {
1713     command_port(info, args);
1714   }
1715   else if (!strcmp("PASV", cmd))
1716   {
1717     command_pasv(info);
1718   }
1719   else if (!strcmp("RETR", cmd))
1720   {
1721     command_retrieve(info, args);
1722   }
1723   else if (!strcmp("STOR", cmd))
1724   {
1725     command_store(info, args);
1726   }
1727   else if (!strcmp("LIST", cmd))
1728   {
1729     command_list(info, args, true);
1730   }
1731   else if (!strcmp("NLST", cmd))
1732   {
1733     command_list(info, args, false);
1734   }
1735   else if (!strcmp("MDTM", cmd))
1736   {
1737     command_mdtm(info, args);
1738   }
1739   else if (!strcmp("SIZE", cmd))
1740   {
1741     command_size(info, args);
1742   }
1743   else if (!strcmp("SYST", cmd))
1744   {
1745     send_reply(info, 215, FTPD_SYSTYPE);
1746   }
1747   else if (!strcmp("TYPE", cmd))
1748   {
1749     if (args[0] == 'I')
1750     {
1751       info->xfer_mode = TYPE_I;
1752       send_reply(info, 200, "Type set to I.");
1753     }
1754     else if (args[0] == 'A')
1755     {
1756       info->xfer_mode = TYPE_A;
1757       send_reply(info, 200, "Type set to A.");
1758     }
1759     else
1760     {
1761       info->xfer_mode = TYPE_I;
1762       send_reply(info, 504, "Type not implemented.  Set to I.");
1763     }
1764   }
1765   else if (!strcmp("USER", cmd))
1766   {
1767     strlcpy(info->user_buf, args, sizeof(info->user_buf));
1768     info->user = info->user_buf;
1769     if (ftpd_config->login &&
1770       !ftpd_config->login(info->user, NULL)) {
1771       info->auth = false;
1772       send_reply(info, 331, "User name okay, need password.");
1773     } else {
1774       info->auth = true;
1775       send_reply(info, 230, "User logged in.");
1776     }
1777   }
1778   else if (!strcmp("PASS", cmd))
1779   {
1780     if (!info->user) {
1781       send_reply(info, 332, "Need account to log in");
1782     } else {
1783       if (ftpd_config->login &&
1784         !ftpd_config->login(info->user, args)) {
1785         info->auth = false;
1786         send_reply(info, 530, "Not logged in.");
1787       } else {
1788         info->auth = true;
1789         send_reply(info, 230, "User logged in.");
1790       }
1791     }
1792   }
1793   else if (!strcmp("DELE", cmd))
1794   {
1795     if(!can_write() || !info->auth)
1796     {
1797       send_reply(info, 550, "Access denied.");
1798     }
1799     else if (unlink(args) == 0)
1800     {
1801       send_reply(info, 257, "DELE successful.");
1802     }
1803     else
1804     {
1805       send_reply(info, 550, "DELE failed.");
1806     }
1807   }
1808   else if (!strcmp("SITE", cmd))
1809   {
1810     char* opts;
1811     split_command(args, &cmd, &opts, &args);
1812     if(!strcmp("CHMOD", cmd))
1813     {
1814       int mask;
1815 
1816       if(!can_write() || !info->auth)
1817       {
1818         send_reply(info, 550, "Access denied.");
1819       }
1820       else {
1821         char *c;
1822         c = strchr(args, ' ');
1823         if((c != NULL) && (sscanf(args, "%o", &mask) == 1)
1824           && (chmod(c + 1, (mode_t)mask) == 0))
1825           send_reply(info, 257, "CHMOD successful.");
1826         else
1827           send_reply(info, 550, "CHMOD failed.");
1828       }
1829     }
1830     else
1831       wrong_command = 1;
1832   }
1833   else if (!strcmp("RMD", cmd))
1834   {
1835     if(!can_write() || !info->auth)
1836     {
1837       send_reply(info, 550, "Access denied.");
1838     }
1839     else if (rmdir(args) == 0)
1840     {
1841       send_reply(info, 257, "RMD successful.");
1842     }
1843     else
1844     {
1845       send_reply(info, 550, "RMD failed.");
1846     }
1847   }
1848   else if (!strcmp("MKD", cmd))
1849   {
1850     if(!can_write() || !info->auth)
1851     {
1852       send_reply(info, 550, "Access denied.");
1853     }
1854     else if (mkdir(args, S_IRWXU | S_IRWXG | S_IRWXO) == 0)
1855     {
1856       send_reply(info, 257, "MKD successful.");
1857     }
1858     else
1859     {
1860       send_reply(info, 550, "MKD failed.");
1861     }
1862   }
1863   else if (!strcmp("CWD", cmd))
1864   {
1865     command_cwd(info, args);
1866   }
1867   else if (!strcmp("CDUP", cmd))
1868   {
1869     command_cwd(info, "..");
1870   }
1871   else if (!strcmp("PWD", cmd))
1872   {
1873     command_pwd(info);
1874   }
1875   else
1876     wrong_command = 1;
1877 
1878   if(wrong_command)
1879     send_reply(info, 500, "Command not understood.");
1880 }
1881 
1882 
1883 /*
1884  * session
1885  *
1886  * This task handles single session.  It is woken up when the FTP daemon gets a
1887  * service request from a remote machine.  Here, we watch for commands that
1888  * will come through the control connection.  These commands are then parsed
1889  * and executed until the connection is closed, either unintentionally or
1890  * intentionally with the "QUIT" command.
1891  *
1892  * Input parameters:
1893  *   arg - pointer to corresponding SessionInfo.
1894  *
1895  * Output parameters:
1896  *   NONE
1897  */
1898 static void
1899 session(rtems_task_argument arg)
1900 {
1901   FTPD_SessionInfo_t  *const info = (FTPD_SessionInfo_t  *)arg;
1902   bool chroot_made = false;
1903 
1904   while (1)
1905   {
1906     rtems_event_set set;
1907 
1908     rtems_event_receive(FTPD_RTEMS_EVENT, RTEMS_EVENT_ANY, RTEMS_NO_TIMEOUT,
1909       &set);
1910 
1911     chroot_made = chroot_made
1912       || (rtems_libio_set_private_env() == RTEMS_SUCCESSFUL
1913         && chroot(ftpd_root) == 0);
1914 
1915     /*
1916      * The chdir() must immediately follow the chroot(), otherwise static
1917      * analysis tools may complain about a security issue.
1918      */
1919 
1920     if (chroot_made && chdir("/") == 0)
1921     {
1922       send_reply(info, 220, FTPD_SERVER_MESSAGE);
1923 
1924       while (1)
1925       {
1926         char buf[FTPD_BUFSIZE];
1927         char *cmd, *opts, *args;
1928         size_t len;
1929 
1930         if (fgets(buf, FTPD_BUFSIZE, info->ctrl_fp) == NULL)
1931         {
1932           syslog(LOG_INFO, "ftpd: Connection aborted.");
1933           break;
1934         }
1935 
1936         len = strlen(buf);
1937 
1938         if (len == 0)
1939           continue;
1940 
1941         if (buf[len - 1] != '\n')
1942         {
1943           send_reply(info, 501, "Command line too long.");
1944 
1945           /*
1946            * We could also try to continue here; however, discarding the rest
1947            * of the current command line and figuring out when the next command
1948            * starts with fgets() is not that easy.  It would be better to avoid
1949            * the FILE stream and just use the socket directly with send() and
1950            * recv().
1951            */
1952           break;
1953         }
1954 
1955         split_command(buf, &cmd, &opts, &args);
1956 
1957         if (!strcmp("QUIT", cmd))
1958         {
1959           send_reply(info, 221, "Goodbye.");
1960           break;
1961         }
1962         else
1963         {
1964           exec_command(info, cmd, args);
1965         }
1966       }
1967     }
1968     else
1969     {
1970       send_reply(info, 421, "Service not available, closing control connection.");
1971     }
1972 
1973     /*
1974      * Go back to the root directory.  A use case is to release a current
1975      * directory in a mounted file system on dynamic media, e.g. USB stick.
1976      * The return value can be ignored since the next session will try do the
1977      * operation again and an error check is performed in this case.
1978      */
1979     chdir("/");
1980 
1981     /* Close connection and put ourselves back into the task pool. */
1982     close_data_socket(info);
1983     close_stream(info);
1984     task_pool_release(info);
1985   }
1986 }
1987 
1988 
1989 /*
1990  * ftpd_daemon
1991  *
1992  * This task runs forever.  It waits for service requests on the FTP port
1993  * (port 21 by default).  When a request is received, it opens a new session
1994  * to handle those requests until the connection is closed.
1995  *
1996  * Input parameters:
1997  *   NONE
1998  *
1999  * Output parameters:
2000  *   NONE
2001  */
2002 static void
2003 ftpd_daemon(rtems_task_argument args RTEMS_UNUSED)
2004 {
2005   int                 s;
2006   socklen_t       addrLen;
2007   struct sockaddr_in  addr;
2008 
2009   memset(&addr, 0, sizeof(addr));
2010   addr.sin_family      = AF_INET;
2011   addr.sin_port        = htons(ftpd_config->port);
2012   addr.sin_addr.s_addr = htonl(INADDR_ANY);
2013 
2014   s = socket(PF_INET, SOCK_STREAM, 0);
2015   if (s < 0)
2016     syslog(LOG_ERR, "ftpd: Error creating control socket: %s", serr());
2017   else if (0 > bind(s, (struct sockaddr *)&addr, sizeof(addr)))
2018     syslog(LOG_ERR, "ftpd: Error binding control socket: %s", serr());
2019   else if (0 > listen(s, 1))
2020     syslog(LOG_ERR, "ftpd: Error listening on control socket: %s", serr());
2021   else while (1)
2022   {
2023     int ss;
2024     addrLen = sizeof(addr);
2025     ss = accept(s, (struct sockaddr *)&addr, &addrLen);
2026     if (0 > ss)
2027       syslog(LOG_ERR, "ftpd: Error accepting control connection: %s", serr());
2028     else
2029     {
2030       FTPD_SessionInfo_t *info;
2031 
2032       set_socket_timeout(ss, ftpd_timeout);
2033       info = task_pool_obtain();
2034       if (NULL == info)
2035       {
2036         close_socket(ss);
2037       }
2038       else
2039       {
2040         info->ctrl_socket = ss;
2041         if ((info->ctrl_fp = fdopen(info->ctrl_socket, "r+")) == NULL)
2042         {
2043           syslog(LOG_ERR, "ftpd: fdopen() on socket failed: %s", serr());
2044           close_stream(info);
2045           task_pool_release(info);
2046         }
2047         else
2048         {
2049           /* Initialize corresponding SessionInfo structure */
2050           info->def_addr = addr;
2051           if(0 > getsockname(ss, (struct sockaddr *)&addr, &addrLen))
2052           {
2053             syslog(LOG_ERR, "ftpd: getsockname(): %s", serr());
2054             close_stream(info);
2055             task_pool_release(info);
2056           }
2057           else
2058           {
2059             info->use_default = 1;
2060             info->ctrl_addr  = addr;
2061             info->pasv_socket = -1;
2062             info->data_socket = -1;
2063             info->xfer_mode   = TYPE_A;
2064             info->data_addr.sin_port =
2065               htons(ntohs(info->ctrl_addr.sin_port) - 1);
2066             info->idle = ftpd_timeout;
2067             info->user = NULL;
2068             if (ftpd_config->login)
2069               info->auth = false;
2070             else
2071               info->auth = true;
2072             /* Wakeup the session task.  The task will call task_pool_release
2073                after it closes connection. */
2074             rtems_event_send(info->tid, FTPD_RTEMS_EVENT);
2075           }
2076         }
2077       }
2078     }
2079   }
2080 
2081   close(s);
2082   rtems_task_exit();
2083 }
2084 
2085 
2086 /*
2087  * rtems_ftpd_start
2088  *
2089  * Here, we start the FTPD task which waits for FTP requests and services
2090  * them.  This procedure returns to its caller once the task is started.
2091  *
2092  *
2093  * Input parameters:
2094  *    config: constant initial setup.
2095  * Output parameters:
2096  *    returns RTEMS_SUCCESSFUL on successful start of the daemon.
2097  */
2098 rtems_status_code
2099 rtems_ftpd_start(const struct rtems_ftpd_configuration* config)
2100 {
2101   rtems_status_code   sc;
2102   rtems_id            tid;
2103   rtems_task_priority priority;
2104   int count;
2105 
2106   if (ftpd_config != NULL)
2107     return RTEMS_RESOURCE_IN_USE;
2108 
2109   ftpd_config = malloc(sizeof(*ftpd_config));
2110   if (ftpd_config == NULL)
2111     return RTEMS_NO_MEMORY;
2112 
2113   *ftpd_config = *config;
2114 
2115   if (ftpd_config->port == 0)
2116   {
2117     ftpd_config->port = FTPD_CONTROL_PORT;
2118   }
2119 
2120   if (ftpd_config->priority == 0)
2121   {
2122     ftpd_config->priority = 40;
2123   }
2124   priority = ftpd_config->priority;
2125 
2126   ftpd_timeout = ftpd_config->idle;
2127   if (ftpd_timeout < 0)
2128     ftpd_timeout = 0;
2129   ftpd_config->idle = ftpd_timeout;
2130 
2131   ftpd_access = ftpd_config->access;
2132 
2133   ftpd_root = "/";
2134   if (ftpd_config->root && ftpd_config->root[0] == '/' )
2135     ftpd_root = ftpd_config->root;
2136 
2137   ftpd_config->root = ftpd_root;
2138 
2139   if (ftpd_config->tasks_count <= 0)
2140     ftpd_config->tasks_count = 1;
2141   count = ftpd_config->tasks_count;
2142 
2143   if (!task_pool_init(count, priority))
2144   {
2145     syslog(LOG_ERR, "ftpd: Could not initialize task pool.");
2146     return RTEMS_UNSATISFIED;
2147   }
2148 
2149   sc = rtems_task_create(rtems_build_name('F', 'T', 'P', 'D'),
2150     priority, RTEMS_MINIMUM_STACK_SIZE,
2151     RTEMS_PREEMPT | RTEMS_NO_TIMESLICE | RTEMS_NO_ASR |
2152     RTEMS_INTERRUPT_LEVEL(0),
2153     RTEMS_FLOATING_POINT | RTEMS_LOCAL,
2154     &tid);
2155 
2156   if (sc == RTEMS_SUCCESSFUL)
2157   {
2158     sc = rtems_task_start(tid, ftpd_daemon, 0);
2159     if (sc != RTEMS_SUCCESSFUL)
2160       rtems_task_delete(tid);
2161   }
2162 
2163   if (sc != RTEMS_SUCCESSFUL)
2164   {
2165     task_pool_done(count);
2166     syslog(LOG_ERR, "ftpd: Could not create/start FTP daemon: %s",
2167       rtems_status_text(sc));
2168     return RTEMS_UNSATISFIED;
2169   }
2170 
2171   if (ftpd_config->verbose)
2172     syslog(LOG_INFO, "ftpd: FTP daemon started (%d session%s max)",
2173            count, ((count > 1) ? "s" : ""));
2174 
2175   return RTEMS_SUCCESSFUL;
2176 }