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"
32 #include "common_bufsiz.h"
34 #include <netinet/tcp.h>
36 #define FTP_DATACONN 150
37 #define FTP_NOOPOK 200
38 #define FTP_TYPEOK 200
39 #define FTP_PORTOK 200
40 #define FTP_STRUOK 200
41 #define FTP_MODEOK 200
42 #define FTP_ALLOOK 202
43 #define FTP_STATOK 211
44 #define FTP_STATFILE_OK 213
46 #define FTP_SYSTOK 215
48 #define FTP_GOODBYE 221
49 #define FTP_TRANSFEROK 226
50 #define FTP_PASVOK 227
51 /*#define FTP_EPRTOK 228*/
52 #define FTP_EPSVOK 229
53 #define FTP_LOGINOK 230
55 #define FTP_RMDIROK 250
56 #define FTP_DELEOK 250
57 #define FTP_RENAMEOK 250
59 #define FTP_MKDIROK 257
60 #define FTP_GIVEPWORD 331
61 #define FTP_RESTOK 350
62 #define FTP_RNFROK 350
63 #define FTP_TIMEOUT 421
64 #define FTP_BADSENDCONN 425
65 #define FTP_BADSENDNET 426
66 #define FTP_BADSENDFILE 451
67 #define FTP_BADCMD 500
68 #define FTP_COMMANDNOTIMPL 502
69 #define FTP_NEEDUSER 503
70 #define FTP_NEEDRNFR 503
71 #define FTP_BADSTRU 504
72 #define FTP_BADMODE 504
73 #define FTP_LOGINERR 530
74 #define FTP_FILEFAIL 550
75 #define FTP_NOPERM 550
76 #define FTP_UPLOADFAIL 553
79 #define STR(s) STR1(s)
81 /* Convert a constant to 3-digit string, packed into uint32_t */
83 /* Shift for Nth decimal digit */
84 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
85 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
86 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
87 /* And for 4th position (space) */
88 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
90 #define STRNUM32(s) (uint32_t)(0 \
91 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
92 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
93 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
95 #define STRNUM32sp(s) (uint32_t)(0 \
97 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
98 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
99 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
102 #define MSG_OK "Operation successful\r\n"
103 #define MSG_ERR "Error\r\n"
114 off_t local_file_pos;
116 len_and_sockaddr *local_addr;
117 len_and_sockaddr *port_addr;
120 #if ENABLE_FEATURE_FTP_WRITE
123 /* We need these aligned to uint32_t */
124 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
125 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
127 #define G (*(struct globals*)bb_common_bufsiz1)
128 #define INIT_G() do { \
129 /* Moved to main */ \
130 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
131 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
136 escape_text(const char *prepend, const char *str, unsigned escapee)
138 unsigned retlen, remainlen, chunklen;
142 append = (char)escapee;
145 remainlen = strlen(str);
146 retlen = strlen(prepend);
147 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
148 strcpy(ret, prepend);
151 found = strchrnul(str, escapee);
152 chunklen = found - str + 1;
154 /* Copy chunk up to and including escapee (or NUL) to ret */
155 memcpy(ret + retlen, str, chunklen);
158 if (*found == '\0') {
159 /* It wasn't escapee, it was NUL! */
160 ret[retlen - 1] = append; /* replace NUL */
161 ret[retlen] = '\0'; /* add NUL */
164 ret[retlen++] = escapee; /* duplicate escapee */
170 /* Returns strlen as a bonus */
172 replace_char(char *str, char from, char to)
184 verbose_log(const char *str)
186 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
189 /* NB: status_str is char[4] packed into uint32_t */
191 cmdio_write(uint32_t status_str, const char *str)
196 /* FTP uses telnet protocol for command link.
197 * In telnet, 0xff is an escape char, and needs to be escaped: */
198 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
200 /* FTP sends embedded LFs as NULs */
201 len = replace_char(response, '\n', '\0');
203 response[len++] = '\n'; /* tack on trailing '\n' */
204 xwrite(STDOUT_FILENO, response, len);
206 verbose_log(response);
211 cmdio_write_ok(unsigned status)
213 *(uint32_t *) G.msg_ok = status;
214 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
216 verbose_log(G.msg_ok);
218 #define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
220 /* TODO: output strerr(errno) if errno != 0? */
222 cmdio_write_error(unsigned status)
224 *(uint32_t *) G.msg_err = status;
225 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
227 verbose_log(G.msg_err);
229 #define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
232 cmdio_write_raw(const char *p_text)
234 xwrite_str(STDOUT_FILENO, p_text);
240 timeout_handler(int sig UNUSED_PARAM)
243 int sv_errno = errno;
245 if ((int)(monotonic_sec() - G.end_time) >= 0)
248 if (!G.local_file_fd)
251 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
252 if (pos == G.local_file_pos)
254 G.local_file_pos = pos;
261 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
262 /* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
266 /* Simple commands */
271 char *cwd, *response;
273 cwd = xrealloc_getcwd_or_warn(NULL);
277 /* We have to promote each " to "" */
278 response = escape_text(" \"", cwd, ('"' << 8) + '"');
280 cmdio_write(STRNUM32(FTP_PWDOK), response);
287 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
288 WRITE_ERR(FTP_FILEFAIL);
297 G.ftp_arg = (char*)"..";
304 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
306 STR(FTP_STATOK)" Ok\r\n");
309 /* Examples of HELP and FEAT:
310 # nc -vvv ftp.kernel.org 21
311 ftp.kernel.org (130.239.17.4:21) open
312 220 Welcome to ftp.kernel.org.
325 214-The following commands are recognized.
326 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
327 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
328 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
333 handle_feat(unsigned status)
335 cmdio_write(status, "-Features:");
336 cmdio_write_raw(" EPSV\r\n"
341 cmdio_write(status, " Ok");
344 /* Download commands */
349 return (G.port_addr != NULL);
355 return (G.pasv_listen_fd > STDOUT_FILENO);
359 port_pasv_cleanup(void)
363 if (G.pasv_listen_fd > STDOUT_FILENO)
364 close(G.pasv_listen_fd);
365 G.pasv_listen_fd = -1;
368 /* On error, emits error code to the peer */
370 ftpdataio_get_pasv_fd(void)
374 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
377 WRITE_ERR(FTP_BADSENDCONN);
381 setsockopt_keepalive(remote_fd);
385 /* Clears port/pasv data.
386 * This means we dont waste resources, for example, keeping
387 * PASV listening socket open when it is no longer needed.
388 * On error, emits error code to the peer (or exits).
389 * On success, emits p_status_msg to the peer.
392 get_remote_transfer_fd(const char *p_status_msg)
397 /* On error, emits error code to the peer */
398 remote_fd = ftpdataio_get_pasv_fd();
401 remote_fd = xconnect_stream(G.port_addr);
408 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
412 /* If there were neither PASV nor PORT, emits error code to the peer */
414 port_or_pasv_was_seen(void)
416 if (!pasv_active() && !port_active()) {
417 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
426 bind_for_passive_mode(void)
433 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
434 setsockopt_reuseaddr(fd);
436 set_nport(&G.local_addr->u.sa, 0);
437 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
439 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
441 port = get_nport(&G.local_addr->u.sa);
451 char *addr, *response;
453 port = bind_for_passive_mode();
455 if (G.local_addr->u.sa.sa_family == AF_INET)
456 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
457 else /* seen this in the wild done by other ftp servers: */
458 addr = xstrdup("0.0.0.0");
459 replace_char(addr, '.', ',');
461 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
462 addr, (int)(port >> 8), (int)(port & 255));
464 cmdio_write_raw(response);
475 port = bind_for_passive_mode();
476 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
477 cmdio_write_raw(response);
484 unsigned port, port_hi;
486 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
487 socklen_t peer_ipv4_len;
488 struct sockaddr_in peer_ipv4;
489 struct in_addr port_ipv4_sin_addr;
496 /* PORT command format makes sense only over IPv4 */
498 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
499 || G.local_addr->u.sa.sa_family != AF_INET
503 WRITE_ERR(FTP_BADCMD);
507 comma = strrchr(raw, ',');
511 port = bb_strtou(&comma[1], NULL, 10);
512 if (errno || port > 0xff)
515 comma = strrchr(raw, ',');
519 port_hi = bb_strtou(&comma[1], NULL, 10);
520 if (errno || port_hi > 0xff)
522 port |= port_hi << 8;
524 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
525 replace_char(raw, ',', '.');
527 /* We are verifying that PORT's IP matches getpeername().
528 * Otherwise peer can make us open data connections
529 * to other hosts (security problem!)
530 * This code would be too simplistic:
531 * lsa = xdotted2sockaddr(raw, port);
532 * if (lsa == NULL) goto bail;
534 if (!inet_aton(raw, &port_ipv4_sin_addr))
536 peer_ipv4_len = sizeof(peer_ipv4);
537 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
539 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
542 G.port_addr = xdotted2sockaddr(raw, port);
544 G.port_addr = get_peer_lsa(STDIN_FILENO);
545 set_nport(&G.port_addr->u.sa, htons(port));
547 WRITE_OK(FTP_PORTOK);
553 /* When ftp_arg == NULL simply restart from beginning */
554 G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
555 WRITE_OK(FTP_RESTOK);
562 off_t bytes_transferred;
565 off_t offset = G.restart_pos;
570 if (!port_or_pasv_was_seen())
571 return; /* port_or_pasv_was_seen emitted error response */
573 /* O_NONBLOCK is useful if file happens to be a device node */
574 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
575 if (local_file_fd < 0) {
576 WRITE_ERR(FTP_FILEFAIL);
580 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
581 /* Note - pretend open failed */
582 WRITE_ERR(FTP_FILEFAIL);
585 G.local_file_fd = local_file_fd;
587 /* Now deactive O_NONBLOCK, otherwise we have a problem
588 * on DMAPI filesystems such as XFS DMAPI.
590 ndelay_off(local_file_fd);
592 /* Set the download offset (from REST) if any */
594 xlseek(local_file_fd, offset, SEEK_SET);
596 response = xasprintf(
597 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
598 G.ftp_arg, statbuf.st_size);
599 remote_fd = get_remote_transfer_fd(response);
604 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
606 if (bytes_transferred < 0)
607 WRITE_ERR(FTP_BADSENDFILE);
609 WRITE_OK(FTP_TRANSFEROK);
612 close(local_file_fd);
619 popen_ls(const char *opt)
622 struct fd_pair outfd;
626 argv[1] = opt; /* "-lA" or "-1A" */
631 /* Improve compatibility with non-RFC conforming FTP clients
632 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
633 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
634 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
635 && G.ftp_arg && G.ftp_arg[0] == '-'
637 const char *tmp = strchr(G.ftp_arg, ' ');
638 if (tmp) /* skip the space */
645 /*fflush_all(); - so far we dont use stdio on output */
646 pid = BB_MMU ? xfork() : xvfork();
652 /* NB: close _first_, then move fd! */
654 xmove_fd(outfd.wr, STDOUT_FILENO);
655 /* Opening /dev/null in chroot is hard.
656 * Just making sure STDIN_FILENO is opened
657 * to something harmless. Paranoia,
658 * ls won't read it anyway */
660 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
662 /* memset(&G, 0, sizeof(G)); - ls_main does it */
663 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
665 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
666 /* On NOMMU, we want to execute a child - copy of ourself
667 * in order to unblock parent after vfork.
668 * In chroot we usually can't re-exec. Thus we escape
669 * out of the chroot back to original root.
671 if (G.root_fd >= 0) {
672 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
674 /*close(G.root_fd); - close_on_exec_on() took care of this */
676 /* Child expects directory to list on fd #3 */
678 execv(bb_busybox_exec_path, (char**) argv);
694 handle_dir_common(int opts)
700 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
701 return; /* port_or_pasv_was_seen emitted error response */
703 ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
704 ls_fp = xfdopen_for_read(ls_fd);
705 /* FIXME: filenames with embedded newlines are mishandled */
707 if (opts & USE_CTRL_CONN) {
708 /* STAT <filename> */
709 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
711 line = xmalloc_fgetline(ls_fp);
714 /* Hack: 0 results in no status at all */
715 /* Note: it's ok that we don't prepend space,
716 * ftp.kernel.org doesn't do that too */
717 cmdio_write(0, line);
720 WRITE_OK(FTP_STATFILE_OK);
722 /* LIST/NLST [<filename>] */
723 int remote_fd = get_remote_transfer_fd(" Directory listing");
724 if (remote_fd >= 0) {
728 line = xmalloc_fgets(ls_fp);
731 /* I've seen clients complaining when they
732 * are fed with ls output with bare '\n'.
733 * Replace trailing "\n\0" with "\r\n".
736 if (len != 0) /* paranoia check */
737 line[len - 1] = '\r';
739 xwrite(remote_fd, line, len + 1);
744 WRITE_OK(FTP_TRANSFEROK);
746 fclose(ls_fp); /* closes ls_fd too */
751 handle_dir_common(LONG_LISTING);
756 /* NLST returns list of names, "\r\n" terminated without regard
757 * to the current binary flag. Names may start with "/",
758 * then they represent full names (we don't produce such names),
759 * otherwise names are relative to current directory.
760 * Embedded "\n" are replaced by NULs. This is safe since names
761 * can never contain NUL.
763 handle_dir_common(0);
766 handle_stat_file(void)
768 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
771 /* This can be extended to handle MLST, as all info is available
772 * in struct stat for that:
774 * 250-Listing file_name
775 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
778 * MLST [<file or dir name, "." assumed if not given>]
779 * Returned name should be either the same as requested, or fully qualified.
780 * If there was no parameter, return "" or (preferred) fully-qualified name.
781 * Returned "facts" (case is not important):
782 * size - size in octets
783 * modify - last modification time
784 * type - entry type (file,dir,OS.unix=block)
785 * (+ cdir and pdir types for MLSD)
786 * unique - unique id of file/directory (inode#)
788 * a: can be appended to (APPE)
789 * d: can be deleted (RMD/DELE)
790 * f: can be renamed (RNFR)
791 * r: can be read (RETR)
792 * w: can be written (STOR)
793 * e: can CWD into this dir
794 * l: this dir can be listed (dir only!)
795 * c: can create files in this dir
796 * m: can create dirs in this dir (MKD)
797 * p: can delete files in this dir
798 * UNIX.mode - unix file mode
801 handle_size_or_mdtm(int need_size)
804 struct tm broken_out;
805 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
806 | sizeof("NNN YYYYMMDDhhmmss\r\n")
810 || stat(G.ftp_arg, &statbuf) != 0
811 || !S_ISREG(statbuf.st_mode)
813 WRITE_ERR(FTP_FILEFAIL);
817 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
819 gmtime_r(&statbuf.st_mtime, &broken_out);
820 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
821 broken_out.tm_year + 1900,
822 broken_out.tm_mon + 1,
828 cmdio_write_raw(buf);
831 /* Upload commands */
833 #if ENABLE_FEATURE_FTP_WRITE
837 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
838 WRITE_ERR(FTP_FILEFAIL);
841 WRITE_OK(FTP_MKDIROK);
847 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
848 WRITE_ERR(FTP_FILEFAIL);
851 WRITE_OK(FTP_RMDIROK);
857 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
858 WRITE_ERR(FTP_FILEFAIL);
861 WRITE_OK(FTP_DELEOK);
867 free(G.rnfr_filename);
868 G.rnfr_filename = xstrdup(G.ftp_arg);
869 WRITE_OK(FTP_RNFROK);
877 /* If we didn't get a RNFR, throw a wobbly */
878 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
879 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
883 retval = rename(G.rnfr_filename, G.ftp_arg);
884 free(G.rnfr_filename);
885 G.rnfr_filename = NULL;
888 WRITE_ERR(FTP_FILEFAIL);
891 WRITE_OK(FTP_RENAMEOK);
895 handle_upload_common(int is_append, int is_unique)
899 off_t bytes_transferred;
904 offset = G.restart_pos;
907 if (!port_or_pasv_was_seen())
908 return; /* port_or_pasv_was_seen emitted error response */
913 tempname = xstrdup(" FILE: uniq.XXXXXX");
914 local_file_fd = mkstemp(tempname + 7);
915 } else if (G.ftp_arg) {
916 int flags = O_WRONLY | O_CREAT | O_TRUNC;
918 flags = O_WRONLY | O_CREAT | O_APPEND;
920 flags = O_WRONLY | O_CREAT;
921 local_file_fd = open(G.ftp_arg, flags, 0666);
924 if (local_file_fd < 0
925 || fstat(local_file_fd, &statbuf) != 0
926 || !S_ISREG(statbuf.st_mode)
929 WRITE_ERR(FTP_UPLOADFAIL);
930 if (local_file_fd >= 0)
931 goto close_local_and_bail;
934 G.local_file_fd = local_file_fd;
937 xlseek(local_file_fd, offset, SEEK_SET);
939 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
943 goto close_local_and_bail;
945 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
947 if (bytes_transferred < 0)
948 WRITE_ERR(FTP_BADSENDFILE);
950 WRITE_OK(FTP_TRANSFEROK);
952 close_local_and_bail:
953 close(local_file_fd);
960 handle_upload_common(0, 0);
967 handle_upload_common(1, 0);
974 handle_upload_common(0, 1);
976 #endif /* ENABLE_FEATURE_FTP_WRITE */
979 cmdio_get_cmd_and_arg(void)
989 /* Paranoia. Peer may send 1 gigabyte long cmd... */
990 /* Using separate len_on_stk instead of len optimizes
991 * code size (allows len to be in CPU register) */
992 size_t len_on_stk = 8 * 1024;
993 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
999 /* De-escape telnet: 0xff,0xff => 0xff */
1000 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1001 * data transfer, and may be preceded by telnet's "Interrupt Process"
1002 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1003 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1004 * and may generate SIGURG on our side. See RFC854).
1005 * So far we don't support that (may install SIGURG handler if we'd want to),
1006 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1007 /* Then de-escape FTP: NUL => '\n' */
1008 /* Testing for \xff:
1009 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1010 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1011 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1012 * Testing for embedded LF:
1013 * LF_HERE=`echo -ne "LF\nHERE"`
1014 * echo Hello >"$LF_HERE"
1015 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1020 /* Strip "\r\n" if it is there */
1021 if (len != 0 && cmd[len - 1] == '\n') {
1023 if (len != 0 && cmd[len - 1] == '\r')
1027 src = strchrnul(cmd, 0xff) - cmd;
1028 /* 99,99% there are neither NULs nor 255s and src == len */
1032 if ((unsigned char)(cmd[src]) == 255) {
1034 /* 255,xxx - skip 255 */
1035 if ((unsigned char)(cmd[src]) != 255) {
1036 /* 255,!255 - skip both */
1040 /* 255,255 - retain one 255 */
1043 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1045 } while (src < len);
1053 G.ftp_arg = strchr(cmd, ' ');
1054 if (G.ftp_arg != NULL)
1055 *G.ftp_arg++ = '\0';
1057 /* Uppercase and pack into uint32_t first word of the command */
1060 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1065 #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1066 #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1068 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1069 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1070 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1071 const_CWD = mk_const3('C', 'W', 'D'),
1072 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1073 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
1074 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
1075 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1076 const_LIST = mk_const4('L', 'I', 'S', 'T'),
1077 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
1078 const_MKD = mk_const3('M', 'K', 'D'),
1079 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1080 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1081 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1082 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1083 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1084 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1085 const_PWD = mk_const3('P', 'W', 'D'),
1086 /* Same as PWD. Reportedly used by windows ftp client */
1087 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
1088 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1089 const_REST = mk_const4('R', 'E', 'S', 'T'),
1090 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1091 const_RMD = mk_const3('R', 'M', 'D'),
1092 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1093 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1094 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1095 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1096 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1097 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1098 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1099 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1100 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1101 const_USER = mk_const4('U', 'S', 'E', 'R'),
1108 OPT_v = (1 << ((!BB_MMU) * 3 + 0)),
1109 OPT_S = (1 << ((!BB_MMU) * 3 + 1)),
1110 OPT_w = (1 << ((!BB_MMU) * 3 + 2)) * ENABLE_FEATURE_FTP_WRITE,
1113 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1115 int ftpd_main(int argc, char **argv)
1117 int ftpd_main(int argc UNUSED_PARAM, char **argv)
1120 #if ENABLE_FEATURE_FTP_AUTHENTICATION
1121 struct passwd *pw = NULL;
1123 unsigned abs_timeout;
1129 abs_timeout = 1 * 60 * 60;
1132 opt_complementary = "t+:T+:vv:SS";
1134 opts = getopt32(argv, "vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1136 opts = getopt32(argv, "l1AvS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1137 if (opts & (OPT_l|OPT_1)) {
1138 /* Our secret backdoor to ls */
1139 /* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
1142 /* memset(&G, 0, sizeof(G)); - ls_main does it */
1143 return ls_main(argc, argv);
1146 if (G.verbose < verbose_S)
1147 G.verbose = verbose_S;
1148 if (abs_timeout | G.timeout) {
1149 if (abs_timeout == 0)
1150 abs_timeout = INT_MAX;
1151 G.end_time = monotonic_sec() + abs_timeout;
1152 if (G.timeout > abs_timeout)
1153 G.timeout = abs_timeout;
1155 strcpy(G.msg_ok + 4, MSG_OK );
1156 strcpy(G.msg_err + 4, MSG_ERR);
1158 G.local_addr = get_sock_lsa(STDIN_FILENO);
1159 if (!G.local_addr) {
1160 /* This is confusing:
1161 * bb_error_msg_and_die("stdin is not a socket");
1164 /* Help text says that ftpd must be used as inetd service,
1165 * which is by far the most usual cause of get_sock_lsa
1169 if (!(opts & OPT_v))
1170 logmode = LOGMODE_NONE;
1172 /* LOG_NDELAY is needed since we may chroot later */
1173 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1174 logmode |= LOGMODE_SYSLOG;
1177 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1179 //umask(077); - admin can set umask before starting us
1183 /* We'll always take EPIPE rather than a rude signal, thanks */
1185 /* LIST command spawns chilren. Prevent zombies */
1189 /* Set up options on the command socket (do we need these all? why?) */
1190 setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
1191 setsockopt_keepalive(STDIN_FILENO);
1192 /* Telnet protocol over command link may send "urgent" data,
1193 * we prefer it to be received in the "normal" data stream: */
1194 setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
1196 WRITE_OK(FTP_GREET);
1197 signal(SIGALRM, timeout_handler);
1199 #if ENABLE_FEATURE_FTP_AUTHENTICATION
1201 uint32_t cmdval = cmdio_get_cmd_and_arg();
1202 if (cmdval == const_USER) {
1203 pw = getpwnam(G.ftp_arg);
1204 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify password\r\n");
1205 } else if (cmdval == const_PASS) {
1206 if (check_password(pw, G.ftp_arg) > 0) {
1207 break; /* login success */
1209 cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
1211 } else if (cmdval == const_QUIT) {
1212 WRITE_OK(FTP_GOODBYE);
1215 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1218 WRITE_OK(FTP_LOGINOK);
1221 /* Do this after auth, else /etc/passwd is not accessible */
1227 const char *basedir = argv[0];
1229 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1230 close_on_exec_on(G.root_fd);
1232 if (chroot(basedir) == 0)
1241 * If chroot failed, assume that we aren't root,
1242 * and at least chdir to the specified DIR
1243 * (older versions were dying with error message).
1244 * If chroot worked, move current dir to new "/":
1249 #if ENABLE_FEATURE_FTP_AUTHENTICATION
1250 change_identity(pw);
1253 /* RFC-959 Section 5.1
1254 * The following commands and options MUST be supported by every
1255 * server-FTP and user-FTP, except in cases where the underlying
1256 * file system or operating system does not allow or support
1257 * a particular command.
1258 * Type: ASCII Non-print, IMAGE, LOCAL 8
1260 * Structure: File, Record*
1261 * (Record structure is REQUIRED only for hosts whose file
1262 * systems support record structure).
1264 * USER, PASS, ACCT, [bbox: ACCT not supported]
1269 * CWD, CDUP, RMD, MKD, PWD,
1275 * "The argument field is a Telnet string identifying the user's account.
1276 * The command is not necessarily related to the USER command, as some
1277 * sites may require an account for login and others only for specific
1278 * access, such as storing files. In the latter case the command may
1279 * arrive at any time.
1280 * There are reply codes to differentiate these cases for the automation:
1281 * when account information is required for login, the response to
1282 * a successful PASSword command is reply code 332. On the other hand,
1283 * if account information is NOT required for login, the reply to
1284 * a successful PASSword command is 230; and if the account information
1285 * is needed for a command issued later in the dialogue, the server
1286 * should return a 332 or 532 reply depending on whether it stores
1287 * (pending receipt of the ACCounT command) or discards the command,
1292 uint32_t cmdval = cmdio_get_cmd_and_arg();
1294 if (cmdval == const_QUIT) {
1295 WRITE_OK(FTP_GOODBYE);
1298 else if (cmdval == const_USER)
1299 /* This would mean "ok, now give me PASS". */
1300 /*WRITE_OK(FTP_GIVEPWORD);*/
1301 /* vsftpd can be configured to not require that,
1302 * and this also saves one roundtrip:
1304 WRITE_OK(FTP_LOGINOK);
1305 else if (cmdval == const_PASS)
1306 WRITE_OK(FTP_LOGINOK);
1307 else if (cmdval == const_NOOP)
1308 WRITE_OK(FTP_NOOPOK);
1309 else if (cmdval == const_TYPE)
1310 WRITE_OK(FTP_TYPEOK);
1311 else if (cmdval == const_STRU)
1312 WRITE_OK(FTP_STRUOK);
1313 else if (cmdval == const_MODE)
1314 WRITE_OK(FTP_MODEOK);
1315 else if (cmdval == const_ALLO)
1316 WRITE_OK(FTP_ALLOOK);
1317 else if (cmdval == const_SYST)
1318 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1319 else if (cmdval == const_PWD || cmdval == const_XPWD)
1321 else if (cmdval == const_CWD)
1323 else if (cmdval == const_CDUP) /* cd .. */
1325 /* HELP is nearly useless, but we can reuse FEAT for it */
1326 /* lftp uses FEAT */
1327 else if (cmdval == const_HELP || cmdval == const_FEAT)
1328 handle_feat(cmdval == const_HELP
1329 ? STRNUM32(FTP_HELP)
1330 : STRNUM32(FTP_STATOK)
1332 else if (cmdval == const_LIST) /* ls -l */
1334 else if (cmdval == const_NLST) /* "name list", bare ls */
1336 /* SIZE is crucial for wget's download indicator etc */
1337 /* Mozilla, lftp use MDTM (presumably for caching) */
1338 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1339 handle_size_or_mdtm(cmdval == const_SIZE);
1340 else if (cmdval == const_STAT) {
1341 if (G.ftp_arg == NULL)
1346 else if (cmdval == const_PASV)
1348 else if (cmdval == const_EPSV)
1350 else if (cmdval == const_RETR)
1352 else if (cmdval == const_PORT)
1354 else if (cmdval == const_REST)
1356 #if ENABLE_FEATURE_FTP_WRITE
1357 else if (opts & OPT_w) {
1358 if (cmdval == const_STOR)
1360 else if (cmdval == const_MKD)
1362 else if (cmdval == const_RMD)
1364 else if (cmdval == const_DELE)
1366 else if (cmdval == const_RNFR) /* "rename from" */
1368 else if (cmdval == const_RNTO) /* "rename to" */
1370 else if (cmdval == const_APPE)
1372 else if (cmdval == const_STOU) /* "store unique" */
1379 else if (cmdval == const_STOR
1380 || cmdval == const_MKD
1381 || cmdval == const_RMD
1382 || cmdval == const_DELE
1383 || cmdval == const_RNFR
1384 || cmdval == const_RNTO
1385 || cmdval == const_APPE
1386 || cmdval == const_STOU
1388 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1392 /* Which unsupported commands were seen in the wild?
1393 * (doesn't necessarily mean "we must support them")
1394 * foo 1.2.3: XXXX - comment
1396 #if ENABLE_FEATURE_FTP_WRITE
1399 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");