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 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,
 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                WRITE_ERR(FTP_UPLOADFAIL);
 931                if (local_file_fd >= 0)
 932                        goto close_local_and_bail;
 933                return;
 934        }
 935        G.local_file_fd = local_file_fd;
 936
 937        if (offset)
 938                xlseek(local_file_fd, offset, SEEK_SET);
 939
 940        remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
 941        free(tempname);
 942
 943        if (remote_fd < 0)
 944                goto close_local_and_bail;
 945
 946        bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
 947        close(remote_fd);
 948        if (bytes_transferred < 0)
 949                WRITE_ERR(FTP_BADSENDFILE);
 950        else
 951                WRITE_OK(FTP_TRANSFEROK);
 952
 953 close_local_and_bail:
 954        close(local_file_fd);
 955        G.local_file_fd = 0;
 956}
 957
 958static void
 959handle_stor(void)
 960{
 961        handle_upload_common(0, 0);
 962}
 963
 964static void
 965handle_appe(void)
 966{
 967        G.restart_pos = 0;
 968        handle_upload_common(1, 0);
 969}
 970
 971static void
 972handle_stou(void)
 973{
 974        G.restart_pos = 0;
 975        handle_upload_common(0, 1);
 976}
 977#endif /* ENABLE_FEATURE_FTP_WRITE */
 978
 979static uint32_t
 980cmdio_get_cmd_and_arg(void)
 981{
 982        int len;
 983        uint32_t cmdval;
 984        char *cmd;
 985
 986        alarm(G.timeout);
 987
 988        free(G.ftp_cmd);
 989        {
 990                /* Paranoia. Peer may send 1 gigabyte long cmd... */
 991                /* Using separate len_on_stk instead of len optimizes
 992                 * code size (allows len to be in CPU register) */
 993                size_t len_on_stk = 8 * 1024;
 994                G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
 995                if (!cmd)
 996                        exit(0);
 997                len = len_on_stk;
 998        }
 999
1000        /* De-escape telnet: 0xff,0xff => 0xff */
1001        /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1002         * data transfer, and may be preceded by telnet's "Interrupt Process"
1003         * code (two-byte sequence 255,244) and then by telnet "Synch" code
1004         * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1005         * and may generate SIGURG on our side. See RFC854).
1006         * So far we don't support that (may install SIGURG handler if we'd want to),
1007         * but we need to at least remove 255,xxx pairs. lftp sends those. */
1008        /* Then de-escape FTP: NUL => '\n' */
1009        /* Testing for \xff:
1010         * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1011         * Try to get it:            ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1012         * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1013         * Testing for embedded LF:
1014         * LF_HERE=`echo -ne "LF\nHERE"`
1015         * echo Hello >"$LF_HERE"
1016         * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1017         */
1018        {
1019                int dst, src;
1020
1021                /* Strip "\r\n" if it is there */
1022                if (len != 0 && cmd[len - 1] == '\n') {
1023                        len--;
1024                        if (len != 0 && cmd[len - 1] == '\r')
1025                                len--;
1026                        cmd[len] = '\0';
1027                }
1028                src = strchrnul(cmd, 0xff) - cmd;
1029                /* 99,99% there are neither NULs nor 255s and src == len */
1030                if (src < len) {
1031                        dst = src;
1032                        do {
1033                                if ((unsigned char)(cmd[src]) == 255) {
1034                                        src++;
1035                                        /* 255,xxx - skip 255 */
1036                                        if ((unsigned char)(cmd[src]) != 255) {
1037                                                /* 255,!255 - skip both */
1038                                                src++;
1039                                                continue;
1040                                        }
1041                                        /* 255,255 - retain one 255 */
1042                                }
1043                                /* NUL => '\n' */
1044                                cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1045                                src++;
1046                        } while (src < len);
1047                        cmd[dst] = '\0';
1048                }
1049        }
1050
1051        if (G.verbose > 1)
1052                verbose_log(cmd);
1053
1054        G.ftp_arg = strchr(cmd, ' ');
1055        if (G.ftp_arg != NULL)
1056                *G.ftp_arg++ = '\0';
1057
1058        /* Uppercase and pack into uint32_t first word of the command */
1059        cmdval = 0;
1060        while (*cmd)
1061                cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1062
1063        return cmdval;
1064}
1065
1066#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1067#define mk_const3(a,b,c)    ((a * 0x100 + b) * 0x100 + c)
1068enum {
1069        const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1070        const_APPE = mk_const4('A', 'P', 'P', 'E'),
1071        const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1072        const_CWD  = mk_const3('C', 'W', 'D'),
1073        const_DELE = mk_const4('D', 'E', 'L', 'E'),
1074        const_EPSV = mk_const4('E', 'P', 'S', 'V'),
1075        const_FEAT = mk_const4('F', 'E', 'A', 'T'),
1076        const_HELP = mk_const4('H', 'E', 'L', 'P'),
1077        const_LIST = mk_const4('L', 'I', 'S', 'T'),
1078        const_MDTM = mk_const4('M', 'D', 'T', 'M'),
1079        const_MKD  = mk_const3('M', 'K', 'D'),
1080        const_MODE = mk_const4('M', 'O', 'D', 'E'),
1081        const_NLST = mk_const4('N', 'L', 'S', 'T'),
1082        const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1083        const_PASS = mk_const4('P', 'A', 'S', 'S'),
1084        const_PASV = mk_const4('P', 'A', 'S', 'V'),
1085        const_PORT = mk_const4('P', 'O', 'R', 'T'),
1086        const_PWD  = mk_const3('P', 'W', 'D'),
1087        const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1088        const_REST = mk_const4('R', 'E', 'S', 'T'),
1089        const_RETR = mk_const4('R', 'E', 'T', 'R'),
1090        const_RMD  = mk_const3('R', 'M', 'D'),
1091        const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1092        const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1093        const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1094        const_STAT = mk_const4('S', 'T', 'A', 'T'),
1095        const_STOR = mk_const4('S', 'T', 'O', 'R'),
1096        const_STOU = mk_const4('S', 'T', 'O', 'U'),
1097        const_STRU = mk_const4('S', 'T', 'R', 'U'),
1098        const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1099        const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1100        const_USER = mk_const4('U', 'S', 'E', 'R'),
1101
1102#if !BB_MMU
1103        OPT_l = (1 << 0),
1104        OPT_1 = (1 << 1),
1105#endif
1106        OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
1107        OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
1108        OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
1109};
1110
1111int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1112#if !BB_MMU
1113int ftpd_main(int argc, char **argv)
1114#else
1115int ftpd_main(int argc UNUSED_PARAM, char **argv)
1116#endif
1117{
1118        unsigned abs_timeout;
1119        unsigned verbose_S;
1120        smallint opts;
1121
1122        INIT_G();
1123
1124        abs_timeout = 1 * 60 * 60;
1125        verbose_S = 0;
1126        G.timeout = 2 * 60;
1127        opt_complementary = "t+:T+:vv:SS";
1128#if BB_MMU
1129        opts = getopt32(argv,   "vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1130#else
1131        opts = getopt32(argv, "l1vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1132        if (opts & (OPT_l|OPT_1)) {
1133                /* Our secret backdoor to ls */
1134/* TODO: pass -n? It prevents user/group resolution, which may not work in chroot anyway */
1135/* TODO: pass -A? It shows dot files */
1136/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
1137                xchdir(argv[2]);
1138                argv[2] = (char*)"--";
1139                /* memset(&G, 0, sizeof(G)); - ls_main does it */
1140                return ls_main(argc, argv);
1141        }
1142#endif
1143        if (G.verbose < verbose_S)
1144                G.verbose = verbose_S;
1145        if (abs_timeout | G.timeout) {
1146                if (abs_timeout == 0)
1147                        abs_timeout = INT_MAX;
1148                G.end_time = monotonic_sec() + abs_timeout;
1149                if (G.timeout > abs_timeout)
1150                        G.timeout = abs_timeout;
1151        }
1152        strcpy(G.msg_ok  + 4, MSG_OK );
1153        strcpy(G.msg_err + 4, MSG_ERR);
1154
1155        G.local_addr = get_sock_lsa(STDIN_FILENO);
1156        if (!G.local_addr) {
1157                /* This is confusing:
1158                 * bb_error_msg_and_die("stdin is not a socket");
1159                 * Better: */
1160                bb_show_usage();
1161                /* Help text says that ftpd must be used as inetd service,
1162                 * which is by far the most usual cause of get_sock_lsa
1163                 * failure */
1164        }
1165
1166        if (!(opts & OPT_v))
1167                logmode = LOGMODE_NONE;
1168        if (opts & OPT_S) {
1169                /* LOG_NDELAY is needed since we may chroot later */
1170                openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1171                logmode |= LOGMODE_SYSLOG;
1172        }
1173        if (logmode)
1174                applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1175
1176#if !BB_MMU
1177        G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1178        close_on_exec_on(G.root_fd);
1179#endif
1180
1181        if (argv[optind]) {
1182                xchdir(argv[optind]);
1183                chroot(".");
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}
1379