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