Makefile.flags: restrict Wno-constant-logical-operand and Wno-string-plus-int options...
[oweals/busybox.git] / networking / ftpd.c
index 2d2a3a44cfe2dc3732ffd7e8860b5630981e4aaf..6ca231c90b203d4ee54a6fb7fe46c5188b61441b 100644 (file)
  *
  * You have to run this daemon via inetd.
  */
+//config:config FTPD
+//config:      bool "ftpd (30 kb)"
+//config:      default y
+//config:      help
+//config:      Simple FTP daemon. You have to run it via inetd.
+//config:
+//config:config FEATURE_FTPD_WRITE
+//config:      bool "Enable -w (upload commands)"
+//config:      default y
+//config:      depends on FTPD
+//config:      help
+//config:      Enable -w option. "ftpd -w" will accept upload commands
+//config:      such as STOR, STOU, APPE, DELE, MKD, RMD, rename commands.
+//config:
+//config:config FEATURE_FTPD_ACCEPT_BROKEN_LIST
+//config:      bool "Enable workaround for RFC-violating clients"
+//config:      default y
+//config:      depends on FTPD
+//config:      help
+//config:      Some ftp clients (among them KDE's Konqueror) issue illegal
+//config:      "LIST -l" requests. This option works around such problems.
+//config:      It might prevent you from listing files starting with "-" and
+//config:      it increases the code size by ~40 bytes.
+//config:      Most other ftp servers seem to behave similar to this.
+//config:
+//config:config FEATURE_FTPD_AUTHENTICATION
+//config:      bool "Enable authentication"
+//config:      default y
+//config:      depends on FTPD
+//config:      help
+//config:      Require login, and change to logged in user's UID:GID before
+//config:      accessing any files. Option "-a USER" allows "anonymous"
+//config:      logins (treats them as if USER logged in).
+//config:
+//config:      If this option is not selected, ftpd runs with the rights
+//config:      of the user it was started under, and does not require login.
+//config:      Take care to not launch it under root.
+
+//applet:IF_FTPD(APPLET(ftpd, BB_DIR_USR_SBIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_FTPD) += ftpd.o
 
 //usage:#define ftpd_trivial_usage
-//usage:       "[-wvS] [-t N] [-T N] [DIR]"
+//usage:       "[-wvS]"IF_FEATURE_FTPD_AUTHENTICATION(" [-a USER]")" [-t N] [-T N] [DIR]"
 //usage:#define ftpd_full_usage "\n\n"
-//usage:       "Anonymous FTP server\n"
-//usage:       "\n"
-//usage:       "ftpd should be used as an inetd service.\n"
-//usage:       "ftpd's line for inetd.conf:\n"
+//usage:       IF_NOT_FEATURE_FTPD_AUTHENTICATION(
+//usage:       "Anonymous FTP server. Client access occurs under ftpd's UID.\n"
+//usage:       )
+//usage:       IF_FEATURE_FTPD_AUTHENTICATION(
+//usage:       "FTP server. "
+//usage:       )
+//usage:       "Chroots to DIR, if this fails (run by non-root), cds to it.\n"
+//usage:       "Should be used as inetd service, inetd.conf line:\n"
 //usage:       "       21 stream tcp nowait root ftpd ftpd /files/to/serve\n"
-//usage:       "It also can be ran from tcpsvd:\n"
-//usage:       "       tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve\n"
+//usage:       "Can be run from tcpsvd:\n"
+//usage:       "       tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve"
+//usage:     "\n"
 //usage:     "\n       -w      Allow upload"
+//usage:       IF_FEATURE_FTPD_AUTHENTICATION(
+//usage:     "\n       -A      No login required, client access occurs under ftpd's UID"
+//
+// if !FTPD_AUTHENTICATION, -A is accepted too, but not shown in --help
+// since it's the only supported mode in that configuration
+//
+//usage:     "\n       -a USER Enable 'anonymous' login and map it to USER"
+//usage:       )
 //usage:     "\n       -v      Log errors to stderr. -vv: verbose log"
 //usage:     "\n       -S      Log errors to syslog. -SS: verbose log"
-//usage:     "\n       -t,-T   Idle and absolute timeouts"
-//usage:     "\n       DIR     Change root to this directory"
+//usage:     "\n       -t,-T N Idle and absolute timeout"
 
 #include "libbb.h"
+#include "common_bufsiz.h"
 #include <syslog.h>
 #include <netinet/tcp.h>
 
@@ -116,15 +170,20 @@ struct globals {
        len_and_sockaddr *port_addr;
        char *ftp_cmd;
        char *ftp_arg;
-#if ENABLE_FEATURE_FTP_WRITE
+#if ENABLE_FEATURE_FTPD_WRITE
        char *rnfr_filename;
 #endif
        /* We need these aligned to uint32_t */
        char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
        char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
 } FIX_ALIASING;
-#define G (*(struct globals*)&bb_common_bufsiz1)
+#define G (*ptr_to_globals)
+/* ^^^ about 75 bytes smaller code than this: */
+//#define G (*(struct globals*)bb_common_bufsiz1)
 #define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       /*setup_common_bufsiz();*/ \
