1 /* vi: set sw=4 ts=4: */
3 * Simple FTP daemon, based on vsftpd 2.0.7 (written by Chris Evans)
5 * Author: Adam Tkac <vonsch@gmail.com>
7 * Licensed under GPLv2, see file LICENSE in this source tree.
9 * Only subset of FTP protocol is implemented but vast majority of clients
10 * should not have any problem.
12 * You have to run this daemon via inetd.
15 //config: bool "ftpd (30 kb)"
18 //config: Simple FTP daemon. You have to run it via inetd.
20 //config:config FEATURE_FTPD_WRITE
21 //config: bool "Enable -w (upload commands)"
23 //config: depends on FTPD
25 //config: Enable -w option. "ftpd -w" will accept upload commands
26 //config: such as STOR, STOU, APPE, DELE, MKD, RMD, rename commands.
28 //config:config FEATURE_FTPD_ACCEPT_BROKEN_LIST
29 //config: bool "Enable workaround for RFC-violating clients"
31 //config: depends on FTPD
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.
39 //config:config FEATURE_FTPD_AUTHENTICATION
40 //config: bool "Enable authentication"
42 //config: depends on FTPD
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).
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.
52 //applet:IF_FTPD(APPLET(ftpd, BB_DIR_USR_SBIN, BB_SUID_DROP))
54 //kbuild:lib-$(CONFIG_FTPD) += ftpd.o
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. Accesses by clients occur under ftpd's UID.\n"
62 //usage: IF_FEATURE_FTPD_AUTHENTICATION(
63 //usage: "FTP server. "
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\n"
70 //usage: "\n -w Allow upload"
71 //usage: IF_FEATURE_FTPD_AUTHENTICATION(
72 //usage: "\n -a USER Enable 'anonymous' login and map it to USER"
74 //usage: "\n -v Log errors to stderr. -vv: verbose log"
75 //usage: "\n -S Log errors to syslog. -SS: verbose log"
76 //usage: "\n -t,-T N Idle and absolute timeout"
79 #include "common_bufsiz.h"
81 #include <netinet/tcp.h>
83 #define FTP_DATACONN 150
84 #define FTP_NOOPOK 200
85 #define FTP_TYPEOK 200
86 #define FTP_PORTOK 200
87 #define FTP_STRUOK 200
88 #define FTP_MODEOK 200
89 #define FTP_ALLOOK 202
90 #define FTP_STATOK 211
91 #define FTP_STATFILE_OK 213
93 #define FTP_SYSTOK 215
95 #define FTP_GOODBYE 221
96 #define FTP_TRANSFEROK 226
97 #define FTP_PASVOK 227
98 /*#define FTP_EPRTOK 228*/
99 #define FTP_EPSVOK 229
100 #define FTP_LOGINOK 230
101 #define FTP_CWDOK 250
102 #define FTP_RMDIROK 250
103 #define FTP_DELEOK 250
104 #define FTP_RENAMEOK 250
105 #define FTP_PWDOK 257
106 #define FTP_MKDIROK 257
107 #define FTP_GIVEPWORD 331
108 #define FTP_RESTOK 350
109 #define FTP_RNFROK 350
110 #define FTP_TIMEOUT 421
111 #define FTP_BADSENDCONN 425
112 #define FTP_BADSENDNET 426
113 #define FTP_BADSENDFILE 451
114 #define FTP_BADCMD 500
115 #define FTP_COMMANDNOTIMPL 502
116 #define FTP_NEEDUSER 503
117 #define FTP_NEEDRNFR 503
118 #define FTP_BADSTRU 504
119 #define FTP_BADMODE 504
120 #define FTP_LOGINERR 530
121 #define FTP_FILEFAIL 550
122 #define FTP_NOPERM 550
123 #define FTP_UPLOADFAIL 553
126 #define STR(s) STR1(s)
128 /* Convert a constant to 3-digit string, packed into uint32_t */
130 /* Shift for Nth decimal digit */
131 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
132 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
133 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
134 /* And for 4th position (space) */
135 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
137 #define STRNUM32(s) (uint32_t)(0 \
138 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
139 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
140 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
142 #define STRNUM32sp(s) (uint32_t)(0 \
144 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
145 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
146 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
149 #define MSG_OK "Operation successful\r\n"
150 #define MSG_ERR "Error\r\n"
161 off_t local_file_pos;
163 len_and_sockaddr *local_addr;
164 len_and_sockaddr *port_addr;
167 #if ENABLE_FEATURE_FTPD_WRITE
170 /* We need these aligned to uint32_t */
171 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
172 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
174 #define G (*ptr_to_globals)
175 /* ^^^ about 75 bytes smaller code than this: */
176 //#define G (*(struct globals*)bb_common_bufsiz1)
177 #define INIT_G() do { \
178 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
179 /*setup_common_bufsiz();*/ \
181 /* Moved to main */ \
182 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
183 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
188 escape_text(const char *prepend, const char *str, unsigned escapee)
190 unsigned retlen, remainlen, chunklen;
194 append = (char)escapee;
197 remainlen = strlen(str);
198 retlen = strlen(prepend);
199 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
200 strcpy(ret, prepend);
203 found = strchrnul(str, escapee);
204 chunklen = found - str + 1;
206 /* Copy chunk up to and including escapee (or NUL) to ret */
207 memcpy(ret + retlen, str, chunklen);
210 if (*found == '\0') {
211 /* It wasn't escapee, it was NUL! */
212 ret[retlen - 1] = append; /* replace NUL */
213 ret[retlen] = '\0'; /* add NUL */
216 ret[retlen++] = escapee; /* duplicate escapee */
222 /* Returns strlen as a bonus */
224 replace_char(char *str, char from, char to)
236 verbose_log(const char *str)
238 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
241 /* NB: status_str is char[4] packed into uint32_t */
243 cmdio_write(uint32_t status_str, const char *str)
248 /* FTP uses telnet protocol for command link.
249 * In telnet, 0xff is an escape char, and needs to be escaped: */
250 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
252 /* FTP sends embedded LFs as NULs */
253 len = replace_char(response, '\n', '\0');
255 response[len++] = '\n'; /* tack on trailing '\n' */
256 xwrite(STDOUT_FILENO, response, len);
258 verbose_log(response);
263 cmdio_write_ok(unsigned status)
265 *(bb__aliased_uint32_t *) G.msg_ok = status;
266 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
268 verbose_log(G.msg_ok);
270 #define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
272 /* TODO: output strerr(errno) if errno != 0? */
274 cmdio_write_error(unsigned status)
276 *(bb__aliased_uint32_t *) G.msg_err = status;
277 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
279 verbose_log(G.msg_err);
281 #define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
284 cmdio_write_raw(const char *p_text)
286 xwrite_str(STDOUT_FILENO, p_text);
292 timeout_handler(int sig UNUSED_PARAM)
295 int sv_errno = errno;
297 if ((int)(monotonic_sec() - G.end_time) >= 0)
300 if (!G.local_file_fd)
303 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
304 if (pos == G.local_file_pos)
306 G.local_file_pos = pos;
313 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
314 /* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
318 /* Simple commands */
323 char *cwd, *response;
325 cwd = xrealloc_getcwd_or_warn(NULL);
329 /* We have to promote each " to "" */
330 response = escape_text(" \"", cwd, ('"' << 8) + '"');
332 cmdio_write(STRNUM32(FTP_PWDOK), response);
339 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
340 WRITE_ERR(FTP_FILEFAIL);
349 G.ftp_arg = (char*)"..";
356 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
358 STR(FTP_STATOK)" Ok\r\n");
361 /* Examples of HELP and FEAT:
362 # nc -vvv ftp.kernel.org 21
363 ftp.kernel.org (130.239.17.4:21) open
364 220 Welcome to ftp.kernel.org.
377 214-The following commands are recognized.
378 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
379 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
380 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
385 handle_feat(unsigned status)
387 cmdio_write(status, "-Features:");
388 cmdio_write_raw(" EPSV\r\n"
393 cmdio_write(status, " Ok");
396 /* Download commands */
401 return (G.port_addr != NULL);
407 return (G.pasv_listen_fd > STDOUT_FILENO);
411 port_pasv_cleanup(void)
415 if (G.pasv_listen_fd > STDOUT_FILENO)
416 close(G.pasv_listen_fd);
417 G.pasv_listen_fd = -1;
420 /* On error, emits error code to the peer */
422 ftpdataio_get_pasv_fd(void)
426 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
429 WRITE_ERR(FTP_BADSENDCONN);
433 setsockopt_keepalive(remote_fd);
437 /* Clears port/pasv data.
438 * This means we dont waste resources, for example, keeping
439 * PASV listening socket open when it is no longer needed.
440 * On error, emits error code to the peer (or exits).
441 * On success, emits p_status_msg to the peer.
444 get_remote_transfer_fd(const char *p_status_msg)
449 /* On error, emits error code to the peer */
450 remote_fd = ftpdataio_get_pasv_fd();
453 remote_fd = xconnect_stream(G.port_addr);
460 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
464 /* If there were neither PASV nor PORT, emits error code to the peer */
466 port_or_pasv_was_seen(void)
468 if (!pasv_active() && !port_active()) {
469 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
478 bind_for_passive_mode(void)
485 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
486 setsockopt_reuseaddr(fd);
488 set_nport(&G.local_addr->u.sa, 0);
489 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
491 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
493 port = get_nport(&G.local_addr->u.sa);
503 char *addr, *response;
505 port = bind_for_passive_mode();
507 if (G.local_addr->u.sa.sa_family == AF_INET)
508 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
509 else /* seen this in the wild done by other ftp servers: */
510 addr = xstrdup("0.0.0.0");
511 replace_char(addr, '.', ',');
513 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
514 addr, (int)(port >> 8), (int)(port & 255));
516 cmdio_write_raw(response);
527 port = bind_for_passive_mode();
528 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
529 cmdio_write_raw(response);
536 unsigned port, port_hi;
538 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
539 socklen_t peer_ipv4_len;
540 struct sockaddr_in peer_ipv4;
541 struct in_addr port_ipv4_sin_addr;
548 /* PORT command format makes sense only over IPv4 */
550 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
551 || G.local_addr->u.sa.sa_family != AF_INET
555 WRITE_ERR(FTP_BADCMD);
559 comma = strrchr(raw, ',');
563 port = bb_strtou(&comma[1], NULL, 10);
564 if (errno || port > 0xff)
567 comma = strrchr(raw, ',');
571 port_hi = bb_strtou(&comma[1], NULL, 10);
572 if (errno || port_hi > 0xff)
574 port |= port_hi << 8;
576 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
577 replace_char(raw, ',', '.');
579 /* We are verifying that PORT's IP matches getpeername().
580 * Otherwise peer can make us open data connections
581 * to other hosts (security problem!)
582 * This code would be too simplistic:
583 * lsa = xdotted2sockaddr(raw, port);
584 * if (lsa == NULL) goto bail;
586 if (!inet_aton(raw, &port_ipv4_sin_addr))
588 peer_ipv4_len = sizeof(peer_ipv4);
589 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
591 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
594 G.port_addr = xdotted2sockaddr(raw, port);
596 G.port_addr = get_peer_lsa(STDIN_FILENO);
597 set_nport(&G.port_addr->u.sa, htons(port));
599 WRITE_OK(FTP_PORTOK);
605 /* When ftp_arg == NULL simply restart from beginning */
606 G.restart_pos = G.ftp_arg ? XATOOFF(G.ftp_arg) : 0;
607 WRITE_OK(FTP_RESTOK);
614 off_t bytes_transferred;
617 off_t offset = G.restart_pos;
622 if (!port_or_pasv_was_seen())
623 return; /* port_or_pasv_was_seen emitted error response */
625 /* O_NONBLOCK is useful if file happens to be a device node */
626 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
627 if (local_file_fd < 0) {
628 WRITE_ERR(FTP_FILEFAIL);
632 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
633 /* Note - pretend open failed */
634 WRITE_ERR(FTP_FILEFAIL);
637 G.local_file_fd = local_file_fd;
639 /* Now deactive O_NONBLOCK, otherwise we have a problem
640 * on DMAPI filesystems such as XFS DMAPI.
642 ndelay_off(local_file_fd);
644 /* Set the download offset (from REST) if any */
646 xlseek(local_file_fd, offset, SEEK_SET);
648 response = xasprintf(
649 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
650 G.ftp_arg, statbuf.st_size);
651 remote_fd = get_remote_transfer_fd(response);
656 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
658 if (bytes_transferred < 0)
659 WRITE_ERR(FTP_BADSENDFILE);
661 WRITE_OK(FTP_TRANSFEROK);
664 close(local_file_fd);
671 popen_ls(const char *opt)
674 struct fd_pair outfd;
678 argv[1] = opt; /* "-lA" or "-1A" */
683 /* Improve compatibility with non-RFC conforming FTP clients
684 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
685 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
686 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
687 && G.ftp_arg && G.ftp_arg[0] == '-'
689 const char *tmp = strchr(G.ftp_arg, ' ');
690 if (tmp) /* skip the space */
697 /*fflush_all(); - so far we dont use stdio on output */
698 pid = BB_MMU ? xfork() : xvfork();
704 /* NB: close _first_, then move fd! */
706 xmove_fd(outfd.wr, STDOUT_FILENO);
707 /* Opening /dev/null in chroot is hard.
708 * Just making sure STDIN_FILENO is opened
709 * to something harmless. Paranoia,
710 * ls won't read it anyway */
712 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
714 /* memset(&G, 0, sizeof(G)); - ls_main does it */
715 exit(ls_main(/*argc_unused*/ 0, (char**) argv));
717 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
718 /* On NOMMU, we want to execute a child - copy of ourself
719 * in order to unblock parent after vfork.
720 * In chroot we usually can't re-exec. Thus we escape
721 * out of the chroot back to original root.
723 if (G.root_fd >= 0) {
724 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
726 /*close(G.root_fd); - close_on_exec_on() took care of this */
728 /* Child expects directory to list on fd #3 */
730 execv(bb_busybox_exec_path, (char**) argv);
746 handle_dir_common(int opts)
752 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
753 return; /* port_or_pasv_was_seen emitted error response */
755 ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
756 ls_fp = xfdopen_for_read(ls_fd);
757 /* FIXME: filenames with embedded newlines are mishandled */
759 if (opts & USE_CTRL_CONN) {
760 /* STAT <filename> */
761 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
763 line = xmalloc_fgetline(ls_fp);
766 /* Hack: 0 results in no status at all */
767 /* Note: it's ok that we don't prepend space,
768 * ftp.kernel.org doesn't do that too */
769 cmdio_write(0, line);
772 WRITE_OK(FTP_STATFILE_OK);
774 /* LIST/NLST [<filename>] */
775 int remote_fd = get_remote_transfer_fd(" Directory listing");
776 if (remote_fd >= 0) {
780 line = xmalloc_fgets(ls_fp);
783 /* I've seen clients complaining when they
784 * are fed with ls output with bare '\n'.
785 * Replace trailing "\n\0" with "\r\n".
788 if (len != 0) /* paranoia check */
789 line[len - 1] = '\r';
791 xwrite(remote_fd, line, len + 1);
796 WRITE_OK(FTP_TRANSFEROK);
798 fclose(ls_fp); /* closes ls_fd too */
803 handle_dir_common(LONG_LISTING);
808 /* NLST returns list of names, "\r\n" terminated without regard
809 * to the current binary flag. Names may start with "/",
810 * then they represent full names (we don't produce such names),
811 * otherwise names are relative to current directory.
812 * Embedded "\n" are replaced by NULs. This is safe since names
813 * can never contain NUL.
815 handle_dir_common(0);
818 handle_stat_file(void)
820 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
823 /* This can be extended to handle MLST, as all info is available
824 * in struct stat for that:
826 * 250-Listing file_name
827 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
830 * MLST [<file or dir name, "." assumed if not given>]
831 * Returned name should be either the same as requested, or fully qualified.
832 * If there was no parameter, return "" or (preferred) fully-qualified name.
833 * Returned "facts" (case is not important):
834 * size - size in octets
835 * modify - last modification time
836 * type - entry type (file,dir,OS.unix=block)
837 * (+ cdir and pdir types for MLSD)
838 * unique - unique id of file/directory (inode#)
840 * a: can be appended to (APPE)
841 * d: can be deleted (RMD/DELE)
842 * f: can be renamed (RNFR)
843 * r: can be read (RETR)
844 * w: can be written (STOR)
845 * e: can CWD into this dir
846 * l: this dir can be listed (dir only!)
847 * c: can create files in this dir
848 * m: can create dirs in this dir (MKD)
849 * p: can delete files in this dir
850 * UNIX.mode - unix file mode
853 handle_size_or_mdtm(int need_size)
856 struct tm broken_out;
857 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
858 | sizeof("NNN YYYYMMDDhhmmss\r\n")
862 || stat(G.ftp_arg, &statbuf) != 0
863 || !S_ISREG(statbuf.st_mode)
865 WRITE_ERR(FTP_FILEFAIL);
869 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
871 gmtime_r(&statbuf.st_mtime, &broken_out);
872 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
873 broken_out.tm_year + 1900,
874 broken_out.tm_mon + 1,
880 cmdio_write_raw(buf);
883 /* Upload commands */
885 #if ENABLE_FEATURE_FTPD_WRITE
889 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
890 WRITE_ERR(FTP_FILEFAIL);
893 WRITE_OK(FTP_MKDIROK);
899 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
900 WRITE_ERR(FTP_FILEFAIL);
903 WRITE_OK(FTP_RMDIROK);
909 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
910 WRITE_ERR(FTP_FILEFAIL);
913 WRITE_OK(FTP_DELEOK);
919 free(G.rnfr_filename);
920 G.rnfr_filename = xstrdup(G.ftp_arg);
921 WRITE_OK(FTP_RNFROK);
929 /* If we didn't get a RNFR, throw a wobbly */
930 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
931 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
935 retval = rename(G.rnfr_filename, G.ftp_arg);
936 free(G.rnfr_filename);
937 G.rnfr_filename = NULL;
940 WRITE_ERR(FTP_FILEFAIL);
943 WRITE_OK(FTP_RENAMEOK);
947 handle_upload_common(int is_append, int is_unique)
951 off_t bytes_transferred;
956 offset = G.restart_pos;
959 if (!port_or_pasv_was_seen())
960 return; /* port_or_pasv_was_seen emitted error response */
965 tempname = xstrdup(" FILE: uniq.XXXXXX");
966 local_file_fd = mkstemp(tempname + 7);
967 } else if (G.ftp_arg) {
968 int flags = O_WRONLY | O_CREAT | O_TRUNC;
970 flags = O_WRONLY | O_CREAT | O_APPEND;
972 flags = O_WRONLY | O_CREAT;
973 local_file_fd = open(G.ftp_arg, flags, 0666);
976 if (local_file_fd < 0
977 || fstat(local_file_fd, &statbuf) != 0
978 || !S_ISREG(statbuf.st_mode)
981 WRITE_ERR(FTP_UPLOADFAIL);
982 if (local_file_fd >= 0)
983 goto close_local_and_bail;
986 G.local_file_fd = local_file_fd;
989 xlseek(local_file_fd, offset, SEEK_SET);
991 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
995 goto close_local_and_bail;
997 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
999 if (bytes_transferred < 0)
1000 WRITE_ERR(FTP_BADSENDFILE);
1002 WRITE_OK(FTP_TRANSFEROK);
1004 close_local_and_bail:
1005 close(local_file_fd);
1006 G.local_file_fd = 0;
1012 handle_upload_common(0, 0);
1019 handle_upload_common(1, 0);
1026 handle_upload_common(0, 1);
1028 #endif /* ENABLE_FEATURE_FTPD_WRITE */
1031 cmdio_get_cmd_and_arg(void)
1041 /* Paranoia. Peer may send 1 gigabyte long cmd... */
1042 /* Using separate len_on_stk instead of len optimizes
1043 * code size (allows len to be in CPU register) */
1044 size_t len_on_stk = 8 * 1024;
1045 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
1051 /* De-escape telnet: 0xff,0xff => 0xff */
1052 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1053 * data transfer, and may be preceded by telnet's "Interrupt Process"
1054 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1055 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1056 * and may generate SIGURG on our side. See RFC854).
1057 * So far we don't support that (may install SIGURG handler if we'd want to),
1058 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1059 /* Then de-escape FTP: NUL => '\n' */
1060 /* Testing for \xff:
1061 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1062 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1063 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1064 * Testing for embedded LF:
1065 * LF_HERE=`echo -ne "LF\nHERE"`
1066 * echo Hello >"$LF_HERE"
1067 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1072 /* Strip "\r\n" if it is there */
1073 if (len != 0 && cmd[len - 1] == '\n') {
1075 if (len != 0 && cmd[len - 1] == '\r')
1079 src = strchrnul(cmd, 0xff) - cmd;
1080 /* 99,99% there are neither NULs nor 255s and src == len */
1084 if ((unsigned char)(cmd[src]) == 255) {
1086 /* 255,xxx - skip 255 */
1087 if ((unsigned char)(cmd[src]) != 255) {
1088 /* 255,!255 - skip both */
1092 /* 255,255 - retain one 255 */
1095 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1097 } while (src < len);
1105 G.ftp_arg = strchr(cmd, ' ');
1106 if (G.ftp_arg != NULL)
1107 *G.ftp_arg++ = '\0';
1109 /* Uppercase and pack into uint32_t first word of the command */
1112 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1117 #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1118 #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1120 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1121 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1122 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1123 const_CWD = mk_const3('C', 'W', 'D'),
1124 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1125 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
1126 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
1127 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1128 const_LIST = mk_const4('L', 'I', 'S', 'T'),
1129 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
1130 const_MKD = mk_const3('M', 'K', 'D'),
1131 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1132 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1133 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1134 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1135 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1136 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1137 const_PWD = mk_const3('P', 'W', 'D'),
1138 /* Same as PWD. Reportedly used by windows ftp client */
1139 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
1140 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1141 const_REST = mk_const4('R', 'E', 'S', 'T'),
1142 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1143 const_RMD = mk_const3('R', 'M', 'D'),
1144 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1145 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1146 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1147 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1148 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1149 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1150 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1151 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1152 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1153 const_USER = mk_const4('U', 'S', 'E', 'R'),
1160 OPT_v = (1 << ((!BB_MMU) * 3 + 0)),
1161 OPT_S = (1 << ((!BB_MMU) * 3 + 1)),
1162 OPT_w = (1 << ((!BB_MMU) * 3 + 2)) * ENABLE_FEATURE_FTPD_WRITE,
1165 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1166 int ftpd_main(int argc UNUSED_PARAM, char **argv)
1168 #if ENABLE_FEATURE_FTPD_AUTHENTICATION
1169 struct passwd *pw = NULL;
1170 char *anon_opt = NULL;
1172 unsigned abs_timeout;
1178 abs_timeout = 1 * 60 * 60;
1182 opts = getopt32(argv, "^" "vS"
1183 IF_FEATURE_FTPD_WRITE("w") "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:")
1185 &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
1186 &G.verbose, &verbose_S
1189 opts = getopt32(argv, "^" "l1AvS"
1190 IF_FEATURE_FTPD_WRITE("w") "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:")
1192 &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
1193 &G.verbose, &verbose_S
1195 if (opts & (OPT_l|OPT_1)) {
1196 /* Our secret backdoor to ls */
1199 /* memset(&G, 0, sizeof(G)); - ls_main does it */
1200 return ls_main(/*argc_unused*/ 0, argv);
1203 if (G.verbose < verbose_S)
1204 G.verbose = verbose_S;
1205 if (abs_timeout | G.timeout) {
1206 if (abs_timeout == 0)
1207 abs_timeout = INT_MAX;
1208 G.end_time = monotonic_sec() + abs_timeout;
1209 if (G.timeout > abs_timeout)
1210 G.timeout = abs_timeout;
1212 strcpy(G.msg_ok + 4, MSG_OK );
1213 strcpy(G.msg_err + 4, MSG_ERR);
1215 G.local_addr = get_sock_lsa(STDIN_FILENO);
1216 if (!G.local_addr) {
1217 /* This is confusing:
1218 * bb_error_msg_and_die("stdin is not a socket");
1221 /* Help text says that ftpd must be used as inetd service,
1222 * which is by far the most usual cause of get_sock_lsa
1226 if (!(opts & OPT_v))
1227 logmode = LOGMODE_NONE;
1229 /* LOG_NDELAY is needed since we may chroot later */
1230 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1231 logmode |= LOGMODE_SYSLOG;
1234 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1236 //umask(077); - admin can set umask before starting us
1240 /* We'll always take EPIPE rather than a rude signal, thanks */
1242 /* LIST command spawns chilren. Prevent zombies */
1246 /* Set up options on the command socket (do we need these all? why?) */
1247 setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
1248 setsockopt_keepalive(STDIN_FILENO);
1249 /* Telnet protocol over command link may send "urgent" data,
1250 * we prefer it to be received in the "normal" data stream: */
1251 setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
1253 WRITE_OK(FTP_GREET);
1254 signal(SIGALRM, timeout_handler);
1256 #if ENABLE_FEATURE_FTPD_AUTHENTICATION
1258 uint32_t cmdval = cmdio_get_cmd_and_arg();
1259 if (cmdval == const_USER) {
1260 if (anon_opt && strcmp(G.ftp_arg, "anonymous") == 0) {
1261 pw = getpwnam(anon_opt);
1263 break; /* does not even ask for password */
1265 pw = getpwnam(G.ftp_arg);
1266 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify password\r\n");
1267 } else if (cmdval == const_PASS) {
1268 if (check_password(pw, G.ftp_arg) > 0) {
1269 break; /* login success */
1271 cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
1273 } else if (cmdval == const_QUIT) {
1274 WRITE_OK(FTP_GOODBYE);
1277 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1280 WRITE_OK(FTP_LOGINOK);
1283 /* Do this after auth, else /etc/passwd is not accessible */
1289 const char *basedir = argv[0];
1291 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1292 close_on_exec_on(G.root_fd);
1294 if (chroot(basedir) == 0)
1303 * If chroot failed, assume that we aren't root,
1304 * and at least chdir to the specified DIR
1305 * (older versions were dying with error message).
1306 * If chroot worked, move current dir to new "/":
1311 #if ENABLE_FEATURE_FTPD_AUTHENTICATION
1312 change_identity(pw);
1315 /* RFC-959 Section 5.1
1316 * The following commands and options MUST be supported by every
1317 * server-FTP and user-FTP, except in cases where the underlying
1318 * file system or operating system does not allow or support
1319 * a particular command.
1320 * Type: ASCII Non-print, IMAGE, LOCAL 8
1322 * Structure: File, Record*
1323 * (Record structure is REQUIRED only for hosts whose file
1324 * systems support record structure).
1326 * USER, PASS, ACCT, [bbox: ACCT not supported]
1331 * CWD, CDUP, RMD, MKD, PWD,
1337 * "The argument field is a Telnet string identifying the user's account.
1338 * The command is not necessarily related to the USER command, as some
1339 * sites may require an account for login and others only for specific
1340 * access, such as storing files. In the latter case the command may
1341 * arrive at any time.
1342 * There are reply codes to differentiate these cases for the automation:
1343 * when account information is required for login, the response to
1344 * a successful PASSword command is reply code 332. On the other hand,
1345 * if account information is NOT required for login, the reply to
1346 * a successful PASSword command is 230; and if the account information
1347 * is needed for a command issued later in the dialogue, the server
1348 * should return a 332 or 532 reply depending on whether it stores
1349 * (pending receipt of the ACCounT command) or discards the command,
1354 uint32_t cmdval = cmdio_get_cmd_and_arg();
1356 if (cmdval == const_QUIT) {
1357 WRITE_OK(FTP_GOODBYE);
1360 else if (cmdval == const_USER)
1361 /* This would mean "ok, now give me PASS". */
1362 /*WRITE_OK(FTP_GIVEPWORD);*/
1363 /* vsftpd can be configured to not require that,
1364 * and this also saves one roundtrip:
1366 WRITE_OK(FTP_LOGINOK);
1367 else if (cmdval == const_PASS)
1368 WRITE_OK(FTP_LOGINOK);
1369 else if (cmdval == const_NOOP)
1370 WRITE_OK(FTP_NOOPOK);
1371 else if (cmdval == const_TYPE)
1372 WRITE_OK(FTP_TYPEOK);
1373 else if (cmdval == const_STRU)
1374 WRITE_OK(FTP_STRUOK);
1375 else if (cmdval == const_MODE)
1376 WRITE_OK(FTP_MODEOK);
1377 else if (cmdval == const_ALLO)
1378 WRITE_OK(FTP_ALLOOK);
1379 else if (cmdval == const_SYST)
1380 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1381 else if (cmdval == const_PWD || cmdval == const_XPWD)
1383 else if (cmdval == const_CWD)
1385 else if (cmdval == const_CDUP) /* cd .. */
1387 /* HELP is nearly useless, but we can reuse FEAT for it */
1388 /* lftp uses FEAT */
1389 else if (cmdval == const_HELP || cmdval == const_FEAT)
1390 handle_feat(cmdval == const_HELP
1391 ? STRNUM32(FTP_HELP)
1392 : STRNUM32(FTP_STATOK)
1394 else if (cmdval == const_LIST) /* ls -l */
1396 else if (cmdval == const_NLST) /* "name list", bare ls */
1398 /* SIZE is crucial for wget's download indicator etc */
1399 /* Mozilla, lftp use MDTM (presumably for caching) */
1400 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1401 handle_size_or_mdtm(cmdval == const_SIZE);
1402 else if (cmdval == const_STAT) {
1403 if (G.ftp_arg == NULL)
1408 else if (cmdval == const_PASV)
1410 else if (cmdval == const_EPSV)
1412 else if (cmdval == const_RETR)
1414 else if (cmdval == const_PORT)
1416 else if (cmdval == const_REST)
1418 #if ENABLE_FEATURE_FTPD_WRITE
1419 else if (opts & OPT_w) {
1420 if (cmdval == const_STOR)
1422 else if (cmdval == const_MKD)
1424 else if (cmdval == const_RMD)
1426 else if (cmdval == const_DELE)
1428 else if (cmdval == const_RNFR) /* "rename from" */
1430 else if (cmdval == const_RNTO) /* "rename to" */
1432 else if (cmdval == const_APPE)
1434 else if (cmdval == const_STOU) /* "store unique" */
1441 else if (cmdval == const_STOR
1442 || cmdval == const_MKD
1443 || cmdval == const_RMD
1444 || cmdval == const_DELE
1445 || cmdval == const_RNFR
1446 || cmdval == const_RNTO
1447 || cmdval == const_APPE
1448 || cmdval == const_STOU
1450 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1454 /* Which unsupported commands were seen in the wild?
1455 * (doesn't necessarily mean "we must support them")
1456 * foo 1.2.3: XXXX - comment
1458 #if ENABLE_FEATURE_FTPD_WRITE
1461 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");