Tomato 1.28
[tomato.git] / release / src / router / busybox / networking / ftpd.c
blobe85e4f8ea16d626835041b76d9b63480fc7944fb
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 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.
15 #include "libbb.h"
16 #include <syslog.h>
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
28 #define FTP_HELP 214
29 #define FTP_SYSTOK 215
30 #define FTP_GREET 220
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
37 #define FTP_CWDOK 250
38 #define FTP_RMDIROK 250
39 #define FTP_DELEOK 250
40 #define FTP_RENAMEOK 250
41 #define FTP_PWDOK 257
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
61 #define STR1(s) #s
62 #define STR(s) STR1(s)
64 /* Convert a constant to 3-digit string, packed into uint32_t */
65 enum {
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 \
79 | (' ' << SHIFTsp) \
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"
88 struct globals {
89 int pasv_listen_fd;
90 #if !BB_MMU
91 int root_fd;
92 #endif
93 int local_file_fd;
94 unsigned end_time;
95 unsigned timeout;
96 unsigned verbose;
97 off_t local_file_pos;
98 off_t restart_pos;
99 len_and_sockaddr *local_addr;
100 len_and_sockaddr *port_addr;
101 char *ftp_cmd;
102 char *ftp_arg;
103 #if ENABLE_FEATURE_FTP_WRITE
104 char *rnfr_filename;
105 #endif
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);*/ \
115 } while (0)
118 static char *
119 escape_text(const char *prepend, const char *str, unsigned escapee)
121 unsigned retlen, remainlen, chunklen;
122 char *ret, *found;
123 char append;
125 append = (char)escapee;
126 escapee >>= 8;
128 remainlen = strlen(str);
129 retlen = strlen(prepend);
130 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
131 strcpy(ret, prepend);
133 for (;;) {
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);
139 retlen += 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 */
145 break;
147 ret[retlen++] = escapee; /* duplicate escapee */
148 str = found + 1;
150 return ret;
153 /* Returns strlen as a bonus */
154 static unsigned
155 replace_char(char *str, char from, char to)
157 char *p = str;
158 while (*p) {
159 if (*p == from)
160 *p = to;
161 p++;
163 return p - str;
166 static void
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 */
173 static void
174 cmdio_write(uint32_t status_str, const char *str)
176 char *response;
177 int len;
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);
188 if (G.verbose > 1)
189 verbose_log(response);
190 free(response);
193 static void
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);
198 if (G.verbose > 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? */
204 static void
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);
209 if (G.verbose > 1)
210 verbose_log(G.msg_err);
212 #define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
214 static void
215 cmdio_write_raw(const char *p_text)
217 xwrite_str(STDOUT_FILENO, p_text);
218 if (G.verbose > 1)
219 verbose_log(p_text);
222 static void
223 timeout_handler(int sig UNUSED_PARAM)
225 off_t pos;
226 int sv_errno = errno;
228 if ((int)(monotonic_sec() - G.end_time) >= 0)
229 goto timed_out;
231 if (!G.local_file_fd)
232 goto timed_out;
234 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
235 if (pos == G.local_file_pos)
236 goto timed_out;
237 G.local_file_pos = pos;
239 alarm(G.timeout);
240 errno = sv_errno;
241 return;
243 timed_out:
244 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
245 /* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
246 exit(1);
249 /* Simple commands */
251 static void
252 handle_pwd(void)
254 char *cwd, *response;
256 cwd = xrealloc_getcwd_or_warn(NULL);
257 if (cwd == NULL)
258 cwd = xstrdup("");
260 /* We have to promote each " to "" */
261 response = escape_text(" \"", cwd, ('"' << 8) + '"');
262 free(cwd);
263 cmdio_write(STRNUM32(FTP_PWDOK), response);
264 free(response);
267 static void
268 handle_cwd(void)
270 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
271 WRITE_ERR(FTP_FILEFAIL);
272 return;
274 WRITE_OK(FTP_CWDOK);
277 static void
278 handle_cdup(void)
280 G.ftp_arg = (char*)"..";
281 handle_cwd();
284 static void
285 handle_stat(void)
287 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
288 " TYPE: BINARY\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.
296 FEAT
297 211-Features:
298 EPRT
299 EPSV
300 MDTM
301 PASV
302 REST STREAM
303 SIZE
304 TVFS
305 UTF8
306 211 End
307 HELP
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
312 XPWD XRMD
313 214 Help OK.
315 static void
316 handle_feat(unsigned status)
318 cmdio_write(status, "-Features:");
319 cmdio_write_raw(" EPSV\r\n"
320 " PASV\r\n"
321 " REST STREAM\r\n"
322 " MDTM\r\n"
323 " SIZE\r\n");
324 cmdio_write(status, " Ok");
327 /* Download commands */
329 static inline int
330 port_active(void)
332 return (G.port_addr != NULL);
335 static inline int
336 pasv_active(void)
338 return (G.pasv_listen_fd > STDOUT_FILENO);
341 static void
342 port_pasv_cleanup(void)
344 free(G.port_addr);
345 G.port_addr = NULL;
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 */
352 static int
353 ftpdataio_get_pasv_fd(void)
355 int remote_fd;
357 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
359 if (remote_fd < 0) {
360 WRITE_ERR(FTP_BADSENDCONN);
361 return remote_fd;
364 setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
365 return remote_fd;
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.
374 static int
375 get_remote_transfer_fd(const char *p_status_msg)
377 int remote_fd;
379 if (pasv_active())
380 /* On error, emits error code to the peer */
381 remote_fd = ftpdataio_get_pasv_fd();
382 else
383 /* Exits on error */
384 remote_fd = xconnect_stream(G.port_addr);
386 port_pasv_cleanup();
388 if (remote_fd < 0)
389 return remote_fd;
391 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
392 return remote_fd;
395 /* If there were neither PASV nor PORT, emits error code to the peer */
396 static int
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");
401 return 0;
404 return 1;
407 /* Exits on error */
408 static unsigned
409 bind_for_passive_mode(void)
411 int fd;
412 unsigned port;
414 port_pasv_cleanup();
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);
421 xlisten(fd, 1);
422 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
424 port = get_nport(&G.local_addr->u.sa);
425 port = ntohs(port);
426 return port;
429 /* Exits on error */
430 static void
431 handle_pasv(void)
433 unsigned port;
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));
446 free(addr);
447 cmdio_write_raw(response);
448 free(response);
451 /* Exits on error */
452 static void
453 handle_epsv(void)
455 unsigned port;
456 char *response;
458 port = bind_for_passive_mode();
459 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
460 cmdio_write_raw(response);
461 free(response);
464 /* libbb candidate */
465 static
466 len_and_sockaddr* get_peer_lsa(int fd)
468 len_and_sockaddr *lsa;
469 socklen_t len = 0;
471 if (getpeername(fd, NULL, &len) != 0)
472 return NULL;
473 lsa = xzalloc(LSA_LEN_SIZE + len);
474 lsa->len = len;
475 getpeername(fd, &lsa->u.sa, &lsa->len);
476 return lsa;
479 static void
480 handle_port(void)
482 unsigned port, port_hi;
483 char *raw, *comma;
484 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
485 socklen_t peer_ipv4_len;
486 struct sockaddr_in peer_ipv4;
487 struct in_addr port_ipv4_sin_addr;
488 #endif
490 port_pasv_cleanup();
492 raw = G.ftp_arg;
494 /* PORT command format makes sense only over IPv4 */
495 if (!raw
496 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
497 || G.local_addr->u.sa.sa_family != AF_INET
498 #endif
500 bail:
501 WRITE_ERR(FTP_BADCMD);
502 return;
505 comma = strrchr(raw, ',');
506 if (comma == NULL)
507 goto bail;
508 *comma = '\0';
509 port = bb_strtou(&comma[1], NULL, 10);
510 if (errno || port > 0xff)
511 goto bail;
513 comma = strrchr(raw, ',');
514 if (comma == NULL)
515 goto bail;
516 *comma = '\0';
517 port_hi = bb_strtou(&comma[1], NULL, 10);
518 if (errno || port_hi > 0xff)
519 goto bail;
520 port |= port_hi << 8;
522 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
523 replace_char(raw, ',', '.');
525 /* We are verifying that PORT's IP matches getpeername().
526 * Otherwise peer can make us open data connections
527 * to other hosts (security problem!)
528 * This code would be too simplistic:
529 * lsa = xdotted2sockaddr(raw, port);
530 * if (lsa == NULL) goto bail;
532 if (!inet_aton(raw, &port_ipv4_sin_addr))
533 goto bail;
534 peer_ipv4_len = sizeof(peer_ipv4);
535 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
536 goto bail;
537 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
538 goto bail;
540 G.port_addr = xdotted2sockaddr(raw, port);
541 #else
542 G.port_addr = get_peer_lsa(STDIN_FILENO);
543 set_nport(G.port_addr, htons(port));
544 #endif
545 WRITE_OK(FTP_PORTOK);
548 static void
549 handle_rest(void)
551 /* When ftp_arg == NULL simply restart from beginning */
552 G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0;
553 WRITE_OK(FTP_RESTOK);
556 static void
557 handle_retr(void)
559 struct stat statbuf;
560 off_t bytes_transferred;
561 int remote_fd;
562 int local_file_fd;
563 off_t offset = G.restart_pos;
564 char *response;
566 G.restart_pos = 0;
568 if (!port_or_pasv_was_seen())
569 return; /* port_or_pasv_was_seen emitted error response */
571 /* O_NONBLOCK is useful if file happens to be a device node */
572 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
573 if (local_file_fd < 0) {
574 WRITE_ERR(FTP_FILEFAIL);
575 return;
578 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
579 /* Note - pretend open failed */
580 WRITE_ERR(FTP_FILEFAIL);
581 goto file_close_out;
583 G.local_file_fd = local_file_fd;
585 /* Now deactive O_NONBLOCK, otherwise we have a problem
586 * on DMAPI filesystems such as XFS DMAPI.
588 ndelay_off(local_file_fd);
590 /* Set the download offset (from REST) if any */
591 if (offset != 0)
592 xlseek(local_file_fd, offset, SEEK_SET);
594 response = xasprintf(
595 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
596 G.ftp_arg, statbuf.st_size);
597 remote_fd = get_remote_transfer_fd(response);
598 free(response);
599 if (remote_fd < 0)
600 goto file_close_out;
602 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
603 close(remote_fd);
604 if (bytes_transferred < 0)
605 WRITE_ERR(FTP_BADSENDFILE);
606 else
607 WRITE_OK(FTP_TRANSFEROK);
609 file_close_out:
610 close(local_file_fd);
611 G.local_file_fd = 0;
614 /* List commands */
616 static int
617 popen_ls(const char *opt)
619 char *cwd;
620 const char *argv[] = {
621 "ftpd",
622 opt,
623 BB_MMU ? "--" : NULL,
624 G.ftp_arg,
625 NULL
627 struct fd_pair outfd;
628 pid_t pid;
630 cwd = xrealloc_getcwd_or_warn(NULL);
631 xpiped_pair(outfd);
633 /*fflush(NULL); - so far we dont use stdio on output */
634 pid = BB_MMU ? fork() : vfork();
635 if (pid < 0)
636 bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork");
638 if (pid == 0) {
639 /* child */
640 #if !BB_MMU
641 if (fchdir(G.root_fd) != 0)
642 _exit(127);
643 close(G.root_fd);
644 #endif
645 /* NB: close _first_, then move fd! */
646 close(outfd.rd);
647 xmove_fd(outfd.wr, STDOUT_FILENO);
648 /* Opening /dev/null in chroot is hard.
649 * Just making sure STDIN_FILENO is opened
650 * to something harmless. Paranoia,
651 * ls won't read it anyway */
652 close(STDIN_FILENO);
653 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
654 #if !BB_MMU
655 /* ftpd ls helper chdirs to argv[2],
656 * preventing peer from seeing real root we are in now
658 argv[2] = cwd;
659 /* + 1: we must use relative path here if in chroot.
660 * For example, execv("/proc/self/exe") will fail, since
661 * it looks for "/proc/self/exe" _relative to chroot!_ */
662 execv(bb_busybox_exec_path + 1, (char**) argv);
663 _exit(127);
664 #else
665 memset(&G, 0, sizeof(G));
666 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
667 #endif
670 /* parent */
671 close(outfd.wr);
672 free(cwd);
673 return outfd.rd;
676 enum {
677 USE_CTRL_CONN = 1,
678 LONG_LISTING = 2,
681 static void
682 handle_dir_common(int opts)
684 FILE *ls_fp;
685 char *line;
686 int ls_fd;
688 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
689 return; /* port_or_pasv_was_seen emitted error response */
691 /* -n prevents user/groupname display,
692 * which can be problematic in chroot */
693 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
694 ls_fp = fdopen(ls_fd, "r");
695 if (!ls_fp) /* never happens. paranoia */
696 bb_perror_msg_and_die("fdopen");
698 if (opts & USE_CTRL_CONN) {
699 /* STAT <filename> */
700 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
701 while (1) {
702 line = xmalloc_fgetline(ls_fp);
703 if (!line)
704 break;
705 /* Hack: 0 results in no status at all */
706 /* Note: it's ok that we don't prepend space,
707 * ftp.kernel.org doesn't do that too */
708 cmdio_write(0, line);
709 free(line);
711 WRITE_OK(FTP_STATFILE_OK);
712 } else {
713 /* LIST/NLST [<filename>] */
714 int remote_fd = get_remote_transfer_fd(" Directory listing");
715 if (remote_fd >= 0) {
716 while (1) {
717 line = xmalloc_fgetline(ls_fp);
718 if (!line)
719 break;
720 /* I've seen clients complaining when they
721 * are fed with ls output with bare '\n'.
722 * Pity... that would be much simpler.
724 /* TODO: need to s/LF/NUL/g here */
725 xwrite_str(remote_fd, line);
726 xwrite(remote_fd, "\r\n", 2);
727 free(line);
730 close(remote_fd);
731 WRITE_OK(FTP_TRANSFEROK);
733 fclose(ls_fp); /* closes ls_fd too */
735 static void
736 handle_list(void)
738 handle_dir_common(LONG_LISTING);
740 static void
741 handle_nlst(void)
743 /* NLST returns list of names, "\r\n" terminated without regard
744 * to the current binary flag. Names may start with "/",
745 * then they represent full names (we don't produce such names),
746 * otherwise names are relative to current directory.
747 * Embedded "\n" are replaced by NULs. This is safe since names
748 * can never contain NUL.
750 handle_dir_common(0);
752 static void
753 handle_stat_file(void)
755 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
758 /* This can be extended to handle MLST, as all info is available
759 * in struct stat for that:
760 * MLST file_name
761 * 250-Listing file_name
762 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
763 * 250 End
764 * Nano-doc:
765 * MLST [<file or dir name, "." assumed if not given>]
766 * Returned name should be either the same as requested, or fully qualified.
767 * If there was no parameter, return "" or (preferred) fully-qualified name.
768 * Returned "facts" (case is not important):
769 * size - size in octets
770 * modify - last modification time
771 * type - entry type (file,dir,OS.unix=block)
772 * (+ cdir and pdir types for MLSD)
773 * unique - unique id of file/directory (inode#)
774 * perm -
775 * a: can be appended to (APPE)
776 * d: can be deleted (RMD/DELE)
777 * f: can be renamed (RNFR)
778 * r: can be read (RETR)
779 * w: can be written (STOR)
780 * e: can CWD into this dir
781 * l: this dir can be listed (dir only!)
782 * c: can create files in this dir
783 * m: can create dirs in this dir (MKD)
784 * p: can delete files in this dir
785 * UNIX.mode - unix file mode
787 static void
788 handle_size_or_mdtm(int need_size)
790 struct stat statbuf;
791 struct tm broken_out;
792 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
793 | sizeof("NNN YYYYMMDDhhmmss\r\n")
796 if (!G.ftp_arg
797 || stat(G.ftp_arg, &statbuf) != 0
798 || !S_ISREG(statbuf.st_mode)
800 WRITE_ERR(FTP_FILEFAIL);
801 return;
803 if (need_size) {
804 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
805 } else {
806 gmtime_r(&statbuf.st_mtime, &broken_out);
807 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
808 broken_out.tm_year + 1900,
809 broken_out.tm_mon,
810 broken_out.tm_mday,
811 broken_out.tm_hour,
812 broken_out.tm_min,
813 broken_out.tm_sec);
815 cmdio_write_raw(buf);
818 /* Upload commands */
820 #if ENABLE_FEATURE_FTP_WRITE
821 static void
822 handle_mkd(void)
824 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
825 WRITE_ERR(FTP_FILEFAIL);
826 return;
828 WRITE_OK(FTP_MKDIROK);
831 static void
832 handle_rmd(void)
834 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
835 WRITE_ERR(FTP_FILEFAIL);
836 return;
838 WRITE_OK(FTP_RMDIROK);
841 static void
842 handle_dele(void)
844 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
845 WRITE_ERR(FTP_FILEFAIL);
846 return;
848 WRITE_OK(FTP_DELEOK);
851 static void
852 handle_rnfr(void)
854 free(G.rnfr_filename);
855 G.rnfr_filename = xstrdup(G.ftp_arg);
856 WRITE_OK(FTP_RNFROK);
859 static void
860 handle_rnto(void)
862 int retval;
864 /* If we didn't get a RNFR, throw a wobbly */
865 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
866 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
867 return;
870 retval = rename(G.rnfr_filename, G.ftp_arg);
871 free(G.rnfr_filename);
872 G.rnfr_filename = NULL;
874 if (retval) {
875 WRITE_ERR(FTP_FILEFAIL);
876 return;
878 WRITE_OK(FTP_RENAMEOK);
881 static void
882 handle_upload_common(int is_append, int is_unique)
884 struct stat statbuf;
885 char *tempname;
886 off_t bytes_transferred;
887 off_t offset;
888 int local_file_fd;
889 int remote_fd;
891 offset = G.restart_pos;
892 G.restart_pos = 0;
894 if (!port_or_pasv_was_seen())
895 return; /* port_or_pasv_was_seen emitted error response */
897 tempname = NULL;
898 local_file_fd = -1;
899 if (is_unique) {
900 tempname = xstrdup(" FILE: uniq.XXXXXX");
901 local_file_fd = mkstemp(tempname + 7);
902 } else if (G.ftp_arg) {
903 int flags = O_WRONLY | O_CREAT | O_TRUNC;
904 if (is_append)
905 flags = O_WRONLY | O_CREAT | O_APPEND;
906 if (offset)
907 flags = O_WRONLY | O_CREAT;
908 local_file_fd = open(G.ftp_arg, flags, 0666);
911 if (local_file_fd < 0
912 || fstat(local_file_fd, &statbuf) != 0
913 || !S_ISREG(statbuf.st_mode)
915 WRITE_ERR(FTP_UPLOADFAIL);
916 if (local_file_fd >= 0)
917 goto close_local_and_bail;
918 return;
920 G.local_file_fd = local_file_fd;
922 if (offset)
923 xlseek(local_file_fd, offset, SEEK_SET);
925 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
926 free(tempname);
928 if (remote_fd < 0)
929 goto close_local_and_bail;
931 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
932 close(remote_fd);
933 if (bytes_transferred < 0)
934 WRITE_ERR(FTP_BADSENDFILE);
935 else
936 WRITE_OK(FTP_TRANSFEROK);
938 close_local_and_bail:
939 close(local_file_fd);
940 G.local_file_fd = 0;
943 static void
944 handle_stor(void)
946 handle_upload_common(0, 0);
949 static void
950 handle_appe(void)
952 G.restart_pos = 0;
953 handle_upload_common(1, 0);
956 static void
957 handle_stou(void)
959 G.restart_pos = 0;
960 handle_upload_common(0, 1);
962 #endif /* ENABLE_FEATURE_FTP_WRITE */
964 static uint32_t
965 cmdio_get_cmd_and_arg(void)
967 size_t len;
968 uint32_t cmdval;
969 char *cmd;
971 alarm(G.timeout);
973 free(G.ftp_cmd);
974 len = 8 * 1024; /* Paranoia. Peer may send 1 gigabyte long cmd... */
975 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len);
976 if (!cmd)
977 exit(0);
979 /* De-escape telnet: 0xff,0xff => 0xff */
980 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
981 * data transfer, and may be preceded by telnet's "Interrupt Process"
982 * code (two-byte sequence 255,244) and then by telnet "Synch" code
983 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
984 * and may generate SIGURG on our side. See RFC854).
985 * So far we don't support that (may install SIGURG handler if we'd want to),
986 * but we need to at least remove 255,xxx pairs. lftp sends those. */
987 /* Then de-escape FTP: NUL => '\n' */
988 /* Testing for \xff:
989 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
990 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
991 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
992 * Testing for embedded LF:
993 * LF_HERE=`echo -ne "LF\nHERE"`
994 * echo Hello >"$LF_HERE"
995 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
998 int dst, src;
1000 /* Strip "\r\n" if it is there */
1001 if (len != 0 && cmd[len - 1] == '\n') {
1002 len--;
1003 if (len != 0 && cmd[len - 1] == '\r')
1004 len--;
1005 cmd[len] = '\0';
1007 src = strchrnul(cmd, 0xff) - cmd;
1008 /* 99,99% there are neither NULs nor 255s and src == len */
1009 if (src < len) {
1010 dst = src;
1011 do {
1012 if ((unsigned char)(cmd[src]) == 255) {
1013 src++;
1014 /* 255,xxx - skip 255 */
1015 if ((unsigned char)(cmd[src]) != 255) {
1016 /* 255,!255 - skip both */
1017 src++;
1018 continue;
1020 /* 255,255 - retain one 255 */
1022 /* NUL => '\n' */
1023 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1024 src++;
1025 } while (src < len);
1026 cmd[dst] = '\0';
1030 if (G.verbose > 1)
1031 verbose_log(cmd);
1033 G.ftp_arg = strchr(cmd, ' ');
1034 if (G.ftp_arg != NULL)
1035 *G.ftp_arg++ = '\0';
1037 /* Uppercase and pack into uint32_t first word of the command */
1038 cmdval = 0;
1039 while (*cmd)
1040 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1042 return cmdval;
1045 #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1046 #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1047 enum {
1048 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1049 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1050 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1051 const_CWD = mk_const3('C', 'W', 'D'),
1052 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1053 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
1054 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
1055 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1056 const_LIST = mk_const4('L', 'I', 'S', 'T'),
1057 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
1058 const_MKD = mk_const3('M', 'K', 'D'),
1059 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1060 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1061 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1062 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1063 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1064 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1065 const_PWD = mk_const3('P', 'W', 'D'),
1066 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1067 const_REST = mk_const4('R', 'E', 'S', 'T'),
1068 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1069 const_RMD = mk_const3('R', 'M', 'D'),
1070 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1071 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1072 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1073 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1074 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1075 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1076 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1077 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1078 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1079 const_USER = mk_const4('U', 'S', 'E', 'R'),
1081 #if !BB_MMU
1082 OPT_l = (1 << 0),
1083 OPT_1 = (1 << 1),
1084 #endif
1085 OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
1086 OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
1087 OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
1090 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1091 #if !BB_MMU
1092 int ftpd_main(int argc, char **argv)
1093 #else
1094 int ftpd_main(int argc UNUSED_PARAM, char **argv)
1095 #endif
1097 unsigned abs_timeout;
1098 smallint opts;
1100 INIT_G();
1102 abs_timeout = 1 * 60 * 60;
1103 G.timeout = 2 * 60;
1104 opt_complementary = "t+:T+:vv";
1105 #if BB_MMU
1106 opts = getopt32(argv, "vS" USE_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose);
1107 #else
1108 opts = getopt32(argv, "l1vS" USE_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose);
1109 if (opts & (OPT_l|OPT_1)) {
1110 /* Our secret backdoor to ls */
1111 /* TODO: pass -n too? */
1112 /* --group-directories-first would be nice, but ls don't do that yet */
1113 xchdir(argv[2]);
1114 argv[2] = (char*)"--";
1115 memset(&G, 0, sizeof(G));
1116 return ls_main(argc, argv);
1118 #endif
1119 if (abs_timeout | G.timeout) {
1120 if (abs_timeout == 0)
1121 abs_timeout = INT_MAX;
1122 G.end_time = monotonic_sec() + abs_timeout;
1123 if (G.timeout > abs_timeout)
1124 G.timeout = abs_timeout;
1126 strcpy(G.msg_ok + 4, MSG_OK );
1127 strcpy(G.msg_err + 4, MSG_ERR);
1129 G.local_addr = get_sock_lsa(STDIN_FILENO);
1130 if (!G.local_addr) {
1131 /* This is confusing:
1132 * bb_error_msg_and_die("stdin is not a socket");
1133 * Better: */
1134 bb_show_usage();
1135 /* Help text says that ftpd must be used as inetd service,
1136 * which is by far the most usual cause of get_sock_lsa
1137 * failure */
1140 if (!(opts & OPT_v))
1141 logmode = LOGMODE_NONE;
1142 if (opts & OPT_S) {
1143 /* LOG_NDELAY is needed since we may chroot later */
1144 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1145 logmode |= LOGMODE_SYSLOG;
1147 if (logmode)
1148 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1150 #if !BB_MMU
1151 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1152 #endif
1154 if (argv[optind]) {
1155 xchdir(argv[optind]);
1156 chroot(".");
1159 //umask(077); - admin can set umask before starting us
1161 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
1162 signal(SIGPIPE, SIG_IGN);
1164 /* Set up options on the command socket (do we need these all? why?) */
1165 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1166 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
1167 /* Telnet protocol over command link may send "urgent" data,
1168 * we prefer it to be received in the "normal" data stream: */
1169 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1171 WRITE_OK(FTP_GREET);
1172 signal(SIGALRM, timeout_handler);
1174 #ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
1176 smallint user_was_specified = 0;
1177 while (1) {
1178 uint32_t cmdval = cmdio_get_cmd_and_arg();
1180 if (cmdval == const_USER) {
1181 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
1182 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
1183 else {
1184 user_was_specified = 1;
1185 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
1187 } else if (cmdval == const_PASS) {
1188 if (user_was_specified)
1189 break;
1190 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
1191 } else if (cmdval == const_QUIT) {
1192 WRITE_OK(FTP_GOODBYE);
1193 return 0;
1194 } else {
1195 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1199 WRITE_OK(FTP_LOGINOK);
1200 #endif
1202 /* RFC-959 Section 5.1
1203 * The following commands and options MUST be supported by every
1204 * server-FTP and user-FTP, except in cases where the underlying
1205 * file system or operating system does not allow or support
1206 * a particular command.
1207 * Type: ASCII Non-print, IMAGE, LOCAL 8
1208 * Mode: Stream
1209 * Structure: File, Record*
1210 * (Record structure is REQUIRED only for hosts whose file
1211 * systems support record structure).
1212 * Commands:
1213 * USER, PASS, ACCT, [bbox: ACCT not supported]
1214 * PORT, PASV,
1215 * TYPE, MODE, STRU,
1216 * RETR, STOR, APPE,
1217 * RNFR, RNTO, DELE,
1218 * CWD, CDUP, RMD, MKD, PWD,
1219 * LIST, NLST,
1220 * SYST, STAT,
1221 * HELP, NOOP, QUIT.
1223 /* ACCOUNT (ACCT)
1224 * "The argument field is a Telnet string identifying the user's account.
1225 * The command is not necessarily related to the USER command, as some
1226 * sites may require an account for login and others only for specific
1227 * access, such as storing files. In the latter case the command may
1228 * arrive at any time.
1229 * There are reply codes to differentiate these cases for the automation:
1230 * when account information is required for login, the response to
1231 * a successful PASSword command is reply code 332. On the other hand,
1232 * if account information is NOT required for login, the reply to
1233 * a successful PASSword command is 230; and if the account information
1234 * is needed for a command issued later in the dialogue, the server
1235 * should return a 332 or 532 reply depending on whether it stores
1236 * (pending receipt of the ACCounT command) or discards the command,
1237 * respectively."
1240 while (1) {
1241 uint32_t cmdval = cmdio_get_cmd_and_arg();
1243 if (cmdval == const_QUIT) {
1244 WRITE_OK(FTP_GOODBYE);
1245 return 0;
1247 else if (cmdval == const_USER)
1248 /* This would mean "ok, now give me PASS". */
1249 /*WRITE_OK(FTP_GIVEPWORD);*/
1250 /* vsftpd can be configured to not require that,
1251 * and this also saves one roundtrip:
1253 WRITE_OK(FTP_LOGINOK);
1254 else if (cmdval == const_PASS)
1255 WRITE_OK(FTP_LOGINOK);
1256 else if (cmdval == const_NOOP)
1257 WRITE_OK(FTP_NOOPOK);
1258 else if (cmdval == const_TYPE)
1259 WRITE_OK(FTP_TYPEOK);
1260 else if (cmdval == const_STRU)
1261 WRITE_OK(FTP_STRUOK);
1262 else if (cmdval == const_MODE)
1263 WRITE_OK(FTP_MODEOK);
1264 else if (cmdval == const_ALLO)
1265 WRITE_OK(FTP_ALLOOK);
1266 else if (cmdval == const_SYST)
1267 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1268 else if (cmdval == const_PWD)
1269 handle_pwd();
1270 else if (cmdval == const_CWD)
1271 handle_cwd();
1272 else if (cmdval == const_CDUP) /* cd .. */
1273 handle_cdup();
1274 /* HELP is nearly useless, but we can reuse FEAT for it */
1275 /* lftp uses FEAT */
1276 else if (cmdval == const_HELP || cmdval == const_FEAT)
1277 handle_feat(cmdval == const_HELP
1278 ? STRNUM32(FTP_HELP)
1279 : STRNUM32(FTP_STATOK)
1281 else if (cmdval == const_LIST) /* ls -l */
1282 handle_list();
1283 else if (cmdval == const_NLST) /* "name list", bare ls */
1284 handle_nlst();
1285 /* SIZE is crucial for wget's download indicator etc */
1286 /* Mozilla, lftp use MDTM (presumably for caching) */
1287 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1288 handle_size_or_mdtm(cmdval == const_SIZE);
1289 else if (cmdval == const_STAT) {
1290 if (G.ftp_arg == NULL)
1291 handle_stat();
1292 else
1293 handle_stat_file();
1295 else if (cmdval == const_PASV)
1296 handle_pasv();
1297 else if (cmdval == const_EPSV)
1298 handle_epsv();
1299 else if (cmdval == const_RETR)
1300 handle_retr();
1301 else if (cmdval == const_PORT)
1302 handle_port();
1303 else if (cmdval == const_REST)
1304 handle_rest();
1305 #if ENABLE_FEATURE_FTP_WRITE
1306 else if (opts & OPT_w) {
1307 if (cmdval == const_STOR)
1308 handle_stor();
1309 else if (cmdval == const_MKD)
1310 handle_mkd();
1311 else if (cmdval == const_RMD)
1312 handle_rmd();
1313 else if (cmdval == const_DELE)
1314 handle_dele();
1315 else if (cmdval == const_RNFR) /* "rename from" */
1316 handle_rnfr();
1317 else if (cmdval == const_RNTO) /* "rename to" */
1318 handle_rnto();
1319 else if (cmdval == const_APPE)
1320 handle_appe();
1321 else if (cmdval == const_STOU) /* "store unique" */
1322 handle_stou();
1323 else
1324 goto bad_cmd;
1326 #endif
1327 #if 0
1328 else if (cmdval == const_STOR
1329 || cmdval == const_MKD
1330 || cmdval == const_RMD
1331 || cmdval == const_DELE
1332 || cmdval == const_RNFR
1333 || cmdval == const_RNTO
1334 || cmdval == const_APPE
1335 || cmdval == const_STOU
1337 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1339 #endif
1340 else {
1341 /* Which unsupported commands were seen in the wild?
1342 * (doesn't necessarily mean "we must support them")
1343 * foo 1.2.3: XXXX - comment
1345 #if ENABLE_FEATURE_FTP_WRITE
1346 bad_cmd:
1347 #endif
1348 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");