source: rtems/cpukit/ftpd/ftpd.c @ 38371dbe

4.104.114.84.95
Last change on this file since 38371dbe was 38371dbe, checked in by Joel Sherrill <joel.sherrill@…>, on Jan 24, 2001 at 7:20:24 PM

2001-01-24 Sergei Organov <osv@…>

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