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