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