aboutsummaryrefslogtreecommitdiff
path: root/networking/httpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'networking/httpd.c')
-rw-r--r--networking/httpd.c376
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
309typedef enum CGI_type {
310 CGI_NONE = 0,
311 CGI_NORMAL,
312 CGI_INDEX,
313 CGI_INTERPRETER,
314} CGI_type;
315
308enum { 316enum {
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 */
1203static int get_line(void) 1208static 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 */
1443static void send_cgi_and_exit( 1447static 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;
1450static void send_cgi_and_exit( 1452static 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
1820static int checkPermIP(void) 1808static 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
2594static void sighup_handler(int sig UNUSED_PARAM) 2600static 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
2599enum { 2607enum {