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 tarball for details.
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.
17 #include <netinet/tcp.h>
19 #define FTP_DATACONN 150
20 #define FTP_NOOPOK 200
21 #define FTP_TYPEOK 200
22 #define FTP_PORTOK 200
23 #define FTP_STRUOK 200
24 #define FTP_MODEOK 200
25 #define FTP_ALLOOK 202
26 #define FTP_STATOK 211
27 #define FTP_STATFILE_OK 213
29 #define FTP_SYSTOK 215
31 #define FTP_GOODBYE 221
32 #define FTP_TRANSFEROK 226
33 #define FTP_PASVOK 227
34 /*#define FTP_EPRTOK 228*/
35 #define FTP_EPSVOK 229
36 #define FTP_LOGINOK 230
38 #define FTP_RMDIROK 250
39 #define FTP_DELEOK 250
40 #define FTP_RENAMEOK 250
42 #define FTP_MKDIROK 257
43 #define FTP_GIVEPWORD 331
44 #define FTP_RESTOK 350
45 #define FTP_RNFROK 350
46 #define FTP_TIMEOUT 421
47 #define FTP_BADSENDCONN 425
48 #define FTP_BADSENDNET 426
49 #define FTP_BADSENDFILE 451
50 #define FTP_BADCMD 500
51 #define FTP_COMMANDNOTIMPL 502
52 #define FTP_NEEDUSER 503
53 #define FTP_NEEDRNFR 503
54 #define FTP_BADSTRU 504
55 #define FTP_BADMODE 504
56 #define FTP_LOGINERR 530
57 #define FTP_FILEFAIL 550
58 #define FTP_NOPERM 550
59 #define FTP_UPLOADFAIL 553
62 #define STR(s) STR1(s)
64 /* Convert a constant to 3-digit string, packed into uint32_t */
66 /* Shift for Nth decimal digit */
67 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
68 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
69 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
70 /* And for 4th position (space) */
71 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
73 #define STRNUM32(s) (uint32_t)(0 \
74 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
75 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
76 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
78 #define STRNUM32sp(s) (uint32_t)(0 \
80 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
81 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
82 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
85 #define MSG_OK "Operation successful\r\n"
86 #define MSG_ERR "Error\r\n"
99 len_and_sockaddr *local_addr;
100 len_and_sockaddr *port_addr;
103 #if ENABLE_FEATURE_FTP_WRITE
106 /* We need these aligned to uint32_t */
107 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
108 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
110 #define G (*(struct globals*)&bb_common_bufsiz1)
111 #define INIT_G() do { \
112 /* Moved to main */ \
113 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
114 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
119 escape_text(const char *prepend, const char *str, unsigned escapee)
121 unsigned retlen, remainlen, chunklen;
125 append = (char)escapee;
128 remainlen = strlen(str);
129 retlen = strlen(prepend);
130 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
131 strcpy(ret, prepend);
134 found = strchrnul(str, escapee);
135 chunklen = found - str + 1;
137 /* Copy chunk up to and including escapee (or NUL) to ret */
138 memcpy(ret + retlen, str, chunklen);
141 if (*found == '\0') {
142 /* It wasn't escapee, it was NUL! */
143 ret[retlen - 1] = append; /* replace NUL */
144 ret[retlen] = '\0'; /* add NUL */
147 ret[retlen++] = escapee; /* duplicate escapee */
153 /* Returns strlen as a bonus */
155 replace_char(char *str, char from, char to)
167 verbose_log(const char *str)
169 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
172 /* NB: status_str is char[4] packed into uint32_t */
174 cmdio_write(uint32_t status_str, const char *str)
179 /* FTP uses telnet protocol for command link.
180 * In telnet, 0xff is an escape char, and needs to be escaped: */
181 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
183 /* FTP sends embedded LFs as NULs */
184 len = replace_char(response, '\n', '\0');
186 response[len++] = '\n'; /* tack on trailing '\n' */
187 xwrite(STDOUT_FILENO, response, len);
189 verbose_log(response);
194 cmdio_write_ok(unsigned status)
196 *(uint32_t *) G.msg_ok = status;
197 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
199 verbose_log(G.msg_ok);
201 #define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
203 /* TODO: output strerr(errno) if errno != 0? */
205 cmdio_write_error(unsigned status)
207 *(uint32_t *) G.msg_err = status;
208 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
210 verbose_log(G.msg_err);
212 #define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
215 cmdio_write_raw(const char *p_text)
217 xwrite_str(STDOUT_FILENO, p_text);
223 timeout_handler(int sig UNUSED_PARAM)
226 int sv_errno = errno;
228 if ((int)(monotonic_sec() - G.end_time) >= 0)
231 if (!G.local_file_fd)
234 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
235 if (pos == G.local_file_pos)
237 G.local_file_pos = pos;
244 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
245 /* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
249 /* Simple commands */
254 char *cwd, *response;
256 cwd = xrealloc_getcwd_or_warn(NULL);
260 /* We have to promote each " to "" */
261 response = escape_text(" \"", cwd, ('"' << 8) + '"');
263 cmdio_write(STRNUM32(FTP_PWDOK), response);
270 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
271 WRITE_ERR(FTP_FILEFAIL);
280 G.ftp_arg = (char*)"..";
287 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
289 STR(FTP_STATOK)" Ok\r\n");
292 /* Examples of HELP and FEAT:
293 # nc -vvv ftp.kernel.org 21
294 ftp.kernel.org (130.239.17.4:21) open
295 220 Welcome to ftp.kernel.org.
308 214-The following commands are recognized.
309 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
310 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
311 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
316 handle_feat(unsigned status)
318 cmdio_write(status, "-Features:");
319 cmdio_write_raw(" EPSV\r\n"
324 cmdio_write(status, " Ok");
327 /* Download commands */
332 return (G.port_addr != NULL);
338 return (G.pasv_listen_fd > STDOUT_FILENO);
342 port_pasv_cleanup(void)
346 if (G.pasv_listen_fd > STDOUT_FILENO)
347 close(G.pasv_listen_fd);
348 G.pasv_listen_fd = -1;
351 /* On error, emits error code to the peer */
353 ftpdataio_get_pasv_fd(void)
357 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
360 WRITE_ERR(FTP_BADSENDCONN);
364 setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
368 /* Clears port/pasv data.
369 * This means we dont waste resources, for example, keeping
370 * PASV listening socket open when it is no longer needed.
371 * On error, emits error code to the peer (or exits).
372 * On success, emits p_status_msg to the peer.
375 get_remote_transfer_fd(const char *p_status_msg)
380 /* On error, emits error code to the peer */
381 remote_fd = ftpdataio_get_pasv_fd();
384 remote_fd = xconnect_stream(G.port_addr);
391 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
395 /* If there were neither PASV nor PORT, emits error code to the peer */
397 port_or_pasv_was_seen(void)
399 if (!pasv_active() && !port_active()) {
400 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
409 bind_for_passive_mode(void)
416 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
417 setsockopt_reuseaddr(fd);
419 set_nport(G.local_addr, 0);
420 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
422 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
424 port = get_nport(&G.local_addr->u.sa);
434 char *addr, *response;
436 port = bind_for_passive_mode();
438 if (G.local_addr->u.sa.sa_family == AF_INET)
439 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
440 else /* seen this in the wild done by other ftp servers: */
441 addr = xstrdup("0.0.0.0");
442 replace_char(addr, '.', ',');
444 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
445 addr, (int)(port >> 8), (int)(port & 255));
447 cmdio_write_raw(response);
458 port = bind_for_passive_mode();
459 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
460 cmdio_write_raw(response);
467 unsigned port, port_hi;
469 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
470 socklen_t peer_ipv4_len;
471 struct sockaddr_in peer_ipv4;
472 struct in_addr port_ipv4_sin_addr;
479 /* PORT command format makes sense only over IPv4 */
481 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
482 || G.local_addr->u.sa.sa_family != AF_INET
486 WRITE_ERR(FTP_BADCMD);
490 comma = strrchr(raw, ',');
494 port = bb_strtou(&comma[1], NULL, 10);
495 if (errno || port > 0xff)
498 comma = strrchr(raw, ',');
502 port_hi = bb_strtou(&comma[1], NULL, 10);
503 if (errno || port_hi > 0xff)
505 port |= port_hi << 8;
507 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
508 replace_char(raw, ',', '.');
510 /* We are verifying that PORT's IP matches getpeername().
511 * Otherwise peer can make us open data connections
512 * to other hosts (security problem!)
513 * This code would be too simplistic:
514 * lsa = xdotted2sockaddr(raw, port);
515 * if (lsa == NULL) goto bail;
517 if (!inet_aton(raw, &port_ipv4_sin_addr))
519 peer_ipv4_len = sizeof(peer_ipv4);
520 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
522 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
525 G.port_addr = xdotted2sockaddr(raw, port);
527 G.port_addr = get_peer_lsa(STDIN_FILENO);
528 set_nport(G.port_addr, htons(port));
530 WRITE_OK(FTP_PORTOK);
536 /* When ftp_arg == NULL simply restart from beginning */
537 G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0;
538 WRITE_OK(FTP_RESTOK);
545 off_t bytes_transferred;
548 off_t offset = G.restart_pos;
553 if (!port_or_pasv_was_seen())
554 return; /* port_or_pasv_was_seen emitted error response */
556 /* O_NONBLOCK is useful if file happens to be a device node */
557 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
558 if (local_file_fd < 0) {
559 WRITE_ERR(FTP_FILEFAIL);
563 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
564 /* Note - pretend open failed */
565 WRITE_ERR(FTP_FILEFAIL);
568 G.local_file_fd = local_file_fd;
570 /* Now deactive O_NONBLOCK, otherwise we have a problem
571 * on DMAPI filesystems such as XFS DMAPI.
573 ndelay_off(local_file_fd);
575 /* Set the download offset (from REST) if any */
577 xlseek(local_file_fd, offset, SEEK_SET);
579 response = xasprintf(
580 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
581 G.ftp_arg, statbuf.st_size);
582 remote_fd = get_remote_transfer_fd(response);
587 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
589 if (bytes_transferred < 0)
590 WRITE_ERR(FTP_BADSENDFILE);
592 WRITE_OK(FTP_TRANSFEROK);
595 close(local_file_fd);
602 popen_ls(const char *opt)
605 struct fd_pair outfd;
609 argv[1] = opt; /* "-l" or "-1" */
613 /* NOMMU ftpd ls helper chdirs to argv[2],
614 * preventing peer from seeing real root. */
615 argv[2] = xrealloc_getcwd_or_warn(NULL);
620 /* Improve compatibility with non-RFC conforming FTP clients
621 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
622 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
623 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
624 && G.ftp_arg && G.ftp_arg[0] == '-'
626 const char *tmp = strchr(G.ftp_arg, ' ');
627 if (tmp) /* skip the space */
634 /*fflush_all(); - so far we dont use stdio on output */
635 pid = BB_MMU ? fork() : vfork();
637 bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork");
642 /* On NOMMU, we want to execute a child - copy of ourself.
643 * In chroot we usually can't do it. Thus we chdir
644 * out of the chroot back to original root,
645 * and (see later below) execute bb_busybox_exec_path
646 * relative to current directory */
647 if (fchdir(G.root_fd) != 0)
649 /*close(G.root_fd); - close_on_exec_on() took care of this */
651 /* NB: close _first_, then move fd! */
653 xmove_fd(outfd.wr, STDOUT_FILENO);
654 /* Opening /dev/null in chroot is hard.
655 * Just making sure STDIN_FILENO is opened
656 * to something harmless. Paranoia,
657 * ls won't read it anyway */
659 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
661 /* memset(&G, 0, sizeof(G)); - ls_main does it */
662 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
664 /* + 1: we must use relative path here if in chroot.
665 * For example, execv("/proc/self/exe") will fail, since
666 * it looks for "/proc/self/exe" _relative to chroot!_ */
667 execv(bb_busybox_exec_path + 1, (char**) argv);
675 free((char*)argv[2]);
686 handle_dir_common(int opts)
692 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
693 return; /* port_or_pasv_was_seen emitted error response */
695 /* -n prevents user/groupname display,
696 * which can be problematic in chroot */
697 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
698 ls_fp = xfdopen_for_read(ls_fd);
700 if (opts & USE_CTRL_CONN) {
701 /* STAT <filename> */
702 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
704 line = xmalloc_fgetline(ls_fp);
707 /* Hack: 0 results in no status at all */
708 /* Note: it's ok that we don't prepend space,
709 * ftp.kernel.org doesn't do that too */
710 cmdio_write(0, line);
713 WRITE_OK(FTP_STATFILE_OK);
715 /* LIST/NLST [<filename>] */
716 int remote_fd = get_remote_transfer_fd(" Directory listing");
717 if (remote_fd >= 0) {
719 line = xmalloc_fgetline(ls_fp);
722 /* I've seen clients complaining when they
723 * are fed with ls output with bare '\n'.
724 * Pity... that would be much simpler.
726 /* TODO: need to s/LF/NUL/g here */
727 xwrite_str(remote_fd, line);
728 xwrite(remote_fd, "\r\n", 2);
733 WRITE_OK(FTP_TRANSFEROK);
735 fclose(ls_fp); /* closes ls_fd too */
740 handle_dir_common(LONG_LISTING);
745 /* NLST returns list of names, "\r\n" terminated without regard
746 * to the current binary flag. Names may start with "/",
747 * then they represent full names (we don't produce such names),
748 * otherwise names are relative to current directory.
749 * Embedded "\n" are replaced by NULs. This is safe since names
750 * can never contain NUL.
752 handle_dir_common(0);
755 handle_stat_file(void)
757 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
760 /* This can be extended to handle MLST, as all info is available
761 * in struct stat for that:
763 * 250-Listing file_name
764 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
767 * MLST [<file or dir name, "." assumed if not given>]
768 * Returned name should be either the same as requested, or fully qualified.
769 * If there was no parameter, return "" or (preferred) fully-qualified name.
770 * Returned "facts" (case is not important):
771 * size - size in octets
772 * modify - last modification time
773 * type - entry type (file,dir,OS.unix=block)
774 * (+ cdir and pdir types for MLSD)
775 * unique - unique id of file/directory (inode#)
777 * a: can be appended to (APPE)
778 * d: can be deleted (RMD/DELE)
779 * f: can be renamed (RNFR)
780 * r: can be read (RETR)
781 * w: can be written (STOR)
782 * e: can CWD into this dir
783 * l: this dir can be listed (dir only!)
784 * c: can create files in this dir
785 * m: can create dirs in this dir (MKD)
786 * p: can delete files in this dir
787 * UNIX.mode - unix file mode
790 handle_size_or_mdtm(int need_size)
793 struct tm broken_out;
794 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
795 | sizeof("NNN YYYYMMDDhhmmss\r\n")
799 || stat(G.ftp_arg, &statbuf) != 0
800 || !S_ISREG(statbuf.st_mode)
802 WRITE_ERR(FTP_FILEFAIL);
806 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
808 gmtime_r(&statbuf.st_mtime, &broken_out);
809 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
810 broken_out.tm_year + 1900,
817 cmdio_write_raw(buf);
820 /* Upload commands */
822 #if ENABLE_FEATURE_FTP_WRITE
826 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
827 WRITE_ERR(FTP_FILEFAIL);
830 WRITE_OK(FTP_MKDIROK);
836 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
837 WRITE_ERR(FTP_FILEFAIL);
840 WRITE_OK(FTP_RMDIROK);
846 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
847 WRITE_ERR(FTP_FILEFAIL);
850 WRITE_OK(FTP_DELEOK);
856 free(G.rnfr_filename);
857 G.rnfr_filename = xstrdup(G.ftp_arg);
858 WRITE_OK(FTP_RNFROK);
866 /* If we didn't get a RNFR, throw a wobbly */
867 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
868 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
872 retval = rename(G.rnfr_filename, G.ftp_arg);
873 free(G.rnfr_filename);
874 G.rnfr_filename = NULL;
877 WRITE_ERR(FTP_FILEFAIL);
880 WRITE_OK(FTP_RENAMEOK);
884 handle_upload_common(int is_append, int is_unique)
888 off_t bytes_transferred;
893 offset = G.restart_pos;
896 if (!port_or_pasv_was_seen())
897 return; /* port_or_pasv_was_seen emitted error response */
902 tempname = xstrdup(" FILE: uniq.XXXXXX");
903 local_file_fd = mkstemp(tempname + 7);
904 } else if (G.ftp_arg) {
905 int flags = O_WRONLY | O_CREAT | O_TRUNC;
907 flags = O_WRONLY | O_CREAT | O_APPEND;
909 flags = O_WRONLY | O_CREAT;
910 local_file_fd = open(G.ftp_arg, flags, 0666);
913 if (local_file_fd < 0
914 || fstat(local_file_fd, &statbuf) != 0
915 || !S_ISREG(statbuf.st_mode)
917 WRITE_ERR(FTP_UPLOADFAIL);
918 if (local_file_fd >= 0)
919 goto close_local_and_bail;
922 G.local_file_fd = local_file_fd;
925 xlseek(local_file_fd, offset, SEEK_SET);
927 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
931 goto close_local_and_bail;
933 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
935 if (bytes_transferred < 0)
936 WRITE_ERR(FTP_BADSENDFILE);
938 WRITE_OK(FTP_TRANSFEROK);
940 close_local_and_bail:
941 close(local_file_fd);
948 handle_upload_common(0, 0);
955 handle_upload_common(1, 0);
962 handle_upload_common(0, 1);
964 #endif /* ENABLE_FEATURE_FTP_WRITE */
967 cmdio_get_cmd_and_arg(void)
977 /* Paranoia. Peer may send 1 gigabyte long cmd... */
978 /* Using separate len_on_stk instead of len optimizes
979 * code size (allows len to be in CPU register) */
980 size_t len_on_stk = 8 * 1024;
981 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
987 /* De-escape telnet: 0xff,0xff => 0xff */
988 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
989 * data transfer, and may be preceded by telnet's "Interrupt Process"
990 * code (two-byte sequence 255,244) and then by telnet "Synch" code
991 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
992 * and may generate SIGURG on our side. See RFC854).
993 * So far we don't support that (may install SIGURG handler if we'd want to),
994 * but we need to at least remove 255,xxx pairs. lftp sends those. */
995 /* Then de-escape FTP: NUL => '\n' */
997 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
998 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
999 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1000 * Testing for embedded LF:
1001 * LF_HERE=`echo -ne "LF\nHERE"`
1002 * echo Hello >"$LF_HERE"
1003 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1008 /* Strip "\r\n" if it is there */
1009 if (len != 0 && cmd[len - 1] == '\n') {
1011 if (len != 0 && cmd[len - 1] == '\r')
1015 src = strchrnul(cmd, 0xff) - cmd;
1016 /* 99,99% there are neither NULs nor 255s and src == len */
1020 if ((unsigned char)(cmd[src]) == 255) {
1022 /* 255,xxx - skip 255 */
1023 if ((unsigned char)(cmd[src]) != 255) {
1024 /* 255,!255 - skip both */
1028 /* 255,255 - retain one 255 */
1031 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1033 } while (src < len);
1041 G.ftp_arg = strchr(cmd, ' ');
1042 if (G.ftp_arg != NULL)
1043 *G.ftp_arg++ = '\0';
1045 /* Uppercase and pack into uint32_t first word of the command */
1048 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1053 #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1054 #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1056 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1057 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1058 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1059 const_CWD = mk_const3('C', 'W', 'D'),
1060 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1061 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
1062 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
1063 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1064 const_LIST = mk_const4('L', 'I', 'S', 'T'),
1065 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
1066 const_MKD = mk_const3('M', 'K', 'D'),
1067 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1068 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1069 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1070 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1071 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1072 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1073 const_PWD = mk_const3('P', 'W', 'D'),
1074 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1075 const_REST = mk_const4('R', 'E', 'S', 'T'),
1076 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1077 const_RMD = mk_const3('R', 'M', 'D'),
1078 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1079 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1080 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1081 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1082 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1083 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1084 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1085 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1086 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1087 const_USER = mk_const4('U', 'S', 'E', 'R'),
1093 OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
1094 OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
1095 OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
1098 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1100 int ftpd_main(int argc, char **argv)
1102 int ftpd_main(int argc UNUSED_PARAM, char **argv)
1105 unsigned abs_timeout;
1111 abs_timeout = 1 * 60 * 60;
1114 opt_complementary = "t+:T+:vv:SS";
1116 opts = getopt32(argv, "vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1118 opts = getopt32(argv, "l1vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1119 if (opts & (OPT_l|OPT_1)) {
1120 /* Our secret backdoor to ls */
1121 /* TODO: pass -n? It prevents user/group resolution, which may not work in chroot anyway */
1122 /* TODO: pass -A? It shows dot files */
1123 /* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
1125 argv[2] = (char*)"--";
1126 /* memset(&G, 0, sizeof(G)); - ls_main does it */
1127 return ls_main(argc, argv);
1130 if (G.verbose < verbose_S)
1131 G.verbose = verbose_S;
1132 if (abs_timeout | G.timeout) {
1133 if (abs_timeout == 0)
1134 abs_timeout = INT_MAX;
1135 G.end_time = monotonic_sec() + abs_timeout;
1136 if (G.timeout > abs_timeout)
1137 G.timeout = abs_timeout;
1139 strcpy(G.msg_ok + 4, MSG_OK );
1140 strcpy(G.msg_err + 4, MSG_ERR);
1142 G.local_addr = get_sock_lsa(STDIN_FILENO);
1143 if (!G.local_addr) {
1144 /* This is confusing:
1145 * bb_error_msg_and_die("stdin is not a socket");
1148 /* Help text says that ftpd must be used as inetd service,
1149 * which is by far the most usual cause of get_sock_lsa
1153 if (!(opts & OPT_v))
1154 logmode = LOGMODE_NONE;
1156 /* LOG_NDELAY is needed since we may chroot later */
1157 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1158 logmode |= LOGMODE_SYSLOG;
1161 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1164 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1165 close_on_exec_on(G.root_fd);
1169 xchdir(argv[optind]);
1173 //umask(077); - admin can set umask before starting us
1175 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
1176 signal(SIGPIPE, SIG_IGN);
1178 /* Set up options on the command socket (do we need these all? why?) */
1179 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1180 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
1181 /* Telnet protocol over command link may send "urgent" data,
1182 * we prefer it to be received in the "normal" data stream: */
1183 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1185 WRITE_OK(FTP_GREET);
1186 signal(SIGALRM, timeout_handler);
1188 #ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
1190 smallint user_was_specified = 0;
1192 uint32_t cmdval = cmdio_get_cmd_and_arg();
1194 if (cmdval == const_USER) {
1195 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
1196 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
1198 user_was_specified = 1;
1199 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
1201 } else if (cmdval == const_PASS) {
1202 if (user_was_specified)
1204 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
1205 } else if (cmdval == const_QUIT) {
1206 WRITE_OK(FTP_GOODBYE);
1209 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1213 WRITE_OK(FTP_LOGINOK);
1216 /* RFC-959 Section 5.1
1217 * The following commands and options MUST be supported by every
1218 * server-FTP and user-FTP, except in cases where the underlying
1219 * file system or operating system does not allow or support
1220 * a particular command.
1221 * Type: ASCII Non-print, IMAGE, LOCAL 8
1223 * Structure: File, Record*
1224 * (Record structure is REQUIRED only for hosts whose file
1225 * systems support record structure).
1227 * USER, PASS, ACCT, [bbox: ACCT not supported]
1232 * CWD, CDUP, RMD, MKD, PWD,
1238 * "The argument field is a Telnet string identifying the user's account.
1239 * The command is not necessarily related to the USER command, as some
1240 * sites may require an account for login and others only for specific
1241 * access, such as storing files. In the latter case the command may
1242 * arrive at any time.
1243 * There are reply codes to differentiate these cases for the automation:
1244 * when account information is required for login, the response to
1245 * a successful PASSword command is reply code 332. On the other hand,
1246 * if account information is NOT required for login, the reply to
1247 * a successful PASSword command is 230; and if the account information
1248 * is needed for a command issued later in the dialogue, the server
1249 * should return a 332 or 532 reply depending on whether it stores
1250 * (pending receipt of the ACCounT command) or discards the command,
1255 uint32_t cmdval = cmdio_get_cmd_and_arg();
1257 if (cmdval == const_QUIT) {
1258 WRITE_OK(FTP_GOODBYE);
1261 else if (cmdval == const_USER)
1262 /* This would mean "ok, now give me PASS". */
1263 /*WRITE_OK(FTP_GIVEPWORD);*/
1264 /* vsftpd can be configured to not require that,
1265 * and this also saves one roundtrip:
1267 WRITE_OK(FTP_LOGINOK);
1268 else if (cmdval == const_PASS)
1269 WRITE_OK(FTP_LOGINOK);
1270 else if (cmdval == const_NOOP)
1271 WRITE_OK(FTP_NOOPOK);
1272 else if (cmdval == const_TYPE)
1273 WRITE_OK(FTP_TYPEOK);
1274 else if (cmdval == const_STRU)
1275 WRITE_OK(FTP_STRUOK);
1276 else if (cmdval == const_MODE)
1277 WRITE_OK(FTP_MODEOK);
1278 else if (cmdval == const_ALLO)
1279 WRITE_OK(FTP_ALLOOK);
1280 else if (cmdval == const_SYST)
1281 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1282 else if (cmdval == const_PWD)
1284 else if (cmdval == const_CWD)
1286 else if (cmdval == const_CDUP) /* cd .. */
1288 /* HELP is nearly useless, but we can reuse FEAT for it */
1289 /* lftp uses FEAT */
1290 else if (cmdval == const_HELP || cmdval == const_FEAT)
1291 handle_feat(cmdval == const_HELP
1292 ? STRNUM32(FTP_HELP)
1293 : STRNUM32(FTP_STATOK)
1295 else if (cmdval == const_LIST) /* ls -l */
1297 else if (cmdval == const_NLST) /* "name list", bare ls */
1299 /* SIZE is crucial for wget's download indicator etc */
1300 /* Mozilla, lftp use MDTM (presumably for caching) */
1301 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1302 handle_size_or_mdtm(cmdval == const_SIZE);
1303 else if (cmdval == const_STAT) {
1304 if (G.ftp_arg == NULL)
1309 else if (cmdval == const_PASV)
1311 else if (cmdval == const_EPSV)
1313 else if (cmdval == const_RETR)
1315 else if (cmdval == const_PORT)
1317 else if (cmdval == const_REST)
1319 #if ENABLE_FEATURE_FTP_WRITE
1320 else if (opts & OPT_w) {
1321 if (cmdval == const_STOR)
1323 else if (cmdval == const_MKD)
1325 else if (cmdval == const_RMD)
1327 else if (cmdval == const_DELE)
1329 else if (cmdval == const_RNFR) /* "rename from" */
1331 else if (cmdval == const_RNTO) /* "rename to" */
1333 else if (cmdval == const_APPE)
1335 else if (cmdval == const_STOU) /* "store unique" */
1342 else if (cmdval == const_STOR
1343 || cmdval == const_MKD
1344 || cmdval == const_RMD
1345 || cmdval == const_DELE
1346 || cmdval == const_RNFR
1347 || cmdval == const_RNTO
1348 || cmdval == const_APPE
1349 || cmdval == const_STOU
1351 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1355 /* Which unsupported commands were seen in the wild?
1356 * (doesn't necessarily mean "we must support them")
1357 * foo 1.2.3: XXXX - comment
1359 #if ENABLE_FEATURE_FTP_WRITE
1362 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");