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