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