X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=networking%2Fftpd.c;h=8af5acac296dde5217cc40955f1284e6be8fdfa1;hb=9c143ce52da11ec3d21a3491c3749841d3dc10f0;hp=b295ddf4f4e38fa98732d19f9aebb63d0d24ca0d;hpb=1432cb4bd9daf304111dd19e0011f8756c32e327;p=oweals%2Fbusybox.git diff --git a/networking/ftpd.c b/networking/ftpd.c index b295ddf4f..8af5acac2 100644 --- a/networking/ftpd.c +++ b/networking/ftpd.c @@ -4,15 +4,79 @@ * * Author: Adam Tkac * - * Licensed under GPLv2, see file LICENSE in this tarball for details. + * Licensed under GPLv2, see file LICENSE in this source tree. * * Only subset of FTP protocol is implemented but vast majority of clients * should not have any problem. * * 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]"IF_FEATURE_FTPD_AUTHENTICATION(" [-a USER]")" [-t N] [-T N] [DIR]" +//usage:#define ftpd_full_usage "\n\n" +//usage: IF_NOT_FEATURE_FTPD_AUTHENTICATION( +//usage: "Anonymous FTP server. Accesses by clients occur 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: "Can be run from tcpsvd:\n" +//usage: " tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve\n" +//usage: "\n -w Allow upload" +//usage: IF_FEATURE_FTPD_AUTHENTICATION( +//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 N Idle and absolute timeout" #include "libbb.h" +#include "common_bufsiz.h" #include #include @@ -87,7 +151,9 @@ enum { struct globals { int pasv_listen_fd; - int proc_self_fd; +#if !BB_MMU + int root_fd; +#endif int local_file_fd; unsigned end_time; unsigned timeout; @@ -98,15 +164,16 @@ 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]; -}; -#define G (*(struct globals*)&bb_common_bufsiz1) +} FIX_ALIASING; +#define G (*(struct globals*)bb_common_bufsiz1) #define INIT_G() do { \ + setup_common_bufsiz(); \ /* Moved to main */ \ /*strcpy(G.msg_ok + 4, MSG_OK );*/ \ /*strcpy(G.msg_err + 4, MSG_ERR);*/ \ @@ -204,7 +271,7 @@ cmdio_write_error(unsigned status) { *(uint32_t *) G.msg_err = status; xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1); - if (G.verbose > 1) + if (G.verbose > 0) verbose_log(G.msg_err); } #define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a)) @@ -282,7 +349,7 @@ handle_cdup(void) static void handle_stat(void) { - cmdio_write_raw(STR(FTP_STATOK)"-FTP server status:\r\n" + cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n" " TYPE: BINARY\r\n" STR(FTP_STATOK)" Ok\r\n"); } @@ -359,7 +426,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; } @@ -395,7 +462,7 @@ static int port_or_pasv_was_seen(void) { if (!pasv_active() && !port_active()) { - cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT or PASV first\r\n"); + cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n"); return 0; } @@ -414,7 +481,7 @@ bind_for_passive_mode(void) G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0); setsockopt_reuseaddr(fd); - set_nport(G.local_addr, 0); + set_nport(&G.local_addr->u.sa, 0); xbind(fd, &G.local_addr->u.sa, G.local_addr->len); xlisten(fd, 1); getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len); @@ -439,7 +506,7 @@ handle_pasv(void) addr = xstrdup("0.0.0.0"); replace_char(addr, '.', ','); - response = xasprintf(STR(FTP_PASVOK)" Entering Passive Mode (%s,%u,%u)\r\n", + response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n", addr, (int)(port >> 8), (int)(port & 255)); free(addr); cmdio_write_raw(response); @@ -454,26 +521,11 @@ handle_epsv(void) char *response; port = bind_for_passive_mode(); - response = xasprintf(STR(FTP_EPSVOK)" EPSV Ok (|||%u|)\r\n", port); + response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port); cmdio_write_raw(response); free(response); } -/* libbb candidate */ -static -len_and_sockaddr* get_peer_lsa(int fd) -{ - len_and_sockaddr *lsa; - socklen_t len = 0; - - if (getpeername(fd, NULL, &len) != 0) - return NULL; - lsa = xzalloc(LSA_LEN_SIZE + len); - lsa->len = len; - getpeername(fd, &lsa->u.sa, &lsa->len); - return lsa; -} - static void handle_port(void) { @@ -538,7 +590,7 @@ handle_port(void) G.port_addr = xdotted2sockaddr(raw, port); #else G.port_addr = get_peer_lsa(STDIN_FILENO); - set_nport(G.port_addr, htons(port)); + set_nport(&G.port_addr->u.sa, htons(port)); #endif WRITE_OK(FTP_PORTOK); } @@ -547,7 +599,7 @@ static void handle_rest(void) { /* When ftp_arg == NULL simply restart from beginning */ - G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0; + G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0; WRITE_OK(FTP_RESTOK); } @@ -590,7 +642,7 @@ handle_retr(void) xlseek(local_file_fd, offset, SEEK_SET); response = xasprintf( - " Opening BINARY mode data connection for %s (%"OFF_FMT"u bytes)", + " Opening BINARY connection for %s (%"OFF_FMT"u bytes)", G.ftp_arg, statbuf.st_size); remote_fd = get_remote_transfer_fd(response); free(response); @@ -614,39 +666,70 @@ handle_retr(void) static int popen_ls(const char *opt) { - char *cwd; - const char *argv[5] = { "ftpd", opt, NULL, G.ftp_arg, NULL }; + const char *argv[5]; struct fd_pair outfd; pid_t pid; - cwd = xrealloc_getcwd_or_warn(NULL); + argv[0] = "ftpd"; + argv[1] = opt; /* "-lA" or "-1A" */ + argv[2] = "--"; + argv[3] = G.ftp_arg; + argv[4] = NULL; + + /* Improve compatibility with non-RFC conforming FTP clients + * which send e.g. "LIST -l", "LIST -la", "LIST -aL". + * See https://bugs.kde.org/show_bug.cgi?id=195578 */ + if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST + && G.ftp_arg && G.ftp_arg[0] == '-' + ) { + const char *tmp = strchr(G.ftp_arg, ' '); + if (tmp) /* skip the space */ + tmp++; + argv[3] = tmp; + } + xpiped_pair(outfd); - /*fflush(NULL); - so far we dont use stdio on output */ - pid = vfork(); - switch (pid) { - case -1: /* failure */ - bb_perror_msg_and_die("vfork"); - case 0: /* child */ - /* NB: close _first_, then move fds! */ + /*fflush_all(); - so far we dont use stdio on output */ + pid = BB_MMU ? xfork() : xvfork(); + if (pid == 0) { +#if !BB_MMU + int cur_fd; +#endif + /* child */ + /* NB: close _first_, then move fd! */ close(outfd.rd); xmove_fd(outfd.wr, STDOUT_FILENO); + /* Opening /dev/null in chroot is hard. + * Just making sure STDIN_FILENO is opened + * to something harmless. Paranoia, + * ls won't read it anyway */ close(STDIN_FILENO); - /* xopen("/dev/null", O_RDONLY); - chroot may lack it! */ - if (fchdir(G.proc_self_fd) == 0) { - close(G.proc_self_fd); - argv[2] = cwd; - /* ftpd ls helper chdirs to argv[2], - * preventing peer from seeing /proc/self - */ - execv("exe", (char**) argv); + dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */ +#if BB_MMU + /* memset(&G, 0, sizeof(G)); - ls_main does it */ + 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 + * in order to unblock parent after vfork. + * In chroot we usually can't re-exec. Thus we escape + * out of the chroot back to original root. + */ + if (G.root_fd >= 0) { + if (fchdir(G.root_fd) != 0 || chroot(".") != 0) + _exit(127); + /*close(G.root_fd); - close_on_exec_on() took care of this */ } + /* Child expects directory to list on fd #3 */ + xmove_fd(cur_fd, 3); + execv(bb_busybox_exec_path, (char**) argv); _exit(127); +#endif } /* parent */ close(outfd.wr); - free(cwd); return outfd.rd; } @@ -665,39 +748,43 @@ handle_dir_common(int opts) if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen()) return; /* port_or_pasv_was_seen emitted error response */ - /* -n prevents user/groupname display, - * which can be problematic in chroot */ - ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1"); - ls_fp = fdopen(ls_fd, "r"); - if (!ls_fp) /* never happens. paranoia */ - bb_perror_msg_and_die("fdopen"); + ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A"); + ls_fp = xfdopen_for_read(ls_fd); +/* FIXME: filenames with embedded newlines are mishandled */ if (opts & USE_CTRL_CONN) { /* STAT */ - cmdio_write_raw(STR(FTP_STATFILE_OK)"-Status follows:\r\n"); + cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n"); while (1) { - line = xmalloc_fgetline(ls_fp); + line = xmalloc_fgetline(ls_fp); if (!line) break; - cmdio_write(0, line); /* hack: 0 results in no status at all */ + /* Hack: 0 results in no status at all */ + /* Note: it's ok that we don't prepend space, + * ftp.kernel.org doesn't do that too */ + cmdio_write(0, line); free(line); } WRITE_OK(FTP_STATFILE_OK); } else { /* LIST/NLST [] */ - int remote_fd = get_remote_transfer_fd(" Here comes the directory listing"); + int remote_fd = get_remote_transfer_fd(" Directory listing"); if (remote_fd >= 0) { while (1) { - line = xmalloc_fgetline(ls_fp); + unsigned len; + + line = xmalloc_fgets(ls_fp); if (!line) break; /* I've seen clients complaining when they * are fed with ls output with bare '\n'. - * Pity... that would be much simpler. + * Replace trailing "\n\0" with "\r\n". */ -/* TODO: need to s/LF/NUL/g here */ - xwrite_str(remote_fd, line); - xwrite(remote_fd, "\r\n", 2); + len = strlen(line); + if (len != 0) /* paranoia check */ + line[len - 1] = '\r'; + line[len] = '\n'; + xwrite(remote_fd, line, len + 1); free(line); } } @@ -780,7 +867,7 @@ handle_size_or_mdtm(int need_size) gmtime_r(&statbuf.st_mtime, &broken_out); sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n", broken_out.tm_year + 1900, - broken_out.tm_mon, + broken_out.tm_mon + 1, broken_out.tm_mday, broken_out.tm_hour, broken_out.tm_min, @@ -791,7 +878,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) { @@ -837,7 +924,7 @@ handle_rnto(void) /* If we didn't get a RNFR, throw a wobbly */ if (G.rnfr_filename == NULL || G.ftp_arg == NULL) { - cmdio_write_raw(STR(FTP_NEEDRNFR)" RNFR required first\r\n"); + cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n"); return; } @@ -886,6 +973,7 @@ handle_upload_common(int is_append, int is_unique) || fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode) ) { + free(tempname); WRITE_ERR(FTP_UPLOADFAIL); if (local_file_fd >= 0) goto close_local_and_bail; @@ -933,27 +1021,79 @@ 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) { - size_t len; + int len; uint32_t cmdval; char *cmd; alarm(G.timeout); free(G.ftp_cmd); - len = 8 * 1024; /* Paranoia. Peer may send 1 gigabyte long cmd... */ - G.ftp_cmd = cmd = xmalloc_reads(STDIN_FILENO, NULL, &len); - if (!cmd) - exit(0); + { + /* Paranoia. Peer may send 1 gigabyte long cmd... */ + /* Using separate len_on_stk instead of len optimizes + * code size (allows len to be in CPU register) */ + size_t len_on_stk = 8 * 1024; + G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk); + if (!cmd) + exit(0); + len = len_on_stk; + } - /* Trailing '\n' is already stripped, strip '\r' */ - len = strlen(cmd) - 1; - if ((ssize_t)len >= 0 && cmd[len] == '\r') - cmd[len--] = '\0'; + /* De-escape telnet: 0xff,0xff => 0xff */ + /* RFC959 says that ABOR, STAT, QUIT may be sent even during + * data transfer, and may be preceded by telnet's "Interrupt Process" + * code (two-byte sequence 255,244) and then by telnet "Synch" code + * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB) + * and may generate SIGURG on our side. See RFC854). + * So far we don't support that (may install SIGURG handler if we'd want to), + * but we need to at least remove 255,xxx pairs. lftp sends those. */ + /* Then de-escape FTP: NUL => '\n' */ + /* Testing for \xff: + * Create file named '\xff': echo Hello >`echo -ne "\xff"` + * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"` + * (need "\xff\xff" until ftpget applet is fixed to do escaping :) + * Testing for embedded LF: + * LF_HERE=`echo -ne "LF\nHERE"` + * echo Hello >"$LF_HERE" + * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE" + */ + { + int dst, src; + + /* Strip "\r\n" if it is there */ + if (len != 0 && cmd[len - 1] == '\n') { + len--; + if (len != 0 && cmd[len - 1] == '\r') + len--; + cmd[len] = '\0'; + } + src = strchrnul(cmd, 0xff) - cmd; + /* 99,99% there are neither NULs nor 255s and src == len */ + if (src < len) { + dst = src; + do { + if ((unsigned char)(cmd[src]) == 255) { + src++; + /* 255,xxx - skip 255 */ + if ((unsigned char)(cmd[src]) != 255) { + /* 255,!255 - skip both */ + src++; + continue; + } + /* 255,255 - retain one 255 */ + } + /* NUL => '\n' */ + cmd[dst++] = cmd[src] ? cmd[src] : '\n'; + src++; + } while (src < len); + cmd[dst] = '\0'; + } + } if (G.verbose > 1) verbose_log(cmd); @@ -991,6 +1131,8 @@ enum { const_PASV = mk_const4('P', 'A', 'S', 'V'), const_PORT = mk_const4('P', 'O', 'R', 'T'), const_PWD = mk_const3('P', 'W', 'D'), + /* Same as PWD. Reportedly used by windows ftp client */ + const_XPWD = mk_const4('X', 'P', 'W', 'D'), const_QUIT = mk_const4('Q', 'U', 'I', 'T'), const_REST = mk_const4('R', 'E', 'S', 'T'), const_RETR = mk_const4('R', 'E', 'T', 'R'), @@ -1006,34 +1148,56 @@ enum { const_TYPE = mk_const4('T', 'Y', 'P', 'E'), const_USER = mk_const4('U', 'S', 'E', 'R'), +#if !BB_MMU OPT_l = (1 << 0), OPT_1 = (1 << 1), - OPT_v = (1 << 2), - OPT_S = (1 << 3), - OPT_w = (1 << 4), + OPT_A = (1 << 2), +#endif + OPT_v = (1 << ((!BB_MMU) * 3 + 0)), + OPT_S = (1 << ((!BB_MMU) * 3 + 1)), + OPT_w = (1 << ((!BB_MMU) * 3 + 2)) * ENABLE_FEATURE_FTPD_WRITE, }; int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; -int ftpd_main(int argc, char **argv) +int ftpd_main(int argc UNUSED_PARAM, char **argv) { +#if ENABLE_FEATURE_FTPD_AUTHENTICATION + struct passwd *pw = NULL; + char *anon_opt = NULL; +#endif unsigned abs_timeout; + unsigned verbose_S; smallint opts; INIT_G(); abs_timeout = 1 * 60 * 60; + verbose_S = 0; G.timeout = 2 * 60; - opt_complementary = "t+:T+:vv"; - opts = getopt32(argv, "l1vS" USE_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose); +#if BB_MMU + opts = getopt32(argv, "^" "vS" + 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, "^" "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 */ - memset(&G, 0, sizeof(G)); -/* TODO: pass -n too? */ -/* --group-directories-first would be nice, but ls don't do that yet */ - xchdir(argv[2]); - argv[2] = (char*)"--"; - return ls_main(argc, argv); + if (fchdir(3) != 0) + _exit(127); + /* memset(&G, 0, sizeof(G)); - ls_main does it */ + return ls_main(/*argc_unused*/ 0, argv); } +#endif + if (G.verbose < verbose_S) + G.verbose = verbose_S; if (abs_timeout | G.timeout) { if (abs_timeout == 0) abs_timeout = INT_MAX; @@ -1065,54 +1229,85 @@ int ftpd_main(int argc, char **argv) if (logmode) applet_name = xasprintf("%s[%u]", applet_name, (int)getpid()); - G.proc_self_fd = xopen("/proc/self", O_RDONLY | O_DIRECTORY); - - if (argv[optind]) { - xchdir(argv[optind]); - chroot("."); - } - //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(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &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_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; - 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"); - } - } else if (cmdval == const_PASS) { - if (user_was_specified) - break; - cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n"); - } 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"); +#if ENABLE_FEATURE_FTPD_AUTHENTICATION + while (1) { + uint32_t cmdval = cmdio_get_cmd_and_arg(); + if (cmdval == const_USER) { + 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)" Please specify password\r\n"); + } else if (cmdval == const_PASS) { + 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"); } } 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 + change_identity(pw); +#endif + /* RFC-959 Section 5.1 * The following commands and options MUST be supported by every * server-FTP and user-FTP, except in cases where the underlying @@ -1179,7 +1374,7 @@ int ftpd_main(int argc, char **argv) WRITE_OK(FTP_ALLOOK); else if (cmdval == const_SYST) cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n"); - else if (cmdval == const_PWD) + else if (cmdval == const_PWD || cmdval == const_XPWD) handle_pwd(); else if (cmdval == const_CWD) handle_cwd(); @@ -1216,7 +1411,7 @@ int ftpd_main(int argc, 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(); @@ -1234,6 +1429,8 @@ int ftpd_main(int argc, char **argv) handle_appe(); else if (cmdval == const_STOU) /* "store unique" */ handle_stou(); + else + goto bad_cmd; } #endif #if 0 @@ -1254,6 +1451,9 @@ int ftpd_main(int argc, char **argv) * (doesn't necessarily mean "we must support them") * foo 1.2.3: XXXX - comment */ +#if ENABLE_FEATURE_FTPD_WRITE + bad_cmd: +#endif cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n"); } }