source: branches/3.2/mindi-busybox/networking/ftpd.c @ 3232

Last change on this file since 3232 was 3232, checked in by bruno, 6 years ago
  • Update mindi-busybox to 1.21.1
  • Property svn:eol-style set to native
File size: 34.4 KB
Line 
1/* vi: set sw=4 ts=4: */
2/*
3 * Simple FTP daemon, based on vsftpd 2.0.7 (written by Chris Evans)
4 *
5 * Author: Adam Tkac <vonsch@gmail.com>
6 *
7 * Licensed under GPLv2, see file LICENSE in this source tree.
8 *
9 * Only subset of FTP protocol is implemented but vast majority of clients
10 * should not have any problem.
11 *
12 * You have to run this daemon via inetd.
13 */
14
15//usage:#define ftpd_trivial_usage
16//usage:       "[-wvS] [-t N] [-T N] [DIR]"
17//usage:#define ftpd_full_usage "\n\n"
18//usage:       "Anonymous FTP server\n"
19//usage:       "\n"
20//usage:       "ftpd should be used as an inetd service.\n"
21//usage:       "ftpd's line for inetd.conf:\n"
22//usage:       "    21 stream tcp nowait root ftpd ftpd /files/to/serve\n"
23//usage:       "It also can be ran from tcpsvd:\n"
24//usage:       "    tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve\n"
25//usage:     "\n    -w  Allow upload"
26//usage:     "\n    -v  Log errors to stderr. -vv: verbose log"
27//usage:     "\n    -S  Log errors to syslog. -SS: verbose log"
28//usage:     "\n    -t,-T   Idle and absolute timeouts"
29//usage:     "\n    DIR Change root to this directory"
30
31#include "libbb.h"
32#include <syslog.h>
33#include <netinet/tcp.h>
34
35#define FTP_DATACONN            150
36#define FTP_NOOPOK              200
37#define FTP_TYPEOK              200
38#define FTP_PORTOK              200
39#define FTP_STRUOK              200
40#define FTP_MODEOK              200
41#define FTP_ALLOOK              202
42#define FTP_STATOK              211
43#define FTP_STATFILE_OK         213
44#define FTP_HELP                214
45#define FTP_SYSTOK              215
46#define FTP_GREET               220
47#define FTP_GOODBYE             221
48#define FTP_TRANSFEROK          226
49#define FTP_PASVOK              227
50/*#define FTP_EPRTOK              228*/
51#define FTP_EPSVOK              229
52#define FTP_LOGINOK             230
53#define FTP_CWDOK               250
54#define FTP_RMDIROK             250
55#define FTP_DELEOK              250
56#define FTP_RENAMEOK            250
57#define FTP_PWDOK               257
58#define FTP_MKDIROK             257
59#define FTP_GIVEPWORD           331
60#define FTP_RESTOK              350
61#define FTP_RNFROK              350
62#define FTP_TIMEOUT             421
63#define FTP_BADSENDCONN         425
64#define FTP_BADSENDNET          426
65#define FTP_BADSENDFILE         451
66#define FTP_BADCMD              500
67#define FTP_COMMANDNOTIMPL      502
68#define FTP_NEEDUSER            503
69#define FTP_NEEDRNFR            503
70#define FTP_BADSTRU             504
71#define FTP_BADMODE             504
72#define FTP_LOGINERR            530
73#define FTP_FILEFAIL            550
74#define FTP_NOPERM              550
75#define FTP_UPLOADFAIL          553
76
77#define STR1(s) #s
78#define STR(s) STR1(s)
79
80/* Convert a constant to 3-digit string, packed into uint32_t */
81enum {
82    /* Shift for Nth decimal digit */
83    SHIFT2  =  0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
84    SHIFT1  =  8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
85    SHIFT0  = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
86    /* And for 4th position (space) */
87    SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
88};
89#define STRNUM32(s) (uint32_t)(0 \
90    | (('0' + ((s) / 1 % 10)) << SHIFT0) \
91    | (('0' + ((s) / 10 % 10)) << SHIFT1) \
92    | (('0' + ((s) / 100 % 10)) << SHIFT2) \
93)
94#define STRNUM32sp(s) (uint32_t)(0 \
95    | (' ' << SHIFTsp) \
96    | (('0' + ((s) / 1 % 10)) << SHIFT0) \
97    | (('0' + ((s) / 10 % 10)) << SHIFT1) \
98    | (('0' + ((s) / 100 % 10)) << SHIFT2) \
99)
100
101#define MSG_OK "Operation successful\r\n"
102#define MSG_ERR "Error\r\n"
103
104struct globals {
105    int pasv_listen_fd;
106#if !BB_MMU
107    int root_fd;
108#endif
109    int local_file_fd;
110    unsigned end_time;
111    unsigned timeout;
112    unsigned verbose;
113    off_t local_file_pos;
114    off_t restart_pos;
115    len_and_sockaddr *local_addr;
116    len_and_sockaddr *port_addr;
117    char *ftp_cmd;
118    char *ftp_arg;
119#if ENABLE_FEATURE_FTP_WRITE
120    char *rnfr_filename;
121#endif
122    /* We need these aligned to uint32_t */
123    char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
124    char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
125} FIX_ALIASING;
126#define G (*(struct globals*)&bb_common_bufsiz1)
127#define INIT_G() do { \
128    /* Moved to main */ \
129    /*strcpy(G.msg_ok  + 4, MSG_OK );*/ \
130    /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
131} while (0)
132
133
134static char *
135escape_text(const char *prepend, const char *str, unsigned escapee)
136{
137    unsigned retlen, remainlen, chunklen;
138    char *ret, *found;
139    char append;
140
141    append = (char)escapee;
142    escapee >>= 8;
143
144    remainlen = strlen(str);
145    retlen = strlen(prepend);
146    ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
147    strcpy(ret, prepend);
148
149    for (;;) {
150        found = strchrnul(str, escapee);
151        chunklen = found - str + 1;
152
153        /* Copy chunk up to and including escapee (or NUL) to ret */
154        memcpy(ret + retlen, str, chunklen);
155        retlen += chunklen;
156
157        if (*found == '\0') {
158            /* It wasn't escapee, it was NUL! */
159            ret[retlen - 1] = append; /* replace NUL */
160            ret[retlen] = '\0'; /* add NUL */
161            break;
162        }
163        ret[retlen++] = escapee; /* duplicate escapee */
164        str = found + 1;
165    }
166    return ret;
167}
168
169/* Returns strlen as a bonus */
170static unsigned
171replace_char(char *str, char from, char to)
172{
173    char *p = str;
174    while (*p) {
175        if (*p == from)
176            *p = to;
177        p++;
178    }
179    return p - str;
180}
181
182static void
183verbose_log(const char *str)
184{
185    bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
186}
187
188/* NB: status_str is char[4] packed into uint32_t */
189static void
190cmdio_write(uint32_t status_str, const char *str)
191{
192    char *response;
193    int len;
194
195    /* FTP uses telnet protocol for command link.
196     * In telnet, 0xff is an escape char, and needs to be escaped: */
197    response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
198
199    /* FTP sends embedded LFs as NULs */
200    len = replace_char(response, '\n', '\0');
201
202    response[len++] = '\n'; /* tack on trailing '\n' */
203    xwrite(STDOUT_FILENO, response, len);
204    if (G.verbose > 1)
205        verbose_log(response);
206    free(response);
207}
208
209static void
210cmdio_write_ok(unsigned status)
211{
212    *(uint32_t *) G.msg_ok = status;
213    xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
214    if (G.verbose > 1)
215        verbose_log(G.msg_ok);
216}
217#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
218
219/* TODO: output strerr(errno) if errno != 0? */
220static void
221cmdio_write_error(unsigned status)
222{
223    *(uint32_t *) G.msg_err = status;
224    xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
225    if (G.verbose > 0)
226        verbose_log(G.msg_err);
227}
228#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
229
230static void
231cmdio_write_raw(const char *p_text)
232{
233    xwrite_str(STDOUT_FILENO, p_text);
234    if (G.verbose > 1)
235        verbose_log(p_text);
236}
237
238static void
239timeout_handler(int sig UNUSED_PARAM)
240{
241    off_t pos;
242    int sv_errno = errno;
243
244    if ((int)(monotonic_sec() - G.end_time) >= 0)
245        goto timed_out;
246
247    if (!G.local_file_fd)
248        goto timed_out;
249
250    pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
251    if (pos == G.local_file_pos)
252        goto timed_out;
253    G.local_file_pos = pos;
254
255    alarm(G.timeout);
256    errno = sv_errno;
257    return;
258
259 timed_out:
260    cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
261/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
262    exit(1);
263}
264
265/* Simple commands */
266
267static void
268handle_pwd(void)
269{
270    char *cwd, *response;
271
272    cwd = xrealloc_getcwd_or_warn(NULL);
273    if (cwd == NULL)
274        cwd = xstrdup("");
275
276    /* We have to promote each " to "" */
277    response = escape_text(" \"", cwd, ('"' << 8) + '"');
278    free(cwd);
279    cmdio_write(STRNUM32(FTP_PWDOK), response);
280    free(response);
281}
282
283static void
284handle_cwd(void)
285{
286    if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
287        WRITE_ERR(FTP_FILEFAIL);
288        return;
289    }
290    WRITE_OK(FTP_CWDOK);
291}
292
293static void
294handle_cdup(void)
295{
296    G.ftp_arg = (char*)"..";
297    handle_cwd();
298}
299
300static void
301handle_stat(void)
302{
303    cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
304            " TYPE: BINARY\r\n"
305            STR(FTP_STATOK)" Ok\r\n");
306}
307
308/* Examples of HELP and FEAT:
309# nc -vvv ftp.kernel.org 21
310ftp.kernel.org (130.239.17.4:21) open
311220 Welcome to ftp.kernel.org.
312FEAT
313211-Features:
314 EPRT
315 EPSV
316 MDTM
317 PASV
318 REST STREAM
319 SIZE
320 TVFS
321 UTF8
322211 End
323HELP
324214-The following commands are recognized.
325 ABOR ACCT ALLO APPE CDUP CWD  DELE EPRT EPSV FEAT HELP LIST MDTM MKD
326 MODE NLST NOOP OPTS PASS PASV PORT PWD  QUIT REIN REST RETR RMD  RNFR
327 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
328 XPWD XRMD
329214 Help OK.
330*/
331static void
332handle_feat(unsigned status)
333{
334    cmdio_write(status, "-Features:");
335    cmdio_write_raw(" EPSV\r\n"
336            " PASV\r\n"
337            " REST STREAM\r\n"
338            " MDTM\r\n"
339            " SIZE\r\n");
340    cmdio_write(status, " Ok");
341}
342
343/* Download commands */
344
345static inline int
346port_active(void)
347{
348    return (G.port_addr != NULL);
349}
350
351static inline int
352pasv_active(void)
353{
354    return (G.pasv_listen_fd > STDOUT_FILENO);
355}
356
357static void
358port_pasv_cleanup(void)
359{
360    free(G.port_addr);
361    G.port_addr = NULL;
362    if (G.pasv_listen_fd > STDOUT_FILENO)
363        close(G.pasv_listen_fd);
364    G.pasv_listen_fd = -1;
365}
366
367/* On error, emits error code to the peer */
368static int
369ftpdataio_get_pasv_fd(void)
370{
371    int remote_fd;
372
373    remote_fd = accept(G.pasv_listen_fd, NULL, 0);
374
375    if (remote_fd < 0) {
376        WRITE_ERR(FTP_BADSENDCONN);
377        return remote_fd;
378    }
379
380    setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
381    return remote_fd;
382}
383
384/* Clears port/pasv data.
385 * This means we dont waste resources, for example, keeping
386 * PASV listening socket open when it is no longer needed.
387 * On error, emits error code to the peer (or exits).
388 * On success, emits p_status_msg to the peer.
389 */
390static int
391get_remote_transfer_fd(const char *p_status_msg)
392{
393    int remote_fd;
394
395    if (pasv_active())
396        /* On error, emits error code to the peer */
397        remote_fd = ftpdataio_get_pasv_fd();
398    else
399        /* Exits on error */
400        remote_fd = xconnect_stream(G.port_addr);
401
402    port_pasv_cleanup();
403
404    if (remote_fd < 0)
405        return remote_fd;
406
407    cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
408    return remote_fd;
409}
410
411/* If there were neither PASV nor PORT, emits error code to the peer */
412static int
413port_or_pasv_was_seen(void)
414{
415    if (!pasv_active() && !port_active()) {
416        cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
417        return 0;
418    }
419
420    return 1;
421}
422
423/* Exits on error */
424static unsigned
425bind_for_passive_mode(void)
426{
427    int fd;
428    unsigned port;
429
430    port_pasv_cleanup();
431
432    G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
433    setsockopt_reuseaddr(fd);
434
435    set_nport(&G.local_addr->u.sa, 0);
436    xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
437    xlisten(fd, 1);
438    getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
439
440    port = get_nport(&G.local_addr->u.sa);
441    port = ntohs(port);
442    return port;
443}
444
445/* Exits on error */
446static void
447handle_pasv(void)
448{
449    unsigned port;
450    char *addr, *response;
451
452    port = bind_for_passive_mode();
453
454    if (G.local_addr->u.sa.sa_family == AF_INET)
455        addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
456    else /* seen this in the wild done by other ftp servers: */
457        addr = xstrdup("0.0.0.0");
458    replace_char(addr, '.', ',');
459
460    response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
461            addr, (int)(port >> 8), (int)(port & 255));
462    free(addr);
463    cmdio_write_raw(response);
464    free(response);
465}
466
467/* Exits on error */
468static void
469handle_epsv(void)
470{
471    unsigned port;
472    char *response;
473
474    port = bind_for_passive_mode();
475    response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
476    cmdio_write_raw(response);
477    free(response);
478}
479
480static void
481handle_port(void)
482{
483    unsigned port, port_hi;
484    char *raw, *comma;
485#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
486    socklen_t peer_ipv4_len;
487    struct sockaddr_in peer_ipv4;
488    struct in_addr port_ipv4_sin_addr;
489#endif
490
491    port_pasv_cleanup();
492
493    raw = G.ftp_arg;
494
495    /* PORT command format makes sense only over IPv4 */
496    if (!raw
497#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
498     || G.local_addr->u.sa.sa_family != AF_INET
499#endif
500    ) {
501 bail:
502        WRITE_ERR(FTP_BADCMD);
503        return;
504    }
505
506    comma = strrchr(raw, ',');
507    if (comma == NULL)
508        goto bail;
509    *comma = '\0';
510    port = bb_strtou(&comma[1], NULL, 10);
511    if (errno || port > 0xff)
512        goto bail;
513
514    comma = strrchr(raw, ',');
515    if (comma == NULL)
516        goto bail;
517    *comma = '\0';
518    port_hi = bb_strtou(&comma[1], NULL, 10);
519    if (errno || port_hi > 0xff)
520        goto bail;
521    port |= port_hi << 8;
522
523#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
524    replace_char(raw, ',', '.');
525
526    /* We are verifying that PORT's IP matches getpeername().
527     * Otherwise peer can make us open data connections
528     * to other hosts (security problem!)
529     * This code would be too simplistic:
530     * lsa = xdotted2sockaddr(raw, port);
531     * if (lsa == NULL) goto bail;
532     */
533    if (!inet_aton(raw, &port_ipv4_sin_addr))
534        goto bail;
535    peer_ipv4_len = sizeof(peer_ipv4);
536    if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
537        goto bail;
538    if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
539        goto bail;
540
541    G.port_addr = xdotted2sockaddr(raw, port);
542#else
543    G.port_addr = get_peer_lsa(STDIN_FILENO);
544    set_nport(&G.port_addr->u.sa, htons(port));
545#endif
546    WRITE_OK(FTP_PORTOK);
547}
548
549static void
550handle_rest(void)
551{
552    /* When ftp_arg == NULL simply restart from beginning */
553    G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
554    WRITE_OK(FTP_RESTOK);
555}
556
557static void
558handle_retr(void)
559{
560    struct stat statbuf;
561    off_t bytes_transferred;
562    int remote_fd;
563    int local_file_fd;
564    off_t offset = G.restart_pos;
565    char *response;
566
567    G.restart_pos = 0;
568
569    if (!port_or_pasv_was_seen())
570        return; /* port_or_pasv_was_seen emitted error response */
571
572    /* O_NONBLOCK is useful if file happens to be a device node */
573    local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
574    if (local_file_fd < 0) {
575        WRITE_ERR(FTP_FILEFAIL);
576        return;
577    }
578
579    if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
580        /* Note - pretend open failed */
581        WRITE_ERR(FTP_FILEFAIL);
582        goto file_close_out;
583    }
584    G.local_file_fd = local_file_fd;
585
586    /* Now deactive O_NONBLOCK, otherwise we have a problem
587     * on DMAPI filesystems such as XFS DMAPI.
588     */
589    ndelay_off(local_file_fd);
590
591    /* Set the download offset (from REST) if any */
592    if (offset != 0)
593        xlseek(local_file_fd, offset, SEEK_SET);
594
595    response = xasprintf(
596        " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
597        G.ftp_arg, statbuf.st_size);
598    remote_fd = get_remote_transfer_fd(response);
599    free(response);
600    if (remote_fd < 0)
601        goto file_close_out;
602
603    bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
604    close(remote_fd);
605    if (bytes_transferred < 0)
606        WRITE_ERR(FTP_BADSENDFILE);
607    else
608        WRITE_OK(FTP_TRANSFEROK);
609
610 file_close_out:
611    close(local_file_fd);
612    G.local_file_fd = 0;
613}
614
615/* List commands */
616
617static int
618popen_ls(const char *opt)
619{
620    const char *argv[5];
621    struct fd_pair outfd;
622    pid_t pid;
623
624    argv[0] = "ftpd";
625    argv[1] = opt; /* "-l" or "-1" */
626#if BB_MMU
627    argv[2] = "--";
628#else
629    /* NOMMU ftpd ls helper chdirs to argv[2],
630     * preventing peer from seeing real root. */
631    argv[2] = xrealloc_getcwd_or_warn(NULL);
632#endif
633    argv[3] = G.ftp_arg;
634    argv[4] = NULL;
635
636    /* Improve compatibility with non-RFC conforming FTP clients
637     * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
638     * See https://bugs.kde.org/show_bug.cgi?id=195578 */
639    if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
640     && G.ftp_arg && G.ftp_arg[0] == '-'
641    ) {
642        const char *tmp = strchr(G.ftp_arg, ' ');
643        if (tmp) /* skip the space */
644            tmp++;
645        argv[3] = tmp;
646    }
647
648    xpiped_pair(outfd);
649
650    /*fflush_all(); - so far we dont use stdio on output */
651    pid = BB_MMU ? xfork() : xvfork();
652    if (pid == 0) {
653        /* child */
654#if !BB_MMU
655        /* On NOMMU, we want to execute a child - copy of ourself.
656         * In chroot we usually can't do it. Thus we chdir
657         * out of the chroot back to original root,
658         * and (see later below) execute bb_busybox_exec_path
659         * relative to current directory */
660        if (fchdir(G.root_fd) != 0)
661            _exit(127);
662        /*close(G.root_fd); - close_on_exec_on() took care of this */
663#endif
664        /* NB: close _first_, then move fd! */
665        close(outfd.rd);
666        xmove_fd(outfd.wr, STDOUT_FILENO);
667        /* Opening /dev/null in chroot is hard.
668         * Just making sure STDIN_FILENO is opened
669         * to something harmless. Paranoia,
670         * ls won't read it anyway */
671        close(STDIN_FILENO);
672        dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
673#if BB_MMU
674        /* memset(&G, 0, sizeof(G)); - ls_main does it */
675        exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
676#else
677        /* + 1: we must use relative path here if in chroot.
678         * For example, execv("/proc/self/exe") will fail, since
679         * it looks for "/proc/self/exe" _relative to chroot!_ */
680        execv(bb_busybox_exec_path + 1, (char**) argv);
681        _exit(127);
682#endif
683    }
684
685    /* parent */
686    close(outfd.wr);
687#if !BB_MMU
688    free((char*)argv[2]);
689#endif
690    return outfd.rd;
691}
692
693enum {
694    USE_CTRL_CONN = 1,
695    LONG_LISTING = 2,
696};
697
698static void
699handle_dir_common(int opts)
700{
701    FILE *ls_fp;
702    char *line;
703    int ls_fd;
704
705    if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
706        return; /* port_or_pasv_was_seen emitted error response */
707
708    /* -n prevents user/groupname display,
709     * which can be problematic in chroot */
710    ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
711    ls_fp = xfdopen_for_read(ls_fd);
712
713    if (opts & USE_CTRL_CONN) {
714        /* STAT <filename> */
715        cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
716        while (1) {
717            line = xmalloc_fgetline(ls_fp);
718            if (!line)
719                break;
720            /* Hack: 0 results in no status at all */
721            /* Note: it's ok that we don't prepend space,
722             * ftp.kernel.org doesn't do that too */
723            cmdio_write(0, line);
724            free(line);
725        }
726        WRITE_OK(FTP_STATFILE_OK);
727    } else {
728        /* LIST/NLST [<filename>] */
729        int remote_fd = get_remote_transfer_fd(" Directory listing");
730        if (remote_fd >= 0) {
731            while (1) {
732                line = xmalloc_fgetline(ls_fp);
733                if (!line)
734                    break;
735                /* I've seen clients complaining when they
736                 * are fed with ls output with bare '\n'.
737                 * Pity... that would be much simpler.
738                 */
739/* TODO: need to s/LF/NUL/g here */
740                xwrite_str(remote_fd, line);
741                xwrite(remote_fd, "\r\n", 2);
742                free(line);
743            }
744        }
745        close(remote_fd);
746        WRITE_OK(FTP_TRANSFEROK);
747    }
748    fclose(ls_fp); /* closes ls_fd too */
749}
750static void
751handle_list(void)
752{
753    handle_dir_common(LONG_LISTING);
754}
755static void
756handle_nlst(void)
757{
758    /* NLST returns list of names, "\r\n" terminated without regard
759     * to the current binary flag. Names may start with "/",
760     * then they represent full names (we don't produce such names),
761     * otherwise names are relative to current directory.
762     * Embedded "\n" are replaced by NULs. This is safe since names
763     * can never contain NUL.
764     */
765    handle_dir_common(0);
766}
767static void
768handle_stat_file(void)
769{
770    handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
771}
772
773/* This can be extended to handle MLST, as all info is available
774 * in struct stat for that:
775 * MLST file_name
776 * 250-Listing file_name
777 *  type=file;size=4161;modify=19970214165800; /dir/dir/file_name
778 * 250 End
779 * Nano-doc:
780 * MLST [<file or dir name, "." assumed if not given>]
781 * Returned name should be either the same as requested, or fully qualified.
782 * If there was no parameter, return "" or (preferred) fully-qualified name.
783 * Returned "facts" (case is not important):
784 *  size    - size in octets
785 *  modify  - last modification time
786 *  type    - entry type (file,dir,OS.unix=block)
787 *            (+ cdir and pdir types for MLSD)
788 *  unique  - unique id of file/directory (inode#)
789 *  perm    -
790 *      a: can be appended to (APPE)
791 *      d: can be deleted (RMD/DELE)
792 *      f: can be renamed (RNFR)
793 *      r: can be read (RETR)
794 *      w: can be written (STOR)
795 *      e: can CWD into this dir
796 *      l: this dir can be listed (dir only!)
797 *      c: can create files in this dir
798 *      m: can create dirs in this dir (MKD)
799 *      p: can delete files in this dir
800 *  UNIX.mode - unix file mode
801 */
802static void
803handle_size_or_mdtm(int need_size)
804{
805    struct stat statbuf;
806    struct tm broken_out;
807    char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
808        | sizeof("NNN YYYYMMDDhhmmss\r\n")
809    ];
810
811    if (!G.ftp_arg
812     || stat(G.ftp_arg, &statbuf) != 0
813     || !S_ISREG(statbuf.st_mode)
814    ) {
815        WRITE_ERR(FTP_FILEFAIL);
816        return;
817    }
818    if (need_size) {
819        sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
820    } else {
821        gmtime_r(&statbuf.st_mtime, &broken_out);
822        sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
823            broken_out.tm_year + 1900,
824            broken_out.tm_mon + 1,
825            broken_out.tm_mday,
826            broken_out.tm_hour,
827            broken_out.tm_min,
828            broken_out.tm_sec);
829    }
830    cmdio_write_raw(buf);
831}
832
833/* Upload commands */
834
835#if ENABLE_FEATURE_FTP_WRITE
836static void
837handle_mkd(void)
838{
839    if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
840        WRITE_ERR(FTP_FILEFAIL);
841        return;
842    }
843    WRITE_OK(FTP_MKDIROK);
844}
845
846static void
847handle_rmd(void)
848{
849    if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
850        WRITE_ERR(FTP_FILEFAIL);
851        return;
852    }
853    WRITE_OK(FTP_RMDIROK);
854}
855
856static void
857handle_dele(void)
858{
859    if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
860        WRITE_ERR(FTP_FILEFAIL);
861        return;
862    }
863    WRITE_OK(FTP_DELEOK);
864}
865
866static void
867handle_rnfr(void)
868{
869    free(G.rnfr_filename);
870    G.rnfr_filename = xstrdup(G.ftp_arg);
871    WRITE_OK(FTP_RNFROK);
872}
873
874static void
875handle_rnto(void)
876{
877    int retval;
878
879    /* If we didn't get a RNFR, throw a wobbly */
880    if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
881        cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
882        return;
883    }
884
885    retval = rename(G.rnfr_filename, G.ftp_arg);
886    free(G.rnfr_filename);
887    G.rnfr_filename = NULL;
888
889    if (retval) {
890        WRITE_ERR(FTP_FILEFAIL);
891        return;
892    }
893    WRITE_OK(FTP_RENAMEOK);
894}
895
896static void
897handle_upload_common(int is_append, int is_unique)
898{
899    struct stat statbuf;
900    char *tempname;
901    off_t bytes_transferred;
902    off_t offset;
903    int local_file_fd;
904    int remote_fd;
905
906    offset = G.restart_pos;
907    G.restart_pos = 0;
908
909    if (!port_or_pasv_was_seen())
910        return; /* port_or_pasv_was_seen emitted error response */
911
912    tempname = NULL;
913    local_file_fd = -1;
914    if (is_unique) {
915        tempname = xstrdup(" FILE: uniq.XXXXXX");
916        local_file_fd = mkstemp(tempname + 7);
917    } else if (G.ftp_arg) {
918        int flags = O_WRONLY | O_CREAT | O_TRUNC;
919        if (is_append)
920            flags = O_WRONLY | O_CREAT | O_APPEND;
921        if (offset)
922            flags = O_WRONLY | O_CREAT;
923        local_file_fd = open(G.ftp_arg, flags, 0666);
924    }
925
926    if (local_file_fd < 0
927     || fstat(local_file_fd, &statbuf) != 0
928     || !S_ISREG(statbuf.st_mode)
929    ) {
930        free(tempname);
931        WRITE_ERR(FTP_UPLOADFAIL);
932        if (local_file_fd >= 0)
933            goto close_local_and_bail;
934        return;
935    }
936    G.local_file_fd = local_file_fd;
937
938    if (offset)
939        xlseek(local_file_fd, offset, SEEK_SET);
940
941    remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
942    free(tempname);
943
944    if (remote_fd < 0)
945        goto close_local_and_bail;
946
947    bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
948    close(remote_fd);
949    if (bytes_transferred < 0)
950        WRITE_ERR(FTP_BADSENDFILE);
951    else
952        WRITE_OK(FTP_TRANSFEROK);
953
954 close_local_and_bail:
955    close(local_file_fd);
956    G.local_file_fd = 0;
957}
958
959static void
960handle_stor(void)
961{
962    handle_upload_common(0, 0);
963}
964
965static void
966handle_appe(void)
967{
968    G.restart_pos = 0;
969    handle_upload_common(1, 0);
970}
971
972static void
973handle_stou(void)
974{
975    G.restart_pos = 0;
976    handle_upload_common(0, 1);
977}
978#endif /* ENABLE_FEATURE_FTP_WRITE */
979
980static uint32_t
981cmdio_get_cmd_and_arg(void)
982{
983    int len;
984    uint32_t cmdval;
985    char *cmd;
986
987    alarm(G.timeout);
988
989    free(G.ftp_cmd);
990    {
991        /* Paranoia. Peer may send 1 gigabyte long cmd... */
992        /* Using separate len_on_stk instead of len optimizes
993         * code size (allows len to be in CPU register) */
994        size_t len_on_stk = 8 * 1024;
995        G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
996        if (!cmd)
997            exit(0);
998        len = len_on_stk;
999    }
1000
1001    /* De-escape telnet: 0xff,0xff => 0xff */
1002    /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1003     * data transfer, and may be preceded by telnet's "Interrupt Process"
1004     * code (two-byte sequence 255,244) and then by telnet "Synch" code
1005     * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1006     * and may generate SIGURG on our side. See RFC854).
1007     * So far we don't support that (may install SIGURG handler if we'd want to),
1008     * but we need to at least remove 255,xxx pairs. lftp sends those. */
1009    /* Then de-escape FTP: NUL => '\n' */
1010    /* Testing for \xff:
1011     * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1012     * Try to get it:            ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1013     * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1014     * Testing for embedded LF:
1015     * LF_HERE=`echo -ne "LF\nHERE"`
1016     * echo Hello >"$LF_HERE"
1017     * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1018     */
1019    {
1020        int dst, src;
1021
1022        /* Strip "\r\n" if it is there */
1023        if (len != 0 && cmd[len - 1] == '\n') {
1024            len--;
1025            if (len != 0 && cmd[len - 1] == '\r')
1026                len--;
1027            cmd[len] = '\0';
1028        }
1029        src = strchrnul(cmd, 0xff) - cmd;
1030        /* 99,99% there are neither NULs nor 255s and src == len */
1031        if (src < len) {
1032            dst = src;
1033            do {
1034                if ((unsigned char)(cmd[src]) == 255) {
1035                    src++;
1036                    /* 255,xxx - skip 255 */
1037                    if ((unsigned char)(cmd[src]) != 255) {
1038                        /* 255,!255 - skip both */
1039                        src++;
1040                        continue;
1041                    }
1042                    /* 255,255 - retain one 255 */
1043                }
1044                /* NUL => '\n' */
1045                cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1046                src++;
1047            } while (src < len);
1048            cmd[dst] = '\0';
1049        }
1050    }
1051
1052    if (G.verbose > 1)
1053        verbose_log(cmd);
1054
1055    G.ftp_arg = strchr(cmd, ' ');
1056    if (G.ftp_arg != NULL)
1057        *G.ftp_arg++ = '\0';
1058
1059    /* Uppercase and pack into uint32_t first word of the command */
1060    cmdval = 0;
1061    while (*cmd)
1062        cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1063
1064    return cmdval;
1065}
1066
1067#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1068#define mk_const3(a,b,c)    ((a * 0x100 + b) * 0x100 + c)
1069enum {
1070    const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1071    const_APPE = mk_const4('A', 'P', 'P', 'E'),
1072    const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1073    const_CWD  = mk_const3('C', 'W', 'D'),
1074    const_DELE = mk_const4('D', 'E', 'L', 'E'),
1075    const_EPSV = mk_const4('E', 'P', 'S', 'V'),
1076    const_FEAT = mk_const4('F', 'E', 'A', 'T'),
1077    const_HELP = mk_const4('H', 'E', 'L', 'P'),
1078    const_LIST = mk_const4('L', 'I', 'S', 'T'),
1079    const_MDTM = mk_const4('M', 'D', 'T', 'M'),
1080    const_MKD  = mk_const3('M', 'K', 'D'),
1081    const_MODE = mk_const4('M', 'O', 'D', 'E'),
1082    const_NLST = mk_const4('N', 'L', 'S', 'T'),
1083    const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1084    const_PASS = mk_const4('P', 'A', 'S', 'S'),
1085    const_PASV = mk_const4('P', 'A', 'S', 'V'),
1086    const_PORT = mk_const4('P', 'O', 'R', 'T'),
1087    const_PWD  = mk_const3('P', 'W', 'D'),
1088    const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1089    const_REST = mk_const4('R', 'E', 'S', 'T'),
1090    const_RETR = mk_const4('R', 'E', 'T', 'R'),
1091    const_RMD  = mk_const3('R', 'M', 'D'),
1092    const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1093    const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1094    const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1095    const_STAT = mk_const4('S', 'T', 'A', 'T'),
1096    const_STOR = mk_const4('S', 'T', 'O', 'R'),
1097    const_STOU = mk_const4('S', 'T', 'O', 'U'),
1098    const_STRU = mk_const4('S', 'T', 'R', 'U'),
1099    const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1100    const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1101    const_USER = mk_const4('U', 'S', 'E', 'R'),
1102
1103#if !BB_MMU
1104    OPT_l = (1 << 0),
1105    OPT_1 = (1 << 1),
1106#endif
1107    OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
1108    OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
1109    OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
1110};
1111
1112int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1113#if !BB_MMU
1114int ftpd_main(int argc, char **argv)
1115#else
1116int ftpd_main(int argc UNUSED_PARAM, char **argv)
1117#endif
1118{
1119    unsigned abs_timeout;
1120    unsigned verbose_S;
1121    smallint opts;
1122
1123    INIT_G();
1124
1125    abs_timeout = 1 * 60 * 60;
1126    verbose_S = 0;
1127    G.timeout = 2 * 60;
1128    opt_complementary = "t+:T+:vv:SS";
1129#if BB_MMU
1130    opts = getopt32(argv,   "vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1131#else
1132    opts = getopt32(argv, "l1vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1133    if (opts & (OPT_l|OPT_1)) {
1134        /* Our secret backdoor to ls */
1135/* TODO: pass -n? It prevents user/group resolution, which may not work in chroot anyway */
1136/* TODO: pass -A? It shows dot files */
1137/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
1138        xchdir(argv[2]);
1139        argv[2] = (char*)"--";
1140        /* memset(&G, 0, sizeof(G)); - ls_main does it */
1141        return ls_main(argc, argv);
1142    }
1143#endif
1144    if (G.verbose < verbose_S)
1145        G.verbose = verbose_S;
1146    if (abs_timeout | G.timeout) {
1147        if (abs_timeout == 0)
1148            abs_timeout = INT_MAX;
1149        G.end_time = monotonic_sec() + abs_timeout;
1150        if (G.timeout > abs_timeout)
1151            G.timeout = abs_timeout;
1152    }
1153    strcpy(G.msg_ok  + 4, MSG_OK );
1154    strcpy(G.msg_err + 4, MSG_ERR);
1155
1156    G.local_addr = get_sock_lsa(STDIN_FILENO);
1157    if (!G.local_addr) {
1158        /* This is confusing:
1159         * bb_error_msg_and_die("stdin is not a socket");
1160         * Better: */
1161        bb_show_usage();
1162        /* Help text says that ftpd must be used as inetd service,
1163         * which is by far the most usual cause of get_sock_lsa
1164         * failure */
1165    }
1166
1167    if (!(opts & OPT_v))
1168        logmode = LOGMODE_NONE;
1169    if (opts & OPT_S) {
1170        /* LOG_NDELAY is needed since we may chroot later */
1171        openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1172        logmode |= LOGMODE_SYSLOG;
1173    }
1174    if (logmode)
1175        applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1176
1177#if !BB_MMU
1178    G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1179    close_on_exec_on(G.root_fd);
1180#endif
1181
1182    if (argv[optind]) {
1183        xchroot(argv[optind]);
1184    }
1185
1186    //umask(077); - admin can set umask before starting us
1187
1188    /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
1189    signal(SIGPIPE, SIG_IGN);
1190
1191    /* Set up options on the command socket (do we need these all? why?) */
1192    setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1193    setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
1194    /* Telnet protocol over command link may send "urgent" data,
1195     * we prefer it to be received in the "normal" data stream: */
1196    setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1197
1198    WRITE_OK(FTP_GREET);
1199    signal(SIGALRM, timeout_handler);
1200
1201#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
1202    {
1203        smallint user_was_specified = 0;
1204        while (1) {
1205            uint32_t cmdval = cmdio_get_cmd_and_arg();
1206
1207            if (cmdval == const_USER) {
1208                if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
1209                    cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
1210                else {
1211                    user_was_specified = 1;
1212                    cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
1213                }
1214            } else if (cmdval == const_PASS) {
1215                if (user_was_specified)
1216                    break;
1217                cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
1218            } else if (cmdval == const_QUIT) {
1219                WRITE_OK(FTP_GOODBYE);
1220                return 0;
1221            } else {
1222                cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1223            }
1224        }
1225    }
1226    WRITE_OK(FTP_LOGINOK);
1227#endif
1228
1229    /* RFC-959 Section 5.1
1230     * The following commands and options MUST be supported by every
1231     * server-FTP and user-FTP, except in cases where the underlying
1232     * file system or operating system does not allow or support
1233     * a particular command.
1234     * Type: ASCII Non-print, IMAGE, LOCAL 8
1235     * Mode: Stream
1236     * Structure: File, Record*
1237     * (Record structure is REQUIRED only for hosts whose file
1238     *  systems support record structure).
1239     * Commands:
1240     * USER, PASS, ACCT, [bbox: ACCT not supported]
1241     * PORT, PASV,
1242     * TYPE, MODE, STRU,
1243     * RETR, STOR, APPE,
1244     * RNFR, RNTO, DELE,
1245     * CWD,  CDUP, RMD,  MKD,  PWD,
1246     * LIST, NLST,
1247     * SYST, STAT,
1248     * HELP, NOOP, QUIT.
1249     */
1250    /* ACCOUNT (ACCT)
1251     * "The argument field is a Telnet string identifying the user's account.
1252     * The command is not necessarily related to the USER command, as some
1253     * sites may require an account for login and others only for specific
1254     * access, such as storing files. In the latter case the command may
1255     * arrive at any time.
1256     * There are reply codes to differentiate these cases for the automation:
1257     * when account information is required for login, the response to
1258     * a successful PASSword command is reply code 332. On the other hand,
1259     * if account information is NOT required for login, the reply to
1260     * a successful PASSword command is 230; and if the account information
1261     * is needed for a command issued later in the dialogue, the server
1262     * should return a 332 or 532 reply depending on whether it stores
1263     * (pending receipt of the ACCounT command) or discards the command,
1264     * respectively."
1265     */
1266
1267    while (1) {
1268        uint32_t cmdval = cmdio_get_cmd_and_arg();
1269
1270        if (cmdval == const_QUIT) {
1271            WRITE_OK(FTP_GOODBYE);
1272            return 0;
1273        }
1274        else if (cmdval == const_USER)
1275            /* This would mean "ok, now give me PASS". */
1276            /*WRITE_OK(FTP_GIVEPWORD);*/
1277            /* vsftpd can be configured to not require that,
1278             * and this also saves one roundtrip:
1279             */
1280            WRITE_OK(FTP_LOGINOK);
1281        else if (cmdval == const_PASS)
1282            WRITE_OK(FTP_LOGINOK);
1283        else if (cmdval == const_NOOP)
1284            WRITE_OK(FTP_NOOPOK);
1285        else if (cmdval == const_TYPE)
1286            WRITE_OK(FTP_TYPEOK);
1287        else if (cmdval == const_STRU)
1288            WRITE_OK(FTP_STRUOK);
1289        else if (cmdval == const_MODE)
1290            WRITE_OK(FTP_MODEOK);
1291        else if (cmdval == const_ALLO)
1292            WRITE_OK(FTP_ALLOOK);
1293        else if (cmdval == const_SYST)
1294            cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1295        else if (cmdval == const_PWD)
1296            handle_pwd();
1297        else if (cmdval == const_CWD)
1298            handle_cwd();
1299        else if (cmdval == const_CDUP) /* cd .. */
1300            handle_cdup();
1301        /* HELP is nearly useless, but we can reuse FEAT for it */
1302        /* lftp uses FEAT */
1303        else if (cmdval == const_HELP || cmdval == const_FEAT)
1304            handle_feat(cmdval == const_HELP
1305                    ? STRNUM32(FTP_HELP)
1306                    : STRNUM32(FTP_STATOK)
1307            );
1308        else if (cmdval == const_LIST) /* ls -l */
1309            handle_list();
1310        else if (cmdval == const_NLST) /* "name list", bare ls */
1311            handle_nlst();
1312        /* SIZE is crucial for wget's download indicator etc */
1313        /* Mozilla, lftp use MDTM (presumably for caching) */
1314        else if (cmdval == const_SIZE || cmdval == const_MDTM)
1315            handle_size_or_mdtm(cmdval == const_SIZE);
1316        else if (cmdval == const_STAT) {
1317            if (G.ftp_arg == NULL)
1318                handle_stat();
1319            else
1320                handle_stat_file();
1321        }
1322        else if (cmdval == const_PASV)
1323            handle_pasv();
1324        else if (cmdval == const_EPSV)
1325            handle_epsv();
1326        else if (cmdval == const_RETR)
1327            handle_retr();
1328        else if (cmdval == const_PORT)
1329            handle_port();
1330        else if (cmdval == const_REST)
1331            handle_rest();
1332#if ENABLE_FEATURE_FTP_WRITE
1333        else if (opts & OPT_w) {
1334            if (cmdval == const_STOR)
1335                handle_stor();
1336            else if (cmdval == const_MKD)
1337                handle_mkd();
1338            else if (cmdval == const_RMD)
1339                handle_rmd();
1340            else if (cmdval == const_DELE)
1341                handle_dele();
1342            else if (cmdval == const_RNFR) /* "rename from" */
1343                handle_rnfr();
1344            else if (cmdval == const_RNTO) /* "rename to" */
1345                handle_rnto();
1346            else if (cmdval == const_APPE)
1347                handle_appe();
1348            else if (cmdval == const_STOU) /* "store unique" */
1349                handle_stou();
1350            else
1351                goto bad_cmd;
1352        }
1353#endif
1354#if 0
1355        else if (cmdval == const_STOR
1356         || cmdval == const_MKD
1357         || cmdval == const_RMD
1358         || cmdval == const_DELE
1359         || cmdval == const_RNFR
1360         || cmdval == const_RNTO
1361         || cmdval == const_APPE
1362         || cmdval == const_STOU
1363        ) {
1364            cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1365        }
1366#endif
1367        else {
1368            /* Which unsupported commands were seen in the wild?
1369             * (doesn't necessarily mean "we must support them")
1370             * foo 1.2.3: XXXX - comment
1371             */
1372#if ENABLE_FEATURE_FTP_WRITE
1373 bad_cmd:
1374#endif
1375            cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
1376        }
1377    }
1378}
Note: See TracBrowser for help on using the repository browser.