*
* 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: "\nOptions:"
+//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>
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);*/ \
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);
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);
return remote_fd;
}
- setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+ setsockopt_keepalive(remote_fd);
return remote_fd;
}
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);
}
pid_t pid;
argv[0] = "ftpd";
- argv[1] = opt; /* "-l" or "-1" */
-#if BB_MMU
+ argv[1] = opt; /* "-lA" or "-1A" */
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;
/*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 */
+ int cur_fd;
#endif
+ /* child */
/* NB: close _first_, then move fd! */
close(outfd.rd);
xmove_fd(outfd.wr, STDOUT_FILENO);
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
- /* + 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);
+ 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);
-#if !BB_MMU
- free((char*)argv[2]);
-#endif
return outfd.rd;
}
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_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 <filename> */
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);
}
}
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,
/* Upload commands */
-#if ENABLE_FEATURE_FTP_WRITE
+#if ENABLE_FEATURE_FTPD_WRITE
static void
handle_mkd(void)
{
|| 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;
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)
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'),
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;
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 -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*)"--";
+ /* 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)
if (logmode)
applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
-#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]);
- 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_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);
+ }
+#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);
}
- WRITE_OK(FTP_LOGINOK);
+
+#if ENABLE_FEATURE_FTPD_AUTHENTICATION
+ if (pw)
+ change_identity(pw);
+ /* else: -A is in effect */
#endif
/* RFC-959 Section 5.1
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();
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();
* (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");