+       \
        /* Moved to main */ \
        /*strcpy(G.msg_ok  + 4, MSG_OK );*/ \
        /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
@@ -209,7 +268,7 @@ cmdio_write(uint32_t status_str, const char *str)
 static void
 cmdio_write_ok(unsigned status)
 {
-       *(uint32_t *) G.msg_ok = status;
+       *(bb__aliased_uint32_t *) G.msg_ok = status;
        xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
        if (G.verbose > 1)
                verbose_log(G.msg_ok);
@@ -220,7 +279,7 @@ cmdio_write_ok(unsigned status)
 static void
 cmdio_write_error(unsigned status)
 {
-       *(uint32_t *) G.msg_err = status;
+       *(bb__aliased_uint32_t *) G.msg_err = status;
        xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
        if (G.verbose > 0)
                verbose_log(G.msg_err);
@@ -377,7 +436,7 @@ ftpdataio_get_pasv_fd(void)
                return remote_fd;
        }
 
-       setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+       setsockopt_keepalive(remote_fd);
        return remote_fd;
 }
 
@@ -550,7 +609,7 @@ static void
 handle_rest(void)
 {
        /* When ftp_arg == NULL simply restart from beginning */
-       G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
+       G.restart_pos = G.ftp_arg ? XATOOFF(G.ftp_arg) : 0;
        WRITE_OK(FTP_RESTOK);
 }
 
@@ -622,7 +681,7 @@ popen_ls(const char *opt)
        pid_t pid;
 
        argv[0] = "ftpd";
-       argv[1] = opt; /* "-l" or "-1" */
+       argv[1] = opt; /* "-lA" or "-1A" */
        argv[2] = "--";
        argv[3] = G.ftp_arg;
        argv[4] = NULL;
@@ -659,7 +718,7 @@ popen_ls(const char *opt)
                dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
 #if BB_MMU
                /* memset(&G, 0, sizeof(G)); - ls_main does it */
-               exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
+               exit(ls_main(/*argc_unused*/ 0, (char**) argv));
 #else
                cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
                /* On NOMMU, we want to execute a child - copy of ourself
@@ -699,7 +758,7 @@ handle_dir_common(int opts)
        if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
                return; /* port_or_pasv_was_seen emitted error response */
 
-       ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
+       ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
        ls_fp = xfdopen_for_read(ls_fd);
 /* FIXME: filenames with embedded newlines are mishandled */
 
@@ -829,7 +888,7 @@ handle_size_or_mdtm(int need_size)
 
 /* Upload commands */
 
-#if ENABLE_FEATURE_FTP_WRITE
+#if ENABLE_FEATURE_FTPD_WRITE
 static void
 handle_mkd(void)
 {
@@ -972,7 +1031,7 @@ handle_stou(void)
        G.restart_pos = 0;
        handle_upload_common(0, 1);
 }
-#endif /* ENABLE_FEATURE_FTP_WRITE */
+#endif /* ENABLE_FEATURE_FTPD_WRITE */
 
 static uint32_t
 cmdio_get_cmd_and_arg(void)
@@ -1103,18 +1162,20 @@ enum {
        OPT_l = (1 << 0),
        OPT_1 = (1 << 1),
 #endif
-       OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
-       OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
-       OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
+       BIT_A =        (!BB_MMU) * 2,
+       OPT_A = (1 << (BIT_A + 0)),
+       OPT_v = (1 << (BIT_A + 1)),
+       OPT_S = (1 << (BIT_A + 2)),
+       OPT_w = (1 << (BIT_A + 3)) * ENABLE_FEATURE_FTPD_WRITE,
 };
 
 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
-#if !BB_MMU
-int ftpd_main(int argc, char **argv)
-#else
 int ftpd_main(int argc UNUSED_PARAM, char **argv)
