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