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