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