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