source: rtems-libbsd/services/ftpd/ftpd.c @ f415cbb

4.1155-freebsd-126-freebsd-12freebsd-9.3
Last change on this file since f415cbb was f415cbb, checked in by Joel Sherrill <joel.sherrill@…>, on 07/28/12 at 12:09:54

ftpd: Added from old stack

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