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