source: rtems/cpukit/ftpd/ftpd.c @ 1343dfa3

4.115
Last change on this file since 1343dfa3 was 1343dfa3, checked in by Sebastian Huber <sebastian.huber@…>, on 06/07/11 at 09:08:37

2011-06-07 Sebastian Huber <sebastian.huber@…>

PR 1811/networking

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