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