source: rtems-libbsd/rtemsbsd/ftpd/ftpd.c @ 269b559

5-freebsd-12freebsd-9.3
Last change on this file since 269b559 was 269b559, checked in by Sebastian Huber <sebastian.huber@…>, on Nov 24, 2016 at 11:01:28 AM

ftpd: Use floating-point tasks due to syslog()

  • Property mode set to 100644
File size: 51.8 KB
Line 
1/* FIXME: 1. Parse command is a hack.  We can do better.
2 *        2. OSV: hooks support seems to be bad, as it requires storing of
3 *           entire input file in memory.  Seem to be better to change it to
4 *           something more reasonable, like having
5 *           'hook_write(void const *buf, int count)' routine that will be
6 *           called multiple times while file is being received.
7 *        3. OSV: Remove hack with "/dev/null"?
8 *
9 *  FTP Server Daemon
10 *
11 *  Submitted by: Jake Janovetz <janovetz@tempest.ece.uiuc.edu>
12 *
13 *  Changed by:   Sergei Organov <osv@javad.ru> (OSV)
14 *                Arnout Vandecappelle <arnout@mind.be> (AV)
15 *                Sebastien Bourdeauducq <sebastien@milkymist.org> (MM)
16 *
17 *
18 *  Changes:
19 *
20 *    2010-12-02        Sebastien Bourdeauducq <sebastien@milkymist.org>
21 *
22 *      * Support spaces in filenames
23 *
24 *    2010-04-29        Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>
25 *
26 *      * Added USER/PASS authentication.
27 *
28 *    2001-01-31        Sergei Organov <osv@javad.ru>
29 *
30 *      * Hacks with current dir and root dir removed in favor of new libio
31 *        support for task-local current and root directories.
32 *
33 *    2001-01-30        Sergei Organov <osv@javad.ru>
34 *
35 *      * Bug in `close_data_socket()' introduced by previous change fixed.
36 *      * `command_pasv()' changed to set timeout on socket we are listening on
37 *        and code fixed to don't close socket twice on error.
38 *      * `serr()' changed to clear `errno'.
39 *      * `data_socket()' changed to clear `errno' before `bind()'.
40 *      * `session()' changed to clear `errno' before processing session.
41 *
42 *    2001-01-29        Sergei Organov <osv@javad.ru>
43 *
44 *      * `close_data_socket()' fixed to close both active and passive sockets
45 *      * Initialize info->data_socket to -1 in `daemon()'
46 *      * Initialize `fname' to empty string  in `exec_command()'
47 *
48 *    2001-01-22        Sergei Organov <osv@javad.ru>
49 *
50 *      * Timeouts on sockets implemented. 'idle' field added to
51 *        configuration. No timeout by default to keep backward compatibility.
52 *        Note: SITE IDLE command not implemented yet.
53 *      * Basic global access control implemented. 'access' field added to
54 *        configuration. No access limitations by default to keep backward
55 *        compatibility.
56 *
57 *    2001-01-17        Sergei Organov <osv@javad.ru>
58 *
59 *      * Anchor data socket for active mode (using self IP and port 20.)
60 *      * Fixed default data port support (still not tested).
61 *      * Don't allow IP address different from originating host in
62 *        PORT command to improve security.
63 *      * Fixed bug in MDTM command.
64 *      * Check for correctness of parsing of argument in command_port().
65 *      * Fixed squeeze_path() to don't allow names like 'NAME/smth' where
66 *        'NAME' is not a directory.
67 *      * Command parsing a little bit improved: command names are now
68 *        converted to upper-case to be more compatible with RFC (command
69 *        names are not case-sensitive.)
70 *      * Reformat comments so that they have RTEMS look-and-feel.
71 *
72 *    2001-01-16        Sergei Organov <osv@javad.ru>
73 *
74 *      * Fixed DELE, SITE CHMOD, RMD, MKD broken by previous changes
75 *      * True ASCII mode implemented (doesn't work for hooks and /dev/null)
76 *      * Passive mode implemented, PASV command added.
77 *      * Default port for data connection could be used (untested, can't find
78 *        ftp client that doesn't send PORT command)
79 *      * SYST reply changed to UNIX, as former RTEMS isn't registered name.
80 *      * Reply codes reviewed and fixed.
81 *
82 *    2001-01-08        Sergei Organov <osv@javad.ru>
83 *
84 *      * use pool of pre-created threads to handle sessions
85 *      * LIST output now similar to what "/bin/ls -al" would output, thus
86 *        FTP clients could parse it.
87 *      * LIST NAME now works (both for files and directories)
88 *      * keep track of CWD for every session separately
89 *      * ability to specify root directory name in configuration table
90 *      * options sent in commands are ignored, thus LIST -al FILE works
91 *      * added support for NLST, CDUP and MDTM commands
92 *      * buffers are allocated on stack instead of heap where possible
93 *      * drop using of task notepad to pass parameters - use function
94 *        arguments instead
95 *      * various bug-fixes, e.g., use of PF_INET in socket() instead of
96 *        AF_INET, use snprintf() instead of sprintf() everywhere for safety,
97 *        etc.
98 */
99
100/*************************************************************************
101 *                                 ftpd.c
102 *************************************************************************
103 * Description:
104 *
105 *    This file contains the daemon which services requests that appear
106 *    on the FTP port.  This server is compatible with FTP, but it
107 *    also provides 'hooks' to make it usable in situations where files
108 *    are not used/necessary.  Once the server is started, it runs
109 *    forever.
110 *
111 *
112 *    Organization:
113 *
114 *       The FTP daemon is started upon boot along with a (configurable)
115 *       number of tasks to handle sessions.  It runs all the time and
116 *       waits for connections on the known FTP port (21).  When
117 *       a connection is made, it wakes-up a 'session' task.  That
118 *       session then interacts with the remote host.  When the session
119 *       is complete, the session task goes to sleep.  The daemon still
120 *       runs, however.
121 *
122 *
123 * Supported commands are:
124 *
125 * RETR xxx     - Sends a file from the client.
126 * STOR xxx     - Receives a file from the client.  xxx = filename.
127 * LIST xxx     - Sends a file list to the client.
128 * NLST xxx     - Sends a file list to the client.
129 * USER         - Does nothing.
130 * PASS         - Does nothing.
131 * SYST         - Replies with the system type (`RTEMS').
132 * DELE xxx     - Delete file xxx.
133 * MKD xxx      - Create directory xxx.
134 * RMD xxx      - Remove directory xxx.
135 * PWD          - Print working directory.
136 * CWD xxx      - Change working directory.
137 * CDUP         - Change to upper directory.
138 * SITE CHMOD xxx yyy - Change permissions on file yyy to xxx.
139 * PORT a,b,c,d,x,y   - Setup for a data port to IP address a.b.c.d
140 *                      and port (x*256 + y).
141 * MDTM xxx     - Send file modification date/time to the client.
142 *                xxx = filename.
143 * PASV         - Use passive mode data connection.
144 *
145 *
146 * The public routines contained in this file are:
147 *
148 *    rtems_initialize_ftpd - Initializes and starts the server daemon,
149 *                            then returns to its caller.
150 *
151 *------------------------------------------------------------------------
152 * Jake Janovetz
153 * University of Illinois
154 * 1406 West Green Street
155 * Urbana IL  61801
156 *************************************************************************
157 * Change History:
158 *  12/01/97   - Creation (JWJ)
159 *  2001-01-08 - Changes by OSV
160 *  2010-04-29 - Authentication added by AV
161 *************************************************************************/
162
163/*************************************************************************
164 * Meanings of first and second digits of reply codes:
165 *
166 * Reply:  Description:
167 *-------- --------------
168 *  1yz    Positive preliminary reply.  The action is being started but
169 *         expect another reply before sending another command.
170 *  2yz    Positive completion reply.  A new command can be sent.
171 *  3yz    Positive intermediate reply.  The command has been accepted
172 *         but another command must be sent.
173 *  4yz    Transient negative completion reply.  The requested action did
174 *         not take place, but the error condition is temporary so the
175 *         command can be reissued later.
176 *  5yz    Permanent negative completion reply.  The command was not
177 *         accepted and should not be retried.
178 *-------------------------------------------------------------------------
179 *  x0z    Syntax errors.
180 *  x1z    Information.
181 *  x2z    Connections.  Replies referring to the control or data
182 *         connections.
183 *  x3z    Authentication and accounting.  Replies for the login or
184 *         accounting commands.
185 *  x4z    Unspecified.
186 *  x5z    Filesystem status.
187 *************************************************************************/
188
189#if HAVE_CONFIG_H
190#include "config.h"
191#endif
192
193#include <stdio.h>
194#include <stdlib.h>
195#include <string.h>
196#include <unistd.h>
197#include <fcntl.h>
198#include <dirent.h>
199#include <errno.h>
200#include <ctype.h>
201#include <inttypes.h>
202#include <sched.h>
203
204#include <rtems.h>
205#include <rtems/rtems_bsdnet.h>
206#include <rtems/error.h>
207#include <rtems/libio.h>
208#include <rtems/userenv.h>
209#include <syslog.h>
210
211#include <sys/types.h>
212#include <sys/socket.h>
213#include <arpa/ftp.h>
214#include <netinet/in.h>
215
216#include <rtems/ftpd.h>
217
218#ifdef __GNUC__
219/* change to #if 1 to disable syslog entirely */
220#if 0
221#undef  syslog
222#define syslog(a, b, ...) while(0){}
223#endif
224#endif
225
226#define FTPD_SERVER_MESSAGE  "RTEMS FTP server (Version 1.1-JWJ) ready."
227
228#define FTPD_SYSTYPE "UNIX Type: L8"
229
230/* Seem to be unused */
231#if 0
232#define FTPD_WELCOME_MESSAGE \
233   "Welcome to the RTEMS FTP server.\n" \
234   "\n" \
235   "Login accepted.\n"
236#endif
237
238/* Event to be used by session tasks for waiting */
239enum
240{
241  FTPD_RTEMS_EVENT = RTEMS_EVENT_1
242};
243
244/* Configuration table */
245static struct rtems_ftpd_configuration* ftpd_config;
246
247/* this is not prototyped in strict ansi mode */
248FILE *fdopen (int fildes, const char *mode);
249
250/*SessionInfo structure.
251 *
252 * The following structure is allocated for each session.
253 */
254typedef struct
255{
256  struct sockaddr_in  ctrl_addr;   /* Control connection self address */
257  struct sockaddr_in  data_addr;   /* Data address set by PORT command */
258  struct sockaddr_in  def_addr;    /* Default address for data */
259  int                 use_default; /* 1 - use default address for data */
260  FILE                *ctrl_fp;    /* File pointer for control connection */
261  int                 ctrl_socket; /* Socket for ctrl connection */
262  int                 pasv_socket; /* Socket for PASV connection */
263  int                 data_socket; /* Socket for data connection */
264  int                 idle;        /* Timeout in seconds */
265  int                 xfer_mode;   /* Transfer mode (ASCII/binary) */
266  rtems_id            tid;         /* Task id */
267  char                *user;       /* user name (0 if not supplied) */
268  char                *pass;       /* password (0 if not supplied) */
269  bool                auth;        /* true if user/pass was valid, false if not or not supplied */
270} FTPD_SessionInfo_t;
271
272
273/*
274 * TaskPool structure.
275 */
276typedef struct
277{
278  FTPD_SessionInfo_t    *info;
279  FTPD_SessionInfo_t    **queue;
280  int                   count;
281  int                   head;
282  int                   tail;
283  rtems_id              mutex;
284  rtems_id              sem;
285} FTPD_TaskPool_t;
286
287/*
288 * Task pool instance.
289 */
290static FTPD_TaskPool_t task_pool;
291
292/*
293 * Root directory
294 */
295
296static char const* ftpd_root = "/";
297
298/*
299 * Default idle timeout for sockets in seconds.
300 */
301static int ftpd_timeout = 0;
302
303/*
304 * Global access flags.
305 */
306static int ftpd_access = 0;
307
308/*
309 * serr
310 *
311 * Return error string corresponding to current 'errno'.
312 *
313 */
314static char const*
315serr(void)
316{
317  int err = errno;
318  errno = 0;
319  return strerror(err);
320}
321
322/*
323 * Utility routines for access control.
324 *
325 */
326
327static int
328can_read(void)
329{
330  return (ftpd_access & FTPD_NO_READ) == 0;
331}
332
333static int
334can_write(void)
335{
336  return (ftpd_access & FTPD_NO_WRITE) == 0;
337}
338
339/*
340 * Task pool management routines
341 *
342 */
343
344
345/*
346 * task_pool_done
347 *
348 * Cleanup task pool.
349 *
350 * Input parameters:
351 *   count - number of entries in task pool to cleanup
352 *
353 * Output parameters:
354 *   NONE
355 *
356 */
357static void
358task_pool_done(int count)
359{
360  int i;
361  for(i = 0; i < count; ++i)
362    rtems_task_delete(task_pool.info[i].tid);
363  if(task_pool.info)
364    free(task_pool.info);
365  if(task_pool.queue)
366    free(task_pool.queue);
367  if(task_pool.mutex != (rtems_id)-1)
368    rtems_semaphore_delete(task_pool.mutex);
369  if(task_pool.sem != (rtems_id)-1)
370    rtems_semaphore_delete(task_pool.sem);
371  task_pool.info = 0;
372  task_pool.queue = 0;
373  task_pool.count = 0;
374  task_pool.sem = -1;
375  task_pool.mutex = -1;
376}
377
378/*
379 * task_pool_init
380 *
381 * Initialize task pool.
382 *
383 * Input parameters:
384 *   count    - number of entries in task pool to create
385 *   priority - priority tasks are started with
386 *
387 * Output parameters:
388 *   returns 1 on success, 0 on failure.
389 *
390 */
391static void session(rtems_task_argument arg); /* Forward declare */
392
393static int
394task_pool_init(int count, rtems_task_priority priority)
395{
396  int i;
397  rtems_status_code sc;
398  char id = 'a';
399
400  task_pool.count = 0;
401  task_pool.head = task_pool.tail = 0;
402  task_pool.mutex = (rtems_id)-1;
403  task_pool.sem   = (rtems_id)-1;
404
405  sc = rtems_semaphore_create(
406    rtems_build_name('F', 'T', 'P', 'M'),
407    1,
408    RTEMS_DEFAULT_ATTRIBUTES
409    | RTEMS_BINARY_SEMAPHORE
410    | RTEMS_INHERIT_PRIORITY
411    | RTEMS_PRIORITY,
412    RTEMS_NO_PRIORITY,
413    &task_pool.mutex);
414
415  if(sc == RTEMS_SUCCESSFUL)
416    sc = rtems_semaphore_create(
417      rtems_build_name('F', 'T', 'P', 'S'),
418      count,
419      RTEMS_DEFAULT_ATTRIBUTES,
420      RTEMS_NO_PRIORITY,
421      &task_pool.sem);
422
423  if(sc != RTEMS_SUCCESSFUL) {
424    task_pool_done(0);
425    syslog(LOG_ERR, "ftpd: Can not create semaphores");
426    return 0;
427  }
428
429  task_pool.info = (FTPD_SessionInfo_t*)
430    malloc(sizeof(FTPD_SessionInfo_t) * count);
431  task_pool.queue = (FTPD_SessionInfo_t**)
432    malloc(sizeof(FTPD_SessionInfo_t*) * count);
433  if (NULL == task_pool.info || NULL == task_pool.queue)
434  {
435    task_pool_done(0);
436    syslog(LOG_ERR, "ftpd: Not enough memory");
437    return 0;
438  }
439
440  for(i = 0; i < count; ++i)
441  {
442    FTPD_SessionInfo_t *info = &task_pool.info[i];
443    sc = rtems_task_create(rtems_build_name('F', 'T', 'P', id),
444      priority, FTPD_STACKSIZE,
445      RTEMS_PREEMPT | RTEMS_NO_TIMESLICE |
446      RTEMS_NO_ASR | RTEMS_INTERRUPT_LEVEL(0),
447      RTEMS_FLOATING_POINT | RTEMS_LOCAL,
448      &info->tid);
449    if (sc == RTEMS_SUCCESSFUL)
450    {
451      sc = rtems_task_start(
452        info->tid, session, (rtems_task_argument)info);
453      if (sc != RTEMS_SUCCESSFUL)
454        task_pool_done(i);
455    }
456    else
457      task_pool_done(i + 1);
458    if (sc != RTEMS_SUCCESSFUL)
459    {
460      syslog(LOG_ERR, "ftpd: Could not create/start FTPD session: %s",
461        rtems_status_text(sc));
462      return 0;
463    }
464    task_pool.queue[i] = task_pool.info + i;
465    if (++id > 'z')
466      id = 'a';
467  }
468  task_pool.count = count;
469  return 1;
470}
471
472/*
473 * task_pool_obtain
474 *
475 * Obtain free task from task pool.
476 *
477 * Input parameters:
478 *   NONE
479 *
480 * Output parameters:
481 *   returns pointer to the corresponding SessionInfo structure on success,
482 *           NULL if there are no free tasks in the pool.
483 *
484 */
485static FTPD_SessionInfo_t*
486task_pool_obtain(void)
487{
488  FTPD_SessionInfo_t* info = 0;
489  rtems_status_code sc;
490  sc = rtems_semaphore_obtain(task_pool.sem, RTEMS_NO_WAIT, RTEMS_NO_TIMEOUT);
491  if (sc == RTEMS_SUCCESSFUL)
492  {
493    rtems_semaphore_obtain(task_pool.mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT);
494    info = task_pool.queue[task_pool.head];
495    if(++task_pool.head >= task_pool.count)
496      task_pool.head = 0;
497    rtems_semaphore_release(task_pool.mutex);
498  }
499  return info;
500}
501
502/*
503 * task_pool_release
504 *
505 * Return task obtained by 'obtain()' back to the task pool.
506 *
507 * Input parameters:
508 *   info  - pointer to corresponding SessionInfo structure.
509 *
510 * Output parameters:
511 *   NONE
512 *
513 */
514static void
515task_pool_release(FTPD_SessionInfo_t* info)
516{
517  rtems_semaphore_obtain(task_pool.mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT);
518  task_pool.queue[task_pool.tail] = info;
519  if(++task_pool.tail >= task_pool.count)
520    task_pool.tail = 0;
521  rtems_semaphore_release(task_pool.mutex);
522  rtems_semaphore_release(task_pool.sem);
523}
524
525/*
526 * End of task pool routines
527 */
528
529/*
530 * Function: send_reply
531 *
532 *
533 *    This procedure sends a reply to the client via the control
534 *    connection.
535 *
536 *
537 * Input parameters:
538 *   code  - 3-digit reply code.
539 *   text  - Reply text.
540 *
541 * Output parameters:
542 *   NONE
543 */
544static void
545send_reply(FTPD_SessionInfo_t  *info, int code, char *text)
546{
547  text = text != NULL ? text : "";
548  fprintf(info->ctrl_fp, "%d %.70s\r\n", code, text);
549  fflush(info->ctrl_fp);
550}
551
552
553/*
554 * close_socket
555 *
556 * Close socket.
557 *
558 * Input parameters:
559 *   s - socket descriptor.
560 *   seconds - number of seconds the timeout should be,
561 *             if >= 0 - infinite timeout (no timeout).
562 *
563 * Output parameters:
564 *   returns 1 on success, 0 on failure.
565 */
566static int
567set_socket_timeout(int s, int seconds)
568{
569  int res = 0;
570  struct timeval tv;
571  int len = sizeof(tv);
572
573  if(seconds < 0)
574    seconds = 0;
575  tv.tv_usec = 0;
576  tv.tv_sec  = seconds;
577  if(0 != setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, len))
578    syslog(LOG_ERR, "ftpd: Can't set send timeout on socket: %s.", serr());
579  else if(0 != setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, len))
580    syslog(LOG_ERR, "ftpd: Can't set receive timeout on socket: %s.", serr());
581  else
582    res = 1;
583  return res;
584}
585
586/*
587 * close_socket
588 *
589 * Close socket.
590 *
591 * Input parameters:
592 *   s - socket descriptor to be closed.
593 *
594 * Output parameters:
595 *   returns 1 on success, 0 on failure
596 */
597static int
598close_socket(int s)
599{
600  if (0 <= s)
601  {
602    if (0 != close(s))
603    {
604      shutdown(s, 2);
605      if (0 != close(s))
606        return 0;
607    }
608  }
609  return 1;
610}
611
612/*
613 * data_socket
614 *
615 * Create data socket for session.
616 *
617 * Input parameters:
618 *   info - corresponding SessionInfo structure
619 *
620 * Output parameters:
621 *   returns socket descriptor, or -1 if failure
622 *
623 */
624static int
625data_socket(FTPD_SessionInfo_t *info)
626{
627  int s = info->pasv_socket;
628  if(0 > s)
629  {
630    int on = 1;
631    s = socket(PF_INET, SOCK_STREAM, 0);
632    if(0 > s)
633      send_reply(info, 425, "Can't create data socket.");
634    else if(0 > setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
635    {
636      close_socket(s);
637      s = -1;
638    }
639    else
640    {
641      struct sockaddr_in data_source;
642      int tries;
643
644      /* anchor socket to avoid multi-homing problems */
645      data_source = info->ctrl_addr;
646      data_source.sin_port = htons(20); /* ftp-data port */
647      for(tries = 1; tries < 10; ++tries)
648      {
649        errno = 0;
650        if(bind(s, (struct sockaddr *)&data_source, sizeof(data_source)) >= 0)
651          break;
652        if (errno != EADDRINUSE)
653          tries = 10;
654        else
655          rtems_task_wake_after(tries * 10);
656      }
657      if(tries >= 10)
658      {
659        send_reply(info, 425, "Can't bind data socket.");
660        close_socket(s);
661        s = -1;
662      }
663      else
664      {
665        struct sockaddr_in *data_dest =
666          (info->use_default) ? &info->def_addr : &info->data_addr;
667        if(0 > connect(s, (struct sockaddr *)data_dest, sizeof(*data_dest)))
668        {
669          send_reply(info, 425, "Can't connect data socket.");
670          close_socket(s);
671          s = -1;
672        }
673      }
674    }
675  }
676  info->data_socket = s;
677  info->use_default = 1;
678  if(s >= 0)
679    set_socket_timeout(s, info->idle);
680  return s;
681}
682
683/*
684 * close_data_socket
685 *
686 * Close data socket for session.
687 *
688 * Input parameters:
689 *   info - corresponding SessionInfo structure
690 *
691 * Output parameters:
692 *   NONE
693 *
694 */
695static void
696close_data_socket(FTPD_SessionInfo_t *info)
697{
698  /* As at most one data socket could be open simultaneously and in some cases
699     data_socket == pasv_socket, we select socket to close, then close it. */
700  int s = info->data_socket;
701  if(0 > s)
702    s = info->pasv_socket;
703  if(!close_socket(s))
704    syslog(LOG_ERR, "ftpd: Error closing data socket.");
705  info->data_socket = -1;
706  info->pasv_socket = -1;
707  info->use_default = 1;
708}
709
710/*
711 * close_stream
712 *
713 * Close control stream of session.
714 *
715 * Input parameters:
716 *   info - corresponding SessionInfo structure
717 *
718 * Output parameters:
719 *   NONE
720 *
721 */
722static void
723close_stream(FTPD_SessionInfo_t* info)
724{
725  if (NULL != info->ctrl_fp)
726  {
727    if (0 != fclose(info->ctrl_fp))
728    {
729      syslog(LOG_ERR, "ftpd: Could not close control stream: %s", serr());
730    }
731    else
732      info->ctrl_socket = -1;
733  }
734
735  if (!close_socket(info->ctrl_socket))
736    syslog(LOG_ERR, "ftpd: Could not close control socket: %s", serr());
737
738  info->ctrl_fp = NULL;
739  info->ctrl_socket = -1;
740}
741
742
743/*
744 * send_mode_reply
745 *
746 * Sends BINARY/ASCII reply string depending on current transfer mode.
747 *
748 * Input parameters:
749 *   info - corresponding SessionInfo structure
750 *
751 * Output parameters:
752 *   NONE
753 *
754 */
755static void
756send_mode_reply(FTPD_SessionInfo_t *info)
757{
758  if(info->xfer_mode == TYPE_I)
759    send_reply(info, 150, "Opening BINARY mode data connection.");
760  else
761    send_reply(info, 150, "Opening ASCII mode data connection.");
762}
763
764/*
765 * command_retrieve
766 *
767 * Perform the "RETR" command (send file to client).
768 *
769 * Input parameters:
770 *   info - corresponding SessionInfo structure
771 *   char *filename  - source filename.
772 *
773 * Output parameters:
774 *   NONE
775 *
776 */
777static void
778command_retrieve(FTPD_SessionInfo_t  *info, char const *filename)
779{
780  int                 s = -1;
781  int                 fd = -1;
782  char                buf[FTPD_DATASIZE];
783  struct stat         stat_buf;
784  int                 res = 0;
785
786  if(!can_read() || !info->auth)
787  {
788    send_reply(info, 550, "Access denied.");
789    return;
790  }
791
792  if (0 > (fd = open(filename, O_RDONLY)))
793  {
794    send_reply(info, 550, "Error opening file.");
795    return;
796  }
797
798  if (fstat(fd, &stat_buf) == 0 && S_ISDIR(stat_buf.st_mode))
799  {
800    if (-1 != fd)
801      close(fd);
802    send_reply(info, 550, "Is a directory.");
803    return;
804  }
805
806  send_mode_reply(info);
807
808  s = data_socket(info);
809
810  if (0 <= s)
811  {
812    int n = -1;
813
814    if(info->xfer_mode == TYPE_I)
815    {
816      while ((n = read(fd, buf, FTPD_DATASIZE)) > 0)
817      {
818        if(send(s, buf, n, 0) != n)
819          break;
820        sched_yield();
821      }
822    }
823    else if (info->xfer_mode == TYPE_A)
824    {
825      int rest = 0;
826      while (rest == 0 && (n = read(fd, buf, FTPD_DATASIZE)) > 0)
827      {
828        char const* e = buf;
829        char const* b;
830        int i;
831        rest = n;
832        do
833        {
834          char lf = '\0';
835
836          b = e;
837          for(i = 0; i < rest; ++i, ++e)
838          {
839            if(*e == '\n')
840            {
841              lf = '\n';
842              break;
843            }
844          }
845          if(send(s, b, i, 0) != i)
846            break;
847          if(lf == '\n')
848          {
849            if(send(s, "\r\n", 2, 0) != 2)
850              break;
851            ++e;
852            ++i;
853          }
854        }
855        while((rest -= i) > 0);
856        sched_yield();
857      }
858    }
859
860    if (0 == n)
861    {
862      if (0 == close(fd))
863      {
864        fd = -1;
865        res = 1;
866      }
867    }
868  }
869
870  if (-1 != fd)
871    close(fd);
872
873  if (0 == res)
874    send_reply(info, 451, "File read error.");
875  else
876    send_reply(info, 226, "Transfer complete.");
877
878  close_data_socket(info);
879
880  return;
881}
882
883
884/*
885 * discard
886 *
887 * Analog of `write' routine that just discards passed data
888 *
889 * Input parameters:
890 *   fd    - file descriptor (ignored)
891 *   buf   - data to write (ignored)
892 *   count - number of bytes in `buf'
893 *
894 * Output parameters:
895 *   returns `count'
896 *
897 */
898static ssize_t
899discard(int fd, void const* buf, size_t count)
900{
901  (void)fd;
902  (void)buf;
903  return count;
904}
905
906/*
907 * command_store
908 *
909 * Performs the "STOR" command (receive data from client).
910 *
911 * Input parameters:
912 *   info - corresponding SessionInfo structure
913 *   char *filename   - Destination filename.
914 *
915 * Output parameters:
916 *   NONE
917 */
918static void
919command_store(FTPD_SessionInfo_t *info, char const *filename)
920{
921  int                    s;
922  int                    n;
923  unsigned long          size = 0;
924  struct rtems_ftpd_hook *usehook = NULL;
925  char                   buf[FTPD_DATASIZE];
926  int                    res = 1;
927  int                    bare_lfs = 0;
928  int                    null = 0;
929  typedef ssize_t (*WriteProc)(int, void const*, size_t);
930  WriteProc              wrt = &write;
931
932  if(!can_write() || !info->auth)
933  {
934    send_reply(info, 550, "Access denied.");
935    return;
936  }
937
938  send_mode_reply(info);
939
940  s = data_socket(info);
941  if(0 > s)
942    return;
943
944  null = !strcmp("/dev/null", filename);
945  if (null)
946  {
947    /* File "/dev/null" just throws data away.
948     *  FIXME: this is hack.  Using `/dev/null' filesystem entry would be
949     *  better.
950     */
951    wrt = &discard;
952  }
953
954  if (!null && ftpd_config->hooks != NULL)
955  {
956
957    /* Search our list of hooks to see if we need to do something special. */
958    struct rtems_ftpd_hook *hook;
959    int i;
960
961    i = 0;
962    hook = &ftpd_config->hooks[i++];
963    while (hook->filename != NULL)
964    {
965      if (!strcmp(hook->filename, filename))
966      {
967        usehook = hook;
968        break;
969      }
970      hook = &ftpd_config->hooks[i++];
971    }
972  }
973
974  if (usehook != NULL)
975  {
976    /*
977     * OSV: FIXME: Small buffer could be used and hook routine
978     * called multiple times instead.  Alternatively, the support could be
979     * removed entirely in favor of configuring RTEMS pseudo-device with
980     * given name.
981     */
982
983    char   *bigBufr;
984    size_t  filesize = ftpd_config->max_hook_filesize + 1;
985
986    /*
987     * Allocate space for our "file".
988     */
989    bigBufr = (char *)malloc(filesize);
990    if (bigBufr == NULL)
991    {
992      send_reply(info, 451, "Local resource failure: malloc.");
993      close_data_socket(info);
994      return;
995    }
996
997    /*
998     * Retrieve the file into our buffer space.
999     */
1000    size = 0;
1001    while ((n = recv(s, bigBufr + size, filesize - size, 0)) > 0)
1002    {
1003      size += n;
1004    }
1005    if (size >= filesize)
1006    {
1007      send_reply(info, 451, "File too long: buffer size exceeded.");
1008      free(bigBufr);
1009      close_data_socket(info);
1010      return;
1011    }
1012
1013    /*
1014     * Call our hook.
1015     */
1016    res = (usehook->hook_function)(bigBufr, size) == 0;
1017    free(bigBufr);
1018    if(!res)
1019    {
1020      send_reply(info, 451, "File processing failed.");
1021      close_data_socket(info);
1022      return;
1023    }
1024  }
1025  else
1026  {
1027    /* Data transfer to regular file or /dev/null. */
1028    int fd = 0;
1029
1030    if(!null)
1031      fd = creat(filename,
1032        S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1033
1034    if (0 > fd)
1035    {
1036      send_reply(info, 550, "Error creating file.");
1037      close_data_socket(info);
1038      return;
1039    }
1040
1041    if(info->xfer_mode == TYPE_I)
1042    {
1043      while ((n = recv(s, buf, FTPD_DATASIZE, 0)) > 0)
1044      {
1045        if (wrt(fd, buf, n) != n)
1046        {
1047          res = 0;
1048          break;
1049        }
1050        sched_yield();
1051      }
1052    }
1053    else if(info->xfer_mode == TYPE_A)
1054    {
1055      int rest = 0;
1056      int pended_cr = 0;
1057      while (res && rest == 0 && (n = recv(s, buf, FTPD_DATASIZE, 0)) > 0)
1058      {
1059        char const* e = buf;
1060        char const* b;
1061        int i;
1062
1063        rest = n;
1064        if(pended_cr && *e != '\n')
1065        {
1066          char const lf = '\r';
1067          pended_cr = 0;
1068          if(wrt(fd, &lf, 1) != 1)
1069          {
1070            res = 0;
1071            break;
1072          }
1073        }
1074        do
1075        {
1076          int count;
1077          int sub = 0;
1078
1079          b = e;
1080          for(i = 0; i < rest; ++i, ++e)
1081          {
1082            int pcr = pended_cr;
1083            pended_cr = 0;
1084            if(*e == '\r')
1085            {
1086              pended_cr = 1;
1087            }
1088            else if(*e == '\n')
1089            {
1090              if(pcr)
1091              {
1092                sub = 2;
1093                ++i;
1094                ++e;
1095                break;
1096              }
1097              ++bare_lfs;
1098            }
1099          }
1100          if(res == 0)
1101            break;
1102          count = i - sub - pended_cr;
1103          if(count > 0 && wrt(fd, b, count) != count)
1104          {
1105            res = 0;
1106            break;
1107          }
1108          if(sub == 2 && wrt(fd, e - 1, 1) != 1)
1109            res = 0;
1110        }
1111        while((rest -= i) > 0);
1112        sched_yield();
1113      }
1114    }
1115
1116    if (0 > close(fd) || res == 0)
1117    {
1118      send_reply(info, 452, "Error writing file.");
1119      close_data_socket(info);
1120      return;
1121    }
1122  }
1123
1124  if (bare_lfs > 0)
1125  {
1126    snprintf(buf, FTPD_BUFSIZE,
1127      "Transfer complete. WARNING! %d bare linefeeds received in ASCII mode.",
1128      bare_lfs);
1129    send_reply(info, 226, buf);
1130  }
1131  else
1132    send_reply(info, 226, "Transfer complete.");
1133  close_data_socket(info);
1134
1135}
1136
1137
1138/*
1139 * send_dirline
1140 *
1141 * Sends one line of LIST command reply corresponding to single file.
1142 *
1143 * Input parameters:
1144 *   s - socket descriptor to send data to
1145 *   wide - if 0, send only file name.  If not 0, send 'stat' info as well in
1146 *          "ls -l" format.
1147 *   curTime - current time
1148 *   path - path to be prepended to what is given by 'add'
1149 *   add  - path to be appended to what is given by 'path', the resulting path
1150 *          is then passed to 'stat()' routine
1151 *   name - file name to be reported in output
1152 *   buf  - buffer for temporary data
1153 *
1154 * Output parameters:
1155 *   returns 0 on failure, 1 on success
1156 *
1157 */
1158static int
1159send_dirline(int s, int wide, time_t curTime, char const* path,
1160  char const* add, char const* fname, char* buf)
1161{
1162  if(wide)
1163  {
1164    struct stat stat_buf;
1165
1166    int plen = strlen(path);
1167    int alen = strlen(add);
1168    if(plen == 0)
1169    {
1170      buf[plen++] = '/';
1171      buf[plen] = '\0';
1172    }
1173    else
1174    {
1175      strcpy(buf, path);
1176      if(alen > 0 && buf[plen - 1] != '/')
1177      {
1178        buf[plen++] = '/';
1179        if(plen >= FTPD_BUFSIZE)
1180          return 0;
1181        buf[plen] = '\0';
1182      }
1183    }
1184    if(plen + alen >= FTPD_BUFSIZE)
1185      return 0;
1186    strcpy(buf + plen, add);
1187
1188    if (stat(buf, &stat_buf) == 0)
1189    {
1190      int len;
1191      struct tm bt;
1192      time_t tf = stat_buf.st_mtime;
1193      enum { SIZE = 80 };
1194      time_t SIX_MONTHS = (365L*24L*60L*60L)/2L;
1195      char timeBuf[SIZE];
1196      gmtime_r(&tf, &bt);
1197      if(curTime > tf + SIX_MONTHS || tf > curTime + SIX_MONTHS)
1198        strftime (timeBuf, SIZE, "%b %d  %Y", &bt);
1199      else
1200        strftime (timeBuf, SIZE, "%b %d %H:%M", &bt);
1201
1202      len = snprintf(buf, FTPD_BUFSIZE,
1203        "%c%c%c%c%c%c%c%c%c%c  1 %5d %5d %11u %s %s\r\n",
1204        (S_ISLNK(stat_buf.st_mode)?('l'):
1205          (S_ISDIR(stat_buf.st_mode)?('d'):('-'))),
1206        (stat_buf.st_mode & S_IRUSR)?('r'):('-'),
1207        (stat_buf.st_mode & S_IWUSR)?('w'):('-'),
1208        (stat_buf.st_mode & S_IXUSR)?('x'):('-'),
1209        (stat_buf.st_mode & S_IRGRP)?('r'):('-'),
1210        (stat_buf.st_mode & S_IWGRP)?('w'):('-'),
1211        (stat_buf.st_mode & S_IXGRP)?('x'):('-'),
1212        (stat_buf.st_mode & S_IROTH)?('r'):('-'),
1213        (stat_buf.st_mode & S_IWOTH)?('w'):('-'),
1214        (stat_buf.st_mode & S_IXOTH)?('x'):('-'),
1215        (int)stat_buf.st_uid,
1216        (int)stat_buf.st_gid,
1217        (int)stat_buf.st_size,
1218        timeBuf,
1219        fname
1220      );
1221
1222      if(send(s, buf, len, 0) != len)
1223        return 0;
1224    }
1225  }
1226  else
1227  {
1228    int len = snprintf(buf, FTPD_BUFSIZE, "%s\r\n", fname);
1229    if(send(s, buf, len, 0) != len)
1230      return 0;
1231  }
1232  return 1;
1233}
1234
1235/*
1236 * command_list
1237 *
1238 * Send file list to client.
1239 *
1240 * Input parameters:
1241 *   info - corresponding SessionInfo structure
1242 *   char *fname  - File (or directory) to list.
1243 *
1244 * Output parameters:
1245 *   NONE
1246 */
1247static void
1248command_list(FTPD_SessionInfo_t *info, char const *fname, int wide)
1249{
1250  int                 s;
1251  DIR                 *dirp = 0;
1252  struct dirent       *dp = 0;
1253  struct stat         stat_buf;
1254  char                buf[FTPD_BUFSIZE];
1255  time_t curTime;
1256  int sc = 1;
1257
1258  if(!info->auth)
1259  {
1260    send_reply(info, 550, "Access denied.");
1261    return;
1262  }
1263
1264  send_reply(info, 150, "Opening ASCII mode data connection for LIST.");
1265
1266  s = data_socket(info);
1267  if(0 > s)
1268  {
1269    syslog(LOG_ERR, "ftpd: Error connecting to data socket.");
1270    return;
1271  }
1272
1273  if(fname[0] == '\0')
1274    fname = ".";
1275
1276  if (0 > stat(fname, &stat_buf))
1277  {
1278    snprintf(buf, FTPD_BUFSIZE,
1279      "%s: No such file or directory.\r\n", fname);
1280    send(s, buf, strlen(buf), 0);
1281  }
1282  else if (S_ISDIR(stat_buf.st_mode) && (NULL == (dirp = opendir(fname))))
1283  {
1284    snprintf(buf, FTPD_BUFSIZE,
1285      "%s: Can not open directory.\r\n", fname);
1286    send(s, buf, strlen(buf), 0);
1287  }
1288  else
1289  {
1290    time(&curTime);
1291    if(!dirp && *fname)
1292      sc = sc && send_dirline(s, wide, curTime, fname, "", fname, buf);
1293    else {
1294      /* FIXME: need "." and ".." only when '-a' option is given */
1295      sc = sc && send_dirline(s, wide, curTime, fname, "", ".", buf);
1296      sc = sc && send_dirline(s, wide, curTime, fname,
1297        (strcmp(fname, ftpd_root) ? ".." : ""), "..", buf);
1298      while (sc && (dp = readdir(dirp)) != NULL)
1299        sc = sc &&
1300          send_dirline(s, wide, curTime, fname, dp->d_name, dp->d_name, buf);
1301    }
1302  }
1303
1304  if(dirp)
1305    closedir(dirp);
1306  close_data_socket(info);
1307
1308  if(sc)
1309    send_reply(info, 226, "Transfer complete.");
1310  else
1311    send_reply(info, 426, "Connection aborted.");
1312}
1313
1314
1315/*
1316 * command_cwd
1317 *
1318 * Change current working directory.
1319 *
1320 * Input parameters:
1321 *   info - corresponding SessionInfo structure
1322 *   dir  - directory name passed in CWD command
1323 *
1324 * Output parameters:
1325 *   NONE
1326 *
1327 */
1328static void
1329command_cwd(FTPD_SessionInfo_t  *info, char *dir)
1330{
1331  if(!info->auth)
1332  {
1333    send_reply(info, 550, "Access denied.");
1334    return;
1335  }
1336
1337  if(chdir(dir) == 0)
1338    send_reply(info, 250, "CWD command successful.");
1339  else
1340    send_reply(info, 550, "CWD command failed.");
1341}
1342
1343
1344/*
1345 * command_pwd
1346 *
1347 * Send current working directory to client.
1348 *
1349 * Input parameters:
1350 *   info - corresponding SessionInfo structure
1351 *
1352 * Output parameters:
1353 *   NONE
1354 */
1355static void
1356command_pwd(FTPD_SessionInfo_t  *info)
1357{
1358  char buf[FTPD_BUFSIZE];
1359  char const* cwd;
1360  errno = 0;
1361  buf[0] = '"';
1362
1363  if(!info->auth)
1364  {
1365    send_reply(info, 550, "Access denied.");
1366    return;
1367  }
1368
1369  cwd = getcwd(buf + 1, FTPD_BUFSIZE - 4);
1370  if(cwd)
1371  {
1372    int len = strlen(cwd);
1373    static char const txt[] = "\" is the current directory.";
1374    int size = sizeof(txt);
1375    if(len + size + 1 >= FTPD_BUFSIZE)
1376      size = FTPD_BUFSIZE - len - 2;
1377    memcpy(buf + len + 1, txt, size);
1378    buf[len + size] = '\0';
1379    send_reply(info, 250, buf);
1380  }
1381  else {
1382    snprintf(buf, FTPD_BUFSIZE, "Error: %s.", serr());
1383    send_reply(info, 452, buf);
1384  }
1385}
1386
1387/*
1388 * command_mdtm
1389 *
1390 * Handle FTP MDTM command (send file modification time to client)/
1391 *
1392 * Input parameters:
1393 *   info - corresponding SessionInfo structure
1394 *   fname - file name passed in MDTM command
1395 *
1396 * Output parameters:
1397 *   info->cwd is set to new CWD value.
1398 */
1399static void
1400command_mdtm(FTPD_SessionInfo_t  *info, char const* fname)
1401{
1402  struct stat stbuf;
1403  char buf[FTPD_BUFSIZE];
1404
1405  if(!info->auth)
1406  {
1407    send_reply(info, 550, "Access denied.");
1408    return;
1409  }
1410
1411  if (0 > stat(fname, &stbuf))
1412  {
1413    snprintf(buf, FTPD_BUFSIZE, "%s: %s.", fname, serr());
1414    send_reply(info, 550, buf);
1415  }
1416  else
1417  {
1418    struct tm *t = gmtime(&stbuf.st_mtime);
1419    snprintf(buf, FTPD_BUFSIZE, "%04d%02d%02d%02d%02d%02d",
1420      1900 + t->tm_year,
1421      t->tm_mon+1, t->tm_mday,
1422      t->tm_hour, t->tm_min, t->tm_sec);
1423    send_reply(info, 213, buf);
1424  }
1425}
1426
1427static void
1428command_size(FTPD_SessionInfo_t *info, char const* fname)
1429{
1430  struct stat stbuf;
1431  char buf[FTPD_BUFSIZE];
1432
1433  if(!info->auth)
1434  {
1435    send_reply(info, 550, "Access denied.");
1436    return;
1437  }
1438
1439  if (info->xfer_mode != TYPE_I || 0 > stat(fname, &stbuf) || stbuf.st_size < 0)
1440  {
1441    send_reply(info, 550, "Could not get file size.");
1442  }
1443  else
1444  {
1445    snprintf(buf, FTPD_BUFSIZE, "%" PRIuMAX, (uintmax_t) stbuf.st_size);
1446    send_reply(info, 213, buf);
1447  }
1448}
1449
1450/*
1451 * command_port
1452 *
1453 * This procedure fills address for data connection given the IP address and
1454 * port of the remote machine.
1455 *
1456 * Input parameters:
1457 *   info - corresponding SessionInfo structure
1458 *   args - arguments to the "PORT" command.
1459 *
1460 * Output parameters:
1461 *   info->data_addr is set according to arguments of the PORT command.
1462 *   info->use_default is set to 0 on success, 1 on failure.
1463 */
1464static void
1465command_port(FTPD_SessionInfo_t *info, char const *args)
1466{
1467  enum { NUM_FIELDS = 6 };
1468  unsigned int a[NUM_FIELDS];
1469  int n;
1470
1471  close_data_socket(info);
1472
1473  n = sscanf(args, "%u,%u,%u,%u,%u,%u", a+0, a+1, a+2, a+3, a+4, a+5);
1474  if(NUM_FIELDS == n)
1475  {
1476    int i;
1477    union {
1478      uint8_t b[NUM_FIELDS];
1479      struct {
1480        uint32_t ip;
1481        uint16_t port;
1482      } u ;
1483    } ip_info;
1484
1485    for(i = 0; i < NUM_FIELDS; ++i)
1486    {
1487      if(a[i] > 255)
1488        break;
1489      ip_info.b[i] = (uint8_t)a[i];
1490    }
1491
1492    if(i == NUM_FIELDS)
1493    {
1494      /* Note: while it contradicts with RFC959, we don't allow PORT command
1495       * to specify IP address different than those of the originating client
1496       * for the sake of safety. */
1497      if (ip_info.u.ip == info->def_addr.sin_addr.s_addr)
1498      {
1499        info->data_addr.sin_addr.s_addr = ip_info.u.ip;
1500        info->data_addr.sin_port        = ip_info.u.port;
1501        info->data_addr.sin_family      = AF_INET;
1502        memset(info->data_addr.sin_zero, 0, sizeof(info->data_addr.sin_zero));
1503
1504        info->use_default = 0;
1505        send_reply(info, 200, "PORT command successful.");
1506        return; /* success */
1507      }
1508      else
1509      {
1510        send_reply(info, 425, "Address doesn't match peer's IP.");
1511        return;
1512      }
1513    }
1514  }
1515  send_reply(info, 501, "Syntax error.");
1516}
1517
1518
1519/*
1520 * command_pasv
1521 *
1522 * Handle FTP PASV command.
1523 * Open socket, listen for and accept connection on it.
1524 *
1525 * Input parameters:
1526 *   info - corresponding SessionInfo structure
1527 *
1528 * Output parameters:
1529 *   info->pasv_socket is set to the descriptor of the data socket
1530 */
1531static void
1532command_pasv(FTPD_SessionInfo_t *info)
1533{
1534  int s = -1;
1535  int err = 1;
1536
1537  close_data_socket(info);
1538
1539  s = socket(PF_INET, SOCK_STREAM, 0);
1540  if (s < 0)
1541    syslog(LOG_ERR, "ftpd: Error creating PASV socket: %s", serr());
1542  else
1543  {
1544    struct sockaddr_in addr;
1545    socklen_t addrLen = sizeof(addr);
1546
1547    addr = info->ctrl_addr;
1548    addr.sin_port = htons(0);
1549
1550    if (0 > bind(s, (struct sockaddr *)&addr, addrLen))
1551      syslog(LOG_ERR, "ftpd: Error binding PASV socket: %s", serr());
1552    else if (0 > listen(s, 1))
1553      syslog(LOG_ERR, "ftpd: Error listening on PASV socket: %s", serr());
1554    else if(set_socket_timeout(s, info->idle))
1555    {
1556      char buf[FTPD_BUFSIZE];
1557      unsigned char const *ip, *p;
1558
1559      getsockname(s, (struct sockaddr *)&addr, &addrLen);
1560      ip = (unsigned char const*)&(addr.sin_addr);
1561      p  = (unsigned char const*)&(addr.sin_port);
1562      snprintf(buf, FTPD_BUFSIZE, "Entering passive mode (%u,%u,%u,%u,%u,%u).",
1563        ip[0], ip[1], ip[2], ip[3], p[0], p[1]);
1564      send_reply(info, 227, buf);
1565
1566      info->pasv_socket = accept(s, (struct sockaddr *)&addr, &addrLen);
1567      if (0 > info->pasv_socket)
1568        syslog(LOG_ERR, "ftpd: Error accepting PASV connection: %s", serr());
1569      else
1570      {
1571        close_socket(s);
1572        s = -1;
1573        err = 0;
1574      }
1575    }
1576  }
1577  if(err)
1578  {
1579    /* (OSV) The note is from FreeBSD FTPD.
1580     * Note: a response of 425 is not mentioned as a possible response to
1581     * the PASV command in RFC959.  However, it has been blessed as a
1582     * legitimate response by Jon Postel in a telephone conversation
1583     * with Rick Adams on 25 Jan 89. */
1584    send_reply(info, 425, "Can't open passive connection.");
1585    close_socket(s);
1586  }
1587}
1588
1589
1590/*
1591 * skip_options
1592 *
1593 * Utility routine to skip options (if any) from input command.
1594 *
1595 * Input parameters:
1596 *   p  - pointer to pointer to command
1597 *
1598 * Output parameters:
1599 *   p  - is changed to point to first non-option argument
1600 */
1601static void
1602skip_options(char **p)
1603{
1604  char* buf = *p;
1605  char* last = NULL;
1606  while(1) {
1607    while(isspace((unsigned char)*buf))
1608      ++buf;
1609    if(*buf == '-') {
1610      if(*++buf == '-') { /* `--' should terminate options */
1611        if(isspace((unsigned char)*++buf)) {
1612          last = buf;
1613          do ++buf;
1614          while(isspace((unsigned char)*buf));
1615          break;
1616        }
1617      }
1618      while(*buf && !isspace((unsigned char)*buf))
1619        ++buf;
1620      last = buf;
1621    }
1622    else
1623      break;
1624  }
1625  if(last)
1626    *last = '\0';
1627  *p = buf;
1628}
1629
1630/*
1631 * split_command
1632 *
1633 * Split command into command itself, options, and arguments. Command itself
1634 * is converted to upper case.
1635 *
1636 * Input parameters:
1637 *   buf - initial command string
1638 *
1639 * Output parameter:
1640 *   buf  - is modified by inserting '\0' at ends of split entities
1641 *   cmd  - upper-cased command code
1642 *   opts - string containing all the options
1643 *   args - string containing all the arguments
1644 */
1645static void
1646split_command(char *buf, char **cmd, char **opts, char **args)
1647{
1648  char* eoc;
1649  char* p = buf;
1650  while(isspace((unsigned char)*p))
1651    ++p;
1652  *cmd = p;
1653  while(*p && !isspace((unsigned char)*p))
1654  {
1655    *p = toupper((unsigned char)*p);
1656    ++p;
1657  }
1658  eoc = p;
1659  if(*p)
1660    *p++ = '\0';
1661  while(isspace((unsigned char)*p))
1662    ++p;
1663  *opts = p;
1664  skip_options(&p);
1665  *args = p;
1666  if(*opts == p)
1667    *opts = eoc;
1668  while(*p && *p != '\r' && *p != '\n')
1669    ++p;
1670  if(*p)
1671    *p++ = '\0';
1672}
1673
1674/*
1675 * exec_command
1676 *
1677 * Parse and execute FTP command.
1678 *
1679 * FIXME: This section is somewhat of a hack.  We should have a better
1680 *        way to parse commands.
1681 *
1682 * Input parameters:
1683 *   info - corresponding SessionInfo structure
1684 *   cmd  - command to be executed (upper-case)
1685 *   args - arguments of the command
1686 *
1687 * Output parameters:
1688 *    NONE
1689 */
1690static void
1691exec_command(FTPD_SessionInfo_t *info, char* cmd, char* args)
1692{
1693  char fname[FTPD_BUFSIZE];
1694  int wrong_command = 0;
1695
1696  fname[0] = '\0';
1697
1698  if (!strcmp("PORT", cmd))
1699  {
1700    command_port(info, args);
1701  }
1702  else if (!strcmp("PASV", cmd))
1703  {
1704    command_pasv(info);
1705  }
1706  else if (!strcmp("RETR", cmd))
1707  {
1708    strncpy(fname, args, 254);
1709    command_retrieve(info, fname);
1710  }
1711  else if (!strcmp("STOR", cmd))
1712  {
1713    strncpy(fname, args, 254);
1714    command_store(info, fname);
1715  }
1716  else if (!strcmp("LIST", cmd))
1717  {
1718    strncpy(fname, args, 254);
1719    command_list(info, fname, 1);
1720  }
1721  else if (!strcmp("NLST", cmd))
1722  {
1723    strncpy(fname, args, 254);
1724    command_list(info, fname, 0);
1725  }
1726  else if (!strcmp("MDTM", cmd))
1727  {
1728    strncpy(fname, args, 254);
1729    command_mdtm(info, fname);
1730  }
1731  else if (!strcmp("SIZE", cmd))
1732  {
1733    strncpy(fname, args, 254);
1734    command_size(info, fname);
1735  }
1736  else if (!strcmp("SYST", cmd))
1737  {
1738    send_reply(info, 215, FTPD_SYSTYPE);
1739  }
1740  else if (!strcmp("TYPE", cmd))
1741  {
1742    if (args[0] == 'I')
1743    {
1744      info->xfer_mode = TYPE_I;
1745      send_reply(info, 200, "Type set to I.");
1746    }
1747    else if (args[0] == 'A')
1748    {
1749      info->xfer_mode = TYPE_A;
1750      send_reply(info, 200, "Type set to A.");
1751    }
1752    else
1753    {
1754      info->xfer_mode = TYPE_I;
1755      send_reply(info, 504, "Type not implemented.  Set to I.");
1756    }
1757  }
1758  else if (!strcmp("USER", cmd))
1759  {
1760    sscanf(args, "%254s", fname);
1761    if (info->user)
1762      free(info->user);
1763    if (info->pass)
1764      free(info->pass);
1765    info->pass = NULL;
1766    info->user = strdup(fname);
1767    if (ftpd_config->login &&
1768      !ftpd_config->login(info->user, NULL)) {
1769      info->auth = false;
1770      send_reply(info, 331, "User name okay, need password.");
1771    } else {
1772      info->auth = true;
1773      send_reply(info, 230, "User logged in.");
1774    }
1775  }
1776  else if (!strcmp("PASS", cmd))
1777  {
1778    sscanf(args, "%254s", fname);
1779    if (info->pass)
1780      free(info->pass);
1781    info->pass = strdup(fname);
1782    if (!info->user) {
1783      send_reply(info, 332, "Need account to log in");
1784    } else {
1785      if (ftpd_config->login &&
1786        !ftpd_config->login(info->user, info->pass)) {
1787        info->auth = false;
1788        send_reply(info, 530, "Not logged in.");
1789      } else {
1790        info->auth = true;
1791        send_reply(info, 230, "User logged in.");
1792      }
1793    }
1794  }
1795  else if (!strcmp("DELE", cmd))
1796  {
1797    if(!can_write() || !info->auth)
1798    {
1799      send_reply(info, 550, "Access denied.");
1800    }
1801    else if (
1802      strncpy(fname, args, 254) &&
1803      unlink(fname) == 0)
1804    {
1805      send_reply(info, 257, "DELE successful.");
1806    }
1807    else
1808    {
1809      send_reply(info, 550, "DELE failed.");
1810    }
1811  }
1812  else if (!strcmp("SITE", cmd))
1813  {
1814    char* opts;
1815    split_command(args, &cmd, &opts, &args);
1816    if(!strcmp("CHMOD", cmd))
1817    {
1818      int mask;
1819
1820      if(!can_write() || !info->auth)
1821      {
1822        send_reply(info, 550, "Access denied.");
1823      }
1824      else {
1825        char *c;
1826        c = strchr(args, ' ');
1827        if((c != NULL) && (sscanf(args, "%o", &mask) == 1) && strncpy(fname, c+1, 254)
1828          && (chmod(fname, (mode_t)mask) == 0))
1829          send_reply(info, 257, "CHMOD successful.");
1830        else
1831          send_reply(info, 550, "CHMOD failed.");
1832      }
1833    }
1834    else
1835      wrong_command = 1;
1836  }
1837  else if (!strcmp("RMD", cmd))
1838  {
1839    if(!can_write() || !info->auth)
1840    {
1841      send_reply(info, 550, "Access denied.");
1842    }
1843    else if (
1844      strncpy(fname, args, 254) &&
1845      rmdir(fname) == 0)
1846    {
1847      send_reply(info, 257, "RMD successful.");
1848    }
1849    else
1850    {
1851      send_reply(info, 550, "RMD failed.");
1852    }
1853  }
1854  else if (!strcmp("MKD", cmd))
1855  {
1856    if(!can_write() || !info->auth)
1857    {
1858      send_reply(info, 550, "Access denied.");
1859    }
1860    else if (
1861      strncpy(fname, args, 254) &&
1862      mkdir(fname, S_IRWXU | S_IRWXG | S_IRWXO) == 0)
1863    {
1864      send_reply(info, 257, "MKD successful.");
1865    }
1866    else
1867    {
1868      send_reply(info, 550, "MKD failed.");
1869    }
1870  }
1871  else if (!strcmp("CWD", cmd))
1872  {
1873    strncpy(fname, args, 254);
1874    command_cwd(info, fname);
1875  }
1876  else if (!strcmp("CDUP", cmd))
1877  {
1878    command_cwd(info, "..");
1879  }
1880  else if (!strcmp("PWD", cmd))
1881  {
1882    command_pwd(info);
1883  }
1884  else
1885    wrong_command = 1;
1886
1887  if(wrong_command)
1888    send_reply(info, 500, "Command not understood.");
1889}
1890
1891
1892/*
1893 * session
1894 *
1895 * This task handles single session.  It is waked up when the FTP daemon gets a
1896 * service request from a remote machine.  Here, we watch for commands that
1897 * will come through the control connection.  These commands are then parsed
1898 * and executed until the connection is closed, either unintentionally or
1899 * intentionally with the "QUIT" command.
1900 *
1901 * Input parameters:
1902 *   arg - pointer to corresponding SessionInfo.
1903 *
1904 * Output parameters:
1905 *   NONE
1906 */
1907static void
1908session(rtems_task_argument arg)
1909{
1910  FTPD_SessionInfo_t  *const info = (FTPD_SessionInfo_t  *)arg;
1911  int chroot_made = 0;
1912
1913  rtems_libio_set_private_env();
1914
1915  /* chroot() can fail here because the directory may not exist yet. */
1916  chroot_made = chroot(ftpd_root) == 0;
1917
1918  while(1)
1919  {
1920    rtems_event_set set;
1921    int rv;
1922
1923    rtems_event_receive(FTPD_RTEMS_EVENT, RTEMS_EVENT_ANY, RTEMS_NO_TIMEOUT,
1924      &set);
1925
1926    chroot_made = chroot_made || chroot(ftpd_root) == 0;
1927
1928    rv = chroot_made ? chdir("/") : -1;
1929
1930    errno = 0;
1931
1932    if (rv == 0)
1933    {
1934      send_reply(info, 220, FTPD_SERVER_MESSAGE);
1935
1936      while (1)
1937      {
1938        char buf[FTPD_BUFSIZE];
1939        char *cmd, *opts, *args;
1940
1941        if (fgets(buf, FTPD_BUFSIZE, info->ctrl_fp) == NULL)
1942        {
1943          syslog(LOG_INFO, "ftpd: Connection aborted.");
1944          break;
1945        }
1946
1947        split_command(buf, &cmd, &opts, &args);
1948
1949        if (!strcmp("QUIT", cmd))
1950        {
1951          send_reply(info, 221, "Goodbye.");
1952          break;
1953        }
1954        else
1955        {
1956          exec_command(info, cmd, args);
1957        }
1958      }
1959    }
1960    else
1961    {
1962      send_reply(info, 421, "Service not available, closing control connection.");
1963    }
1964
1965    /*
1966     * Go back to the root directory.  A use case is to release a current
1967     * directory in a mounted file system on dynamic media, e.g. USB stick.
1968     * The return value can be ignored since the next session will try do the
1969     * operation again and an error check is performed in this case.
1970     */
1971    chdir("/");
1972
1973    /* Close connection and put ourselves back into the task pool. */
1974    close_data_socket(info);
1975    close_stream(info);
1976    free(info->user);
1977    free(info->pass);
1978    task_pool_release(info);
1979  }
1980}
1981
1982
1983/*
1984 * ftpd_daemon
1985 *
1986 * This task runs forever.  It waits for service requests on the FTP port
1987 * (port 21 by default).  When a request is received, it opens a new session
1988 * to handle those requests until the connection is closed.
1989 *
1990 * Input parameters:
1991 *   NONE
1992 *
1993 * Output parameters:
1994 *   NONE
1995 */
1996static void
1997ftpd_daemon(rtems_task_argument args __attribute__((unused)))
1998{
1999  int                 s;
2000  socklen_t           addrLen;
2001  struct sockaddr_in  addr;
2002  FTPD_SessionInfo_t  *info = NULL;
2003
2004
2005  s = socket(PF_INET, SOCK_STREAM, 0);
2006  if (s < 0)
2007    syslog(LOG_ERR, "ftpd: Error creating socket: %s", serr());
2008
2009  addr.sin_family      = AF_INET;
2010  addr.sin_port        = htons(ftpd_config->port);
2011  addr.sin_addr.s_addr = htonl(INADDR_ANY);
2012  memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
2013
2014  if (0 > bind(s, (struct sockaddr *)&addr, sizeof(addr)))
2015    syslog(LOG_ERR, "ftpd: Error binding control socket: %s", serr());
2016  else if (0 > listen(s, 1))
2017    syslog(LOG_ERR, "ftpd: Error listening on control socket: %s", serr());
2018  else while (1)
2019  {
2020    int ss;
2021    addrLen = sizeof(addr);
2022    ss = accept(s, (struct sockaddr *)&addr, &addrLen);
2023    if (0 > ss)
2024      syslog(LOG_ERR, "ftpd: Error accepting control connection: %s", serr());
2025    else if(!set_socket_timeout(ss, ftpd_timeout))
2026      close_socket(ss);
2027    else
2028    {
2029      info = task_pool_obtain();
2030      if (NULL == info)
2031      {
2032        close_socket(ss);
2033      }
2034      else
2035      {
2036        info->ctrl_socket = ss;
2037        if ((info->ctrl_fp = fdopen(info->ctrl_socket, "r+")) == NULL)
2038        {
2039          syslog(LOG_ERR, "ftpd: fdopen() on socket failed: %s", serr());
2040          close_stream(info);
2041          task_pool_release(info);
2042        }
2043        else
2044        {
2045          /* Initialize corresponding SessionInfo structure */
2046          info->def_addr = addr;
2047          if(0 > getsockname(ss, (struct sockaddr *)&addr, &addrLen))
2048          {
2049            syslog(LOG_ERR, "ftpd: getsockname(): %s", serr());
2050            close_stream(info);
2051            task_pool_release(info);
2052          }
2053          else
2054          {
2055            info->use_default = 1;
2056            info->ctrl_addr  = addr;
2057            info->pasv_socket = -1;
2058            info->data_socket = -1;
2059            info->xfer_mode   = TYPE_A;
2060            info->data_addr.sin_port =
2061              htons(ntohs(info->ctrl_addr.sin_port) - 1);
2062            info->idle = ftpd_timeout;
2063            info->user = NULL;
2064            info->pass = NULL;
2065            if (ftpd_config->login)
2066              info->auth = false;
2067            else
2068              info->auth = true;
2069            /* Wakeup the session task.  The task will call task_pool_release
2070               after it closes connection. */
2071            rtems_event_send(info->tid, FTPD_RTEMS_EVENT);
2072          }
2073        }
2074      }
2075    }
2076  }
2077  rtems_task_delete(RTEMS_SELF);
2078}
2079
2080
2081/*
2082 * rtems_ftpd_start
2083 *
2084 * Here, we start the FTPD task which waits for FTP requests and services
2085 * them.  This procedure returns to its caller once the task is started.
2086 *
2087 *
2088 * Input parameters:
2089 *    config: constant initial setup.
2090 * Output parameters:
2091 *    returns RTEMS_SUCCESSFUL on successful start of the daemon.
2092 */
2093int
2094rtems_ftpd_start(const struct rtems_ftpd_configuration* config)
2095{
2096  rtems_status_code   sc;
2097  rtems_id            tid;
2098  rtems_task_priority priority;
2099  int count;
2100
2101  if (ftpd_config != NULL)
2102    return RTEMS_RESOURCE_IN_USE;
2103
2104  ftpd_config = malloc(sizeof(*ftpd_config));
2105  if (ftpd_config == NULL)
2106    return RTEMS_NO_MEMORY;
2107
2108  *ftpd_config = *config;
2109
2110  if (ftpd_config->port == 0)
2111  {
2112    ftpd_config->port = FTPD_CONTROL_PORT;
2113  }
2114
2115  if (ftpd_config->priority == 0)
2116  {
2117    ftpd_config->priority = 40;
2118  }
2119  priority = ftpd_config->priority;
2120
2121  ftpd_timeout = ftpd_config->idle;
2122  if (ftpd_timeout < 0)
2123    ftpd_timeout = 0;
2124  ftpd_config->idle = ftpd_timeout;
2125
2126  ftpd_access = ftpd_config->access;
2127
2128  ftpd_root = "/";
2129  if (ftpd_config->root && ftpd_config->root[0] == '/' )
2130    ftpd_root = ftpd_config->root;
2131
2132  ftpd_config->root = ftpd_root;
2133
2134  if (ftpd_config->tasks_count <= 0)
2135    ftpd_config->tasks_count = 1;
2136  count = ftpd_config->tasks_count;
2137
2138  if (!task_pool_init(count, priority))
2139  {
2140    syslog(LOG_ERR, "ftpd: Could not initialize task pool.");
2141    return RTEMS_UNSATISFIED;
2142  }
2143
2144  sc = rtems_task_create(rtems_build_name('F', 'T', 'P', 'D'),
2145    priority, RTEMS_MINIMUM_STACK_SIZE,
2146    RTEMS_PREEMPT | RTEMS_NO_TIMESLICE | RTEMS_NO_ASR |
2147    RTEMS_INTERRUPT_LEVEL(0),
2148    RTEMS_FLOATING_POINT | RTEMS_LOCAL,
2149    &tid);
2150
2151  if (sc == RTEMS_SUCCESSFUL)
2152  {
2153    sc = rtems_task_start(tid, ftpd_daemon, 0);
2154    if (sc != RTEMS_SUCCESSFUL)
2155      rtems_task_delete(tid);
2156  }
2157
2158  if (sc != RTEMS_SUCCESSFUL)
2159  {
2160    task_pool_done(count);
2161    syslog(LOG_ERR, "ftpd: Could not create/start FTP daemon: %s",
2162      rtems_status_text(sc));
2163    return RTEMS_UNSATISFIED;
2164  }
2165
2166  if (ftpd_config->verbose)
2167    syslog(LOG_INFO, "ftpd: FTP daemon started (%d session%s max)",
2168           count, ((count > 1) ? "s" : ""));
2169
2170  return RTEMS_SUCCESSFUL;
2171}
Note: See TracBrowser for help on using the repository browser.