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