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