busybox: update to 1.23.2
[tomato.git] / release / src / router / busybox / networking / ftpd.c
blob0c10e1f25ddedd90b46d1331ec5493acc43521a6
1 /* vi: set sw=4 ts=4: */
2 /*
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"
19 //usage: "\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"
31 #include "libbb.h"
32 #include <syslog.h>
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
44 #define FTP_HELP 214
45 #define FTP_SYSTOK 215
46 #define FTP_GREET 220
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
53 #define FTP_CWDOK 250
54 #define FTP_RMDIROK 250
55 #define FTP_DELEOK 250
56 #define FTP_RENAMEOK 250
57 #define FTP_PWDOK 257
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
77 #define STR1(s) #s
78 #define STR(s) STR1(s)
80 /* Convert a constant to 3-digit string, packed into uint32_t */
81 enum {
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 \
95 | (' ' << SHIFTsp) \
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"
104 struct globals {
105 int pasv_listen_fd;
106 #if !BB_MMU
107 int root_fd;
108 #endif
109 int local_file_fd;
110 unsigned end_time;
111 unsigned timeout;
112 unsigned verbose;
113 off_t local_file_pos;
114 off_t restart_pos;
115 len_and_sockaddr *local_addr;
116 len_and_sockaddr *port_addr;
117 char *ftp_cmd;
118 char *ftp_arg;
119 #if ENABLE_FEATURE_FTP_WRITE
120 char *rnfr_filename;
121 #endif
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];
125 } FIX_ALIASING;
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);*/ \
131 } while (0)
134 static char *
135 escape_text(const char *prepend, const char *str, unsigned escapee)
137 unsigned retlen, remainlen, chunklen;
138 char *ret, *found;
139 char append;
141 append = (char)escapee;
142 escapee >>= 8;
144 remainlen = strlen(str);
145 retlen = strlen(prepend);
146 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
147 strcpy(ret, prepend);
149 for (;;) {
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);
155 retlen += 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 */
161 break;
163 ret[retlen++] = escapee; /* duplicate escapee */
164 str = found + 1;
166 return ret;
169 /* Returns strlen as a bonus */
170 static unsigned
171 replace_char(char *str, char from, char to)
173 char *p = str;
174 while (*p) {
175 if (*p == from)
176 *p = to;
177 p++;
179 return p - str;
182 static void
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 */
189 static void
190 cmdio_write(uint32_t status_str, const char *str)
192 char *response;
193 int len;
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);
204 if (G.verbose > 1)
205 verbose_log(response);
206 free(response);
209 static void
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);
214 if (G.verbose > 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? */
220 static void
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);
225 if (G.verbose > 0)
226 verbose_log(G.msg_err);
228 #define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
230 static void
231 cmdio_write_raw(const char *p_text)
233 xwrite_str(STDOUT_FILENO, p_text);
234 if (G.verbose > 1)
235 verbose_log(p_text);
238 static void
239 timeout_handler(int sig UNUSED_PARAM)
241 off_t pos;
242 int sv_errno = errno;
244 if ((int)(monotonic_sec() - G.end_time) >= 0)
245 goto timed_out;
247 if (!G.local_file_fd)
248 goto timed_out;
250 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
251 if (pos == G.local_file_pos)
252 goto timed_out;
253 G.local_file_pos = pos;
255 alarm(G.timeout);
256 errno = sv_errno;
257 return;
259 timed_out:
260 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
261 /* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
262 exit(1);
265 /* Simple commands */
267 static void
268 handle_pwd(void)
270 char *cwd, *response;
272 cwd = xrealloc_getcwd_or_warn(NULL);
273 if (cwd == NULL)
274 cwd = xstrdup("");
276 /* We have to promote each " to "" */
277 response = escape_text(" \"", cwd, ('"' << 8) + '"');
278 free(cwd);
279 cmdio_write(STRNUM32(FTP_PWDOK), response);
280 free(response);
283 static void
284 handle_cwd(void)
286 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
287 WRITE_ERR(FTP_FILEFAIL);
288 return;
290 WRITE_OK(FTP_CWDOK);
293 static void
294 handle_cdup(void)
296 G.ftp_arg = (char*)"..";
297 handle_cwd();
300 static void
301 handle_stat(void)
303 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
304 " TYPE: BINARY\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.
312 FEAT
313 211-Features:
314 EPRT
315 EPSV
316 MDTM
317 PASV
318 REST STREAM
319 SIZE
320 TVFS
321 UTF8
322 211 End
323 HELP
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
328 XPWD XRMD
329 214 Help OK.
331 static void
332 handle_feat(unsigned status)
334 cmdio_write(status, "-Features:");
335 cmdio_write_raw(" EPSV\r\n"
336 " PASV\r\n"
337 " REST STREAM\r\n"
338 " MDTM\r\n"
339 " SIZE\r\n");
340 cmdio_write(status, " Ok");
343 /* Download commands */
345 static inline int
346 port_active(void)
348 return (G.port_addr != NULL);
351 static inline int
352 pasv_active(void)
354 return (G.pasv_listen_fd > STDOUT_FILENO);
357 static void
358 port_pasv_cleanup(void)
360 free(G.port_addr);
361 G.port_addr = NULL;
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 */
368 static int
369 ftpdataio_get_pasv_fd(void)
371 int remote_fd;
373 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
375 if (remote_fd < 0) {
376 WRITE_ERR(FTP_BADSENDCONN);
377 return remote_fd;
380 setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
381 return remote_fd;
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.
390 static int
391 get_remote_transfer_fd(const char *p_status_msg)
393 int remote_fd;
395 if (pasv_active())
396 /* On error, emits error code to the peer */
397 remote_fd = ftpdataio_get_pasv_fd();
398 else
399 /* Exits on error */
400 remote_fd = xconnect_stream(G.port_addr);
402 port_pasv_cleanup();
404 if (remote_fd < 0)
405 return remote_fd;
407 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
408 return remote_fd;
411 /* If there were neither PASV nor PORT, emits error code to the peer */
412 static int
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");
417 return 0;
420 return 1;
423 /* Exits on error */
424 static unsigned
425 bind_for_passive_mode(void)
427 int fd;
428 unsigned port;
430 port_pasv_cleanup();
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);
437 xlisten(fd, 1);
438 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
440 port = get_nport(&G.local_addr->u.sa);
441 port = ntohs(port);
442 return port;
445 /* Exits on error */
446 static void
447 handle_pasv(void)
449 unsigned port;
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));
462 free(addr);
463 cmdio_write_raw(response);
464 free(response);
467 /* Exits on error */
468 static void
469 handle_epsv(void)
471 unsigned port;
472 char *response;
474 port = bind_for_passive_mode();
475 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
476 cmdio_write_raw(response);
477 free(response);
480 static void
481 handle_port(void)
483 unsigned port, port_hi;
484 char *raw, *comma;
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;
489 #endif
491 port_pasv_cleanup();
493 raw = G.ftp_arg;
495 /* PORT command format makes sense only over IPv4 */
496 if (!raw
497 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
498 || G.local_addr->u.sa.sa_family != AF_INET
499 #endif
501 bail:
502 WRITE_ERR(FTP_BADCMD);
503 return;
506 comma = strrchr(raw, ',');
507 if (comma == NULL)
508 goto bail;
509 *comma = '\0';
510 port = bb_strtou(&comma[1], NULL, 10);
511 if (errno || port > 0xff)
512 goto bail;
514 comma = strrchr(raw, ',');
515 if (comma == NULL)
516 goto bail;
517 *comma = '\0';
518 port_hi = bb_strtou(&comma[1], NULL, 10);
519 if (errno || port_hi > 0xff)
520 goto bail;
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))
534 goto bail;
535 peer_ipv4_len = sizeof(peer_ipv4);
536 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
537 goto bail;
538 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
539 goto bail;
541 G.port_addr = xdotted2sockaddr(raw, port);
542 #else
543 G.port_addr = get_peer_lsa(STDIN_FILENO);
544 set_nport(&G.port_addr->u.sa, htons(port));
545 #endif
546 WRITE_OK(FTP_PORTOK);
549 static void
550 handle_rest(void)
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);
557 static void
558 handle_retr(void)
560 struct stat statbuf;
561 off_t bytes_transferred;
562 int remote_fd;
563 int local_file_fd;
564 off_t offset = G.restart_pos;
565 char *response;
567 G.restart_pos = 0;
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);
576 return;
579 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
580 /* Note - pretend open failed */
581 WRITE_ERR(FTP_FILEFAIL);
582 goto file_close_out;
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 */
592 if (offset != 0)
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);
599 free(response);
600 if (remote_fd < 0)
601 goto file_close_out;
603 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
604 close(remote_fd);
605 if (bytes_transferred < 0)
606 WRITE_ERR(FTP_BADSENDFILE);
607 else
608 WRITE_OK(FTP_TRANSFEROK);
610 file_close_out:
611 close(local_file_fd);
612 G.local_file_fd = 0;
615 /* List commands */
617 static int
618 popen_ls(const char *opt)
620 const char *argv[5];
621 struct fd_pair outfd;
622 pid_t pid;
624 argv[0] = "ftpd";
625 argv[1] = opt; /* "-lA" or "-1A" */
626 argv[2] = "--";
627 argv[3] = G.ftp_arg;
628 argv[4] = NULL;
630 /* Improve compatibility with non-RFC conforming FTP clients
631 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
632 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
633 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
634 && G.ftp_arg && G.ftp_arg[0] == '-'
636 const char *tmp = strchr(G.ftp_arg, ' ');
637 if (tmp) /* skip the space */
638 tmp++;
639 argv[3] = tmp;
642 xpiped_pair(outfd);
644 /*fflush_all(); - so far we dont use stdio on output */
645 pid = BB_MMU ? xfork() : xvfork();
646 if (pid == 0) {
647 #if !BB_MMU
648 int cur_fd;
649 #endif
650 /* child */
651 /* NB: close _first_, then move fd! */
652 close(outfd.rd);
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 */
658 close(STDIN_FILENO);
659 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
660 #if BB_MMU
661 /* memset(&G, 0, sizeof(G)); - ls_main does it */
662 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
663 #else
664 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
665 /* On NOMMU, we want to execute a child - copy of ourself
666 * in order to unblock parent after vfork.
667 * In chroot we usually can't re-exec. Thus we escape
668 * out of the chroot back to original root.
670 if (G.root_fd >= 0) {
671 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
672 _exit(127);
673 /*close(G.root_fd); - close_on_exec_on() took care of this */
675 /* Child expects directory to list on fd #3 */
676 xmove_fd(cur_fd, 3);
677 execv(bb_busybox_exec_path, (char**) argv);
678 _exit(127);
679 #endif
682 /* parent */
683 close(outfd.wr);
684 return outfd.rd;
687 enum {
688 USE_CTRL_CONN = 1,
689 LONG_LISTING = 2,
692 static void
693 handle_dir_common(int opts)
695 FILE *ls_fp;
696 char *line;
697 int ls_fd;
699 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
700 return; /* port_or_pasv_was_seen emitted error response */
702 ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
703 ls_fp = xfdopen_for_read(ls_fd);
704 /* FIXME: filenames with embedded newlines are mishandled */
706 if (opts & USE_CTRL_CONN) {
707 /* STAT <filename> */
708 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
709 while (1) {
710 line = xmalloc_fgetline(ls_fp);
711 if (!line)
712 break;
713 /* Hack: 0 results in no status at all */
714 /* Note: it's ok that we don't prepend space,
715 * ftp.kernel.org doesn't do that too */
716 cmdio_write(0, line);
717 free(line);
719 WRITE_OK(FTP_STATFILE_OK);
720 } else {
721 /* LIST/NLST [<filename>] */
722 int remote_fd = get_remote_transfer_fd(" Directory listing");
723 if (remote_fd >= 0) {
724 while (1) {
725 unsigned len;
727 line = xmalloc_fgets(ls_fp);
728 if (!line)
729 break;
730 /* I've seen clients complaining when they
731 * are fed with ls output with bare '\n'.
732 * Replace trailing "\n\0" with "\r\n".
734 len = strlen(line);
735 if (len != 0) /* paranoia check */
736 line[len - 1] = '\r';
737 line[len] = '\n';
738 xwrite(remote_fd, line, len + 1);
739 free(line);
742 close(remote_fd);
743 WRITE_OK(FTP_TRANSFEROK);
745 fclose(ls_fp); /* closes ls_fd too */
747 static void
748 handle_list(void)
750 handle_dir_common(LONG_LISTING);
752 static void
753 handle_nlst(void)
755 /* NLST returns list of names, "\r\n" terminated without regard
756 * to the current binary flag. Names may start with "/",
757 * then they represent full names (we don't produce such names),
758 * otherwise names are relative to current directory.
759 * Embedded "\n" are replaced by NULs. This is safe since names
760 * can never contain NUL.
762 handle_dir_common(0);
764 static void
765 handle_stat_file(void)
767 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
770 /* This can be extended to handle MLST, as all info is available
771 * in struct stat for that:
772 * MLST file_name
773 * 250-Listing file_name
774 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
775 * 250 End
776 * Nano-doc:
777 * MLST [<file or dir name, "." assumed if not given>]
778 * Returned name should be either the same as requested, or fully qualified.
779 * If there was no parameter, return "" or (preferred) fully-qualified name.
780 * Returned "facts" (case is not important):
781 * size - size in octets
782 * modify - last modification time
783 * type - entry type (file,dir,OS.unix=block)
784 * (+ cdir and pdir types for MLSD)
785 * unique - unique id of file/directory (inode#)
786 * perm -
787 * a: can be appended to (APPE)
788 * d: can be deleted (RMD/DELE)
789 * f: can be renamed (RNFR)
790 * r: can be read (RETR)
791 * w: can be written (STOR)
792 * e: can CWD into this dir
793 * l: this dir can be listed (dir only!)
794 * c: can create files in this dir
795 * m: can create dirs in this dir (MKD)
796 * p: can delete files in this dir
797 * UNIX.mode - unix file mode
799 static void
800 handle_size_or_mdtm(int need_size)
802 struct stat statbuf;
803 struct tm broken_out;
804 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
805 | sizeof("NNN YYYYMMDDhhmmss\r\n")
808 if (!G.ftp_arg
809 || stat(G.ftp_arg, &statbuf) != 0
810 || !S_ISREG(statbuf.st_mode)
812 WRITE_ERR(FTP_FILEFAIL);
813 return;
815 if (need_size) {
816 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
817 } else {
818 gmtime_r(&statbuf.st_mtime, &broken_out);
819 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
820 broken_out.tm_year + 1900,
821 broken_out.tm_mon + 1,
822 broken_out.tm_mday,
823 broken_out.tm_hour,
824 broken_out.tm_min,
825 broken_out.tm_sec);
827 cmdio_write_raw(buf);
830 /* Upload commands */
832 #if ENABLE_FEATURE_FTP_WRITE
833 static void
834 handle_mkd(void)
836 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
837 WRITE_ERR(FTP_FILEFAIL);
838 return;
840 WRITE_OK(FTP_MKDIROK);
843 static void
844 handle_rmd(void)
846 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
847 WRITE_ERR(FTP_FILEFAIL);
848 return;
850 WRITE_OK(FTP_RMDIROK);
853 static void
854 handle_dele(void)
856 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
857 WRITE_ERR(FTP_FILEFAIL);
858 return;
860 WRITE_OK(FTP_DELEOK);
863 static void
864 handle_rnfr(void)
866 free(G.rnfr_filename);
867 G.rnfr_filename = xstrdup(G.ftp_arg);
868 WRITE_OK(FTP_RNFROK);
871 static void
872 handle_rnto(void)
874 int retval;
876 /* If we didn't get a RNFR, throw a wobbly */
877 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
878 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
879 return;
882 retval = rename(G.rnfr_filename, G.ftp_arg);
883 free(G.rnfr_filename);
884 G.rnfr_filename = NULL;
886 if (retval) {
887 WRITE_ERR(FTP_FILEFAIL);
888 return;
890 WRITE_OK(FTP_RENAMEOK);
893 static void
894 handle_upload_common(int is_append, int is_unique)
896 struct stat statbuf;
897 char *tempname;
898 off_t bytes_transferred;
899 off_t offset;
900 int local_file_fd;
901 int remote_fd;
903 offset = G.restart_pos;
904 G.restart_pos = 0;
906 if (!port_or_pasv_was_seen())
907 return; /* port_or_pasv_was_seen emitted error response */
909 tempname = NULL;
910 local_file_fd = -1;
911 if (is_unique) {
912 tempname = xstrdup(" FILE: uniq.XXXXXX");
913 local_file_fd = mkstemp(tempname + 7);
914 } else if (G.ftp_arg) {
915 int flags = O_WRONLY | O_CREAT | O_TRUNC;
916 if (is_append)
917 flags = O_WRONLY | O_CREAT | O_APPEND;
918 if (offset)
919 flags = O_WRONLY | O_CREAT;
920 local_file_fd = open(G.ftp_arg, flags, 0666);
923 if (local_file_fd < 0
924 || fstat(local_file_fd, &statbuf) != 0
925 || !S_ISREG(statbuf.st_mode)
927 free(tempname);
928 WRITE_ERR(FTP_UPLOADFAIL);
929 if (local_file_fd >= 0)
930 goto close_local_and_bail;
931 return;
933 G.local_file_fd = local_file_fd;
935 if (offset)
936 xlseek(local_file_fd, offset, SEEK_SET);
938 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
939 free(tempname);
941 if (remote_fd < 0)
942 goto close_local_and_bail;
944 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
945 close(remote_fd);
946 if (bytes_transferred < 0)
947 WRITE_ERR(FTP_BADSENDFILE);
948 else
949 WRITE_OK(FTP_TRANSFEROK);
951 close_local_and_bail:
952 close(local_file_fd);
953 G.local_file_fd = 0;
956 static void
957 handle_stor(void)
959 handle_upload_common(0, 0);
962 static void
963 handle_appe(void)
965 G.restart_pos = 0;
966 handle_upload_common(1, 0);
969 static void
970 handle_stou(void)
972 G.restart_pos = 0;
973 handle_upload_common(0, 1);
975 #endif /* ENABLE_FEATURE_FTP_WRITE */
977 static uint32_t
978 cmdio_get_cmd_and_arg(void)
980 int len;
981 uint32_t cmdval;
982 char *cmd;
984 alarm(G.timeout);
986 free(G.ftp_cmd);
988 /* Paranoia. Peer may send 1 gigabyte long cmd... */
989 /* Using separate len_on_stk instead of len optimizes
990 * code size (allows len to be in CPU register) */
991 size_t len_on_stk = 8 * 1024;
992 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
993 if (!cmd)
994 exit(0);
995 len = len_on_stk;
998 /* De-escape telnet: 0xff,0xff => 0xff */
999 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1000 * data transfer, and may be preceded by telnet's "Interrupt Process"
1001 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1002 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1003 * and may generate SIGURG on our side. See RFC854).
1004 * So far we don't support that (may install SIGURG handler if we'd want to),
1005 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1006 /* Then de-escape FTP: NUL => '\n' */
1007 /* Testing for \xff:
1008 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1009 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1010 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1011 * Testing for embedded LF:
1012 * LF_HERE=`echo -ne "LF\nHERE"`
1013 * echo Hello >"$LF_HERE"
1014 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1017 int dst, src;
1019 /* Strip "\r\n" if it is there */
1020 if (len != 0 && cmd[len - 1] == '\n') {
1021 len--;
1022 if (len != 0 && cmd[len - 1] == '\r')
1023 len--;
1024 cmd[len] = '\0';
1026 src = strchrnul(cmd, 0xff) - cmd;
1027 /* 99,99% there are neither NULs nor 255s and src == len */
1028 if (src < len) {
1029 dst = src;
1030 do {
1031 if ((unsigned char)(cmd[src]) == 255) {
1032 src++;
1033 /* 255,xxx - skip 255 */
1034 if ((unsigned char)(cmd[src]) != 255) {
1035 /* 255,!255 - skip both */
1036 src++;
1037 continue;
1039 /* 255,255 - retain one 255 */
1041 /* NUL => '\n' */
1042 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1043 src++;
1044 } while (src < len);
1045 cmd[dst] = '\0';
1049 if (G.verbose > 1)
1050 verbose_log(cmd);
1052 G.ftp_arg = strchr(cmd, ' ');
1053 if (G.ftp_arg != NULL)
1054 *G.ftp_arg++ = '\0';
1056 /* Uppercase and pack into uint32_t first word of the command */
1057 cmdval = 0;
1058 while (*cmd)
1059 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1061 return cmdval;
1064 #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1065 #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1066 enum {
1067 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1068 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1069 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1070 const_CWD = mk_const3('C', 'W', 'D'),
1071 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1072 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
1073 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
1074 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1075 const_LIST = mk_const4('L', 'I', 'S', 'T'),
1076 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
1077 const_MKD = mk_const3('M', 'K', 'D'),
1078 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1079 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1080 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1081 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1082 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1083 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1084 const_PWD = mk_const3('P', 'W', 'D'),
1085 /* Same as PWD. Reportedly used by windows ftp client */
1086 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
1087 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1088 const_REST = mk_const4('R', 'E', 'S', 'T'),
1089 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1090 const_RMD = mk_const3('R', 'M', 'D'),
1091 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1092 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1093 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1094 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1095 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1096 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1097 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1098 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1099 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1100 const_USER = mk_const4('U', 'S', 'E', 'R'),
1102 #if !BB_MMU
1103 OPT_l = (1 << 0),
1104 OPT_1 = (1 << 1),
1105 OPT_A = (1 << 2),
1106 #endif
1107 OPT_v = (1 << ((!BB_MMU) * 3 + 0)),
1108 OPT_S = (1 << ((!BB_MMU) * 3 + 1)),
1109 OPT_w = (1 << ((!BB_MMU) * 3 + 2)) * ENABLE_FEATURE_FTP_WRITE,
1112 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1113 #if !BB_MMU
1114 int ftpd_main(int argc, char **argv)
1115 #else
1116 int ftpd_main(int argc UNUSED_PARAM, char **argv)
1117 #endif
1119 unsigned abs_timeout;
1120 unsigned verbose_S;
1121 smallint opts;
1123 INIT_G();
1125 abs_timeout = 1 * 60 * 60;
1126 verbose_S = 0;
1127 G.timeout = 2 * 60;
1128 opt_complementary = "t+:T+:vv:SS";
1129 #if BB_MMU
1130 opts = getopt32(argv, "vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1131 #else
1132 opts = getopt32(argv, "l1AvS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1133 if (opts & (OPT_l|OPT_1)) {
1134 /* Our secret backdoor to ls */
1135 /* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
1136 if (fchdir(3) != 0)
1137 _exit(127);
1138 /* memset(&G, 0, sizeof(G)); - ls_main does it */
1139 return ls_main(argc, argv);
1141 #endif
1142 if (G.verbose < verbose_S)
1143 G.verbose = verbose_S;
1144 if (abs_timeout | G.timeout) {
1145 if (abs_timeout == 0)
1146 abs_timeout = INT_MAX;
1147 G.end_time = monotonic_sec() + abs_timeout;
1148 if (G.timeout > abs_timeout)
1149 G.timeout = abs_timeout;
1151 strcpy(G.msg_ok + 4, MSG_OK );
1152 strcpy(G.msg_err + 4, MSG_ERR);
1154 G.local_addr = get_sock_lsa(STDIN_FILENO);
1155 if (!G.local_addr) {
1156 /* This is confusing:
1157 * bb_error_msg_and_die("stdin is not a socket");
1158 * Better: */
1159 bb_show_usage();
1160 /* Help text says that ftpd must be used as inetd service,
1161 * which is by far the most usual cause of get_sock_lsa
1162 * failure */
1165 if (!(opts & OPT_v))
1166 logmode = LOGMODE_NONE;
1167 if (opts & OPT_S) {
1168 /* LOG_NDELAY is needed since we may chroot later */
1169 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1170 logmode |= LOGMODE_SYSLOG;
1172 if (logmode)
1173 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1175 //umask(077); - admin can set umask before starting us
1177 /* Signals */
1178 bb_signals(0
1179 /* We'll always take EPIPE rather than a rude signal, thanks */
1180 + (1 << SIGPIPE)
1181 /* LIST command spawns chilren. Prevent zombies */
1182 + (1 << SIGCHLD)
1183 , SIG_IGN);
1185 /* Set up options on the command socket (do we need these all? why?) */
1186 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1187 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
1188 /* Telnet protocol over command link may send "urgent" data,
1189 * we prefer it to be received in the "normal" data stream: */
1190 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1192 WRITE_OK(FTP_GREET);
1193 signal(SIGALRM, timeout_handler);
1195 #if ENABLE_FEATURE_FTP_AUTHENTICATION
1197 struct passwd *pw = NULL;
1199 while (1) {
1200 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");
1210 pw = NULL;
1211 } else if (cmdval == const_QUIT) {
1212 WRITE_OK(FTP_GOODBYE);
1213 return 0;
1214 } else {
1215 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1218 change_identity(pw);
1220 WRITE_OK(FTP_LOGINOK);
1221 #endif
1223 /* Do this after auth, else /etc/passwd is not accessible */
1224 #if !BB_MMU
1225 G.root_fd = -1;
1226 #endif
1227 argv += optind;
1228 if (argv[0]) {
1229 #if !BB_MMU
1230 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1231 close_on_exec_on(G.root_fd);
1232 #endif
1233 xchroot(argv[0]);
1236 /* RFC-959 Section 5.1
1237 * The following commands and options MUST be supported by every
1238 * server-FTP and user-FTP, except in cases where the underlying
1239 * file system or operating system does not allow or support
1240 * a particular command.
1241 * Type: ASCII Non-print, IMAGE, LOCAL 8
1242 * Mode: Stream
1243 * Structure: File, Record*
1244 * (Record structure is REQUIRED only for hosts whose file
1245 * systems support record structure).
1246 * Commands:
1247 * USER, PASS, ACCT, [bbox: ACCT not supported]
1248 * PORT, PASV,
1249 * TYPE, MODE, STRU,
1250 * RETR, STOR, APPE,
1251 * RNFR, RNTO, DELE,
1252 * CWD, CDUP, RMD, MKD, PWD,
1253 * LIST, NLST,
1254 * SYST, STAT,
1255 * HELP, NOOP, QUIT.
1257 /* ACCOUNT (ACCT)
1258 * "The argument field is a Telnet string identifying the user's account.
1259 * The command is not necessarily related to the USER command, as some
1260 * sites may require an account for login and others only for specific
1261 * access, such as storing files. In the latter case the command may
1262 * arrive at any time.
1263 * There are reply codes to differentiate these cases for the automation:
1264 * when account information is required for login, the response to
1265 * a successful PASSword command is reply code 332. On the other hand,
1266 * if account information is NOT required for login, the reply to
1267 * a successful PASSword command is 230; and if the account information
1268 * is needed for a command issued later in the dialogue, the server
1269 * should return a 332 or 532 reply depending on whether it stores
1270 * (pending receipt of the ACCounT command) or discards the command,
1271 * respectively."
1274 while (1) {
1275 uint32_t cmdval = cmdio_get_cmd_and_arg();
1277 if (cmdval == const_QUIT) {
1278 WRITE_OK(FTP_GOODBYE);
1279 return 0;
1281 else if (cmdval == const_USER)
1282 /* This would mean "ok, now give me PASS". */
1283 /*WRITE_OK(FTP_GIVEPWORD);*/
1284 /* vsftpd can be configured to not require that,
1285 * and this also saves one roundtrip:
1287 WRITE_OK(FTP_LOGINOK);
1288 else if (cmdval == const_PASS)
1289 WRITE_OK(FTP_LOGINOK);
1290 else if (cmdval == const_NOOP)
1291 WRITE_OK(FTP_NOOPOK);
1292 else if (cmdval == const_TYPE)
1293 WRITE_OK(FTP_TYPEOK);
1294 else if (cmdval == const_STRU)
1295 WRITE_OK(FTP_STRUOK);
1296 else if (cmdval == const_MODE)
1297 WRITE_OK(FTP_MODEOK);
1298 else if (cmdval == const_ALLO)
1299 WRITE_OK(FTP_ALLOOK);
1300 else if (cmdval == const_SYST)
1301 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1302 else if (cmdval == const_PWD || cmdval == const_XPWD)
1303 handle_pwd();
1304 else if (cmdval == const_CWD)
1305 handle_cwd();
1306 else if (cmdval == const_CDUP) /* cd .. */
1307 handle_cdup();
1308 /* HELP is nearly useless, but we can reuse FEAT for it */
1309 /* lftp uses FEAT */
1310 else if (cmdval == const_HELP || cmdval == const_FEAT)
1311 handle_feat(cmdval == const_HELP
1312 ? STRNUM32(FTP_HELP)
1313 : STRNUM32(FTP_STATOK)
1315 else if (cmdval == const_LIST) /* ls -l */
1316 handle_list();
1317 else if (cmdval == const_NLST) /* "name list", bare ls */
1318 handle_nlst();
1319 /* SIZE is crucial for wget's download indicator etc */
1320 /* Mozilla, lftp use MDTM (presumably for caching) */
1321 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1322 handle_size_or_mdtm(cmdval == const_SIZE);
1323 else if (cmdval == const_STAT) {
1324 if (G.ftp_arg == NULL)
1325 handle_stat();
1326 else
1327 handle_stat_file();
1329 else if (cmdval == const_PASV)
1330 handle_pasv();
1331 else if (cmdval == const_EPSV)
1332 handle_epsv();
1333 else if (cmdval == const_RETR)
1334 handle_retr();
1335 else if (cmdval == const_PORT)
1336 handle_port();
1337 else if (cmdval == const_REST)
1338 handle_rest();
1339 #if ENABLE_FEATURE_FTP_WRITE
1340 else if (opts & OPT_w) {
1341 if (cmdval == const_STOR)
1342 handle_stor();
1343 else if (cmdval == const_MKD)
1344 handle_mkd();
1345 else if (cmdval == const_RMD)
1346 handle_rmd();
1347 else if (cmdval == const_DELE)
1348 handle_dele();
1349 else if (cmdval == const_RNFR) /* "rename from" */
1350 handle_rnfr();
1351 else if (cmdval == const_RNTO) /* "rename to" */
1352 handle_rnto();
1353 else if (cmdval == const_APPE)
1354 handle_appe();
1355 else if (cmdval == const_STOU) /* "store unique" */
1356 handle_stou();
1357 else
1358 goto bad_cmd;
1360 #endif
1361 #if 0
1362 else if (cmdval == const_STOR
1363 || cmdval == const_MKD
1364 || cmdval == const_RMD
1365 || cmdval == const_DELE
1366 || cmdval == const_RNFR
1367 || cmdval == const_RNTO
1368 || cmdval == const_APPE
1369 || cmdval == const_STOU
1371 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1373 #endif
1374 else {
1375 /* Which unsupported commands were seen in the wild?
1376 * (doesn't necessarily mean "we must support them")
1377 * foo 1.2.3: XXXX - comment
1379 #if ENABLE_FEATURE_FTP_WRITE
1380 bad_cmd:
1381 #endif
1382 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");