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