source: rtems/cpukit/ftpd/ftpd.c @ 4d111157

Last change on this file since 4d111157 was 4d111157, checked in by Joel Sherrill <joel.sherrill@…>, on 08/28/07 at 14:03:52

2007-08-28 Joel Sherrill <joel.sherrill@…>

PR 1256/networking

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