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