source: rtems/cpukit/ftpd/ftpd.c @ 5bc8a75

4.104.114.95
Last change on this file since 5bc8a75 was 5bc8a75, checked in by Ralf Corsepius <ralf.corsepius@…>, on 07/29/08 at 11:46:19

Add prototypes.

  • Property mode set to 100644
File size: 48.7 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#if HAVE_CONFIG_H
181#include "config.h"
182#endif
183
184#include <stdio.h>
185#include <stdlib.h>
186#include <string.h>
187#include <unistd.h>
188#include <fcntl.h>
189#include <dirent.h>
190#include <errno.h>
191#include <ctype.h>
192
193#include <rtems.h>
194#include <rtems/rtems_bsdnet.h>
195#include <rtems/error.h>
196#include <rtems/libio.h>
197#include <rtems/userenv.h>
198#include <syslog.h>
199
200#include <sys/types.h>
201#include <sys/socket.h>
202#include <arpa/ftp.h>
203#include <netinet/in.h>
204
205#include "ftpd.h"
206
207
208#ifdef __GNUC__
209/* change to #if 1 to disable syslog entirely */
210#if 0
211#undef  syslog
212#define syslog(a, b, ...) while(0){}
213#endif
214#endif
215
216#define FTPD_SERVER_MESSAGE  "RTEMS FTP server (Version 1.1-JWJ) ready."
217
218#define FTPD_SYSTYPE "UNIX Type: L8"
219
220/* Seem to be unused */
221#if 0
222#define FTPD_WELCOME_MESSAGE \
223   "Welcome to the RTEMS FTP server.\n" \
224   "\n" \
225   "Login accepted.\n"
226#endif
227
228/* Various buffer sizes */
229enum
230{
231  FTPD_BUFSIZE  = 256,       /* Size for temporary buffers */
232  FTPD_DATASIZE = 1024,      /* Size for file transfer buffers */
233  FTPD_STACKSIZE = 8 * 1024, /* Tasks stack size */
234};
235
236/* Event to be used by session tasks for waiting */
237enum
238{
239  FTPD_RTEMS_EVENT = RTEMS_EVENT_1
240};
241
242/* Configuration table */
243extern struct rtems_ftpd_configuration rtems_ftpd_configuration;
244
245/* this is not prototyped in strict ansi mode */
246FILE *fdopen (int fildes, const char *mode);
247
248/*PAGE
249 * SessionInfo structure.
250 *
251 * The following structure is allocated for each session.
252 */
253typedef struct
254{
255  struct sockaddr_in  ctrl_addr;   /* Control connection self address */
256  struct sockaddr_in  data_addr;   /* Data address set by PORT command */
257  struct sockaddr_in  def_addr;    /* Default address for data */
258  int                 use_default; /* 1 - use default address for data */
259  FILE                *ctrl_fp;    /* File pointer for control connection */
260  int                 ctrl_socket; /* Socket for ctrl connection */
261  int                 pasv_socket; /* Socket for PASV connection */
262  int                 data_socket; /* Socket for data connection */
263  int                 idle;        /* Timeout in seconds */
264  int                 xfer_mode;   /* Transfer mode (ASCII/binary) */
265  rtems_id            tid;         /* Task id */
266} FTPD_SessionInfo_t;
267
268
269/*
270 * TaskPool structure.
271 */
272typedef struct
273{
274  FTPD_SessionInfo_t    *info;
275  FTPD_SessionInfo_t    **queue;
276  int                   count;
277  int                   head;
278  int                   tail;
279  rtems_id              mutex;
280  rtems_id              sem;
281} FTPD_TaskPool_t;
282
283/*
284 * Task pool instance.
285 */
286static FTPD_TaskPool_t task_pool;
287
288/*
289 * Root directory
290 */
291
292static char const* ftpd_root = "/";
293
294/*
295 * Default idle timeout for sockets in seconds.
296 */
297static int ftpd_timeout = 0;
298
299/*
300 * Global access flags.
301 */
302static int ftpd_access = 0;
303
304/*PAGE
305 *
306 * serr
307 *
308 * Return error string corresponding to current 'errno'.
309 *
310 */
311static char const*
312serr(void)
313{
314  int err = errno;
315  errno = 0;
316  return strerror(err);
317}
318
319/*PAGE
320 *
321 * Utility routines for access control.
322 *
323 */
324
325static int
326can_read(void)
327{
328  return (ftpd_access & FTPD_NO_READ) == 0;
329}
330
331static int
332can_write(void)
333{
334  return (ftpd_access & FTPD_NO_WRITE) == 0;
335}
336
337/*PAGE
338 *
339 * Task pool management routines
340 *
341 */
342
343
344/*PAGE
345 *
346 * task_pool_done
347 *
348 * Cleanup task pool.
349 *
350 * Input parameters:
351 *   count - number of entries in task pool to cleanup
352 *
353 * Output parameters:
354 *   NONE
355 *
356 */
357static void
358task_pool_done(int count)
359{
360  int i;
361  for(i = 0; i < count; ++i)
362    rtems_task_delete(task_pool.info[i].tid);
363  if(task_pool.info)
364    free(task_pool.info);
365  if(task_pool.queue)
366    free(task_pool.queue);
367  if(task_pool.mutex != (rtems_id)-1)
368    rtems_semaphore_delete(task_pool.mutex);
369  if(task_pool.sem != (rtems_id)-1)
370    rtems_semaphore_delete(task_pool.sem);
371  task_pool.info = 0;
372  task_pool.queue = 0;
373  task_pool.count = 0;
374  task_pool.sem = -1;
375  task_pool.mutex = -1;
376}
377
378/*PAGE
379 *
380 * task_pool_init
381 *
382 * Initialize task pool.
383 *
384 * Input parameters:
385 *   count    - number of entries in task pool to create
386 *   priority - priority tasks are started with
387 *
388 * Output parameters:
389 *   returns 1 on success, 0 on failure.
390 *
391 */
392static void session(rtems_task_argument arg); /* Forward declare */
393
394static int
395task_pool_init(int count, rtems_task_priority priority)
396{
397  int i;
398  rtems_status_code sc;
399  char id = 'a';
400
401  task_pool.count = 0;
402  task_pool.head = task_pool.tail = 0;
403  task_pool.mutex = (rtems_id)-1;
404  task_pool.sem   = (rtems_id)-1;
405
406  sc = rtems_semaphore_create(
407    rtems_build_name('F', 'T', 'P', 'M'),
408    1,
409    RTEMS_DEFAULT_ATTRIBUTES
410    | RTEMS_BINARY_SEMAPHORE
411    | RTEMS_INHERIT_PRIORITY
412    | RTEMS_PRIORITY,
413    RTEMS_NO_PRIORITY,
414    &task_pool.mutex);
415
416  if(sc == RTEMS_SUCCESSFUL)
417    sc = rtems_semaphore_create(
418      rtems_build_name('F', 'T', 'P', 'S'),
419      count,
420      RTEMS_DEFAULT_ATTRIBUTES,
421      RTEMS_NO_PRIORITY,
422      &task_pool.sem);
423
424  if(sc != RTEMS_SUCCESSFUL) {
425    task_pool_done(0);
426    syslog(LOG_ERR, "ftpd: Can not create semaphores");
427    return 0;
428  }
429
430  task_pool.info = (FTPD_SessionInfo_t*)
431    malloc(sizeof(FTPD_SessionInfo_t) * count);
432  task_pool.queue = (FTPD_SessionInfo_t**)
433    malloc(sizeof(FTPD_SessionInfo_t*) * count);
434  if (NULL == task_pool.info || NULL == task_pool.queue)
435  {
436    task_pool_done(0);
437    syslog(LOG_ERR, "ftpd: Not enough memory");
438    return 0;
439  }
440
441  for(i = 0; i < count; ++i)
442  {
443    FTPD_SessionInfo_t *info = &task_pool.info[i];
444    sc = rtems_task_create(rtems_build_name('F', 'T', 'P', id),
445      priority, FTPD_STACKSIZE,
446      RTEMS_PREEMPT | RTEMS_NO_TIMESLICE |
447      RTEMS_NO_ASR | RTEMS_INTERRUPT_LEVEL(0),
448      RTEMS_NO_FLOATING_POINT | RTEMS_LOCAL,
449      &info->tid);
450    if (sc == RTEMS_SUCCESSFUL)
451    {
452      sc = rtems_task_start(
453        info->tid, session, (rtems_task_argument)info);
454      if (sc != RTEMS_SUCCESSFUL)
455        task_pool_done(i);
456    }
457    else
458      task_pool_done(i + 1);
459    if (sc != RTEMS_SUCCESSFUL)
460    {
461      syslog(LOG_ERR, "ftpd: Could not create/start FTPD session: %s",
462        rtems_status_text(sc));
463      return 0;
464    }
465    task_pool.queue[i] = task_pool.info + i;
466    if (++id > 'z')
467      id = 'a';
468  }
469  task_pool.count = count;
470  return 1;
471}
472
473/*PAGE
474 *
475 * task_pool_obtain
476 *
477 * Obtain free task from task pool.
478 *
479 * Input parameters:
480 *   NONE
481 *
482 * Output parameters:
483 *   returns pointer to the corresponding SessionInfo structure on success,
484 *           NULL if there are no free tasks in the pool.
485 *
486 */
487static FTPD_SessionInfo_t*
488task_pool_obtain(void)
489{
490  FTPD_SessionInfo_t* info = 0;
491  rtems_status_code sc;
492  sc = rtems_semaphore_obtain(task_pool.sem, RTEMS_NO_WAIT, RTEMS_NO_TIMEOUT);
493  if (sc == RTEMS_SUCCESSFUL)
494  {
495    rtems_semaphore_obtain(task_pool.mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT);
496    info = task_pool.queue[task_pool.head];
497    if(++task_pool.head >= task_pool.count)
498      task_pool.head = 0;
499    rtems_semaphore_release(task_pool.mutex);
500  }
501  return info;
502}
503
504/*PAGE
505 *
506 * task_pool_release
507 *
508 * Return task obtained by 'obtain()' back to the task pool.
509 *
510 * Input parameters:
511 *   info  - pointer to corresponding SessionInfo structure.
512 *
513 * Output parameters:
514 *   NONE
515 *
516 */
517static void
518task_pool_release(FTPD_SessionInfo_t* info)
519{
520  rtems_semaphore_obtain(task_pool.mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT);
521  task_pool.queue[task_pool.tail] = info;
522  if(++task_pool.tail >= task_pool.count)
523    task_pool.tail = 0;
524  rtems_semaphore_release(task_pool.mutex);
525  rtems_semaphore_release(task_pool.sem);
526}
527
528/*
529 * End of task pool routines
530 */
531
532/*PAGE
533 *
534 * Function: send_reply
535 *
536 *
537 *    This procedure sends a reply to the client via the control
538 *    connection.
539 *
540 *
541 * Input parameters:
542 *   code  - 3-digit reply code.
543 *   text  - Reply text.
544 *
545 * Output parameters:
546 *   NONE
547 */
548static void
549send_reply(FTPD_SessionInfo_t  *info, int code, char *text)
550{
551  char const* s = (info->xfer_mode == TYPE_A) ? "\r" : "";
552  /* If a text reply exists, add it to the reply data. */
553  if (text != NULL)
554    fprintf(info->ctrl_fp, "%d %.70s%s\n", code, text, s);
555  else
556    fprintf(info->ctrl_fp, "%d%s\n", code, s);
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  int                 res = 0;
799
800  if(!can_read())
801  {
802    send_reply(info, 550, "Access denied.");
803    return;
804  }
805
806  if (0 > (fd = open(filename, O_RDONLY)))
807  {
808    send_reply(info, 550, "Error opening file.");
809    return;
810  }
811
812  send_mode_reply(info);
813
814  s = data_socket(info);
815
816  if (0 <= s)
817  {
818    int n = -1;
819
820    if(info->xfer_mode == TYPE_I)
821    {
822      while ((n = read(fd, buf, FTPD_DATASIZE)) > 0)
823      {
824        if(send(s, buf, n, 0) != n)
825          break;
826      }
827    }
828    else if (info->xfer_mode == TYPE_A)
829    {
830      int rest = 0;
831      while (rest == 0 && (n = read(fd, buf, FTPD_DATASIZE)) > 0)
832      {
833        char const* e = buf;
834        char const* b;
835        int i;
836        rest = n;
837        do
838        {
839          char lf = '\0';
840
841          b = e;
842          for(i = 0; i < rest; ++i, ++e)
843          {
844            if(*e == '\n')
845            {
846              lf = '\n';
847              break;
848            }
849          }
850          if(send(s, b, i, 0) != i)
851            break;
852          if(lf == '\n')
853          {
854            if(send(s, "\r\n", 2, 0) != 2)
855              break;
856            ++e;
857            ++i;
858          }
859        }
860        while((rest -= i) > 0);
861      }
862    }
863
864    if (0 == n)
865    {
866      if (0 == close(fd))
867      {
868        fd = -1;
869        res = 1;
870      }
871    }
872  }
873
874  if (-1 != fd)
875    close(fd);
876
877  if (0 == res)
878    send_reply(info, 451, "File read error.");
879  else
880    send_reply(info, 226, "Transfer complete.");
881
882  close_data_socket(info);
883
884  return;
885}
886
887
888/*PAGE
889 *
890 * discard
891 *
892 * Analog of `write' routine that just discards passed data
893 *
894 * Input parameters:
895 *   fd    - file descriptor (ignored)
896 *   buf   - data to write (ignored)
897 *   count - number of bytes in `buf'
898 *
899 * Output parameters:
900 *   returns `count'
901 *
902 */
903static ssize_t
904discard(int fd, void const* buf, size_t count)
905{
906  (void)fd;
907  (void)buf;
908  return count;
909}
910
911/*PAGE
912 *
913 * command_store
914 *
915 * Performs the "STOR" command (receive data from client).
916 *
917 * Input parameters:
918 *   info - corresponding SessionInfo structure
919 *   char *filename   - Destination filename.
920 *
921 * Output parameters:
922 *   NONE
923 */
924static void
925command_store(FTPD_SessionInfo_t *info, char const *filename)
926{
927  int                    s;
928  int                    n;
929  unsigned long          size = 0;
930  struct rtems_ftpd_hook *usehook = NULL;
931  char                   buf[FTPD_DATASIZE];
932  int                    res = 1;
933  int                    bare_lfs = 0;
934  int                    null = 0;
935  typedef ssize_t (*WriteProc)(int, void const*, size_t);
936  WriteProc              wrt = &write;
937
938  if(!can_write())
939  {
940    send_reply(info, 550, "Access denied.");
941    return;
942  }
943
944  send_mode_reply(info);
945
946  s = data_socket(info);
947  if(0 > s)
948    return;
949
950  null = !strcmp("/dev/null", filename);
951  if (null)
952  {
953    /* File "/dev/null" just throws data away.
954     *  FIXME: this is hack.  Using `/dev/null' filesystem entry would be
955     *  better.
956     */
957    wrt = &discard;
958  }
959
960  if (!null && rtems_ftpd_configuration.hooks != NULL)
961  {
962
963    /* Search our list of hooks to see if we need to do something special. */
964    struct rtems_ftpd_hook *hook;
965    int i;
966
967    i = 0;
968    hook = &rtems_ftpd_configuration.hooks[i++];
969    while (hook->filename != NULL)
970    {
971      if (!strcmp(hook->filename, filename))
972      {
973        usehook = hook;
974        break;
975      }
976      hook = &rtems_ftpd_configuration.hooks[i++];
977    }
978  }
979
980  if (usehook != NULL)
981  {
982    /*
983     * OSV: FIXME: Small buffer could be used and hook routine
984     * called multiple times instead.  Alternatively, the support could be
985     * removed entirely in favor of configuring RTEMS pseudo-device with
986     * given name.
987     */
988
989    char                *bigBufr;
990    size_t filesize = rtems_ftpd_configuration.max_hook_filesize + 1;
991
992    /*
993     * Allocate space for our "file".
994     */
995    bigBufr = (char *)malloc(filesize);
996    if (bigBufr == NULL)
997    {
998      send_reply(info, 451, "Local resource failure: malloc.");
999      close_data_socket(info);
1000      return;
1001    }
1002
1003    /*
1004     * Retrieve the file into our buffer space.
1005     */
1006    size = 0;
1007    while ((n = recv(s, bigBufr + size, filesize - size, 0)) > 0)
1008    {
1009      size += n;
1010    }
1011    if (size >= filesize)
1012    {
1013      send_reply(info, 451, "File too long: buffer size exceeded.");
1014      free(bigBufr);
1015      close_data_socket(info);
1016      return;
1017    }
1018
1019    /*
1020     * Call our hook.
1021     */
1022    res = (usehook->hook_function)(bigBufr, size) == 0;
1023    free(bigBufr);
1024    if(!res)
1025    {
1026      send_reply(info, 451, "File processing failed.");
1027      close_data_socket(info);
1028      return;
1029    }
1030  }
1031  else
1032  {
1033    /* Data transfer to regular file or /dev/null. */
1034    int fd = 0;
1035
1036    if(!null)
1037      fd = creat(filename,
1038        S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1039
1040    if (0 > fd)
1041    {
1042      send_reply(info, 550, "Error creating file.");
1043      close_data_socket(info);
1044      return;
1045    }
1046
1047    if(info->xfer_mode == TYPE_I)
1048    {
1049      while ((n = recv(s, buf, FTPD_DATASIZE, 0)) > 0)
1050      {
1051        if (wrt(fd, buf, n) != n)
1052        {
1053          res = 0;
1054          break;
1055        }
1056      }
1057    }
1058    else if(info->xfer_mode == TYPE_A)
1059    {
1060      int rest = 0;
1061      int pended_cr = 0;
1062      while (res && rest == 0 && (n = recv(s, buf, FTPD_DATASIZE, 0)) > 0)
1063      {
1064        char const* e = buf;
1065        char const* b;
1066        int i;
1067
1068        rest = n;
1069        if(pended_cr && *e != '\n')
1070        {
1071          char const lf = '\r';
1072          pended_cr = 0;
1073          if(wrt(fd, &lf, 1) != 1)
1074          {
1075            res = 0;
1076            break;
1077          }
1078        }
1079        do
1080        {
1081          int count;
1082          int sub = 0;
1083
1084          b = e;
1085          for(i = 0; i < rest; ++i, ++e)
1086          {
1087            int pcr = pended_cr;
1088            pended_cr = 0;
1089            if(*e == '\r')
1090            {
1091              pended_cr = 1;
1092            }
1093            else if(*e == '\n')
1094            {
1095              if(pcr)
1096              {
1097                sub = 2;
1098                ++i;
1099                ++e;
1100                break;
1101              }
1102              ++bare_lfs;
1103            }
1104          }
1105          if(res == 0)
1106            break;
1107          count = i - sub - pended_cr;
1108          if(count > 0 && wrt(fd, b, count) != count)
1109          {
1110            res = 0;
1111            break;
1112          }
1113          if(sub == 2 && wrt(fd, e - 1, 1) != 1)
1114            res = 0;
1115        }
1116        while((rest -= i) > 0);
1117      }
1118    }
1119
1120    if (0 > close(fd) || res == 0)
1121    {
1122      send_reply(info, 452, "Error writing file.");
1123      close_data_socket(info);
1124      return;
1125    }
1126  }
1127
1128  if (bare_lfs > 0)
1129  {
1130    snprintf(buf, FTPD_BUFSIZE,
1131      "Transfer complete. WARNING! %d bare linefeeds received in ASCII mode.",
1132      bare_lfs);
1133    send_reply(info, 226, buf);
1134  }
1135  else
1136    send_reply(info, 226, "Transfer complete.");
1137  close_data_socket(info);
1138
1139}
1140
1141
1142/*PAGE
1143 *
1144 * send_dirline
1145 *
1146 * Sends one line of LIST command reply corresponding to single file.
1147 *
1148 * Input parameters:
1149 *   s - socket descriptor to send data to
1150 *   wide - if 0, send only file name.  If not 0, send 'stat' info as well in
1151 *          "ls -l" format.
1152 *   curTime - current time
1153 *   path - path to be prepended to what is given by 'add'
1154 *   add  - path to be appended to what is given by 'path', the resulting path
1155 *          is then passed to 'stat()' routine
1156 *   name - file name to be reported in output
1157 *   buf  - buffer for temporary data
1158 *
1159 * Output parameters:
1160 *   returns 0 on failure, 1 on success
1161 *
1162 */
1163static int
1164send_dirline(int s, int wide, time_t curTime, char const* path,
1165  char const* add, char const* fname, char* buf)
1166{
1167  if(wide)
1168  {
1169    struct stat stat_buf;
1170
1171    int plen = strlen(path);
1172    int alen = strlen(add);
1173    if(plen == 0)
1174    {
1175      buf[plen++] = '/';
1176      buf[plen] = '\0';
1177    }
1178    else
1179    {
1180      strcpy(buf, path);
1181      if(alen > 0 && buf[plen - 1] != '/')
1182      {
1183        buf[plen++] = '/';
1184        if(plen >= FTPD_BUFSIZE)
1185          return 0;
1186        buf[plen] = '\0';
1187      }
1188    }
1189    if(plen + alen >= FTPD_BUFSIZE)
1190      return 0;
1191    strcpy(buf + plen, add);
1192
1193    if (stat(buf, &stat_buf) == 0)
1194    {
1195      int len;
1196      struct tm bt;
1197      time_t tf = stat_buf.st_mtime;
1198      enum { SIZE = 80 };
1199      uint32_t SIX_MONTHS = (365L*24L*60L*60L)/2L;
1200      char timeBuf[SIZE];
1201      gmtime_r(&tf, &bt);
1202      if(curTime > tf + SIX_MONTHS || tf > curTime + SIX_MONTHS)
1203        strftime (timeBuf, SIZE, "%b %d  %Y", &bt);
1204      else
1205        strftime (timeBuf, SIZE, "%b %d %H:%M", &bt);
1206
1207      len = snprintf(buf, FTPD_BUFSIZE,
1208        "%c%c%c%c%c%c%c%c%c%c  1 %5d %5d %11u %s %s\r\n",
1209        (S_ISLNK(stat_buf.st_mode)?('l'):
1210          (S_ISDIR(stat_buf.st_mode)?('d'):('-'))),
1211        (stat_buf.st_mode & S_IRUSR)?('r'):('-'),
1212        (stat_buf.st_mode & S_IWUSR)?('w'):('-'),
1213        (stat_buf.st_mode & S_IXUSR)?('x'):('-'),
1214        (stat_buf.st_mode & S_IRGRP)?('r'):('-'),
1215        (stat_buf.st_mode & S_IWGRP)?('w'):('-'),
1216        (stat_buf.st_mode & S_IXGRP)?('x'):('-'),
1217        (stat_buf.st_mode & S_IROTH)?('r'):('-'),
1218        (stat_buf.st_mode & S_IWOTH)?('w'):('-'),
1219        (stat_buf.st_mode & S_IXOTH)?('x'):('-'),
1220        (int)stat_buf.st_uid,
1221        (int)stat_buf.st_gid,
1222        (int)stat_buf.st_size,
1223        timeBuf,
1224        fname
1225      );
1226
1227      if(send(s, buf, len, 0) != len)
1228        return 0;
1229    }
1230  }
1231  else
1232  {
1233    int len = snprintf(buf, FTPD_BUFSIZE, "%s\r\n", fname);
1234    if(send(s, buf, len, 0) != len)
1235      return 0;
1236  }
1237  return 1;
1238}
1239
1240/*PAGE
1241 *
1242 * command_list
1243 *
1244 * Send file list to client.
1245 *
1246 * Input parameters:
1247 *   info - corresponding SessionInfo structure
1248 *   char *fname  - File (or directory) to list.
1249 *
1250 * Output parameters:
1251 *   NONE
1252 */
1253static void
1254command_list(FTPD_SessionInfo_t *info, char const *fname, int wide)
1255{
1256  int                 s;
1257  DIR                 *dirp = 0;
1258  struct dirent       *dp = 0;
1259  struct stat         stat_buf;
1260  char                buf[FTPD_BUFSIZE];
1261  time_t curTime;
1262  int sc = 1;
1263
1264  send_reply(info, 150, "Opening ASCII mode data connection for LIST.");
1265
1266  s = data_socket(info);
1267  if(0 > s)
1268  {
1269    syslog(LOG_ERR, "ftpd: Error connecting to data socket.");
1270    return;
1271  }
1272
1273  if(fname[0] == '\0')
1274    fname = ".";
1275
1276  if (0 > stat(fname, &stat_buf))
1277  {
1278    snprintf(buf, FTPD_BUFSIZE,
1279      "%s: No such file or directory.\r\n", fname);
1280    send(s, buf, strlen(buf), 0);
1281  }
1282  else if (S_ISDIR(stat_buf.st_mode) && (NULL == (dirp = opendir(fname))))
1283  {
1284    snprintf(buf, FTPD_BUFSIZE,
1285      "%s: Can not open directory.\r\n", fname);
1286    send(s, buf, strlen(buf), 0);
1287  }
1288  else
1289  {
1290    time(&curTime);
1291    if(!dirp && *fname)
1292      sc = sc && send_dirline(s, wide, curTime, fname, "", fname, buf);
1293    else {
1294      /* FIXME: need "." and ".." only when '-a' option is given */
1295      sc = sc && send_dirline(s, wide, curTime, fname, "", ".", buf);
1296      sc = sc && send_dirline(s, wide, curTime, fname,
1297        (strcmp(fname, ftpd_root) ? ".." : ""), "..", buf);
1298      while (sc && (dp = readdir(dirp)) != NULL)
1299        sc = sc &&
1300          send_dirline(s, wide, curTime, fname, dp->d_name, dp->d_name, buf);
1301    }
1302  }
1303
1304  if(dirp)
1305    closedir(dirp);
1306  close_data_socket(info);
1307
1308  if(sc)
1309    send_reply(info, 226, "Transfer complete.");
1310  else
1311    send_reply(info, 426, "Connection aborted.");
1312}
1313
1314
1315/*PAGE
1316 *
1317 * command_cwd
1318 *
1319 * Change current working directory.
1320 *
1321 * Input parameters:
1322 *   info - corresponding SessionInfo structure
1323 *   dir  - directory name passed in CWD command
1324 *
1325 * Output parameters:
1326 *   NONE
1327 *
1328 */
1329static void
1330command_cwd(FTPD_SessionInfo_t  *info, char *dir)
1331{
1332  if(chdir(dir) == 0)
1333    send_reply(info, 250, "CWD command successful.");
1334  else
1335    send_reply(info, 550, "CWD command failed.");
1336}
1337
1338
1339/*PAGE
1340 *
1341 * command_pwd
1342 *
1343 * Send current working directory to client.
1344 *
1345 * Input parameters:
1346 *   info - corresponding SessionInfo structure
1347 *
1348 * Output parameters:
1349 *   NONE
1350 */
1351static void
1352command_pwd(FTPD_SessionInfo_t  *info)
1353{
1354  char buf[FTPD_BUFSIZE];
1355  char const* cwd;
1356  errno = 0;
1357  buf[0] = '"';
1358  cwd = getcwd(buf + 1, FTPD_BUFSIZE - 4);
1359  if(cwd)
1360  {
1361    int len = strlen(cwd);
1362    static char const txt[] = "\" is the current directory.";
1363    int size = sizeof(txt);
1364    if(len + size + 1 >= FTPD_BUFSIZE)
1365      size = FTPD_BUFSIZE - len - 2;
1366    memcpy(buf + len + 1, txt, size);
1367    buf[len + size] = '\0';
1368    send_reply(info, 250, buf);
1369  }
1370  else {
1371    snprintf(buf, FTPD_BUFSIZE, "Error: %s.", serr());
1372    send_reply(info, 452, buf);
1373  }
1374}
1375
1376/*PAGE
1377 *
1378 * command_mdtm
1379 *
1380 * Handle FTP MDTM command (send file modification time to client)/
1381 *
1382 * Input parameters:
1383 *   info - corresponding SessionInfo structure
1384 *   fname - file name passed in MDTM command
1385 *
1386 * Output parameters:
1387 *   info->cwd is set to new CWD value.
1388 */
1389static void
1390command_mdtm(FTPD_SessionInfo_t  *info, char const* fname)
1391{
1392  struct stat stbuf;
1393  char buf[FTPD_BUFSIZE];
1394
1395  if (0 > stat(fname, &stbuf))
1396  {
1397    snprintf(buf, FTPD_BUFSIZE, "%s: %s.", fname, serr());
1398    send_reply(info, 550, buf);
1399  }
1400  else
1401  {
1402    struct tm *t = gmtime(&stbuf.st_mtime);
1403    snprintf(buf, FTPD_BUFSIZE, "%04d%02d%02d%02d%02d%02d",
1404      1900 + t->tm_year,
1405      t->tm_mon+1, t->tm_mday,
1406      t->tm_hour, t->tm_min, t->tm_sec);
1407    send_reply(info, 213, buf);
1408  }
1409}
1410
1411/*PAGE
1412 *
1413 * command_port
1414 *
1415 * This procedure fills address for data connection given the IP address and
1416 * port of the remote machine.
1417 *
1418 * Input parameters:
1419 *   info - corresponding SessionInfo structure
1420 *   args - arguments to the "PORT" command.
1421 *
1422 * Output parameters:
1423 *   info->data_addr is set according to arguments of the PORT command.
1424 *   info->use_default is set to 0 on success, 1 on failure.
1425 */
1426static void
1427command_port(FTPD_SessionInfo_t *info, char const *args)
1428{
1429  enum { NUM_FIELDS = 6 };
1430  unsigned int a[NUM_FIELDS];
1431  int n;
1432
1433  close_data_socket(info);
1434
1435  n = sscanf(args, "%u,%u,%u,%u,%u,%u", a+0, a+1, a+2, a+3, a+4, a+5);
1436  if(NUM_FIELDS == n)
1437  {
1438    int i;
1439    union {
1440      uint8_t b[NUM_FIELDS];
1441      struct {
1442        uint32_t ip;
1443        uint16_t port;
1444      };
1445    } ip_info;
1446
1447    for(i = 0; i < NUM_FIELDS; ++i)
1448    {
1449      if(a[i] > 255)
1450        break;
1451      ip_info.b[i] = (uint8_t)a[i];
1452    }
1453
1454    if(i == NUM_FIELDS)
1455    {
1456      /* Note: while it contradicts with RFC959, we don't allow PORT command
1457       * to specify IP address different than those of the originating client
1458       * for the sake of safety. */
1459      if (ip_info.ip == info->def_addr.sin_addr.s_addr)
1460      {
1461        info->data_addr.sin_addr.s_addr = ip_info.ip;
1462        info->data_addr.sin_port        = ip_info.port;
1463        info->data_addr.sin_family      = AF_INET;
1464        memset(info->data_addr.sin_zero, 0, sizeof(info->data_addr.sin_zero));
1465
1466        info->use_default = 0;
1467        send_reply(info, 200, "PORT command successful.");
1468        return; /* success */
1469      }
1470      else
1471      {
1472        send_reply(info, 425, "Address doesn't match peer's IP.");
1473        return;
1474      }
1475    }
1476  }
1477  send_reply(info, 501, "Syntax error.");
1478}
1479
1480
1481/*PAGE
1482 *
1483 * command_pasv
1484 *
1485 * Handle FTP PASV command.
1486 * Open socket, listen for and accept connection on it.
1487 *
1488 * Input parameters:
1489 *   info - corresponding SessionInfo structure
1490 *
1491 * Output parameters:
1492 *   info->pasv_socket is set to the descriptor of the data socket
1493 */
1494static void
1495command_pasv(FTPD_SessionInfo_t *info)
1496{
1497  int s = -1;
1498  int err = 1;
1499
1500  close_data_socket(info);
1501
1502  s = socket(PF_INET, SOCK_STREAM, 0);
1503  if (s < 0)
1504    syslog(LOG_ERR, "ftpd: Error creating PASV socket: %s", serr());
1505  else
1506  {
1507    struct sockaddr_in addr;
1508    socklen_t addrLen = sizeof(addr);
1509
1510    addr = info->ctrl_addr;
1511    addr.sin_port = htons(0);
1512
1513    if (0 > bind(s, (struct sockaddr *)&addr, addrLen))
1514      syslog(LOG_ERR, "ftpd: Error binding PASV socket: %s", serr());
1515    else if (0 > listen(s, 1))
1516      syslog(LOG_ERR, "ftpd: Error listening on PASV socket: %s", serr());
1517    else if(set_socket_timeout(s, info->idle))
1518    {
1519      char buf[FTPD_BUFSIZE];
1520      unsigned char const *ip, *p;
1521
1522      getsockname(s, (struct sockaddr *)&addr, &addrLen);
1523      ip = (unsigned char const*)&(addr.sin_addr);
1524      p  = (unsigned char const*)&(addr.sin_port);
1525      snprintf(buf, FTPD_BUFSIZE, "Entering passive mode (%u,%u,%u,%u,%u,%u).",
1526        ip[0], ip[1], ip[2], ip[3], p[0], p[1]);
1527      send_reply(info, 227, buf);
1528
1529      info->pasv_socket = accept(s, (struct sockaddr *)&addr, &addrLen);
1530      if (0 > info->pasv_socket)
1531        syslog(LOG_ERR, "ftpd: Error accepting PASV connection: %s", serr());
1532      else
1533      {
1534        close_socket(s);
1535        s = -1;
1536        err = 0;
1537      }
1538    }
1539  }
1540  if(err)
1541  {
1542    /* (OSV) The note is from FreeBSD FTPD.
1543     * Note: a response of 425 is not mentioned as a possible response to
1544     * the PASV command in RFC959.  However, it has been blessed as a
1545     * legitimate response by Jon Postel in a telephone conversation
1546     * with Rick Adams on 25 Jan 89. */
1547    send_reply(info, 425, "Can't open passive connection.");
1548    close_socket(s);
1549  }
1550}
1551
1552
1553/*PAGE
1554 *
1555 * skip_options
1556 *
1557 * Utility routine to skip options (if any) from input command.
1558 *
1559 * Input parameters:
1560 *   p  - pointer to pointer to command
1561 *
1562 * Output parameters:
1563 *   p  - is changed to point to first non-option argument
1564 */
1565static void
1566skip_options(char **p)
1567{
1568  char* buf = *p;
1569  char* last = NULL;
1570  while(1) {
1571    while(isspace(*buf))
1572      ++buf;
1573    if(*buf == '-') {
1574      if(*++buf == '-') { /* `--' should terminate options */
1575        if(isspace(*++buf)) {
1576          last = buf;
1577          do ++buf;
1578          while(isspace(*buf));
1579          break;
1580        }
1581      }
1582      while(*buf && !isspace(*buf))
1583        ++buf;
1584      last = buf;
1585    }
1586    else
1587      break;
1588  }
1589  if(last)
1590    *last = '\0';
1591  *p = buf;
1592}
1593
1594/*PAGE
1595 *
1596 * split_command
1597 *
1598 * Split command into command itself, options, and arguments. Command itself
1599 * is converted to upper case.
1600 *
1601 * Input parameters:
1602 *   buf - initial command string
1603 *
1604 * Output parameter:
1605 *   buf  - is modified by inserting '\0' at ends of split entities
1606 *   cmd  - upper-cased command code
1607 *   opts - string containing all the options
1608 *   args - string containing all the arguments
1609 */
1610void
1611split_command(char *buf, char **cmd, char **opts, char **args)
1612{
1613  char* eoc;
1614  char* p = buf;
1615  while(isspace(*p))
1616    ++p;
1617  *cmd = p;
1618  while(*p && !isspace(*p))
1619  {
1620    *p = toupper(*p);
1621    ++p;
1622  }
1623  eoc = p;
1624  if(*p)
1625    *p++ = '\0';
1626  while(isspace(*p))
1627    ++p;
1628  *opts = p;
1629  skip_options(&p);
1630  *args = p;
1631  if(*opts == p)
1632    *opts = eoc;
1633  while(*p && *p != '\r' && *p != '\n')
1634    ++p;
1635  if(*p)
1636    *p++ = '\0';
1637}
1638
1639/*PAGE
1640 *
1641 * exec_command
1642 *
1643 * Parse and execute FTP command.
1644 *
1645 * FIXME: This section is somewhat of a hack.  We should have a better
1646 *        way to parse commands.
1647 *
1648 * Input parameters:
1649 *   info - corresponding SessionInfo structure
1650 *   cmd  - command to be executed (upper-case)
1651 *   args - arguments of the command
1652 *
1653 * Output parameters:
1654 *    NONE
1655 */
1656static void
1657exec_command(FTPD_SessionInfo_t *info, char* cmd, char* args)
1658{
1659  char fname[FTPD_BUFSIZE];
1660  int wrong_command = 0;
1661
1662  fname[0] = '\0';
1663
1664  if (!strcmp("PORT", cmd))
1665  {
1666    command_port(info, args);
1667  }
1668  else if (!strcmp("PASV", cmd))
1669  {
1670    command_pasv(info);
1671  }
1672  else if (!strcmp("RETR", cmd))
1673  {
1674    sscanf(args, "%254s", fname);
1675    command_retrieve(info, fname);
1676  }
1677  else if (!strcmp("STOR", cmd))
1678  {
1679    sscanf(args, "%254s", fname);
1680    command_store(info, fname);
1681  }
1682  else if (!strcmp("LIST", cmd))
1683  {
1684    sscanf(args, "%254s", fname);
1685    command_list(info, fname, 1);
1686  }
1687  else if (!strcmp("NLST", cmd))
1688  {
1689    sscanf(args, "%254s", fname);
1690    command_list(info, fname, 0);
1691  }
1692  else if (!strcmp("MDTM", cmd))
1693  {
1694    sscanf(args, "%254s", fname);
1695    command_mdtm(info, fname);
1696  }
1697  else if (!strcmp("SYST", cmd))
1698  {
1699    send_reply(info, 215, FTPD_SYSTYPE);
1700  }
1701  else if (!strcmp("TYPE", cmd))
1702  {
1703    if (args[0] == 'I')
1704    {
1705      info->xfer_mode = TYPE_I;
1706      send_reply(info, 200, "Type set to I.");
1707    }
1708    else if (args[0] == 'A')
1709    {
1710      info->xfer_mode = TYPE_A;
1711      send_reply(info, 200, "Type set to A.");
1712    }
1713    else
1714    {
1715      info->xfer_mode = TYPE_I;
1716      send_reply(info, 504, "Type not implemented.  Set to I.");
1717    }
1718  }
1719  else if (!strcmp("USER", cmd) || !strcmp("PASS", cmd))
1720  {
1721    send_reply(info, 230, "User logged in.");
1722  }
1723  else if (!strcmp("DELE", cmd))
1724  {
1725    if(!can_write())
1726    {
1727      send_reply(info, 550, "Access denied.");
1728    }
1729    else if (
1730      1 == sscanf(args, "%254s", fname) &&
1731      unlink(fname) == 0)
1732    {
1733      send_reply(info, 257, "DELE successful.");
1734    }
1735    else
1736    {
1737      send_reply(info, 550, "DELE failed.");
1738    }
1739  }
1740  else if (!strcmp("SITE", cmd))
1741  {
1742    char* opts;
1743    split_command(args, &cmd, &opts, &args);
1744    if(!strcmp("CHMOD", cmd))
1745    {
1746      int mask;
1747
1748      if(!can_write())
1749      {
1750        send_reply(info, 550, "Access denied.");
1751      }
1752      else if(
1753        2 == sscanf(args, "%o %254s", &mask, fname) &&
1754        chmod(fname, (mode_t)mask) == 0)
1755      {
1756        send_reply(info, 257, "CHMOD successful.");
1757      }
1758      else
1759      {
1760        send_reply(info, 550, "CHMOD failed.");
1761      }
1762    }
1763    else
1764      wrong_command = 1;
1765  }
1766  else if (!strcmp("RMD", cmd))
1767  {
1768    if(!can_write())
1769    {
1770      send_reply(info, 550, "Access denied.");
1771    }
1772    else if (
1773      1 == sscanf(args, "%254s", fname) &&
1774      rmdir(fname) == 0)
1775    {
1776      send_reply(info, 257, "RMD successful.");
1777    }
1778    else
1779    {
1780      send_reply(info, 550, "RMD failed.");
1781    }
1782  }
1783  else if (!strcmp("MKD", cmd))
1784  {
1785    if(!can_write())
1786    {
1787      send_reply(info, 550, "Access denied.");
1788    }
1789    else if (
1790      1 == sscanf(args, "%254s", fname) &&
1791      mkdir(fname, S_IRWXU | S_IRWXG | S_IRWXO) == 0)
1792    {
1793      send_reply(info, 257, "MKD successful.");
1794    }
1795    else
1796    {
1797      send_reply(info, 550, "MKD failed.");
1798    }
1799  }
1800  else if (!strcmp("CWD", cmd))
1801  {
1802    sscanf(args, "%254s", fname);
1803    command_cwd(info, fname);
1804  }
1805  else if (!strcmp("CDUP", cmd))
1806  {
1807    command_cwd(info, "..");
1808  }
1809  else if (!strcmp("PWD", cmd))
1810  {
1811    command_pwd(info);
1812  }
1813  else
1814    wrong_command = 1;
1815
1816  if(wrong_command)
1817    send_reply(info, 500, "Command not understood.");
1818}
1819
1820
1821/*PAGE
1822 *
1823 * session
1824 *
1825 * This task handles single session.  It is waked up when the FTP daemon gets a
1826 * service request from a remote machine.  Here, we watch for commands that
1827 * will come through the control connection.  These commands are then parsed
1828 * and executed until the connection is closed, either unintentionally or
1829 * intentionally with the "QUIT" command.
1830 *
1831 * Input parameters:
1832 *   arg - pointer to corresponding SessionInfo.
1833 *
1834 * Output parameters:
1835 *   NONE
1836 */
1837static void
1838session(rtems_task_argument arg)
1839{
1840  FTPD_SessionInfo_t  *const info = (FTPD_SessionInfo_t  *)arg;
1841  int chroot_made = 0;
1842
1843  rtems_libio_set_private_env();
1844
1845  /* chroot() can fail here because the directory may not exist yet. */
1846  chroot_made = chroot(ftpd_root) == 0;
1847
1848  while(1)
1849  {
1850    rtems_event_set set;
1851
1852    rtems_event_receive(FTPD_RTEMS_EVENT, RTEMS_EVENT_ANY, RTEMS_NO_TIMEOUT,
1853      &set);
1854
1855    chroot_made = chroot_made || chroot(ftpd_root) == 0;
1856    chdir("/");
1857
1858    errno = 0;
1859
1860    send_reply(info, 220, FTPD_SERVER_MESSAGE);
1861
1862    while (1)
1863    {
1864      char buf[FTPD_BUFSIZE];
1865      char *cmd, *opts, *args;
1866
1867      if (fgets(buf, FTPD_BUFSIZE, info->ctrl_fp) == NULL)
1868      {
1869        syslog(LOG_INFO, "ftpd: Connection aborted.");
1870        break;
1871      }
1872
1873      split_command(buf, &cmd, &opts, &args);
1874
1875      if (!strcmp("QUIT", cmd))
1876      {
1877        send_reply(info, 221, "Goodbye.");
1878        break;
1879      }
1880      else
1881      {
1882        exec_command(info, cmd, args);
1883      }
1884    }
1885
1886    /* Close connection and put ourselves back into the task pool. */
1887    close_data_socket(info);
1888    close_stream(info);
1889    task_pool_release(info);
1890  }
1891}
1892
1893
1894/*PAGE
1895 *
1896 * daemon
1897 *
1898 * This task runs forever.  It waits for service requests on the FTP port
1899 * (port 21 by default).  When a request is received, it opens a new session
1900 * to handle those requests until the connection is closed.
1901 *
1902 * Input parameters:
1903 *   NONE
1904 *
1905 * Output parameters:
1906 *   NONE
1907 */
1908static void
1909daemon(void)
1910{
1911  int                 s;
1912  socklen_t           addrLen;
1913  struct sockaddr_in  addr;
1914  FTPD_SessionInfo_t  *info = NULL;
1915
1916
1917  s = socket(PF_INET, SOCK_STREAM, 0);
1918  if (s < 0)
1919    syslog(LOG_ERR, "ftpd: Error creating socket: %s", serr());
1920
1921  addr.sin_family      = AF_INET;
1922  addr.sin_port        = htons(rtems_ftpd_configuration.port);
1923  addr.sin_addr.s_addr = htonl(INADDR_ANY);
1924  memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
1925
1926  if (0 > bind(s, (struct sockaddr *)&addr, sizeof(addr)))
1927    syslog(LOG_ERR, "ftpd: Error binding control socket: %s", serr());
1928  else if (0 > listen(s, 1))
1929    syslog(LOG_ERR, "ftpd: Error listening on control socket: %s", serr());
1930  else while (1)
1931  {
1932    int ss;
1933    addrLen = sizeof(addr);
1934    ss = accept(s, (struct sockaddr *)&addr, &addrLen);
1935    if (0 > ss)
1936      syslog(LOG_ERR, "ftpd: Error accepting control connection: %s", serr());
1937    else if(!set_socket_timeout(ss, ftpd_timeout))
1938      close_socket(ss);
1939    else
1940    {
1941      info = task_pool_obtain();
1942      if (NULL == info)
1943      {
1944        close_socket(ss);
1945      }
1946      else
1947      {
1948        info->ctrl_socket = ss;
1949        if ((info->ctrl_fp = fdopen(info->ctrl_socket, "r+")) == NULL)
1950        {
1951          syslog(LOG_ERR, "ftpd: fdopen() on socket failed: %s", serr());
1952          close_stream(info);
1953          task_pool_release(info);
1954        }
1955        else
1956        {
1957          /* Initialize corresponding SessionInfo structure */
1958          info->def_addr = addr;
1959          if(0 > getsockname(ss, (struct sockaddr *)&addr, &addrLen))
1960          {
1961            syslog(LOG_ERR, "ftpd: getsockname(): %s", serr());
1962            close_stream(info);
1963            task_pool_release(info);
1964          }
1965          else
1966          {
1967            info->use_default = 1;
1968            info->ctrl_addr  = addr;
1969            info->pasv_socket = -1;
1970            info->data_socket = -1;
1971            info->xfer_mode   = TYPE_A;
1972            info->data_addr.sin_port =
1973              htons(ntohs(info->ctrl_addr.sin_port) - 1);
1974            info->idle = ftpd_timeout;
1975            /* Wakeup the session task.  The task will call task_pool_release
1976               after it closes connection. */
1977            rtems_event_send(info->tid, FTPD_RTEMS_EVENT);
1978          }
1979        }
1980      }
1981    }
1982  }
1983  rtems_task_delete(RTEMS_SELF);
1984}
1985
1986
1987/*PAGE
1988 *
1989 * rtems_ftpd_start
1990 *
1991 * Here, we start the FTPD task which waits for FTP requests and services
1992 * them.  This procedure returns to its caller once the task is started.
1993 *
1994 *
1995 * Input parameters:
1996 *
1997 * Output parameters:
1998 *    returns RTEMS_SUCCESSFUL on successful start of the daemon.
1999 */
2000int
2001rtems_initialize_ftpd()
2002{
2003  rtems_status_code   sc;
2004  rtems_id            tid;
2005  rtems_task_priority priority;
2006  int count;
2007
2008  if (rtems_ftpd_configuration.port == 0)
2009  {
2010    rtems_ftpd_configuration.port = FTPD_CONTROL_PORT;
2011  }
2012
2013  if (rtems_ftpd_configuration.priority == 0)
2014  {
2015    rtems_ftpd_configuration.priority = 40;
2016  }
2017  priority = rtems_ftpd_configuration.priority;
2018
2019  ftpd_timeout = rtems_ftpd_configuration.idle;
2020  if (ftpd_timeout < 0)
2021    ftpd_timeout = 0;
2022  rtems_ftpd_configuration.idle = ftpd_timeout;
2023
2024  ftpd_access = rtems_ftpd_configuration.access;
2025
2026  if (rtems_ftpd_configuration.tasks_count <= 0)
2027    rtems_ftpd_configuration.tasks_count = 1;
2028  count = rtems_ftpd_configuration.tasks_count;
2029
2030  if (!task_pool_init(count, priority))
2031  {
2032    syslog(LOG_ERR, "ftpd: Could not initialize task pool.");
2033    return RTEMS_UNSATISFIED;
2034  }
2035
2036  sc = rtems_task_create(rtems_build_name('F', 'T', 'P', 'D'),
2037    priority, FTPD_STACKSIZE,
2038    RTEMS_PREEMPT | RTEMS_NO_TIMESLICE | RTEMS_NO_ASR |
2039    RTEMS_INTERRUPT_LEVEL(0),
2040    RTEMS_NO_FLOATING_POINT | RTEMS_LOCAL,
2041    &tid);
2042
2043  if (sc == RTEMS_SUCCESSFUL)
2044  {
2045    sc = rtems_task_start(tid, daemon, 0);
2046    if (sc != RTEMS_SUCCESSFUL)
2047      rtems_task_delete(tid);
2048  }
2049
2050  if (sc != RTEMS_SUCCESSFUL)
2051  {
2052    task_pool_done(count);
2053    syslog(LOG_ERR, "ftpd: Could not create/start FTP daemon: %s",
2054      rtems_status_text(sc));
2055    return RTEMS_UNSATISFIED;
2056  }
2057
2058  ftpd_root = "/";
2059  if (
2060    rtems_ftpd_configuration.root &&
2061    rtems_ftpd_configuration.root[0] == '/'
2062  )
2063    ftpd_root = rtems_ftpd_configuration.root;
2064
2065  rtems_ftpd_configuration.root = ftpd_root;
2066
2067  syslog(LOG_INFO, "ftpd: FTP daemon started (%d session%s max)",
2068    count, ((count > 1) ? "s" : ""));
2069
2070  return RTEMS_SUCCESSFUL;
2071}
Note: See TracBrowser for help on using the repository browser.