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