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};
 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
 464/* libbb candidate */
 465static
 466len_and_sockaddr* get_peer_lsa(int fd)
 467{
 468        len_and_sockaddr *lsa;
 469        socklen_t len = 0;
 470
 471        if (getpeername(fd, NULL, &len) != 0)
 472                return NULL;
 473        lsa = xzalloc(LSA_LEN_SIZE + len);
 474        lsa->len = len;
 475        getpeername(fd, &lsa->u.sa, &lsa->len);
 476        return lsa;
 477}
 478
 479static void
 480handle_port(void)
 481{
 482        unsigned port, port_hi;
 483        char *raw, *comma;
 484#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
 485        socklen_t peer_ipv4_len;
 486        struct sockaddr_in peer_ipv4;
 487        struct in_addr port_ipv4_sin_addr;
 488#endif
 489
 490        port_pasv_cleanup();
 491
 492        raw = G.ftp_arg;
 493
 494        /* PORT command format makes sense only over IPv4 */
 495        if (!raw
 496#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
 497         || G.local_addr->u.sa.sa_family != AF_INET
 498#endif
 499        ) {
 500 bail:
 501                WRITE_ERR(FTP_BADCMD);
 502                return;
 503        }
 504
 505        comma = strrchr(raw, ',');
 506        if (comma == NULL)
 507                goto bail;
 508        *comma = '\0';
 509        port = bb_strtou(&comma[1], NULL, 10);
 510        if (errno || port > 0xff)
 511                goto bail;
 512
 513        comma = strrchr(raw, ',');
 514        if (comma == NULL)
 515                goto bail;
 516        *comma = '\0';
 517        port_hi = bb_strtou(&comma[1], NULL, 10);
 518        if (errno || port_hi > 0xff)
 519                goto bail;
 520        port |= port_hi << 8;
 521
 522#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
 523        replace_char(raw, ',', '.');
 524
 525        /* We are verifying that PORT's IP matches getpeername().
 526         * Otherwise peer can make us open data connections
 527         * to other hosts (security problem!)
 528         * This code would be too simplistic:
 529         * lsa = xdotted2sockaddr(raw, port);
 530         * if (lsa == NULL) goto bail;
 531         */
 532        if (!inet_aton(raw, &port_ipv4_sin_addr))
 533                goto bail;
 534        peer_ipv4_len = sizeof(peer_ipv4);
 535        if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
 536                goto bail;
 537        if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
 538                goto bail;
 539
 540        G.port_addr = xdotted2sockaddr(raw, port);
 541#else
 542        G.port_addr = get_peer_lsa(STDIN_FILENO);
 543        set_nport(G.port_addr, htons(port));
 544#endif
 545        WRITE_OK(FTP_PORTOK);
 546}
 547
 548static void
 549handle_rest(void)
 550{
 551        /* When ftp_arg == NULL simply restart from beginning */
 552        G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0;
 553        WRITE_OK(FTP_RESTOK);
 554}
 555
 556static void
 557handle_retr(void)
 558{
 559        struct stat statbuf;
 560        off_t bytes_transferred;
 561        int remote_fd;
 562        int local_file_fd;
 563        off_t offset = G.restart_pos;
 564        char *response;
 565
 566        G.restart_pos = 0;
 567
 568        if (!port_or_pasv_was_seen())
 569                return; /* port_or_pasv_was_seen emitted error response */
 570
 571        /* O_NONBLOCK is useful if file happens to be a device node */
 572        local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
 573        if (local_file_fd < 0) {
 574                WRITE_ERR(FTP_FILEFAIL);
 575                return;
 576        }
 577
 578        if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
 579                /* Note - pretend open failed */
 580                WRITE_ERR(FTP_FILEFAIL);
 581                goto file_close_out;
 582        }
 583        G.local_file_fd = local_file_fd;
 584
 585        /* Now deactive O_NONBLOCK, otherwise we have a problem
 586         * on DMAPI filesystems such as XFS DMAPI.
 587         */
 588        ndelay_off(local_file_fd);
 589
 590        /* Set the download offset (from REST) if any */
 591        if (offset != 0)
 592                xlseek(local_file_fd, offset, SEEK_SET);
 593
 594        response = xasprintf(
 595                " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
 596                G.ftp_arg, statbuf.st_size);
 597        remote_fd = get_remote_transfer_fd(response);
 598        free(response);
 599        if (remote_fd < 0)
 600                goto file_close_out;
 601
 602        bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
 603        close(remote_fd);
 604        if (bytes_transferred < 0)
 605                WRITE_ERR(FTP_BADSENDFILE);
 606        else
 607                WRITE_OK(FTP_TRANSFEROK);
 608
 609 file_close_out:
 610        close(local_file_fd);
 611        G.local_file_fd = 0;
 612}
 613
 614/* List commands */
 615
 616static int
 617popen_ls(const char *opt)
 618{
 619        const char *argv[5];
 620        struct fd_pair outfd;
 621        pid_t pid;
 622
 623        argv[0] = "ftpd";
 624        argv[1] = opt; /* "-l" or "-1" */
 625#if BB_MMU
 626        argv[2] = "--";
 627#else
 628        /* NOMMU ftpd ls helper chdirs to argv[2],
 629         * preventing peer from seeing real root. */
 630        argv[2] = xrealloc_getcwd_or_warn(NULL);
 631#endif
 632        argv[3] = G.ftp_arg;
 633        argv[4] = NULL;
 634
 635        /* Improve compatibility with non-RFC conforming FTP clients
 636         * which send e.g. "LIST -l", "LIST -la".
 637         * See https://bugs.kde.org/show_bug.cgi?id=195578 */
 638        if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
 639         && G.ftp_arg && G.ftp_arg[0] == '-' && G.ftp_arg[1] == 'l'
 640        ) {
 641                const char *tmp = strchr(G.ftp_arg, ' ');
 642                if (tmp) /* skip the space */
 643                        tmp++;
 644                argv[3] = tmp;
 645        }
 646
 647        xpiped_pair(outfd);
 648
 649        /*fflush_all(); - so far we dont use stdio on output */
 650        pid = BB_MMU ? fork() : vfork();
 651        if (pid < 0)
 652                bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork");
 653
 654        if (pid == 0) {
 655                /* child */
 656#if !BB_MMU
 657                /* On NOMMU, we want to execute a child - copy of ourself.
 658                 * In chroot we usually can't do it. Thus we chdir
 659                 * out of the chroot back to original root,
 660                 * and (see later below) execute bb_busybox_exec_path
 661                 * relative to current directory */
 662                if (fchdir(G.root_fd) != 0)
 663                        _exit(127);
 664                /*close(G.root_fd); - close_on_exec_on() took care of this */
 665#endif
 666                /* NB: close _first_, then move fd! */
 667                close(outfd.rd);
 668                xmove_fd(outfd.wr, STDOUT_FILENO);
 669                /* Opening /dev/null in chroot is hard.
 670                 * Just making sure STDIN_FILENO is opened
 671                 * to something harmless. Paranoia,
 672                 * ls won't read it anyway */
 673                close(STDIN_FILENO);
 674                dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
 675#if BB_MMU
 676                /* memset(&G, 0, sizeof(G)); - ls_main does it */
 677                exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
 678#else
 679                /* + 1: we must use relative path here if in chroot.
 680                 * For example, execv("/proc/self/exe") will fail, since
 681                 * it looks for "/proc/self/exe" _relative to chroot!_ */
 682                execv(bb_busybox_exec_path + 1, (char**) argv);
 683                _exit(127);
 684#endif
 685        }
 686
 687        /* parent */
 688        close(outfd.wr);
 689#if !BB_MMU
 690        free((char*)argv[2]);
 691#endif
 692        return outfd.rd;
 693}
 694
 695enum {
 696        USE_CTRL_CONN = 1,
 697        LONG_LISTING = 2,
 698};
 699
 700static void
 701handle_dir_common(int opts)
 702{
 703        FILE *ls_fp;
 704        char *line;
 705        int ls_fd;
 706
 707        if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
 708                return; /* port_or_pasv_was_seen emitted error response */
 709
 710        /* -n prevents user/groupname display,
 711         * which can be problematic in chroot */
 712        ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
 713        ls_fp = xfdopen_for_read(ls_fd);
 714
 715        if (opts & USE_CTRL_CONN) {
 716                /* STAT <filename> */
 717                cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
 718                while (1) {
 719                        line = xmalloc_fgetline(ls_fp);
 720                        if (!line)
 721                                break;
 722                        /* Hack: 0 results in no status at all */
 723                        /* Note: it's ok that we don't prepend space,
 724                         * ftp.kernel.org doesn't do that too */
 725                        cmdio_write(0, line);
 726                        free(line);
 727                }
 728                WRITE_OK(FTP_STATFILE_OK);
 729        } else {
 730                /* LIST/NLST [<filename>] */
 731                int remote_fd = get_remote_transfer_fd(" Directory listing");
 732                if (remote_fd >= 0) {
 733                        while (1) {
 734                                line = xmalloc_fgetline(ls_fp);
 735                                if (!line)
 736                                        break;
 737                                /* I've seen clients complaining when they
 738                                 * are fed with ls output with bare '\n'.
 739                                 * Pity... that would be much simpler.
 740                                 */
 741/* TODO: need to s/LF/NUL/g here */
 742                                xwrite_str(remote_fd, line);
 743                                xwrite(remote_fd, "\r\n", 2);
 744                                free(line);
 745                        }
 746                }
 747                close(remote_fd);
 748                WRITE_OK(FTP_TRANSFEROK);
 749        }
 750        fclose(ls_fp); /* closes ls_fd too */
 751}
 752static void
 753handle_list(void)
 754{
 755        handle_dir_common(LONG_LISTING);
 756}
 757static void
 758handle_nlst(void)
 759{
 760        /* NLST returns list of names, "\r\n" terminated without regard
 761         * to the current binary flag. Names may start with "/",
 762         * then they represent full names (we don't produce such names),
 763         * otherwise names are relative to current directory.
 764         * Embedded "\n" are replaced by NULs. This is safe since names
 765         * can never contain NUL.
 766         */
 767        handle_dir_common(0);
 768}
 769static void
 770handle_stat_file(void)
 771{
 772        handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
 773}
 774
 775/* This can be extended to handle MLST, as all info is available
 776 * in struct stat for that:
 777 * MLST file_name
 778 * 250-Listing file_name
 779 *  type=file;size=4161;modify=19970214165800; /dir/dir/file_name
 780 * 250 End
 781 * Nano-doc:
 782 * MLST [<file or dir name, "." assumed if not given>]
 783 * Returned name should be either the same as requested, or fully qualified.
 784 * If there was no parameter, return "" or (preferred) fully-qualified name.
 785 * Returned "facts" (case is not important):
 786 *  size    - size in octets
 787 *  modify  - last modification time
 788 *  type    - entry type (file,dir,OS.unix=block)
 789 *            (+ cdir and pdir types for MLSD)
 790 *  unique  - unique id of file/directory (inode#)
 791 *  perm    -
 792 *      a: can be appended to (APPE)
 793 *      d: can be deleted (RMD/DELE)
 794 *      f: can be renamed (RNFR)
 795 *      r: can be read (RETR)
 796 *      w: can be written (STOR)
 797 *      e: can CWD into this dir
 798 *      l: this dir can be listed (dir only!)
 799 *      c: can create files in this dir
 800 *      m: can create dirs in this dir (MKD)
 801 *      p: can delete files in this dir
 802 *  UNIX.mode - unix file mode
 803 */
 804static void
 805handle_size_or_mdtm(int need_size)
 806{
 807        struct stat statbuf;
 808        struct tm broken_out;
 809        char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
 810                | sizeof("NNN YYYYMMDDhhmmss\r\n")
 811        ];
 812
 813        if (!G.ftp_arg
 814         || stat(G.ftp_arg, &statbuf) != 0
 815         || !S_ISREG(statbuf.st_mode)
 816        ) {
 817                WRITE_ERR(FTP_FILEFAIL);
 818                return;
 819        }
 820        if (need_size) {
 821                sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
 822        } else {
 823                gmtime_r(&statbuf.st_mtime, &broken_out);
 824                sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
 825                        broken_out.tm_year + 1900,
 826                        broken_out.tm_mon,
 827                        broken_out.tm_mday,
 828                        broken_out.tm_hour,
 829                        broken_out.tm_min,
 830                        broken_out.tm_sec);
 831        }
 832        cmdio_write_raw(buf);
 833}
 834
 835/* Upload commands */
 836
 837#if ENABLE_FEATURE_FTP_WRITE
 838static void
 839handle_mkd(void)
 840{
 841        if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
 842                WRITE_ERR(FTP_FILEFAIL);
 843                return;
 844        }
 845        WRITE_OK(FTP_MKDIROK);
 846}
 847
 848static void
 849handle_rmd(void)
 850{
 851        if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
 852                WRITE_ERR(FTP_FILEFAIL);
 853                return;
 854        }
 855        WRITE_OK(FTP_RMDIROK);
 856}
 857
 858static void
 859handle_dele(void)
 860{
 861        if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
 862                WRITE_ERR(FTP_FILEFAIL);
 863                return;
 864        }
 865        WRITE_OK(FTP_DELEOK);
 866}
 867
 868static void
 869handle_rnfr(void)
 870{
 871        free(G.rnfr_filename);
 872        G.rnfr_filename = xstrdup(G.ftp_arg);
 873        WRITE_OK(FTP_RNFROK);
 874}
 875
 876static void
 877handle_rnto(void)
 878{
 879        int retval;
 880
 881        /* If we didn't get a RNFR, throw a wobbly */
 882        if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
 883                cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
 884                return;
 885        }
 886
 887        retval = rename(G.rnfr_filename, G.ftp_arg);
 888        free(G.rnfr_filename);
 889        G.rnfr_filename = NULL;
 890
 891        if (retval) {
 892                WRITE_ERR(FTP_FILEFAIL);
 893                return;
 894        }
 895        WRITE_OK(FTP_RENAMEOK);
 896}
 897
 898static void
 899handle_upload_common(int is_append, int is_unique)
 900{
 901        struct stat statbuf;
 902        char *tempname;
 903        off_t bytes_transferred;
 904        off_t offset;
 905        int local_file_fd;
 906        int remote_fd;
 907
 908        offset = G.restart_pos;
 909        G.restart_pos = 0;
 910
 911        if (!port_or_pasv_was_seen())
 912                return; /* port_or_pasv_was_seen emitted error response */
 913
 914        tempname = NULL;
 915        local_file_fd = -1;
 916        if (is_unique) {
 917                tempname = xstrdup(" FILE: uniq.XXXXXX");
 918                local_file_fd = mkstemp(tempname + 7);
 919        } else if (G.ftp_arg) {
 920                int flags = O_WRONLY | O_CREAT | O_TRUNC;
 921                if (is_append)
 922                        flags = O_WRONLY | O_CREAT | O_APPEND;
 923                if (offset)
 924                        flags = O_WRONLY | O_CREAT;
 925                local_file_fd = open(G.ftp_arg, flags, 0666);
 926        }
 927
 928        if (local_file_fd < 0
 929         || fstat(local_file_fd, &statbuf) != 0
 930         || !S_ISREG(statbuf.st_mode)
 931        ) {
 932                WRITE_ERR(FTP_UPLOADFAIL);
 933                if (local_file_fd >= 0)
 934                        goto close_local_and_bail;
 935                return;
 936        }
 937        G.local_file_fd = local_file_fd;
 938
 939        if (offset)
 940                xlseek(local_file_fd, offset, SEEK_SET);
 941
 942        remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
 943        free(tempname);
 944
 945        if (remote_fd < 0)
 946                goto close_local_and_bail;
 947
 948        bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
 949        close(remote_fd);
 950        if (bytes_transferred < 0)
 951                WRITE_ERR(FTP_BADSENDFILE);
 952        else
 953                WRITE_OK(FTP_TRANSFEROK);
 954
 955 close_local_and_bail:
 956        close(local_file_fd);
 957        G.local_file_fd = 0;
 958}
 959
 960static void
 961handle_stor(void)
 962{
 963        handle_upload_common(0, 0);
 964}
 965
 966static void
 967handle_appe(void)
 968{
 969        G.restart_pos = 0;
 970        handle_upload_common(1, 0);
 971}
 972
 973static void
 974handle_stou(void)
 975{
 976        G.restart_pos = 0;
 977        handle_upload_common(0, 1);
 978}
 979#endif /* ENABLE_FEATURE_FTP_WRITE */
 980
 981static uint32_t
 982cmdio_get_cmd_and_arg(void)
 983{
 984        size_t len;
 985        uint32_t cmdval;
 986        char *cmd;
 987
 988        alarm(G.timeout);
 989
 990        free(G.ftp_cmd);
 991        len = 8 * 1024; /* Paranoia. Peer may send 1 gigabyte long cmd... */
 992        G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len);
 993        if (!cmd)
 994                exit(0);
 995
 996        /* De-escape telnet: 0xff,0xff => 0xff */
 997        /* RFC959 says that ABOR, STAT, QUIT may be sent even during
 998         * data transfer, and may be preceded by telnet's "Interrupt Process"
 999         * code (two-byte sequence 255,244) and then by telnet "Synch" code
1000         * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1001         * and may generate SIGURG on our side. See RFC854).
1002         * So far we don't support that (may install SIGURG handler if we'd want to),
1003         * but we need to at least remove 255,xxx pairs. lftp sends those. */
1004        /* Then de-escape FTP: NUL => '\n' */
1005        /* Testing for \xff:
1006         * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1007         * Try to get it:            ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1008         * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1009         * Testing for embedded LF:
1010         * LF_HERE=`echo -ne "LF\nHERE"`
1011         * echo Hello >"$LF_HERE"
1012         * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1013         */
1014        {
1015                int dst, src;
1016
1017                /* Strip "\r\n" if it is there */
1018                if (len != 0 && cmd[len - 1] == '\n') {
1019                        len--;
1020                        if (len != 0 && cmd[len - 1] == '\r')
1021                                len--;
1022                        cmd[len] = '\0';
1023                }
1024                src = strchrnul(cmd, 0xff) - cmd;
1025                /* 99,99% there are neither NULs nor 255s and src == len */
1026                if (src < len) {
1027                        dst = src;
1028                        do {
1029                                if ((unsigned char)(cmd[src]) == 255) {
1030                                        src++;
1031                                        /* 255,xxx - skip 255 */
1032                                        if ((unsigned char)(cmd[src]) != 255) {
1033                                                /* 255,!255 - skip both */
1034                                                src++;
1035                                                continue;
1036                                        }
1037                                        /* 255,255 - retain one 255 */
1038                                }
1039                                /* NUL => '\n' */
1040                                cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1041                                src++;
1042                        } while (src < len);
1043                        cmd[dst] = '\0';
1044                }
1045        }
1046
1047        if (G.verbose > 1)
1048                verbose_log(cmd);
1049
1050        G.ftp_arg = strchr(cmd, ' ');
1051        if (G.ftp_arg != NULL)
1052                *G.ftp_arg++ = '\0';
1053
1054        /* Uppercase and pack into uint32_t first word of the command */
1055        cmdval = 0;
1056        while (*cmd)
1057                cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1058
1059        return cmdval;
1060}
1061
1062#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1063#define mk_const3(a,b,c)    ((a * 0x100 + b) * 0x100 + c)
1064enum {
1065        const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1066        const_APPE = mk_const4('A', 'P', 'P', 'E'),
1067        const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1068        const_CWD  = mk_const3('C', 'W', 'D'),
1069        const_DELE = mk_const4('D', 'E', 'L', 'E'),
1070        const_EPSV = mk_const4('E', 'P', 'S', 'V'),
1071        const_FEAT = mk_const4('F', 'E', 'A', 'T'),
1072        const_HELP = mk_const4('H', 'E', 'L', 'P'),
1073        const_LIST = mk_const4('L', 'I', 'S', 'T'),
1074        const_MDTM = mk_const4('M', 'D', 'T', 'M'),
1075        const_MKD  = mk_const3('M', 'K', 'D'),
1076        const_MODE = mk_const4('M', 'O', 'D', 'E'),
1077        const_NLST = mk_const4('N', 'L', 'S', 'T'),
1078        const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1079        const_PASS = mk_const4('P', 'A', 'S', 'S'),
1080        const_PASV = mk_const4('P', 'A', 'S', 'V'),
1081        const_PORT = mk_const4('P', 'O', 'R', 'T'),
1082        const_PWD  = mk_const3('P', 'W', 'D'),
1083        const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1084        const_REST = mk_const4('R', 'E', 'S', 'T'),
1085        const_RETR = mk_const4('R', 'E', 'T', 'R'),
1086        const_RMD  = mk_const3('R', 'M', 'D'),
1087        const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1088        const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1089        const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1090        const_STAT = mk_const4('S', 'T', 'A', 'T'),
1091        const_STOR = mk_const4('S', 'T', 'O', 'R'),
1092        const_STOU = mk_const4('S', 'T', 'O', 'U'),
1093        const_STRU = mk_const4('S', 'T', 'R', 'U'),
1094        const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1095        const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1096        const_USER = mk_const4('U', 'S', 'E', 'R'),
1097
1098#if !BB_MMU
1099        OPT_l = (1 << 0),
1100        OPT_1 = (1 << 1),
1101#endif
1102        OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
1103        OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
1104        OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
1105};
1106
1107int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1108#if !BB_MMU
1109int ftpd_main(int argc, char **argv)
1110#else
1111int ftpd_main(int argc UNUSED_PARAM, char **argv)
1112#endif
1113{
1114        unsigned abs_timeout;
1115        unsigned verbose_S;
1116        smallint opts;
1117
1118        INIT_G();
1119
1120        abs_timeout = 1 * 60 * 60;
1121        verbose_S = 0;
1122        G.timeout = 2 * 60;
1123        opt_complementary = "t+:T+:vv:SS";
1124#if BB_MMU
1125        opts = getopt32(argv,   "vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1126#else
1127        opts = getopt32(argv, "l1vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1128        if (opts & (OPT_l|OPT_1)) {
1129                /* Our secret backdoor to ls */
1130/* TODO: pass -n? It prevents user/group resolution, whicj may not work in chroot anyway */
1131/* TODO: pass -A? It shows dot files */
1132/* TODO: pass --group-directories-first? would be nice, but ls don't do that yet */
1133                xchdir(argv[2]);
1134                argv[2] = (char*)"--";
1135                /* memset(&G, 0, sizeof(G)); - ls_main does it */
1136                return ls_main(argc, argv);
1137        }
1138#endif
1139        if (G.verbose < verbose_S)
1140                G.verbose = verbose_S;
1141        if (abs_timeout | G.timeout) {
1142                if (abs_timeout == 0)
1143                        abs_timeout = INT_MAX;
1144                G.end_time = monotonic_sec() + abs_timeout;
1145                if (G.timeout > abs_timeout)
1146                        G.timeout = abs_timeout;
1147        }
1148        strcpy(G.msg_ok  + 4, MSG_OK );
1149        strcpy(G.msg_err + 4, MSG_ERR);
1150
1151        G.local_addr = get_sock_lsa(STDIN_FILENO);
1152        if (!G.local_addr) {
1153                /* This is confusing:
1154                 * bb_error_msg_and_die("stdin is not a socket");
1155                 * Better: */
1156                bb_show_usage();
1157                /* Help text says that ftpd must be used as inetd service,
1158                 * which is by far the most usual cause of get_sock_lsa
1159                 * failure */
1160        }
1161
1162        if (!(opts & OPT_v))
1163                logmode = LOGMODE_NONE;
1164        if (opts & OPT_S) {
1165                /* LOG_NDELAY is needed since we may chroot later */
1166                openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1167                logmode |= LOGMODE_SYSLOG;
1168        }
1169        if (logmode)
1170                applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1171
1172#if !BB_MMU
1173        G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1174        close_on_exec_on(G.root_fd);
1175#endif
1176
1177        if (argv[optind]) {
1178                xchdir(argv[optind]);
1179                chroot(".");
1180        }
1181
1182        //umask(077); - admin can set umask before starting us
1183
1184        /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
1185        signal(SIGPIPE, SIG_IGN);
1186
1187        /* Set up options on the command socket (do we need these all? why?) */
1188        setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1189        setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
1190        /* Telnet protocol over command link may send "urgent" data,
1191         * we prefer it to be received in the "normal" data stream: */
1192        setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1193
1194        WRITE_OK(FTP_GREET);
1195        signal(SIGALRM, timeout_handler);
1196
1197#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
1198        {
1199                smallint user_was_specified = 0;
1200                while (1) {
1201                        uint32_t cmdval = cmdio_get_cmd_and_arg();
1202
1203                        if (cmdval == const_USER) {
1204                                if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
1205                                        cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
1206                                else {
1207                                        user_was_specified = 1;
1208                                        cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
1209                                }
1210                        } else if (cmdval == const_PASS) {
1211                                if (user_was_specified)
1212                                        break;
1213                                cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
1214                        } else if (cmdval == const_QUIT) {
1215                                WRITE_OK(FTP_GOODBYE);
1216                                return 0;
1217                        } else {
1218                                cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1219                        }
1220                }
1221        }
1222        WRITE_OK(FTP_LOGINOK);
1223#endif
1224
1225        /* RFC-959 Section 5.1
1226         * The following commands and options MUST be supported by every
1227         * server-FTP and user-FTP, except in cases where the underlying
1228         * file system or operating system does not allow or support
1229         * a particular command.
1230         * Type: ASCII Non-print, IMAGE, LOCAL 8
1231         * Mode: Stream
1232         * Structure: File, Record*
1233         * (Record structure is REQUIRED only for hosts whose file
1234         *  systems support record structure).
1235         * Commands:
1236         * USER, PASS, ACCT, [bbox: ACCT not supported]
1237         * PORT, PASV,
1238         * TYPE, MODE, STRU,
1239         * RETR, STOR, APPE,
1240         * RNFR, RNTO, DELE,
1241         * CWD,  CDUP, RMD,  MKD,  PWD,
1242         * LIST, NLST,
1243         * SYST, STAT,
1244         * HELP, NOOP, QUIT.
1245         */
1246        /* ACCOUNT (ACCT)
1247         * "The argument field is a Telnet string identifying the user's account.
1248         * The command is not necessarily related to the USER command, as some
1249         * sites may require an account for login and others only for specific
1250         * access, such as storing files. In the latter case the command may
1251         * arrive at any time.
1252         * There are reply codes to differentiate these cases for the automation:
1253         * when account information is required for login, the response to
1254         * a successful PASSword command is reply code 332. On the other hand,
1255         * if account information is NOT required for login, the reply to
1256         * a successful PASSword command is 230; and if the account information
1257         * is needed for a command issued later in the dialogue, the server
1258         * should return a 332 or 532 reply depending on whether it stores
1259         * (pending receipt of the ACCounT command) or discards the command,
1260         * respectively."
1261         */
1262
1263        while (1) {
1264                uint32_t cmdval = cmdio_get_cmd_and_arg();
1265
1266                if (cmdval == const_QUIT) {
1267                        WRITE_OK(FTP_GOODBYE);
1268                        return 0;
1269                }
1270                else if (cmdval == const_USER)
1271                        /* This would mean "ok, now give me PASS". */
1272                        /*WRITE_OK(FTP_GIVEPWORD);*/
1273                        /* vsftpd can be configured to not require that,
1274                         * and this also saves one roundtrip:
1275                         */
1276                        WRITE_OK(FTP_LOGINOK);
1277                else if (cmdval == const_PASS)
1278                        WRITE_OK(FTP_LOGINOK);
1279                else if (cmdval == const_NOOP)
1280                        WRITE_OK(FTP_NOOPOK);
1281                else if (cmdval == const_TYPE)
1282                        WRITE_OK(FTP_TYPEOK);
1283                else if (cmdval == const_STRU)
1284                        WRITE_OK(FTP_STRUOK);
1285                else if (cmdval == const_MODE)
1286                        WRITE_OK(FTP_MODEOK);
1287                else if (cmdval == const_ALLO)
1288                        WRITE_OK(FTP_ALLOOK);
1289                else if (cmdval == const_SYST)
1290                        cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1291                else if (cmdval == const_PWD)
1292                        handle_pwd();
1293                else if (cmdval == const_CWD)
1294                        handle_cwd();
1295                else if (cmdval == const_CDUP) /* cd .. */
1296                        handle_cdup();
1297                /* HELP is nearly useless, but we can reuse FEAT for it */
1298                /* lftp uses FEAT */
1299                else if (cmdval == const_HELP || cmdval == const_FEAT)
1300                        handle_feat(cmdval == const_HELP
1301                                        ? STRNUM32(FTP_HELP)
1302                                        : STRNUM32(FTP_STATOK)
1303                        );
1304                else if (cmdval == const_LIST) /* ls -l */
1305                        handle_list();
1306                else if (cmdval == const_NLST) /* "name list", bare ls */
1307                        handle_nlst();
1308                /* SIZE is crucial for wget's download indicator etc */
1309                /* Mozilla, lftp use MDTM (presumably for caching) */
1310                else if (cmdval == const_SIZE || cmdval == const_MDTM)
1311                        handle_size_or_mdtm(cmdval == const_SIZE);
1312                else if (cmdval == const_STAT) {
1313                        if (G.ftp_arg == NULL)
1314                                handle_stat();
1315                        else
1316                                handle_stat_file();
1317                }
1318                else if (cmdval == const_PASV)
1319                        handle_pasv();
1320                else if (cmdval == const_EPSV)
1321                        handle_epsv();
1322                else if (cmdval == const_RETR)
1323                        handle_retr();
1324                else if (cmdval == const_PORT)
1325                        handle_port();
1326                else if (cmdval == const_REST)
1327                        handle_rest();
1328#if ENABLE_FEATURE_FTP_WRITE
1329                else if (opts & OPT_w) {
1330                        if (cmdval == const_STOR)
1331                                handle_stor();
1332                        else if (cmdval == const_MKD)
1333                                handle_mkd();
1334                        else if (cmdval == const_RMD)
1335                                handle_rmd();
1336                        else if (cmdval == const_DELE)
1337                                handle_dele();
1338                        else if (cmdval == const_RNFR) /* "rename from" */
1339                                handle_rnfr();
1340                        else if (cmdval == const_RNTO) /* "rename to" */
1341                                handle_rnto();
1342                        else if (cmdval == const_APPE)
1343                                handle_appe();
1344                        else if (cmdval == const_STOU) /* "store unique" */
1345                                handle_stou();
1346                        else
1347                                goto bad_cmd;
1348                }
1349#endif
1350#if 0
1351                else if (cmdval == const_STOR
1352                 || cmdval == const_MKD
1353                 || cmdval == const_RMD
1354                 || cmdval == const_DELE
1355                 || cmdval == const_RNFR
1356                 || cmdval == const_RNTO
1357                 || cmdval == const_APPE
1358                 || cmdval == const_STOU
1359                ) {
1360                        cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1361                }
1362#endif
1363                else {
1364                        /* Which unsupported commands were seen in the wild?
1365                         * (doesn't necessarily mean "we must support them")
1366                         * foo 1.2.3: XXXX - comment
1367                         */
1368#if ENABLE_FEATURE_FTP_WRITE
1369 bad_cmd:
1370#endif
1371                        cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
1372                }
1373        }
1374}
1375