ftpd: better usage text
[oweals/busybox.git] / networking / ftpd.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Simple FTP daemon, based on vsftpd 2.0.7 (written by Chris Evans)
4  *
5  * Author: Adam Tkac <vonsch@gmail.com>
6  *
7  * Licensed under GPLv2, see file LICENSE in this source tree.
8  *
9  * Only subset of FTP protocol is implemented but vast majority of clients
10  * should not have any problem.
11  *
12  * You have to run this daemon via inetd.
13  */
14
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:     "\nOptions:"
26 //usage:     "\n        -w      Allow upload"
27 //usage:     "\n        -v      Log errors to stderr. -vv: verbose log"
28 //usage:     "\n        -S      Log errors to syslog. -SS: verbose log"
29 //usage:     "\n        -t,-T   Idle and absolute timeouts"
30 //usage:     "\n        DIR     Change root to this directory"
31
32 #include "libbb.h"
33 #include <syslog.h>
34 #include <netinet/tcp.h>
35
36 #define FTP_DATACONN            150
37 #define FTP_NOOPOK              200
38 #define FTP_TYPEOK              200
39 #define FTP_PORTOK              200
40 #define FTP_STRUOK              200
41 #define FTP_MODEOK              200
42 #define FTP_ALLOOK              202
43 #define FTP_STATOK              211
44 #define FTP_STATFILE_OK         213
45 #define FTP_HELP                214
46 #define FTP_SYSTOK              215
47 #define FTP_GREET               220
48 #define FTP_GOODBYE             221
49 #define FTP_TRANSFEROK          226
50 #define FTP_PASVOK              227
51 /*#define FTP_EPRTOK              228*/
52 #define FTP_EPSVOK              229
53 #define FTP_LOGINOK             230
54 #define FTP_CWDOK               250
55 #define FTP_RMDIROK             250
56 #define FTP_DELEOK              250
57 #define FTP_RENAMEOK            250
58 #define FTP_PWDOK               257
59 #define FTP_MKDIROK             257
60 #define FTP_GIVEPWORD           331
61 #define FTP_RESTOK              350
62 #define FTP_RNFROK              350
63 #define FTP_TIMEOUT             421
64 #define FTP_BADSENDCONN         425
65 #define FTP_BADSENDNET          426
66 #define FTP_BADSENDFILE         451
67 #define FTP_BADCMD              500
68 #define FTP_COMMANDNOTIMPL      502
69 #define FTP_NEEDUSER            503
70 #define FTP_NEEDRNFR            503
71 #define FTP_BADSTRU             504
72 #define FTP_BADMODE             504
73 #define FTP_LOGINERR            530
74 #define FTP_FILEFAIL            550
75 #define FTP_NOPERM              550
76 #define FTP_UPLOADFAIL          553
77
78 #define STR1(s) #s
79 #define STR(s) STR1(s)
80
81 /* Convert a constant to 3-digit string, packed into uint32_t */
82 enum {
83         /* Shift for Nth decimal digit */
84         SHIFT2  =  0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
85         SHIFT1  =  8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
86         SHIFT0  = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
87         /* And for 4th position (space) */
88         SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
89 };
90 #define STRNUM32(s) (uint32_t)(0 \
91         | (('0' + ((s) / 1 % 10)) << SHIFT0) \
92         | (('0' + ((s) / 10 % 10)) << SHIFT1) \
93         | (('0' + ((s) / 100 % 10)) << SHIFT2) \
94 )
95 #define STRNUM32sp(s) (uint32_t)(0 \
96         | (' ' << SHIFTsp) \
97         | (('0' + ((s) / 1 % 10)) << SHIFT0) \
98         | (('0' + ((s) / 10 % 10)) << SHIFT1) \
99         | (('0' + ((s) / 100 % 10)) << SHIFT2) \
100 )
101
102 #define MSG_OK "Operation successful\r\n"
103 #define MSG_ERR "Error\r\n"
104
105 struct globals {
106         int pasv_listen_fd;
107 #if !BB_MMU
108         int root_fd;
109 #endif
110         int local_file_fd;
111         unsigned end_time;
112         unsigned timeout;
113         unsigned verbose;
114         off_t local_file_pos;
115         off_t restart_pos;
116         len_and_sockaddr *local_addr;
117         len_and_sockaddr *port_addr;
118         char *ftp_cmd;
119         char *ftp_arg;
120 #if ENABLE_FEATURE_FTP_WRITE
121         char *rnfr_filename;
122 #endif
123         /* We need these aligned to uint32_t */
124         char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
125         char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
126 } FIX_ALIASING;
127 #define G (*(struct globals*)&bb_common_bufsiz1)
128 #define INIT_G() do { \
129         /* Moved to main */ \
130         /*strcpy(G.msg_ok  + 4, MSG_OK );*/ \
131         /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
132 } while (0)
133
134
135 static char *
136 escape_text(const char *prepend, const char *str, unsigned escapee)
137 {
138         unsigned retlen, remainlen, chunklen;
139         char *ret, *found;
140         char append;
141
142         append = (char)escapee;
143         escapee >>= 8;
144
145         remainlen = strlen(str);
146         retlen = strlen(prepend);
147         ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
148         strcpy(ret, prepend);
149
150         for (;;) {
151                 found = strchrnul(str, escapee);
152                 chunklen = found - str + 1;
153
154                 /* Copy chunk up to and including escapee (or NUL) to ret */
155                 memcpy(ret + retlen, str, chunklen);
156                 retlen += chunklen;
157
158                 if (*found == '\0') {
159                         /* It wasn't escapee, it was NUL! */
160                         ret[retlen - 1] = append; /* replace NUL */
161                         ret[retlen] = '\0'; /* add NUL */
162                         break;
163                 }
164                 ret[retlen++] = escapee; /* duplicate escapee */
165                 str = found + 1;
166         }
167         return ret;
168 }
169
170 /* Returns strlen as a bonus */
171 static unsigned
172 replace_char(char *str, char from, char to)
173 {
174         char *p = str;
175         while (*p) {
176                 if (*p == from)
177                         *p = to;
178                 p++;
179         }
180         return p - str;
181 }
182
183 static void
184 verbose_log(const char *str)
185 {
186         bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
187 }
188
189 /* NB: status_str is char[4] packed into uint32_t */
190 static void
191 cmdio_write(uint32_t status_str, const char *str)
192 {
193         char *response;
194         int len;
195
196         /* FTP uses telnet protocol for command link.
197          * In telnet, 0xff is an escape char, and needs to be escaped: */
198         response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
199
200         /* FTP sends embedded LFs as NULs */
201         len = replace_char(response, '\n', '\0');
202
203         response[len++] = '\n'; /* tack on trailing '\n' */
204         xwrite(STDOUT_FILENO, response, len);
205         if (G.verbose > 1)
206                 verbose_log(response);
207         free(response);
208 }
209
210 static void
211 cmdio_write_ok(unsigned status)
212 {
213         *(uint32_t *) G.msg_ok = status;
214         xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
215         if (G.verbose > 1)
216                 verbose_log(G.msg_ok);
217 }
218 #define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
219
220 /* TODO: output strerr(errno) if errno != 0? */
221 static void
222 cmdio_write_error(unsigned status)
223 {
224         *(uint32_t *) G.msg_err = status;
225         xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
226         if (G.verbose > 0)
227                 verbose_log(G.msg_err);
228 }
229 #define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
230
231 static void
232 cmdio_write_raw(const char *p_text)
233 {
234         xwrite_str(STDOUT_FILENO, p_text);
235         if (G.verbose > 1)
236                 verbose_log(p_text);
237 }
238
239 static void
240 timeout_handler(int sig UNUSED_PARAM)
241 {
242         off_t pos;
243         int sv_errno = errno;
244
245         if ((int)(monotonic_sec() - G.end_time) >= 0)
246                 goto timed_out;
247
248         if (!G.local_file_fd)
249                 goto timed_out;
250
251         pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
252         if (pos == G.local_file_pos)
253                 goto timed_out;
254         G.local_file_pos = pos;
255
256         alarm(G.timeout);
257         errno = sv_errno;
258         return;
259
260  timed_out:
261         cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
262 /* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
263         exit(1);
264 }
265
266 /* Simple commands */
267
268 static void
269 handle_pwd(void)
270 {
271         char *cwd, *response;
272
273         cwd = xrealloc_getcwd_or_warn(NULL);
274         if (cwd == NULL)
275                 cwd = xstrdup("");
276
277         /* We have to promote each " to "" */
278         response = escape_text(" \"", cwd, ('"' << 8) + '"');
279         free(cwd);
280         cmdio_write(STRNUM32(FTP_PWDOK), response);
281         free(response);
282 }
283
284 static void
285 handle_cwd(void)
286 {
287         if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
288                 WRITE_ERR(FTP_FILEFAIL);
289                 return;
290         }
291         WRITE_OK(FTP_CWDOK);
292 }
293
294 static void
295 handle_cdup(void)
296 {
297         G.ftp_arg = (char*)"..";
298         handle_cwd();
299 }
300
301 static void
302 handle_stat(void)
303 {
304         cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
305                         " TYPE: BINARY\r\n"
306                         STR(FTP_STATOK)" Ok\r\n");
307 }
308
309 /* Examples of HELP and FEAT:
310 # nc -vvv ftp.kernel.org 21
311 ftp.kernel.org (130.239.17.4:21) open
312 220 Welcome to ftp.kernel.org.
313 FEAT
314 211-Features:
315  EPRT
316  EPSV
317  MDTM
318  PASV
319  REST STREAM
320  SIZE
321  TVFS
322  UTF8
323 211 End
324 HELP
325 214-The following commands are recognized.
326  ABOR ACCT ALLO APPE CDUP CWD  DELE EPRT EPSV FEAT HELP LIST MDTM MKD
327  MODE NLST NOOP OPTS PASS PASV PORT PWD  QUIT REIN REST RETR RMD  RNFR
328  RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
329  XPWD XRMD
330 214 Help OK.
331 */
332 static void
333 handle_feat(unsigned status)
334 {
335         cmdio_write(status, "-Features:");
336         cmdio_write_raw(" EPSV\r\n"
337                         " PASV\r\n"
338                         " REST STREAM\r\n"
339                         " MDTM\r\n"
340                         " SIZE\r\n");
341         cmdio_write(status, " Ok");
342 }
343
344 /* Download commands */
345
346 static inline int
347 port_active(void)
348 {
349         return (G.port_addr != NULL);
350 }
351
352 static inline int
353 pasv_active(void)
354 {
355         return (G.pasv_listen_fd > STDOUT_FILENO);
356 }
357
358 static void
359 port_pasv_cleanup(void)
360 {
361         free(G.port_addr);
362         G.port_addr = NULL;
363         if (G.pasv_listen_fd > STDOUT_FILENO)
364                 close(G.pasv_listen_fd);
365         G.pasv_listen_fd = -1;
366 }
367
368 /* On error, emits error code to the peer */
369 static int
370 ftpdataio_get_pasv_fd(void)
371 {
372         int remote_fd;
373
374         remote_fd = accept(G.pasv_listen_fd, NULL, 0);
375
376         if (remote_fd < 0) {
377                 WRITE_ERR(FTP_BADSENDCONN);
378                 return remote_fd;
379         }
380
381         setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
382         return remote_fd;
383 }
384
385 /* Clears port/pasv data.
386  * This means we dont waste resources, for example, keeping
387  * PASV listening socket open when it is no longer needed.
388  * On error, emits error code to the peer (or exits).
389  * On success, emits p_status_msg to the peer.
390  */
391 static int
392 get_remote_transfer_fd(const char *p_status_msg)
393 {
394         int remote_fd;
395
396         if (pasv_active())
397                 /* On error, emits error code to the peer */
398                 remote_fd = ftpdataio_get_pasv_fd();
399         else
400                 /* Exits on error */
401                 remote_fd = xconnect_stream(G.port_addr);
402
403         port_pasv_cleanup();
404
405         if (remote_fd < 0)
406                 return remote_fd;
407
408         cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
409         return remote_fd;
410 }
411
412 /* If there were neither PASV nor PORT, emits error code to the peer */
413 static int
414 port_or_pasv_was_seen(void)
415 {
416         if (!pasv_active() && !port_active()) {
417                 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
418                 return 0;
419         }
420
421         return 1;
422 }
423
424 /* Exits on error */
425 static unsigned
426 bind_for_passive_mode(void)
427 {
428         int fd;
429         unsigned port;
430
431         port_pasv_cleanup();
432
433         G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
434         setsockopt_reuseaddr(fd);
435
436         set_nport(G.local_addr, 0);
437         xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
438         xlisten(fd, 1);
439         getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
440
441         port = get_nport(&G.local_addr->u.sa);
442         port = ntohs(port);
443         return port;
444 }
445
446 /* Exits on error */
447 static void
448 handle_pasv(void)
449 {
450         unsigned port;
451         char *addr, *response;
452
453         port = bind_for_passive_mode();
454
455         if (G.local_addr->u.sa.sa_family == AF_INET)
456                 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
457         else /* seen this in the wild done by other ftp servers: */
458                 addr = xstrdup("0.0.0.0");
459         replace_char(addr, '.', ',');
460
461         response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
462                         addr, (int)(port >> 8), (int)(port & 255));
463         free(addr);
464         cmdio_write_raw(response);
465         free(response);
466 }
467
468 /* Exits on error */
469 static void
470 handle_epsv(void)
471 {
472         unsigned port;
473         char *response;
474
475         port = bind_for_passive_mode();
476         response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
477         cmdio_write_raw(response);
478         free(response);
479 }
480
481 static void
482 handle_port(void)
483 {
484         unsigned port, port_hi;
485         char *raw, *comma;
486 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
487         socklen_t peer_ipv4_len;
488         struct sockaddr_in peer_ipv4;
489         struct in_addr port_ipv4_sin_addr;
490 #endif
491
492         port_pasv_cleanup();
493
494         raw = G.ftp_arg;
495
496         /* PORT command format makes sense only over IPv4 */
497         if (!raw
498 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
499          || G.local_addr->u.sa.sa_family != AF_INET
500 #endif
501         ) {
502  bail:
503                 WRITE_ERR(FTP_BADCMD);
504                 return;
505         }
506
507         comma = strrchr(raw, ',');
508         if (comma == NULL)
509                 goto bail;
510         *comma = '\0';
511         port = bb_strtou(&comma[1], NULL, 10);
512         if (errno || port > 0xff)
513                 goto bail;
514
515         comma = strrchr(raw, ',');
516         if (comma == NULL)
517                 goto bail;
518         *comma = '\0';
519         port_hi = bb_strtou(&comma[1], NULL, 10);
520         if (errno || port_hi > 0xff)
521                 goto bail;
522         port |= port_hi << 8;
523
524 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
525         replace_char(raw, ',', '.');
526
527         /* We are verifying that PORT's IP matches getpeername().
528          * Otherwise peer can make us open data connections
529          * to other hosts (security problem!)
530          * This code would be too simplistic:
531          * lsa = xdotted2sockaddr(raw, port);
532          * if (lsa == NULL) goto bail;
533          */
534         if (!inet_aton(raw, &port_ipv4_sin_addr))
535                 goto bail;
536         peer_ipv4_len = sizeof(peer_ipv4);
537         if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
538                 goto bail;
539         if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
540                 goto bail;
541
542         G.port_addr = xdotted2sockaddr(raw, port);
543 #else
544         G.port_addr = get_peer_lsa(STDIN_FILENO);
545         set_nport(G.port_addr, htons(port));
546 #endif
547         WRITE_OK(FTP_PORTOK);
548 }
549
550 static void
551 handle_rest(void)
552 {
553         /* When ftp_arg == NULL simply restart from beginning */
554         G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
555         WRITE_OK(FTP_RESTOK);
556 }
557
558 static void
559 handle_retr(void)
560 {
561         struct stat statbuf;
562         off_t bytes_transferred;
563         int remote_fd;
564         int local_file_fd;
565         off_t offset = G.restart_pos;
566         char *response;
567
568         G.restart_pos = 0;
569
570         if (!port_or_pasv_was_seen())
571                 return; /* port_or_pasv_was_seen emitted error response */
572
573         /* O_NONBLOCK is useful if file happens to be a device node */
574         local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
575         if (local_file_fd < 0) {
576                 WRITE_ERR(FTP_FILEFAIL);
577                 return;
578         }
579
580         if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
581                 /* Note - pretend open failed */
582                 WRITE_ERR(FTP_FILEFAIL);
583                 goto file_close_out;
584         }
585         G.local_file_fd = local_file_fd;
586
587         /* Now deactive O_NONBLOCK, otherwise we have a problem
588          * on DMAPI filesystems such as XFS DMAPI.
589          */
590         ndelay_off(local_file_fd);
591
592         /* Set the download offset (from REST) if any */
593         if (offset != 0)
594                 xlseek(local_file_fd, offset, SEEK_SET);
595
596         response = xasprintf(
597                 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
598                 G.ftp_arg, statbuf.st_size);
599         remote_fd = get_remote_transfer_fd(response);
600         free(response);
601         if (remote_fd < 0)
602                 goto file_close_out;
603
604         bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
605         close(remote_fd);
606         if (bytes_transferred < 0)
607                 WRITE_ERR(FTP_BADSENDFILE);
608         else
609                 WRITE_OK(FTP_TRANSFEROK);
610
611  file_close_out:
612         close(local_file_fd);
613         G.local_file_fd = 0;
614 }
615
616 /* List commands */
617
618 static int
619 popen_ls(const char *opt)
620 {
621         const char *argv[5];
622         struct fd_pair outfd;
623         pid_t pid;
624
625         argv[0] = "ftpd";
626         argv[1] = opt; /* "-l" or "-1" */
627 #if BB_MMU
628         argv[2] = "--";
629 #else
630         /* NOMMU ftpd ls helper chdirs to argv[2],
631          * preventing peer from seeing real root. */
632         argv[2] = xrealloc_getcwd_or_warn(NULL);
633 #endif
634         argv[3] = G.ftp_arg;
635         argv[4] = NULL;
636
637         /* Improve compatibility with non-RFC conforming FTP clients
638          * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
639          * See https://bugs.kde.org/show_bug.cgi?id=195578 */
640         if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
641          && G.ftp_arg && G.ftp_arg[0] == '-'
642         ) {
643                 const char *tmp = strchr(G.ftp_arg, ' ');
644                 if (tmp) /* skip the space */
645                         tmp++;
646                 argv[3] = tmp;
647         }
648
649         xpiped_pair(outfd);
650
651         /*fflush_all(); - so far we dont use stdio on output */
652         pid = BB_MMU ? xfork() : xvfork();
653         if (pid == 0) {
654                 /* child */
655 #if !BB_MMU
656                 /* On NOMMU, we want to execute a child - copy of ourself.
657                  * In chroot we usually can't do it. Thus we chdir
658                  * out of the chroot back to original root,
659                  * and (see later below) execute bb_busybox_exec_path
660                  * relative to current directory */
661                 if (fchdir(G.root_fd) != 0)
662                         _exit(127);
663                 /*close(G.root_fd); - close_on_exec_on() took care of this */
664 #endif
665                 /* NB: close _first_, then move fd! */
666                 close(outfd.rd);
667                 xmove_fd(outfd.wr, STDOUT_FILENO);
668                 /* Opening /dev/null in chroot is hard.
669                  * Just making sure STDIN_FILENO is opened
670                  * to something harmless. Paranoia,
671                  * ls won't read it anyway */
672                 close(STDIN_FILENO);
673                 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
674 #if BB_MMU
675                 /* memset(&G, 0, sizeof(G)); - ls_main does it */
676                 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
677 #else
678                 /* + 1: we must use relative path here if in chroot.
679                  * For example, execv("/proc/self/exe") will fail, since
680                  * it looks for "/proc/self/exe" _relative to chroot!_ */
681                 execv(bb_busybox_exec_path + 1, (char**) argv);
682                 _exit(127);
683 #endif
684         }
685
686         /* parent */
687         close(outfd.wr);
688 #if !BB_MMU
689         free((char*)argv[2]);
690 #endif
691         return outfd.rd;
692 }
693
694 enum {
695         USE_CTRL_CONN = 1,
696         LONG_LISTING = 2,
697 };
698
699 static void
700 handle_dir_common(int opts)
701 {
702         FILE *ls_fp;
703         char *line;
704         int ls_fd;
705
706         if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
707                 return; /* port_or_pasv_was_seen emitted error response */
708
709         /* -n prevents user/groupname display,
710          * which can be problematic in chroot */
711         ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
712         ls_fp = xfdopen_for_read(ls_fd);
713
714         if (opts & USE_CTRL_CONN) {
715                 /* STAT <filename> */
716                 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
717                 while (1) {
718                         line = xmalloc_fgetline(ls_fp);
719                         if (!line)
720                                 break;
721                         /* Hack: 0 results in no status at all */
722                         /* Note: it's ok that we don't prepend space,
723                          * ftp.kernel.org doesn't do that too */
724                         cmdio_write(0, line);
725                         free(line);
726                 }
727                 WRITE_OK(FTP_STATFILE_OK);
728         } else {
729                 /* LIST/NLST [<filename>] */
730                 int remote_fd = get_remote_transfer_fd(" Directory listing");
731                 if (remote_fd >= 0) {
732                         while (1) {
733                                 line = xmalloc_fgetline(ls_fp);
734                                 if (!line)
735                                         break;
736                                 /* I've seen clients complaining when they
737                                  * are fed with ls output with bare '\n'.
738                                  * Pity... that would be much simpler.
739                                  */
740 /* TODO: need to s/LF/NUL/g here */
741                                 xwrite_str(remote_fd, line);
742                                 xwrite(remote_fd, "\r\n", 2);
743                                 free(line);
744                         }
745                 }
746                 close(remote_fd);
747                 WRITE_OK(FTP_TRANSFEROK);
748         }
749         fclose(ls_fp); /* closes ls_fd too */
750 }
751 static void
752 handle_list(void)
753 {
754         handle_dir_common(LONG_LISTING);
755 }
756 static void
757 handle_nlst(void)
758 {
759         /* NLST returns list of names, "\r\n" terminated without regard
760          * to the current binary flag. Names may start with "/",
761          * then they represent full names (we don't produce such names),
762          * otherwise names are relative to current directory.
763          * Embedded "\n" are replaced by NULs. This is safe since names
764          * can never contain NUL.
765          */
766         handle_dir_common(0);
767 }
768 static void
769 handle_stat_file(void)
770 {
771         handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
772 }
773
774 /* This can be extended to handle MLST, as all info is available
775  * in struct stat for that:
776  * MLST file_name
777  * 250-Listing file_name
778  *  type=file;size=4161;modify=19970214165800; /dir/dir/file_name
779  * 250 End
780  * Nano-doc:
781  * MLST [<file or dir name, "." assumed if not given>]
782  * Returned name should be either the same as requested, or fully qualified.
783  * If there was no parameter, return "" or (preferred) fully-qualified name.
784  * Returned "facts" (case is not important):
785  *  size    - size in octets
786  *  modify  - last modification time
787  *  type    - entry type (file,dir,OS.unix=block)
788  *            (+ cdir and pdir types for MLSD)
789  *  unique  - unique id of file/directory (inode#)
790  *  perm    -
791  *      a: can be appended to (APPE)
792  *      d: can be deleted (RMD/DELE)
793  *      f: can be renamed (RNFR)
794  *      r: can be read (RETR)
795  *      w: can be written (STOR)
796  *      e: can CWD into this dir
797  *      l: this dir can be listed (dir only!)
798  *      c: can create files in this dir
799  *      m: can create dirs in this dir (MKD)
800  *      p: can delete files in this dir
801  *  UNIX.mode - unix file mode
802  */
803 static void
804 handle_size_or_mdtm(int need_size)
805 {
806         struct stat statbuf;
807         struct tm broken_out;
808         char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
809                 | sizeof("NNN YYYYMMDDhhmmss\r\n")
810         ];
811
812         if (!G.ftp_arg
813          || stat(G.ftp_arg, &statbuf) != 0
814          || !S_ISREG(statbuf.st_mode)
815         ) {
816                 WRITE_ERR(FTP_FILEFAIL);
817                 return;
818         }
819         if (need_size) {
820                 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
821         } else {
822                 gmtime_r(&statbuf.st_mtime, &broken_out);
823                 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
824                         broken_out.tm_year + 1900,
825                         broken_out.tm_mon,
826                         broken_out.tm_mday,
827                         broken_out.tm_hour,
828                         broken_out.tm_min,
829                         broken_out.tm_sec);
830         }
831         cmdio_write_raw(buf);
832 }
833
834 /* Upload commands */
835
836 #if ENABLE_FEATURE_FTP_WRITE
837 static void
838 handle_mkd(void)
839 {
840         if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
841                 WRITE_ERR(FTP_FILEFAIL);
842                 return;
843         }
844         WRITE_OK(FTP_MKDIROK);
845 }
846
847 static void
848 handle_rmd(void)
849 {
850         if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
851                 WRITE_ERR(FTP_FILEFAIL);
852                 return;
853         }
854         WRITE_OK(FTP_RMDIROK);
855 }
856
857 static void
858 handle_dele(void)
859 {
860         if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
861                 WRITE_ERR(FTP_FILEFAIL);
862                 return;
863         }
864         WRITE_OK(FTP_DELEOK);
865 }
866
867 static void
868 handle_rnfr(void)
869 {
870         free(G.rnfr_filename);
871         G.rnfr_filename = xstrdup(G.ftp_arg);
872         WRITE_OK(FTP_RNFROK);
873 }
874
875 static void
876 handle_rnto(void)
877 {
878         int retval;
879
880         /* If we didn't get a RNFR, throw a wobbly */
881         if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
882                 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
883                 return;
884         }
885
886         retval = rename(G.rnfr_filename, G.ftp_arg);
887         free(G.rnfr_filename);
888         G.rnfr_filename = NULL;
889
890         if (retval) {
891                 WRITE_ERR(FTP_FILEFAIL);
892                 return;
893         }
894         WRITE_OK(FTP_RENAMEOK);
895 }
896
897 static void
898 handle_upload_common(int is_append, int is_unique)
899 {
900         struct stat statbuf;
901         char *tempname;
902         off_t bytes_transferred;
903         off_t offset;
904         int local_file_fd;
905         int remote_fd;
906
907         offset = G.restart_pos;
908         G.restart_pos = 0;
909
910         if (!port_or_pasv_was_seen())
911                 return; /* port_or_pasv_was_seen emitted error response */
912
913         tempname = NULL;
914         local_file_fd = -1;
915         if (is_unique) {
916                 tempname = xstrdup(" FILE: uniq.XXXXXX");
917                 local_file_fd = mkstemp(tempname + 7);
918         } else if (G.ftp_arg) {
919                 int flags = O_WRONLY | O_CREAT | O_TRUNC;
920                 if (is_append)
921                         flags = O_WRONLY | O_CREAT | O_APPEND;
922                 if (offset)
923                         flags = O_WRONLY | O_CREAT;
924                 local_file_fd = open(G.ftp_arg, flags, 0666);
925         }
926
927         if (local_file_fd < 0
928          || fstat(local_file_fd, &statbuf) != 0
929          || !S_ISREG(statbuf.st_mode)
930         ) {
931                 WRITE_ERR(FTP_UPLOADFAIL);
932                 if (local_file_fd >= 0)
933                         goto close_local_and_bail;
934                 return;
935         }
936         G.local_file_fd = local_file_fd;
937
938         if (offset)
939                 xlseek(local_file_fd, offset, SEEK_SET);
940
941         remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
942         free(tempname);
943
944         if (remote_fd < 0)
945                 goto close_local_and_bail;
946
947         bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
948         close(remote_fd);
949         if (bytes_transferred < 0)
950                 WRITE_ERR(FTP_BADSENDFILE);
951         else
952                 WRITE_OK(FTP_TRANSFEROK);
953
954  close_local_and_bail:
955         close(local_file_fd);
956         G.local_file_fd = 0;
957 }
958
959 static void
960 handle_stor(void)
961 {
962         handle_upload_common(0, 0);
963 }
964
965 static void
966 handle_appe(void)
967 {
968         G.restart_pos = 0;
969         handle_upload_common(1, 0);
970 }
971
972 static void
973 handle_stou(void)
974 {
975         G.restart_pos = 0;
976         handle_upload_common(0, 1);
977 }
978 #endif /* ENABLE_FEATURE_FTP_WRITE */
979
980 static uint32_t
981 cmdio_get_cmd_and_arg(void)
982 {
983         int len;
984         uint32_t cmdval;
985         char *cmd;
986
987         alarm(G.timeout);
988
989         free(G.ftp_cmd);
990         {
991                 /* Paranoia. Peer may send 1 gigabyte long cmd... */
992                 /* Using separate len_on_stk instead of len optimizes
993                  * code size (allows len to be in CPU register) */
994                 size_t len_on_stk = 8 * 1024;
995                 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
996                 if (!cmd)
997                         exit(0);
998                 len = len_on_stk;
999         }
1000
1001         /* De-escape telnet: 0xff,0xff => 0xff */
1002         /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1003          * data transfer, and may be preceded by telnet's "Interrupt Process"
1004          * code (two-byte sequence 255,244) and then by telnet "Synch" code
1005          * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1006          * and may generate SIGURG on our side. See RFC854).
1007          * So far we don't support that (may install SIGURG handler if we'd want to),
1008          * but we need to at least remove 255,xxx pairs. lftp sends those. */
1009         /* Then de-escape FTP: NUL => '\n' */
1010         /* Testing for \xff:
1011          * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1012          * Try to get it:            ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1013          * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1014          * Testing for embedded LF:
1015          * LF_HERE=`echo -ne "LF\nHERE"`
1016          * echo Hello >"$LF_HERE"
1017          * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1018          */
1019         {
1020                 int dst, src;
1021
1022                 /* Strip "\r\n" if it is there */
1023                 if (len != 0 && cmd[len - 1] == '\n') {
1024                         len--;
1025                         if (len != 0 && cmd[len - 1] == '\r')
1026                                 len--;
1027                         cmd[len] = '\0';
1028                 }
1029                 src = strchrnul(cmd, 0xff) - cmd;
1030                 /* 99,99% there are neither NULs nor 255s and src == len */
1031                 if (src < len) {
1032                         dst = src;
1033                         do {
1034                                 if ((unsigned char)(cmd[src]) == 255) {
1035                                         src++;
1036                                         /* 255,xxx - skip 255 */
1037                                         if ((unsigned char)(cmd[src]) != 255) {
1038                                                 /* 255,!255 - skip both */
1039                                                 src++;
1040                                                 continue;
1041                                         }
1042                                         /* 255,255 - retain one 255 */
1043                                 }
1044                                 /* NUL => '\n' */
1045                                 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1046                                 src++;
1047                         } while (src < len);
1048                         cmd[dst] = '\0';
1049                 }
1050         }
1051
1052         if (G.verbose > 1)
1053                 verbose_log(cmd);
1054
1055         G.ftp_arg = strchr(cmd, ' ');
1056         if (G.ftp_arg != NULL)
1057                 *G.ftp_arg++ = '\0';
1058
1059         /* Uppercase and pack into uint32_t first word of the command */
1060         cmdval = 0;
1061         while (*cmd)
1062                 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1063
1064         return cmdval;
1065 }
1066
1067 #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1068 #define mk_const3(a,b,c)    ((a * 0x100 + b) * 0x100 + c)
1069 enum {
1070         const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1071         const_APPE = mk_const4('A', 'P', 'P', 'E'),
1072         const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1073         const_CWD  = mk_const3('C', 'W', 'D'),
1074         const_DELE = mk_const4('D', 'E', 'L', 'E'),
1075         const_EPSV = mk_const4('E', 'P', 'S', 'V'),
1076         const_FEAT = mk_const4('F', 'E', 'A', 'T'),
1077         const_HELP = mk_const4('H', 'E', 'L', 'P'),
1078         const_LIST = mk_const4('L', 'I', 'S', 'T'),
1079         const_MDTM = mk_const4('M', 'D', 'T', 'M'),
1080         const_MKD  = mk_const3('M', 'K', 'D'),
1081         const_MODE = mk_const4('M', 'O', 'D', 'E'),
1082         const_NLST = mk_const4('N', 'L', 'S', 'T'),
1083         const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1084         const_PASS = mk_const4('P', 'A', 'S', 'S'),
1085         const_PASV = mk_const4('P', 'A', 'S', 'V'),
1086         const_PORT = mk_const4('P', 'O', 'R', 'T'),
1087         const_PWD  = mk_const3('P', 'W', 'D'),
1088         const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1089         const_REST = mk_const4('R', 'E', 'S', 'T'),
1090         const_RETR = mk_const4('R', 'E', 'T', 'R'),
1091         const_RMD  = mk_const3('R', 'M', 'D'),
1092         const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1093         const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1094         const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1095         const_STAT = mk_const4('S', 'T', 'A', 'T'),
1096         const_STOR = mk_const4('S', 'T', 'O', 'R'),
1097         const_STOU = mk_const4('S', 'T', 'O', 'U'),
1098         const_STRU = mk_const4('S', 'T', 'R', 'U'),
1099         const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1100         const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1101         const_USER = mk_const4('U', 'S', 'E', 'R'),
1102
1103 #if !BB_MMU
1104         OPT_l = (1 << 0),
1105         OPT_1 = (1 << 1),
1106 #endif
1107         OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
1108         OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
1109         OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
1110 };
1111
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
1118 {
1119         unsigned abs_timeout;
1120         unsigned verbose_S;
1121         smallint opts;
1122
1123         INIT_G();
1124
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, "l1vS" 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 -n? It prevents user/group resolution, which may not work in chroot anyway */
1136 /* TODO: pass -A? It shows dot files */
1137 /* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
1138                 xchdir(argv[2]);
1139                 argv[2] = (char*)"--";
1140                 /* memset(&G, 0, sizeof(G)); - ls_main does it */
1141                 return ls_main(argc, argv);
1142         }
1143 #endif
1144         if (G.verbose < verbose_S)
1145                 G.verbose = verbose_S;
1146         if (abs_timeout | G.timeout) {
1147                 if (abs_timeout == 0)
1148                         abs_timeout = INT_MAX;
1149                 G.end_time = monotonic_sec() + abs_timeout;
1150                 if (G.timeout > abs_timeout)
1151                         G.timeout = abs_timeout;
1152         }
1153         strcpy(G.msg_ok  + 4, MSG_OK );
1154         strcpy(G.msg_err + 4, MSG_ERR);
1155
1156         G.local_addr = get_sock_lsa(STDIN_FILENO);
1157         if (!G.local_addr) {
1158                 /* This is confusing:
1159                  * bb_error_msg_and_die("stdin is not a socket");
1160                  * Better: */
1161                 bb_show_usage();
1162                 /* Help text says that ftpd must be used as inetd service,
1163                  * which is by far the most usual cause of get_sock_lsa
1164                  * failure */
1165         }
1166
1167         if (!(opts & OPT_v))
1168                 logmode = LOGMODE_NONE;
1169         if (opts & OPT_S) {
1170                 /* LOG_NDELAY is needed since we may chroot later */
1171                 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1172                 logmode |= LOGMODE_SYSLOG;
1173         }
1174         if (logmode)
1175                 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1176
1177 #if !BB_MMU
1178         G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1179         close_on_exec_on(G.root_fd);
1180 #endif
1181
1182         if (argv[optind]) {
1183                 xchdir(argv[optind]);
1184                 chroot(".");
1185         }
1186
1187         //umask(077); - admin can set umask before starting us
1188
1189         /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
1190         signal(SIGPIPE, SIG_IGN);
1191
1192         /* Set up options on the command socket (do we need these all? why?) */
1193         setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1194         setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
1195         /* Telnet protocol over command link may send "urgent" data,
1196          * we prefer it to be received in the "normal" data stream: */
1197         setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1198
1199         WRITE_OK(FTP_GREET);
1200         signal(SIGALRM, timeout_handler);
1201
1202 #ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
1203         {
1204                 smallint user_was_specified = 0;
1205                 while (1) {
1206                         uint32_t cmdval = cmdio_get_cmd_and_arg();
1207
1208                         if (cmdval == const_USER) {
1209                                 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
1210                                         cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
1211                                 else {
1212                                         user_was_specified = 1;
1213                                         cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
1214                                 }
1215                         } else if (cmdval == const_PASS) {
1216                                 if (user_was_specified)
1217                                         break;
1218                                 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
1219                         } else if (cmdval == const_QUIT) {
1220                                 WRITE_OK(FTP_GOODBYE);
1221                                 return 0;
1222                         } else {
1223                                 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1224                         }
1225                 }
1226         }
1227         WRITE_OK(FTP_LOGINOK);
1228 #endif
1229
1230         /* RFC-959 Section 5.1
1231          * The following commands and options MUST be supported by every
1232          * server-FTP and user-FTP, except in cases where the underlying
1233          * file system or operating system does not allow or support
1234          * a particular command.
1235          * Type: ASCII Non-print, IMAGE, LOCAL 8
1236          * Mode: Stream
1237          * Structure: File, Record*
1238          * (Record structure is REQUIRED only for hosts whose file
1239          *  systems support record structure).
1240          * Commands:
1241          * USER, PASS, ACCT, [bbox: ACCT not supported]
1242          * PORT, PASV,
1243          * TYPE, MODE, STRU,
1244          * RETR, STOR, APPE,
1245          * RNFR, RNTO, DELE,
1246          * CWD,  CDUP, RMD,  MKD,  PWD,
1247          * LIST, NLST,
1248          * SYST, STAT,
1249          * HELP, NOOP, QUIT.
1250          */
1251         /* ACCOUNT (ACCT)
1252          * "The argument field is a Telnet string identifying the user's account.
1253          * The command is not necessarily related to the USER command, as some
1254          * sites may require an account for login and others only for specific
1255          * access, such as storing files. In the latter case the command may
1256          * arrive at any time.
1257          * There are reply codes to differentiate these cases for the automation:
1258          * when account information is required for login, the response to
1259          * a successful PASSword command is reply code 332. On the other hand,
1260          * if account information is NOT required for login, the reply to
1261          * a successful PASSword command is 230; and if the account information
1262          * is needed for a command issued later in the dialogue, the server
1263          * should return a 332 or 532 reply depending on whether it stores
1264          * (pending receipt of the ACCounT command) or discards the command,
1265          * respectively."
1266          */
1267
1268         while (1) {
1269                 uint32_t cmdval = cmdio_get_cmd_and_arg();
1270
1271                 if (cmdval == const_QUIT) {
1272                         WRITE_OK(FTP_GOODBYE);
1273                         return 0;
1274                 }
1275                 else if (cmdval == const_USER)
1276                         /* This would mean "ok, now give me PASS". */
1277                         /*WRITE_OK(FTP_GIVEPWORD);*/
1278                         /* vsftpd can be configured to not require that,
1279                          * and this also saves one roundtrip:
1280                          */
1281                         WRITE_OK(FTP_LOGINOK);
1282                 else if (cmdval == const_PASS)
1283                         WRITE_OK(FTP_LOGINOK);
1284                 else if (cmdval == const_NOOP)
1285                         WRITE_OK(FTP_NOOPOK);
1286                 else if (cmdval == const_TYPE)
1287                         WRITE_OK(FTP_TYPEOK);
1288                 else if (cmdval == const_STRU)
1289                         WRITE_OK(FTP_STRUOK);
1290                 else if (cmdval == const_MODE)
1291                         WRITE_OK(FTP_MODEOK);
1292                 else if (cmdval == const_ALLO)
1293                         WRITE_OK(FTP_ALLOOK);
1294                 else if (cmdval == const_SYST)
1295                         cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1296                 else if (cmdval == const_PWD)
1297                         handle_pwd();
1298                 else if (cmdval == const_CWD)
1299                         handle_cwd();
1300                 else if (cmdval == const_CDUP) /* cd .. */
1301                         handle_cdup();
1302                 /* HELP is nearly useless, but we can reuse FEAT for it */
1303                 /* lftp uses FEAT */
1304                 else if (cmdval == const_HELP || cmdval == const_FEAT)
1305                         handle_feat(cmdval == const_HELP
1306                                         ? STRNUM32(FTP_HELP)
1307                                         : STRNUM32(FTP_STATOK)
1308                         );
1309                 else if (cmdval == const_LIST) /* ls -l */
1310                         handle_list();
1311                 else if (cmdval == const_NLST) /* "name list", bare ls */
1312                         handle_nlst();
1313                 /* SIZE is crucial for wget's download indicator etc */
1314                 /* Mozilla, lftp use MDTM (presumably for caching) */
1315                 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1316                         handle_size_or_mdtm(cmdval == const_SIZE);
1317                 else if (cmdval == const_STAT) {
1318                         if (G.ftp_arg == NULL)
1319                                 handle_stat();
1320                         else
1321                                 handle_stat_file();
1322                 }
1323                 else if (cmdval == const_PASV)
1324                         handle_pasv();
1325                 else if (cmdval == const_EPSV)
1326                         handle_epsv();
1327                 else if (cmdval == const_RETR)
1328                         handle_retr();
1329                 else if (cmdval == const_PORT)
1330                         handle_port();
1331                 else if (cmdval == const_REST)
1332                         handle_rest();
1333 #if ENABLE_FEATURE_FTP_WRITE
1334                 else if (opts & OPT_w) {
1335                         if (cmdval == const_STOR)
1336                                 handle_stor();
1337                         else if (cmdval == const_MKD)
1338                                 handle_mkd();
1339                         else if (cmdval == const_RMD)
1340                                 handle_rmd();
1341                         else if (cmdval == const_DELE)
1342                                 handle_dele();
1343                         else if (cmdval == const_RNFR) /* "rename from" */
1344                                 handle_rnfr();
1345                         else if (cmdval == const_RNTO) /* "rename to" */
1346                                 handle_rnto();
1347                         else if (cmdval == const_APPE)
1348                                 handle_appe();
1349                         else if (cmdval == const_STOU) /* "store unique" */
1350                                 handle_stou();
1351                         else
1352                                 goto bad_cmd;
1353                 }
1354 #endif
1355 #if 0
1356                 else if (cmdval == const_STOR
1357                  || cmdval == const_MKD
1358                  || cmdval == const_RMD
1359                  || cmdval == const_DELE
1360                  || cmdval == const_RNFR
1361                  || cmdval == const_RNTO
1362                  || cmdval == const_APPE
1363                  || cmdval == const_STOU
1364                 ) {
1365                         cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1366                 }
1367 #endif
1368                 else {
1369                         /* Which unsupported commands were seen in the wild?
1370                          * (doesn't necessarily mean "we must support them")
1371                          * foo 1.2.3: XXXX - comment
1372                          */
1373 #if ENABLE_FEATURE_FTP_WRITE
1374  bad_cmd:
1375 #endif
1376                         cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
1377                 }
1378         }
1379 }