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 setup_common_bufsiz(); \
130 /* Moved to main */ \
131 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
132 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
137 escape_text(const char *prepend, const char *str, unsigned escapee)
139 unsigned retlen, remainlen, chunklen;
143 append = (char)escapee;
146 remainlen = strlen(str);
147 retlen = strlen(prepend);
148 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
149 strcpy(ret, prepend);
152 found = strchrnul(str, escapee);
153 chunklen = found - str + 1;
155 /* Copy chunk up to and including escapee (or NUL) to ret */
156 memcpy(ret + retlen, str, chunklen);
159 if (*found == '\0') {
160 /* It wasn't escapee, it was NUL! */
161 ret[retlen - 1] = append; /* replace NUL */
162 ret[retlen] = '\0'; /* add NUL */
165 ret[retlen++] = escapee; /* duplicate escapee */
171 /* Returns strlen as a bonus */
173 replace_char(char *str, char from, char to)
185 verbose_log(const char *str)
187 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
190 /* NB: status_str is char[4] packed into uint32_t */
192 cmdio_write(uint32_t status_str, const char *str)
197 /* FTP uses telnet protocol for command link.
198 * In telnet, 0xff is an escape char, and needs to be escaped: */
199 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
201 /* FTP sends embedded LFs as NULs */
202 len = replace_char(response, '\n', '\0');
204 response[len++] = '\n'; /* tack on trailing '\n' */
205 xwrite(STDOUT_FILENO, response, len);
207 verbose_log(response);
212 cmdio_write_ok(unsigned status)
214 *(uint32_t *) G.msg_ok = status;
215 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
217 verbose_log(G.msg_ok);
219 #define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
221 /* TODO: output strerr(errno) if errno != 0? */
223 cmdio_write_error(unsigned status)
225 *(uint32_t *) G.msg_err = status;
226 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
228 verbose_log(G.msg_err);
230 #define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
233 cmdio_write_raw(const char *p_text)
235 xwrite_str(STDOUT_FILENO, p_text);
241 timeout_handler(int sig UNUSED_PARAM)
244 int sv_errno = errno;
246 if ((int)(monotonic_sec() - G.end_time) >= 0)
249 if (!G.local_file_fd)
252 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
253 if (pos == G.local_file_pos)
255 G.local_file_pos = pos;
262 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
263 /* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
267 /* Simple commands */
272 char *cwd, *response;
274 cwd = xrealloc_getcwd_or_warn(NULL);
278 /* We have to promote each " to "" */
279 response = escape_text(" \"", cwd, ('"' << 8) + '"');
281 cmdio_write(STRNUM32(FTP_PWDOK), response);
288 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
289 WRITE_ERR(FTP_FILEFAIL);
298 G.ftp_arg = (char*)"..";
305 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
307 STR(FTP_STATOK)" Ok\r\n");
310 /* Examples of HELP and FEAT:
311 # nc -vvv ftp.kernel.org 21
312 ftp.kernel.org (130.239.17.4:21) open
313 220 Welcome to ftp.kernel.org.
326 214-The following commands are recognized.
327 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
328 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
329 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
334 handle_feat(unsigned status)
336 cmdio_write(status, "-Features:");
337 cmdio_write_raw(" EPSV\r\n"
342 cmdio_write(status, " Ok");
345 /* Download commands */
350 return (G.port_addr != NULL);
356 return (G.pasv_listen_fd > STDOUT_FILENO);
360 port_pasv_cleanup(void)
364 if (G.pasv_listen_fd > STDOUT_FILENO)
365 close(G.pasv_listen_fd);
366 G.pasv_listen_fd = -1;
369 /* On error, emits error code to the peer */
371 ftpdataio_get_pasv_fd(void)
375 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
378 WRITE_ERR(FTP_BADSENDCONN);
382 setsockopt_keepalive(remote_fd);
386 /* Clears port/pasv data.
387 * This means we dont waste resources, for example, keeping
388 * PASV listening socket open when it is no longer needed.
389 * On error, emits error code to the peer (or exits).
390 * On success, emits p_status_msg to the peer.
393 get_remote_transfer_fd(const char *p_status_msg)
398 /* On error, emits error code to the peer */
399 remote_fd = ftpdataio_get_pasv_fd();
402 remote_fd = xconnect_stream(G.port_addr);
409 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
413 /* If there were neither PASV nor PORT, emits error code to the peer */
415 port_or_pasv_was_seen(void)
417 if (!pasv_active() && !port_active()) {
418 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
427 bind_for_passive_mode(void)
434 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
435 setsockopt_reuseaddr(fd);
437 set_nport(&G.local_addr->u.sa, 0);
438 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
440 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
442 port = get_nport(&G.local_addr->u.sa);
452 char *addr, *response;
454 port = bind_for_passive_mode();
456 if (G.local_addr->u.sa.sa_family == AF_INET)
457 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
458 else /* seen this in the wild done by other ftp servers: */
459 addr = xstrdup("0.0.0.0");
460 replace_char(addr, '.', ',');
462 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
463 addr, (int)(port >> 8), (int)(port & 255));
465 cmdio_write_raw(response);
476 port = bind_for_passive_mode();
477 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
478 cmdio_write_raw(response);
485 unsigned port, port_hi;
487 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
488 socklen_t peer_ipv4_len;
489 struct sockaddr_in peer_ipv4;
490 struct in_addr port_ipv4_sin_addr;
497 /* PORT command format makes sense only over IPv4 */
499 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
500 || G.local_addr->u.sa.sa_family != AF_INET
504 WRITE_ERR(FTP_BADCMD);
508 comma = strrchr(raw, ',');
512 port = bb_strtou(&comma[1], NULL, 10);
513 if (errno || port > 0xff)
516 comma = strrchr(raw, ',');
520 port_hi = bb_strtou(&comma[1], NULL, 10);
521 if (errno || port_hi > 0xff)
523 port |= port_hi << 8;
525 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
526 replace_char(raw, ',', '.');
528 /* We are verifying that PORT's IP matches getpeername().
529 * Otherwise peer can make us open data connections
530 * to other hosts (security problem!)
531 * This code would be too simplistic:
532 * lsa = xdotted2sockaddr(raw, port);
533 * if (lsa == NULL) goto bail;
535 if (!inet_aton(raw, &port_ipv4_sin_addr))
537 peer_ipv4_len = sizeof(peer_ipv4);
538 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
540 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
543 G.port_addr = xdotted2sockaddr(raw, port);
545 G.port_addr = get_peer_lsa(STDIN_FILENO);
546 set_nport(&G.port_addr->u.sa, htons(port));
548 WRITE_OK(FTP_PORTOK);
554 /* When ftp_arg == NULL simply restart from beginning */
555 G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
556 WRITE_OK(FTP_RESTOK);
563 off_t bytes_transferred;
566 off_t offset = G.restart_pos;
571 if (!port_or_pasv_was_seen())
572 return; /* port_or_pasv_was_seen emitted error response */
574 /* O_NONBLOCK is useful if file happens to be a device node */
575 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
576 if (local_file_fd < 0) {
577 WRITE_ERR(FTP_FILEFAIL);
581 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
582 /* Note - pretend open failed */
583 WRITE_ERR(FTP_FILEFAIL);
586 G.local_file_fd = local_file_fd;
588 /* Now deactive O_NONBLOCK, otherwise we have a problem
589 * on DMAPI filesystems such as XFS DMAPI.
591 ndelay_off(local_file_fd);
593 /* Set the download offset (from REST) if any */
595 xlseek(local_file_fd, offset, SEEK_SET);
597 response = xasprintf(
598 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
599 G.ftp_arg, statbuf.st_size);
600 remote_fd = get_remote_transfer_fd(response);
605 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
607 if (bytes_transferred < 0)
608 WRITE_ERR(FTP_BADSENDFILE);
610 WRITE_OK(FTP_TRANSFEROK);
613 close(local_file_fd);
620 popen_ls(const char *opt)
623 struct fd_pair outfd;
627 argv[1] = opt; /* "-lA" or "-1A" */
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();
653 /* NB: close _first_, then move fd! */
655 xmove_fd(outfd.wr, STDOUT_FILENO);
656 /* Opening /dev/null in chroot is hard.
657 * Just making sure STDIN_FILENO is opened
658 * to something harmless. Paranoia,
659 * ls won't read it anyway */
661 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
663 /* memset(&G, 0, sizeof(G)); - ls_main does it */
664 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
666 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
667 /* On NOMMU, we want to execute a child - copy of ourself
668 * in order to unblock parent after vfork.
669 * In chroot we usually can't re-exec. Thus we escape
670 * out of the chroot back to original root.
672 if (G.root_fd >= 0) {
673 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
675 /*close(G.root_fd); - close_on_exec_on() took care of this */
677 /* Child expects directory to list on fd #3 */
679 execv(bb_busybox_exec_path, (char**) argv);
695 handle_dir_common(int opts)
701 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
702 return; /* port_or_pasv_was_seen emitted error response */
704 ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
705 ls_fp = xfdopen_for_read(ls_fd);
706 /* FIXME: filenames with embedded newlines are mishandled */
708 if (opts & USE_CTRL_CONN) {
709 /* STAT <filename> */
710 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
712 line = xmalloc_fgetline(ls_fp);
715 /* Hack: 0 results in no status at all */
716 /* Note: it's ok that we don't prepend space,
717 * ftp.kernel.org doesn't do that too */
718 cmdio_write(0, line);
721 WRITE_OK(FTP_STATFILE_OK);
723 /* LIST/NLST [<filename>] */
724 int remote_fd = get_remote_transfer_fd(" Directory listing");
725 if (remote_fd >= 0) {
729 line = xmalloc_fgets(ls_fp);
732 /* I've seen clients complaining when they
733 * are fed with ls output with bare '\n'.
734 * Replace trailing "\n\0" with "\r\n".
737 if (len != 0) /* paranoia check */
738 line[len - 1] = '\r';
740 xwrite(remote_fd, line, len + 1);
745 WRITE_OK(FTP_TRANSFEROK);
747 fclose(ls_fp); /* closes ls_fd too */
752 handle_dir_common(LONG_LISTING);
757 /* NLST returns list of names, "\r\n" terminated without regard
758 * to the current binary flag. Names may start with "/",
759 * then they represent full names (we don't produce such names),
760 * otherwise names are relative to current directory.
761 * Embedded "\n" are replaced by NULs. This is safe since names
762 * can never contain NUL.
764 handle_dir_common(0);
767 handle_stat_file(void)
769 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
772 /* This can be extended to handle MLST, as all info is available
773 * in struct stat for that:
775 * 250-Listing file_name
776 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
779 * MLST [<file or dir name, "." assumed if not given>]
780 * Returned name should be either the same as requested, or fully qualified.
781 * If there was no parameter, return "" or (preferred) fully-qualified name.
782 * Returned "facts" (case is not important):
783 * size - size in octets
784 * modify - last modification time
785 * type - entry type (file,dir,OS.unix=block)
786 * (+ cdir and pdir types for MLSD)
787 * unique - unique id of file/directory (inode#)
789 * a: can be appended to (APPE)
790 * d: can be deleted (RMD/DELE)
791 * f: can be renamed (RNFR)
792 * r: can be read (RETR)
793 * w: can be written (STOR)
794 * e: can CWD into this dir
795 * l: this dir can be listed (dir only!)
796 * c: can create files in this dir
797 * m: can create dirs in this dir (MKD)
798 * p: can delete files in this dir
799 * UNIX.mode - unix file mode
802 handle_size_or_mdtm(int need_size)
805 struct tm broken_out;
806 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
807 | sizeof("NNN YYYYMMDDhhmmss\r\n")
811 || stat(G.ftp_arg, &statbuf) != 0
812 || !S_ISREG(statbuf.st_mode)
814 WRITE_ERR(FTP_FILEFAIL);
818 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
820 gmtime_r(&statbuf.st_mtime, &broken_out);
821 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
822 broken_out.tm_year + 1900,
823 broken_out.tm_mon + 1,
829 cmdio_write_raw(buf);
832 /* Upload commands */
834 #if ENABLE_FEATURE_FTP_WRITE
838 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
839 WRITE_ERR(FTP_FILEFAIL);
842 WRITE_OK(FTP_MKDIROK);
848 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
849 WRITE_ERR(FTP_FILEFAIL);
852 WRITE_OK(FTP_RMDIROK);
858 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
859 WRITE_ERR(FTP_FILEFAIL);
862 WRITE_OK(FTP_DELEOK);
868 free(G.rnfr_filename);
869 G.rnfr_filename = xstrdup(G.ftp_arg);
870 WRITE_OK(FTP_RNFROK);
878 /* If we didn't get a RNFR, throw a wobbly */
879 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
880 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
884 retval = rename(G.rnfr_filename, G.ftp_arg);
885 free(G.rnfr_filename);
886 G.rnfr_filename = NULL;
889 WRITE_ERR(FTP_FILEFAIL);
892 WRITE_OK(FTP_RENAMEOK);
896 handle_upload_common(int is_append, int is_unique)
900 off_t bytes_transferred;
905 offset = G.restart_pos;
908 if (!port_or_pasv_was_seen())
909 return; /* port_or_pasv_was_seen emitted error response */
914 tempname = xstrdup(" FILE: uniq.XXXXXX");
915 local_file_fd = mkstemp(tempname + 7);
916 } else if (G.ftp_arg) {
917 int flags = O_WRONLY | O_CREAT | O_TRUNC;
919 flags = O_WRONLY | O_CREAT | O_APPEND;
921 flags = O_WRONLY | O_CREAT;
922 local_file_fd = open(G.ftp_arg, flags, 0666);
925 if (local_file_fd < 0
926 || fstat(local_file_fd, &statbuf) != 0
927 || !S_ISREG(statbuf.st_mode)
930 WRITE_ERR(FTP_UPLOADFAIL);
931 if (local_file_fd >= 0)
932 goto close_local_and_bail;
935 G.local_file_fd = local_file_fd;
938 xlseek(local_file_fd, offset, SEEK_SET);
940 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
944 goto close_local_and_bail;
946 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
948 if (bytes_transferred < 0)
949 WRITE_ERR(FTP_BADSENDFILE);
951 WRITE_OK(FTP_TRANSFEROK);
953 close_local_and_bail:
954 close(local_file_fd);
961 handle_upload_common(0, 0);
968 handle_upload_common(1, 0);
975 handle_upload_common(0, 1);
977 #endif /* ENABLE_FEATURE_FTP_WRITE */
980 cmdio_get_cmd_and_arg(void)
990 /* Paranoia. Peer may send 1 gigabyte long cmd... */
991 /* Using separate len_on_stk instead of len optimizes
992 * code size (allows len to be in CPU register) */
993 size_t len_on_stk = 8 * 1024;
994 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
1000 /* De-escape telnet: 0xff,0xff => 0xff */
1001 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1002 * data transfer, and may be preceded by telnet's "Interrupt Process"
1003 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1004 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1005 * and may generate SIGURG on our side. See RFC854).
1006 * So far we don't support that (may install SIGURG handler if we'd want to),
1007 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1008 /* Then de-escape FTP: NUL => '\n' */
1009 /* Testing for \xff:
1010 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1011 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1012 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1013 * Testing for embedded LF:
1014 * LF_HERE=`echo -ne "LF\nHERE"`
1015 * echo Hello >"$LF_HERE"
1016 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1021 /* Strip "\r\n" if it is there */
1022 if (len != 0 && cmd[len - 1] == '\n') {
1024 if (len != 0 && cmd[len - 1] == '\r')
1028 src = strchrnul(cmd, 0xff) - cmd;
1029 /* 99,99% there are neither NULs nor 255s and src == len */
1033 if ((unsigned char)(cmd[src]) == 255) {
1035 /* 255,xxx - skip 255 */
1036 if ((unsigned char)(cmd[src]) != 255) {
1037 /* 255,!255 - skip both */
1041 /* 255,255 - retain one 255 */
1044 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1046 } while (src < len);
1054 G.ftp_arg = strchr(cmd, ' ');
1055 if (G.ftp_arg != NULL)
1056 *G.ftp_arg++ = '\0';
1058 /* Uppercase and pack into uint32_t first word of the command */
1061 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1066 #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1067 #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1069 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1070 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1071 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1072 const_CWD = mk_const3('C', 'W', 'D'),
1073 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1074 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
1075 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
1076 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1077 const_LIST = mk_const4('L', 'I', 'S', 'T'),
1078 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
1079 const_MKD = mk_const3('M', 'K', 'D'),
1080 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1081 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1082 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1083 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1084 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1085 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1086 const_PWD = mk_const3('P', 'W', 'D'),
1087 /* Same as PWD. Reportedly used by windows ftp client */
1088 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
1089 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1090 const_REST = mk_const4('R', 'E', 'S', 'T'),
1091 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1092 const_RMD = mk_const3('R', 'M', 'D'),
1093 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1094 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1095 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1096 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1097 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1098 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1099 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1100 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1101 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1102 const_USER = mk_const4('U', 'S', 'E', 'R'),
1109 OPT_v = (1 << ((!BB_MMU) * 3 + 0)),
1110 OPT_S = (1 << ((!BB_MMU) * 3 + 1)),
1111 OPT_w = (1 << ((!BB_MMU) * 3 + 2)) * ENABLE_FEATURE_FTP_WRITE,
1114 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1116 int ftpd_main(int argc, char **argv)
1118 int ftpd_main(int argc UNUSED_PARAM, char **argv)
1121 #if ENABLE_FEATURE_FTP_AUTHENTICATION
1122 struct passwd *pw = NULL;
1124 unsigned abs_timeout;
1130 abs_timeout = 1 * 60 * 60;
1133 opt_complementary = "vv:SS";
1135 opts = getopt32(argv, "vS" IF_FEATURE_FTP_WRITE("w") "t:+T:+", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1137 opts = getopt32(argv, "l1AvS" IF_FEATURE_FTP_WRITE("w") "t:+T:+", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1138 if (opts & (OPT_l|OPT_1)) {
1139 /* Our secret backdoor to ls */
1140 /* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
1143 /* memset(&G, 0, sizeof(G)); - ls_main does it */
1144 return ls_main(argc, argv);
1147 if (G.verbose < verbose_S)
1148 G.verbose = verbose_S;
1149 if (abs_timeout | G.timeout) {
1150 if (abs_timeout == 0)
1151 abs_timeout = INT_MAX;
1152 G.end_time = monotonic_sec() + abs_timeout;
1153 if (G.timeout > abs_timeout)
1154 G.timeout = abs_timeout;
1156 strcpy(G.msg_ok + 4, MSG_OK );
1157 strcpy(G.msg_err + 4, MSG_ERR);
1159 G.local_addr = get_sock_lsa(STDIN_FILENO);
1160 if (!G.local_addr) {
1161 /* This is confusing:
1162 * bb_error_msg_and_die("stdin is not a socket");
1165 /* Help text says that ftpd must be used as inetd service,
1166 * which is by far the most usual cause of get_sock_lsa
1170 if (!(opts & OPT_v))
1171 logmode = LOGMODE_NONE;
1173 /* LOG_NDELAY is needed since we may chroot later */
1174 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1175 logmode |= LOGMODE_SYSLOG;
1178 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1180 //umask(077); - admin can set umask before starting us
1184 /* We'll always take EPIPE rather than a rude signal, thanks */
1186 /* LIST command spawns chilren. Prevent zombies */
1190 /* Set up options on the command socket (do we need these all? why?) */
1191 setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
1192 setsockopt_keepalive(STDIN_FILENO);
1193 /* Telnet protocol over command link may send "urgent" data,
1194 * we prefer it to be received in the "normal" data stream: */
1195 setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
1197 WRITE_OK(FTP_GREET);
1198 signal(SIGALRM, timeout_handler);
1200 #if ENABLE_FEATURE_FTP_AUTHENTICATION
1202 uint32_t cmdval = cmdio_get_cmd_and_arg();
1203 if (cmdval == const_USER) {
1204 pw = getpwnam(G.ftp_arg);
1205 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify password\r\n");
1206 } else if (cmdval == const_PASS) {
1207 if (check_password(pw, G.ftp_arg) > 0) {
1208 break; /* login success */
1210 cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
1212 } else if (cmdval == const_QUIT) {
1213 WRITE_OK(FTP_GOODBYE);
1216 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1219 WRITE_OK(FTP_LOGINOK);
1222 /* Do this after auth, else /etc/passwd is not accessible */
1228 const char *basedir = argv[0];
1230 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1231 close_on_exec_on(G.root_fd);
1233 if (chroot(basedir) == 0)
1242 * If chroot failed, assume that we aren't root,
1243 * and at least chdir to the specified DIR
1244 * (older versions were dying with error message).
1245 * If chroot worked, move current dir to new "/":
1250 #if ENABLE_FEATURE_FTP_AUTHENTICATION
1251 change_identity(pw);
1254 /* RFC-959 Section 5.1
1255 * The following commands and options MUST be supported by every
1256 * server-FTP and user-FTP, except in cases where the underlying
1257 * file system or operating system does not allow or support
1258 * a particular command.
1259 * Type: ASCII Non-print, IMAGE, LOCAL 8
1261 * Structure: File, Record*
1262 * (Record structure is REQUIRED only for hosts whose file
1263 * systems support record structure).
1265 * USER, PASS, ACCT, [bbox: ACCT not supported]
1270 * CWD, CDUP, RMD, MKD, PWD,
1276 * "The argument field is a Telnet string identifying the user's account.
1277 * The command is not necessarily related to the USER command, as some
1278 * sites may require an account for login and others only for specific
1279 * access, such as storing files. In the latter case the command may
1280 * arrive at any time.
1281 * There are reply codes to differentiate these cases for the automation:
1282 * when account information is required for login, the response to
1283 * a successful PASSword command is reply code 332. On the other hand,
1284 * if account information is NOT required for login, the reply to
1285 * a successful PASSword command is 230; and if the account information
1286 * is needed for a command issued later in the dialogue, the server
1287 * should return a 332 or 532 reply depending on whether it stores
1288 * (pending receipt of the ACCounT command) or discards the command,
1293 uint32_t cmdval = cmdio_get_cmd_and_arg();
1295 if (cmdval == const_QUIT) {
1296 WRITE_OK(FTP_GOODBYE);
1299 else if (cmdval == const_USER)
1300 /* This would mean "ok, now give me PASS". */
1301 /*WRITE_OK(FTP_GIVEPWORD);*/
1302 /* vsftpd can be configured to not require that,
1303 * and this also saves one roundtrip:
1305 WRITE_OK(FTP_LOGINOK);
1306 else if (cmdval == const_PASS)
1307 WRITE_OK(FTP_LOGINOK);
1308 else if (cmdval == const_NOOP)
1309 WRITE_OK(FTP_NOOPOK);
1310 else if (cmdval == const_TYPE)
1311 WRITE_OK(FTP_TYPEOK);
1312 else if (cmdval == const_STRU)
1313 WRITE_OK(FTP_STRUOK);
1314 else if (cmdval == const_MODE)
1315 WRITE_OK(FTP_MODEOK);
1316 else if (cmdval == const_ALLO)
1317 WRITE_OK(FTP_ALLOOK);
1318 else if (cmdval == const_SYST)
1319 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1320 else if (cmdval == const_PWD || cmdval == const_XPWD)
1322 else if (cmdval == const_CWD)
1324 else if (cmdval == const_CDUP) /* cd .. */
1326 /* HELP is nearly useless, but we can reuse FEAT for it */
1327 /* lftp uses FEAT */
1328 else if (cmdval == const_HELP || cmdval == const_FEAT)
1329 handle_feat(cmdval == const_HELP
1330 ? STRNUM32(FTP_HELP)
1331 : STRNUM32(FTP_STATOK)
1333 else if (cmdval == const_LIST) /* ls -l */
1335 else if (cmdval == const_NLST) /* "name list", bare ls */
1337 /* SIZE is crucial for wget's download indicator etc */
1338 /* Mozilla, lftp use MDTM (presumably for caching) */
1339 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1340 handle_size_or_mdtm(cmdval == const_SIZE);
1341 else if (cmdval == const_STAT) {
1342 if (G.ftp_arg == NULL)
1347 else if (cmdval == const_PASV)
1349 else if (cmdval == const_EPSV)
1351 else if (cmdval == const_RETR)
1353 else if (cmdval == const_PORT)
1355 else if (cmdval == const_REST)
1357 #if ENABLE_FEATURE_FTP_WRITE
1358 else if (opts & OPT_w) {
1359 if (cmdval == const_STOR)
1361 else if (cmdval == const_MKD)
1363 else if (cmdval == const_RMD)
1365 else if (cmdval == const_DELE)
1367 else if (cmdval == const_RNFR) /* "rename from" */
1369 else if (cmdval == const_RNTO) /* "rename to" */
1371 else if (cmdval == const_APPE)
1373 else if (cmdval == const_STOU) /* "store unique" */
1380 else if (cmdval == const_STOR
1381 || cmdval == const_MKD
1382 || cmdval == const_RMD
1383 || cmdval == const_DELE
1384 || cmdval == const_RNFR
1385 || cmdval == const_RNTO
1386 || cmdval == const_APPE
1387 || cmdval == const_STOU
1389 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1393 /* Which unsupported commands were seen in the wild?
1394 * (doesn't necessarily mean "we must support them")
1395 * foo 1.2.3: XXXX - comment
1397 #if ENABLE_FEATURE_FTP_WRITE
1400 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");