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