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