OpenCores
URL https://opencores.org/ocsvn/or1k/or1k/trunk

Subversion Repositories or1k

[/] [or1k/] [trunk/] [rtems-20020807/] [c/] [src/] [libnetworking/] [rtems_servers/] [ftpd.c] - Blame information for rev 1765

Details | Compare with Previous | View Log

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

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.