ftpd: document NLST format
[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 /* TODO: need to s/LF/NUL/g here */
696                                 xwrite_str(remote_fd, line);
697                                 xwrite(remote_fd, "\r\n", 2);
698                                 free(line);
699                         }
700                 }
701                 close(remote_fd);
702                 WRITE_OK(FTP_TRANSFEROK);
703         }
704         fclose(ls_fp); /* closes ls_fd too */
705 }
706 static void
707 handle_list(void)
708 {
709         handle_dir_common(LONG_LISTING);
710 }
711 static void
712 handle_nlst(void)
713 {
714         /* NLST returns list of names, "\r\n" terminated without regard
715          * to the current binary flag. Names may start with "/",
716          * then they represent full names (we don't produce such names),
717          * otherwise names are relative to current directory.
718          * Embedded "\n" are replaced by NULs. This is safe since names
719          * can never contain NUL.
720          */
721         handle_dir_common(0);
722 }
723 static void
724 handle_stat_file(void)
725 {
726         handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
727 }
728
729 static void
730 handle_size(void)
731 {
732         struct stat statbuf;
733         char buf[sizeof(STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n") + sizeof(off_t)*3];
734
735         if (!G.ftp_arg
736          || stat(G.ftp_arg, &statbuf) != 0
737          || !S_ISREG(statbuf.st_mode)
738         ) {
739                 WRITE_ERR(FTP_FILEFAIL);
740                 return;
741         }
742         sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
743         cmdio_write_raw(buf);
744 }
745
746 /* Upload commands */
747
748 #if ENABLE_FEATURE_FTP_WRITE
749 static void
750 handle_mkd(void)
751 {
752         if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
753                 WRITE_ERR(FTP_FILEFAIL);
754                 return;
755         }
756         WRITE_OK(FTP_MKDIROK);
757 }
758
759 static void
760 handle_rmd(void)
761 {
762         if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
763                 WRITE_ERR(FTP_FILEFAIL);
764                 return;
765         }
766         WRITE_OK(FTP_RMDIROK);
767 }
768
769 static void
770 handle_dele(void)
771 {
772         if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
773                 WRITE_ERR(FTP_FILEFAIL);
774                 return;
775         }
776         WRITE_OK(FTP_DELEOK);
777 }
778
779 static void
780 handle_rnfr(void)
781 {
782         free(G.rnfr_filename);
783         G.rnfr_filename = xstrdup(G.ftp_arg);
784         WRITE_OK(FTP_RNFROK);
785 }
786
787 static void
788 handle_rnto(void)
789 {
790         int retval;
791
792         /* If we didn't get a RNFR, throw a wobbly */
793         if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
794                 cmdio_write_raw(STR(FTP_NEEDRNFR)" RNFR required first\r\n");
795                 return;
796         }
797
798         retval = rename(G.rnfr_filename, G.ftp_arg);
799         free(G.rnfr_filename);
800         G.rnfr_filename = NULL;
801
802         if (retval) {
803                 WRITE_ERR(FTP_FILEFAIL);
804                 return;
805         }
806         WRITE_OK(FTP_RENAMEOK);
807 }
808
809 static void
810 handle_upload_common(int is_append, int is_unique)
811 {
812         struct stat statbuf;
813         char *tempname;
814         off_t bytes_transferred;
815         off_t offset;
816         int local_file_fd;
817         int remote_fd;
818
819         offset = G.restart_pos;
820         G.restart_pos = 0;
821
822         if (!port_or_pasv_was_seen())
823                 return; /* port_or_pasv_was_seen emitted error response */
824
825         tempname = NULL;
826         local_file_fd = -1;
827         if (is_unique) {
828                 tempname = xstrdup(" FILE: uniq.XXXXXX");
829                 local_file_fd = mkstemp(tempname + 7);
830         } else if (G.ftp_arg) {
831                 int flags = O_WRONLY | O_CREAT | O_TRUNC;
832                 if (is_append)
833                         flags = O_WRONLY | O_CREAT | O_APPEND;
834                 if (offset)
835                         flags = O_WRONLY | O_CREAT;
836                 local_file_fd = open(G.ftp_arg, flags, 0666);
837         }
838
839         if (local_file_fd < 0
840          || fstat(local_file_fd, &statbuf) != 0
841          || !S_ISREG(statbuf.st_mode)
842         ) {
843                 WRITE_ERR(FTP_UPLOADFAIL);
844                 if (local_file_fd >= 0)
845                         goto close_local_and_bail;
846                 return;
847         }
848         G.local_file_fd = local_file_fd;
849
850         if (offset)
851                 xlseek(local_file_fd, offset, SEEK_SET);
852
853         remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
854         free(tempname);
855
856         if (remote_fd < 0)
857                 goto close_local_and_bail;
858
859         bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
860         close(remote_fd);
861         if (bytes_transferred < 0)
862                 WRITE_ERR(FTP_BADSENDFILE);
863         else
864                 WRITE_OK(FTP_TRANSFEROK);
865
866  close_local_and_bail:
867         close(local_file_fd);
868         G.local_file_fd = 0;
869 }
870
871 static void
872 handle_stor(void)
873 {
874         handle_upload_common(0, 0);
875 }
876
877 static void
878 handle_appe(void)
879 {
880         G.restart_pos = 0;
881         handle_upload_common(1, 0);
882 }
883
884 static void
885 handle_stou(void)
886 {
887         G.restart_pos = 0;
888         handle_upload_common(0, 1);
889 }
890 #endif /* ENABLE_FEATURE_FTP_WRITE */
891
892 static uint32_t
893 cmdio_get_cmd_and_arg(void)
894 {
895         size_t len;
896         uint32_t cmdval;
897         char *cmd;
898
899         alarm(G.timeout);
900
901         free(G.ftp_cmd);
902         len = 8 * 1024; /* Paranoia. Peer may send 1 gigabyte long cmd... */
903         G.ftp_cmd = cmd = xmalloc_reads(STDIN_FILENO, NULL, &len);
904         if (!cmd)
905                 exit(0);
906
907         /* Trailing '\n' is already stripped, strip '\r' */
908         len = strlen(cmd) - 1;
909         if ((ssize_t)len >= 0 && cmd[len] == '\r')
910                 cmd[len--] = '\0';
911
912         if (G.verbose > 1)
913                 verbose_log(cmd);
914
915         G.ftp_arg = strchr(cmd, ' ');
916         if (G.ftp_arg != NULL)
917                 *G.ftp_arg++ = '\0';
918
919         /* Uppercase and pack into uint32_t first word of the command */
920         cmdval = 0;
921         while (*cmd)
922                 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
923
924         return cmdval;
925 }
926
927 #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
928 #define mk_const3(a,b,c)    ((a * 0x100 + b) * 0x100 + c)
929 enum {
930         const_ALLO = mk_const4('A', 'L', 'L', 'O'),
931         const_APPE = mk_const4('A', 'P', 'P', 'E'),
932         const_CDUP = mk_const4('C', 'D', 'U', 'P'),
933         const_CWD  = mk_const3('C', 'W', 'D'),
934         const_DELE = mk_const4('D', 'E', 'L', 'E'),
935         const_EPSV = mk_const4('E', 'P', 'S', 'V'),
936         const_FEAT = mk_const4('F', 'E', 'A', 'T'),
937         const_HELP = mk_const4('H', 'E', 'L', 'P'),
938         const_LIST = mk_const4('L', 'I', 'S', 'T'),
939         const_MKD  = mk_const3('M', 'K', 'D'),
940         const_MODE = mk_const4('M', 'O', 'D', 'E'),
941         const_NLST = mk_const4('N', 'L', 'S', 'T'),
942         const_NOOP = mk_const4('N', 'O', 'O', 'P'),
943         const_PASS = mk_const4('P', 'A', 'S', 'S'),
944         const_PASV = mk_const4('P', 'A', 'S', 'V'),
945         const_PORT = mk_const4('P', 'O', 'R', 'T'),
946         const_PWD  = mk_const3('P', 'W', 'D'),
947         const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
948         const_REST = mk_const4('R', 'E', 'S', 'T'),
949         const_RETR = mk_const4('R', 'E', 'T', 'R'),
950         const_RMD  = mk_const3('R', 'M', 'D'),
951         const_RNFR = mk_const4('R', 'N', 'F', 'R'),
952         const_RNTO = mk_const4('R', 'N', 'T', 'O'),
953         const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
954         const_STAT = mk_const4('S', 'T', 'A', 'T'),
955         const_STOR = mk_const4('S', 'T', 'O', 'R'),
956         const_STOU = mk_const4('S', 'T', 'O', 'U'),
957         const_STRU = mk_const4('S', 'T', 'R', 'U'),
958         const_SYST = mk_const4('S', 'Y', 'S', 'T'),
959         const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
960         const_USER = mk_const4('U', 'S', 'E', 'R'),
961
962         OPT_l = (1 << 0),
963         OPT_1 = (1 << 1),
964         OPT_v = (1 << 2),
965         OPT_S = (1 << 3),
966         OPT_w = (1 << 4),
967 };
968
969 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
970 int ftpd_main(int argc, char **argv)
971 {
972         unsigned abs_timeout;
973         smallint opts;
974
975         INIT_G();
976
977         abs_timeout = 1 * 60 * 60;
978         G.timeout = 2 * 60;
979         opt_complementary = "t+:T+:vv";
980         opts = getopt32(argv, "l1vS" USE_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose);
981         if (opts & (OPT_l|OPT_1)) {
982                 /* Our secret backdoor to ls */
983                 memset(&G, 0, sizeof(G));
984 /* TODO: pass -n too? */
985 /* --group-directories-first would be nice, but ls don't do that yet */
986                 xchdir(argv[2]);
987                 argv[2] = (char*)"--";
988                 return ls_main(argc, argv);
989         }
990         if (abs_timeout | G.timeout) {
991                 if (abs_timeout == 0)
992                         abs_timeout = INT_MAX;
993                 G.end_time = monotonic_sec() + abs_timeout;
994                 if (G.timeout > abs_timeout)
995                         G.timeout = abs_timeout;
996         }
997         strcpy(G.msg_ok  + 4, MSG_OK );
998         strcpy(G.msg_err + 4, MSG_ERR);
999
1000         G.local_addr = get_sock_lsa(STDIN_FILENO);
1001         if (!G.local_addr) {
1002                 /* This is confusing:
1003                  * bb_error_msg_and_die("stdin is not a socket");
1004                  * Better: */
1005                 bb_show_usage();
1006                 /* Help text says that ftpd must be used as inetd service,
1007                  * which is by far the most usual cause of get_sock_lsa
1008                  * failure */
1009         }
1010
1011         if (!(opts & OPT_v))
1012                 logmode = LOGMODE_NONE;
1013         if (opts & OPT_S) {
1014                 /* LOG_NDELAY is needed since we may chroot later */
1015                 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1016                 logmode |= LOGMODE_SYSLOG;
1017         }
1018         if (logmode)
1019                 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1020
1021         G.proc_self_fd = xopen("/proc/self", O_RDONLY | O_DIRECTORY);
1022
1023         if (argv[optind]) {
1024                 xchdir(argv[optind]);
1025                 chroot(".");
1026         }
1027
1028         //umask(077); - admin can set umask before starting us
1029
1030         /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
1031         signal(SIGPIPE, SIG_IGN);
1032
1033         /* Set up options on the command socket (do we need these all? why?) */
1034         setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1035         setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
1036         setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1037
1038         WRITE_OK(FTP_GREET);
1039         signal(SIGALRM, timeout_handler);
1040
1041 #ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
1042         {
1043                 smallint user_was_specified = 0;
1044                 while (1) {
1045                         uint32_t cmdval = cmdio_get_cmd_and_arg();
1046
1047                         if (cmdval == const_USER) {
1048                                 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
1049                                         cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
1050                                 else {
1051                                         user_was_specified = 1;
1052                                         cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
1053                                 }
1054                         } else if (cmdval == const_PASS) {
1055                                 if (user_was_specified)
1056                                         break;
1057                                 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
1058                         } else if (cmdval == const_QUIT) {
1059                                 WRITE_OK(FTP_GOODBYE);
1060                                 return 0;
1061                         } else {
1062                                 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1063                         }
1064                 }
1065         }
1066         WRITE_OK(FTP_LOGINOK);
1067 #endif
1068
1069         /* RFC-959 Section 5.1
1070          * The following commands and options MUST be supported by every
1071          * server-FTP and user-FTP, except in cases where the underlying
1072          * file system or operating system does not allow or support
1073          * a particular command.
1074          * Type: ASCII Non-print, IMAGE, LOCAL 8
1075          * Mode: Stream
1076          * Structure: File, Record*
1077          * (Record structure is REQUIRED only for hosts whose file
1078          *  systems support record structure).
1079          * Commands:
1080          * USER, PASS, ACCT, [bbox: ACCT not supported]
1081          * PORT, PASV,
1082          * TYPE, MODE, STRU,
1083          * RETR, STOR, APPE,
1084          * RNFR, RNTO, DELE,
1085          * CWD,  CDUP, RMD,  MKD,  PWD,
1086          * LIST, NLST,
1087          * SYST, STAT,
1088          * HELP, NOOP, QUIT.
1089          */
1090         /* ACCOUNT (ACCT)
1091          * "The argument field is a Telnet string identifying the user's account.
1092          * The command is not necessarily related to the USER command, as some
1093          * sites may require an account for login and others only for specific
1094          * access, such as storing files. In the latter case the command may
1095          * arrive at any time.
1096          * There are reply codes to differentiate these cases for the automation:
1097          * when account information is required for login, the response to
1098          * a successful PASSword command is reply code 332. On the other hand,
1099          * if account information is NOT required for login, the reply to
1100          * a successful PASSword command is 230; and if the account information
1101          * is needed for a command issued later in the dialogue, the server
1102          * should return a 332 or 532 reply depending on whether it stores
1103          * (pending receipt of the ACCounT command) or discards the command,
1104          * respectively."
1105          */
1106
1107         while (1) {
1108                 uint32_t cmdval = cmdio_get_cmd_and_arg();
1109
1110                 if (cmdval == const_QUIT) {
1111                         WRITE_OK(FTP_GOODBYE);
1112                         return 0;
1113                 }
1114                 else if (cmdval == const_USER)
1115                         WRITE_OK(FTP_GIVEPWORD);
1116                 else if (cmdval == const_PASS)
1117                         WRITE_OK(FTP_LOGINOK);
1118                 else if (cmdval == const_NOOP)
1119                         WRITE_OK(FTP_NOOPOK);
1120                 else if (cmdval == const_TYPE)
1121                         WRITE_OK(FTP_TYPEOK);
1122                 else if (cmdval == const_STRU)
1123                         WRITE_OK(FTP_STRUOK);
1124                 else if (cmdval == const_MODE)
1125                         WRITE_OK(FTP_MODEOK);
1126                 else if (cmdval == const_ALLO)
1127                         WRITE_OK(FTP_ALLOOK);
1128                 else if (cmdval == const_SYST)
1129                         cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1130                 else if (cmdval == const_PWD)
1131                         handle_pwd();
1132                 else if (cmdval == const_CWD)
1133                         handle_cwd();
1134                 else if (cmdval == const_CDUP) /* cd .. */
1135                         handle_cdup();
1136                 /* HELP is nearly useless, but we can reuse FEAT for it */
1137                 else if (cmdval == const_HELP || cmdval == const_FEAT)
1138                         handle_feat(cmdval == const_HELP ? STRNUM32(FTP_HELP) : STRNUM32(FTP_STATOK));
1139                 else if (cmdval == const_LIST) /* ls -l */
1140                         handle_list();
1141                 else if (cmdval == const_NLST) /* "name list", bare ls */
1142                         handle_nlst();
1143                 else if (cmdval == const_SIZE)
1144                         handle_size();
1145                 else if (cmdval == const_STAT) {
1146                         if (G.ftp_arg == NULL)
1147                                 handle_stat();
1148                         else
1149                                 handle_stat_file();
1150                 }
1151                 else if (cmdval == const_PASV)
1152                         handle_pasv();
1153                 else if (cmdval == const_EPSV)
1154                         handle_epsv();
1155                 else if (cmdval == const_RETR)
1156                         handle_retr();
1157                 else if (cmdval == const_PORT)
1158                         handle_port();
1159                 else if (cmdval == const_REST)
1160                         handle_rest();
1161 #if ENABLE_FEATURE_FTP_WRITE
1162                 else if (opts & OPT_w) {
1163                         if (cmdval == const_STOR)
1164                                 handle_stor();
1165                         else if (cmdval == const_MKD)
1166                                 handle_mkd();
1167                         else if (cmdval == const_RMD)
1168                                 handle_rmd();
1169                         else if (cmdval == const_DELE)
1170                                 handle_dele();
1171                         else if (cmdval == const_RNFR) /* "rename from" */
1172                                 handle_rnfr();
1173                         else if (cmdval == const_RNTO) /* "rename to" */
1174                                 handle_rnto();
1175                         else if (cmdval == const_APPE)
1176                                 handle_appe();
1177                         else if (cmdval == const_STOU) /* "store unique" */
1178                                 handle_stou();
1179                 }
1180 #endif
1181 #if 0
1182                 else if (cmdval == const_STOR
1183                  || cmdval == const_MKD
1184                  || cmdval == const_RMD
1185                  || cmdval == const_DELE
1186                  || cmdval == const_RNFR
1187                  || cmdval == const_RNTO
1188                  || cmdval == const_APPE
1189                  || cmdval == const_STOU
1190                 ) {
1191                         cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1192                 }
1193 #endif
1194                 else {
1195                         /* Which unsupported commands were seen in the wild?
1196                          * (doesn't necessarily mean "we must support them")
1197                          * lftp 3.6.3: MDTM - works fine without it anyway
1198                          */
1199                         cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
1200                 }
1201         }
1202 }