cut: code shrink
[busybox-git.git] / networking / ftpd.c
blob0d6a289c7cf5095a4a86b53187d41223b0c0e761
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.
14 //config:config FTPD
15 //config: bool "ftpd (30 kb)"
16 //config: default y
17 //config: help
18 //config: Simple FTP daemon. You have to run it via inetd.
19 //config:
20 //config:config FEATURE_FTPD_WRITE
21 //config: bool "Enable -w (upload commands)"
22 //config: default y
23 //config: depends on FTPD
24 //config: help
25 //config: Enable -w option. "ftpd -w" will accept upload commands
26 //config: such as STOR, STOU, APPE, DELE, MKD, RMD, rename commands.
27 //config:
28 //config:config FEATURE_FTPD_ACCEPT_BROKEN_LIST
29 //config: bool "Enable workaround for RFC-violating clients"
30 //config: default y
31 //config: depends on FTPD
32 //config: help
33 //config: Some ftp clients (among them KDE's Konqueror) issue illegal
34 //config: "LIST -l" requests. This option works around such problems.
35 //config: It might prevent you from listing files starting with "-" and
36 //config: it increases the code size by ~40 bytes.
37 //config: Most other ftp servers seem to behave similar to this.
38 //config:
39 //config:config FEATURE_FTPD_AUTHENTICATION
40 //config: bool "Enable authentication"
41 //config: default y
42 //config: depends on FTPD
43 //config: help
44 //config: Require login, and change to logged in user's UID:GID before
45 //config: accessing any files. Option "-a USER" allows "anonymous"
46 //config: logins (treats them as if USER logged in).
47 //config:
48 //config: If this option is not selected, ftpd runs with the rights
49 //config: of the user it was started under, and does not require login.
50 //config: Take care to not launch it under root.
52 //applet:IF_FTPD(APPLET(ftpd, BB_DIR_USR_SBIN, BB_SUID_DROP))
54 //kbuild:lib-$(CONFIG_FTPD) += ftpd.o
56 //usage:#define ftpd_trivial_usage
57 //usage: "[-wvS]"IF_FEATURE_FTPD_AUTHENTICATION(" [-a USER]")" [-t SEC] [-T SEC] [DIR]"
58 //usage:#define ftpd_full_usage "\n\n"
59 //usage: IF_NOT_FEATURE_FTPD_AUTHENTICATION(
60 //usage: "Anonymous FTP server. Client access occurs under ftpd's UID.\n"
61 //usage: )
62 //usage: IF_FEATURE_FTPD_AUTHENTICATION(
63 //usage: "FTP server. "
64 //usage: )
65 //usage: "Chroots to DIR, if this fails (run by non-root), cds to it.\n"
66 //usage: "It is an inetd service, inetd.conf line:\n"
67 //usage: " 21 stream tcp nowait root ftpd ftpd /files/to/serve\n"
68 //usage: "Can be run from tcpsvd:\n"
69 //usage: " tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve"
70 //usage: "\n"
71 //usage: "\n -w Allow upload"
72 //usage: IF_FEATURE_FTPD_AUTHENTICATION(
73 //usage: "\n -A No login required, client access occurs under ftpd's UID"
75 // if !FTPD_AUTHENTICATION, -A is accepted too, but not shown in --help
76 // since it's the only supported mode in that configuration
78 //usage: "\n -a USER Enable 'anonymous' login and map it to USER"
79 //usage: )
80 //usage: "\n -v Log errors to stderr. -vv: verbose log"
81 //usage: "\n -S Log errors to syslog. -SS: verbose log"
82 //usage: "\n -t,-T N Idle and absolute timeout"
84 #include "libbb.h"
85 #include "common_bufsiz.h"
86 #include <syslog.h>
87 #include <netinet/tcp.h>
89 #define FTP_DATACONN 150
90 #define FTP_NOOPOK 200
91 #define FTP_TYPEOK 200
92 #define FTP_PORTOK 200
93 #define FTP_STRUOK 200
94 #define FTP_MODEOK 200
95 #define FTP_ALLOOK 202
96 #define FTP_STATOK 211
97 #define FTP_STATFILE_OK 213
98 #define FTP_HELP 214
99 #define FTP_SYSTOK 215
100 #define FTP_GREET 220
101 #define FTP_GOODBYE 221
102 #define FTP_TRANSFEROK 226
103 #define FTP_PASVOK 227
104 /*#define FTP_EPRTOK 228*/
105 #define FTP_EPSVOK 229
106 #define FTP_LOGINOK 230
107 #define FTP_CWDOK 250
108 #define FTP_RMDIROK 250
109 #define FTP_DELEOK 250
110 #define FTP_RENAMEOK 250
111 #define FTP_PWDOK 257
112 #define FTP_MKDIROK 257
113 #define FTP_GIVEPWORD 331
114 #define FTP_RESTOK 350
115 #define FTP_RNFROK 350
116 #define FTP_TIMEOUT 421
117 #define FTP_BADSENDCONN 425
118 #define FTP_BADSENDNET 426
119 #define FTP_BADSENDFILE 451
120 #define FTP_BADCMD 500
121 #define FTP_COMMANDNOTIMPL 502
122 #define FTP_NEEDUSER 503
123 #define FTP_NEEDRNFR 503
124 #define FTP_BADSTRU 504
125 #define FTP_BADMODE 504
126 #define FTP_LOGINERR 530
127 #define FTP_FILEFAIL 550
128 #define FTP_NOPERM 550
129 #define FTP_UPLOADFAIL 553
131 #define STR1(s) #s
132 #define STR(s) STR1(s)
134 /* Convert a constant to 3-digit string, packed into uint32_t */
135 enum {
136 /* Shift for Nth decimal digit */
137 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
138 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
139 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
140 /* And for 4th position (space) */
141 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
143 #define STRNUM32(s) (uint32_t)(0 \
144 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
145 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
146 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
148 #define STRNUM32sp(s) (uint32_t)(0 \
149 | (' ' << SHIFTsp) \
150 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
151 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
152 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
155 #define MSG_OK "Operation successful\r\n"
156 #define MSG_ERR "Error\r\n"
158 struct globals {
159 int pasv_listen_fd;
160 #if !BB_MMU
161 int root_fd;
162 #endif
163 int local_file_fd;
164 unsigned end_time;
165 unsigned timeout;
166 unsigned verbose;
167 off_t local_file_pos;
168 off_t restart_pos;
169 len_and_sockaddr *local_addr;
170 len_and_sockaddr *port_addr;
171 char *ftp_cmd;
172 char *ftp_arg;
173 #if ENABLE_FEATURE_FTPD_WRITE
174 char *rnfr_filename;
175 #endif
176 /* We need these aligned to uint32_t */
177 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
178 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
179 } FIX_ALIASING;
180 #define G (*ptr_to_globals)
181 /* ^^^ about 75 bytes smaller code than this: */
182 //#define G (*(struct globals*)bb_common_bufsiz1)
183 #define INIT_G() do { \
184 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
185 /*setup_common_bufsiz();*/ \
187 /* Moved to main */ \
188 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
189 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
190 } while (0)
193 static char *
194 escape_text(const char *prepend, const char *str, unsigned escapee)
196 unsigned retlen, remainlen, chunklen;
197 char *ret, *found;
198 char append;
200 append = (char)escapee;
201 escapee >>= 8;
203 remainlen = strlen(str);
204 retlen = strlen(prepend);
205 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
206 strcpy(ret, prepend);
208 for (;;) {
209 found = strchrnul(str, escapee);
210 chunklen = found - str + 1;
212 /* Copy chunk up to and including escapee (or NUL) to ret */
213 memcpy(ret + retlen, str, chunklen);
214 retlen += chunklen;
216 if (*found == '\0') {
217 /* It wasn't escapee, it was NUL! */
218 ret[retlen - 1] = append; /* replace NUL */
219 ret[retlen] = '\0'; /* add NUL */
220 break;
222 ret[retlen++] = escapee; /* duplicate escapee */
223 str = found + 1;
225 return ret;
228 /* Returns strlen as a bonus */
229 static unsigned
230 replace_char(char *str, char from, char to)
232 char *p = str;
233 while (*p) {
234 if (*p == from)
235 *p = to;
236 p++;
238 return p - str;
241 static void
242 verbose_log(const char *str)
244 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
247 /* NB: status_str is char[4] packed into uint32_t */
248 static void
249 cmdio_write(uint32_t status_str, const char *str)
251 char *response;
252 int len;
254 /* FTP uses telnet protocol for command link.
255 * In telnet, 0xff is an escape char, and needs to be escaped: */
256 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
258 /* FTP sends embedded LFs as NULs */
259 len = replace_char(response, '\n', '\0');
261 response[len++] = '\n'; /* tack on trailing '\n' */
262 xwrite(STDOUT_FILENO, response, len);
263 if (G.verbose > 1)
264 verbose_log(response);
265 free(response);
268 static void
269 cmdio_write_ok(unsigned status)
271 *(bb__aliased_uint32_t *) G.msg_ok = status;
272 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
273 if (G.verbose > 1)
274 verbose_log(G.msg_ok);
276 #define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
278 /* TODO: output strerr(errno) if errno != 0? */
279 static void
280 cmdio_write_error(unsigned status)
282 *(bb__aliased_uint32_t *) G.msg_err = status;
283 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
284 if (G.verbose > 0)
285 verbose_log(G.msg_err);
287 #define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
289 static void
290 cmdio_write_raw(const char *p_text)
292 xwrite_str(STDOUT_FILENO, p_text);
293 if (G.verbose > 1)
294 verbose_log(p_text);
297 static void
298 timeout_handler(int sig UNUSED_PARAM)
300 off_t pos;
301 int sv_errno = errno;
303 if ((int)(monotonic_sec() - G.end_time) >= 0)
304 goto timed_out;
306 if (!G.local_file_fd)
307 goto timed_out;
309 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
310 if (pos == G.local_file_pos)
311 goto timed_out;
312 G.local_file_pos = pos;
314 alarm(G.timeout);
315 errno = sv_errno;
316 return;
318 timed_out:
319 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
320 /* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
321 exit(1);
324 /* Simple commands */
326 static void
327 handle_pwd(void)
329 char *cwd, *response;
331 cwd = xrealloc_getcwd_or_warn(NULL);
332 if (cwd == NULL)
333 cwd = xstrdup("");
335 /* We have to promote each " to "" */
336 response = escape_text(" \"", cwd, ('"' << 8) + '"');
337 free(cwd);
338 cmdio_write(STRNUM32(FTP_PWDOK), response);
339 free(response);
342 static void
343 handle_cwd(void)
345 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
346 WRITE_ERR(FTP_FILEFAIL);
347 return;
349 WRITE_OK(FTP_CWDOK);
352 static void
353 handle_cdup(void)
355 G.ftp_arg = (char*)"..";
356 handle_cwd();
359 static void
360 handle_stat(void)
362 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
363 " TYPE: BINARY\r\n"
364 STR(FTP_STATOK)" Ok\r\n");
367 /* Examples of HELP and FEAT:
368 # nc -vvv ftp.kernel.org 21
369 ftp.kernel.org (130.239.17.4:21) open
370 220 Welcome to ftp.kernel.org.
371 FEAT
372 211-Features:
373 EPRT
374 EPSV
375 MDTM
376 PASV
377 REST STREAM
378 SIZE
379 TVFS
380 UTF8
381 211 End
382 HELP
383 214-The following commands are recognized.
384 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
385 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
386 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
387 XPWD XRMD
388 214 Help OK.
390 static void
391 handle_feat(unsigned status)
393 cmdio_write(status, "-Features:");
394 cmdio_write_raw(" EPSV\r\n"
395 " PASV\r\n"
396 " REST STREAM\r\n"
397 " MDTM\r\n"
398 " SIZE\r\n");
399 cmdio_write(status, " Ok");
402 /* Download commands */
404 static inline int
405 port_active(void)
407 return (G.port_addr != NULL);
410 static inline int
411 pasv_active(void)
413 return (G.pasv_listen_fd > STDOUT_FILENO);
416 static void
417 port_pasv_cleanup(void)
419 free(G.port_addr);
420 G.port_addr = NULL;
421 if (G.pasv_listen_fd > STDOUT_FILENO)
422 close(G.pasv_listen_fd);
423 G.pasv_listen_fd = -1;
426 /* On error, emits error code to the peer */
427 static int
428 ftpdataio_get_pasv_fd(void)
430 int remote_fd;
432 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
434 if (remote_fd < 0) {
435 WRITE_ERR(FTP_BADSENDCONN);
436 return remote_fd;
439 setsockopt_keepalive(remote_fd);
440 return remote_fd;
443 /* Clears port/pasv data.
444 * This means we dont waste resources, for example, keeping
445 * PASV listening socket open when it is no longer needed.
446 * On error, emits error code to the peer (or exits).
447 * On success, emits p_status_msg to the peer.
449 static int
450 get_remote_transfer_fd(const char *p_status_msg)
452 int remote_fd;
454 if (pasv_active())
455 /* On error, emits error code to the peer */
456 remote_fd = ftpdataio_get_pasv_fd();
457 else
458 /* Exits on error */
459 remote_fd = xconnect_stream(G.port_addr);
461 port_pasv_cleanup();
463 if (remote_fd < 0)
464 return remote_fd;
466 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
467 return remote_fd;
470 /* If there were neither PASV nor PORT, emits error code to the peer */
471 static int
472 port_or_pasv_was_seen(void)
474 if (!pasv_active() && !port_active()) {
475 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
476 return 0;
479 return 1;
482 /* Exits on error */
483 static unsigned
484 bind_for_passive_mode(void)
486 int fd;
487 unsigned port;
489 port_pasv_cleanup();
491 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
492 setsockopt_reuseaddr(fd);
494 set_nport(&G.local_addr->u.sa, 0);
495 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
496 xlisten(fd, 1);
497 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
499 port = get_nport(&G.local_addr->u.sa);
500 port = ntohs(port);
501 return port;
504 /* Exits on error */
505 static void
506 handle_pasv(void)
508 unsigned port;
509 char *addr, *response;
511 port = bind_for_passive_mode();
513 if (G.local_addr->u.sa.sa_family == AF_INET)
514 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
515 else /* seen this in the wild done by other ftp servers: */
516 addr = xstrdup("0.0.0.0");
517 replace_char(addr, '.', ',');
519 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
520 addr, (int)(port >> 8), (int)(port & 255));
521 free(addr);
522 cmdio_write_raw(response);
523 free(response);
526 /* Exits on error */
527 static void
528 handle_epsv(void)
530 unsigned port;
531 char *response;
533 port = bind_for_passive_mode();
534 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
535 cmdio_write_raw(response);
536 free(response);
539 static void
540 handle_port(void)
542 unsigned port, port_hi;
543 char *raw, *comma;
544 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
545 socklen_t peer_ipv4_len;
546 struct sockaddr_in peer_ipv4;
547 struct in_addr port_ipv4_sin_addr;
548 #endif
550 port_pasv_cleanup();
552 raw = G.ftp_arg;
554 /* PORT command format makes sense only over IPv4 */
555 if (!raw
556 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
557 || G.local_addr->u.sa.sa_family != AF_INET
558 #endif
560 bail:
561 WRITE_ERR(FTP_BADCMD);
562 return;
565 comma = strrchr(raw, ',');
566 if (comma == NULL)
567 goto bail;
568 *comma = '\0';
569 port = bb_strtou(&comma[1], NULL, 10);
570 if (errno || port > 0xff)
571 goto bail;
573 comma = strrchr(raw, ',');
574 if (comma == NULL)
575 goto bail;
576 *comma = '\0';
577 port_hi = bb_strtou(&comma[1], NULL, 10);
578 if (errno || port_hi > 0xff)
579 goto bail;
580 port |= port_hi << 8;
582 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
583 replace_char(raw, ',', '.');
585 /* We are verifying that PORT's IP matches getpeername().
586 * Otherwise peer can make us open data connections
587 * to other hosts (security problem!)
588 * This code would be too simplistic:
589 * lsa = xdotted2sockaddr(raw, port);
590 * if (lsa == NULL) goto bail;
592 if (!inet_aton(raw, &port_ipv4_sin_addr))
593 goto bail;
594 peer_ipv4_len = sizeof(peer_ipv4);
595 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
596 goto bail;
597 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
598 goto bail;
600 G.port_addr = xdotted2sockaddr(raw, port);
601 #else
602 G.port_addr = get_peer_lsa(STDIN_FILENO);
603 set_nport(&G.port_addr->u.sa, htons(port));
604 #endif
605 WRITE_OK(FTP_PORTOK);
608 static void
609 handle_rest(void)
611 /* When ftp_arg == NULL simply restart from beginning */
612 G.restart_pos = G.ftp_arg ? XATOOFF(G.ftp_arg) : 0;
613 WRITE_OK(FTP_RESTOK);
616 static void
617 handle_retr(void)
619 struct stat statbuf;
620 off_t bytes_transferred;
621 int remote_fd;
622 int local_file_fd;
623 off_t offset = G.restart_pos;
624 char *response;
626 G.restart_pos = 0;
628 if (!port_or_pasv_was_seen())
629 return; /* port_or_pasv_was_seen emitted error response */
631 /* O_NONBLOCK is useful if file happens to be a device node */
632 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
633 if (local_file_fd < 0) {
634 WRITE_ERR(FTP_FILEFAIL);
635 return;
638 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
639 /* Note - pretend open failed */
640 WRITE_ERR(FTP_FILEFAIL);
641 goto file_close_out;
643 G.local_file_fd = local_file_fd;
645 /* Now deactive O_NONBLOCK, otherwise we have a problem
646 * on DMAPI filesystems such as XFS DMAPI.
648 ndelay_off(local_file_fd);
650 /* Set the download offset (from REST) if any */
651 if (offset != 0)
652 xlseek(local_file_fd, offset, SEEK_SET);
654 response = xasprintf(
655 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
656 G.ftp_arg, statbuf.st_size);
657 remote_fd = get_remote_transfer_fd(response);
658 free(response);
659 if (remote_fd < 0)
660 goto file_close_out;
662 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
663 close(remote_fd);
664 if (bytes_transferred < 0)
665 WRITE_ERR(FTP_BADSENDFILE);
666 else
667 WRITE_OK(FTP_TRANSFEROK);
669 file_close_out:
670 close(local_file_fd);
671 G.local_file_fd = 0;
674 /* List commands */
676 static int
677 popen_ls(const char *opt)
679 const char *argv[5];
680 struct fd_pair outfd;
681 pid_t pid;
683 argv[0] = "ftpd";
684 argv[1] = opt; /* "-lA" or "-1A" */
685 argv[2] = "--";
686 argv[3] = G.ftp_arg;
687 argv[4] = NULL;
689 /* Improve compatibility with non-RFC conforming FTP clients
690 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
691 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
692 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
693 && G.ftp_arg && G.ftp_arg[0] == '-'
695 const char *tmp = strchr(G.ftp_arg, ' ');
696 if (tmp) /* skip the space */
697 tmp++;
698 argv[3] = tmp;
701 xpiped_pair(outfd);
703 /*fflush_all(); - so far we dont use stdio on output */
704 pid = BB_MMU ? xfork() : xvfork();
705 if (pid == 0) {
706 #if !BB_MMU
707 int cur_fd;
708 #endif
709 /* child */
710 /* NB: close _first_, then move fd! */
711 close(outfd.rd);
712 xmove_fd(outfd.wr, STDOUT_FILENO);
713 /* Opening /dev/null in chroot is hard.
714 * Just making sure STDIN_FILENO is opened
715 * to something harmless. Paranoia,
716 * ls won't read it anyway */
717 close(STDIN_FILENO);
718 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
719 #if BB_MMU
720 /* memset(&G, 0, sizeof(G)); - ls_main does it */
721 exit(ls_main(/*argc_unused*/ 0, (char**) argv));
722 #else
723 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
724 /* On NOMMU, we want to execute a child - copy of ourself
725 * in order to unblock parent after vfork.
726 * In chroot we usually can't re-exec. Thus we escape
727 * out of the chroot back to original root.
729 if (G.root_fd >= 0) {
730 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
731 _exit(127);
732 /*close(G.root_fd); - close_on_exec_on() took care of this */
734 /* Child expects directory to list on fd #3 */
735 xmove_fd(cur_fd, 3);
736 execv(bb_busybox_exec_path, (char**) argv);
737 _exit(127);
738 #endif
741 /* parent */
742 close(outfd.wr);
743 return outfd.rd;
746 enum {
747 USE_CTRL_CONN = 1,
748 LONG_LISTING = 2,
751 static void
752 handle_dir_common(int opts)
754 FILE *ls_fp;
755 char *line;
756 int ls_fd;
758 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
759 return; /* port_or_pasv_was_seen emitted error response */
761 ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
762 ls_fp = xfdopen_for_read(ls_fd);
763 /* FIXME: filenames with embedded newlines are mishandled */
765 if (opts & USE_CTRL_CONN) {
766 /* STAT <filename> */
767 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
768 while (1) {
769 line = xmalloc_fgetline(ls_fp);
770 if (!line)
771 break;
772 /* Hack: 0 results in no status at all */
773 /* Note: it's ok that we don't prepend space,
774 * ftp.kernel.org doesn't do that too */
775 cmdio_write(0, line);
776 free(line);
778 WRITE_OK(FTP_STATFILE_OK);
779 } else {
780 /* LIST/NLST [<filename>] */
781 int remote_fd = get_remote_transfer_fd(" Directory listing");
782 if (remote_fd >= 0) {
783 while (1) {
784 unsigned len;
786 line = xmalloc_fgets(ls_fp);
787 if (!line)
788 break;
789 /* I've seen clients complaining when they
790 * are fed with ls output with bare '\n'.
791 * Replace trailing "\n\0" with "\r\n".
793 len = strlen(line);
794 if (len != 0) /* paranoia check */
795 line[len - 1] = '\r';
796 line[len] = '\n';
797 xwrite(remote_fd, line, len + 1);
798 free(line);
801 close(remote_fd);
802 WRITE_OK(FTP_TRANSFEROK);
804 fclose(ls_fp); /* closes ls_fd too */
806 static void
807 handle_list(void)
809 handle_dir_common(LONG_LISTING);
811 static void
812 handle_nlst(void)
814 /* NLST returns list of names, "\r\n" terminated without regard
815 * to the current binary flag. Names may start with "/",
816 * then they represent full names (we don't produce such names),
817 * otherwise names are relative to current directory.
818 * Embedded "\n" are replaced by NULs. This is safe since names
819 * can never contain NUL.
821 handle_dir_common(0);
823 static void
824 handle_stat_file(void)
826 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
829 /* This can be extended to handle MLST, as all info is available
830 * in struct stat for that:
831 * MLST file_name
832 * 250-Listing file_name
833 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
834 * 250 End
835 * Nano-doc:
836 * MLST [<file or dir name, "." assumed if not given>]
837 * Returned name should be either the same as requested, or fully qualified.
838 * If there was no parameter, return "" or (preferred) fully-qualified name.
839 * Returned "facts" (case is not important):
840 * size - size in octets
841 * modify - last modification time
842 * type - entry type (file,dir,OS.unix=block)
843 * (+ cdir and pdir types for MLSD)
844 * unique - unique id of file/directory (inode#)
845 * perm -
846 * a: can be appended to (APPE)
847 * d: can be deleted (RMD/DELE)
848 * f: can be renamed (RNFR)
849 * r: can be read (RETR)
850 * w: can be written (STOR)
851 * e: can CWD into this dir
852 * l: this dir can be listed (dir only!)
853 * c: can create files in this dir
854 * m: can create dirs in this dir (MKD)
855 * p: can delete files in this dir
856 * UNIX.mode - unix file mode
858 static void
859 handle_size_or_mdtm(int need_size)
861 struct stat statbuf;
862 struct tm broken_out;
863 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
864 | sizeof("NNN YYYYMMDDhhmmss\r\n")
867 if (!G.ftp_arg
868 || stat(G.ftp_arg, &statbuf) != 0
869 || !S_ISREG(statbuf.st_mode)
871 WRITE_ERR(FTP_FILEFAIL);
872 return;
874 if (need_size) {
875 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
876 } else {
877 gmtime_r(&statbuf.st_mtime, &broken_out);
878 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
879 broken_out.tm_year + 1900,
880 broken_out.tm_mon + 1,
881 broken_out.tm_mday,
882 broken_out.tm_hour,
883 broken_out.tm_min,
884 broken_out.tm_sec);
886 cmdio_write_raw(buf);
889 /* Upload commands */
891 #if ENABLE_FEATURE_FTPD_WRITE
892 static void
893 handle_mkd(void)
895 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
896 WRITE_ERR(FTP_FILEFAIL);
897 return;
899 WRITE_OK(FTP_MKDIROK);
902 static void
903 handle_rmd(void)
905 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
906 WRITE_ERR(FTP_FILEFAIL);
907 return;
909 WRITE_OK(FTP_RMDIROK);
912 static void
913 handle_dele(void)
915 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
916 WRITE_ERR(FTP_FILEFAIL);
917 return;
919 WRITE_OK(FTP_DELEOK);
922 static void
923 handle_rnfr(void)
925 free(G.rnfr_filename);
926 G.rnfr_filename = xstrdup(G.ftp_arg);
927 WRITE_OK(FTP_RNFROK);
930 static void
931 handle_rnto(void)
933 int retval;
935 /* If we didn't get a RNFR, throw a wobbly */
936 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
937 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
938 return;
941 retval = rename(G.rnfr_filename, G.ftp_arg);
942 free(G.rnfr_filename);
943 G.rnfr_filename = NULL;
945 if (retval) {
946 WRITE_ERR(FTP_FILEFAIL);
947 return;
949 WRITE_OK(FTP_RENAMEOK);
952 static void
953 handle_upload_common(int is_append, int is_unique)
955 struct stat statbuf;
956 char *tempname;
957 off_t bytes_transferred;
958 off_t offset;
959 int local_file_fd;
960 int remote_fd;
962 offset = G.restart_pos;
963 G.restart_pos = 0;
965 if (!port_or_pasv_was_seen())
966 return; /* port_or_pasv_was_seen emitted error response */
968 tempname = NULL;
969 local_file_fd = -1;
970 if (is_unique) {
971 tempname = xstrdup(" FILE: uniq.XXXXXX");
972 local_file_fd = mkstemp(tempname + 7);
973 } else if (G.ftp_arg) {
974 int flags = O_WRONLY | O_CREAT | O_TRUNC;
975 if (is_append)
976 flags = O_WRONLY | O_CREAT | O_APPEND;
977 if (offset)
978 flags = O_WRONLY | O_CREAT;
979 local_file_fd = open(G.ftp_arg, flags, 0666);
982 if (local_file_fd < 0
983 || fstat(local_file_fd, &statbuf) != 0
984 || !S_ISREG(statbuf.st_mode)
986 free(tempname);
987 WRITE_ERR(FTP_UPLOADFAIL);
988 if (local_file_fd >= 0)
989 goto close_local_and_bail;
990 return;
992 G.local_file_fd = local_file_fd;
994 if (offset)
995 xlseek(local_file_fd, offset, SEEK_SET);
997 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
998 free(tempname);
1000 if (remote_fd < 0)
1001 goto close_local_and_bail;
1003 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
1004 close(remote_fd);
1005 if (bytes_transferred < 0)
1006 WRITE_ERR(FTP_BADSENDFILE);
1007 else
1008 WRITE_OK(FTP_TRANSFEROK);
1010 close_local_and_bail:
1011 close(local_file_fd);
1012 G.local_file_fd = 0;
1015 static void
1016 handle_stor(void)
1018 handle_upload_common(0, 0);
1021 static void
1022 handle_appe(void)
1024 G.restart_pos = 0;
1025 handle_upload_common(1, 0);
1028 static void
1029 handle_stou(void)
1031 G.restart_pos = 0;
1032 handle_upload_common(0, 1);
1034 #endif /* ENABLE_FEATURE_FTPD_WRITE */
1036 static uint32_t
1037 cmdio_get_cmd_and_arg(void)
1039 int len;
1040 uint32_t cmdval;
1041 char *cmd;
1043 alarm(G.timeout);
1045 free(G.ftp_cmd);
1047 /* Paranoia. Peer may send 1 gigabyte long cmd... */
1048 /* Using separate len_on_stk instead of len optimizes
1049 * code size (allows len to be in CPU register) */
1050 size_t len_on_stk = 8 * 1024;
1051 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
1052 if (!cmd)
1053 exit(0);
1054 len = len_on_stk;
1057 /* De-escape telnet: 0xff,0xff => 0xff */
1058 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1059 * data transfer, and may be preceded by telnet's "Interrupt Process"
1060 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1061 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1062 * and may generate SIGURG on our side. See RFC854).
1063 * So far we don't support that (may install SIGURG handler if we'd want to),
1064 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1065 /* Then de-escape FTP: NUL => '\n' */
1066 /* Testing for \xff:
1067 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1068 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1069 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1070 * Testing for embedded LF:
1071 * LF_HERE=`echo -ne "LF\nHERE"`
1072 * echo Hello >"$LF_HERE"
1073 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1076 int dst, src;
1078 /* Strip "\r\n" if it is there */
1079 if (len != 0 && cmd[len - 1] == '\n') {
1080 len--;
1081 if (len != 0 && cmd[len - 1] == '\r')
1082 len--;
1083 cmd[len] = '\0';
1085 src = strchrnul(cmd, 0xff) - cmd;
1086 /* 99,99% there are neither NULs nor 255s and src == len */
1087 if (src < len) {
1088 dst = src;
1089 do {
1090 if ((unsigned char)(cmd[src]) == 255) {
1091 src++;
1092 /* 255,xxx - skip 255 */
1093 if ((unsigned char)(cmd[src]) != 255) {
1094 /* 255,!255 - skip both */
1095 src++;
1096 continue;
1098 /* 255,255 - retain one 255 */
1100 /* NUL => '\n' */
1101 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1102 src++;
1103 } while (src < len);
1104 cmd[dst] = '\0';
1108 if (G.verbose > 1)
1109 verbose_log(cmd);
1111 G.ftp_arg = strchr(cmd, ' ');
1112 if (G.ftp_arg != NULL)
1113 *G.ftp_arg++ = '\0';
1115 /* Uppercase and pack into uint32_t first word of the command */
1116 cmdval = 0;
1117 while (*cmd)
1118 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1120 return cmdval;
1123 #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1124 #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1125 enum {
1126 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1127 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1128 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1129 const_CWD = mk_const3('C', 'W', 'D'),
1130 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1131 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
1132 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
1133 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1134 const_LIST = mk_const4('L', 'I', 'S', 'T'),
1135 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
1136 const_MKD = mk_const3('M', 'K', 'D'),
1137 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1138 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1139 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1140 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1141 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1142 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1143 const_PWD = mk_const3('P', 'W', 'D'),
1144 /* Same as PWD. Reportedly used by windows ftp client */
1145 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
1146 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1147 const_REST = mk_const4('R', 'E', 'S', 'T'),
1148 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1149 const_RMD = mk_const3('R', 'M', 'D'),
1150 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1151 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1152 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1153 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1154 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1155 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1156 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1157 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1158 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1159 const_USER = mk_const4('U', 'S', 'E', 'R'),
1161 #if !BB_MMU
1162 OPT_l = (1 << 0),
1163 OPT_1 = (1 << 1),
1164 #endif
1165 BIT_A = (!BB_MMU) * 2,
1166 OPT_A = (1 << (BIT_A + 0)),
1167 OPT_v = (1 << (BIT_A + 1)),
1168 OPT_S = (1 << (BIT_A + 2)),
1169 OPT_w = (1 << (BIT_A + 3)) * ENABLE_FEATURE_FTPD_WRITE,
1172 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1173 int ftpd_main(int argc UNUSED_PARAM, char **argv)
1175 #if ENABLE_FEATURE_FTPD_AUTHENTICATION
1176 struct passwd *pw = NULL;
1177 char *anon_opt = NULL;
1178 #endif
1179 unsigned abs_timeout;
1180 unsigned verbose_S;
1181 smallint opts;
1183 INIT_G();
1185 abs_timeout = 1 * 60 * 60;
1186 verbose_S = 0;
1187 G.timeout = 2 * 60;
1188 #if BB_MMU
1189 opts = getopt32(argv, "^" "AvS" IF_FEATURE_FTPD_WRITE("w")
1190 "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:")
1191 "\0" "vv:SS",
1192 &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
1193 &G.verbose, &verbose_S
1195 #else
1196 opts = getopt32(argv, "^" "l1AvS" IF_FEATURE_FTPD_WRITE("w")
1197 "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:")
1198 "\0" "vv:SS",
1199 &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
1200 &G.verbose, &verbose_S
1202 if (opts & (OPT_l|OPT_1)) {
1203 /* Our secret backdoor to ls: see popen_ls() */
1204 if (fchdir(3) != 0)
1205 _exit(127);
1206 /* memset(&G, 0, sizeof(G)); - ls_main does it */
1207 /* NB: in this case -A has a different meaning: like "ls -A" */
1208 return ls_main(/*argc_unused*/ 0, argv);
1210 #endif
1211 if (G.verbose < verbose_S)
1212 G.verbose = verbose_S;
1213 if (abs_timeout | G.timeout) {
1214 if (abs_timeout == 0)
1215 abs_timeout = INT_MAX;
1216 G.end_time = monotonic_sec() + abs_timeout;
1217 if (G.timeout > abs_timeout)
1218 G.timeout = abs_timeout;
1220 strcpy(G.msg_ok + 4, MSG_OK );
1221 strcpy(G.msg_err + 4, MSG_ERR);
1223 G.local_addr = get_sock_lsa(STDIN_FILENO);
1224 if (!G.local_addr) {
1225 /* This is confusing:
1226 * bb_error_msg_and_die("stdin is not a socket");
1227 * Better: */
1228 bb_show_usage();
1229 /* Help text says that ftpd must be used as inetd service,
1230 * which is by far the most usual cause of get_sock_lsa
1231 * failure */
1234 if (!(opts & OPT_v))
1235 logmode = LOGMODE_NONE;
1236 if (opts & OPT_S) {
1237 /* LOG_NDELAY is needed since we may chroot later */
1238 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1239 logmode |= LOGMODE_SYSLOG;
1241 if (logmode)
1242 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1244 //umask(077); - admin can set umask before starting us
1246 /* Signals */
1247 bb_signals(0
1248 /* We'll always take EPIPE rather than a rude signal, thanks */
1249 + (1 << SIGPIPE)
1250 /* LIST command spawns chilren. Prevent zombies */
1251 + (1 << SIGCHLD)
1252 , SIG_IGN);
1254 /* Set up options on the command socket (do we need these all? why?) */
1255 setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
1256 setsockopt_keepalive(STDIN_FILENO);
1257 /* Telnet protocol over command link may send "urgent" data,
1258 * we prefer it to be received in the "normal" data stream: */
1259 setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
1261 WRITE_OK(FTP_GREET);
1262 signal(SIGALRM, timeout_handler);
1264 #if ENABLE_FEATURE_FTPD_AUTHENTICATION
1265 if (!(opts & OPT_A)) {
1266 while (1) {
1267 uint32_t cmdval = cmdio_get_cmd_and_arg();
1268 if (cmdval == const_USER) {
1269 if (anon_opt && strcmp(G.ftp_arg, "anonymous") == 0) {
1270 pw = getpwnam(anon_opt);
1271 if (pw)
1272 break; /* does not even ask for password */
1274 pw = getpwnam(G.ftp_arg);
1275 cmdio_write_raw(STR(FTP_GIVEPWORD)" Specify password\r\n");
1276 } else if (cmdval == const_PASS) {
1277 if (check_password(pw, G.ftp_arg) > 0) {
1278 break; /* login success */
1280 cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
1281 pw = NULL;
1282 } else if (cmdval == const_QUIT) {
1283 WRITE_OK(FTP_GOODBYE);
1284 return 0;
1285 } else {
1286 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER+PASS\r\n");
1289 WRITE_OK(FTP_LOGINOK);
1291 #endif
1293 /* Do this after auth, else /etc/passwd is not accessible */
1294 #if !BB_MMU
1295 G.root_fd = -1;
1296 #endif
1297 argv += optind;
1298 if (argv[0]) {
1299 const char *basedir = argv[0];
1300 #if !BB_MMU
1301 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1302 close_on_exec_on(G.root_fd);
1303 #endif
1304 if (chroot(basedir) == 0)
1305 basedir = "/";
1306 #if !BB_MMU
1307 else {
1308 close(G.root_fd);
1309 G.root_fd = -1;
1311 #endif
1313 * If chroot failed, assume that we aren't root,
1314 * and at least chdir to the specified DIR
1315 * (older versions were dying with error message).
1316 * If chroot worked, move current dir to new "/":
1318 xchdir(basedir);
1321 #if ENABLE_FEATURE_FTPD_AUTHENTICATION
1322 if (pw)
1323 change_identity(pw);
1324 /* else: -A is in effect */
1325 #endif
1327 /* RFC-959 Section 5.1
1328 * The following commands and options MUST be supported by every
1329 * server-FTP and user-FTP, except in cases where the underlying
1330 * file system or operating system does not allow or support
1331 * a particular command.
1332 * Type: ASCII Non-print, IMAGE, LOCAL 8
1333 * Mode: Stream
1334 * Structure: File, Record*
1335 * (Record structure is REQUIRED only for hosts whose file
1336 * systems support record structure).
1337 * Commands:
1338 * USER, PASS, ACCT, [bbox: ACCT not supported]
1339 * PORT, PASV,
1340 * TYPE, MODE, STRU,
1341 * RETR, STOR, APPE,
1342 * RNFR, RNTO, DELE,
1343 * CWD, CDUP, RMD, MKD, PWD,
1344 * LIST, NLST,
1345 * SYST, STAT,
1346 * HELP, NOOP, QUIT.
1348 /* ACCOUNT (ACCT)
1349 * "The argument field is a Telnet string identifying the user's account.
1350 * The command is not necessarily related to the USER command, as some
1351 * sites may require an account for login and others only for specific
1352 * access, such as storing files. In the latter case the command may
1353 * arrive at any time.
1354 * There are reply codes to differentiate these cases for the automation:
1355 * when account information is required for login, the response to
1356 * a successful PASSword command is reply code 332. On the other hand,
1357 * if account information is NOT required for login, the reply to
1358 * a successful PASSword command is 230; and if the account information
1359 * is needed for a command issued later in the dialogue, the server
1360 * should return a 332 or 532 reply depending on whether it stores
1361 * (pending receipt of the ACCounT command) or discards the command,
1362 * respectively."
1365 while (1) {
1366 uint32_t cmdval = cmdio_get_cmd_and_arg();
1368 if (cmdval == const_QUIT) {
1369 WRITE_OK(FTP_GOODBYE);
1370 return 0;
1372 else if (cmdval == const_USER)
1373 /* This would mean "ok, now give me PASS". */
1374 /*WRITE_OK(FTP_GIVEPWORD);*/
1375 /* vsftpd can be configured to not require that,
1376 * and this also saves one roundtrip:
1378 WRITE_OK(FTP_LOGINOK);
1379 else if (cmdval == const_PASS)
1380 WRITE_OK(FTP_LOGINOK);
1381 else if (cmdval == const_NOOP)
1382 WRITE_OK(FTP_NOOPOK);
1383 else if (cmdval == const_TYPE)
1384 WRITE_OK(FTP_TYPEOK);
1385 else if (cmdval == const_STRU)
1386 WRITE_OK(FTP_STRUOK);
1387 else if (cmdval == const_MODE)
1388 WRITE_OK(FTP_MODEOK);
1389 else if (cmdval == const_ALLO)
1390 WRITE_OK(FTP_ALLOOK);
1391 else if (cmdval == const_SYST)
1392 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1393 else if (cmdval == const_PWD || cmdval == const_XPWD)
1394 handle_pwd();
1395 else if (cmdval == const_CWD)
1396 handle_cwd();
1397 else if (cmdval == const_CDUP) /* cd .. */
1398 handle_cdup();
1399 /* HELP is nearly useless, but we can reuse FEAT for it */
1400 /* lftp uses FEAT */
1401 else if (cmdval == const_HELP || cmdval == const_FEAT)
1402 handle_feat(cmdval == const_HELP
1403 ? STRNUM32(FTP_HELP)
1404 : STRNUM32(FTP_STATOK)
1406 else if (cmdval == const_LIST) /* ls -l */
1407 handle_list();
1408 else if (cmdval == const_NLST) /* "name list", bare ls */
1409 handle_nlst();
1410 /* SIZE is crucial for wget's download indicator etc */
1411 /* Mozilla, lftp use MDTM (presumably for caching) */
1412 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1413 handle_size_or_mdtm(cmdval == const_SIZE);
1414 else if (cmdval == const_STAT) {
1415 if (G.ftp_arg == NULL)
1416 handle_stat();
1417 else
1418 handle_stat_file();
1420 else if (cmdval == const_PASV)
1421 handle_pasv();
1422 else if (cmdval == const_EPSV)
1423 handle_epsv();
1424 else if (cmdval == const_RETR)
1425 handle_retr();
1426 else if (cmdval == const_PORT)
1427 handle_port();
1428 else if (cmdval == const_REST)
1429 handle_rest();
1430 #if ENABLE_FEATURE_FTPD_WRITE
1431 else if (opts & OPT_w) {
1432 if (cmdval == const_STOR)
1433 handle_stor();
1434 else if (cmdval == const_MKD)
1435 handle_mkd();
1436 else if (cmdval == const_RMD)
1437 handle_rmd();
1438 else if (cmdval == const_DELE)
1439 handle_dele();
1440 else if (cmdval == const_RNFR) /* "rename from" */
1441 handle_rnfr();
1442 else if (cmdval == const_RNTO) /* "rename to" */
1443 handle_rnto();
1444 else if (cmdval == const_APPE)
1445 handle_appe();
1446 else if (cmdval == const_STOU) /* "store unique" */
1447 handle_stou();
1448 else
1449 goto bad_cmd;
1451 #endif
1452 #if 0
1453 else if (cmdval == const_STOR
1454 || cmdval == const_MKD
1455 || cmdval == const_RMD
1456 || cmdval == const_DELE
1457 || cmdval == const_RNFR
1458 || cmdval == const_RNTO
1459 || cmdval == const_APPE
1460 || cmdval == const_STOU
1462 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1464 #endif
1465 else {
1466 /* Which unsupported commands were seen in the wild?
1467 * (doesn't necessarily mean "we must support them")
1468 * foo 1.2.3: XXXX - comment
1470 #if ENABLE_FEATURE_FTPD_WRITE
1471 bad_cmd:
1472 #endif
1473 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");