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