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