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.
18 //config: Simple FTP daemon. You have to run it via inetd.
20 //config:config FEATURE_FTPD_WRITE
21 //config: bool "Enable upload commands"
23 //config: depends on FTPD
25 //config: Enable all kinds of FTP upload commands (-w option)
27 //config:config FEATURE_FTPD_ACCEPT_BROKEN_LIST
28 //config: bool "Enable workaround for RFC-violating clients"
30 //config: depends on FTPD
32 //config: Some ftp clients (among them KDE's Konqueror) issue illegal
33 //config: "LIST -l" requests. This option works around such problems.
34 //config: It might prevent you from listing files starting with "-" and
35 //config: it increases the code size by ~40 bytes.
36 //config: Most other ftp servers seem to behave similar to this.
38 //config:config FEATURE_FTPD_AUTHENTICATION
39 //config: bool "Enable authentication"
41 //config: depends on FTPD
43 //config: Enable basic system login as seen in telnet etc.
45 //applet:IF_FTPD(APPLET(ftpd, BB_DIR_USR_SBIN, BB_SUID_DROP))
47 //kbuild:lib-$(CONFIG_FTPD) += ftpd.o
49 //usage:#define ftpd_trivial_usage
50 //usage: "[-wvS] [-t N] [-T N] [DIR]"
51 //usage:#define ftpd_full_usage "\n\n"
52 //usage: "Anonymous FTP server\n"
54 //usage: "ftpd should be used as an inetd service.\n"
55 //usage: "ftpd's line for inetd.conf:\n"
56 //usage: " 21 stream tcp nowait root ftpd ftpd /files/to/serve\n"
57 //usage: "It also can be ran from tcpsvd:\n"
58 //usage: " tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve\n"
59 //usage: "\n -w Allow upload"
60 //usage: "\n -v Log errors to stderr. -vv: verbose log"
61 //usage: "\n -S Log errors to syslog. -SS: verbose log"
62 //usage: "\n -t,-T Idle and absolute timeouts"
63 //usage: "\n DIR Change root to this directory"
66 #include "common_bufsiz.h"
68 #include <netinet/tcp.h>
70 #define FTP_DATACONN 150
71 #define FTP_NOOPOK 200
72 #define FTP_TYPEOK 200
73 #define FTP_PORTOK 200
74 #define FTP_STRUOK 200
75 #define FTP_MODEOK 200
76 #define FTP_ALLOOK 202
77 #define FTP_STATOK 211
78 #define FTP_STATFILE_OK 213
80 #define FTP_SYSTOK 215
82 #define FTP_GOODBYE 221
83 #define FTP_TRANSFEROK 226
84 #define FTP_PASVOK 227
85 /*#define FTP_EPRTOK 228*/
86 #define FTP_EPSVOK 229
87 #define FTP_LOGINOK 230
89 #define FTP_RMDIROK 250
90 #define FTP_DELEOK 250
91 #define FTP_RENAMEOK 250
93 #define FTP_MKDIROK 257
94 #define FTP_GIVEPWORD 331
95 #define FTP_RESTOK 350
96 #define FTP_RNFROK 350
97 #define FTP_TIMEOUT 421
98 #define FTP_BADSENDCONN 425
99 #define FTP_BADSENDNET 426
100 #define FTP_BADSENDFILE 451
101 #define FTP_BADCMD 500
102 #define FTP_COMMANDNOTIMPL 502
103 #define FTP_NEEDUSER 503
104 #define FTP_NEEDRNFR 503
105 #define FTP_BADSTRU 504
106 #define FTP_BADMODE 504
107 #define FTP_LOGINERR 530
108 #define FTP_FILEFAIL 550
109 #define FTP_NOPERM 550
110 #define FTP_UPLOADFAIL 553
113 #define STR(s) STR1(s)
115 /* Convert a constant to 3-digit string, packed into uint32_t */
117 /* Shift for Nth decimal digit */
118 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
119 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
120 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
121 /* And for 4th position (space) */
122 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
124 #define STRNUM32(s) (uint32_t)(0 \
125 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
126 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
127 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
129 #define STRNUM32sp(s) (uint32_t)(0 \
131 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
132 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
133 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
136 #define MSG_OK "Operation successful\r\n"
137 #define MSG_ERR "Error\r\n"
148 off_t local_file_pos;
150 len_and_sockaddr *local_addr;
151 len_and_sockaddr *port_addr;
154 #if ENABLE_FEATURE_FTPD_WRITE
157 /* We need these aligned to uint32_t */
158 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
159 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
161 #define G (*(struct globals*)bb_common_bufsiz1)
162 #define INIT_G() do { \
163 setup_common_bufsiz(); \
164 /* Moved to main */ \
165 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
166 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
171 escape_text(const char *prepend, const char *str, unsigned escapee)
173 unsigned retlen, remainlen, chunklen;
177 append = (char)escapee;
180 remainlen = strlen(str);
181 retlen = strlen(prepend);
182 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
183 strcpy(ret, prepend);
186 found = strchrnul(str, escapee);
187 chunklen = found - str + 1;
189 /* Copy chunk up to and including escapee (or NUL) to ret */
190 memcpy(ret + retlen, str, chunklen);
193 if (*found == '\0') {
194 /* It wasn't escapee, it was NUL! */
195 ret[retlen - 1] = append; /* replace NUL */
196 ret[retlen] = '\0'; /* add NUL */
199 ret[retlen++] = escapee; /* duplicate escapee */
205 /* Returns strlen as a bonus */
207 replace_char(char *str, char from, char to)
219 verbose_log(const char *str)
221 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
224 /* NB: status_str is char[4] packed into uint32_t */
226 cmdio_write(uint32_t status_str, const char *str)
231 /* FTP uses telnet protocol for command link.
232 * In telnet, 0xff is an escape char, and needs to be escaped: */
233 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
235 /* FTP sends embedded LFs as NULs */
236 len = replace_char(response, '\n', '\0');
238 response[len++] = '\n'; /* tack on trailing '\n' */
239 xwrite(STDOUT_FILENO, response, len);
241 verbose_log(response);
246 cmdio_write_ok(unsigned status)
248 *(uint32_t *) G.msg_ok = status;
249 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
251 verbose_log(G.msg_ok);
253 #define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
255 /* TODO: output strerr(errno) if errno != 0? */
257 cmdio_write_error(unsigned status)
259 *(uint32_t *) G.msg_err = status;
260 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
262 verbose_log(G.msg_err);
264 #define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
267 cmdio_write_raw(const char *p_text)
269 xwrite_str(STDOUT_FILENO, p_text);
275 timeout_handler(int sig UNUSED_PARAM)
278 int sv_errno = errno;
280 if ((int)(monotonic_sec() - G.end_time) >= 0)
283 if (!G.local_file_fd)
286 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
287 if (pos == G.local_file_pos)
289 G.local_file_pos = pos;
296 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
297 /* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
301 /* Simple commands */
306 char *cwd, *response;
308 cwd = xrealloc_getcwd_or_warn(NULL);
312 /* We have to promote each " to "" */
313 response = escape_text(" \"", cwd, ('"' << 8) + '"');
315 cmdio_write(STRNUM32(FTP_PWDOK), response);
322 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
323 WRITE_ERR(FTP_FILEFAIL);
332 G.ftp_arg = (char*)"..";
339 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
341 STR(FTP_STATOK)" Ok\r\n");
344 /* Examples of HELP and FEAT:
345 # nc -vvv ftp.kernel.org 21
346 ftp.kernel.org (130.239.17.4:21) open
347 220 Welcome to ftp.kernel.org.
360 214-The following commands are recognized.
361 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
362 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
363 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
368 handle_feat(unsigned status)
370 cmdio_write(status, "-Features:");
371 cmdio_write_raw(" EPSV\r\n"
376 cmdio_write(status, " Ok");
379 /* Download commands */
384 return (G.port_addr != NULL);
390 return (G.pasv_listen_fd > STDOUT_FILENO);
394 port_pasv_cleanup(void)
398 if (G.pasv_listen_fd > STDOUT_FILENO)
399 close(G.pasv_listen_fd);
400 G.pasv_listen_fd = -1;
403 /* On error, emits error code to the peer */
405 ftpdataio_get_pasv_fd(void)
409 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
412 WRITE_ERR(FTP_BADSENDCONN);
416 setsockopt_keepalive(remote_fd);
420 /* Clears port/pasv data.
421 * This means we dont waste resources, for example, keeping
422 * PASV listening socket open when it is no longer needed.
423 * On error, emits error code to the peer (or exits).
424 * On success, emits p_status_msg to the peer.
427 get_remote_transfer_fd(const char *p_status_msg)
432 /* On error, emits error code to the peer */
433 remote_fd = ftpdataio_get_pasv_fd();
436 remote_fd = xconnect_stream(G.port_addr);
443 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
447 /* If there were neither PASV nor PORT, emits error code to the peer */
449 port_or_pasv_was_seen(void)
451 if (!pasv_active() && !port_active()) {
452 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
461 bind_for_passive_mode(void)
468 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
469 setsockopt_reuseaddr(fd);
471 set_nport(&G.local_addr->u.sa, 0);
472 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
474 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
476 port = get_nport(&G.local_addr->u.sa);
486 char *addr, *response;
488 port = bind_for_passive_mode();
490 if (G.local_addr->u.sa.sa_family == AF_INET)
491 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
492 else /* seen this in the wild done by other ftp servers: */
493 addr = xstrdup("0.0.0.0");
494 replace_char(addr, '.', ',');
496 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
497 addr, (int)(port >> 8), (int)(port & 255));
499 cmdio_write_raw(response);
510 port = bind_for_passive_mode();
511 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
512 cmdio_write_raw(response);
519 unsigned port, port_hi;
521 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
522 socklen_t peer_ipv4_len;
523 struct sockaddr_in peer_ipv4;
524 struct in_addr port_ipv4_sin_addr;
531 /* PORT command format makes sense only over IPv4 */
533 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
534 || G.local_addr->u.sa.sa_family != AF_INET
538 WRITE_ERR(FTP_BADCMD);
542 comma = strrchr(raw, ',');
546 port = bb_strtou(&comma[1], NULL, 10);
547 if (errno || port > 0xff)
550 comma = strrchr(raw, ',');
554 port_hi = bb_strtou(&comma[1], NULL, 10);
555 if (errno || port_hi > 0xff)
557 port |= port_hi << 8;
559 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
560 replace_char(raw, ',', '.');
562 /* We are verifying that PORT's IP matches getpeername().
563 * Otherwise peer can make us open data connections
564 * to other hosts (security problem!)
565 * This code would be too simplistic:
566 * lsa = xdotted2sockaddr(raw, port);
567 * if (lsa == NULL) goto bail;
569 if (!inet_aton(raw, &port_ipv4_sin_addr))
571 peer_ipv4_len = sizeof(peer_ipv4);
572 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
574 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
577 G.port_addr = xdotted2sockaddr(raw, port);
579 G.port_addr = get_peer_lsa(STDIN_FILENO);
580 set_nport(&G.port_addr->u.sa, htons(port));
582 WRITE_OK(FTP_PORTOK);
588 /* When ftp_arg == NULL simply restart from beginning */
589 G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
590 WRITE_OK(FTP_RESTOK);
597 off_t bytes_transferred;
600 off_t offset = G.restart_pos;
605 if (!port_or_pasv_was_seen())
606 return; /* port_or_pasv_was_seen emitted error response */
608 /* O_NONBLOCK is useful if file happens to be a device node */
609 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
610 if (local_file_fd < 0) {
611 WRITE_ERR(FTP_FILEFAIL);
615 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
616 /* Note - pretend open failed */
617 WRITE_ERR(FTP_FILEFAIL);
620 G.local_file_fd = local_file_fd;
622 /* Now deactive O_NONBLOCK, otherwise we have a problem
623 * on DMAPI filesystems such as XFS DMAPI.
625 ndelay_off(local_file_fd);
627 /* Set the download offset (from REST) if any */
629 xlseek(local_file_fd, offset, SEEK_SET);
631 response = xasprintf(
632 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
633 G.ftp_arg, statbuf.st_size);
634 remote_fd = get_remote_transfer_fd(response);
639 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
641 if (bytes_transferred < 0)
642 WRITE_ERR(FTP_BADSENDFILE);
644 WRITE_OK(FTP_TRANSFEROK);
647 close(local_file_fd);
654 popen_ls(const char *opt)
657 struct fd_pair outfd;
661 argv[1] = opt; /* "-lA" or "-1A" */
666 /* Improve compatibility with non-RFC conforming FTP clients
667 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
668 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
669 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
670 && G.ftp_arg && G.ftp_arg[0] == '-'
672 const char *tmp = strchr(G.ftp_arg, ' ');
673 if (tmp) /* skip the space */
680 /*fflush_all(); - so far we dont use stdio on output */
681 pid = BB_MMU ? xfork() : xvfork();
687 /* NB: close _first_, then move fd! */
689 xmove_fd(outfd.wr, STDOUT_FILENO);
690 /* Opening /dev/null in chroot is hard.
691 * Just making sure STDIN_FILENO is opened
692 * to something harmless. Paranoia,
693 * ls won't read it anyway */
695 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
697 /* memset(&G, 0, sizeof(G)); - ls_main does it */
698 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
700 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
701 /* On NOMMU, we want to execute a child - copy of ourself
702 * in order to unblock parent after vfork.
703 * In chroot we usually can't re-exec. Thus we escape
704 * out of the chroot back to original root.
706 if (G.root_fd >= 0) {
707 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
709 /*close(G.root_fd); - close_on_exec_on() took care of this */
711 /* Child expects directory to list on fd #3 */
713 execv(bb_busybox_exec_path, (char**) argv);
729 handle_dir_common(int opts)
735 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
736 return; /* port_or_pasv_was_seen emitted error response */
738 ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
739 ls_fp = xfdopen_for_read(ls_fd);
740 /* FIXME: filenames with embedded newlines are mishandled */
742 if (opts & USE_CTRL_CONN) {
743 /* STAT <filename> */
744 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
746 line = xmalloc_fgetline(ls_fp);
749 /* Hack: 0 results in no status at all */
750 /* Note: it's ok that we don't prepend space,
751 * ftp.kernel.org doesn't do that too */
752 cmdio_write(0, line);
755 WRITE_OK(FTP_STATFILE_OK);
757 /* LIST/NLST [<filename>] */
758 int remote_fd = get_remote_transfer_fd(" Directory listing");
759 if (remote_fd >= 0) {
763 line = xmalloc_fgets(ls_fp);
766 /* I've seen clients complaining when they
767 * are fed with ls output with bare '\n'.
768 * Replace trailing "\n\0" with "\r\n".
771 if (len != 0) /* paranoia check */
772 line[len - 1] = '\r';
774 xwrite(remote_fd, line, len + 1);
779 WRITE_OK(FTP_TRANSFEROK);
781 fclose(ls_fp); /* closes ls_fd too */
786 handle_dir_common(LONG_LISTING);
791 /* NLST returns list of names, "\r\n" terminated without regard
792 * to the current binary flag. Names may start with "/",
793 * then they represent full names (we don't produce such names),
794 * otherwise names are relative to current directory.
795 * Embedded "\n" are replaced by NULs. This is safe since names
796 * can never contain NUL.
798 handle_dir_common(0);
801 handle_stat_file(void)
803 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
806 /* This can be extended to handle MLST, as all info is available
807 * in struct stat for that:
809 * 250-Listing file_name
810 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
813 * MLST [<file or dir name, "." assumed if not given>]
814 * Returned name should be either the same as requested, or fully qualified.
815 * If there was no parameter, return "" or (preferred) fully-qualified name.
816 * Returned "facts" (case is not important):
817 * size - size in octets
818 * modify - last modification time
819 * type - entry type (file,dir,OS.unix=block)
820 * (+ cdir and pdir types for MLSD)
821 * unique - unique id of file/directory (inode#)
823 * a: can be appended to (APPE)
824 * d: can be deleted (RMD/DELE)
825 * f: can be renamed (RNFR)
826 * r: can be read (RETR)
827 * w: can be written (STOR)
828 * e: can CWD into this dir
829 * l: this dir can be listed (dir only!)
830 * c: can create files in this dir
831 * m: can create dirs in this dir (MKD)
832 * p: can delete files in this dir
833 * UNIX.mode - unix file mode
836 handle_size_or_mdtm(int need_size)
839 struct tm broken_out;
840 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
841 | sizeof("NNN YYYYMMDDhhmmss\r\n")
845 || stat(G.ftp_arg, &statbuf) != 0
846 || !S_ISREG(statbuf.st_mode)
848 WRITE_ERR(FTP_FILEFAIL);
852 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
854 gmtime_r(&statbuf.st_mtime, &broken_out);
855 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
856 broken_out.tm_year + 1900,
857 broken_out.tm_mon + 1,
863 cmdio_write_raw(buf);
866 /* Upload commands */
868 #if ENABLE_FEATURE_FTPD_WRITE
872 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
873 WRITE_ERR(FTP_FILEFAIL);
876 WRITE_OK(FTP_MKDIROK);
882 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
883 WRITE_ERR(FTP_FILEFAIL);
886 WRITE_OK(FTP_RMDIROK);
892 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
893 WRITE_ERR(FTP_FILEFAIL);
896 WRITE_OK(FTP_DELEOK);
902 free(G.rnfr_filename);
903 G.rnfr_filename = xstrdup(G.ftp_arg);
904 WRITE_OK(FTP_RNFROK);
912 /* If we didn't get a RNFR, throw a wobbly */
913 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
914 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
918 retval = rename(G.rnfr_filename, G.ftp_arg);
919 free(G.rnfr_filename);
920 G.rnfr_filename = NULL;
923 WRITE_ERR(FTP_FILEFAIL);
926 WRITE_OK(FTP_RENAMEOK);
930 handle_upload_common(int is_append, int is_unique)
934 off_t bytes_transferred;
939 offset = G.restart_pos;
942 if (!port_or_pasv_was_seen())
943 return; /* port_or_pasv_was_seen emitted error response */
948 tempname = xstrdup(" FILE: uniq.XXXXXX");
949 local_file_fd = mkstemp(tempname + 7);
950 } else if (G.ftp_arg) {
951 int flags = O_WRONLY | O_CREAT | O_TRUNC;
953 flags = O_WRONLY | O_CREAT | O_APPEND;
955 flags = O_WRONLY | O_CREAT;
956 local_file_fd = open(G.ftp_arg, flags, 0666);
959 if (local_file_fd < 0
960 || fstat(local_file_fd, &statbuf) != 0
961 || !S_ISREG(statbuf.st_mode)
964 WRITE_ERR(FTP_UPLOADFAIL);
965 if (local_file_fd >= 0)
966 goto close_local_and_bail;
969 G.local_file_fd = local_file_fd;
972 xlseek(local_file_fd, offset, SEEK_SET);
974 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
978 goto close_local_and_bail;
980 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
982 if (bytes_transferred < 0)
983 WRITE_ERR(FTP_BADSENDFILE);
985 WRITE_OK(FTP_TRANSFEROK);
987 close_local_and_bail:
988 close(local_file_fd);
995 handle_upload_common(0, 0);
1002 handle_upload_common(1, 0);
1009 handle_upload_common(0, 1);
1011 #endif /* ENABLE_FEATURE_FTPD_WRITE */
1014 cmdio_get_cmd_and_arg(void)
1024 /* Paranoia. Peer may send 1 gigabyte long cmd... */
1025 /* Using separate len_on_stk instead of len optimizes
1026 * code size (allows len to be in CPU register) */
1027 size_t len_on_stk = 8 * 1024;
1028 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
1034 /* De-escape telnet: 0xff,0xff => 0xff */
1035 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1036 * data transfer, and may be preceded by telnet's "Interrupt Process"
1037 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1038 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1039 * and may generate SIGURG on our side. See RFC854).
1040 * So far we don't support that (may install SIGURG handler if we'd want to),
1041 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1042 /* Then de-escape FTP: NUL => '\n' */
1043 /* Testing for \xff:
1044 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1045 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1046 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1047 * Testing for embedded LF:
1048 * LF_HERE=`echo -ne "LF\nHERE"`
1049 * echo Hello >"$LF_HERE"
1050 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1055 /* Strip "\r\n" if it is there */
1056 if (len != 0 && cmd[len - 1] == '\n') {
1058 if (len != 0 && cmd[len - 1] == '\r')
1062 src = strchrnul(cmd, 0xff) - cmd;
1063 /* 99,99% there are neither NULs nor 255s and src == len */
1067 if ((unsigned char)(cmd[src]) == 255) {
1069 /* 255,xxx - skip 255 */
1070 if ((unsigned char)(cmd[src]) != 255) {
1071 /* 255,!255 - skip both */
1075 /* 255,255 - retain one 255 */
1078 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1080 } while (src < len);
1088 G.ftp_arg = strchr(cmd, ' ');
1089 if (G.ftp_arg != NULL)
1090 *G.ftp_arg++ = '\0';
1092 /* Uppercase and pack into uint32_t first word of the command */
1095 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1100 #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1101 #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1103 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1104 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1105 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1106 const_CWD = mk_const3('C', 'W', 'D'),
1107 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1108 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
1109 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
1110 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1111 const_LIST = mk_const4('L', 'I', 'S', 'T'),
1112 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
1113 const_MKD = mk_const3('M', 'K', 'D'),
1114 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1115 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1116 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1117 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1118 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1119 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1120 const_PWD = mk_const3('P', 'W', 'D'),
1121 /* Same as PWD. Reportedly used by windows ftp client */
1122 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
1123 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1124 const_REST = mk_const4('R', 'E', 'S', 'T'),
1125 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1126 const_RMD = mk_const3('R', 'M', 'D'),
1127 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1128 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1129 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1130 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1131 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1132 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1133 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1134 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1135 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1136 const_USER = mk_const4('U', 'S', 'E', 'R'),
1143 OPT_v = (1 << ((!BB_MMU) * 3 + 0)),
1144 OPT_S = (1 << ((!BB_MMU) * 3 + 1)),
1145 OPT_w = (1 << ((!BB_MMU) * 3 + 2)) * ENABLE_FEATURE_FTPD_WRITE,
1148 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1150 int ftpd_main(int argc, char **argv)
1152 int ftpd_main(int argc UNUSED_PARAM, char **argv)
1155 #if ENABLE_FEATURE_FTPD_AUTHENTICATION
1156 struct passwd *pw = NULL;
1158 unsigned abs_timeout;
1164 abs_timeout = 1 * 60 * 60;
1167 opt_complementary = "vv:SS";
1169 opts = getopt32(argv, "vS" IF_FEATURE_FTPD_WRITE("w") "t:+T:+", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1171 opts = getopt32(argv, "l1AvS" IF_FEATURE_FTPD_WRITE("w") "t:+T:+", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1172 if (opts & (OPT_l|OPT_1)) {
1173 /* Our secret backdoor to ls */
1174 /* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
1177 /* memset(&G, 0, sizeof(G)); - ls_main does it */
1178 return ls_main(argc, argv);
1181 if (G.verbose < verbose_S)
1182 G.verbose = verbose_S;
1183 if (abs_timeout | G.timeout) {
1184 if (abs_timeout == 0)
1185 abs_timeout = INT_MAX;
1186 G.end_time = monotonic_sec() + abs_timeout;
1187 if (G.timeout > abs_timeout)
1188 G.timeout = abs_timeout;
1190 strcpy(G.msg_ok + 4, MSG_OK );
1191 strcpy(G.msg_err + 4, MSG_ERR);
1193 G.local_addr = get_sock_lsa(STDIN_FILENO);
1194 if (!G.local_addr) {
1195 /* This is confusing:
1196 * bb_error_msg_and_die("stdin is not a socket");
1199 /* Help text says that ftpd must be used as inetd service,
1200 * which is by far the most usual cause of get_sock_lsa
1204 if (!(opts & OPT_v))
1205 logmode = LOGMODE_NONE;
1207 /* LOG_NDELAY is needed since we may chroot later */
1208 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1209 logmode |= LOGMODE_SYSLOG;
1212 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1214 //umask(077); - admin can set umask before starting us
1218 /* We'll always take EPIPE rather than a rude signal, thanks */
1220 /* LIST command spawns chilren. Prevent zombies */
1224 /* Set up options on the command socket (do we need these all? why?) */
1225 setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
1226 setsockopt_keepalive(STDIN_FILENO);
1227 /* Telnet protocol over command link may send "urgent" data,
1228 * we prefer it to be received in the "normal" data stream: */
1229 setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
1231 WRITE_OK(FTP_GREET);
1232 signal(SIGALRM, timeout_handler);
1234 #if ENABLE_FEATURE_FTPD_AUTHENTICATION
1236 uint32_t cmdval = cmdio_get_cmd_and_arg();
1237 if (cmdval == const_USER) {
1238 pw = getpwnam(G.ftp_arg);
1239 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify password\r\n");
1240 } else if (cmdval == const_PASS) {
1241 if (check_password(pw, G.ftp_arg) > 0) {
1242 break; /* login success */
1244 cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
1246 } else if (cmdval == const_QUIT) {
1247 WRITE_OK(FTP_GOODBYE);
1250 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1253 WRITE_OK(FTP_LOGINOK);
1256 /* Do this after auth, else /etc/passwd is not accessible */
1262 const char *basedir = argv[0];
1264 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1265 close_on_exec_on(G.root_fd);
1267 if (chroot(basedir) == 0)
1276 * If chroot failed, assume that we aren't root,
1277 * and at least chdir to the specified DIR
1278 * (older versions were dying with error message).
1279 * If chroot worked, move current dir to new "/":
1284 #if ENABLE_FEATURE_FTPD_AUTHENTICATION
1285 change_identity(pw);
1288 /* RFC-959 Section 5.1
1289 * The following commands and options MUST be supported by every
1290 * server-FTP and user-FTP, except in cases where the underlying
1291 * file system or operating system does not allow or support
1292 * a particular command.
1293 * Type: ASCII Non-print, IMAGE, LOCAL 8
1295 * Structure: File, Record*
1296 * (Record structure is REQUIRED only for hosts whose file
1297 * systems support record structure).
1299 * USER, PASS, ACCT, [bbox: ACCT not supported]
1304 * CWD, CDUP, RMD, MKD, PWD,
1310 * "The argument field is a Telnet string identifying the user's account.
1311 * The command is not necessarily related to the USER command, as some
1312 * sites may require an account for login and others only for specific
1313 * access, such as storing files. In the latter case the command may
1314 * arrive at any time.
1315 * There are reply codes to differentiate these cases for the automation:
1316 * when account information is required for login, the response to
1317 * a successful PASSword command is reply code 332. On the other hand,
1318 * if account information is NOT required for login, the reply to
1319 * a successful PASSword command is 230; and if the account information
1320 * is needed for a command issued later in the dialogue, the server
1321 * should return a 332 or 532 reply depending on whether it stores
1322 * (pending receipt of the ACCounT command) or discards the command,
1327 uint32_t cmdval = cmdio_get_cmd_and_arg();
1329 if (cmdval == const_QUIT) {
1330 WRITE_OK(FTP_GOODBYE);
1333 else if (cmdval == const_USER)
1334 /* This would mean "ok, now give me PASS". */
1335 /*WRITE_OK(FTP_GIVEPWORD);*/
1336 /* vsftpd can be configured to not require that,
1337 * and this also saves one roundtrip:
1339 WRITE_OK(FTP_LOGINOK);
1340 else if (cmdval == const_PASS)
1341 WRITE_OK(FTP_LOGINOK);
1342 else if (cmdval == const_NOOP)
1343 WRITE_OK(FTP_NOOPOK);
1344 else if (cmdval == const_TYPE)
1345 WRITE_OK(FTP_TYPEOK);
1346 else if (cmdval == const_STRU)
1347 WRITE_OK(FTP_STRUOK);
1348 else if (cmdval == const_MODE)
1349 WRITE_OK(FTP_MODEOK);
1350 else if (cmdval == const_ALLO)
1351 WRITE_OK(FTP_ALLOOK);
1352 else if (cmdval == const_SYST)
1353 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1354 else if (cmdval == const_PWD || cmdval == const_XPWD)
1356 else if (cmdval == const_CWD)
1358 else if (cmdval == const_CDUP) /* cd .. */
1360 /* HELP is nearly useless, but we can reuse FEAT for it */
1361 /* lftp uses FEAT */
1362 else if (cmdval == const_HELP || cmdval == const_FEAT)
1363 handle_feat(cmdval == const_HELP
1364 ? STRNUM32(FTP_HELP)
1365 : STRNUM32(FTP_STATOK)
1367 else if (cmdval == const_LIST) /* ls -l */
1369 else if (cmdval == const_NLST) /* "name list", bare ls */
1371 /* SIZE is crucial for wget's download indicator etc */
1372 /* Mozilla, lftp use MDTM (presumably for caching) */
1373 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1374 handle_size_or_mdtm(cmdval == const_SIZE);
1375 else if (cmdval == const_STAT) {
1376 if (G.ftp_arg == NULL)
1381 else if (cmdval == const_PASV)
1383 else if (cmdval == const_EPSV)
1385 else if (cmdval == const_RETR)
1387 else if (cmdval == const_PORT)
1389 else if (cmdval == const_REST)
1391 #if ENABLE_FEATURE_FTPD_WRITE
1392 else if (opts & OPT_w) {
1393 if (cmdval == const_STOR)
1395 else if (cmdval == const_MKD)
1397 else if (cmdval == const_RMD)
1399 else if (cmdval == const_DELE)
1401 else if (cmdval == const_RNFR) /* "rename from" */
1403 else if (cmdval == const_RNTO) /* "rename to" */
1405 else if (cmdval == const_APPE)
1407 else if (cmdval == const_STOU) /* "store unique" */
1414 else if (cmdval == const_STOR
1415 || cmdval == const_MKD
1416 || cmdval == const_RMD
1417 || cmdval == const_DELE
1418 || cmdval == const_RNFR
1419 || cmdval == const_RNTO
1420 || cmdval == const_APPE
1421 || cmdval == const_STOU
1423 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1427 /* Which unsupported commands were seen in the wild?
1428 * (doesn't necessarily mean "we must support them")
1429 * foo 1.2.3: XXXX - comment
1431 #if ENABLE_FEATURE_FTPD_WRITE
1434 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");