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