source: rtems/cpukit/ftpd/ftpd.c @ 32b5b23

5
Last change on this file since 32b5b23 was 32b5b23, checked in by Sebastian Huber <sebastian.huber@…>, on 04/30/18 at 08:12:37

ftpd: Use floating-point tasks due to syslog()

Update #3419.

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