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