-#endif
 {
+#if ENABLE_FEATURE_FTPD_AUTHENTICATION
+       struct passwd *pw = NULL;
+       char *anon_opt = NULL;
+#endif
        unsigned abs_timeout;
        unsigned verbose_S;
        smallint opts;
@@ -1124,19 +1185,27 @@ int ftpd_main(int argc UNUSED_PARAM, char **argv)
        abs_timeout = 1 * 60 * 60;
        verbose_S = 0;
        G.timeout = 2 * 60;
-       opt_complementary = "t+:T+:vv:SS";
 #if BB_MMU
-       opts = getopt32(argv,   "vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
+       opts = getopt32(argv, "^"   "AvS" IF_FEATURE_FTPD_WRITE("w")
+               "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:")
+               "\0" "vv:SS",
+               &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
+               &G.verbose, &verbose_S
+       );
 #else
-       opts = getopt32(argv, "l1vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
+       opts = getopt32(argv, "^" "l1AvS" IF_FEATURE_FTPD_WRITE("w")
+               "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:")
+               "\0" "vv:SS",
+               &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
+               &G.verbose, &verbose_S
+       );
        if (opts & (OPT_l|OPT_1)) {
-               /* Our secret backdoor to ls */
-/* TODO: pass -A? It shows dot files */
-/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
+               /* Our secret backdoor to ls: see popen_ls() */
                if (fchdir(3) != 0)
                        _exit(127);
                /* memset(&G, 0, sizeof(G)); - ls_main does it */
-               return ls_main(argc, argv);
+               /* NB: in this case -A has a different meaning: like "ls -A" */
+               return ls_main(/*argc_unused*/ 0, argv);
        }
 #endif
        if (G.verbose < verbose_S)
@@ -1172,59 +1241,87 @@ int ftpd_main(int argc UNUSED_PARAM, char **argv)
        if (logmode)
                applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
 
-#if !BB_MMU
-       G.root_fd = -1;
-#endif
-       argv += optind;
-       if (argv[0]) {
-#if !BB_MMU
-               G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
-               close_on_exec_on(G.root_fd);
-#endif
-               xchroot(argv[0]);
-       }
-
        //umask(077); - admin can set umask before starting us
 
-       /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
-       signal(SIGPIPE, SIG_IGN);
+       /* Signals */
+       bb_signals(0
+               /* We'll always take EPIPE rather than a rude signal, thanks */
+               + (1 << SIGPIPE)
+               /* LIST command spawns chilren. Prevent zombies */
+               + (1 << SIGCHLD)
+               , SIG_IGN);
 
        /* Set up options on the command socket (do we need these all? why?) */
-       setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
-       setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+       setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
+       setsockopt_keepalive(STDIN_FILENO);
        /* Telnet protocol over command link may send "urgent" data,
         * we prefer it to be received in the "normal" data stream: */
-       setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
+       setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
 
        WRITE_OK(FTP_GREET);
        signal(SIGALRM, timeout_handler);
 
-#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
-       {
-               smallint user_was_specified = 0;
+#if ENABLE_FEATURE_FTPD_AUTHENTICATION
+       if (!(opts & OPT_A)) {
                while (1) {
                        uint32_t cmdval = cmdio_get_cmd_and_arg();
-
                        if (cmdval == const_USER) {
-                               if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
-                                       cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
-                               else {
-                                       user_was_specified = 1;
-                                       cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
+                               if (anon_opt && strcmp(G.ftp_arg, "anonymous") == 0) {
+                                       pw = getpwnam(anon_opt);
+                                       if (pw)
+                                               break; /* does not even ask for password */
                                }
+                               pw = getpwnam(G.ftp_arg);
+                               cmdio_write_raw(STR(FTP_GIVEPWORD)" Specify password\r\n");
                        } else if (cmdval == const_PASS) {
-                               if (user_was_specified)
-                                       break;
-                               cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
+                               if (check_password(pw, G.ftp_arg) > 0) {
+                                       break;  /* login success */
+                               }
+                               cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
+                               pw = NULL;
                        } else if (cmdval == const_QUIT) {
                                WRITE_OK(FTP_GOODBYE);
                                return 0;
                        } else {
-                               cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
+                               cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER+PASS\r\n");
                        }
                }
+               WRITE_OK(FTP_LOGINOK);
        }
-       WRITE_OK(FTP_LOGINOK);
+#endif
+
+       /* Do this after auth, else /etc/passwd is not accessible */
+#if !BB_MMU
+       G.root_fd = -1;
+#endif
+       argv += optind;
+       if (argv[0]) {
+               const char *basedir = argv[0];
+#if !BB_MMU
+               G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
+               close_on_exec_on(G.root_fd);
+#endif
+               if (chroot(basedir) == 0)
+                       basedir = "/";
+#if !BB_MMU
+               else {
+                       close(G.root_fd);
+                       G.root_fd = -1;
+               }
+#endif
+               /*
+                * If chroot failed, assume that we aren't root,
+                * and at least chdir to the specified DIR
+                * (older versions were dying with error message).
+                * If chroot worked, move current dir to new "/":
+                */
+               xchdir(basedir);
+       }
+
+#if ENABLE_FEATURE_FTPD_AUTHENTICATION
+       if (pw)
+               change_identity(pw);
+       /* else: -A is in effect */
 #endif
 
        /* RFC-959 Section 5.1
@@ -1330,7 +1427,7 @@ int ftpd_main(int argc UNUSED_PARAM, char **argv)
                        handle_port();
                else if (cmdval == const_REST)
                        handle_rest();
-#if ENABLE_FEATURE_FTP_WRITE
+#if ENABLE_FEATURE_FTPD_WRITE
                else if (opts & OPT_w) {
                        if (cmdval == const_STOR)
                                handle_stor();
@@ -1370,7 +1467,7 @@ int ftpd_main(int argc UNUSED_PARAM, char **argv)
                         * (doesn't necessarily mean "we must support them")
                         * foo 1.2.3: XXXX - comment
                         */
-#if ENABLE_FEATURE_FTP_WRITE
+#if ENABLE_FEATURE_FTPD_WRITE
  bad_cmd:
 #endif
                        cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");