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