aboutsummaryrefslogtreecommitdiff
path: root/networking/ftpd.c
diff options
context:
space:
mode:
authorDenis Vlasenko <vda.linux@googlemail.com>2009-03-16 14:53:54 +0000
committerDenis Vlasenko <vda.linux@googlemail.com>2009-03-16 14:53:54 +0000
commitf2160b6a09f4e4879fb718526106c548d8f8ec23 (patch)
treeff52e5b8dfb026571302023c39f925092a579f41 /networking/ftpd.c
parent7a6766428e3306c3bdc98e0fff207e5015af8930 (diff)
downloadbusybox-w32-f2160b6a09f4e4879fb718526106c548d8f8ec23.tar.gz
busybox-w32-f2160b6a09f4e4879fb718526106c548d8f8ec23.tar.bz2
busybox-w32-f2160b6a09f4e4879fb718526106c548d8f8ec23.zip
ftpd: security tightened up:
PORT is not allowed on !IPv4 PORT must have IP == peer's IP upload is allowed only into regular files function old new delta ftpd_main 1815 2019 +204 handle_upload_common 260 306 +46 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/0 up/down: 250/0) Total: 250 bytes
Diffstat (limited to 'networking/ftpd.c')
-rw-r--r--networking/ftpd.c196
1 files changed, 107 insertions, 89 deletions
diff --git a/networking/ftpd.c b/networking/ftpd.c
index 37b340e78..22cec83a8 100644
--- a/networking/ftpd.c
+++ b/networking/ftpd.c
@@ -66,9 +66,9 @@
66/* Convert a constant to 3-digit string, packed into uint32_t */ 66/* Convert a constant to 3-digit string, packed into uint32_t */
67enum { 67enum {
68 /* Shift for Nth decimal digit */ 68 /* Shift for Nth decimal digit */
69 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
70 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
71 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN, 69 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
70 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
71 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
72}; 72};
73#define STRNUM32(s) (uint32_t)(0 \ 73#define STRNUM32(s) (uint32_t)(0 \
74 | (('0' + ((s) / 1 % 10)) << SHIFT0) \ 74 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
@@ -76,47 +76,6 @@ enum {
76 | (('0' + ((s) / 100 % 10)) << SHIFT2) \ 76 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
77) 77)
78 78
79enum {
80 OPT_l = (1 << 0),
81 OPT_1 = (1 << 1),
82 OPT_v = (1 << 2),
83 OPT_S = (1 << 3),
84 OPT_w = (1 << 4),
85
86#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
87#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
88 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
89 const_APPE = mk_const4('A', 'P', 'P', 'E'),
90 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
91 const_CWD = mk_const3('C', 'W', 'D'),
92 const_DELE = mk_const4('D', 'E', 'L', 'E'),
93 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
94 const_HELP = mk_const4('H', 'E', 'L', 'P'),
95 const_LIST = mk_const4('L', 'I', 'S', 'T'),
96 const_MKD = mk_const3('M', 'K', 'D'),
97 const_MODE = mk_const4('M', 'O', 'D', 'E'),
98 const_NLST = mk_const4('N', 'L', 'S', 'T'),
99 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
100 const_PASS = mk_const4('P', 'A', 'S', 'S'),
101 const_PASV = mk_const4('P', 'A', 'S', 'V'),
102 const_PORT = mk_const4('P', 'O', 'R', 'T'),
103 const_PWD = mk_const3('P', 'W', 'D'),
104 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
105 const_REST = mk_const4('R', 'E', 'S', 'T'),
106 const_RETR = mk_const4('R', 'E', 'T', 'R'),
107 const_RMD = mk_const3('R', 'M', 'D'),
108 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
109 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
110 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
111 const_STAT = mk_const4('S', 'T', 'A', 'T'),
112 const_STOR = mk_const4('S', 'T', 'O', 'R'),
113 const_STOU = mk_const4('S', 'T', 'O', 'U'),
114 const_STRU = mk_const4('S', 'T', 'R', 'U'),
115 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
116 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
117 const_USER = mk_const4('U', 'S', 'E', 'R'),
118};
119
120struct globals { 79struct globals {
121 char *p_control_line_buf; 80 char *p_control_line_buf;
122 len_and_sockaddr *local_addr; 81 len_and_sockaddr *local_addr;
@@ -182,21 +141,16 @@ replace_char(char *str, char from, char to)
182 return p - str; 141 return p - str;
183} 142}
184 143
144/* NB: status_str is char[4] packed into uint32_t */
185static void 145static void
186cmdio_write(uint32_t status_str, const char *str) 146cmdio_write(uint32_t status_str, const char *str)
187{ 147{
188 union {
189 char buf[4];
190 uint32_t v32;
191 } u;
192 char *response; 148 char *response;
193 int len; 149 int len;
194 150
195 u.v32 = status_str;
196
197 /* FTP allegedly uses telnet protocol for command link. 151 /* FTP allegedly uses telnet protocol for command link.
198 * In telnet, 0xff is an escape char, and needs to be escaped: */ 152 * In telnet, 0xff is an escape char, and needs to be escaped: */
199 response = escape_text(u.buf, str, (0xff << 8) + '\r'); 153 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
200 154
201 /* ?! does FTP send embedded LFs as NULs? wow */ 155 /* ?! does FTP send embedded LFs as NULs? wow */
202 len = replace_char(response, '\n', '\0'); 156 len = replace_char(response, '\n', '\0');
@@ -444,46 +398,58 @@ handle_epsv(void)
444static void 398static void
445handle_port(void) 399handle_port(void)
446{ 400{
447 unsigned short port; 401 unsigned port, port_hi;
448 char *raw, *port_part; 402 char *raw, *comma;
449 len_and_sockaddr *lsa; 403 socklen_t peer_ipv4_len;
404 struct sockaddr_in peer_ipv4;
405 struct in_addr port_ipv4_sin_addr;
450 406
451 port_pasv_cleanup(); 407 port_pasv_cleanup();
452 408
453 raw = G.ftp_arg; 409 raw = G.ftp_arg;
454 410
455/* Buglets: 411 /* PORT command format makes sense only over IPv4 */
456 * xatou16 will accept wrong input, 412 if (!raw || G.local_addr->u.sa.sa_family != AF_INET) {
457 * xatou16 will exit instead of generating error to peer 413 bail:
458 */ 414 cmdio_write_error(FTP_BADCMD);
415 return;
416 }
459 417
460 port_part = strrchr(raw, ','); 418 comma = strrchr(raw, ',');
461 if (port_part == NULL) 419 if (comma == NULL)
420 goto bail;
421 *comma = '\0';
422 port = bb_strtou(&comma[1], NULL, 10);
423 if (errno || port > 0xff)
462 goto bail; 424 goto bail;
463 port = xatou16(&port_part[1]);
464 *port_part = '\0';
465 425
466 port_part = strrchr(raw, ','); 426 comma = strrchr(raw, ',');
467 if (port_part == NULL) 427 if (comma == NULL)
468 goto bail; 428 goto bail;
469 port |= xatou16(&port_part[1]) << 8; 429 *comma = '\0';
470 *port_part = '\0'; 430 port_hi = bb_strtou(&comma[1], NULL, 10);
431 if (errno || port_hi > 0xff)
432 goto bail;
433 port |= port_hi << 8;
471 434
472 replace_char(raw, ',', '.'); 435 replace_char(raw, ',', '.');
473 lsa = xdotted2sockaddr(raw, port);
474 436
475 if (lsa == NULL) { 437 /* We are verifying that PORT's IP matches getpeername().
476 bail: 438 * Otherwise peer can make us open data connections
477 cmdio_write_error(FTP_BADCMD); 439 * to other hosts (security problem!)
478 return; 440 * This code would be too simplistic:
479 } 441 * lsa = xdotted2sockaddr(raw, port);
480 442 * if (lsa == NULL) goto bail;
481/* Should we verify that lsa matches getpeername(STDIN)? 443 */
482 * Otherwise peer can make us open data connections 444 if (!inet_aton(raw, &port_ipv4_sin_addr))
483 * to other hosts (security problem!) 445 goto bail;
484 */ 446 peer_ipv4_len = sizeof(peer_ipv4);
447 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
448 goto bail;
449 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
450 goto bail;
485 451
486 G.port_addr = lsa; 452 G.port_addr = xdotted2sockaddr(raw, port);
487 cmdio_write_ok(FTP_PORTOK); 453 cmdio_write_ok(FTP_PORTOK);
488} 454}
489 455
@@ -612,6 +578,8 @@ handle_dir_common(int opts)
612 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen()) 578 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
613 return; /* port_or_pasv_was_seen emitted error response */ 579 return; /* port_or_pasv_was_seen emitted error response */
614 580
581 /* -n prevents user/groupname display,
582 * which can be problematic in chroot */
615 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1"); 583 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
616 ls_fp = fdopen(ls_fd, "r"); 584 ls_fp = fdopen(ls_fd, "r");
617 if (!ls_fp) /* never happens. paranoia */ 585 if (!ls_fp) /* never happens. paranoia */
@@ -749,11 +717,12 @@ handle_rnto(void)
749static void 717static void
750handle_upload_common(int is_append, int is_unique) 718handle_upload_common(int is_append, int is_unique)
751{ 719{
752 char *tempname = NULL; 720 struct stat statbuf;
721 char *tempname;
753 off_t bytes_transferred; 722 off_t bytes_transferred;
723 off_t offset;
754 int local_file_fd; 724 int local_file_fd;
755 int remote_fd; 725 int remote_fd;
756 off_t offset;
757 726
758 offset = G.restart_pos; 727 offset = G.restart_pos;
759 G.restart_pos = 0; 728 G.restart_pos = 0;
@@ -761,6 +730,7 @@ handle_upload_common(int is_append, int is_unique)
761 if (!port_or_pasv_was_seen()) 730 if (!port_or_pasv_was_seen())
762 return; /* port_or_pasv_was_seen emitted error response */ 731 return; /* port_or_pasv_was_seen emitted error response */
763 732
733 tempname = NULL;
764 local_file_fd = -1; 734 local_file_fd = -1;
765 if (is_unique) { 735 if (is_unique) {
766 tempname = xstrdup(" FILE: uniq.XXXXXX"); 736 tempname = xstrdup(" FILE: uniq.XXXXXX");
@@ -773,13 +743,17 @@ handle_upload_common(int is_append, int is_unique)
773 flags = O_WRONLY | O_CREAT; 743 flags = O_WRONLY | O_CREAT;
774 local_file_fd = open(G.ftp_arg, flags, 0666); 744 local_file_fd = open(G.ftp_arg, flags, 0666);
775 } 745 }
776 if (local_file_fd < 0) { 746
747 if (local_file_fd < 0
748 || fstat(local_file_fd, &statbuf) != 0
749 || !S_ISREG(statbuf.st_mode)
750 ) {
777 cmdio_write_error(FTP_UPLOADFAIL); 751 cmdio_write_error(FTP_UPLOADFAIL);
752 if (local_file_fd >= 0)
753 goto close_local_and_bail;
778 return; 754 return;
779 } 755 }
780 756
781/* TODO: paranoia: fstat it, refuse to do anything if it's not a regular file */
782
783 if (offset) 757 if (offset)
784 xlseek(local_file_fd, offset, SEEK_SET); 758 xlseek(local_file_fd, offset, SEEK_SET);
785 759
@@ -787,7 +761,7 @@ handle_upload_common(int is_append, int is_unique)
787 free(tempname); 761 free(tempname);
788 762
789 if (remote_fd < 0) 763 if (remote_fd < 0)
790 goto bail; 764 goto close_local_and_bail;
791 765
792/* TODO: if we'll implement timeout, this will need more clever handling. 766/* TODO: if we'll implement timeout, this will need more clever handling.
793 * Perhaps alarm(N) + checking that current position on local_file_fd 767 * Perhaps alarm(N) + checking that current position on local_file_fd
@@ -800,7 +774,8 @@ handle_upload_common(int is_append, int is_unique)
800 cmdio_write_error(FTP_BADSENDFILE); 774 cmdio_write_error(FTP_BADSENDFILE);
801 else 775 else
802 cmdio_write_ok(FTP_TRANSFEROK); 776 cmdio_write_ok(FTP_TRANSFEROK);
803 bail: 777
778 close_local_and_bail:
804 close(local_file_fd); 779 close(local_file_fd);
805} 780}
806 781
@@ -838,17 +813,18 @@ cmdio_get_cmd_and_arg(void)
838 if (!cmd) 813 if (!cmd)
839 exit(0); 814 exit(0);
840 815
816 /* Trailing '\n' is already stripped, strip '\r' */
841 len = strlen(cmd) - 1; 817 len = strlen(cmd) - 1;
842 while (len >= 0 && cmd[len] == '\r') { 818 while ((ssize_t)len >= 0 && cmd[len] == '\r') {
843 cmd[len] = '\0'; 819 cmd[len] = '\0';
844 len--; 820 len--;
845 } 821 }
846 822
847 G.ftp_arg = strchr(cmd, ' '); 823 G.ftp_arg = strchr(cmd, ' ');
848 if (G.ftp_arg != NULL) { 824 if (G.ftp_arg != NULL)
849 *G.ftp_arg = '\0'; 825 *G.ftp_arg++ = '\0';
850 G.ftp_arg++; 826
851 } 827 /* Uppercase and pack into uint32_t first word of the command */
852 cmdval = 0; 828 cmdval = 0;
853 while (*cmd) 829 while (*cmd)
854 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20); 830 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
@@ -856,6 +832,47 @@ cmdio_get_cmd_and_arg(void)
856 return cmdval; 832 return cmdval;
857} 833}
858 834
835#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
836#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
837enum {
838 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
839 const_APPE = mk_const4('A', 'P', 'P', 'E'),
840 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
841 const_CWD = mk_const3('C', 'W', 'D'),
842 const_DELE = mk_const4('D', 'E', 'L', 'E'),
843 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
844 const_HELP = mk_const4('H', 'E', 'L', 'P'),
845 const_LIST = mk_const4('L', 'I', 'S', 'T'),
846 const_MKD = mk_const3('M', 'K', 'D'),
847 const_MODE = mk_const4('M', 'O', 'D', 'E'),
848 const_NLST = mk_const4('N', 'L', 'S', 'T'),
849 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
850 const_PASS = mk_const4('P', 'A', 'S', 'S'),
851 const_PASV = mk_const4('P', 'A', 'S', 'V'),
852 const_PORT = mk_const4('P', 'O', 'R', 'T'),
853 const_PWD = mk_const3('P', 'W', 'D'),
854 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
855 const_REST = mk_const4('R', 'E', 'S', 'T'),
856 const_RETR = mk_const4('R', 'E', 'T', 'R'),
857 const_RMD = mk_const3('R', 'M', 'D'),
858 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
859 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
860 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
861 const_STAT = mk_const4('S', 'T', 'A', 'T'),
862 const_STOR = mk_const4('S', 'T', 'O', 'R'),
863 const_STOU = mk_const4('S', 'T', 'O', 'U'),
864 const_STRU = mk_const4('S', 'T', 'R', 'U'),
865 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
866 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
867 const_USER = mk_const4('U', 'S', 'E', 'R'),
868
869 OPT_l = (1 << 0),
870 OPT_1 = (1 << 1),
871 OPT_v = (1 << 2),
872 OPT_S = (1 << 3),
873 OPT_w = (1 << 4),
874};
875
859int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 876int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
860int ftpd_main(int argc, char **argv) 877int ftpd_main(int argc, char **argv)
861{ 878{
@@ -865,6 +882,7 @@ int ftpd_main(int argc, char **argv)
865 882
866 if (opts & (OPT_l|OPT_1)) { 883 if (opts & (OPT_l|OPT_1)) {
867 /* Our secret backdoor to ls */ 884 /* Our secret backdoor to ls */
885/* TODO: pass -n too? */
868 xchdir(argv[2]); 886 xchdir(argv[2]);
869 argv[2] = (char*)"--"; 887 argv[2] = (char*)"--";
870 return ls_main(argc, argv); 888 return ls_main(argc, argv);