ftpd: fix the bug where >2GB file ops report errors;
[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 /* TODO: implement FEAT. Example:
272 # nc -vvv ftp.kernel.org 21
273 ftp.kernel.org (130.239.17.4:21) open
274 220 Welcome to ftp.kernel.org.
275 FEAT
276 211-Features:
277  EPRT
278  EPSV
279  MDTM
280  PASV
281  REST STREAM
282  SIZE
283  TVFS
284  UTF8
285 211 End
286 HELP
287 214-The following commands are recognized.
288  ABOR ACCT ALLO APPE CDUP CWD  DELE EPRT EPSV FEAT HELP LIST MDTM MKD
289  MODE NLST NOOP OPTS PASS PASV PORT PWD  QUIT REIN REST RETR RMD  RNFR
290  RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
291  XPWD XRMD
292 214 Help OK.
293 */
294 static void
295 handle_help(void)
296 {
297         cmdio_write_raw(STR(FTP_HELP)"-Commands:\r\n"
298                         " ALLO CDUP CWD EPSV HELP LIST\r\n"
299                         " MODE NLST NOOP PASS PASV PORT PWD QUIT\r\n"
300                         " REST RETR SIZE STAT STRU SYST TYPE USER\r\n"
301 #if ENABLE_FEATURE_FTP_WRITE
302                         " APPE DELE MKD RMD RNFR RNTO STOR STOU\r\n"
303 #endif
304                         STR(FTP_HELP)" Ok\r\n");
305 }
306
307 /* Download commands */
308
309 static inline int
310 port_active(void)
311 {
312         return (G.port_addr != NULL);
313 }
314
315 static inline int
316 pasv_active(void)
317 {
318         return (G.pasv_listen_fd > STDOUT_FILENO);
319 }
320
321 static void
322 port_pasv_cleanup(void)
323 {
324         free(G.port_addr);
325         G.port_addr = NULL;
326         if (G.pasv_listen_fd > STDOUT_FILENO)
327                 close(G.pasv_listen_fd);
328         G.pasv_listen_fd = -1;
329 }
330
331 /* On error, emits error code to the peer */
332 static int
333 ftpdataio_get_pasv_fd(void)
334 {
335         int remote_fd;
336
337         remote_fd = accept(G.pasv_listen_fd, NULL, 0);
338
339         if (remote_fd < 0) {
340                 cmdio_write_error(FTP_BADSENDCONN);
341                 return remote_fd;
342         }
343
344         setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
345         return remote_fd;
346 }
347
348 /* Clears port/pasv data.
349  * This means we dont waste resources, for example, keeping
350  * PASV listening socket open when it is no longer needed.
351  * On error, emits error code to the peer (or exits).
352  * On success, emits p_status_msg to the peer.
353  */
354 static int
355 get_remote_transfer_fd(const char *p_status_msg)
356 {
357         int remote_fd;
358
359         if (pasv_active())
360                 /* On error, emits error code to the peer */
361                 remote_fd = ftpdataio_get_pasv_fd();
362         else
363                 /* Exits on error */
364                 remote_fd = xconnect_stream(G.port_addr);
365
366         port_pasv_cleanup();
367
368         if (remote_fd < 0)
369                 return remote_fd;
370
371         cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
372         return remote_fd;
373 }
374
375 /* If there were neither PASV nor PORT, emits error code to the peer */
376 static int
377 port_or_pasv_was_seen(void)
378 {
379         if (!pasv_active() && !port_active()) {
380                 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT or PASV first\r\n");
381                 return 0;
382         }
383
384         return 1;
385 }
386
387 /* Exits on error */
388 static unsigned
389 bind_for_passive_mode(void)
390 {
391         int fd;
392         unsigned port;
393
394         port_pasv_cleanup();
395
396         G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
397         setsockopt_reuseaddr(fd);
398
399         set_nport(G.local_addr, 0);
400         xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
401         xlisten(fd, 1);
402         getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
403
404         port = get_nport(&G.local_addr->u.sa);
405         port = ntohs(port);
406         return port;
407 }
408
409 /* Exits on error */
410 static void
411 handle_pasv(void)
412 {
413         unsigned port;
414         char *addr, *response;
415
416         port = bind_for_passive_mode();
417
418         if (G.local_addr->u.sa.sa_family == AF_INET)
419                 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
420         else /* seen this in the wild done by other ftp servers: */
421                 addr = xstrdup("0.0.0.0");
422         replace_char(addr, '.', ',');
423
424         response = xasprintf(STR(FTP_PASVOK)" Entering Passive Mode (%s,%u,%u)\r\n",
425                         addr, (int)(port >> 8), (int)(port & 255));
426         free(addr);
427         cmdio_write_raw(response);
428         free(response);
429 }
430
431 /* Exits on error */
432 static void
433 handle_epsv(void)
434 {
435         unsigned port;
436         char *response;
437
438         port = bind_for_passive_mode();
439         response = xasprintf(STR(FTP_EPSVOK)" EPSV Ok (|||%u|)\r\n", port);
440         cmdio_write_raw(response);
441         free(response);
442 }
443
444 static void
445 handle_port(void)
446 {
447         unsigned short port;
448         char *raw, *port_part;
449         len_and_sockaddr *lsa;
450
451         port_pasv_cleanup();
452
453         raw = G.ftp_arg;
454
455 /* Buglets:
456  * xatou16 will accept wrong input,
457  * xatou16 will exit instead of generating error to peer
458  */
459
460         port_part = strrchr(raw, ',');
461         if (port_part == NULL)
462                 goto bail;
463         port = xatou16(&port_part[1]);
464         *port_part = '\0';
465
466         port_part = strrchr(raw, ',');
467         if (port_part == NULL)
468                 goto bail;
469         port |= xatou16(&port_part[1]) << 8;
470         *port_part = '\0';
471
472         replace_char(raw, ',', '.');
473         lsa = xdotted2sockaddr(raw, port);
474
475         if (lsa == NULL) {
476  bail:
477                 cmdio_write_error(FTP_BADCMD);
478                 return;
479         }
480
481 /* Should we verify that lsa matches getpeername(STDIN)?
482  * Otherwise peer can make us open data connections
483  * to other hosts (security problem!)
484  */
485
486         G.port_addr = lsa;
487         cmdio_write_ok(FTP_PORTOK);
488 }
489
490 static void
491 handle_rest(void)
492 {
493         /* When ftp_arg == NULL simply restart from beginning */
494         G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0;
495         cmdio_write_ok(FTP_RESTOK);
496 }
497
498 static void
499 handle_retr(void)
500 {
501         struct stat statbuf;
502         off_t bytes_transferred;
503         int remote_fd;
504         int local_file_fd;
505         off_t offset = G.restart_pos;
506         char *response;
507
508         G.restart_pos = 0;
509
510         if (!port_or_pasv_was_seen())
511                 return; /* port_or_pasv_was_seen emitted error response */
512
513         /* O_NONBLOCK is useful if file happens to be a device node */
514         local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
515         if (local_file_fd < 0) {
516                 cmdio_write_error(FTP_FILEFAIL);
517                 return;
518         }
519
520         if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
521                 /* Note - pretend open failed */
522                 cmdio_write_error(FTP_FILEFAIL);
523                 goto file_close_out;
524         }
525
526         /* Now deactive O_NONBLOCK, otherwise we have a problem
527          * on DMAPI filesystems such as XFS DMAPI.
528          */
529         ndelay_off(local_file_fd);
530
531         /* Set the download offset (from REST) if any */
532         if (offset != 0)
533                 xlseek(local_file_fd, offset, SEEK_SET);
534
535         response = xasprintf(
536                 " Opening BINARY mode data connection for %s (%"OFF_FMT"u bytes)",
537                 G.ftp_arg, statbuf.st_size);
538         remote_fd = get_remote_transfer_fd(response);
539         free(response);
540         if (remote_fd < 0)
541                 goto file_close_out;
542
543 /* TODO: if we'll implement timeout, this will need more clever handling.
544  * Perhaps alarm(N) + checking that current position on local_file_fd
545  * is advancing. As of now, peer may stall us indefinitely.
546  */
547
548         bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
549         close(remote_fd);
550         if (bytes_transferred < 0)
551                 cmdio_write_error(FTP_BADSENDFILE);
552         else
553                 cmdio_write_ok(FTP_TRANSFEROK);
554
555  file_close_out:
556         close(local_file_fd);
557 }
558
559 /* List commands */
560
561 static int
562 popen_ls(const char *opt)
563 {
564         char *cwd;
565         const char *argv[5] = { "ftpd", opt, NULL, G.ftp_arg, NULL };
566         struct fd_pair outfd;
567         pid_t pid;
568
569         cwd = xrealloc_getcwd_or_warn(NULL);
570         xpiped_pair(outfd);
571
572         /*fflush(NULL); - so far we dont use stdio on output */
573         pid = vfork();
574         switch (pid) {
575         case -1:  /* failure */
576                 bb_perror_msg_and_die("vfork");
577         case 0:  /* child */
578                 /* NB: close _first_, then move fds! */
579                 close(outfd.rd);
580                 xmove_fd(outfd.wr, STDOUT_FILENO);
581                 close(STDIN_FILENO);
582                 /* xopen("/dev/null", O_RDONLY); - chroot may lack it! */
583                 if (fchdir(G.proc_self_fd) == 0) {
584                         close(G.proc_self_fd);
585                         argv[2] = cwd;
586                         /* ftpd ls helper chdirs to argv[2],
587                          * preventing peer from seeing /proc/self
588                          */
589                         execv("exe", (char**) argv);
590                 }
591                 _exit(127);
592         }
593
594         /* parent */
595         close(outfd.wr);
596         free(cwd);
597         return outfd.rd;
598 }
599
600 enum {
601         USE_CTRL_CONN = 1,
602         LONG_LISTING = 2,
603 };
604
605 static void
606 handle_dir_common(int opts)
607 {
608         FILE *ls_fp;
609         char *line;
610         int ls_fd;
611
612         if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
613                 return; /* port_or_pasv_was_seen emitted error response */
614
615         ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
616         ls_fp = fdopen(ls_fd, "r");
617         if (!ls_fp) /* never happens. paranoia */
618                 bb_perror_msg_and_die("fdopen");
619
620         if (opts & USE_CTRL_CONN) {
621                 /* STAT <filename> */
622                 cmdio_write_raw(STR(FTP_STATFILE_OK)"-Status follows:\r\n");
623                 while (1) {
624                         line = xmalloc_fgetline(ls_fp);
625                         if (!line)
626                                 break;
627                         cmdio_write(0, line); /* hack: 0 results in no status at all */
628                         free(line);
629                 }
630                 cmdio_write_ok(FTP_STATFILE_OK);
631         } else {
632                 /* LIST/NLST [<filename>] */
633                 int remote_fd = get_remote_transfer_fd(" Here comes the directory listing");
634                 if (remote_fd >= 0) {
635                         while (1) {
636                                 line = xmalloc_fgetline(ls_fp);
637                                 if (!line)
638                                         break;
639                                 /* I've seen clients complaining when they
640                                  * are fed with ls output with bare '\n'.
641                                  * Pity... that would be much simpler.
642                                  */
643                                 xwrite_str(remote_fd, line);
644                                 xwrite(remote_fd, "\r\n", 2);
645                                 free(line);
646                         }
647                 }
648                 close(remote_fd);
649                 cmdio_write_ok(FTP_TRANSFEROK);
650         }
651         fclose(ls_fp); /* closes ls_fd too */
652 }
653 static void
654 handle_list(void)
655 {
656         handle_dir_common(LONG_LISTING);
657 }
658 static void
659 handle_nlst(void)
660 {
661         handle_dir_common(0);
662 }
663 static void
664 handle_stat_file(void)
665 {
666         handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
667 }
668
669 static void
670 handle_size(void)
671 {
672         struct stat statbuf;
673         char buf[sizeof(STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n") + sizeof(off_t)*3];
674
675         if (!G.ftp_arg
676          || stat(G.ftp_arg, &statbuf) != 0
677          || !S_ISREG(statbuf.st_mode)
678         ) {
679                 cmdio_write_error(FTP_FILEFAIL);
680                 return;
681         }
682         sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
683         cmdio_write_raw(buf);
684 }
685
686 /* Upload commands */
687
688 #if ENABLE_FEATURE_FTP_WRITE
689 static void
690 handle_mkd(void)
691 {
692         if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
693                 cmdio_write_error(FTP_FILEFAIL);
694                 return;
695         }
696         cmdio_write_ok(FTP_MKDIROK);
697 }
698
699 static void
700 handle_rmd(void)
701 {
702         if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
703                 cmdio_write_error(FTP_FILEFAIL);
704                 return;
705         }
706         cmdio_write_ok(FTP_RMDIROK);
707 }
708
709 static void
710 handle_dele(void)
711 {
712         if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
713                 cmdio_write_error(FTP_FILEFAIL);
714                 return;
715         }
716         cmdio_write_ok(FTP_DELEOK);
717 }
718
719 static void
720 handle_rnfr(void)
721 {
722         free(G.rnfr_filename);
723         G.rnfr_filename = xstrdup(G.ftp_arg);
724         cmdio_write_ok(FTP_RNFROK);
725 }
726
727 static void
728 handle_rnto(void)
729 {
730         int retval;
731
732         /* If we didn't get a RNFR, throw a wobbly */
733         if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
734                 cmdio_write_raw(STR(FTP_NEEDRNFR)" RNFR required first\r\n");
735                 return;
736         }
737
738         retval = rename(G.rnfr_filename, G.ftp_arg);
739         free(G.rnfr_filename);
740         G.rnfr_filename = NULL;
741
742         if (retval) {
743                 cmdio_write_error(FTP_FILEFAIL);
744                 return;
745         }
746         cmdio_write_ok(FTP_RENAMEOK);
747 }
748
749 static void
750 handle_upload_common(int is_append, int is_unique)
751 {
752         char *tempname = NULL;
753         off_t bytes_transferred;
754         int local_file_fd;
755         int remote_fd;
756         off_t offset;
757
758         offset = G.restart_pos;
759         G.restart_pos = 0;
760
761         if (!port_or_pasv_was_seen())
762                 return; /* port_or_pasv_was_seen emitted error response */
763
764         local_file_fd = -1;
765         if (is_unique) {
766                 tempname = xstrdup(" FILE: uniq.XXXXXX");
767                 local_file_fd = mkstemp(tempname + 7);
768         } else if (G.ftp_arg) {
769                 int flags = O_WRONLY | O_CREAT | O_TRUNC;
770                 if (is_append)
771                         flags = O_WRONLY | O_CREAT | O_APPEND;
772                 if (offset)
773                         flags = O_WRONLY | O_CREAT;
774                 local_file_fd = open(G.ftp_arg, flags, 0666);
775         }
776         if (local_file_fd < 0) {
777                 cmdio_write_error(FTP_UPLOADFAIL);
778                 return;
779         }
780
781 /* TODO: paranoia: fstat it, refuse to do anything if it's not a regular file */
782
783         if (offset)
784                 xlseek(local_file_fd, offset, SEEK_SET);
785
786         remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
787         free(tempname);
788
789         if (remote_fd < 0)
790                 goto bail;
791
792 /* TODO: if we'll implement timeout, this will need more clever handling.
793  * Perhaps alarm(N) + checking that current position on local_file_fd
794  * is advancing. As of now, peer may stall us indefinitely.
795  */
796
797         bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
798         close(remote_fd);
799         if (bytes_transferred < 0)
800                 cmdio_write_error(FTP_BADSENDFILE);
801         else
802                 cmdio_write_ok(FTP_TRANSFEROK);
803  bail:
804         close(local_file_fd);
805 }
806
807 static void
808 handle_stor(void)
809 {
810         handle_upload_common(0, 0);
811 }
812
813 static void
814 handle_appe(void)
815 {
816         G.restart_pos = 0;
817         handle_upload_common(1, 0);
818 }
819
820 static void
821 handle_stou(void)
822 {
823         G.restart_pos = 0;
824         handle_upload_common(0, 1);
825 }
826 #endif /* ENABLE_FEATURE_FTP_WRITE */
827
828 static uint32_t
829 cmdio_get_cmd_and_arg(void)
830 {
831         size_t len;
832         uint32_t cmdval;
833         char *cmd;
834
835         free(G.ftp_cmd);
836         len = 8 * 1024; /* Paranoia. Peer may send 1 gigabyte long cmd... */
837         G.ftp_cmd = cmd = xmalloc_reads(STDIN_FILENO, NULL, &len);
838         if (!cmd)
839                 exit(0);
840
841         len = strlen(cmd) - 1;
842         while (len >= 0 && cmd[len] == '\r') {
843                 cmd[len] = '\0';
844                 len--;
845         }
846
847         G.ftp_arg = strchr(cmd, ' ');
848         if (G.ftp_arg != NULL) {
849                 *G.ftp_arg = '\0';
850                 G.ftp_arg++;
851         }
852         cmdval = 0;
853         while (*cmd)
854                 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
855
856         return cmdval;
857 }
858
859 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
860 int ftpd_main(int argc, char **argv)
861 {
862         smallint opts;
863
864         opts = getopt32(argv, "l1vS" USE_FEATURE_FTP_WRITE("w"));
865
866         if (opts & (OPT_l|OPT_1)) {
867                 /* Our secret backdoor to ls */
868                 xchdir(argv[2]);
869                 argv[2] = (char*)"--";
870                 return ls_main(argc, argv);
871         }
872
873         INIT_G();
874
875         G.local_addr = get_sock_lsa(STDIN_FILENO);
876         if (!G.local_addr) {
877                 /* This is confusing:
878                  * bb_error_msg_and_die("stdin is not a socket");
879                  * Better: */
880                 bb_show_usage();
881                 /* Help text says that ftpd must be used as inetd service,
882                  * which is by far the most usual cause of get_sock_lsa
883                  * failure */
884         }
885
886         if (!(opts & OPT_v))
887                 logmode = LOGMODE_NONE;
888         if (opts & OPT_S) {
889                 /* LOG_NDELAY is needed since we may chroot later */
890                 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
891                 logmode |= LOGMODE_SYSLOG;
892         }
893
894         G.proc_self_fd = xopen("/proc/self", O_RDONLY | O_DIRECTORY);
895
896         if (argv[optind]) {
897                 xchdir(argv[optind]);
898                 chroot(".");
899         }
900
901         //umask(077); - admin can set umask before starting us
902
903         /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
904         signal(SIGPIPE, SIG_IGN);
905
906         /* Set up options on the command socket (do we need these all? why?) */
907         setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
908         setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
909         setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
910
911         cmdio_write_ok(FTP_GREET);
912
913 #ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
914         {
915                 smallint user_was_specified = 0;
916                 while (1) {
917                         uint32_t cmdval = cmdio_get_cmd_and_arg();
918
919                         if (cmdval == const_USER) {
920                                 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
921                                         cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
922                                 else {
923                                         user_was_specified = 1;
924                                         cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
925                                 }
926                         } else if (cmdval == const_PASS) {
927                                 if (user_was_specified)
928                                         break;
929                                 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
930                         } else if (cmdval == const_QUIT) {
931                                 cmdio_write_ok(FTP_GOODBYE);
932                                 return 0;
933                         } else {
934                                 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
935                         }
936                 }
937         }
938         cmdio_write_ok(FTP_LOGINOK);
939 #endif
940
941         /* RFC-959 Section 5.1
942          * The following commands and options MUST be supported by every
943          * server-FTP and user-FTP, except in cases where the underlying
944          * file system or operating system does not allow or support
945          * a particular command.
946          * Type: ASCII Non-print, IMAGE, LOCAL 8
947          * Mode: Stream
948          * Structure: File, Record*
949          * (Record structure is REQUIRED only for hosts whose file
950          *  systems support record structure).
951          * Commands:
952          * USER, PASS, ACCT, [bbox: ACCT not supported]
953          * PORT, PASV,
954          * TYPE, MODE, STRU,
955          * RETR, STOR, APPE,
956          * RNFR, RNTO, DELE,
957          * CWD,  CDUP, RMD,  MKD,  PWD,
958          * LIST, NLST,
959          * SYST, STAT,
960          * HELP, NOOP, QUIT.
961          */
962         /* ACCOUNT (ACCT)
963          * "The argument field is a Telnet string identifying the user's account.
964          * The command is not necessarily related to the USER command, as some
965          * sites may require an account for login and others only for specific
966          * access, such as storing files. In the latter case the command may
967          * arrive at any time.
968          * There are reply codes to differentiate these cases for the automation:
969          * when account information is required for login, the response to
970          * a successful PASSword command is reply code 332. On the other hand,
971          * if account information is NOT required for login, the reply to
972          * a successful PASSword command is 230; and if the account information
973          * is needed for a command issued later in the dialogue, the server
974          * should return a 332 or 532 reply depending on whether it stores
975          * (pending receipt of the ACCounT command) or discards the command,
976          * respectively."
977          */
978
979         while (1) {
980                 uint32_t cmdval = cmdio_get_cmd_and_arg();
981
982                 if (cmdval == const_QUIT) {
983                         cmdio_write_ok(FTP_GOODBYE);
984                         return 0;
985                 }
986                 else if (cmdval == const_USER)
987                         cmdio_write_ok(FTP_GIVEPWORD);
988                 else if (cmdval == const_PASS)
989                         cmdio_write_ok(FTP_LOGINOK);
990                 else if (cmdval == const_NOOP)
991                         cmdio_write_ok(FTP_NOOPOK);
992                 else if (cmdval == const_TYPE)
993                         cmdio_write_ok(FTP_TYPEOK);
994                 else if (cmdval == const_STRU)
995                         cmdio_write_ok(FTP_STRUOK);
996                 else if (cmdval == const_MODE)
997                         cmdio_write_ok(FTP_MODEOK);
998                 else if (cmdval == const_ALLO)
999                         cmdio_write_ok(FTP_ALLOOK);
1000                 else if (cmdval == const_SYST)
1001                         cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1002                 else if (cmdval == const_PWD)
1003                         handle_pwd();
1004                 else if (cmdval == const_CWD)
1005                         handle_cwd();
1006                 else if (cmdval == const_CDUP) /* cd .. */
1007                         handle_cdup();
1008                 else if (cmdval == const_HELP)
1009                         handle_help();
1010                 else if (cmdval == const_LIST) /* ls -l */
1011                         handle_list();
1012                 else if (cmdval == const_NLST) /* "name list", bare ls */
1013                         handle_nlst();
1014                 else if (cmdval == const_SIZE)
1015                         handle_size();
1016                 else if (cmdval == const_STAT) {
1017                         if (G.ftp_arg == NULL)
1018                                 handle_stat();
1019                         else
1020                                 handle_stat_file();
1021                 }
1022                 else if (cmdval == const_PASV)
1023                         handle_pasv();
1024                 else if (cmdval == const_EPSV)
1025                         handle_epsv();
1026                 else if (cmdval == const_RETR)
1027                         handle_retr();
1028                 else if (cmdval == const_PORT)
1029                         handle_port();
1030                 else if (cmdval == const_REST)
1031                         handle_rest();
1032 #if ENABLE_FEATURE_FTP_WRITE
1033                 else if (opts & OPT_w) {
1034                         if (cmdval == const_STOR)
1035                                 handle_stor();
1036                         else if (cmdval == const_MKD)
1037                                 handle_mkd();
1038                         else if (cmdval == const_RMD)
1039                                 handle_rmd();
1040                         else if (cmdval == const_DELE)
1041                                 handle_dele();
1042                         else if (cmdval == const_RNFR) /* "rename from" */
1043                                 handle_rnfr();
1044                         else if (cmdval == const_RNTO) /* "rename to" */
1045                                 handle_rnto();
1046                         else if (cmdval == const_APPE)
1047                                 handle_appe();
1048                         else if (cmdval == const_STOU) /* "store unique" */
1049                                 handle_stou();
1050                 }
1051 #endif
1052 #if 0
1053                 else if (cmdval == const_STOR
1054                  || cmdval == const_MKD
1055                  || cmdval == const_RMD
1056                  || cmdval == const_DELE
1057                  || cmdval == const_RNFR
1058                  || cmdval == const_RNTO
1059                  || cmdval == const_APPE
1060                  || cmdval == const_STOU
1061                 ) {
1062                         cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1063                 }
1064 #endif
1065                 else {
1066                         /* Which unsupported commands were seen in the wild?
1067                          * (doesn't necessarily mean "we must support them")
1068                          * lftp 3.6.3: FEAT - is it useful?
1069                          *             MDTM - works fine without it anyway
1070                          */
1071                         cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
1072                 }
1073         }
1074 }