source: rtems/cpukit/ftpd/ftpd.c

Last change on this file was d4b3698, checked in by Sebastian Huber <sebastian.huber@…>, on 12/19/23 at 07:03:12

ftpd: Fix set but not used warning

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