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