diff options
Diffstat (limited to 'networking/httpd.c')
-rw-r--r-- | networking/httpd.c | 376 |
1 files changed, 192 insertions, 184 deletions
diff --git a/networking/httpd.c b/networking/httpd.c index b52526a78..2b0acd7dc 100644 --- a/networking/httpd.c +++ b/networking/httpd.c | |||
@@ -95,9 +95,7 @@ | |||
95 | * If -c is not set, an attempt will be made to open the default | 95 | * If -c is not set, an attempt will be made to open the default |
96 | * root configuration file. If -c is set and the file is not found, the | 96 | * root configuration file. If -c is set and the file is not found, the |
97 | * server exits with an error. | 97 | * server exits with an error. |
98 | * | ||
99 | */ | 98 | */ |
100 | /* TODO: use TCP_CORK, parse_config() */ | ||
101 | //config:config HTTPD | 99 | //config:config HTTPD |
102 | //config: bool "httpd (32 kb)" | 100 | //config: bool "httpd (32 kb)" |
103 | //config: default y | 101 | //config: default y |
@@ -246,6 +244,8 @@ | |||
246 | //usage: "\n -e STRING HTML encode STRING" | 244 | //usage: "\n -e STRING HTML encode STRING" |
247 | //usage: "\n -d STRING URL decode STRING" | 245 | //usage: "\n -d STRING URL decode STRING" |
248 | 246 | ||
247 | /* TODO: use TCP_CORK, parse_config() */ | ||
248 | |||
249 | #include "libbb.h" | 249 | #include "libbb.h" |
250 | #include "common_bufsiz.h" | 250 | #include "common_bufsiz.h" |
251 | #if ENABLE_PAM | 251 | #if ENABLE_PAM |
@@ -267,6 +267,7 @@ | |||
267 | #define DEBUG 0 | 267 | #define DEBUG 0 |
268 | 268 | ||
269 | #define IOBUF_SIZE 8192 | 269 | #define IOBUF_SIZE 8192 |
270 | #define MAX_HTTP_HEADERS_SIZE ((8*1024) - 16) | ||
270 | #if PIPE_BUF >= IOBUF_SIZE | 271 | #if PIPE_BUF >= IOBUF_SIZE |
271 | # error "PIPE_BUF >= IOBUF_SIZE" | 272 | # error "PIPE_BUF >= IOBUF_SIZE" |
272 | #endif | 273 | #endif |
@@ -305,6 +306,13 @@ typedef struct Htaccess_Proxy { | |||
305 | char *url_to; | 306 | char *url_to; |
306 | } Htaccess_Proxy; | 307 | } Htaccess_Proxy; |
307 | 308 | ||
309 | typedef enum CGI_type { | ||
310 | CGI_NONE = 0, | ||
311 | CGI_NORMAL, | ||
312 | CGI_INDEX, | ||
313 | CGI_INTERPRETER, | ||
314 | } CGI_type; | ||
315 | |||
308 | enum { | 316 | enum { |
309 | HTTP_OK = 200, | 317 | HTTP_OK = 200, |
310 | HTTP_PARTIAL_CONTENT = 206, | 318 | HTTP_PARTIAL_CONTENT = 206, |
@@ -316,6 +324,7 @@ enum { | |||
316 | HTTP_REQUEST_TIMEOUT = 408, | 324 | HTTP_REQUEST_TIMEOUT = 408, |
317 | HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */ | 325 | HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */ |
318 | HTTP_INTERNAL_SERVER_ERROR = 500, | 326 | HTTP_INTERNAL_SERVER_ERROR = 500, |
327 | HTTP_ENTITY_TOO_LARGE = 413, | ||
319 | HTTP_CONTINUE = 100, | 328 | HTTP_CONTINUE = 100, |
320 | #if 0 /* future use */ | 329 | #if 0 /* future use */ |
321 | HTTP_SWITCHING_PROTOCOLS = 101, | 330 | HTTP_SWITCHING_PROTOCOLS = 101, |
@@ -347,6 +356,7 @@ static const uint16_t http_response_type[] ALIGN2 = { | |||
347 | HTTP_BAD_REQUEST, | 356 | HTTP_BAD_REQUEST, |
348 | HTTP_FORBIDDEN, | 357 | HTTP_FORBIDDEN, |
349 | HTTP_INTERNAL_SERVER_ERROR, | 358 | HTTP_INTERNAL_SERVER_ERROR, |
359 | HTTP_ENTITY_TOO_LARGE, | ||
350 | #if 0 /* not implemented */ | 360 | #if 0 /* not implemented */ |
351 | HTTP_CREATED, | 361 | HTTP_CREATED, |
352 | HTTP_ACCEPTED, | 362 | HTTP_ACCEPTED, |
@@ -377,6 +387,7 @@ static const struct { | |||
377 | { "Bad Request", "Unsupported method" }, | 387 | { "Bad Request", "Unsupported method" }, |
378 | { "Forbidden", "" }, | 388 | { "Forbidden", "" }, |
379 | { "Internal Server Error", "Internal Server Error" }, | 389 | { "Internal Server Error", "Internal Server Error" }, |
390 | { "Entity Too Large", "Entity Too Large" }, | ||
380 | #if 0 /* not implemented */ | 391 | #if 0 /* not implemented */ |
381 | { "Created" }, | 392 | { "Created" }, |
382 | { "Accepted" }, | 393 | { "Accepted" }, |
@@ -396,12 +407,11 @@ struct globals { | |||
396 | /* client can handle gzip / we are going to send gzip */ | 407 | /* client can handle gzip / we are going to send gzip */ |
397 | smallint content_gzip; | 408 | smallint content_gzip; |
398 | #endif | 409 | #endif |
399 | unsigned rmt_ip; /* used for IP-based allow/deny rules */ | ||
400 | time_t last_mod; | 410 | time_t last_mod; |
401 | char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */ | 411 | char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */ |
402 | const char *bind_addr_or_port; | 412 | const char *bind_addr_or_port; |
403 | 413 | ||
404 | const char *g_query; | 414 | char *g_query; |
405 | const char *opt_c_configFile; | 415 | const char *opt_c_configFile; |
406 | const char *home_httpd; | 416 | const char *home_httpd; |
407 | const char *index_page; | 417 | const char *index_page; |
@@ -412,11 +422,6 @@ struct globals { | |||
412 | 422 | ||
413 | IF_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;) | 423 | IF_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;) |
414 | IF_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;) | 424 | IF_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;) |
415 | IF_FEATURE_HTTPD_CGI(char *referer;) | ||
416 | IF_FEATURE_HTTPD_CGI(char *user_agent;) | ||
417 | IF_FEATURE_HTTPD_CGI(char *host;) | ||
418 | IF_FEATURE_HTTPD_CGI(char *http_accept;) | ||
419 | IF_FEATURE_HTTPD_CGI(char *http_accept_language;) | ||
420 | 425 | ||
421 | off_t file_size; /* -1 - unknown */ | 426 | off_t file_size; /* -1 - unknown */ |
422 | #if ENABLE_FEATURE_HTTPD_RANGES | 427 | #if ENABLE_FEATURE_HTTPD_RANGES |
@@ -452,7 +457,6 @@ struct globals { | |||
452 | #else | 457 | #else |
453 | # define content_gzip 0 | 458 | # define content_gzip 0 |
454 | #endif | 459 | #endif |
455 | #define rmt_ip (G.rmt_ip ) | ||
456 | #define bind_addr_or_port (G.bind_addr_or_port) | 460 | #define bind_addr_or_port (G.bind_addr_or_port) |
457 | #define g_query (G.g_query ) | 461 | #define g_query (G.g_query ) |
458 | #define opt_c_configFile (G.opt_c_configFile ) | 462 | #define opt_c_configFile (G.opt_c_configFile ) |
@@ -1194,37 +1198,39 @@ static void send_headers_and_exit(int responseNum) | |||
1194 | } | 1198 | } |
1195 | 1199 | ||
1196 | /* | 1200 | /* |
1197 | * Read from the socket until '\n' or EOF. '\r' chars are removed. | 1201 | * Read from the socket until '\n' or EOF. |
1202 | * '\r' chars are removed. | ||
1198 | * '\n' is replaced with NUL. | 1203 | * '\n' is replaced with NUL. |
1199 | * Return number of characters read or 0 if nothing is read | 1204 | * Return number of characters read or 0 if nothing is read |
1200 | * ('\r' and '\n' are not counted). | 1205 | * ('\r' and '\n' are not counted). |
1201 | * Data is returned in iobuf. | 1206 | * Data is returned in iobuf. |
1202 | */ | 1207 | */ |
1203 | static int get_line(void) | 1208 | static unsigned get_line(void) |
1204 | { | 1209 | { |
1205 | int count = 0; | 1210 | unsigned count; |
1206 | char c; | 1211 | char c; |
1207 | 1212 | ||
1208 | alarm(HEADER_READ_TIMEOUT); | 1213 | alarm(HEADER_READ_TIMEOUT); |
1214 | count = 0; | ||
1209 | while (1) { | 1215 | while (1) { |
1210 | if (hdr_cnt <= 0) { | 1216 | if (hdr_cnt <= 0) { |
1211 | hdr_cnt = safe_read(STDIN_FILENO, hdr_buf, sizeof_hdr_buf); | 1217 | hdr_cnt = safe_read(STDIN_FILENO, hdr_buf, sizeof_hdr_buf); |
1212 | if (hdr_cnt <= 0) | 1218 | if (hdr_cnt <= 0) |
1213 | break; | 1219 | goto ret; |
1214 | hdr_ptr = hdr_buf; | 1220 | hdr_ptr = hdr_buf; |
1215 | } | 1221 | } |
1216 | iobuf[count] = c = *hdr_ptr++; | ||
1217 | hdr_cnt--; | 1222 | hdr_cnt--; |
1218 | 1223 | c = *hdr_ptr++; | |
1219 | if (c == '\r') | 1224 | if (c == '\r') |
1220 | continue; | 1225 | continue; |
1221 | if (c == '\n') { | 1226 | if (c == '\n') |
1222 | iobuf[count] = '\0'; | ||
1223 | break; | 1227 | break; |
1224 | } | 1228 | iobuf[count] = c; |
1225 | if (count < (IOBUF_SIZE - 1)) /* check overflow */ | 1229 | if (count < (IOBUF_SIZE - 1)) /* check overflow */ |
1226 | count++; | 1230 | count++; |
1227 | } | 1231 | } |
1232 | ret: | ||
1233 | iobuf[count] = '\0'; | ||
1228 | return count; | 1234 | return count; |
1229 | } | 1235 | } |
1230 | 1236 | ||
@@ -1437,23 +1443,17 @@ static void setenv1(const char *name, const char *value) | |||
1437 | * const char *url The requested URL (with leading /). | 1443 | * const char *url The requested URL (with leading /). |
1438 | * const char *orig_uri The original URI before rewriting (if any) | 1444 | * const char *orig_uri The original URI before rewriting (if any) |
1439 | * int post_len Length of the POST body. | 1445 | * int post_len Length of the POST body. |
1440 | * const char *cookie For set HTTP_COOKIE. | ||
1441 | * const char *content_type For set CONTENT_TYPE. | ||
1442 | */ | 1446 | */ |
1443 | static void send_cgi_and_exit( | 1447 | static void send_cgi_and_exit( |
1444 | const char *url, | 1448 | const char *url, |
1445 | const char *orig_uri, | 1449 | const char *orig_uri, |
1446 | const char *request, | 1450 | const char *request, |
1447 | int post_len, | 1451 | int post_len) NORETURN; |
1448 | const char *cookie, | ||
1449 | const char *content_type) NORETURN; | ||
1450 | static void send_cgi_and_exit( | 1452 | static void send_cgi_and_exit( |
1451 | const char *url, | 1453 | const char *url, |
1452 | const char *orig_uri, | 1454 | const char *orig_uri, |
1453 | const char *request, | 1455 | const char *request, |
1454 | int post_len, | 1456 | int post_len) |
1455 | const char *cookie, | ||
1456 | const char *content_type) | ||
1457 | { | 1457 | { |
1458 | struct fd_pair fromCgi; /* CGI -> httpd pipe */ | 1458 | struct fd_pair fromCgi; /* CGI -> httpd pipe */ |
1459 | struct fd_pair toCgi; /* httpd -> CGI pipe */ | 1459 | struct fd_pair toCgi; /* httpd -> CGI pipe */ |
@@ -1531,26 +1531,14 @@ static void send_cgi_and_exit( | |||
1531 | #endif | 1531 | #endif |
1532 | } | 1532 | } |
1533 | } | 1533 | } |
1534 | setenv1("HTTP_USER_AGENT", G.user_agent); | ||
1535 | if (G.http_accept) | ||
1536 | setenv1("HTTP_ACCEPT", G.http_accept); | ||
1537 | if (G.http_accept_language) | ||
1538 | setenv1("HTTP_ACCEPT_LANGUAGE", G.http_accept_language); | ||
1539 | if (post_len) | 1534 | if (post_len) |
1540 | putenv(xasprintf("CONTENT_LENGTH=%u", post_len)); | 1535 | putenv(xasprintf("CONTENT_LENGTH=%u", post_len)); |
1541 | if (cookie) | ||
1542 | setenv1("HTTP_COOKIE", cookie); | ||
1543 | if (content_type) | ||
1544 | setenv1("CONTENT_TYPE", content_type); | ||
1545 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH | 1536 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
1546 | if (remoteuser) { | 1537 | if (remoteuser) { |
1547 | setenv1("REMOTE_USER", remoteuser); | 1538 | setenv1("REMOTE_USER", remoteuser); |
1548 | putenv((char*)"AUTH_TYPE=Basic"); | 1539 | putenv((char*)"AUTH_TYPE=Basic"); |
1549 | } | 1540 | } |
1550 | #endif | 1541 | #endif |
1551 | if (G.referer) | ||
1552 | setenv1("HTTP_REFERER", G.referer); | ||
1553 | setenv1("HTTP_HOST", G.host); /* set to "" if NULL */ | ||
1554 | /* setenv1("SERVER_NAME", safe_gethostname()); - don't do this, | 1542 | /* setenv1("SERVER_NAME", safe_gethostname()); - don't do this, |
1555 | * just run "env SERVER_NAME=xyz httpd ..." instead */ | 1543 | * just run "env SERVER_NAME=xyz httpd ..." instead */ |
1556 | 1544 | ||
@@ -1817,7 +1805,7 @@ static NOINLINE void send_file_and_exit(const char *url, int what) | |||
1817 | log_and_exit(); | 1805 | log_and_exit(); |
1818 | } | 1806 | } |
1819 | 1807 | ||
1820 | static int checkPermIP(void) | 1808 | static void if_ip_denied_send_HTTP_FORBIDDEN_and_exit(unsigned remote_ip) |
1821 | { | 1809 | { |
1822 | Htaccess_IP *cur; | 1810 | Htaccess_IP *cur; |
1823 | 1811 | ||
@@ -1836,11 +1824,15 @@ static int checkPermIP(void) | |||
1836 | (unsigned char)(cur->mask) | 1824 | (unsigned char)(cur->mask) |
1837 | ); | 1825 | ); |
1838 | #endif | 1826 | #endif |
1839 | if ((rmt_ip & cur->mask) == cur->ip) | 1827 | if ((remote_ip & cur->mask) == cur->ip) { |
1840 | return (cur->allow_deny == 'A'); /* A -> 1 */ | 1828 | if (cur->allow_deny == 'A') |
1829 | return; | ||
1830 | send_headers_and_exit(HTTP_FORBIDDEN); | ||
1831 | } | ||
1841 | } | 1832 | } |
1842 | 1833 | ||
1843 | return !flg_deny_all; /* depends on whether we saw "D:*" */ | 1834 | if (flg_deny_all) /* depends on whether we saw "D:*" */ |
1835 | send_headers_and_exit(HTTP_FORBIDDEN); | ||
1844 | } | 1836 | } |
1845 | 1837 | ||
1846 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH | 1838 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
@@ -2077,12 +2069,15 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2077 | char *urlcopy; | 2069 | char *urlcopy; |
2078 | char *urlp; | 2070 | char *urlp; |
2079 | char *tptr; | 2071 | char *tptr; |
2072 | unsigned remote_ip; | ||
2073 | #if ENABLE_FEATURE_HTTPD_CGI | ||
2074 | unsigned total_headers_len; | ||
2075 | #endif | ||
2080 | #if ENABLE_FEATURE_HTTPD_CGI | 2076 | #if ENABLE_FEATURE_HTTPD_CGI |
2081 | static const char request_HEAD[] ALIGN1 = "HEAD"; | 2077 | static const char request_HEAD[] ALIGN1 = "HEAD"; |
2082 | const char *prequest; | 2078 | const char *prequest; |
2083 | char *cookie = NULL; | ||
2084 | char *content_type = NULL; | ||
2085 | unsigned long length = 0; | 2079 | unsigned long length = 0; |
2080 | enum CGI_type cgi_type = CGI_NONE; | ||
2086 | #elif ENABLE_FEATURE_HTTPD_PROXY | 2081 | #elif ENABLE_FEATURE_HTTPD_PROXY |
2087 | #define prequest request_GET | 2082 | #define prequest request_GET |
2088 | unsigned long length = 0; | 2083 | unsigned long length = 0; |
@@ -2090,29 +2085,23 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2090 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH | 2085 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
2091 | smallint authorized = -1; | 2086 | smallint authorized = -1; |
2092 | #endif | 2087 | #endif |
2093 | smallint ip_allowed; | ||
2094 | char http_major_version; | 2088 | char http_major_version; |
2095 | #if ENABLE_FEATURE_HTTPD_PROXY | 2089 | char *HTTP_slash; |
2096 | char http_minor_version; | ||
2097 | char *header_buf = header_buf; /* for gcc */ | ||
2098 | char *header_ptr = header_ptr; | ||
2099 | Htaccess_Proxy *proxy_entry; | ||
2100 | #endif | ||
2101 | 2090 | ||
2102 | /* Allocation of iobuf is postponed until now | 2091 | /* Allocation of iobuf is postponed until now |
2103 | * (IOW, server process doesn't need to waste 8k) */ | 2092 | * (IOW, server process doesn't need to waste 8k) */ |
2104 | iobuf = xmalloc(IOBUF_SIZE); | 2093 | iobuf = xmalloc(IOBUF_SIZE); |
2105 | 2094 | ||
2106 | rmt_ip = 0; | 2095 | remote_ip = 0; |
2107 | if (fromAddr->u.sa.sa_family == AF_INET) { | 2096 | if (fromAddr->u.sa.sa_family == AF_INET) { |
2108 | rmt_ip = ntohl(fromAddr->u.sin.sin_addr.s_addr); | 2097 | remote_ip = ntohl(fromAddr->u.sin.sin_addr.s_addr); |
2109 | } | 2098 | } |
2110 | #if ENABLE_FEATURE_IPV6 | 2099 | #if ENABLE_FEATURE_IPV6 |
2111 | if (fromAddr->u.sa.sa_family == AF_INET6 | 2100 | if (fromAddr->u.sa.sa_family == AF_INET6 |
2112 | && fromAddr->u.sin6.sin6_addr.s6_addr32[0] == 0 | 2101 | && fromAddr->u.sin6.sin6_addr.s6_addr32[0] == 0 |
2113 | && fromAddr->u.sin6.sin6_addr.s6_addr32[1] == 0 | 2102 | && fromAddr->u.sin6.sin6_addr.s6_addr32[1] == 0 |
2114 | && ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[2]) == 0xffff) | 2103 | && ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[2]) == 0xffff) |
2115 | rmt_ip = ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[3]); | 2104 | remote_ip = ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[3]); |
2116 | #endif | 2105 | #endif |
2117 | if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) { | 2106 | if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) { |
2118 | /* NB: can be NULL (user runs httpd -i by hand?) */ | 2107 | /* NB: can be NULL (user runs httpd -i by hand?) */ |
@@ -2125,6 +2114,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2125 | if (verbose > 2) | 2114 | if (verbose > 2) |
2126 | bb_error_msg("connected"); | 2115 | bb_error_msg("connected"); |
2127 | } | 2116 | } |
2117 | if_ip_denied_send_HTTP_FORBIDDEN_and_exit(remote_ip); | ||
2128 | 2118 | ||
2129 | /* Install timeout handler. get_line() needs it. */ | 2119 | /* Install timeout handler. get_line() needs it. */ |
2130 | signal(SIGALRM, send_REQUEST_TIMEOUT_and_exit); | 2120 | signal(SIGALRM, send_REQUEST_TIMEOUT_and_exit); |
@@ -2159,30 +2149,58 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2159 | send_headers_and_exit(HTTP_BAD_REQUEST); | 2149 | send_headers_and_exit(HTTP_BAD_REQUEST); |
2160 | 2150 | ||
2161 | /* Find end of URL and parse HTTP version, if any */ | 2151 | /* Find end of URL and parse HTTP version, if any */ |
2162 | http_major_version = '0'; | 2152 | //TODO: maybe just reject all queries which have no " HTTP/xyz" suffix? |
2163 | IF_FEATURE_HTTPD_PROXY(http_minor_version = '0';) | 2153 | //Then 'http_major_version' can be deleted |
2164 | tptr = strchrnul(urlp, ' '); | 2154 | http_major_version = ('0' - 1); /* "less than 0th" version */ |
2155 | HTTP_slash = strchrnul(urlp, ' '); | ||
2165 | /* Is it " HTTP/"? */ | 2156 | /* Is it " HTTP/"? */ |
2166 | if (tptr[0] && strncmp(tptr + 1, HTTP_200, 5) == 0) { | 2157 | if (HTTP_slash[0] && strncmp(HTTP_slash + 1, HTTP_200, 5) == 0) { |
2167 | http_major_version = tptr[6]; | 2158 | http_major_version = HTTP_slash[6]; |
2168 | IF_FEATURE_HTTPD_PROXY(http_minor_version = tptr[8];) | 2159 | *HTTP_slash++ = '\0'; |
2169 | } | 2160 | } |
2170 | *tptr = '\0'; | ||
2171 | 2161 | ||
2172 | /* Copy URL from after "GET "/"POST " to stack-allocated char[] */ | 2162 | /* Copy URL from after "GET "/"POST " to stack-allocated char[] */ |
2173 | urlcopy = alloca((tptr - urlp) + 2 + strlen(index_page)); | 2163 | urlcopy = alloca((HTTP_slash - urlp) + 2 + strlen(index_page)); |
2174 | /*if (urlcopy == NULL) | 2164 | /*if (urlcopy == NULL) |
2175 | * send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);*/ | 2165 | * send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);*/ |
2176 | strcpy(urlcopy, urlp); | 2166 | strcpy(urlcopy, urlp); |
2177 | /* NB: urlcopy ptr is never changed after this */ | 2167 | /* NB: urlcopy ptr is never changed after this */ |
2178 | 2168 | ||
2179 | /* Extract url args if present */ | 2169 | #if ENABLE_FEATURE_HTTPD_PROXY |
2180 | /* g_query = NULL; - already is */ | 2170 | { |
2181 | tptr = strchr(urlcopy, '?'); | 2171 | int proxy_fd; |
2182 | if (tptr) { | 2172 | len_and_sockaddr *lsa; |
2183 | *tptr++ = '\0'; | 2173 | Htaccess_Proxy *proxy_entry = find_proxy_entry(urlcopy); |
2184 | g_query = tptr; | 2174 | |
2175 | if (proxy_entry) { | ||
2176 | lsa = host2sockaddr(proxy_entry->host_port, 80); | ||
2177 | if (!lsa) | ||
2178 | send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); | ||
2179 | proxy_fd = socket(lsa->u.sa.sa_family, SOCK_STREAM, 0); | ||
2180 | if (proxy_fd < 0) | ||
2181 | send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); | ||
2182 | if (connect(proxy_fd, &lsa->u.sa, lsa->len) < 0) | ||
2183 | send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); | ||
2184 | /* Config directive was of the form: | ||
2185 | * P:/url:[http://]hostname[:port]/new/path | ||
2186 | * When /urlSFX is requested, reverse proxy it | ||
2187 | * to http://hostname[:port]/new/pathSFX | ||
2188 | */ | ||
2189 | fdprintf(proxy_fd, "%s %s%s %s\r\n", | ||
2190 | prequest, /* "GET" or "POST" */ | ||
2191 | proxy_entry->url_to, /* "/new/path" */ | ||
2192 | urlcopy + strlen(proxy_entry->url_from), /* "SFX" */ | ||
2193 | HTTP_slash /* HTTP/xyz" or "" */ | ||
2194 | ); | ||
2195 | cgi_io_loop_and_exit(proxy_fd, proxy_fd, /*max POST length:*/ INT_MAX); | ||
2196 | } | ||
2185 | } | 2197 | } |
2198 | #endif | ||
2199 | |||
2200 | /* Extract url args if present */ | ||
2201 | g_query = strchr(urlcopy, '?'); | ||
2202 | if (g_query) | ||
2203 | *g_query++ = '\0'; | ||
2186 | 2204 | ||
2187 | /* Decode URL escape sequences */ | 2205 | /* Decode URL escape sequences */ |
2188 | tptr = percent_decode_in_place(urlcopy, /*strict:*/ 1); | 2206 | tptr = percent_decode_in_place(urlcopy, /*strict:*/ 1); |
@@ -2222,7 +2240,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2222 | } | 2240 | } |
2223 | } | 2241 | } |
2224 | *++urlp = *tptr; | 2242 | *++urlp = *tptr; |
2225 | if (*urlp == '\0') | 2243 | if (*tptr == '\0') |
2226 | break; | 2244 | break; |
2227 | next_char: | 2245 | next_char: |
2228 | tptr++; | 2246 | tptr++; |
@@ -2240,47 +2258,87 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2240 | bb_error_msg("url:%s", urlcopy); | 2258 | bb_error_msg("url:%s", urlcopy); |
2241 | 2259 | ||
2242 | tptr = urlcopy; | 2260 | tptr = urlcopy; |
2243 | ip_allowed = checkPermIP(); | 2261 | while ((tptr = strchr(tptr + 1, '/')) != NULL) { |
2244 | while (ip_allowed && (tptr = strchr(tptr + 1, '/')) != NULL) { | ||
2245 | /* have path1/path2 */ | 2262 | /* have path1/path2 */ |
2246 | *tptr = '\0'; | 2263 | *tptr = '\0'; |
2247 | if (is_directory(urlcopy + 1, /*followlinks:*/ 1)) { | 2264 | if (is_directory(urlcopy + 1, /*followlinks:*/ 1)) { |
2248 | /* may have subdir config */ | 2265 | /* may have subdir config */ |
2249 | parse_conf(urlcopy + 1, SUBDIR_PARSE); | 2266 | parse_conf(urlcopy + 1, SUBDIR_PARSE); |
2250 | ip_allowed = checkPermIP(); | 2267 | if_ip_denied_send_HTTP_FORBIDDEN_and_exit(remote_ip); |
2251 | } | 2268 | } |
2252 | *tptr = '/'; | 2269 | *tptr = '/'; |
2253 | } | 2270 | } |
2254 | 2271 | ||
2255 | #if ENABLE_FEATURE_HTTPD_PROXY | 2272 | tptr = urlcopy + 1; /* skip first '/' */ |
2256 | proxy_entry = find_proxy_entry(urlcopy); | 2273 | |
2257 | if (proxy_entry) | 2274 | #if ENABLE_FEATURE_HTTPD_CGI |
2258 | header_buf = header_ptr = xmalloc(IOBUF_SIZE); | 2275 | if (is_prefixed_with(tptr, "cgi-bin/")) { |
2276 | if (tptr[8] == '\0') { | ||
2277 | /* protect listing "cgi-bin/" */ | ||
2278 | send_headers_and_exit(HTTP_FORBIDDEN); | ||
2279 | } | ||
2280 | cgi_type = CGI_NORMAL; | ||
2281 | } | ||
2282 | #endif | ||
2283 | |||
2284 | if (urlp[-1] == '/') { | ||
2285 | /* When index_page string is appended to <dir>/ URL, it overwrites | ||
2286 | * the query string. If we fall back to call /cgi-bin/index.cgi, | ||
2287 | * query string would be lost and not available to the CGI. | ||
2288 | * Work around it by making a deep copy. | ||
2289 | */ | ||
2290 | if (ENABLE_FEATURE_HTTPD_CGI) | ||
2291 | g_query = xstrdup(g_query); /* ok for NULL too */ | ||
2292 | strcpy(urlp, index_page); | ||
2293 | } | ||
2294 | if (stat(tptr, &sb) == 0) { | ||
2295 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR | ||
2296 | char *suffix = strrchr(tptr, '.'); | ||
2297 | if (suffix) { | ||
2298 | Htaccess *cur; | ||
2299 | for (cur = script_i; cur; cur = cur->next) { | ||
2300 | if (strcmp(cur->before_colon + 1, suffix) == 0) { | ||
2301 | cgi_type = CGI_INTERPRETER; | ||
2302 | break; | ||
2303 | } | ||
2304 | } | ||
2305 | } | ||
2306 | #endif | ||
2307 | if (!found_moved_temporarily) { | ||
2308 | file_size = sb.st_size; | ||
2309 | last_mod = sb.st_mtime; | ||
2310 | } | ||
2311 | } | ||
2312 | #if ENABLE_FEATURE_HTTPD_CGI | ||
2313 | else if (urlp[-1] == '/') { | ||
2314 | /* It's a dir URL and there is no index.html | ||
2315 | * Try cgi-bin/index.cgi */ | ||
2316 | if (access("/cgi-bin/index.cgi"+1, X_OK) == 0) { | ||
2317 | cgi_type = CGI_INDEX; | ||
2318 | } | ||
2319 | } | ||
2259 | #endif | 2320 | #endif |
2321 | urlp[0] = '\0'; | ||
2260 | 2322 | ||
2323 | #if ENABLE_FEATURE_HTTPD_CGI | ||
2324 | total_headers_len = 0; | ||
2325 | #endif | ||
2261 | if (http_major_version >= '0') { | 2326 | if (http_major_version >= '0') { |
2262 | /* Request was with "... HTTP/nXXX", and n >= 0 */ | 2327 | /* Request was with "... HTTP/nXXX", and n >= 0 */ |
2263 | 2328 | ||
2264 | /* Read until blank line */ | 2329 | /* Read until blank line */ |
2265 | while (1) { | 2330 | while (1) { |
2266 | if (!get_line()) | 2331 | unsigned iobuf_len = get_line(); |
2332 | if (!iobuf_len) | ||
2267 | break; /* EOF or error or empty line */ | 2333 | break; /* EOF or error or empty line */ |
2334 | #if ENABLE_FEATURE_HTTPD_CGI | ||
2335 | /* Prevent unlimited growth of HTTP_xyz envvars */ | ||
2336 | total_headers_len += iobuf_len; | ||
2337 | if (total_headers_len >= MAX_HTTP_HEADERS_SIZE) | ||
2338 | send_headers_and_exit(HTTP_ENTITY_TOO_LARGE); | ||
2339 | #endif | ||
2268 | if (DEBUG) | 2340 | if (DEBUG) |
2269 | bb_error_msg("header: '%s'", iobuf); | 2341 | bb_error_msg("header: '%s'", iobuf); |
2270 | |||
2271 | #if ENABLE_FEATURE_HTTPD_PROXY | ||
2272 | /* We need 2 more bytes for yet another "\r\n" - | ||
2273 | * see near fdprintf(proxy_fd...) further below */ | ||
2274 | if (proxy_entry && (header_ptr - header_buf) < IOBUF_SIZE - 4) { | ||
2275 | int len = strnlen(iobuf, IOBUF_SIZE - (header_ptr - header_buf) - 4); | ||
2276 | memcpy(header_ptr, iobuf, len); | ||
2277 | header_ptr += len; | ||
2278 | header_ptr[0] = '\r'; | ||
2279 | header_ptr[1] = '\n'; | ||
2280 | header_ptr += 2; | ||
2281 | } | ||
2282 | #endif | ||
2283 | |||
2284 | #if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY | 2342 | #if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY |
2285 | /* Try and do our best to parse more lines */ | 2343 | /* Try and do our best to parse more lines */ |
2286 | if ((STRNCASECMP(iobuf, "Content-Length:") == 0)) { | 2344 | if ((STRNCASECMP(iobuf, "Content-Length:") == 0)) { |
@@ -2299,30 +2357,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2299 | if (errno || length > INT_MAX) | 2357 | if (errno || length > INT_MAX) |
2300 | send_headers_and_exit(HTTP_BAD_REQUEST); | 2358 | send_headers_and_exit(HTTP_BAD_REQUEST); |
2301 | } | 2359 | } |
2302 | } | 2360 | continue; |
2303 | #endif | ||
2304 | #if ENABLE_FEATURE_HTTPD_CGI | ||
2305 | else if (STRNCASECMP(iobuf, "Cookie:") == 0) { | ||
2306 | if (!cookie) /* in case they send millions of these, do not OOM */ | ||
2307 | cookie = xstrdup(skip_whitespace(iobuf + sizeof("Cookie:")-1)); | ||
2308 | } else if (STRNCASECMP(iobuf, "Content-Type:") == 0) { | ||
2309 | if (!content_type) | ||
2310 | content_type = xstrdup(skip_whitespace(iobuf + sizeof("Content-Type:")-1)); | ||
2311 | } else if (STRNCASECMP(iobuf, "Referer:") == 0) { | ||
2312 | if (!G.referer) | ||
2313 | G.referer = xstrdup(skip_whitespace(iobuf + sizeof("Referer:")-1)); | ||
2314 | } else if (STRNCASECMP(iobuf, "User-Agent:") == 0) { | ||
2315 | if (!G.user_agent) | ||
2316 | G.user_agent = xstrdup(skip_whitespace(iobuf + sizeof("User-Agent:")-1)); | ||
2317 | } else if (STRNCASECMP(iobuf, "Host:") == 0) { | ||
2318 | if (!G.host) | ||
2319 | G.host = xstrdup(skip_whitespace(iobuf + sizeof("Host:")-1)); | ||
2320 | } else if (STRNCASECMP(iobuf, "Accept:") == 0) { | ||
2321 | if (!G.http_accept) | ||
2322 | G.http_accept = xstrdup(skip_whitespace(iobuf + sizeof("Accept:")-1)); | ||
2323 | } else if (STRNCASECMP(iobuf, "Accept-Language:") == 0) { | ||
2324 | if (!G.http_accept_language) | ||
2325 | G.http_accept_language = xstrdup(skip_whitespace(iobuf + sizeof("Accept-Language:")-1)); | ||
2326 | } | 2361 | } |
2327 | #endif | 2362 | #endif |
2328 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH | 2363 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
@@ -2338,6 +2373,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2338 | /* decodeBase64() skips whitespace itself */ | 2373 | /* decodeBase64() skips whitespace itself */ |
2339 | decodeBase64(tptr); | 2374 | decodeBase64(tptr); |
2340 | authorized = check_user_passwd(urlcopy, tptr); | 2375 | authorized = check_user_passwd(urlcopy, tptr); |
2376 | continue; | ||
2341 | } | 2377 | } |
2342 | #endif | 2378 | #endif |
2343 | #if ENABLE_FEATURE_HTTPD_RANGES | 2379 | #if ENABLE_FEATURE_HTTPD_RANGES |
@@ -2355,6 +2391,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2355 | range_start = -1; | 2391 | range_start = -1; |
2356 | } | 2392 | } |
2357 | } | 2393 | } |
2394 | continue; | ||
2358 | } | 2395 | } |
2359 | #endif | 2396 | #endif |
2360 | #if ENABLE_FEATURE_HTTPD_GZIP | 2397 | #if ENABLE_FEATURE_HTTPD_GZIP |
@@ -2372,6 +2409,35 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2372 | content_gzip = 1; | 2409 | content_gzip = 1; |
2373 | //} | 2410 | //} |
2374 | } | 2411 | } |
2412 | continue; | ||
2413 | } | ||
2414 | #endif | ||
2415 | #if ENABLE_FEATURE_HTTPD_CGI | ||
2416 | if (cgi_type != CGI_NONE) { | ||
2417 | bool ct = (STRNCASECMP(iobuf, "Content-Type:") == 0); | ||
2418 | char *cp; | ||
2419 | char *colon = strchr(iobuf, ':'); | ||
2420 | |||
2421 | if (!colon) | ||
2422 | continue; | ||
2423 | cp = iobuf; | ||
2424 | while (cp < colon) { | ||
2425 | /* a-z => A-Z, not-alnum => _ */ | ||
2426 | char c = (*cp & ~0x20); /* toupper for A-Za-z, undef for others */ | ||
2427 | if ((unsigned)(c - 'A') <= ('Z' - 'A')) { | ||
2428 | *cp++ = c; | ||
2429 | continue; | ||
2430 | } | ||
2431 | if (!isdigit(*cp)) | ||
2432 | *cp = '_'; | ||
2433 | cp++; | ||
2434 | } | ||
2435 | /* "Content-Type:" gets no HTTP_ prefix, all others do */ | ||
2436 | cp = xasprintf(ct ? "HTTP_%.*s=%s" + 5 : "HTTP_%.*s=%s", | ||
2437 | (int)(colon - iobuf), iobuf, | ||
2438 | skip_whitespace(colon + 1) | ||
2439 | ); | ||
2440 | putenv(cp); | ||
2375 | } | 2441 | } |
2376 | #endif | 2442 | #endif |
2377 | } /* while extra header reading */ | 2443 | } /* while extra header reading */ |
@@ -2380,7 +2446,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2380 | /* We are done reading headers, disable peer timeout */ | 2446 | /* We are done reading headers, disable peer timeout */ |
2381 | alarm(0); | 2447 | alarm(0); |
2382 | 2448 | ||
2383 | if (strcmp(bb_basename(urlcopy), HTTPD_CONF) == 0 || !ip_allowed) { | 2449 | if (strcmp(bb_basename(urlcopy), HTTPD_CONF) == 0) { |
2384 | /* protect listing [/path]/httpd.conf or IP deny */ | 2450 | /* protect listing [/path]/httpd.conf or IP deny */ |
2385 | send_headers_and_exit(HTTP_FORBIDDEN); | 2451 | send_headers_and_exit(HTTP_FORBIDDEN); |
2386 | } | 2452 | } |
@@ -2398,83 +2464,23 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) | |||
2398 | send_headers_and_exit(HTTP_MOVED_TEMPORARILY); | 2464 | send_headers_and_exit(HTTP_MOVED_TEMPORARILY); |
2399 | } | 2465 | } |
2400 | 2466 | ||
2401 | #if ENABLE_FEATURE_HTTPD_PROXY | ||
2402 | if (proxy_entry != NULL) { | ||
2403 | int proxy_fd; | ||
2404 | len_and_sockaddr *lsa; | ||
2405 | |||
2406 | lsa = host2sockaddr(proxy_entry->host_port, 80); | ||
2407 | if (lsa == NULL) | ||
2408 | send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); | ||
2409 | proxy_fd = socket(lsa->u.sa.sa_family, SOCK_STREAM, 0); | ||
2410 | if (proxy_fd < 0) | ||
2411 | send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); | ||
2412 | if (connect(proxy_fd, &lsa->u.sa, lsa->len) < 0) | ||
2413 | send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); | ||
2414 | fdprintf(proxy_fd, "%s %s%s%s%s HTTP/%c.%c\r\n", | ||
2415 | prequest, /* GET or POST */ | ||
2416 | proxy_entry->url_to, /* url part 1 */ | ||
2417 | urlcopy + strlen(proxy_entry->url_from), /* url part 2 */ | ||
2418 | (g_query ? "?" : ""), /* "?" (maybe) */ | ||
2419 | (g_query ? g_query : ""), /* query string (maybe) */ | ||
2420 | http_major_version, http_minor_version); | ||
2421 | header_ptr[0] = '\r'; | ||
2422 | header_ptr[1] = '\n'; | ||
2423 | header_ptr += 2; | ||
2424 | write(proxy_fd, header_buf, header_ptr - header_buf); | ||
2425 | free(header_buf); /* on the order of 8k, free it */ | ||
2426 | cgi_io_loop_and_exit(proxy_fd, proxy_fd, length); | ||
2427 | } | ||
2428 | #endif | ||
2429 | |||
2430 | tptr = urlcopy + 1; /* skip first '/' */ | 2467 | tptr = urlcopy + 1; /* skip first '/' */ |
2431 | 2468 | ||
2432 | #if ENABLE_FEATURE_HTTPD_CGI | 2469 | #if ENABLE_FEATURE_HTTPD_CGI |
2433 | if (is_prefixed_with(tptr, "cgi-bin/")) { | 2470 | if (cgi_type != CGI_NONE) { |
2434 | if (tptr[8] == '\0') { | 2471 | send_cgi_and_exit( |
2435 | /* protect listing "cgi-bin/" */ | 2472 | (cgi_type == CGI_INDEX) ? "/cgi-bin/index.cgi" |
2436 | send_headers_and_exit(HTTP_FORBIDDEN); | 2473 | /*CGI_NORMAL or CGI_INTERPRETER*/ : urlcopy, |
2437 | } | 2474 | urlcopy, prequest, length |
2438 | send_cgi_and_exit(urlcopy, urlcopy, prequest, length, cookie, content_type); | 2475 | ); |
2439 | } | 2476 | } |
2440 | #endif | 2477 | #endif |
2441 | 2478 | ||
2442 | if (urlp[-1] == '/') { | 2479 | if (urlp[-1] == '/') { |
2443 | /* When index_page string is appended to <dir>/ URL, it overwrites | ||
2444 | * the query string. If we fall back to call /cgi-bin/index.cgi, | ||
2445 | * query string would be lost and not available to the CGI. | ||
2446 | * Work around it by making a deep copy. | ||
2447 | */ | ||
2448 | if (ENABLE_FEATURE_HTTPD_CGI) | ||
2449 | g_query = xstrdup(g_query); /* ok for NULL too */ | ||
2450 | strcpy(urlp, index_page); | 2480 | strcpy(urlp, index_page); |
2451 | } | 2481 | } |
2452 | if (stat(tptr, &sb) == 0) { | ||
2453 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR | ||
2454 | char *suffix = strrchr(tptr, '.'); | ||
2455 | if (suffix) { | ||
2456 | Htaccess *cur; | ||
2457 | for (cur = script_i; cur; cur = cur->next) { | ||
2458 | if (strcmp(cur->before_colon + 1, suffix) == 0) { | ||
2459 | send_cgi_and_exit(urlcopy, urlcopy, prequest, length, cookie, content_type); | ||
2460 | } | ||
2461 | } | ||
2462 | } | ||
2463 | #endif | ||
2464 | file_size = sb.st_size; | ||
2465 | last_mod = sb.st_mtime; | ||
2466 | } | ||
2467 | #if ENABLE_FEATURE_HTTPD_CGI | ||
2468 | else if (urlp[-1] == '/') { | ||
2469 | /* It's a dir URL and there is no index.html | ||
2470 | * Try cgi-bin/index.cgi */ | ||
2471 | if (access("/cgi-bin/index.cgi"+1, X_OK) == 0) { | ||
2472 | urlp[0] = '\0'; /* remove index_page */ | ||
2473 | send_cgi_and_exit("/cgi-bin/index.cgi", urlcopy, prequest, length, cookie, content_type); | ||
2474 | } | ||
2475 | } | ||
2476 | /* else fall through to send_file, it errors out if open fails: */ | ||
2477 | 2482 | ||
2483 | #if ENABLE_FEATURE_HTTPD_CGI | ||
2478 | if (prequest != request_GET && prequest != request_HEAD) { | 2484 | if (prequest != request_GET && prequest != request_HEAD) { |
2479 | /* POST for files does not make sense */ | 2485 | /* POST for files does not make sense */ |
2480 | send_headers_and_exit(HTTP_NOT_IMPLEMENTED); | 2486 | send_headers_and_exit(HTTP_NOT_IMPLEMENTED); |
@@ -2593,7 +2599,9 @@ static void mini_httpd_inetd(void) | |||
2593 | 2599 | ||
2594 | static void sighup_handler(int sig UNUSED_PARAM) | 2600 | static void sighup_handler(int sig UNUSED_PARAM) |
2595 | { | 2601 | { |
2602 | int sv = errno; | ||
2596 | parse_conf(DEFAULT_PATH_HTTPD_CONF, SIGNALED_PARSE); | 2603 | parse_conf(DEFAULT_PATH_HTTPD_CONF, SIGNALED_PARSE); |
2604 | errno = sv; | ||
2597 | } | 2605 | } |
2598 | 2606 | ||
2599 | enum { | 2607 | enum { |