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