X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=networking%2Fftpd.c;h=e38138c0a85b1b4d8ef0e753597ab323c5113ae9;hb=1f56e51ca1d96b70635eb1b9df1d1ab0edd98a72;hp=675324803b6fcea5a34d6eddbd99f89268234342;hpb=43bb7bba3b09f9beddb07417fa4997a599f5c6d1;p=oweals%2Fbusybox.git diff --git a/networking/ftpd.c b/networking/ftpd.c index 675324803..e38138c0a 100644 --- a/networking/ftpd.c +++ b/networking/ftpd.c @@ -4,12 +4,30 @@ * * 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. + * should not have any problem. + * + * You have to run this daemon via inetd. */ +//usage:#define ftpd_trivial_usage +//usage: "[-wvS] [-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: " 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: "\n -w Allow upload" +//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" + #include "libbb.h" #include #include @@ -62,23 +80,36 @@ /* Convert a constant to 3-digit string, packed into uint32_t */ enum { /* Shift for Nth decimal digit */ - SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN, - SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN, - SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN, + SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN, + SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN, + SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN, + /* And for 4th position (space) */ + SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN, }; #define STRNUM32(s) (uint32_t)(0 \ | (('0' + ((s) / 1 % 10)) << SHIFT0) \ | (('0' + ((s) / 10 % 10)) << SHIFT1) \ | (('0' + ((s) / 100 % 10)) << SHIFT2) \ ) +#define STRNUM32sp(s) (uint32_t)(0 \ + | (' ' << SHIFTsp) \ + | (('0' + ((s) / 1 % 10)) << SHIFT0) \ + | (('0' + ((s) / 10 % 10)) << SHIFT1) \ + | (('0' + ((s) / 100 % 10)) << SHIFT2) \ +) + +#define MSG_OK "Operation successful\r\n" +#define MSG_ERR "Error\r\n" struct globals { int pasv_listen_fd; - int proc_self_fd; +#if !BB_MMU + int root_fd; +#endif int local_file_fd; - int start_time; - int abs_timeout; - int timeout; + unsigned end_time; + unsigned timeout; + unsigned verbose; off_t local_file_pos; off_t restart_pos; len_and_sockaddr *local_addr; @@ -88,9 +119,16 @@ struct globals { #if ENABLE_FEATURE_FTP_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 INIT_G() do { } while (0) +#define INIT_G() do { \ + /* Moved to main */ \ + /*strcpy(G.msg_ok + 4, MSG_OK );*/ \ + /*strcpy(G.msg_err + 4, MSG_ERR);*/ \ +} while (0) static char * @@ -141,6 +179,12 @@ replace_char(char *str, char from, char to) return p - str; } +static void +verbose_log(const char *str) +{ + bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str); +} + /* NB: status_str is char[4] packed into uint32_t */ static void cmdio_write(uint32_t status_str, const char *str) @@ -148,35 +192,47 @@ cmdio_write(uint32_t status_str, const char *str) char *response; int len; - /* FTP allegedly uses telnet protocol for command link. + /* FTP uses telnet protocol for command link. * In telnet, 0xff is an escape char, and needs to be escaped: */ response = escape_text((char *) &status_str, str, (0xff << 8) + '\r'); - /* ?! does FTP send embedded LFs as NULs? wow */ + /* FTP sends embedded LFs as NULs */ len = replace_char(response, '\n', '\0'); response[len++] = '\n'; /* tack on trailing '\n' */ xwrite(STDOUT_FILENO, response, len); + if (G.verbose > 1) + verbose_log(response); free(response); } static void -cmdio_write_ok(int status) +cmdio_write_ok(unsigned status) { - fdprintf(STDOUT_FILENO, "%u Operation successful\r\n", status); + *(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); } +#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a)) /* TODO: output strerr(errno) if errno != 0? */ static void -cmdio_write_error(int status) +cmdio_write_error(unsigned status) { - fdprintf(STDOUT_FILENO, "%u Error\r\n", status); + *(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); } +#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a)) static void cmdio_write_raw(const char *p_text) { xwrite_str(STDOUT_FILENO, p_text); + if (G.verbose > 1) + verbose_log(p_text); } static void @@ -185,7 +241,7 @@ timeout_handler(int sig UNUSED_PARAM) off_t pos; int sv_errno = errno; - if (monotonic_sec() - G.start_time > G.abs_timeout) + if ((int)(monotonic_sec() - G.end_time) >= 0) goto timed_out; if (!G.local_file_fd) @@ -228,10 +284,10 @@ static void handle_cwd(void) { if (!G.ftp_arg || chdir(G.ftp_arg) != 0) { - cmdio_write_error(FTP_FILEFAIL); + WRITE_ERR(FTP_FILEFAIL); return; } - cmdio_write_ok(FTP_CWDOK); + WRITE_OK(FTP_CWDOK); } static void @@ -244,12 +300,12 @@ 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"); } -/* TODO: implement FEAT. Example: +/* Examples of HELP and FEAT: # nc -vvv ftp.kernel.org 21 ftp.kernel.org (130.239.17.4:21) open 220 Welcome to ftp.kernel.org. @@ -273,16 +329,15 @@ HELP 214 Help OK. */ static void -handle_help(void) +handle_feat(unsigned status) { - cmdio_write_raw(STR(FTP_HELP)"-Commands:\r\n" - " ALLO CDUP CWD EPSV HELP LIST\r\n" - " MODE NLST NOOP PASS PASV PORT PWD QUIT\r\n" - " REST RETR SIZE STAT STRU SYST TYPE USER\r\n" -#if ENABLE_FEATURE_FTP_WRITE - " APPE DELE MKD RMD RNFR RNTO STOR STOU\r\n" -#endif - STR(FTP_HELP)" Ok\r\n"); + cmdio_write(status, "-Features:"); + cmdio_write_raw(" EPSV\r\n" + " PASV\r\n" + " REST STREAM\r\n" + " MDTM\r\n" + " SIZE\r\n"); + cmdio_write(status, " Ok"); } /* Download commands */ @@ -318,7 +373,7 @@ ftpdataio_get_pasv_fd(void) remote_fd = accept(G.pasv_listen_fd, NULL, 0); if (remote_fd < 0) { - cmdio_write_error(FTP_BADSENDCONN); + WRITE_ERR(FTP_BADSENDCONN); return remote_fd; } @@ -358,7 +413,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; } @@ -377,7 +432,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); @@ -402,7 +457,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); @@ -417,26 +472,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) { @@ -459,7 +499,7 @@ handle_port(void) #endif ) { bail: - cmdio_write_error(FTP_BADCMD); + WRITE_ERR(FTP_BADCMD); return; } @@ -501,17 +541,17 @@ handle_port(void) G.port_addr = xdotted2sockaddr(raw, port); #else G.port_addr = get_peer_lsa(STDIN_FILENO); - set_nport(G.port_addr, port); + set_nport(&G.port_addr->u.sa, htons(port)); #endif - cmdio_write_ok(FTP_PORTOK); + WRITE_OK(FTP_PORTOK); } 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; - cmdio_write_ok(FTP_RESTOK); + G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0; + WRITE_OK(FTP_RESTOK); } static void @@ -532,13 +572,13 @@ handle_retr(void) /* O_NONBLOCK is useful if file happens to be a device node */ local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1; if (local_file_fd < 0) { - cmdio_write_error(FTP_FILEFAIL); + WRITE_ERR(FTP_FILEFAIL); return; } if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) { /* Note - pretend open failed */ - cmdio_write_error(FTP_FILEFAIL); + WRITE_ERR(FTP_FILEFAIL); goto file_close_out; } G.local_file_fd = local_file_fd; @@ -553,7 +593,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); @@ -563,9 +603,9 @@ handle_retr(void) bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd); close(remote_fd); if (bytes_transferred < 0) - cmdio_write_error(FTP_BADSENDFILE); + WRITE_ERR(FTP_BADSENDFILE); else - cmdio_write_ok(FTP_TRANSFEROK); + WRITE_OK(FTP_TRANSFEROK); file_close_out: close(local_file_fd); @@ -577,39 +617,76 @@ 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; /* "-l" or "-1" */ +#if BB_MMU + argv[2] = "--"; +#else + /* NOMMU ftpd ls helper chdirs to argv[2], + * preventing peer from seeing real root. */ + argv[2] = xrealloc_getcwd_or_warn(NULL); +#endif + 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) { + /* child */ +#if !BB_MMU + /* On NOMMU, we want to execute a child - copy of ourself. + * In chroot we usually can't do it. Thus we chdir + * out of the chroot back to original root, + * and (see later below) execute bb_busybox_exec_path + * relative to current directory */ + if (fchdir(G.root_fd) != 0) + _exit(127); + /*close(G.root_fd); - close_on_exec_on() took care of this */ +#endif + /* 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(ARRAY_SIZE(argv) - 1, (char**) argv)); +#else + /* + 1: we must use relative path here if in chroot. + * For example, execv("/proc/self/exe") will fail, since + * it looks for "/proc/self/exe" _relative to chroot!_ */ + execv(bb_busybox_exec_path + 1, (char**) argv); _exit(127); +#endif } /* parent */ close(outfd.wr); - free(cwd); +#if !BB_MMU + free((char*)argv[2]); +#endif return outfd.rd; } @@ -631,40 +708,42 @@ handle_dir_common(int opts) /* -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_fp = xfdopen_for_read(ls_fd); 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); } - cmdio_write_ok(FTP_STATFILE_OK); + 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); + line = xmalloc_fgetline(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. */ +/* TODO: need to s/LF/NUL/g here */ xwrite_str(remote_fd, line); xwrite(remote_fd, "\r\n", 2); free(line); } } close(remote_fd); - cmdio_write_ok(FTP_TRANSFEROK); + WRITE_OK(FTP_TRANSFEROK); } fclose(ls_fp); /* closes ls_fd too */ } @@ -676,6 +755,13 @@ handle_list(void) static void handle_nlst(void) { + /* NLST returns list of names, "\r\n" terminated without regard + * to the current binary flag. Names may start with "/", + * then they represent full names (we don't produce such names), + * otherwise names are relative to current directory. + * Embedded "\n" are replaced by NULs. This is safe since names + * can never contain NUL. + */ handle_dir_common(0); } static void @@ -684,20 +770,63 @@ handle_stat_file(void) handle_dir_common(LONG_LISTING + USE_CTRL_CONN); } +/* This can be extended to handle MLST, as all info is available + * in struct stat for that: + * MLST file_name + * 250-Listing file_name + * type=file;size=4161;modify=19970214165800; /dir/dir/file_name + * 250 End + * Nano-doc: + * MLST [] + * Returned name should be either the same as requested, or fully qualified. + * If there was no parameter, return "" or (preferred) fully-qualified name. + * Returned "facts" (case is not important): + * size - size in octets + * modify - last modification time + * type - entry type (file,dir,OS.unix=block) + * (+ cdir and pdir types for MLSD) + * unique - unique id of file/directory (inode#) + * perm - + * a: can be appended to (APPE) + * d: can be deleted (RMD/DELE) + * f: can be renamed (RNFR) + * r: can be read (RETR) + * w: can be written (STOR) + * e: can CWD into this dir + * l: this dir can be listed (dir only!) + * c: can create files in this dir + * m: can create dirs in this dir (MKD) + * p: can delete files in this dir + * UNIX.mode - unix file mode + */ static void -handle_size(void) +handle_size_or_mdtm(int need_size) { struct stat statbuf; - char buf[sizeof(STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n") + sizeof(off_t)*3]; + struct tm broken_out; + char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3) + | sizeof("NNN YYYYMMDDhhmmss\r\n") + ]; if (!G.ftp_arg || stat(G.ftp_arg, &statbuf) != 0 || !S_ISREG(statbuf.st_mode) ) { - cmdio_write_error(FTP_FILEFAIL); + WRITE_ERR(FTP_FILEFAIL); return; } - sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size); + if (need_size) { + sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size); + } else { + 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_mday, + broken_out.tm_hour, + broken_out.tm_min, + broken_out.tm_sec); + } cmdio_write_raw(buf); } @@ -708,30 +837,30 @@ static void handle_mkd(void) { if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) { - cmdio_write_error(FTP_FILEFAIL); + WRITE_ERR(FTP_FILEFAIL); return; } - cmdio_write_ok(FTP_MKDIROK); + WRITE_OK(FTP_MKDIROK); } static void handle_rmd(void) { if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) { - cmdio_write_error(FTP_FILEFAIL); + WRITE_ERR(FTP_FILEFAIL); return; } - cmdio_write_ok(FTP_RMDIROK); + WRITE_OK(FTP_RMDIROK); } static void handle_dele(void) { if (!G.ftp_arg || unlink(G.ftp_arg) != 0) { - cmdio_write_error(FTP_FILEFAIL); + WRITE_ERR(FTP_FILEFAIL); return; } - cmdio_write_ok(FTP_DELEOK); + WRITE_OK(FTP_DELEOK); } static void @@ -739,7 +868,7 @@ handle_rnfr(void) { free(G.rnfr_filename); G.rnfr_filename = xstrdup(G.ftp_arg); - cmdio_write_ok(FTP_RNFROK); + WRITE_OK(FTP_RNFROK); } static void @@ -749,7 +878,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; } @@ -758,10 +887,10 @@ handle_rnto(void) G.rnfr_filename = NULL; if (retval) { - cmdio_write_error(FTP_FILEFAIL); + WRITE_ERR(FTP_FILEFAIL); return; } - cmdio_write_ok(FTP_RENAMEOK); + WRITE_OK(FTP_RENAMEOK); } static void @@ -798,7 +927,7 @@ handle_upload_common(int is_append, int is_unique) || fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode) ) { - cmdio_write_error(FTP_UPLOADFAIL); + WRITE_ERR(FTP_UPLOADFAIL); if (local_file_fd >= 0) goto close_local_and_bail; return; @@ -817,9 +946,9 @@ handle_upload_common(int is_append, int is_unique) bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd); close(remote_fd); if (bytes_transferred < 0) - cmdio_write_error(FTP_BADSENDFILE); + WRITE_ERR(FTP_BADSENDFILE); else - cmdio_write_ok(FTP_TRANSFEROK); + WRITE_OK(FTP_TRANSFEROK); close_local_and_bail: close(local_file_fd); @@ -850,25 +979,78 @@ handle_stou(void) 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); - - /* Trailing '\n' is already stripped, strip '\r' */ - len = strlen(cmd) - 1; - while ((ssize_t)len >= 0 && cmd[len] == '\r') { - cmd[len] = '\0'; - len--; + { + /* 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; + } + + /* 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); + G.ftp_arg = strchr(cmd, ' '); if (G.ftp_arg != NULL) *G.ftp_arg++ = '\0'; @@ -890,8 +1072,10 @@ enum { const_CWD = mk_const3('C', 'W', 'D'), const_DELE = mk_const4('D', 'E', 'L', 'E'), const_EPSV = mk_const4('E', 'P', 'S', 'V'), + const_FEAT = mk_const4('F', 'E', 'A', 'T'), const_HELP = mk_const4('H', 'E', 'L', 'P'), const_LIST = mk_const4('L', 'I', 'S', 'T'), + const_MDTM = mk_const4('M', 'D', 'T', 'M'), const_MKD = mk_const3('M', 'K', 'D'), const_MODE = mk_const4('M', 'O', 'D', 'E'), const_NLST = mk_const4('N', 'L', 'S', 'T'), @@ -915,35 +1099,58 @@ 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), +#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, }; 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 { + unsigned abs_timeout; + unsigned verbose_S; smallint opts; INIT_G(); - G.start_time = monotonic_sec(); - G.abs_timeout = 1 * 60 * 60; + abs_timeout = 1 * 60 * 60; + verbose_S = 0; G.timeout = 2 * 60; - opt_complementary = "t+:T+"; - opts = getopt32(argv, "l1vS" USE_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &G.abs_timeout); - + 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); +#else + opts = getopt32(argv, "l1vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S); if (opts & (OPT_l|OPT_1)) { /* Our secret backdoor to ls */ - memset(&G, 0, sizeof(G)); -/* TODO: pass -n too? */ +/* TODO: pass -n? It prevents user/group resolution, which may not work in chroot anyway */ +/* TODO: pass -A? It shows dot files */ +/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */ xchdir(argv[2]); argv[2] = (char*)"--"; + /* memset(&G, 0, sizeof(G)); - ls_main does it */ return ls_main(argc, argv); } - +#endif + if (G.verbose < verbose_S) + G.verbose = verbose_S; + if (abs_timeout | G.timeout) { + if (abs_timeout == 0) + abs_timeout = INT_MAX; + G.end_time = monotonic_sec() + abs_timeout; + if (G.timeout > abs_timeout) + G.timeout = abs_timeout; + } + strcpy(G.msg_ok + 4, MSG_OK ); + strcpy(G.msg_err + 4, MSG_ERR); G.local_addr = get_sock_lsa(STDIN_FILENO); if (!G.local_addr) { @@ -963,8 +1170,13 @@ int ftpd_main(int argc, char **argv) openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON); logmode |= LOGMODE_SYSLOG; } + if (logmode) + applet_name = xasprintf("%s[%u]", applet_name, (int)getpid()); - G.proc_self_fd = xopen("/proc/self", O_RDONLY | O_DIRECTORY); +#if !BB_MMU + G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY); + close_on_exec_on(G.root_fd); +#endif if (argv[optind]) { xchdir(argv[optind]); @@ -979,9 +1191,11 @@ int ftpd_main(int argc, char **argv) /* 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)); + /* 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)); - cmdio_write_ok(FTP_GREET); + WRITE_OK(FTP_GREET); signal(SIGALRM, timeout_handler); #ifdef IF_WE_WANT_TO_REQUIRE_LOGIN @@ -1002,14 +1216,14 @@ int ftpd_main(int argc, char **argv) break; cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n"); } else if (cmdval == const_QUIT) { - cmdio_write_ok(FTP_GOODBYE); + WRITE_OK(FTP_GOODBYE); return 0; } else { cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n"); } } } - cmdio_write_ok(FTP_LOGINOK); + WRITE_OK(FTP_LOGINOK); #endif /* RFC-959 Section 5.1 @@ -1054,23 +1268,28 @@ int ftpd_main(int argc, char **argv) uint32_t cmdval = cmdio_get_cmd_and_arg(); if (cmdval == const_QUIT) { - cmdio_write_ok(FTP_GOODBYE); + WRITE_OK(FTP_GOODBYE); return 0; } else if (cmdval == const_USER) - cmdio_write_ok(FTP_GIVEPWORD); + /* This would mean "ok, now give me PASS". */ + /*WRITE_OK(FTP_GIVEPWORD);*/ + /* vsftpd can be configured to not require that, + * and this also saves one roundtrip: + */ + WRITE_OK(FTP_LOGINOK); else if (cmdval == const_PASS) - cmdio_write_ok(FTP_LOGINOK); + WRITE_OK(FTP_LOGINOK); else if (cmdval == const_NOOP) - cmdio_write_ok(FTP_NOOPOK); + WRITE_OK(FTP_NOOPOK); else if (cmdval == const_TYPE) - cmdio_write_ok(FTP_TYPEOK); + WRITE_OK(FTP_TYPEOK); else if (cmdval == const_STRU) - cmdio_write_ok(FTP_STRUOK); + WRITE_OK(FTP_STRUOK); else if (cmdval == const_MODE) - cmdio_write_ok(FTP_MODEOK); + WRITE_OK(FTP_MODEOK); else if (cmdval == const_ALLO) - cmdio_write_ok(FTP_ALLOOK); + 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) @@ -1079,14 +1298,21 @@ int ftpd_main(int argc, char **argv) handle_cwd(); else if (cmdval == const_CDUP) /* cd .. */ handle_cdup(); - else if (cmdval == const_HELP) - handle_help(); + /* HELP is nearly useless, but we can reuse FEAT for it */ + /* lftp uses FEAT */ + else if (cmdval == const_HELP || cmdval == const_FEAT) + handle_feat(cmdval == const_HELP + ? STRNUM32(FTP_HELP) + : STRNUM32(FTP_STATOK) + ); else if (cmdval == const_LIST) /* ls -l */ handle_list(); else if (cmdval == const_NLST) /* "name list", bare ls */ handle_nlst(); - else if (cmdval == const_SIZE) - handle_size(); + /* SIZE is crucial for wget's download indicator etc */ + /* Mozilla, lftp use MDTM (presumably for caching) */ + else if (cmdval == const_SIZE || cmdval == const_MDTM) + handle_size_or_mdtm(cmdval == const_SIZE); else if (cmdval == const_STAT) { if (G.ftp_arg == NULL) handle_stat(); @@ -1121,6 +1347,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 @@ -1139,9 +1367,11 @@ int ftpd_main(int argc, char **argv) else { /* Which unsupported commands were seen in the wild? * (doesn't necessarily mean "we must support them") - * lftp 3.6.3: FEAT - is it useful? - * MDTM - works fine without it anyway + * foo 1.2.3: XXXX - comment */ +#if ENABLE_FEATURE_FTP_WRITE + bad_cmd: +#endif cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n"); } }