source: rtems-libbsd/rtemsbsd/ftpd/ftpd.c @ 983e452

4.115-freebsd-12freebsd-9.3
Last change on this file since 983e452 was 983e452, checked in by Sebastian Huber <sebastian.huber@…>, on Dec 20, 2013 at 10:04:46 AM

Use integer-only printf for ftpd

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