source: rtems/cpukit/ftpd/ftpd.c @ f004b2b8

Last change on this file since f004b2b8 was f004b2b8, checked in by Sebastian Huber <sebastian.huber@…>, on Oct 2, 2018 at 8:22:15 AM

Use rtems_task_exit()

Update #3530.
Update #3533.

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