ftpd/ls: show directories first
[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:        IF_FEATURE_FTPD_AUTHENTICATION(
65 //usage:     "\n        -a USER Enable 'anonymous' login and map it to USER"
66 //usage:        )
67 //usage:     "\n        -v      Log errors to stderr. -vv: verbose log"
68 //usage:     "\n        -S      Log errors to syslog. -SS: verbose log"
69 //usage:     "\n        -t,-T N Idle and absolute timeout"
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(/*argc_unused*/ 0, (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 int ftpd_main(int argc UNUSED_PARAM, char **argv)
1156 {
1157 #if ENABLE_FEATURE_FTPD_AUTHENTICATION
1158         struct passwd *pw = NULL;
1159         char *anon_opt = NULL;
1160 #endif
1161         unsigned abs_timeout;
1162         unsigned verbose_S;
1163         smallint opts;
1164
1165         INIT_G();
1166
1167         abs_timeout = 1 * 60 * 60;
1168         verbose_S = 0;
1169         G.timeout = 2 * 60;
1170         opt_complementary = "vv:SS";
1171 #if BB_MMU
1172         opts = getopt32(argv,    "vS"
1173                 IF_FEATURE_FTPD_WRITE("w") "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:"),
1174                 &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
1175                 &G.verbose, &verbose_S);
1176 #else
1177         opts = getopt32(argv, "l1AvS"
1178                 IF_FEATURE_FTPD_WRITE("w") "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:"),
1179                 &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
1180                 &G.verbose, &verbose_S);
1181         if (opts & (OPT_l|OPT_1)) {
1182                 /* Our secret backdoor to ls */
1183                 if (fchdir(3) != 0)
1184                         _exit(127);
1185                 /* memset(&G, 0, sizeof(G)); - ls_main does it */
1186                 return ls_main(/*argc_unused*/ 0, argv);
1187         }
1188 #endif
1189         if (G.verbose < verbose_S)
1190                 G.verbose = verbose_S;
1191         if (abs_timeout | G.timeout) {
1192                 if (abs_timeout == 0)
1193                         abs_timeout = INT_MAX;
1194                 G.end_time = monotonic_sec() + abs_timeout;
1195                 if (G.timeout > abs_timeout)
1196                         G.timeout = abs_timeout;
1197         }
1198         strcpy(G.msg_ok  + 4, MSG_OK );
1199         strcpy(G.msg_err + 4, MSG_ERR);
1200
1201         G.local_addr = get_sock_lsa(STDIN_FILENO);
1202         if (!G.local_addr) {
1203                 /* This is confusing:
1204                  * bb_error_msg_and_die("stdin is not a socket");
1205                  * Better: */
1206                 bb_show_usage();
1207                 /* Help text says that ftpd must be used as inetd service,
1208                  * which is by far the most usual cause of get_sock_lsa
1209                  * failure */
1210         }
1211
1212         if (!(opts & OPT_v))
1213                 logmode = LOGMODE_NONE;
1214         if (opts & OPT_S) {
1215                 /* LOG_NDELAY is needed since we may chroot later */
1216                 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1217                 logmode |= LOGMODE_SYSLOG;
1218         }
1219         if (logmode)
1220                 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1221
1222         //umask(077); - admin can set umask before starting us
1223
1224         /* Signals */
1225         bb_signals(0
1226                 /* We'll always take EPIPE rather than a rude signal, thanks */
1227                 + (1 << SIGPIPE)
1228                 /* LIST command spawns chilren. Prevent zombies */
1229                 + (1 << SIGCHLD)
1230                 , SIG_IGN);
1231
1232         /* Set up options on the command socket (do we need these all? why?) */
1233         setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
1234         setsockopt_keepalive(STDIN_FILENO);
1235         /* Telnet protocol over command link may send "urgent" data,
1236          * we prefer it to be received in the "normal" data stream: */
1237         setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
1238
1239         WRITE_OK(FTP_GREET);
1240         signal(SIGALRM, timeout_handler);
1241
1242 #if ENABLE_FEATURE_FTPD_AUTHENTICATION
1243         while (1) {
1244                 uint32_t cmdval = cmdio_get_cmd_and_arg();
1245                 if (cmdval == const_USER) {
1246                         if (anon_opt && strcmp(G.ftp_arg, "anonymous") == 0) {
1247                                 pw = getpwnam(anon_opt);
1248                                 if (pw)
1249                                         break; /* does not even ask for password */
1250                         }
1251                         pw = getpwnam(G.ftp_arg);
1252                         cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify password\r\n");
1253                 } else if (cmdval == const_PASS) {
1254                         if (check_password(pw, G.ftp_arg) > 0) {
1255                                 break;  /* login success */
1256                         }
1257                         cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
1258                         pw = NULL;
1259                 } else if (cmdval == const_QUIT) {
1260                         WRITE_OK(FTP_GOODBYE);
1261                         return 0;
1262                 } else {
1263                         cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1264                 }
1265         }
1266         WRITE_OK(FTP_LOGINOK);
1267 #endif
1268
1269         /* Do this after auth, else /etc/passwd is not accessible */
1270 #if !BB_MMU
1271         G.root_fd = -1;
1272 #endif
1273         argv += optind;
1274         if (argv[0]) {
1275                 const char *basedir = argv[0];
1276 #if !BB_MMU
1277                 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1278                 close_on_exec_on(G.root_fd);
1279 #endif
1280                 if (chroot(basedir) == 0)
1281                         basedir = "/";
1282 #if !BB_MMU
1283                 else {
1284                         close(G.root_fd);
1285                         G.root_fd = -1;
1286                 }
1287 #endif
1288                 /*
1289                  * If chroot failed, assume that we aren't root,
1290                  * and at least chdir to the specified DIR
1291                  * (older versions were dying with error message).
1292                  * If chroot worked, move current dir to new "/":
1293                  */
1294                 xchdir(basedir);
1295         }
1296
1297 #if ENABLE_FEATURE_FTPD_AUTHENTICATION
1298         change_identity(pw);
1299 #endif
1300
1301         /* RFC-959 Section 5.1
1302          * The following commands and options MUST be supported by every
1303          * server-FTP and user-FTP, except in cases where the underlying
1304          * file system or operating system does not allow or support
1305          * a particular command.
1306          * Type: ASCII Non-print, IMAGE, LOCAL 8
1307          * Mode: Stream
1308          * Structure: File, Record*
1309          * (Record structure is REQUIRED only for hosts whose file
1310          *  systems support record structure).
1311          * Commands:
1312          * USER, PASS, ACCT, [bbox: ACCT not supported]
1313          * PORT, PASV,
1314          * TYPE, MODE, STRU,
1315          * RETR, STOR, APPE,
1316          * RNFR, RNTO, DELE,
1317          * CWD,  CDUP, RMD,  MKD,  PWD,
1318          * LIST, NLST,
1319          * SYST, STAT,
1320          * HELP, NOOP, QUIT.
1321          */
1322         /* ACCOUNT (ACCT)
1323          * "The argument field is a Telnet string identifying the user's account.
1324          * The command is not necessarily related to the USER command, as some
1325          * sites may require an account for login and others only for specific
1326          * access, such as storing files. In the latter case the command may
1327          * arrive at any time.
1328          * There are reply codes to differentiate these cases for the automation:
1329          * when account information is required for login, the response to
1330          * a successful PASSword command is reply code 332. On the other hand,
1331          * if account information is NOT required for login, the reply to
1332          * a successful PASSword command is 230; and if the account information
1333          * is needed for a command issued later in the dialogue, the server
1334          * should return a 332 or 532 reply depending on whether it stores
1335          * (pending receipt of the ACCounT command) or discards the command,
1336          * respectively."
1337          */
1338
1339         while (1) {
1340                 uint32_t cmdval = cmdio_get_cmd_and_arg();
1341
1342                 if (cmdval == const_QUIT) {
1343                         WRITE_OK(FTP_GOODBYE);
1344                         return 0;
1345                 }
1346                 else if (cmdval == const_USER)
1347                         /* This would mean "ok, now give me PASS". */
1348                         /*WRITE_OK(FTP_GIVEPWORD);*/
1349                         /* vsftpd can be configured to not require that,
1350                          * and this also saves one roundtrip:
1351                          */
1352                         WRITE_OK(FTP_LOGINOK);
1353                 else if (cmdval == const_PASS)
1354                         WRITE_OK(FTP_LOGINOK);
1355                 else if (cmdval == const_NOOP)
1356                         WRITE_OK(FTP_NOOPOK);
1357                 else if (cmdval == const_TYPE)
1358                         WRITE_OK(FTP_TYPEOK);
1359                 else if (cmdval == const_STRU)
1360                         WRITE_OK(FTP_STRUOK);
1361                 else if (cmdval == const_MODE)
1362                         WRITE_OK(FTP_MODEOK);
1363                 else if (cmdval == const_ALLO)
1364                         WRITE_OK(FTP_ALLOOK);
1365                 else if (cmdval == const_SYST)
1366                         cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1367                 else if (cmdval == const_PWD || cmdval == const_XPWD)
1368                         handle_pwd();
1369                 else if (cmdval == const_CWD)
1370                         handle_cwd();
1371                 else if (cmdval == const_CDUP) /* cd .. */
1372                         handle_cdup();
1373                 /* HELP is nearly useless, but we can reuse FEAT for it */
1374                 /* lftp uses FEAT */
1375                 else if (cmdval == const_HELP || cmdval == const_FEAT)
1376                         handle_feat(cmdval == const_HELP
1377                                         ? STRNUM32(FTP_HELP)
1378                                         : STRNUM32(FTP_STATOK)
1379                         );
1380                 else if (cmdval == const_LIST) /* ls -l */
1381                         handle_list();
1382                 else if (cmdval == const_NLST) /* "name list", bare ls */
1383                         handle_nlst();
1384                 /* SIZE is crucial for wget's download indicator etc */
1385                 /* Mozilla, lftp use MDTM (presumably for caching) */
1386                 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1387                         handle_size_or_mdtm(cmdval == const_SIZE);
1388                 else if (cmdval == const_STAT) {
1389                         if (G.ftp_arg == NULL)
1390                                 handle_stat();
1391                         else
1392                                 handle_stat_file();
1393                 }
1394                 else if (cmdval == const_PASV)
1395                         handle_pasv();
1396                 else if (cmdval == const_EPSV)
1397                         handle_epsv();
1398                 else if (cmdval == const_RETR)
1399                         handle_retr();
1400                 else if (cmdval == const_PORT)
1401                         handle_port();
1402                 else if (cmdval == const_REST)
1403                         handle_rest();
1404 #if ENABLE_FEATURE_FTPD_WRITE
1405                 else if (opts & OPT_w) {
1406                         if (cmdval == const_STOR)
1407                                 handle_stor();
1408                         else if (cmdval == const_MKD)
1409                                 handle_mkd();
1410                         else if (cmdval == const_RMD)
1411                                 handle_rmd();
1412                         else if (cmdval == const_DELE)
1413                                 handle_dele();
1414                         else if (cmdval == const_RNFR) /* "rename from" */
1415                                 handle_rnfr();
1416                         else if (cmdval == const_RNTO) /* "rename to" */
1417                                 handle_rnto();
1418                         else if (cmdval == const_APPE)
1419                                 handle_appe();
1420                         else if (cmdval == const_STOU) /* "store unique" */
1421                                 handle_stou();
1422                         else
1423                                 goto bad_cmd;
1424                 }
1425 #endif
1426 #if 0
1427                 else if (cmdval == const_STOR
1428                  || cmdval == const_MKD
1429                  || cmdval == const_RMD
1430                  || cmdval == const_DELE
1431                  || cmdval == const_RNFR
1432                  || cmdval == const_RNTO
1433                  || cmdval == const_APPE
1434                  || cmdval == const_STOU
1435                 ) {
1436                         cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1437                 }
1438 #endif
1439                 else {
1440                         /* Which unsupported commands were seen in the wild?
1441                          * (doesn't necessarily mean "we must support them")
1442                          * foo 1.2.3: XXXX - comment
1443                          */
1444 #if ENABLE_FEATURE_FTPD_WRITE
1445  bad_cmd:
1446 #endif
1447                         cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
1448                 }
1449         }
1450 }