source: rtems/cpukit/ftpd/ftpd.c @ 87da638

4.104.114.84.95
Last change on this file since 87da638 was 2129ac8, checked in by Ralf Corsepius <ralf.corsepius@…>, on 08/14/02 at 10:44:49

2002-08-14 Ralf Corsepius <corsepiu@…>

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