diff options
| -rw-r--r-- | networking/httpd.c | 62 | ||||
| -rw-r--r-- | networking/httpd_ratelimit_cgi.c | 4 |
2 files changed, 37 insertions, 29 deletions
diff --git a/networking/httpd.c b/networking/httpd.c index ed6b0682e..5101f86f7 100644 --- a/networking/httpd.c +++ b/networking/httpd.c | |||
| @@ -309,6 +309,12 @@ | |||
| 309 | * | 309 | * |
| 310 | * TODO: | 310 | * TODO: |
| 311 | * - do not allow all MAXCONN connections be taken up by the same remote IP. | 311 | * - do not allow all MAXCONN connections be taken up by the same remote IP. |
| 312 | * - CGI I/O loop lacks full timeout protection (may be abused by slow POST client?), | ||
| 313 | * see cgi_io_loop_and_exit() | ||
| 314 | * - sanity: add a limit how big POSTDATA can be? (-P 1024: "megabyte+ of POSTDATA is insanity") | ||
| 315 | * currently we only do: if (POST_length > INT_MAX) HTTP_BAD_REQUEST | ||
| 316 | * - sanity: kill CGIs which are obviously stuck? (-K 60: "if CGI isn't done in 1 minute, it's fishy. SIGTERM+SIGKILL") | ||
| 317 | * - sanity: measure CGI memory consumption (how?), kill when way too big? | ||
| 312 | */ | 318 | */ |
| 313 | #define HEADER_READ_TIMEOUT 30 | 319 | #define HEADER_READ_TIMEOUT 30 |
| 314 | #define DATA_WRITE_TIMEOUT 60 | 320 | #define DATA_WRITE_TIMEOUT 60 |
| @@ -1091,7 +1097,7 @@ static void log_and_exit(void) | |||
| 1091 | { | 1097 | { |
| 1092 | if (VERBOSE_3) | 1098 | if (VERBOSE_3) |
| 1093 | bb_simple_error_msg("closed"); | 1099 | bb_simple_error_msg("closed"); |
| 1094 | _exit(xfunc_error_retval); | 1100 | _exit_SUCCESS(); |
| 1095 | } | 1101 | } |
| 1096 | static void send_EOF_and_exit(void) NORETURN; | 1102 | static void send_EOF_and_exit(void) NORETURN; |
| 1097 | static void send_EOF_and_exit(void) | 1103 | static void send_EOF_and_exit(void) |
| @@ -1440,7 +1446,12 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post | |||
| 1440 | } | 1446 | } |
| 1441 | 1447 | ||
| 1442 | /* Now wait on the set of sockets */ | 1448 | /* Now wait on the set of sockets */ |
| 1449 | /* Poll whether TO_CGI is ready to accept writes *only* if we have some POSTDATA to give to it */ | ||
| 1443 | count = safe_poll(pfd, hdr_cnt > 0 ? TO_CGI+1 : FROM_CGI+1, -1); | 1450 | count = safe_poll(pfd, hdr_cnt > 0 ? TO_CGI+1 : FROM_CGI+1, -1); |
| 1451 | //FIXME:do not allow the client to stall POSTDATA: | ||
| 1452 | //the timeout must not be infinite, should be similar to DATA_WRITE_TIMEOUT | ||
| 1453 | //("give some data at least once a minute", better "give >=N bytes once minute") | ||
| 1454 | |||
| 1444 | if (count <= 0) { | 1455 | if (count <= 0) { |
| 1445 | #if 0 /* This doesn't work since we SIG_IGNed SIGCHLD (which means kernel auto-reaps our children) */ | 1456 | #if 0 /* This doesn't work since we SIG_IGNed SIGCHLD (which means kernel auto-reaps our children) */ |
| 1446 | if (VERBOSE_3) { | 1457 | if (VERBOSE_3) { |
| @@ -1479,15 +1490,15 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post | |||
| 1479 | if (pfd[0].revents) { | 1490 | if (pfd[0].revents) { |
| 1480 | /* post_len > 0 && hdr_cnt == 0 here */ | 1491 | /* post_len > 0 && hdr_cnt == 0 here */ |
| 1481 | /* We expect data, prev data portion is eaten by CGI | 1492 | /* We expect data, prev data portion is eaten by CGI |
| 1482 | * and there *is* data to read from the peer | 1493 | * and there *is* POSTDATA to read from the peer |
| 1483 | * (POSTDATA) */ | 1494 | */ |
| 1484 | //count = post_len > (int)sizeof_hdr_buf ? (int)sizeof_hdr_buf : post_len; | 1495 | //count = post_len > (int)sizeof_hdr_buf ? (int)sizeof_hdr_buf : post_len; |
| 1485 | //count = safe_read(STDIN_FILENO, hdr_buf, count); | 1496 | //count = safe_read(STDIN_FILENO, hdr_buf, count); |
| 1486 | count = safe_read(STDIN_FILENO, hdr_buf, sizeof_hdr_buf); | 1497 | count = safe_read(STDIN_FILENO, hdr_buf, sizeof_hdr_buf); |
| 1487 | if (count > 0) { | 1498 | if (count > 0) { |
| 1488 | hdr_cnt = count; | 1499 | hdr_cnt = count; |
| 1489 | hdr_ptr = hdr_buf; | 1500 | hdr_ptr = hdr_buf; |
| 1490 | post_len -= count; | 1501 | post_len -= count; /* can go negative (peer wrote more than expected POSTDATA) */ |
| 1491 | } else { | 1502 | } else { |
| 1492 | /* no more POST data can be read */ | 1503 | /* no more POST data can be read */ |
| 1493 | post_len = 0; | 1504 | post_len = 0; |
| @@ -1498,34 +1509,34 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post | |||
| 1498 | /* There is something to read from CGI */ | 1509 | /* There is something to read from CGI */ |
| 1499 | char *rbuf = iobuf; | 1510 | char *rbuf = iobuf; |
| 1500 | 1511 | ||
| 1501 | /* Are we still buffering CGI output? */ | 1512 | /* Still buffering CGI output? */ |
| 1502 | if (out_cnt >= 0) { | 1513 | if (out_cnt >= 0) { |
| 1503 | /* HTTP_200[] has single "\r\n" at the end. | 1514 | /* HTTP_200[] has single "\r\n" at the end. |
| 1504 | * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html, | 1515 | * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html, |
| 1505 | * CGI scripts MUST send their own header terminated by | 1516 | * CGI scripts MUST send their own header terminated by |
| 1506 | * empty line, then data. That's why we have only one | 1517 | * empty line, then data. That's why we have only one |
| 1507 | * <cr><lf> pair here. We will output "200 OK" line | 1518 | * <cr><lf> pair here. We will output "200 OK" line |
| 1508 | * if needed, but CGI still has to provide blank line | 1519 | * if needed, but CGI still has to provide the blank line |
| 1509 | * between header and body */ | 1520 | * between header and body */ |
| 1510 | 1521 | ||
| 1511 | /* Must use safe_read, not full_read, because | 1522 | /* Must use safe_read, not full_read, because |
| 1512 | * CGI may output a few first bytes and then wait | 1523 | * CGI may output a few first bytes and then wait |
| 1513 | * for POSTDATA without closing stdout. | 1524 | * for POSTDATA without closing stdout. |
| 1514 | * With full_read we may wait here forever. */ | 1525 | * With full_read we may wait here forever. */ |
| 1515 | count = safe_read(fromCgi_rd, rbuf + out_cnt, IOBUF_SIZE - 8); | 1526 | count = safe_read(fromCgi_rd, rbuf + out_cnt, IOBUF_SIZE); |
| 1516 | if (count <= 0) { | 1527 | if (count <= 0) { |
| 1517 | /* eof (or error) and there was no "HTTP", | 1528 | /* eof (or error) and there was no "HTTP" (tiny 0..3 bytes output), |
| 1518 | * send "HTTP/1.1 200 OK\r\n", then send received data */ | 1529 | * send "HTTP/1.1 200 OK\r\n", then send received 0..3 bytes */ |
| 1519 | if (out_cnt) { | 1530 | goto write_HTTP_200_OK; |
| 1520 | full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1); | 1531 | /* we'll read once more later, in "no longer buffering" |
| 1521 | full_write(STDOUT_FILENO, rbuf, out_cnt); | 1532 | * code path, get another EOF there and exit */ |
| 1522 | } | ||
| 1523 | send_EOF_and_exit(); /* CGI stdout is closed, exiting */ | ||
| 1524 | } | 1533 | } |
| 1525 | out_cnt += count; | 1534 | out_cnt += count; |
| 1526 | count = 0; | 1535 | count = 0; |
| 1527 | /* "Status" header format is: "Status: 302 Redirected\r\n" */ | 1536 | /* "Status" header format is: "Status: 302 Redirected\r\n" */ |
| 1528 | if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) { | 1537 | if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) { |
| 1538 | //FIXME: "Status: " is not required to be the first header! It can be anywhere! | ||
| 1539 | //FIXME: many servers also check "Location: ". If it exists but "Status: " does _not_, "302 Found" is assumed instead of "200 OK". | ||
| 1529 | /* send "HTTP/1.1 " */ | 1540 | /* send "HTTP/1.1 " */ |
| 1530 | if (full_write(STDOUT_FILENO, HTTP_200, 9) != 9) | 1541 | if (full_write(STDOUT_FILENO, HTTP_200, 9) != 9) |
| 1531 | break; | 1542 | break; |
| @@ -1534,29 +1545,31 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post | |||
| 1534 | count = out_cnt - 8; | 1545 | count = out_cnt - 8; |
| 1535 | out_cnt = -1; /* buffering off */ | 1546 | out_cnt = -1; /* buffering off */ |
| 1536 | } else if (out_cnt >= 4) { | 1547 | } else if (out_cnt >= 4) { |
| 1548 | //NB: Apache has no such autodetection. It always adds its own HTTP/ header, | ||
| 1549 | //unless the CGI name starts with "nph-", in which case it passes its output verbatim to network. | ||
| 1537 | /* Did CGI add "HTTP"? */ | 1550 | /* Did CGI add "HTTP"? */ |
| 1538 | if (memcmp(rbuf, HTTP_200, 4) != 0) { | 1551 | if (memcmp(rbuf, HTTP_200, 4) != 0) { |
| 1539 | /* there is no "HTTP", do it ourself */ | 1552 | write_HTTP_200_OK: |
| 1553 | /* there is no "HTTP", send "HTTP/1.1 200 OK\r\n" ourself */ | ||
| 1540 | if (full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1) | 1554 | if (full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1) |
| 1541 | break; | 1555 | break; |
| 1542 | } | 1556 | } |
| 1543 | /* Commented out: | 1557 | /* Used to add "Content-type: text/plain\r\n\r\n" here if CGI didn't, |
| 1544 | if (!strstr(rbuf, "ontent-")) { | 1558 | * but it's wrong. Counter-example of valid CGI without Content-type: |
| 1545 | full_write(s, "Content-type: text/plain\r\n\r\n", 28); | ||
| 1546 | } | ||
| 1547 | * Counter-example of valid CGI without Content-type: | ||
| 1548 | * echo -en "HTTP/1.1 302 Found\r\n" | 1559 | * echo -en "HTTP/1.1 302 Found\r\n" |
| 1549 | * echo -en "Location: http://www.busybox.net\r\n" | 1560 | * echo -en "Location: http://www.busybox.net\r\n" |
| 1550 | * echo -en "\r\n" | 1561 | * echo -en "\r\n" |
| 1551 | */ | 1562 | */ |
| 1552 | count = out_cnt; | 1563 | count = out_cnt; |
| 1553 | out_cnt = -1; /* buffering off */ | 1564 | out_cnt = -1; /* buffering off */ |
| 1565 | /* NB: we mishandle CGI writing "Stat","us..." in two separate writes */ | ||
| 1554 | } | 1566 | } |
| 1555 | } else { | 1567 | } else { |
| 1556 | count = safe_read(fromCgi_rd, rbuf, IOBUF_SIZE); | 1568 | count = safe_read(fromCgi_rd, rbuf, IOBUF_SIZE); |
| 1557 | if (count <= 0) | 1569 | if (count <= 0) |
| 1558 | send_EOF_and_exit(); /* eof (or error) */ | 1570 | send_EOF_and_exit(); /* eof (or error) */ |
| 1559 | } | 1571 | } |
| 1572 | //FIXME: many (most?) servers translate bare "\n" to "\r\n", often only in the headers, not body (the part after empty line) | ||
| 1560 | if (full_write(STDOUT_FILENO, rbuf, count) != count) | 1573 | if (full_write(STDOUT_FILENO, rbuf, count) != count) |
| 1561 | break; | 1574 | break; |
| 1562 | dbg("cgi read %d bytes: '%.*s'\n", count, count, rbuf); | 1575 | dbg("cgi read %d bytes: '%.*s'\n", count, count, rbuf); |
| @@ -1697,8 +1710,6 @@ static void send_cgi_and_exit( | |||
| 1697 | /* Child process */ | 1710 | /* Child process */ |
| 1698 | char *argv[3]; | 1711 | char *argv[3]; |
| 1699 | 1712 | ||
| 1700 | xfunc_error_retval = 242; | ||
| 1701 | |||
| 1702 | /* NB: close _first_, then move fds! */ | 1713 | /* NB: close _first_, then move fds! */ |
| 1703 | close(toCgi.wr); | 1714 | close(toCgi.wr); |
| 1704 | close(fromCgi.rd); | 1715 | close(fromCgi.rd); |
| @@ -1768,9 +1779,6 @@ static void send_cgi_and_exit( | |||
| 1768 | 1779 | ||
| 1769 | /* Parent process */ | 1780 | /* Parent process */ |
| 1770 | 1781 | ||
| 1771 | /* Restore variables possibly changed by child */ | ||
| 1772 | xfunc_error_retval = 0; | ||
| 1773 | |||
| 1774 | /* Pump data */ | 1782 | /* Pump data */ |
| 1775 | close(fromCgi.wr); | 1783 | close(fromCgi.wr); |
| 1776 | close(toCgi.rd); | 1784 | close(toCgi.rd); |
| @@ -2239,7 +2247,7 @@ static void send_REQUEST_TIMEOUT_and_exit(int sig UNUSED_PARAM) | |||
| 2239 | // /* writing timed out: exit without writing anything */ | 2247 | // /* writing timed out: exit without writing anything */ |
| 2240 | // if (VERBOSE_3) | 2248 | // if (VERBOSE_3) |
| 2241 | // bb_simple_error_msg("write timeout"); | 2249 | // bb_simple_error_msg("write timeout"); |
| 2242 | // _exit(xfunc_error_retval); | 2250 | // _exit_SUCCESS(); |
| 2243 | } | 2251 | } |
| 2244 | 2252 | ||
| 2245 | static void prepare_write_timeout(void) | 2253 | static void prepare_write_timeout(void) |
| @@ -2334,7 +2342,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
| 2334 | * just close the socket. | 2342 | * just close the socket. |
| 2335 | */ | 2343 | */ |
| 2336 | //send_headers_and_exit(HTTP_BAD_REQUEST); | 2344 | //send_headers_and_exit(HTTP_BAD_REQUEST); |
| 2337 | _exit(xfunc_error_retval); | 2345 | _exit_SUCCESS(); |
| 2338 | } | 2346 | } |
| 2339 | dbg("Request:'%s'\n", iobuf); | 2347 | dbg("Request:'%s'\n", iobuf); |
| 2340 | 2348 | ||
diff --git a/networking/httpd_ratelimit_cgi.c b/networking/httpd_ratelimit_cgi.c index 713274873..96702131e 100644 --- a/networking/httpd_ratelimit_cgi.c +++ b/networking/httpd_ratelimit_cgi.c | |||
| @@ -223,7 +223,7 @@ int main(int argc, char **argv) | |||
| 223 | } | 223 | } |
| 224 | 224 | ||
| 225 | /* No slot available, return 429 */ | 225 | /* No slot available, return 429 */ |
| 226 | write_and_die(1, "HTTP/1.1 429 Too Many Requests\r\n" | 226 | write_and_die(1, "Status: 429 Too Many Requests\r\n" |
| 227 | "Content-Type: text/plain\r\n" | 227 | "Content-Type: text/plain\r\n" |
| 228 | "Retry-After: 60\r\n" | 228 | "Retry-After: 60\r\n" |
| 229 | "Connection: close\r\n\r\n" | 229 | "Connection: close\r\n\r\n" |
| @@ -235,7 +235,7 @@ int main(int argc, char **argv) | |||
| 235 | exec: | 235 | exec: |
| 236 | execv(argv[0], argv); | 236 | execv(argv[0], argv); |
| 237 | full_write2(2, "can't execute", argv[0]); | 237 | full_write2(2, "can't execute", argv[0]); |
| 238 | write_and_die(1, "HTTP/1.1 500 Internal Server Error\r\n" | 238 | write_and_die(1, "Status: 500 Internal Server Error\r\n" |
| 239 | "Content-Type: text/plain\r\n\r\n" | 239 | "Content-Type: text/plain\r\n\r\n" |
| 240 | "Failed to execute binary\n"); | 240 | "Failed to execute binary\n"); |
| 241 | return 1; | 241 | return 1; |
