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