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