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 //usage:#define ftpd_trivial_usage
16 //usage: "[-wvS] [-t N] [-T N] [DIR]"
17 //usage:#define ftpd_full_usage "\n\n"
18 //usage: "Anonymous FTP server\n"
20 //usage: "ftpd should be used as an inetd service.\n"
21 //usage: "ftpd's line for inetd.conf:\n"
22 //usage: " 21 stream tcp nowait root ftpd ftpd /files/to/serve\n"
23 //usage: "It also can be ran from tcpsvd:\n"
24 //usage: " tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve\n"
25 //usage: "\n -w Allow upload"
26 //usage: "\n -v Log errors to stderr. -vv: verbose log"
27 //usage: "\n -S Log errors to syslog. -SS: verbose log"
28 //usage: "\n -t,-T Idle and absolute timeouts"
29 //usage: "\n DIR Change root to this directory"
33 #include <netinet/tcp.h>
35 #define FTP_DATACONN 150
36 #define FTP_NOOPOK 200
37 #define FTP_TYPEOK 200
38 #define FTP_PORTOK 200
39 #define FTP_STRUOK 200
40 #define FTP_MODEOK 200
41 #define FTP_ALLOOK 202
42 #define FTP_STATOK 211
43 #define FTP_STATFILE_OK 213
45 #define FTP_SYSTOK 215
47 #define FTP_GOODBYE 221
48 #define FTP_TRANSFEROK 226
49 #define FTP_PASVOK 227
50 /*#define FTP_EPRTOK 228*/
51 #define FTP_EPSVOK 229
52 #define FTP_LOGINOK 230
54 #define FTP_RMDIROK 250
55 #define FTP_DELEOK 250
56 #define FTP_RENAMEOK 250
58 #define FTP_MKDIROK 257
59 #define FTP_GIVEPWORD 331
60 #define FTP_RESTOK 350
61 #define FTP_RNFROK 350
62 #define FTP_TIMEOUT 421
63 #define FTP_BADSENDCONN 425
64 #define FTP_BADSENDNET 426
65 #define FTP_BADSENDFILE 451
66 #define FTP_BADCMD 500
67 #define FTP_COMMANDNOTIMPL 502
68 #define FTP_NEEDUSER 503
69 #define FTP_NEEDRNFR 503
70 #define FTP_BADSTRU 504
71 #define FTP_BADMODE 504
72 #define FTP_LOGINERR 530
73 #define FTP_FILEFAIL 550
74 #define FTP_NOPERM 550
75 #define FTP_UPLOADFAIL 553
78 #define STR(s) STR1(s)
80 /* Convert a constant to 3-digit string, packed into uint32_t */
82 /* Shift for Nth decimal digit */
83 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
84 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
85 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
86 /* And for 4th position (space) */
87 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
89 #define STRNUM32(s) (uint32_t)(0 \
90 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
91 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
92 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
94 #define STRNUM32sp(s) (uint32_t)(0 \
96 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
97 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
98 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
101 #define MSG_OK "Operation successful\r\n"
102 #define MSG_ERR "Error\r\n"
113 off_t local_file_pos;
115 len_and_sockaddr *local_addr;
116 len_and_sockaddr *port_addr;
119 #if ENABLE_FEATURE_FTP_WRITE
122 /* We need these aligned to uint32_t */
123 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
124 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
126 #define G (*(struct globals*)&bb_common_bufsiz1)
127 #define INIT_G() do { \
128 /* Moved to main */ \
129 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
130 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
135 escape_text(const char *prepend, const char *str, unsigned escapee)
137 unsigned retlen, remainlen, chunklen;
141 append = (char)escapee;
144 remainlen = strlen(str);
145 retlen = strlen(prepend);
146 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
147 strcpy(ret, prepend);
150 found = strchrnul(str, escapee);
151 chunklen = found - str + 1;
153 /* Copy chunk up to and including escapee (or NUL) to ret */
154 memcpy(ret + retlen, str, chunklen);
157 if (*found == '\0') {
158 /* It wasn't escapee, it was NUL! */
159 ret[retlen - 1] = append; /* replace NUL */
160 ret[retlen] = '\0'; /* add NUL */
163 ret[retlen++] = escapee; /* duplicate escapee */
169 /* Returns strlen as a bonus */
171 replace_char(char *str, char from, char to)
183 verbose_log(const char *str)
185 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
188 /* NB: status_str is char[4] packed into uint32_t */
190 cmdio_write(uint32_t status_str, const char *str)
195 /* FTP uses telnet protocol for command link.
196 * In telnet, 0xff is an escape char, and needs to be escaped: */
197 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
199 /* FTP sends embedded LFs as NULs */
200 len = replace_char(response, '\n', '\0');
202 response[len++] = '\n'; /* tack on trailing '\n' */
203 xwrite(STDOUT_FILENO, response, len);
205 verbose_log(response);
210 cmdio_write_ok(unsigned status)
212 *(uint32_t *) G.msg_ok = status;
213 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
215 verbose_log(G.msg_ok);
217 #define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
219 /* TODO: output strerr(errno) if errno != 0? */
221 cmdio_write_error(unsigned status)
223 *(uint32_t *) G.msg_err = status;
224 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
226 verbose_log(G.msg_err);
228 #define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
231 cmdio_write_raw(const char *p_text)
233 xwrite_str(STDOUT_FILENO, p_text);
239 timeout_handler(int sig UNUSED_PARAM)
242 int sv_errno = errno;
244 if ((int)(monotonic_sec() - G.end_time) >= 0)
247 if (!G.local_file_fd)
250 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
251 if (pos == G.local_file_pos)
253 G.local_file_pos = pos;
260 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
261 /* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
265 /* Simple commands */
270 char *cwd, *response;
272 cwd = xrealloc_getcwd_or_warn(NULL);
276 /* We have to promote each " to "" */
277 response = escape_text(" \"", cwd, ('"' << 8) + '"');
279 cmdio_write(STRNUM32(FTP_PWDOK), response);
286 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
287 WRITE_ERR(FTP_FILEFAIL);
296 G.ftp_arg = (char*)"..";
303 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
305 STR(FTP_STATOK)" Ok\r\n");
308 /* Examples of HELP and FEAT:
309 # nc -vvv ftp.kernel.org 21
310 ftp.kernel.org (130.239.17.4:21) open
311 220 Welcome to ftp.kernel.org.
324 214-The following commands are recognized.
325 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
326 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
327 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
332 handle_feat(unsigned status)
334 cmdio_write(status, "-Features:");
335 cmdio_write_raw(" EPSV\r\n"
340 cmdio_write(status, " Ok");
343 /* Download commands */
348 return (G.port_addr != NULL);
354 return (G.pasv_listen_fd > STDOUT_FILENO);
358 port_pasv_cleanup(void)
362 if (G.pasv_listen_fd > STDOUT_FILENO)
363 close(G.pasv_listen_fd);
364 G.pasv_listen_fd = -1;
367 /* On error, emits error code to the peer */
369 ftpdataio_get_pasv_fd(void)
373 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
376 WRITE_ERR(FTP_BADSENDCONN);
380 setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
384 /* Clears port/pasv data.
385 * This means we dont waste resources, for example, keeping
386 * PASV listening socket open when it is no longer needed.
387 * On error, emits error code to the peer (or exits).
388 * On success, emits p_status_msg to the peer.
391 get_remote_transfer_fd(const char *p_status_msg)
396 /* On error, emits error code to the peer */
397 remote_fd = ftpdataio_get_pasv_fd();
400 remote_fd = xconnect_stream(G.port_addr);
407 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
411 /* If there were neither PASV nor PORT, emits error code to the peer */
413 port_or_pasv_was_seen(void)
415 if (!pasv_active() && !port_active()) {
416 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
425 bind_for_passive_mode(void)
432 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
433 setsockopt_reuseaddr(fd);
435 set_nport(&G.local_addr->u.sa, 0);
436 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
438 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
440 port = get_nport(&G.local_addr->u.sa);
450 char *addr, *response;
452 port = bind_for_passive_mode();
454 if (G.local_addr->u.sa.sa_family == AF_INET)
455 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
456 else /* seen this in the wild done by other ftp servers: */
457 addr = xstrdup("0.0.0.0");
458 replace_char(addr, '.', ',');
460 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
461 addr, (int)(port >> 8), (int)(port & 255));
463 cmdio_write_raw(response);
474 port = bind_for_passive_mode();
475 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
476 cmdio_write_raw(response);
483 unsigned port, port_hi;
485 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
486 socklen_t peer_ipv4_len;
487 struct sockaddr_in peer_ipv4;
488 struct in_addr port_ipv4_sin_addr;
495 /* PORT command format makes sense only over IPv4 */
497 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
498 || G.local_addr->u.sa.sa_family != AF_INET
502 WRITE_ERR(FTP_BADCMD);
506 comma = strrchr(raw, ',');
510 port = bb_strtou(&comma[1], NULL, 10);
511 if (errno || port > 0xff)
514 comma = strrchr(raw, ',');
518 port_hi = bb_strtou(&comma[1], NULL, 10);
519 if (errno || port_hi > 0xff)
521 port |= port_hi << 8;
523 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
524 replace_char(raw, ',', '.');
526 /* We are verifying that PORT's IP matches getpeername().
527 * Otherwise peer can make us open data connections
528 * to other hosts (security problem!)
529 * This code would be too simplistic:
530 * lsa = xdotted2sockaddr(raw, port);
531 * if (lsa == NULL) goto bail;
533 if (!inet_aton(raw, &port_ipv4_sin_addr))
535 peer_ipv4_len = sizeof(peer_ipv4);
536 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
538 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
541 G.port_addr = xdotted2sockaddr(raw, port);
543 G.port_addr = get_peer_lsa(STDIN_FILENO);
544 set_nport(&G.port_addr->u.sa, htons(port));
546 WRITE_OK(FTP_PORTOK);
552 /* When ftp_arg == NULL simply restart from beginning */
553 G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
554 WRITE_OK(FTP_RESTOK);
561 off_t bytes_transferred;
564 off_t offset = G.restart_pos;
569 if (!port_or_pasv_was_seen())
570 return; /* port_or_pasv_was_seen emitted error response */
572 /* O_NONBLOCK is useful if file happens to be a device node */
573 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
574 if (local_file_fd < 0) {
575 WRITE_ERR(FTP_FILEFAIL);
579 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
580 /* Note - pretend open failed */
581 WRITE_ERR(FTP_FILEFAIL);
584 G.local_file_fd = local_file_fd;
586 /* Now deactive O_NONBLOCK, otherwise we have a problem
587 * on DMAPI filesystems such as XFS DMAPI.
589 ndelay_off(local_file_fd);
591 /* Set the download offset (from REST) if any */
593 xlseek(local_file_fd, offset, SEEK_SET);
595 response = xasprintf(
596 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
597 G.ftp_arg, statbuf.st_size);
598 remote_fd = get_remote_transfer_fd(response);
603 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
605 if (bytes_transferred < 0)
606 WRITE_ERR(FTP_BADSENDFILE);
608 WRITE_OK(FTP_TRANSFEROK);
611 close(local_file_fd);
618 popen_ls(const char *opt)
621 struct fd_pair outfd;
624 int cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
627 argv[1] = opt; /* "-l" or "-1" */
632 /* Improve compatibility with non-RFC conforming FTP clients
633 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
634 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
635 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
636 && G.ftp_arg && G.ftp_arg[0] == '-'
638 const char *tmp = strchr(G.ftp_arg, ' ');
639 if (tmp) /* skip the space */
646 /*fflush_all(); - so far we dont use stdio on output */
647 pid = BB_MMU ? xfork() : xvfork();
650 /* NB: close _first_, then move fd! */
652 xmove_fd(outfd.wr, STDOUT_FILENO);
653 /* Opening /dev/null in chroot is hard.
654 * Just making sure STDIN_FILENO is opened
655 * to something harmless. Paranoia,
656 * ls won't read it anyway */
658 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
660 /* memset(&G, 0, sizeof(G)); - ls_main does it */
661 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
663 /* On NOMMU, we want to execute a child - copy of ourself
664 * in order to unblock parent after vfork.
665 * In chroot we usually can't re-exec. Thus we escape
666 * out of the chroot back to original root.
668 if (G.root_fd >= 0) {
669 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
671 /*close(G.root_fd); - close_on_exec_on() took care of this */
673 /* Child expects directory to list on fd #3 */
675 execv(bb_busybox_exec_path, (char**) argv);
691 handle_dir_common(int opts)
697 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
698 return; /* port_or_pasv_was_seen emitted error response */
700 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
701 ls_fp = xfdopen_for_read(ls_fd);
702 /* FIXME: filenames with embedded newlines are mishandled */
704 if (opts & USE_CTRL_CONN) {
705 /* STAT <filename> */
706 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
708 line = xmalloc_fgetline(ls_fp);
711 /* Hack: 0 results in no status at all */
712 /* Note: it's ok that we don't prepend space,
713 * ftp.kernel.org doesn't do that too */
714 cmdio_write(0, line);
717 WRITE_OK(FTP_STATFILE_OK);
719 /* LIST/NLST [<filename>] */
720 int remote_fd = get_remote_transfer_fd(" Directory listing");
721 if (remote_fd >= 0) {
725 line = xmalloc_fgets(ls_fp);
728 /* I've seen clients complaining when they
729 * are fed with ls output with bare '\n'.
730 * Replace trailing "\n\0" with "\r\n".
733 if (len != 0) /* paranoia check */
734 line[len - 1] = '\r';
736 xwrite(remote_fd, line, len + 1);
741 WRITE_OK(FTP_TRANSFEROK);
743 fclose(ls_fp); /* closes ls_fd too */
748 handle_dir_common(LONG_LISTING);
753 /* NLST returns list of names, "\r\n" terminated without regard
754 * to the current binary flag. Names may start with "/",
755 * then they represent full names (we don't produce such names),
756 * otherwise names are relative to current directory.
757 * Embedded "\n" are replaced by NULs. This is safe since names
758 * can never contain NUL.
760 handle_dir_common(0);
763 handle_stat_file(void)
765 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
768 /* This can be extended to handle MLST, as all info is available
769 * in struct stat for that:
771 * 250-Listing file_name
772 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
775 * MLST [<file or dir name, "." assumed if not given>]
776 * Returned name should be either the same as requested, or fully qualified.
777 * If there was no parameter, return "" or (preferred) fully-qualified name.
778 * Returned "facts" (case is not important):
779 * size - size in octets
780 * modify - last modification time
781 * type - entry type (file,dir,OS.unix=block)
782 * (+ cdir and pdir types for MLSD)
783 * unique - unique id of file/directory (inode#)
785 * a: can be appended to (APPE)
786 * d: can be deleted (RMD/DELE)
787 * f: can be renamed (RNFR)
788 * r: can be read (RETR)
789 * w: can be written (STOR)
790 * e: can CWD into this dir
791 * l: this dir can be listed (dir only!)
792 * c: can create files in this dir
793 * m: can create dirs in this dir (MKD)
794 * p: can delete files in this dir
795 * UNIX.mode - unix file mode
798 handle_size_or_mdtm(int need_size)
801 struct tm broken_out;
802 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
803 | sizeof("NNN YYYYMMDDhhmmss\r\n")
807 || stat(G.ftp_arg, &statbuf) != 0
808 || !S_ISREG(statbuf.st_mode)
810 WRITE_ERR(FTP_FILEFAIL);
814 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
816 gmtime_r(&statbuf.st_mtime, &broken_out);
817 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
818 broken_out.tm_year + 1900,
819 broken_out.tm_mon + 1,
825 cmdio_write_raw(buf);
828 /* Upload commands */
830 #if ENABLE_FEATURE_FTP_WRITE
834 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
835 WRITE_ERR(FTP_FILEFAIL);
838 WRITE_OK(FTP_MKDIROK);
844 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
845 WRITE_ERR(FTP_FILEFAIL);
848 WRITE_OK(FTP_RMDIROK);
854 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
855 WRITE_ERR(FTP_FILEFAIL);
858 WRITE_OK(FTP_DELEOK);
864 free(G.rnfr_filename);
865 G.rnfr_filename = xstrdup(G.ftp_arg);
866 WRITE_OK(FTP_RNFROK);
874 /* If we didn't get a RNFR, throw a wobbly */
875 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
876 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
880 retval = rename(G.rnfr_filename, G.ftp_arg);
881 free(G.rnfr_filename);
882 G.rnfr_filename = NULL;
885 WRITE_ERR(FTP_FILEFAIL);
888 WRITE_OK(FTP_RENAMEOK);
892 handle_upload_common(int is_append, int is_unique)
896 off_t bytes_transferred;
901 offset = G.restart_pos;
904 if (!port_or_pasv_was_seen())
905 return; /* port_or_pasv_was_seen emitted error response */
910 tempname = xstrdup(" FILE: uniq.XXXXXX");
911 local_file_fd = mkstemp(tempname + 7);
912 } else if (G.ftp_arg) {
913 int flags = O_WRONLY | O_CREAT | O_TRUNC;
915 flags = O_WRONLY | O_CREAT | O_APPEND;
917 flags = O_WRONLY | O_CREAT;
918 local_file_fd = open(G.ftp_arg, flags, 0666);
921 if (local_file_fd < 0
922 || fstat(local_file_fd, &statbuf) != 0
923 || !S_ISREG(statbuf.st_mode)
926 WRITE_ERR(FTP_UPLOADFAIL);
927 if (local_file_fd >= 0)
928 goto close_local_and_bail;
931 G.local_file_fd = local_file_fd;
934 xlseek(local_file_fd, offset, SEEK_SET);
936 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
940 goto close_local_and_bail;
942 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
944 if (bytes_transferred < 0)
945 WRITE_ERR(FTP_BADSENDFILE);
947 WRITE_OK(FTP_TRANSFEROK);
949 close_local_and_bail:
950 close(local_file_fd);
957 handle_upload_common(0, 0);
964 handle_upload_common(1, 0);
971 handle_upload_common(0, 1);
973 #endif /* ENABLE_FEATURE_FTP_WRITE */
976 cmdio_get_cmd_and_arg(void)
986 /* Paranoia. Peer may send 1 gigabyte long cmd... */
987 /* Using separate len_on_stk instead of len optimizes
988 * code size (allows len to be in CPU register) */
989 size_t len_on_stk = 8 * 1024;
990 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
996 /* De-escape telnet: 0xff,0xff => 0xff */
997 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
998 * data transfer, and may be preceded by telnet's "Interrupt Process"
999 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1000 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1001 * and may generate SIGURG on our side. See RFC854).
1002 * So far we don't support that (may install SIGURG handler if we'd want to),
1003 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1004 /* Then de-escape FTP: NUL => '\n' */
1005 /* Testing for \xff:
1006 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1007 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1008 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1009 * Testing for embedded LF:
1010 * LF_HERE=`echo -ne "LF\nHERE"`
1011 * echo Hello >"$LF_HERE"
1012 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1017 /* Strip "\r\n" if it is there */
1018 if (len != 0 && cmd[len - 1] == '\n') {
1020 if (len != 0 && cmd[len - 1] == '\r')
1024 src = strchrnul(cmd, 0xff) - cmd;
1025 /* 99,99% there are neither NULs nor 255s and src == len */
1029 if ((unsigned char)(cmd[src]) == 255) {
1031 /* 255,xxx - skip 255 */
1032 if ((unsigned char)(cmd[src]) != 255) {
1033 /* 255,!255 - skip both */
1037 /* 255,255 - retain one 255 */
1040 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1042 } while (src < len);
1050 G.ftp_arg = strchr(cmd, ' ');
1051 if (G.ftp_arg != NULL)
1052 *G.ftp_arg++ = '\0';
1054 /* Uppercase and pack into uint32_t first word of the command */
1057 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1062 #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1063 #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1065 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1066 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1067 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1068 const_CWD = mk_const3('C', 'W', 'D'),
1069 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1070 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
1071 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
1072 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1073 const_LIST = mk_const4('L', 'I', 'S', 'T'),
1074 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
1075 const_MKD = mk_const3('M', 'K', 'D'),
1076 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1077 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1078 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1079 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1080 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1081 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1082 const_PWD = mk_const3('P', 'W', 'D'),
1083 /* Same as PWD. Reportedly used by windows ftp client */
1084 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
1085 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1086 const_REST = mk_const4('R', 'E', 'S', 'T'),
1087 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1088 const_RMD = mk_const3('R', 'M', 'D'),
1089 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1090 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1091 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1092 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1093 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1094 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1095 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1096 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1097 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1098 const_USER = mk_const4('U', 'S', 'E', 'R'),
1104 OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
1105 OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
1106 OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
1109 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1111 int ftpd_main(int argc, char **argv)
1113 int ftpd_main(int argc UNUSED_PARAM, char **argv)
1116 unsigned abs_timeout;
1122 abs_timeout = 1 * 60 * 60;
1125 opt_complementary = "t+:T+:vv:SS";
1127 opts = getopt32(argv, "vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1129 opts = getopt32(argv, "l1vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1130 if (opts & (OPT_l|OPT_1)) {
1131 /* Our secret backdoor to ls */
1132 /* TODO: pass -A? It shows dot files */
1133 /* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
1136 /* memset(&G, 0, sizeof(G)); - ls_main does it */
1137 return ls_main(argc, argv);
1140 if (G.verbose < verbose_S)
1141 G.verbose = verbose_S;
1142 if (abs_timeout | G.timeout) {
1143 if (abs_timeout == 0)
1144 abs_timeout = INT_MAX;
1145 G.end_time = monotonic_sec() + abs_timeout;
1146 if (G.timeout > abs_timeout)
1147 G.timeout = abs_timeout;
1149 strcpy(G.msg_ok + 4, MSG_OK );
1150 strcpy(G.msg_err + 4, MSG_ERR);
1152 G.local_addr = get_sock_lsa(STDIN_FILENO);
1153 if (!G.local_addr) {
1154 /* This is confusing:
1155 * bb_error_msg_and_die("stdin is not a socket");
1158 /* Help text says that ftpd must be used as inetd service,
1159 * which is by far the most usual cause of get_sock_lsa
1163 if (!(opts & OPT_v))
1164 logmode = LOGMODE_NONE;
1166 /* LOG_NDELAY is needed since we may chroot later */
1167 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1168 logmode |= LOGMODE_SYSLOG;
1171 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1174 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1175 close_on_exec_on(G.root_fd);
1182 //umask(077); - admin can set umask before starting us
1184 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
1185 signal(SIGPIPE, SIG_IGN);
1187 /* Set up options on the command socket (do we need these all? why?) */
1188 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1189 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
1190 /* Telnet protocol over command link may send "urgent" data,
1191 * we prefer it to be received in the "normal" data stream: */
1192 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1194 WRITE_OK(FTP_GREET);
1195 signal(SIGALRM, timeout_handler);
1197 #ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
1199 smallint user_was_specified = 0;
1201 uint32_t cmdval = cmdio_get_cmd_and_arg();
1203 if (cmdval == const_USER) {
1204 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
1205 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
1207 user_was_specified = 1;
1208 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
1210 } else if (cmdval == const_PASS) {
1211 if (user_was_specified)
1213 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
1214 } else if (cmdval == const_QUIT) {
1215 WRITE_OK(FTP_GOODBYE);
1218 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1222 WRITE_OK(FTP_LOGINOK);
1225 /* RFC-959 Section 5.1
1226 * The following commands and options MUST be supported by every
1227 * server-FTP and user-FTP, except in cases where the underlying
1228 * file system or operating system does not allow or support
1229 * a particular command.
1230 * Type: ASCII Non-print, IMAGE, LOCAL 8
1232 * Structure: File, Record*
1233 * (Record structure is REQUIRED only for hosts whose file
1234 * systems support record structure).
1236 * USER, PASS, ACCT, [bbox: ACCT not supported]
1241 * CWD, CDUP, RMD, MKD, PWD,
1247 * "The argument field is a Telnet string identifying the user's account.
1248 * The command is not necessarily related to the USER command, as some
1249 * sites may require an account for login and others only for specific
1250 * access, such as storing files. In the latter case the command may
1251 * arrive at any time.
1252 * There are reply codes to differentiate these cases for the automation:
1253 * when account information is required for login, the response to
1254 * a successful PASSword command is reply code 332. On the other hand,
1255 * if account information is NOT required for login, the reply to
1256 * a successful PASSword command is 230; and if the account information
1257 * is needed for a command issued later in the dialogue, the server
1258 * should return a 332 or 532 reply depending on whether it stores
1259 * (pending receipt of the ACCounT command) or discards the command,
1264 uint32_t cmdval = cmdio_get_cmd_and_arg();
1266 if (cmdval == const_QUIT) {
1267 WRITE_OK(FTP_GOODBYE);
1270 else if (cmdval == const_USER)
1271 /* This would mean "ok, now give me PASS". */
1272 /*WRITE_OK(FTP_GIVEPWORD);*/
1273 /* vsftpd can be configured to not require that,
1274 * and this also saves one roundtrip:
1276 WRITE_OK(FTP_LOGINOK);
1277 else if (cmdval == const_PASS)
1278 WRITE_OK(FTP_LOGINOK);
1279 else if (cmdval == const_NOOP)
1280 WRITE_OK(FTP_NOOPOK);
1281 else if (cmdval == const_TYPE)
1282 WRITE_OK(FTP_TYPEOK);
1283 else if (cmdval == const_STRU)
1284 WRITE_OK(FTP_STRUOK);
1285 else if (cmdval == const_MODE)
1286 WRITE_OK(FTP_MODEOK);
1287 else if (cmdval == const_ALLO)
1288 WRITE_OK(FTP_ALLOOK);
1289 else if (cmdval == const_SYST)
1290 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1291 else if (cmdval == const_PWD || cmdval == const_XPWD)
1293 else if (cmdval == const_CWD)
1295 else if (cmdval == const_CDUP) /* cd .. */
1297 /* HELP is nearly useless, but we can reuse FEAT for it */
1298 /* lftp uses FEAT */
1299 else if (cmdval == const_HELP || cmdval == const_FEAT)
1300 handle_feat(cmdval == const_HELP
1301 ? STRNUM32(FTP_HELP)
1302 : STRNUM32(FTP_STATOK)
1304 else if (cmdval == const_LIST) /* ls -l */
1306 else if (cmdval == const_NLST) /* "name list", bare ls */
1308 /* SIZE is crucial for wget's download indicator etc */
1309 /* Mozilla, lftp use MDTM (presumably for caching) */
1310 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1311 handle_size_or_mdtm(cmdval == const_SIZE);
1312 else if (cmdval == const_STAT) {
1313 if (G.ftp_arg == NULL)
1318 else if (cmdval == const_PASV)
1320 else if (cmdval == const_EPSV)
1322 else if (cmdval == const_RETR)
1324 else if (cmdval == const_PORT)
1326 else if (cmdval == const_REST)
1328 #if ENABLE_FEATURE_FTP_WRITE
1329 else if (opts & OPT_w) {
1330 if (cmdval == const_STOR)
1332 else if (cmdval == const_MKD)
1334 else if (cmdval == const_RMD)
1336 else if (cmdval == const_DELE)
1338 else if (cmdval == const_RNFR) /* "rename from" */
1340 else if (cmdval == const_RNTO) /* "rename to" */
1342 else if (cmdval == const_APPE)
1344 else if (cmdval == const_STOU) /* "store unique" */
1351 else if (cmdval == const_STOR
1352 || cmdval == const_MKD
1353 || cmdval == const_RMD
1354 || cmdval == const_DELE
1355 || cmdval == const_RNFR
1356 || cmdval == const_RNTO
1357 || cmdval == const_APPE
1358 || cmdval == const_STOU
1360 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1364 /* Which unsupported commands were seen in the wild?
1365 * (doesn't necessarily mean "we must support them")
1366 * foo 1.2.3: XXXX - comment
1368 #if ENABLE_FEATURE_FTP_WRITE
1371 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");