Contents of /trunk/mkinitrd-magellan/busybox/networking/httpd.c
Parent Directory | Revision Log
Revision 816 -
(show annotations)
(download)
Fri Apr 24 18:33:46 2009 UTC (15 years, 5 months ago) by niro
File MIME type: text/plain
File size: 68003 byte(s)
Fri Apr 24 18:33:46 2009 UTC (15 years, 5 months ago) by niro
File MIME type: text/plain
File size: 68003 byte(s)
-updated to busybox-1.13.4
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * httpd implementation for busybox |
4 | * |
5 | * Copyright (C) 2002,2003 Glenn Engel <glenne@engel.org> |
6 | * Copyright (C) 2003-2006 Vladimir Oleynik <dzo@simtreas.ru> |
7 | * |
8 | * simplify patch stolen from libbb without using strdup |
9 | * |
10 | * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. |
11 | * |
12 | ***************************************************************************** |
13 | * |
14 | * Typical usage: |
15 | * for non root user |
16 | * httpd -p 8080 -h $HOME/public_html |
17 | * or for daemon start from rc script with uid=0: |
18 | * httpd -u www |
19 | * This is equivalent if www user have uid=80 to |
20 | * httpd -p 80 -u 80 -h /www -c /etc/httpd.conf -r "Web Server Authentication" |
21 | * |
22 | * |
23 | * When a url starts by "/cgi-bin/" it is assumed to be a cgi script. The |
24 | * server changes directory to the location of the script and executes it |
25 | * after setting QUERY_STRING and other environment variables. |
26 | * |
27 | * Doc: |
28 | * "CGI Environment Variables": http://hoohoo.ncsa.uiuc.edu/cgi/env.html |
29 | * |
30 | * The applet can also be invoked as a url arg decoder and html text encoder |
31 | * as follows: |
32 | * foo=`httpd -d $foo` # decode "Hello%20World" as "Hello World" |
33 | * bar=`httpd -e "<Hello World>"` # encode as "<Hello World>" |
34 | * Note that url encoding for arguments is not the same as html encoding for |
35 | * presentation. -d decodes a url-encoded argument while -e encodes in html |
36 | * for page display. |
37 | * |
38 | * httpd.conf has the following format: |
39 | * |
40 | * H:/serverroot # define the server root. It will override -h |
41 | * A:172.20. # Allow address from 172.20.0.0/16 |
42 | * A:10.0.0.0/25 # Allow any address from 10.0.0.0-10.0.0.127 |
43 | * A:10.0.0.0/255.255.255.128 # Allow any address that previous set |
44 | * A:127.0.0.1 # Allow local loopback connections |
45 | * D:* # Deny from other IP connections |
46 | * E404:/path/e404.html # /path/e404.html is the 404 (not found) error page |
47 | * I:index.html # Show index.html when a directory is requested |
48 | * |
49 | * P:/url:[http://]hostname[:port]/new/path |
50 | * # When /urlXXXXXX is requested, reverse proxy |
51 | * # it to http://hostname[:port]/new/pathXXXXXX |
52 | * |
53 | * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin/ |
54 | * /adm:admin:setup # Require user admin, pwd setup on urls starting with /adm/ |
55 | * /adm:toor:PaSsWd # or user toor, pwd PaSsWd on urls starting with /adm/ |
56 | * .au:audio/basic # additional mime type for audio.au files |
57 | * *.php:/path/php # running cgi.php scripts through an interpreter |
58 | * |
59 | * A/D may be as a/d or allow/deny - only first char matters. |
60 | * Deny/Allow IP logic: |
61 | * - Default is to allow all (Allow all (A:*) is a no-op). |
62 | * - Deny rules take precedence over allow rules. |
63 | * - "Deny all" rule (D:*) is applied last. |
64 | * |
65 | * Example: |
66 | * 1. Allow only specified addresses |
67 | * A:172.20 # Allow any address that begins with 172.20. |
68 | * A:10.10. # Allow any address that begins with 10.10. |
69 | * A:127.0.0.1 # Allow local loopback connections |
70 | * D:* # Deny from other IP connections |
71 | * |
72 | * 2. Only deny specified addresses |
73 | * D:1.2.3. # deny from 1.2.3.0 - 1.2.3.255 |
74 | * D:2.3.4. # deny from 2.3.4.0 - 2.3.4.255 |
75 | * A:* # (optional line added for clarity) |
76 | * |
77 | * If a sub directory contains a config file it is parsed and merged with |
78 | * any existing settings as if it was appended to the original configuration. |
79 | * |
80 | * subdir paths are relative to the containing subdir and thus cannot |
81 | * affect the parent rules. |
82 | * |
83 | * Note that since the sub dir is parsed in the forked thread servicing the |
84 | * subdir http request, any merge is discarded when the process exits. As a |
85 | * result, the subdir settings only have a lifetime of a single request. |
86 | * |
87 | * Custom error pages can contain an absolute path or be relative to |
88 | * 'home_httpd'. Error pages are to be static files (no CGI or script). Error |
89 | * page can only be defined in the root configuration file and are not taken |
90 | * into account in local (directories) config files. |
91 | * |
92 | * If -c is not set, an attempt will be made to open the default |
93 | * root configuration file. If -c is set and the file is not found, the |
94 | * server exits with an error. |
95 | * |
96 | */ |
97 | |
98 | #include "libbb.h" |
99 | #if ENABLE_FEATURE_HTTPD_USE_SENDFILE |
100 | #include <sys/sendfile.h> |
101 | #endif |
102 | |
103 | //#define DEBUG 1 |
104 | #define DEBUG 0 |
105 | |
106 | #define IOBUF_SIZE 8192 /* IO buffer */ |
107 | |
108 | /* amount of buffering in a pipe */ |
109 | #ifndef PIPE_BUF |
110 | # define PIPE_BUF 4096 |
111 | #endif |
112 | #if PIPE_BUF >= IOBUF_SIZE |
113 | # error "PIPE_BUF >= IOBUF_SIZE" |
114 | #endif |
115 | |
116 | #define HEADER_READ_TIMEOUT 60 |
117 | |
118 | static const char default_path_httpd_conf[] ALIGN1 = "/etc"; |
119 | static const char httpd_conf[] ALIGN1 = "httpd.conf"; |
120 | static const char HTTP_200[] ALIGN1 = "HTTP/1.0 200 OK\r\n"; |
121 | |
122 | typedef struct has_next_ptr { |
123 | struct has_next_ptr *next; |
124 | } has_next_ptr; |
125 | |
126 | /* Must have "next" as a first member */ |
127 | typedef struct Htaccess { |
128 | struct Htaccess *next; |
129 | char *after_colon; |
130 | char before_colon[1]; /* really bigger, must be last */ |
131 | } Htaccess; |
132 | |
133 | /* Must have "next" as a first member */ |
134 | typedef struct Htaccess_IP { |
135 | struct Htaccess_IP *next; |
136 | unsigned ip; |
137 | unsigned mask; |
138 | int allow_deny; |
139 | } Htaccess_IP; |
140 | |
141 | /* Must have "next" as a first member */ |
142 | typedef struct Htaccess_Proxy { |
143 | struct Htaccess_Proxy *next; |
144 | char *url_from; |
145 | char *host_port; |
146 | char *url_to; |
147 | } Htaccess_Proxy; |
148 | |
149 | enum { |
150 | HTTP_OK = 200, |
151 | HTTP_PARTIAL_CONTENT = 206, |
152 | HTTP_MOVED_TEMPORARILY = 302, |
153 | HTTP_BAD_REQUEST = 400, /* malformed syntax */ |
154 | HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */ |
155 | HTTP_NOT_FOUND = 404, |
156 | HTTP_FORBIDDEN = 403, |
157 | HTTP_REQUEST_TIMEOUT = 408, |
158 | HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */ |
159 | HTTP_INTERNAL_SERVER_ERROR = 500, |
160 | HTTP_CONTINUE = 100, |
161 | #if 0 /* future use */ |
162 | HTTP_SWITCHING_PROTOCOLS = 101, |
163 | HTTP_CREATED = 201, |
164 | HTTP_ACCEPTED = 202, |
165 | HTTP_NON_AUTHORITATIVE_INFO = 203, |
166 | HTTP_NO_CONTENT = 204, |
167 | HTTP_MULTIPLE_CHOICES = 300, |
168 | HTTP_MOVED_PERMANENTLY = 301, |
169 | HTTP_NOT_MODIFIED = 304, |
170 | HTTP_PAYMENT_REQUIRED = 402, |
171 | HTTP_BAD_GATEWAY = 502, |
172 | HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */ |
173 | HTTP_RESPONSE_SETSIZE = 0xffffffff |
174 | #endif |
175 | }; |
176 | |
177 | static const uint16_t http_response_type[] ALIGN2 = { |
178 | HTTP_OK, |
179 | #if ENABLE_FEATURE_HTTPD_RANGES |
180 | HTTP_PARTIAL_CONTENT, |
181 | #endif |
182 | HTTP_MOVED_TEMPORARILY, |
183 | HTTP_REQUEST_TIMEOUT, |
184 | HTTP_NOT_IMPLEMENTED, |
185 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
186 | HTTP_UNAUTHORIZED, |
187 | #endif |
188 | HTTP_NOT_FOUND, |
189 | HTTP_BAD_REQUEST, |
190 | HTTP_FORBIDDEN, |
191 | HTTP_INTERNAL_SERVER_ERROR, |
192 | #if 0 /* not implemented */ |
193 | HTTP_CREATED, |
194 | HTTP_ACCEPTED, |
195 | HTTP_NO_CONTENT, |
196 | HTTP_MULTIPLE_CHOICES, |
197 | HTTP_MOVED_PERMANENTLY, |
198 | HTTP_NOT_MODIFIED, |
199 | HTTP_BAD_GATEWAY, |
200 | HTTP_SERVICE_UNAVAILABLE, |
201 | #endif |
202 | }; |
203 | |
204 | static const struct { |
205 | const char *name; |
206 | const char *info; |
207 | } http_response[ARRAY_SIZE(http_response_type)] = { |
208 | { "OK", NULL }, |
209 | #if ENABLE_FEATURE_HTTPD_RANGES |
210 | { "Partial Content", NULL }, |
211 | #endif |
212 | { "Found", NULL }, |
213 | { "Request Timeout", "No request appeared within 60 seconds" }, |
214 | { "Not Implemented", "The requested method is not recognized" }, |
215 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
216 | { "Unauthorized", "" }, |
217 | #endif |
218 | { "Not Found", "The requested URL was not found" }, |
219 | { "Bad Request", "Unsupported method" }, |
220 | { "Forbidden", "" }, |
221 | { "Internal Server Error", "Internal Server Error" }, |
222 | #if 0 /* not implemented */ |
223 | { "Created" }, |
224 | { "Accepted" }, |
225 | { "No Content" }, |
226 | { "Multiple Choices" }, |
227 | { "Moved Permanently" }, |
228 | { "Not Modified" }, |
229 | { "Bad Gateway", "" }, |
230 | { "Service Unavailable", "" }, |
231 | #endif |
232 | }; |
233 | |
234 | |
235 | struct globals { |
236 | int verbose; /* must be int (used by getopt32) */ |
237 | smallint flg_deny_all; |
238 | |
239 | unsigned rmt_ip; /* used for IP-based allow/deny rules */ |
240 | time_t last_mod; |
241 | char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */ |
242 | const char *bind_addr_or_port; |
243 | |
244 | const char *g_query; |
245 | const char *configFile; |
246 | const char *home_httpd; |
247 | const char *index_page; |
248 | |
249 | const char *found_mime_type; |
250 | const char *found_moved_temporarily; |
251 | Htaccess_IP *ip_a_d; /* config allow/deny lines */ |
252 | |
253 | USE_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;) |
254 | USE_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;) |
255 | USE_FEATURE_HTTPD_CGI(char *referer;) |
256 | USE_FEATURE_HTTPD_CGI(char *user_agent;) |
257 | USE_FEATURE_HTTPD_CGI(char *http_accept;) |
258 | USE_FEATURE_HTTPD_CGI(char *http_accept_language;) |
259 | |
260 | off_t file_size; /* -1 - unknown */ |
261 | #if ENABLE_FEATURE_HTTPD_RANGES |
262 | off_t range_start; |
263 | off_t range_end; |
264 | off_t range_len; |
265 | #endif |
266 | |
267 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
268 | Htaccess *g_auth; /* config user:password lines */ |
269 | #endif |
270 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES |
271 | Htaccess *mime_a; /* config mime types */ |
272 | #endif |
273 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
274 | Htaccess *script_i; /* config script interpreters */ |
275 | #endif |
276 | char *iobuf; /* [IOBUF_SIZE] */ |
277 | #define hdr_buf bb_common_bufsiz1 |
278 | char *hdr_ptr; |
279 | int hdr_cnt; |
280 | #if ENABLE_FEATURE_HTTPD_ERROR_PAGES |
281 | const char *http_error_page[ARRAY_SIZE(http_response_type)]; |
282 | #endif |
283 | #if ENABLE_FEATURE_HTTPD_PROXY |
284 | Htaccess_Proxy *proxy; |
285 | #endif |
286 | }; |
287 | #define G (*ptr_to_globals) |
288 | #define verbose (G.verbose ) |
289 | #define flg_deny_all (G.flg_deny_all ) |
290 | #define rmt_ip (G.rmt_ip ) |
291 | #define bind_addr_or_port (G.bind_addr_or_port) |
292 | #define g_query (G.g_query ) |
293 | #define configFile (G.configFile ) |
294 | #define home_httpd (G.home_httpd ) |
295 | #define index_page (G.index_page ) |
296 | #define found_mime_type (G.found_mime_type ) |
297 | #define found_moved_temporarily (G.found_moved_temporarily) |
298 | #define last_mod (G.last_mod ) |
299 | #define ip_a_d (G.ip_a_d ) |
300 | #define g_realm (G.g_realm ) |
301 | #define remoteuser (G.remoteuser ) |
302 | #define referer (G.referer ) |
303 | #define user_agent (G.user_agent ) |
304 | #define http_accept (G.http_accept ) |
305 | #define http_accept_language (G.http_accept_language) |
306 | #define file_size (G.file_size ) |
307 | #if ENABLE_FEATURE_HTTPD_RANGES |
308 | #define range_start (G.range_start ) |
309 | #define range_end (G.range_end ) |
310 | #define range_len (G.range_len ) |
311 | #endif |
312 | #define rmt_ip_str (G.rmt_ip_str ) |
313 | #define g_auth (G.g_auth ) |
314 | #define mime_a (G.mime_a ) |
315 | #define script_i (G.script_i ) |
316 | #define iobuf (G.iobuf ) |
317 | #define hdr_ptr (G.hdr_ptr ) |
318 | #define hdr_cnt (G.hdr_cnt ) |
319 | #define http_error_page (G.http_error_page ) |
320 | #define proxy (G.proxy ) |
321 | #define INIT_G() do { \ |
322 | SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ |
323 | USE_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \ |
324 | bind_addr_or_port = "80"; \ |
325 | index_page = "index.html"; \ |
326 | file_size = -1; \ |
327 | } while (0) |
328 | |
329 | #if !ENABLE_FEATURE_HTTPD_RANGES |
330 | enum { |
331 | range_start = 0, |
332 | range_end = MAXINT(off_t) - 1, |
333 | range_len = MAXINT(off_t), |
334 | }; |
335 | #endif |
336 | |
337 | |
338 | #define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1) |
339 | |
340 | /* Prototypes */ |
341 | enum { |
342 | SEND_HEADERS = (1 << 0), |
343 | SEND_BODY = (1 << 1), |
344 | SEND_HEADERS_AND_BODY = SEND_HEADERS + SEND_BODY, |
345 | }; |
346 | static void send_file_and_exit(const char *url, int what) NORETURN; |
347 | |
348 | static void free_llist(has_next_ptr **pptr) |
349 | { |
350 | has_next_ptr *cur = *pptr; |
351 | while (cur) { |
352 | has_next_ptr *t = cur; |
353 | cur = cur->next; |
354 | free(t); |
355 | } |
356 | *pptr = NULL; |
357 | } |
358 | |
359 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH \ |
360 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \ |
361 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
362 | static ALWAYS_INLINE void free_Htaccess_list(Htaccess **pptr) |
363 | { |
364 | free_llist((has_next_ptr**)pptr); |
365 | } |
366 | #endif |
367 | |
368 | static ALWAYS_INLINE void free_Htaccess_IP_list(Htaccess_IP **pptr) |
369 | { |
370 | free_llist((has_next_ptr**)pptr); |
371 | } |
372 | |
373 | /* Returns presumed mask width in bits or < 0 on error. |
374 | * Updates strp, stores IP at provided pointer */ |
375 | static int scan_ip(const char **strp, unsigned *ipp, unsigned char endc) |
376 | { |
377 | const char *p = *strp; |
378 | int auto_mask = 8; |
379 | unsigned ip = 0; |
380 | int j; |
381 | |
382 | if (*p == '/') |
383 | return -auto_mask; |
384 | |
385 | for (j = 0; j < 4; j++) { |
386 | unsigned octet; |
387 | |
388 | if ((*p < '0' || *p > '9') && *p != '/' && *p) |
389 | return -auto_mask; |
390 | octet = 0; |
391 | while (*p >= '0' && *p <= '9') { |
392 | octet *= 10; |
393 | octet += *p - '0'; |
394 | if (octet > 255) |
395 | return -auto_mask; |
396 | p++; |
397 | } |
398 | if (*p == '.') |
399 | p++; |
400 | if (*p != '/' && *p) |
401 | auto_mask += 8; |
402 | ip = (ip << 8) | octet; |
403 | } |
404 | if (*p) { |
405 | if (*p != endc) |
406 | return -auto_mask; |
407 | p++; |
408 | if (*p == '\0') |
409 | return -auto_mask; |
410 | } |
411 | *ipp = ip; |
412 | *strp = p; |
413 | return auto_mask; |
414 | } |
415 | |
416 | /* Returns 0 on success. Stores IP and mask at provided pointers */ |
417 | static int scan_ip_mask(const char *str, unsigned *ipp, unsigned *maskp) |
418 | { |
419 | int i; |
420 | unsigned mask; |
421 | char *p; |
422 | |
423 | i = scan_ip(&str, ipp, '/'); |
424 | if (i < 0) |
425 | return i; |
426 | |
427 | if (*str) { |
428 | /* there is /xxx after dotted-IP address */ |
429 | i = bb_strtou(str, &p, 10); |
430 | if (*p == '.') { |
431 | /* 'xxx' itself is dotted-IP mask, parse it */ |
432 | /* (return 0 (success) only if it has N.N.N.N form) */ |
433 | return scan_ip(&str, maskp, '\0') - 32; |
434 | } |
435 | if (*p) |
436 | return -1; |
437 | } |
438 | |
439 | if (i > 32) |
440 | return -1; |
441 | |
442 | if (sizeof(unsigned) == 4 && i == 32) { |
443 | /* mask >>= 32 below may not work */ |
444 | mask = 0; |
445 | } else { |
446 | mask = 0xffffffff; |
447 | mask >>= i; |
448 | } |
449 | /* i == 0 -> *maskp = 0x00000000 |
450 | * i == 1 -> *maskp = 0x80000000 |
451 | * i == 4 -> *maskp = 0xf0000000 |
452 | * i == 31 -> *maskp = 0xfffffffe |
453 | * i == 32 -> *maskp = 0xffffffff */ |
454 | *maskp = (uint32_t)(~mask); |
455 | return 0; |
456 | } |
457 | |
458 | /* |
459 | * Parse configuration file into in-memory linked list. |
460 | * |
461 | * The first non-white character is examined to determine if the config line |
462 | * is one of the following: |
463 | * .ext:mime/type # new mime type not compiled into httpd |
464 | * [adAD]:from # ip address allow/deny, * for wildcard |
465 | * /path:user:pass # username/password |
466 | * Ennn:error.html # error page for status nnn |
467 | * P:/url:[http://]hostname[:port]/new/path # reverse proxy |
468 | * |
469 | * Any previous IP rules are discarded. |
470 | * If the flag argument is not SUBDIR_PARSE then all /path and mime rules |
471 | * are also discarded. That is, previous settings are retained if flag is |
472 | * SUBDIR_PARSE. |
473 | * Error pages are only parsed on the main config file. |
474 | * |
475 | * path Path where to look for httpd.conf (without filename). |
476 | * flag Type of the parse request. |
477 | */ |
478 | /* flag */ |
479 | #define FIRST_PARSE 0 |
480 | #define SUBDIR_PARSE 1 |
481 | #define SIGNALED_PARSE 2 |
482 | #define FIND_FROM_HTTPD_ROOT 3 |
483 | static void parse_conf(const char *path, int flag) |
484 | { |
485 | FILE *f; |
486 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
487 | Htaccess *prev; |
488 | #endif |
489 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH \ |
490 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \ |
491 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
492 | Htaccess *cur; |
493 | #endif |
494 | const char *filename = configFile; |
495 | char buf[160]; |
496 | char *p, *p0; |
497 | char *after_colon; |
498 | Htaccess_IP *pip; |
499 | |
500 | /* discard old rules */ |
501 | free_Htaccess_IP_list(&ip_a_d); |
502 | flg_deny_all = 0; |
503 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH \ |
504 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \ |
505 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
506 | /* retain previous auth and mime config only for subdir parse */ |
507 | if (flag != SUBDIR_PARSE) { |
508 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
509 | free_Htaccess_list(&g_auth); |
510 | #endif |
511 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES |
512 | free_Htaccess_list(&mime_a); |
513 | #endif |
514 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
515 | free_Htaccess_list(&script_i); |
516 | #endif |
517 | } |
518 | #endif |
519 | |
520 | if (flag == SUBDIR_PARSE || filename == NULL) { |
521 | filename = alloca(strlen(path) + sizeof(httpd_conf) + 2); |
522 | sprintf((char *)filename, "%s/%s", path, httpd_conf); |
523 | } |
524 | |
525 | while ((f = fopen_for_read(filename)) == NULL) { |
526 | if (flag == SUBDIR_PARSE || flag == FIND_FROM_HTTPD_ROOT) { |
527 | /* config file not found, no changes to config */ |
528 | return; |
529 | } |
530 | if (configFile && flag == FIRST_PARSE) /* if -c option given */ |
531 | bb_simple_perror_msg_and_die(filename); |
532 | flag = FIND_FROM_HTTPD_ROOT; |
533 | filename = httpd_conf; |
534 | } |
535 | |
536 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
537 | prev = g_auth; |
538 | #endif |
539 | /* This could stand some work */ |
540 | while ((p0 = fgets(buf, sizeof(buf), f)) != NULL) { |
541 | after_colon = NULL; |
542 | for (p = p0; *p0 != '\0' && *p0 != '#'; p0++) { |
543 | if (!isspace(*p0)) { |
544 | *p++ = *p0; |
545 | if (*p0 == ':' && after_colon == NULL) |
546 | after_colon = p; |
547 | } |
548 | } |
549 | *p = '\0'; |
550 | |
551 | /* test for empty or strange line */ |
552 | if (after_colon == NULL || *after_colon == '\0') |
553 | continue; |
554 | p0 = buf; |
555 | if (*p0 == 'd' || *p0 == 'a') |
556 | *p0 -= 0x20; /* a/d -> A/D */ |
557 | if (*after_colon == '*') { |
558 | if (*p0 == 'D') { |
559 | /* memorize "deny all" */ |
560 | flg_deny_all = 1; |
561 | } |
562 | /* skip assumed "A:*", it is a default anyway */ |
563 | continue; |
564 | } |
565 | |
566 | if (*p0 == 'A' || *p0 == 'D') { |
567 | /* storing current config IP line */ |
568 | pip = xzalloc(sizeof(Htaccess_IP)); |
569 | if (scan_ip_mask(after_colon, &(pip->ip), &(pip->mask))) { |
570 | /* IP{/mask} syntax error detected, protect all */ |
571 | *p0 = 'D'; |
572 | pip->mask = 0; |
573 | } |
574 | pip->allow_deny = *p0; |
575 | if (*p0 == 'D') { |
576 | /* Deny:from_IP - prepend */ |
577 | pip->next = ip_a_d; |
578 | ip_a_d = pip; |
579 | } else { |
580 | /* A:from_IP - append (thus D precedes A) */ |
581 | Htaccess_IP *prev_IP = ip_a_d; |
582 | if (prev_IP == NULL) { |
583 | ip_a_d = pip; |
584 | } else { |
585 | while (prev_IP->next) |
586 | prev_IP = prev_IP->next; |
587 | prev_IP->next = pip; |
588 | } |
589 | } |
590 | continue; |
591 | } |
592 | |
593 | #if ENABLE_FEATURE_HTTPD_ERROR_PAGES |
594 | if (flag == FIRST_PARSE && *p0 == 'E') { |
595 | unsigned i; |
596 | int status = atoi(++p0); /* error status code */ |
597 | if (status < HTTP_CONTINUE) { |
598 | bb_error_msg("config error '%s' in '%s'", buf, filename); |
599 | continue; |
600 | } |
601 | /* then error page; find matching status */ |
602 | for (i = 0; i < ARRAY_SIZE(http_response_type); i++) { |
603 | if (http_response_type[i] == status) { |
604 | /* We chdir to home_httpd, thus no need to |
605 | * concat_path_file(home_httpd, after_colon) |
606 | * here */ |
607 | http_error_page[i] = xstrdup(after_colon); |
608 | break; |
609 | } |
610 | } |
611 | continue; |
612 | } |
613 | #endif |
614 | |
615 | #if ENABLE_FEATURE_HTTPD_PROXY |
616 | if (flag == FIRST_PARSE && *p0 == 'P') { |
617 | /* P:/url:[http://]hostname[:port]/new/path */ |
618 | char *url_from, *host_port, *url_to; |
619 | Htaccess_Proxy *proxy_entry; |
620 | |
621 | url_from = after_colon; |
622 | host_port = strchr(after_colon, ':'); |
623 | if (host_port == NULL) { |
624 | bb_error_msg("config error '%s' in '%s'", buf, filename); |
625 | continue; |
626 | } |
627 | *host_port++ = '\0'; |
628 | if (strncmp(host_port, "http://", 7) == 0) |
629 | host_port += 7; |
630 | if (*host_port == '\0') { |
631 | bb_error_msg("config error '%s' in '%s'", buf, filename); |
632 | continue; |
633 | } |
634 | url_to = strchr(host_port, '/'); |
635 | if (url_to == NULL) { |
636 | bb_error_msg("config error '%s' in '%s'", buf, filename); |
637 | continue; |
638 | } |
639 | *url_to = '\0'; |
640 | proxy_entry = xzalloc(sizeof(Htaccess_Proxy)); |
641 | proxy_entry->url_from = xstrdup(url_from); |
642 | proxy_entry->host_port = xstrdup(host_port); |
643 | *url_to = '/'; |
644 | proxy_entry->url_to = xstrdup(url_to); |
645 | proxy_entry->next = proxy; |
646 | proxy = proxy_entry; |
647 | continue; |
648 | } |
649 | #endif |
650 | |
651 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
652 | if (*p0 == '/') { |
653 | /* make full path from httpd root / current_path / config_line_path */ |
654 | const char *tp = (flag == SUBDIR_PARSE ? path : ""); |
655 | p0 = xmalloc(strlen(tp) + (after_colon - buf) + 2 + strlen(after_colon)); |
656 | after_colon[-1] = '\0'; |
657 | sprintf(p0, "/%s%s", tp, buf); |
658 | |
659 | /* looks like bb_simplify_path... */ |
660 | tp = p = p0; |
661 | do { |
662 | if (*p == '/') { |
663 | if (*tp == '/') { /* skip duplicate (or initial) slash */ |
664 | continue; |
665 | } |
666 | if (*tp == '.') { |
667 | if (tp[1] == '/' || tp[1] == '\0') { /* remove extra '.' */ |
668 | continue; |
669 | } |
670 | if ((tp[1] == '.') && (tp[2] == '/' || tp[2] == '\0')) { |
671 | ++tp; |
672 | if (p > p0) { |
673 | while (*--p != '/') /* omit previous dir */ |
674 | continue; |
675 | } |
676 | continue; |
677 | } |
678 | } |
679 | } |
680 | *++p = *tp; |
681 | } while (*++tp); |
682 | |
683 | if ((p == p0) || (*p != '/')) { /* not a trailing slash */ |
684 | ++p; /* so keep last character */ |
685 | } |
686 | *p = ':'; |
687 | strcpy(p + 1, after_colon); |
688 | } |
689 | #endif |
690 | if (*p0 == 'I') { |
691 | index_page = xstrdup(after_colon); |
692 | continue; |
693 | } |
694 | |
695 | /* Do not allow jumping around using H in subdir's configs */ |
696 | if (flag == FIRST_PARSE && *p0 == 'H') { |
697 | home_httpd = xstrdup(after_colon); |
698 | xchdir(home_httpd); |
699 | continue; |
700 | } |
701 | |
702 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH \ |
703 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \ |
704 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
705 | /* storing current config line */ |
706 | cur = xzalloc(sizeof(Htaccess) + strlen(p0)); |
707 | strcpy(cur->before_colon, p0); |
708 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
709 | if (*p0 == '/') /* was malloced - see above */ |
710 | free(p0); |
711 | #endif |
712 | cur->after_colon = strchr(cur->before_colon, ':'); |
713 | *cur->after_colon++ = '\0'; |
714 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES |
715 | if (cur->before_colon[0] == '.') { |
716 | /* .mime line: prepend to mime_a list */ |
717 | cur->next = mime_a; |
718 | mime_a = cur; |
719 | continue; |
720 | } |
721 | #endif |
722 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
723 | if (cur->before_colon[0] == '*' && cur->before_colon[1] == '.') { |
724 | /* script interpreter line: prepend to script_i list */ |
725 | cur->next = script_i; |
726 | script_i = cur; |
727 | continue; |
728 | } |
729 | #endif |
730 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
731 | //TODO: we do not test for leading "/"?? |
732 | //also, do we leak cur if BASIC_AUTH is off? |
733 | if (prev == NULL) { |
734 | /* first line */ |
735 | g_auth = prev = cur; |
736 | } else { |
737 | /* sort path, if current length eq or bigger then move up */ |
738 | Htaccess *prev_hti = g_auth; |
739 | size_t l = strlen(cur->before_colon); |
740 | Htaccess *hti; |
741 | |
742 | for (hti = prev_hti; hti; hti = hti->next) { |
743 | if (l >= strlen(hti->before_colon)) { |
744 | /* insert before hti */ |
745 | cur->next = hti; |
746 | if (prev_hti != hti) { |
747 | prev_hti->next = cur; |
748 | } else { |
749 | /* insert as top */ |
750 | g_auth = cur; |
751 | } |
752 | break; |
753 | } |
754 | if (prev_hti != hti) |
755 | prev_hti = prev_hti->next; |
756 | } |
757 | if (!hti) { /* not inserted, add to bottom */ |
758 | prev->next = cur; |
759 | prev = cur; |
760 | } |
761 | } |
762 | #endif /* BASIC_AUTH */ |
763 | #endif /* BASIC_AUTH || MIME_TYPES || SCRIPT_INTERPR */ |
764 | } /* while (fgets) */ |
765 | fclose(f); |
766 | } |
767 | |
768 | #if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR |
769 | /* |
770 | * Given a string, html-encode special characters. |
771 | * This is used for the -e command line option to provide an easy way |
772 | * for scripts to encode result data without confusing browsers. The |
773 | * returned string pointer is memory allocated by malloc(). |
774 | * |
775 | * Returns a pointer to the encoded string (malloced). |
776 | */ |
777 | static char *encodeString(const char *string) |
778 | { |
779 | /* take the simple route and encode everything */ |
780 | /* could possibly scan once to get length. */ |
781 | int len = strlen(string); |
782 | char *out = xmalloc(len * 6 + 1); |
783 | char *p = out; |
784 | char ch; |
785 | |
786 | while ((ch = *string++)) { |
787 | /* very simple check for what to encode */ |
788 | if (isalnum(ch)) |
789 | *p++ = ch; |
790 | else |
791 | p += sprintf(p, "&#%d;", (unsigned char) ch); |
792 | } |
793 | *p = '\0'; |
794 | return out; |
795 | } |
796 | #endif /* FEATURE_HTTPD_ENCODE_URL_STR */ |
797 | |
798 | /* |
799 | * Given a URL encoded string, convert it to plain ascii. |
800 | * Since decoding always makes strings smaller, the decode is done in-place. |
801 | * Thus, callers should xstrdup() the argument if they do not want the |
802 | * argument modified. The return is the original pointer, allowing this |
803 | * function to be easily used as arguments to other functions. |
804 | * |
805 | * string The first string to decode. |
806 | * option_d 1 if called for httpd -d |
807 | * |
808 | * Returns a pointer to the decoded string (same as input). |
809 | */ |
810 | static unsigned hex_to_bin(unsigned char c) |
811 | { |
812 | unsigned v; |
813 | |
814 | v = c - '0'; |
815 | if (v <= 9) |
816 | return v; |
817 | /* c | 0x20: letters to lower case, non-letters |
818 | * to (potentially different) non-letters */ |
819 | v = (unsigned)(c | 0x20) - 'a'; |
820 | if (v <= 5) |
821 | return v + 10; |
822 | return ~0; |
823 | } |
824 | /* For testing: |
825 | void t(char c) { printf("'%c'(%u) %u\n", c, c, hex_to_bin(c)); } |
826 | int main() { t(0x10); t(0x20); t('0'); t('9'); t('A'); t('F'); t('a'); t('f'); |
827 | t('0'-1); t('9'+1); t('A'-1); t('F'+1); t('a'-1); t('f'+1); return 0; } |
828 | */ |
829 | static char *decodeString(char *orig, int option_d) |
830 | { |
831 | /* note that decoded string is always shorter than original */ |
832 | char *string = orig; |
833 | char *ptr = string; |
834 | char c; |
835 | |
836 | while ((c = *ptr++) != '\0') { |
837 | unsigned v; |
838 | |
839 | if (option_d && c == '+') { |
840 | *string++ = ' '; |
841 | continue; |
842 | } |
843 | if (c != '%') { |
844 | *string++ = c; |
845 | continue; |
846 | } |
847 | v = hex_to_bin(ptr[0]); |
848 | if (v > 15) { |
849 | bad_hex: |
850 | if (!option_d) |
851 | return NULL; |
852 | *string++ = '%'; |
853 | continue; |
854 | } |
855 | v = (v * 16) | hex_to_bin(ptr[1]); |
856 | if (v > 255) |
857 | goto bad_hex; |
858 | if (!option_d && (v == '/' || v == '\0')) { |
859 | /* caller takes it as indication of invalid |
860 | * (dangerous wrt exploits) chars */ |
861 | return orig + 1; |
862 | } |
863 | *string++ = v; |
864 | ptr += 2; |
865 | } |
866 | *string = '\0'; |
867 | return orig; |
868 | } |
869 | |
870 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
871 | /* |
872 | * Decode a base64 data stream as per rfc1521. |
873 | * Note that the rfc states that non base64 chars are to be ignored. |
874 | * Since the decode always results in a shorter size than the input, |
875 | * it is OK to pass the input arg as an output arg. |
876 | * Parameter: a pointer to a base64 encoded string. |
877 | * Decoded data is stored in-place. |
878 | */ |
879 | static void decodeBase64(char *Data) |
880 | { |
881 | const unsigned char *in = (const unsigned char *)Data; |
882 | /* The decoded size will be at most 3/4 the size of the encoded */ |
883 | unsigned ch = 0; |
884 | int i = 0; |
885 | |
886 | while (*in) { |
887 | int t = *in++; |
888 | |
889 | if (t >= '0' && t <= '9') |
890 | t = t - '0' + 52; |
891 | else if (t >= 'A' && t <= 'Z') |
892 | t = t - 'A'; |
893 | else if (t >= 'a' && t <= 'z') |
894 | t = t - 'a' + 26; |
895 | else if (t == '+') |
896 | t = 62; |
897 | else if (t == '/') |
898 | t = 63; |
899 | else if (t == '=') |
900 | t = 0; |
901 | else |
902 | continue; |
903 | |
904 | ch = (ch << 6) | t; |
905 | i++; |
906 | if (i == 4) { |
907 | *Data++ = (char) (ch >> 16); |
908 | *Data++ = (char) (ch >> 8); |
909 | *Data++ = (char) ch; |
910 | i = 0; |
911 | } |
912 | } |
913 | *Data = '\0'; |
914 | } |
915 | #endif |
916 | |
917 | /* |
918 | * Create a listen server socket on the designated port. |
919 | */ |
920 | static int openServer(void) |
921 | { |
922 | unsigned n = bb_strtou(bind_addr_or_port, NULL, 10); |
923 | if (!errno && n && n <= 0xffff) |
924 | n = create_and_bind_stream_or_die(NULL, n); |
925 | else |
926 | n = create_and_bind_stream_or_die(bind_addr_or_port, 80); |
927 | xlisten(n, 9); |
928 | return n; |
929 | } |
930 | |
931 | /* |
932 | * Log the connection closure and exit. |
933 | */ |
934 | static void log_and_exit(void) NORETURN; |
935 | static void log_and_exit(void) |
936 | { |
937 | /* Paranoia. IE said to be buggy. It may send some extra data |
938 | * or be confused by us just exiting without SHUT_WR. Oh well. */ |
939 | shutdown(1, SHUT_WR); |
940 | /* Why?? |
941 | (this also messes up stdin when user runs httpd -i from terminal) |
942 | ndelay_on(0); |
943 | while (read(STDIN_FILENO, iobuf, IOBUF_SIZE) > 0) |
944 | continue; |
945 | */ |
946 | |
947 | if (verbose > 2) |
948 | bb_error_msg("closed"); |
949 | _exit(xfunc_error_retval); |
950 | } |
951 | |
952 | /* |
953 | * Create and send HTTP response headers. |
954 | * The arguments are combined and sent as one write operation. Note that |
955 | * IE will puke big-time if the headers are not sent in one packet and the |
956 | * second packet is delayed for any reason. |
957 | * responseNum - the result code to send. |
958 | */ |
959 | static void send_headers(int responseNum) |
960 | { |
961 | static const char RFC1123FMT[] ALIGN1 = "%a, %d %b %Y %H:%M:%S GMT"; |
962 | |
963 | const char *responseString = ""; |
964 | const char *infoString = NULL; |
965 | const char *mime_type; |
966 | #if ENABLE_FEATURE_HTTPD_ERROR_PAGES |
967 | const char *error_page = NULL; |
968 | #endif |
969 | unsigned i; |
970 | time_t timer = time(0); |
971 | char tmp_str[80]; |
972 | int len; |
973 | |
974 | for (i = 0; i < ARRAY_SIZE(http_response_type); i++) { |
975 | if (http_response_type[i] == responseNum) { |
976 | responseString = http_response[i].name; |
977 | infoString = http_response[i].info; |
978 | #if ENABLE_FEATURE_HTTPD_ERROR_PAGES |
979 | error_page = http_error_page[i]; |
980 | #endif |
981 | break; |
982 | } |
983 | } |
984 | /* error message is HTML */ |
985 | mime_type = responseNum == HTTP_OK ? |
986 | found_mime_type : "text/html"; |
987 | |
988 | if (verbose) |
989 | bb_error_msg("response:%u", responseNum); |
990 | |
991 | /* emit the current date */ |
992 | strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&timer)); |
993 | len = sprintf(iobuf, |
994 | "HTTP/1.0 %d %s\r\nContent-type: %s\r\n" |
995 | "Date: %s\r\nConnection: close\r\n", |
996 | responseNum, responseString, mime_type, tmp_str); |
997 | |
998 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
999 | if (responseNum == HTTP_UNAUTHORIZED) { |
1000 | len += sprintf(iobuf + len, |
1001 | "WWW-Authenticate: Basic realm=\"%s\"\r\n", |
1002 | g_realm); |
1003 | } |
1004 | #endif |
1005 | if (responseNum == HTTP_MOVED_TEMPORARILY) { |
1006 | len += sprintf(iobuf + len, "Location: %s/%s%s\r\n", |
1007 | found_moved_temporarily, |
1008 | (g_query ? "?" : ""), |
1009 | (g_query ? g_query : "")); |
1010 | } |
1011 | |
1012 | #if ENABLE_FEATURE_HTTPD_ERROR_PAGES |
1013 | if (error_page && access(error_page, R_OK) == 0) { |
1014 | strcat(iobuf, "\r\n"); |
1015 | len += 2; |
1016 | |
1017 | if (DEBUG) |
1018 | fprintf(stderr, "headers: '%s'\n", iobuf); |
1019 | full_write(STDOUT_FILENO, iobuf, len); |
1020 | if (DEBUG) |
1021 | fprintf(stderr, "writing error page: '%s'\n", error_page); |
1022 | return send_file_and_exit(error_page, SEND_BODY); |
1023 | } |
1024 | #endif |
1025 | |
1026 | if (file_size != -1) { /* file */ |
1027 | strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&last_mod)); |
1028 | #if ENABLE_FEATURE_HTTPD_RANGES |
1029 | if (responseNum == HTTP_PARTIAL_CONTENT) { |
1030 | len += sprintf(iobuf + len, "Content-Range: bytes %"OFF_FMT"d-%"OFF_FMT"d/%"OFF_FMT"d\r\n", |
1031 | range_start, |
1032 | range_end, |
1033 | file_size); |
1034 | file_size = range_end - range_start + 1; |
1035 | } |
1036 | #endif |
1037 | len += sprintf(iobuf + len, |
1038 | #if ENABLE_FEATURE_HTTPD_RANGES |
1039 | "Accept-Ranges: bytes\r\n" |
1040 | #endif |
1041 | "Last-Modified: %s\r\n%s %"OFF_FMT"d\r\n", |
1042 | tmp_str, |
1043 | "Content-length:", |
1044 | file_size |
1045 | ); |
1046 | } |
1047 | iobuf[len++] = '\r'; |
1048 | iobuf[len++] = '\n'; |
1049 | if (infoString) { |
1050 | len += sprintf(iobuf + len, |
1051 | "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>\n" |
1052 | "<BODY><H1>%d %s</H1>\n%s\n</BODY></HTML>\n", |
1053 | responseNum, responseString, |
1054 | responseNum, responseString, infoString); |
1055 | } |
1056 | if (DEBUG) |
1057 | fprintf(stderr, "headers: '%s'\n", iobuf); |
1058 | if (full_write(STDOUT_FILENO, iobuf, len) != len) { |
1059 | if (verbose > 1) |
1060 | bb_perror_msg("error"); |
1061 | log_and_exit(); |
1062 | } |
1063 | } |
1064 | |
1065 | static void send_headers_and_exit(int responseNum) NORETURN; |
1066 | static void send_headers_and_exit(int responseNum) |
1067 | { |
1068 | send_headers(responseNum); |
1069 | log_and_exit(); |
1070 | } |
1071 | |
1072 | /* |
1073 | * Read from the socket until '\n' or EOF. '\r' chars are removed. |
1074 | * '\n' is replaced with NUL. |
1075 | * Return number of characters read or 0 if nothing is read |
1076 | * ('\r' and '\n' are not counted). |
1077 | * Data is returned in iobuf. |
1078 | */ |
1079 | static int get_line(void) |
1080 | { |
1081 | int count = 0; |
1082 | char c; |
1083 | |
1084 | while (1) { |
1085 | if (hdr_cnt <= 0) { |
1086 | hdr_cnt = safe_read(STDIN_FILENO, hdr_buf, sizeof(hdr_buf)); |
1087 | if (hdr_cnt <= 0) |
1088 | break; |
1089 | hdr_ptr = hdr_buf; |
1090 | } |
1091 | iobuf[count] = c = *hdr_ptr++; |
1092 | hdr_cnt--; |
1093 | |
1094 | if (c == '\r') |
1095 | continue; |
1096 | if (c == '\n') { |
1097 | iobuf[count] = '\0'; |
1098 | return count; |
1099 | } |
1100 | if (count < (IOBUF_SIZE - 1)) /* check overflow */ |
1101 | count++; |
1102 | } |
1103 | return count; |
1104 | } |
1105 | |
1106 | #if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY |
1107 | |
1108 | /* gcc 4.2.1 fares better with NOINLINE */ |
1109 | static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) NORETURN; |
1110 | static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) |
1111 | { |
1112 | enum { FROM_CGI = 1, TO_CGI = 2 }; /* indexes in pfd[] */ |
1113 | struct pollfd pfd[3]; |
1114 | int out_cnt; /* we buffer a bit of initial CGI output */ |
1115 | int count; |
1116 | |
1117 | /* iobuf is used for CGI -> network data, |
1118 | * hdr_buf is for network -> CGI data (POSTDATA) */ |
1119 | |
1120 | /* If CGI dies, we still want to correctly finish reading its output |
1121 | * and send it to the peer. So please no SIGPIPEs! */ |
1122 | signal(SIGPIPE, SIG_IGN); |
1123 | |
1124 | // We inconsistently handle a case when more POSTDATA from network |
1125 | // is coming than we expected. We may give *some part* of that |
1126 | // extra data to CGI. |
1127 | |
1128 | //if (hdr_cnt > post_len) { |
1129 | // /* We got more POSTDATA from network than we expected */ |
1130 | // hdr_cnt = post_len; |
1131 | //} |
1132 | post_len -= hdr_cnt; |
1133 | /* post_len - number of POST bytes not yet read from network */ |
1134 | |
1135 | /* NB: breaking out of this loop jumps to log_and_exit() */ |
1136 | out_cnt = 0; |
1137 | while (1) { |
1138 | memset(pfd, 0, sizeof(pfd)); |
1139 | |
1140 | pfd[FROM_CGI].fd = fromCgi_rd; |
1141 | pfd[FROM_CGI].events = POLLIN; |
1142 | |
1143 | if (toCgi_wr) { |
1144 | pfd[TO_CGI].fd = toCgi_wr; |
1145 | if (hdr_cnt > 0) { |
1146 | pfd[TO_CGI].events = POLLOUT; |
1147 | } else if (post_len > 0) { |
1148 | pfd[0].events = POLLIN; |
1149 | } else { |
1150 | /* post_len <= 0 && hdr_cnt <= 0: |
1151 | * no more POST data to CGI, |
1152 | * let CGI see EOF on CGI's stdin */ |
1153 | close(toCgi_wr); |
1154 | toCgi_wr = 0; |
1155 | } |
1156 | } |
1157 | |
1158 | /* Now wait on the set of sockets */ |
1159 | count = safe_poll(pfd, 3, -1); |
1160 | if (count <= 0) { |
1161 | #if 0 |
1162 | if (safe_waitpid(pid, &status, WNOHANG) <= 0) { |
1163 | /* Weird. CGI didn't exit and no fd's |
1164 | * are ready, yet poll returned?! */ |
1165 | continue; |
1166 | } |
1167 | if (DEBUG && WIFEXITED(status)) |
1168 | bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status)); |
1169 | if (DEBUG && WIFSIGNALED(status)) |
1170 | bb_error_msg("CGI killed, signal=%d", WTERMSIG(status)); |
1171 | #endif |
1172 | break; |
1173 | } |
1174 | |
1175 | if (pfd[TO_CGI].revents) { |
1176 | /* hdr_cnt > 0 here due to the way pfd[TO_CGI].events set */ |
1177 | /* Have data from peer and can write to CGI */ |
1178 | count = safe_write(toCgi_wr, hdr_ptr, hdr_cnt); |
1179 | /* Doesn't happen, we dont use nonblocking IO here |
1180 | *if (count < 0 && errno == EAGAIN) { |
1181 | * ... |
1182 | *} else */ |
1183 | if (count > 0) { |
1184 | hdr_ptr += count; |
1185 | hdr_cnt -= count; |
1186 | } else { |
1187 | /* EOF/broken pipe to CGI, stop piping POST data */ |
1188 | hdr_cnt = post_len = 0; |
1189 | } |
1190 | } |
1191 | |
1192 | if (pfd[0].revents) { |
1193 | /* post_len > 0 && hdr_cnt == 0 here */ |
1194 | /* We expect data, prev data portion is eaten by CGI |
1195 | * and there *is* data to read from the peer |
1196 | * (POSTDATA) */ |
1197 | //count = post_len > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : post_len; |
1198 | //count = safe_read(STDIN_FILENO, hdr_buf, count); |
1199 | count = safe_read(STDIN_FILENO, hdr_buf, sizeof(hdr_buf)); |
1200 | if (count > 0) { |
1201 | hdr_cnt = count; |
1202 | hdr_ptr = hdr_buf; |
1203 | post_len -= count; |
1204 | } else { |
1205 | /* no more POST data can be read */ |
1206 | post_len = 0; |
1207 | } |
1208 | } |
1209 | |
1210 | if (pfd[FROM_CGI].revents) { |
1211 | /* There is something to read from CGI */ |
1212 | char *rbuf = iobuf; |
1213 | |
1214 | /* Are we still buffering CGI output? */ |
1215 | if (out_cnt >= 0) { |
1216 | /* HTTP_200[] has single "\r\n" at the end. |
1217 | * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html, |
1218 | * CGI scripts MUST send their own header terminated by |
1219 | * empty line, then data. That's why we have only one |
1220 | * <cr><lf> pair here. We will output "200 OK" line |
1221 | * if needed, but CGI still has to provide blank line |
1222 | * between header and body */ |
1223 | |
1224 | /* Must use safe_read, not full_read, because |
1225 | * CGI may output a few first bytes and then wait |
1226 | * for POSTDATA without closing stdout. |
1227 | * With full_read we may wait here forever. */ |
1228 | count = safe_read(fromCgi_rd, rbuf + out_cnt, PIPE_BUF - 8); |
1229 | if (count <= 0) { |
1230 | /* eof (or error) and there was no "HTTP", |
1231 | * so write it, then write received data */ |
1232 | if (out_cnt) { |
1233 | full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1); |
1234 | full_write(STDOUT_FILENO, rbuf, out_cnt); |
1235 | } |
1236 | break; /* CGI stdout is closed, exiting */ |
1237 | } |
1238 | out_cnt += count; |
1239 | count = 0; |
1240 | /* "Status" header format is: "Status: 302 Redirected\r\n" */ |
1241 | if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) { |
1242 | /* send "HTTP/1.0 " */ |
1243 | if (full_write(STDOUT_FILENO, HTTP_200, 9) != 9) |
1244 | break; |
1245 | rbuf += 8; /* skip "Status: " */ |
1246 | count = out_cnt - 8; |
1247 | out_cnt = -1; /* buffering off */ |
1248 | } else if (out_cnt >= 4) { |
1249 | /* Did CGI add "HTTP"? */ |
1250 | if (memcmp(rbuf, HTTP_200, 4) != 0) { |
1251 | /* there is no "HTTP", do it ourself */ |
1252 | if (full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1) |
1253 | break; |
1254 | } |
1255 | /* Commented out: |
1256 | if (!strstr(rbuf, "ontent-")) { |
1257 | full_write(s, "Content-type: text/plain\r\n\r\n", 28); |
1258 | } |
1259 | * Counter-example of valid CGI without Content-type: |
1260 | * echo -en "HTTP/1.0 302 Found\r\n" |
1261 | * echo -en "Location: http://www.busybox.net\r\n" |
1262 | * echo -en "\r\n" |
1263 | */ |
1264 | count = out_cnt; |
1265 | out_cnt = -1; /* buffering off */ |
1266 | } |
1267 | } else { |
1268 | count = safe_read(fromCgi_rd, rbuf, PIPE_BUF); |
1269 | if (count <= 0) |
1270 | break; /* eof (or error) */ |
1271 | } |
1272 | if (full_write(STDOUT_FILENO, rbuf, count) != count) |
1273 | break; |
1274 | if (DEBUG) |
1275 | fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf); |
1276 | } /* if (pfd[FROM_CGI].revents) */ |
1277 | } /* while (1) */ |
1278 | log_and_exit(); |
1279 | } |
1280 | #endif |
1281 | |
1282 | #if ENABLE_FEATURE_HTTPD_CGI |
1283 | |
1284 | static void setenv1(const char *name, const char *value) |
1285 | { |
1286 | setenv(name, value ? value : "", 1); |
1287 | } |
1288 | |
1289 | /* |
1290 | * Spawn CGI script, forward CGI's stdin/out <=> network |
1291 | * |
1292 | * Environment variables are set up and the script is invoked with pipes |
1293 | * for stdin/stdout. If a POST is being done the script is fed the POST |
1294 | * data in addition to setting the QUERY_STRING variable (for GETs or POSTs). |
1295 | * |
1296 | * Parameters: |
1297 | * const char *url The requested URL (with leading /). |
1298 | * int post_len Length of the POST body. |
1299 | * const char *cookie For set HTTP_COOKIE. |
1300 | * const char *content_type For set CONTENT_TYPE. |
1301 | */ |
1302 | static void send_cgi_and_exit( |
1303 | const char *url, |
1304 | const char *request, |
1305 | int post_len, |
1306 | const char *cookie, |
1307 | const char *content_type) NORETURN; |
1308 | static void send_cgi_and_exit( |
1309 | const char *url, |
1310 | const char *request, |
1311 | int post_len, |
1312 | const char *cookie, |
1313 | const char *content_type) |
1314 | { |
1315 | struct fd_pair fromCgi; /* CGI -> httpd pipe */ |
1316 | struct fd_pair toCgi; /* httpd -> CGI pipe */ |
1317 | char *script; |
1318 | int pid; |
1319 | |
1320 | /* Make a copy. NB: caller guarantees: |
1321 | * url[0] == '/', url[1] != '/' */ |
1322 | url = xstrdup(url); |
1323 | |
1324 | /* |
1325 | * We are mucking with environment _first_ and then vfork/exec, |
1326 | * this allows us to use vfork safely. Parent doesn't care about |
1327 | * these environment changes anyway. |
1328 | */ |
1329 | |
1330 | /* Check for [dirs/]script.cgi/PATH_INFO */ |
1331 | script = (char*)url; |
1332 | while ((script = strchr(script + 1, '/')) != NULL) { |
1333 | struct stat sb; |
1334 | |
1335 | *script = '\0'; |
1336 | if (!is_directory(url + 1, 1, &sb)) { |
1337 | /* not directory, found script.cgi/PATH_INFO */ |
1338 | *script = '/'; |
1339 | break; |
1340 | } |
1341 | *script = '/'; /* is directory, find next '/' */ |
1342 | } |
1343 | setenv1("PATH_INFO", script); /* set to /PATH_INFO or "" */ |
1344 | setenv1("REQUEST_METHOD", request); |
1345 | if (g_query) { |
1346 | putenv(xasprintf("%s=%s?%s", "REQUEST_URI", url, g_query)); |
1347 | } else { |
1348 | setenv1("REQUEST_URI", url); |
1349 | } |
1350 | if (script != NULL) |
1351 | *script = '\0'; /* cut off /PATH_INFO */ |
1352 | |
1353 | /* SCRIPT_FILENAME is required by PHP in CGI mode */ |
1354 | if (home_httpd[0] == '/') { |
1355 | char *fullpath = concat_path_file(home_httpd, url); |
1356 | setenv1("SCRIPT_FILENAME", fullpath); |
1357 | } |
1358 | /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */ |
1359 | setenv1("SCRIPT_NAME", url); |
1360 | /* http://hoohoo.ncsa.uiuc.edu/cgi/env.html: |
1361 | * QUERY_STRING: The information which follows the ? in the URL |
1362 | * which referenced this script. This is the query information. |
1363 | * It should not be decoded in any fashion. This variable |
1364 | * should always be set when there is query information, |
1365 | * regardless of command line decoding. */ |
1366 | /* (Older versions of bbox seem to do some decoding) */ |
1367 | setenv1("QUERY_STRING", g_query); |
1368 | putenv((char*)"SERVER_SOFTWARE=busybox httpd/"BB_VER); |
1369 | putenv((char*)"SERVER_PROTOCOL=HTTP/1.0"); |
1370 | putenv((char*)"GATEWAY_INTERFACE=CGI/1.1"); |
1371 | /* Having _separate_ variables for IP and port defeats |
1372 | * the purpose of having socket abstraction. Which "port" |
1373 | * are you using on Unix domain socket? |
1374 | * IOW - REMOTE_PEER="1.2.3.4:56" makes much more sense. |
1375 | * Oh well... */ |
1376 | { |
1377 | char *p = rmt_ip_str ? rmt_ip_str : (char*)""; |
1378 | char *cp = strrchr(p, ':'); |
1379 | if (ENABLE_FEATURE_IPV6 && cp && strchr(cp, ']')) |
1380 | cp = NULL; |
1381 | if (cp) *cp = '\0'; /* delete :PORT */ |
1382 | setenv1("REMOTE_ADDR", p); |
1383 | if (cp) { |
1384 | *cp = ':'; |
1385 | #if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV |
1386 | setenv1("REMOTE_PORT", cp + 1); |
1387 | #endif |
1388 | } |
1389 | } |
1390 | setenv1("HTTP_USER_AGENT", user_agent); |
1391 | if (http_accept) |
1392 | setenv1("HTTP_ACCEPT", http_accept); |
1393 | if (http_accept_language) |
1394 | setenv1("HTTP_ACCEPT_LANGUAGE", http_accept_language); |
1395 | if (post_len) |
1396 | putenv(xasprintf("CONTENT_LENGTH=%d", post_len)); |
1397 | if (cookie) |
1398 | setenv1("HTTP_COOKIE", cookie); |
1399 | if (content_type) |
1400 | setenv1("CONTENT_TYPE", content_type); |
1401 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
1402 | if (remoteuser) { |
1403 | setenv1("REMOTE_USER", remoteuser); |
1404 | putenv((char*)"AUTH_TYPE=Basic"); |
1405 | } |
1406 | #endif |
1407 | if (referer) |
1408 | setenv1("HTTP_REFERER", referer); |
1409 | |
1410 | xpiped_pair(fromCgi); |
1411 | xpiped_pair(toCgi); |
1412 | |
1413 | pid = vfork(); |
1414 | if (pid < 0) { |
1415 | /* TODO: log perror? */ |
1416 | log_and_exit(); |
1417 | } |
1418 | |
1419 | if (!pid) { |
1420 | /* Child process */ |
1421 | char *argv[3]; |
1422 | |
1423 | xfunc_error_retval = 242; |
1424 | |
1425 | /* NB: close _first_, then move fds! */ |
1426 | close(toCgi.wr); |
1427 | close(fromCgi.rd); |
1428 | xmove_fd(toCgi.rd, 0); /* replace stdin with the pipe */ |
1429 | xmove_fd(fromCgi.wr, 1); /* replace stdout with the pipe */ |
1430 | /* User seeing stderr output can be a security problem. |
1431 | * If CGI really wants that, it can always do dup itself. */ |
1432 | /* dup2(1, 2); */ |
1433 | |
1434 | /* Chdiring to script's dir */ |
1435 | script = strrchr(url, '/'); |
1436 | if (script != url) { /* paranoia */ |
1437 | *script = '\0'; |
1438 | if (chdir(url + 1) != 0) { |
1439 | bb_perror_msg("chdir %s", url + 1); |
1440 | goto error_execing_cgi; |
1441 | } |
1442 | // not needed: *script = '/'; |
1443 | } |
1444 | script++; |
1445 | |
1446 | /* set argv[0] to name without path */ |
1447 | argv[0] = script; |
1448 | argv[1] = NULL; |
1449 | |
1450 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
1451 | { |
1452 | char *suffix = strrchr(script, '.'); |
1453 | |
1454 | if (suffix) { |
1455 | Htaccess *cur; |
1456 | for (cur = script_i; cur; cur = cur->next) { |
1457 | if (strcmp(cur->before_colon + 1, suffix) == 0) { |
1458 | /* found interpreter name */ |
1459 | argv[0] = cur->after_colon; |
1460 | argv[1] = script; |
1461 | argv[2] = NULL; |
1462 | break; |
1463 | } |
1464 | } |
1465 | } |
1466 | } |
1467 | #endif |
1468 | /* restore default signal dispositions for CGI process */ |
1469 | bb_signals(0 |
1470 | | (1 << SIGCHLD) |
1471 | | (1 << SIGPIPE) |
1472 | | (1 << SIGHUP) |
1473 | , SIG_DFL); |
1474 | |
1475 | /* _NOT_ execvp. We do not search PATH. argv[0] is a filename |
1476 | * without any dir components and will only match a file |
1477 | * in the current directory */ |
1478 | execv(argv[0], argv); |
1479 | if (verbose) |
1480 | bb_perror_msg("exec %s", argv[0]); |
1481 | error_execing_cgi: |
1482 | /* send to stdout |
1483 | * (we are CGI here, our stdout is pumped to the net) */ |
1484 | send_headers_and_exit(HTTP_NOT_FOUND); |
1485 | } /* end child */ |
1486 | |
1487 | /* Parent process */ |
1488 | |
1489 | /* Restore variables possibly changed by child */ |
1490 | xfunc_error_retval = 0; |
1491 | |
1492 | /* Pump data */ |
1493 | close(fromCgi.wr); |
1494 | close(toCgi.rd); |
1495 | cgi_io_loop_and_exit(fromCgi.rd, toCgi.wr, post_len); |
1496 | } |
1497 | |
1498 | #endif /* FEATURE_HTTPD_CGI */ |
1499 | |
1500 | /* |
1501 | * Send a file response to a HTTP request, and exit |
1502 | * |
1503 | * Parameters: |
1504 | * const char *url The requested URL (with leading /). |
1505 | * what What to send (headers/body/both). |
1506 | */ |
1507 | static void send_file_and_exit(const char *url, int what) |
1508 | { |
1509 | static const char *const suffixTable[] = { |
1510 | /* Warning: shorter equivalent suffix in one line must be first */ |
1511 | ".htm.html", "text/html", |
1512 | ".jpg.jpeg", "image/jpeg", |
1513 | ".gif", "image/gif", |
1514 | ".png", "image/png", |
1515 | ".txt.h.c.cc.cpp", "text/plain", |
1516 | ".css", "text/css", |
1517 | ".wav", "audio/wav", |
1518 | ".avi", "video/x-msvideo", |
1519 | ".qt.mov", "video/quicktime", |
1520 | ".mpe.mpeg", "video/mpeg", |
1521 | ".mid.midi", "audio/midi", |
1522 | ".mp3", "audio/mpeg", |
1523 | #if 0 /* unpopular */ |
1524 | ".au", "audio/basic", |
1525 | ".pac", "application/x-ns-proxy-autoconfig", |
1526 | ".vrml.wrl", "model/vrml", |
1527 | #endif |
1528 | NULL |
1529 | }; |
1530 | |
1531 | char *suffix; |
1532 | int f; |
1533 | const char *const *table; |
1534 | const char *try_suffix; |
1535 | ssize_t count; |
1536 | #if ENABLE_FEATURE_HTTPD_USE_SENDFILE |
1537 | off_t offset; |
1538 | #endif |
1539 | |
1540 | /* If you want to know about EPIPE below |
1541 | * (happens if you abort downloads from local httpd): */ |
1542 | signal(SIGPIPE, SIG_IGN); |
1543 | |
1544 | suffix = strrchr(url, '.'); |
1545 | |
1546 | /* If not found, set default as "application/octet-stream"; */ |
1547 | found_mime_type = "application/octet-stream"; |
1548 | if (suffix) { |
1549 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES |
1550 | Htaccess *cur; |
1551 | #endif |
1552 | for (table = suffixTable; *table; table += 2) { |
1553 | try_suffix = strstr(table[0], suffix); |
1554 | if (try_suffix) { |
1555 | try_suffix += strlen(suffix); |
1556 | if (*try_suffix == '\0' || *try_suffix == '.') { |
1557 | found_mime_type = table[1]; |
1558 | break; |
1559 | } |
1560 | } |
1561 | } |
1562 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES |
1563 | for (cur = mime_a; cur; cur = cur->next) { |
1564 | if (strcmp(cur->before_colon, suffix) == 0) { |
1565 | found_mime_type = cur->after_colon; |
1566 | break; |
1567 | } |
1568 | } |
1569 | #endif |
1570 | } |
1571 | |
1572 | if (DEBUG) |
1573 | bb_error_msg("sending file '%s' content-type: %s", |
1574 | url, found_mime_type); |
1575 | |
1576 | f = open(url, O_RDONLY); |
1577 | if (f < 0) { |
1578 | if (DEBUG) |
1579 | bb_perror_msg("can't open '%s'", url); |
1580 | /* Error pages are sent by using send_file_and_exit(SEND_BODY). |
1581 | * IOW: it is unsafe to call send_headers_and_exit |
1582 | * if what is SEND_BODY! Can recurse! */ |
1583 | if (what != SEND_BODY) |
1584 | send_headers_and_exit(HTTP_NOT_FOUND); |
1585 | log_and_exit(); |
1586 | } |
1587 | #if ENABLE_FEATURE_HTTPD_RANGES |
1588 | if (what == SEND_BODY) |
1589 | range_start = 0; /* err pages and ranges don't mix */ |
1590 | range_len = MAXINT(off_t); |
1591 | if (range_start) { |
1592 | if (!range_end) { |
1593 | range_end = file_size - 1; |
1594 | } |
1595 | if (range_end < range_start |
1596 | || lseek(f, range_start, SEEK_SET) != range_start |
1597 | ) { |
1598 | lseek(f, 0, SEEK_SET); |
1599 | range_start = 0; |
1600 | } else { |
1601 | range_len = range_end - range_start + 1; |
1602 | send_headers(HTTP_PARTIAL_CONTENT); |
1603 | what = SEND_BODY; |
1604 | } |
1605 | } |
1606 | #endif |
1607 | |
1608 | if (what & SEND_HEADERS) |
1609 | send_headers(HTTP_OK); |
1610 | |
1611 | #if ENABLE_FEATURE_HTTPD_USE_SENDFILE |
1612 | offset = range_start; |
1613 | do { |
1614 | /* sz is rounded down to 64k */ |
1615 | ssize_t sz = MAXINT(ssize_t) - 0xffff; |
1616 | USE_FEATURE_HTTPD_RANGES(if (sz > range_len) sz = range_len;) |
1617 | count = sendfile(1, f, &offset, sz); |
1618 | if (count < 0) { |
1619 | if (offset == range_start) |
1620 | goto fallback; |
1621 | goto fin; |
1622 | } |
1623 | USE_FEATURE_HTTPD_RANGES(range_len -= sz;) |
1624 | } while (count > 0 && range_len); |
1625 | log_and_exit(); |
1626 | |
1627 | fallback: |
1628 | #endif |
1629 | while ((count = safe_read(f, iobuf, IOBUF_SIZE)) > 0) { |
1630 | ssize_t n; |
1631 | USE_FEATURE_HTTPD_RANGES(if (count > range_len) count = range_len;) |
1632 | n = full_write(STDOUT_FILENO, iobuf, count); |
1633 | if (count != n) |
1634 | break; |
1635 | USE_FEATURE_HTTPD_RANGES(range_len -= count;) |
1636 | if (!range_len) |
1637 | break; |
1638 | } |
1639 | #if ENABLE_FEATURE_HTTPD_USE_SENDFILE |
1640 | fin: |
1641 | #endif |
1642 | if (count < 0 && verbose > 1) |
1643 | bb_perror_msg("error"); |
1644 | log_and_exit(); |
1645 | } |
1646 | |
1647 | static int checkPermIP(void) |
1648 | { |
1649 | Htaccess_IP *cur; |
1650 | |
1651 | for (cur = ip_a_d; cur; cur = cur->next) { |
1652 | #if DEBUG |
1653 | fprintf(stderr, |
1654 | "checkPermIP: '%s' ? '%u.%u.%u.%u/%u.%u.%u.%u'\n", |
1655 | rmt_ip_str, |
1656 | (unsigned char)(cur->ip >> 24), |
1657 | (unsigned char)(cur->ip >> 16), |
1658 | (unsigned char)(cur->ip >> 8), |
1659 | (unsigned char)(cur->ip), |
1660 | (unsigned char)(cur->mask >> 24), |
1661 | (unsigned char)(cur->mask >> 16), |
1662 | (unsigned char)(cur->mask >> 8), |
1663 | (unsigned char)(cur->mask) |
1664 | ); |
1665 | #endif |
1666 | if ((rmt_ip & cur->mask) == cur->ip) |
1667 | return (cur->allow_deny == 'A'); /* A -> 1 */ |
1668 | } |
1669 | |
1670 | return !flg_deny_all; /* depends on whether we saw "D:*" */ |
1671 | } |
1672 | |
1673 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
1674 | /* |
1675 | * Config file entries are of the form "/<path>:<user>:<passwd>". |
1676 | * If config file has no prefix match for path, access is allowed. |
1677 | * |
1678 | * path The file path |
1679 | * user_and_passwd "user:passwd" to validate |
1680 | * |
1681 | * Returns 1 if user_and_passwd is OK. |
1682 | */ |
1683 | static int check_user_passwd(const char *path, const char *user_and_passwd) |
1684 | { |
1685 | Htaccess *cur; |
1686 | const char *prev = NULL; |
1687 | |
1688 | for (cur = g_auth; cur; cur = cur->next) { |
1689 | const char *dir_prefix; |
1690 | size_t len; |
1691 | |
1692 | dir_prefix = cur->before_colon; |
1693 | |
1694 | /* WHY? */ |
1695 | /* If already saw a match, don't accept other different matches */ |
1696 | if (prev && strcmp(prev, dir_prefix) != 0) |
1697 | continue; |
1698 | |
1699 | if (DEBUG) |
1700 | fprintf(stderr, "checkPerm: '%s' ? '%s'\n", dir_prefix, user_and_passwd); |
1701 | |
1702 | /* If it's not a prefix match, continue searching */ |
1703 | len = strlen(dir_prefix); |
1704 | if (len != 1 /* dir_prefix "/" matches all, don't need to check */ |
1705 | && (strncmp(dir_prefix, path, len) != 0 |
1706 | || (path[len] != '/' && path[len] != '\0')) |
1707 | ) { |
1708 | continue; |
1709 | } |
1710 | |
1711 | /* Path match found */ |
1712 | prev = dir_prefix; |
1713 | |
1714 | if (ENABLE_FEATURE_HTTPD_AUTH_MD5) { |
1715 | char *md5_passwd; |
1716 | |
1717 | md5_passwd = strchr(cur->after_colon, ':'); |
1718 | if (md5_passwd && md5_passwd[1] == '$' && md5_passwd[2] == '1' |
1719 | && md5_passwd[3] == '$' && md5_passwd[4] |
1720 | ) { |
1721 | char *encrypted; |
1722 | int r, user_len_p1; |
1723 | |
1724 | md5_passwd++; |
1725 | user_len_p1 = md5_passwd - cur->after_colon; |
1726 | /* comparing "user:" */ |
1727 | if (strncmp(cur->after_colon, user_and_passwd, user_len_p1) != 0) { |
1728 | continue; |
1729 | } |
1730 | |
1731 | encrypted = pw_encrypt( |
1732 | user_and_passwd + user_len_p1 /* cleartext pwd from user */, |
1733 | md5_passwd /*salt */, 1 /* cleanup */); |
1734 | r = strcmp(encrypted, md5_passwd); |
1735 | free(encrypted); |
1736 | if (r == 0) |
1737 | goto set_remoteuser_var; /* Ok */ |
1738 | continue; |
1739 | } |
1740 | } |
1741 | |
1742 | /* Comparing plaintext "user:pass" in one go */ |
1743 | if (strcmp(cur->after_colon, user_and_passwd) == 0) { |
1744 | set_remoteuser_var: |
1745 | remoteuser = xstrndup(user_and_passwd, |
1746 | strchrnul(user_and_passwd, ':') - user_and_passwd); |
1747 | return 1; /* Ok */ |
1748 | } |
1749 | } /* for */ |
1750 | |
1751 | /* 0(bad) if prev is set: matches were found but passwd was wrong */ |
1752 | return (prev == NULL); |
1753 | } |
1754 | #endif /* FEATURE_HTTPD_BASIC_AUTH */ |
1755 | |
1756 | #if ENABLE_FEATURE_HTTPD_PROXY |
1757 | static Htaccess_Proxy *find_proxy_entry(const char *url) |
1758 | { |
1759 | Htaccess_Proxy *p; |
1760 | for (p = proxy; p; p = p->next) { |
1761 | if (strncmp(url, p->url_from, strlen(p->url_from)) == 0) |
1762 | return p; |
1763 | } |
1764 | return NULL; |
1765 | } |
1766 | #endif |
1767 | |
1768 | /* |
1769 | * Handle timeouts |
1770 | */ |
1771 | static void exit_on_signal(int sig) NORETURN; |
1772 | static void exit_on_signal(int sig UNUSED_PARAM) |
1773 | { |
1774 | send_headers_and_exit(HTTP_REQUEST_TIMEOUT); |
1775 | } |
1776 | |
1777 | /* |
1778 | * Handle an incoming http request and exit. |
1779 | */ |
1780 | static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) NORETURN; |
1781 | static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) |
1782 | { |
1783 | static const char request_GET[] ALIGN1 = "GET"; |
1784 | struct stat sb; |
1785 | char *urlcopy; |
1786 | char *urlp; |
1787 | char *tptr; |
1788 | #if ENABLE_FEATURE_HTTPD_CGI |
1789 | static const char request_HEAD[] ALIGN1 = "HEAD"; |
1790 | const char *prequest; |
1791 | char *cookie = NULL; |
1792 | char *content_type = NULL; |
1793 | unsigned long length = 0; |
1794 | #elif ENABLE_FEATURE_HTTPD_PROXY |
1795 | #define prequest request_GET |
1796 | unsigned long length = 0; |
1797 | #endif |
1798 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
1799 | smallint authorized = -1; |
1800 | #endif |
1801 | smallint ip_allowed; |
1802 | char http_major_version; |
1803 | #if ENABLE_FEATURE_HTTPD_PROXY |
1804 | char http_minor_version; |
1805 | char *header_buf = header_buf; /* for gcc */ |
1806 | char *header_ptr = header_ptr; |
1807 | Htaccess_Proxy *proxy_entry; |
1808 | #endif |
1809 | |
1810 | /* Allocation of iobuf is postponed until now |
1811 | * (IOW, server process doesn't need to waste 8k) */ |
1812 | iobuf = xmalloc(IOBUF_SIZE); |
1813 | |
1814 | rmt_ip = 0; |
1815 | if (fromAddr->u.sa.sa_family == AF_INET) { |
1816 | rmt_ip = ntohl(fromAddr->u.sin.sin_addr.s_addr); |
1817 | } |
1818 | #if ENABLE_FEATURE_IPV6 |
1819 | if (fromAddr->u.sa.sa_family == AF_INET6 |
1820 | && fromAddr->u.sin6.sin6_addr.s6_addr32[0] == 0 |
1821 | && fromAddr->u.sin6.sin6_addr.s6_addr32[1] == 0 |
1822 | && ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[2]) == 0xffff) |
1823 | rmt_ip = ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[3]); |
1824 | #endif |
1825 | if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) { |
1826 | /* NB: can be NULL (user runs httpd -i by hand?) */ |
1827 | rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr->u.sa); |
1828 | } |
1829 | if (verbose) { |
1830 | /* this trick makes -v logging much simpler */ |
1831 | if (rmt_ip_str) |
1832 | applet_name = rmt_ip_str; |
1833 | if (verbose > 2) |
1834 | bb_error_msg("connected"); |
1835 | } |
1836 | |
1837 | /* Install timeout handler */ |
1838 | signal_no_SA_RESTART_empty_mask(SIGALRM, exit_on_signal); |
1839 | alarm(HEADER_READ_TIMEOUT); |
1840 | |
1841 | if (!get_line()) /* EOF or error or empty line */ |
1842 | send_headers_and_exit(HTTP_BAD_REQUEST); |
1843 | |
1844 | /* Determine type of request (GET/POST) */ |
1845 | urlp = strpbrk(iobuf, " \t"); |
1846 | if (urlp == NULL) |
1847 | send_headers_and_exit(HTTP_BAD_REQUEST); |
1848 | *urlp++ = '\0'; |
1849 | #if ENABLE_FEATURE_HTTPD_CGI |
1850 | prequest = request_GET; |
1851 | if (strcasecmp(iobuf, prequest) != 0) { |
1852 | prequest = request_HEAD; |
1853 | if (strcasecmp(iobuf, prequest) != 0) { |
1854 | prequest = "POST"; |
1855 | if (strcasecmp(iobuf, prequest) != 0) |
1856 | send_headers_and_exit(HTTP_NOT_IMPLEMENTED); |
1857 | } |
1858 | } |
1859 | #else |
1860 | if (strcasecmp(iobuf, request_GET) != 0) |
1861 | send_headers_and_exit(HTTP_NOT_IMPLEMENTED); |
1862 | #endif |
1863 | urlp = skip_whitespace(urlp); |
1864 | if (urlp[0] != '/') |
1865 | send_headers_and_exit(HTTP_BAD_REQUEST); |
1866 | |
1867 | /* Find end of URL and parse HTTP version, if any */ |
1868 | http_major_version = '0'; |
1869 | USE_FEATURE_HTTPD_PROXY(http_minor_version = '0';) |
1870 | tptr = strchrnul(urlp, ' '); |
1871 | /* Is it " HTTP/"? */ |
1872 | if (tptr[0] && strncmp(tptr + 1, HTTP_200, 5) == 0) { |
1873 | http_major_version = tptr[6]; |
1874 | USE_FEATURE_HTTPD_PROXY(http_minor_version = tptr[8];) |
1875 | } |
1876 | *tptr = '\0'; |
1877 | |
1878 | /* Copy URL from after "GET "/"POST " to stack-allocated char[] */ |
1879 | urlcopy = alloca((tptr - urlp) + 2 + strlen(index_page)); |
1880 | /*if (urlcopy == NULL) |
1881 | * send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);*/ |
1882 | strcpy(urlcopy, urlp); |
1883 | /* NB: urlcopy ptr is never changed after this */ |
1884 | |
1885 | /* Extract url args if present */ |
1886 | g_query = NULL; |
1887 | tptr = strchr(urlcopy, '?'); |
1888 | if (tptr) { |
1889 | *tptr++ = '\0'; |
1890 | g_query = tptr; |
1891 | } |
1892 | |
1893 | /* Decode URL escape sequences */ |
1894 | tptr = decodeString(urlcopy, 0); |
1895 | if (tptr == NULL) |
1896 | send_headers_and_exit(HTTP_BAD_REQUEST); |
1897 | if (tptr == urlcopy + 1) { |
1898 | /* '/' or NUL is encoded */ |
1899 | send_headers_and_exit(HTTP_NOT_FOUND); |
1900 | } |
1901 | |
1902 | /* Canonicalize path */ |
1903 | /* Algorithm stolen from libbb bb_simplify_path(), |
1904 | * but don't strdup, retain trailing slash, protect root */ |
1905 | urlp = tptr = urlcopy; |
1906 | do { |
1907 | if (*urlp == '/') { |
1908 | /* skip duplicate (or initial) slash */ |
1909 | if (*tptr == '/') { |
1910 | continue; |
1911 | } |
1912 | if (*tptr == '.') { |
1913 | /* skip extra "/./" */ |
1914 | if (tptr[1] == '/' || !tptr[1]) { |
1915 | continue; |
1916 | } |
1917 | /* "..": be careful */ |
1918 | if (tptr[1] == '.' && (tptr[2] == '/' || !tptr[2])) { |
1919 | ++tptr; |
1920 | if (urlp == urlcopy) /* protect root */ |
1921 | send_headers_and_exit(HTTP_BAD_REQUEST); |
1922 | while (*--urlp != '/') /* omit previous dir */; |
1923 | continue; |
1924 | } |
1925 | } |
1926 | } |
1927 | *++urlp = *tptr; |
1928 | } while (*++tptr); |
1929 | *++urlp = '\0'; /* terminate after last character */ |
1930 | |
1931 | /* If URL is a directory, add '/' */ |
1932 | if (urlp[-1] != '/') { |
1933 | if (is_directory(urlcopy + 1, 1, &sb)) { |
1934 | found_moved_temporarily = urlcopy; |
1935 | } |
1936 | } |
1937 | |
1938 | /* Log it */ |
1939 | if (verbose > 1) |
1940 | bb_error_msg("url:%s", urlcopy); |
1941 | |
1942 | tptr = urlcopy; |
1943 | ip_allowed = checkPermIP(); |
1944 | while (ip_allowed && (tptr = strchr(tptr + 1, '/')) != NULL) { |
1945 | /* have path1/path2 */ |
1946 | *tptr = '\0'; |
1947 | if (is_directory(urlcopy + 1, 1, &sb)) { |
1948 | /* may have subdir config */ |
1949 | parse_conf(urlcopy + 1, SUBDIR_PARSE); |
1950 | ip_allowed = checkPermIP(); |
1951 | } |
1952 | *tptr = '/'; |
1953 | } |
1954 | |
1955 | #if ENABLE_FEATURE_HTTPD_PROXY |
1956 | proxy_entry = find_proxy_entry(urlcopy); |
1957 | if (proxy_entry) |
1958 | header_buf = header_ptr = xmalloc(IOBUF_SIZE); |
1959 | #endif |
1960 | |
1961 | if (http_major_version >= '0') { |
1962 | /* Request was with "... HTTP/nXXX", and n >= 0 */ |
1963 | |
1964 | /* Read until blank line for HTTP version specified, else parse immediate */ |
1965 | while (1) { |
1966 | alarm(HEADER_READ_TIMEOUT); |
1967 | if (!get_line()) |
1968 | break; /* EOF or error or empty line */ |
1969 | if (DEBUG) |
1970 | bb_error_msg("header: '%s'", iobuf); |
1971 | |
1972 | #if ENABLE_FEATURE_HTTPD_PROXY |
1973 | /* We need 2 more bytes for yet another "\r\n" - |
1974 | * see near fdprintf(proxy_fd...) further below */ |
1975 | if (proxy_entry && (header_ptr - header_buf) < IOBUF_SIZE - 2) { |
1976 | int len = strlen(iobuf); |
1977 | if (len > IOBUF_SIZE - (header_ptr - header_buf) - 4) |
1978 | len = IOBUF_SIZE - (header_ptr - header_buf) - 4; |
1979 | memcpy(header_ptr, iobuf, len); |
1980 | header_ptr += len; |
1981 | header_ptr[0] = '\r'; |
1982 | header_ptr[1] = '\n'; |
1983 | header_ptr += 2; |
1984 | } |
1985 | #endif |
1986 | |
1987 | #if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY |
1988 | /* Try and do our best to parse more lines */ |
1989 | if ((STRNCASECMP(iobuf, "Content-length:") == 0)) { |
1990 | /* extra read only for POST */ |
1991 | if (prequest != request_GET |
1992 | #if ENABLE_FEATURE_HTTPD_CGI |
1993 | && prequest != request_HEAD |
1994 | #endif |
1995 | ) { |
1996 | tptr = skip_whitespace(iobuf + sizeof("Content-length:") - 1); |
1997 | if (!tptr[0]) |
1998 | send_headers_and_exit(HTTP_BAD_REQUEST); |
1999 | /* not using strtoul: it ignores leading minus! */ |
2000 | length = bb_strtou(tptr, NULL, 10); |
2001 | /* length is "ulong", but we need to pass it to int later */ |
2002 | if (errno || length > INT_MAX) |
2003 | send_headers_and_exit(HTTP_BAD_REQUEST); |
2004 | } |
2005 | } |
2006 | #endif |
2007 | #if ENABLE_FEATURE_HTTPD_CGI |
2008 | else if (STRNCASECMP(iobuf, "Cookie:") == 0) { |
2009 | cookie = xstrdup(skip_whitespace(iobuf + sizeof("Cookie:")-1)); |
2010 | } else if (STRNCASECMP(iobuf, "Content-Type:") == 0) { |
2011 | content_type = xstrdup(skip_whitespace(iobuf + sizeof("Content-Type:")-1)); |
2012 | } else if (STRNCASECMP(iobuf, "Referer:") == 0) { |
2013 | referer = xstrdup(skip_whitespace(iobuf + sizeof("Referer:")-1)); |
2014 | } else if (STRNCASECMP(iobuf, "User-Agent:") == 0) { |
2015 | user_agent = xstrdup(skip_whitespace(iobuf + sizeof("User-Agent:")-1)); |
2016 | } else if (STRNCASECMP(iobuf, "Accept:") == 0) { |
2017 | http_accept = xstrdup(skip_whitespace(iobuf + sizeof("Accept:")-1)); |
2018 | } else if (STRNCASECMP(iobuf, "Accept-Language:") == 0) { |
2019 | http_accept_language = xstrdup(skip_whitespace(iobuf + sizeof("Accept-Language:")-1)); |
2020 | } |
2021 | #endif |
2022 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
2023 | if (STRNCASECMP(iobuf, "Authorization:") == 0) { |
2024 | /* We only allow Basic credentials. |
2025 | * It shows up as "Authorization: Basic <user>:<passwd>" where |
2026 | * "<user>:<passwd>" is base64 encoded. |
2027 | */ |
2028 | tptr = skip_whitespace(iobuf + sizeof("Authorization:")-1); |
2029 | if (STRNCASECMP(tptr, "Basic") != 0) |
2030 | continue; |
2031 | tptr += sizeof("Basic")-1; |
2032 | /* decodeBase64() skips whitespace itself */ |
2033 | decodeBase64(tptr); |
2034 | authorized = check_user_passwd(urlcopy, tptr); |
2035 | } |
2036 | #endif |
2037 | #if ENABLE_FEATURE_HTTPD_RANGES |
2038 | if (STRNCASECMP(iobuf, "Range:") == 0) { |
2039 | /* We know only bytes=NNN-[MMM] */ |
2040 | char *s = skip_whitespace(iobuf + sizeof("Range:")-1); |
2041 | if (strncmp(s, "bytes=", 6) == 0) { |
2042 | s += sizeof("bytes=")-1; |
2043 | range_start = BB_STRTOOFF(s, &s, 10); |
2044 | if (s[0] != '-' || range_start < 0) { |
2045 | range_start = 0; |
2046 | } else if (s[1]) { |
2047 | range_end = BB_STRTOOFF(s+1, NULL, 10); |
2048 | if (errno || range_end < range_start) |
2049 | range_start = 0; |
2050 | } |
2051 | } |
2052 | } |
2053 | #endif |
2054 | } /* while extra header reading */ |
2055 | } |
2056 | |
2057 | /* We are done reading headers, disable peer timeout */ |
2058 | alarm(0); |
2059 | |
2060 | if (strcmp(bb_basename(urlcopy), httpd_conf) == 0 || !ip_allowed) { |
2061 | /* protect listing [/path]/httpd_conf or IP deny */ |
2062 | send_headers_and_exit(HTTP_FORBIDDEN); |
2063 | } |
2064 | |
2065 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
2066 | /* Case: no "Authorization:" was seen, but page does require passwd. |
2067 | * Check that with dummy user:pass */ |
2068 | if (authorized < 0) |
2069 | authorized = check_user_passwd(urlcopy, ":"); |
2070 | if (!authorized) |
2071 | send_headers_and_exit(HTTP_UNAUTHORIZED); |
2072 | #endif |
2073 | |
2074 | if (found_moved_temporarily) { |
2075 | send_headers_and_exit(HTTP_MOVED_TEMPORARILY); |
2076 | } |
2077 | |
2078 | #if ENABLE_FEATURE_HTTPD_PROXY |
2079 | if (proxy_entry != NULL) { |
2080 | int proxy_fd; |
2081 | len_and_sockaddr *lsa; |
2082 | |
2083 | proxy_fd = socket(AF_INET, SOCK_STREAM, 0); |
2084 | if (proxy_fd < 0) |
2085 | send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); |
2086 | lsa = host2sockaddr(proxy_entry->host_port, 80); |
2087 | if (lsa == NULL) |
2088 | send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); |
2089 | if (connect(proxy_fd, &lsa->u.sa, lsa->len) < 0) |
2090 | send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR); |
2091 | fdprintf(proxy_fd, "%s %s%s%s%s HTTP/%c.%c\r\n", |
2092 | prequest, /* GET or POST */ |
2093 | proxy_entry->url_to, /* url part 1 */ |
2094 | urlcopy + strlen(proxy_entry->url_from), /* url part 2 */ |
2095 | (g_query ? "?" : ""), /* "?" (maybe) */ |
2096 | (g_query ? g_query : ""), /* query string (maybe) */ |
2097 | http_major_version, http_minor_version); |
2098 | header_ptr[0] = '\r'; |
2099 | header_ptr[1] = '\n'; |
2100 | header_ptr += 2; |
2101 | write(proxy_fd, header_buf, header_ptr - header_buf); |
2102 | free(header_buf); /* on the order of 8k, free it */ |
2103 | /* cgi_io_loop_and_exit needs to have two disctinct fds */ |
2104 | cgi_io_loop_and_exit(proxy_fd, dup(proxy_fd), length); |
2105 | } |
2106 | #endif |
2107 | |
2108 | tptr = urlcopy + 1; /* skip first '/' */ |
2109 | |
2110 | #if ENABLE_FEATURE_HTTPD_CGI |
2111 | if (strncmp(tptr, "cgi-bin/", 8) == 0) { |
2112 | if (tptr[8] == '\0') { |
2113 | /* protect listing "cgi-bin/" */ |
2114 | send_headers_and_exit(HTTP_FORBIDDEN); |
2115 | } |
2116 | send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type); |
2117 | } |
2118 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
2119 | { |
2120 | char *suffix = strrchr(tptr, '.'); |
2121 | if (suffix) { |
2122 | Htaccess *cur; |
2123 | for (cur = script_i; cur; cur = cur->next) { |
2124 | if (strcmp(cur->before_colon + 1, suffix) == 0) { |
2125 | send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type); |
2126 | } |
2127 | } |
2128 | } |
2129 | } |
2130 | #endif |
2131 | if (prequest != request_GET && prequest != request_HEAD) { |
2132 | send_headers_and_exit(HTTP_NOT_IMPLEMENTED); |
2133 | } |
2134 | #endif /* FEATURE_HTTPD_CGI */ |
2135 | |
2136 | if (urlp[-1] == '/') |
2137 | strcpy(urlp, index_page); |
2138 | if (stat(tptr, &sb) == 0) { |
2139 | file_size = sb.st_size; |
2140 | last_mod = sb.st_mtime; |
2141 | } |
2142 | #if ENABLE_FEATURE_HTTPD_CGI |
2143 | else if (urlp[-1] == '/') { |
2144 | /* It's a dir URL and there is no index.html |
2145 | * Try cgi-bin/index.cgi */ |
2146 | if (access("/cgi-bin/index.cgi"+1, X_OK) == 0) { |
2147 | urlp[0] = '\0'; |
2148 | g_query = urlcopy; |
2149 | send_cgi_and_exit("/cgi-bin/index.cgi", prequest, length, cookie, content_type); |
2150 | } |
2151 | } |
2152 | #endif |
2153 | /* else { |
2154 | * fall through to send_file, it errors out if open fails |
2155 | * } |
2156 | */ |
2157 | |
2158 | send_file_and_exit(tptr, |
2159 | #if ENABLE_FEATURE_HTTPD_CGI |
2160 | (prequest != request_HEAD ? SEND_HEADERS_AND_BODY : SEND_HEADERS) |
2161 | #else |
2162 | SEND_HEADERS_AND_BODY |
2163 | #endif |
2164 | ); |
2165 | } |
2166 | |
2167 | /* |
2168 | * The main http server function. |
2169 | * Given a socket, listen for new connections and farm out |
2170 | * the processing as a [v]forked process. |
2171 | * Never returns. |
2172 | */ |
2173 | #if BB_MMU |
2174 | static void mini_httpd(int server_socket) NORETURN; |
2175 | static void mini_httpd(int server_socket) |
2176 | { |
2177 | /* NB: it's best to not use xfuncs in this loop before fork(). |
2178 | * Otherwise server may die on transient errors (temporary |
2179 | * out-of-memory condition, etc), which is Bad(tm). |
2180 | * Try to do any dangerous calls after fork. |
2181 | */ |
2182 | while (1) { |
2183 | int n; |
2184 | len_and_sockaddr fromAddr; |
2185 | |
2186 | /* Wait for connections... */ |
2187 | fromAddr.len = LSA_SIZEOF_SA; |
2188 | n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len); |
2189 | |
2190 | if (n < 0) |
2191 | continue; |
2192 | /* set the KEEPALIVE option to cull dead connections */ |
2193 | setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1)); |
2194 | |
2195 | if (fork() == 0) { |
2196 | /* child */ |
2197 | #if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP |
2198 | /* Do not reload config on HUP */ |
2199 | signal(SIGHUP, SIG_IGN); |
2200 | #endif |
2201 | close(server_socket); |
2202 | xmove_fd(n, 0); |
2203 | xdup2(0, 1); |
2204 | |
2205 | handle_incoming_and_exit(&fromAddr); |
2206 | } |
2207 | /* parent, or fork failed */ |
2208 | close(n); |
2209 | } /* while (1) */ |
2210 | /* never reached */ |
2211 | } |
2212 | #else |
2213 | static void mini_httpd_nommu(int server_socket, int argc, char **argv) NORETURN; |
2214 | static void mini_httpd_nommu(int server_socket, int argc, char **argv) |
2215 | { |
2216 | char *argv_copy[argc + 2]; |
2217 | |
2218 | argv_copy[0] = argv[0]; |
2219 | argv_copy[1] = (char*)"-i"; |
2220 | memcpy(&argv_copy[2], &argv[1], argc * sizeof(argv[0])); |
2221 | |
2222 | /* NB: it's best to not use xfuncs in this loop before vfork(). |
2223 | * Otherwise server may die on transient errors (temporary |
2224 | * out-of-memory condition, etc), which is Bad(tm). |
2225 | * Try to do any dangerous calls after fork. |
2226 | */ |
2227 | while (1) { |
2228 | int n; |
2229 | len_and_sockaddr fromAddr; |
2230 | |
2231 | /* Wait for connections... */ |
2232 | fromAddr.len = LSA_SIZEOF_SA; |
2233 | n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len); |
2234 | |
2235 | if (n < 0) |
2236 | continue; |
2237 | /* set the KEEPALIVE option to cull dead connections */ |
2238 | setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1)); |
2239 | |
2240 | if (vfork() == 0) { |
2241 | /* child */ |
2242 | #if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP |
2243 | /* Do not reload config on HUP */ |
2244 | signal(SIGHUP, SIG_IGN); |
2245 | #endif |
2246 | close(server_socket); |
2247 | xmove_fd(n, 0); |
2248 | xdup2(0, 1); |
2249 | |
2250 | /* Run a copy of ourself in inetd mode */ |
2251 | re_exec(argv_copy); |
2252 | } |
2253 | /* parent, or vfork failed */ |
2254 | close(n); |
2255 | } /* while (1) */ |
2256 | /* never reached */ |
2257 | } |
2258 | #endif |
2259 | |
2260 | /* |
2261 | * Process a HTTP connection on stdin/out. |
2262 | * Never returns. |
2263 | */ |
2264 | static void mini_httpd_inetd(void) NORETURN; |
2265 | static void mini_httpd_inetd(void) |
2266 | { |
2267 | len_and_sockaddr fromAddr; |
2268 | |
2269 | memset(&fromAddr, 0, sizeof(fromAddr)); |
2270 | fromAddr.len = LSA_SIZEOF_SA; |
2271 | /* NB: can fail if user runs it by hand and types in http cmds */ |
2272 | getpeername(0, &fromAddr.u.sa, &fromAddr.len); |
2273 | handle_incoming_and_exit(&fromAddr); |
2274 | } |
2275 | |
2276 | #if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP |
2277 | static void sighup_handler(int sig) |
2278 | { |
2279 | parse_conf(default_path_httpd_conf, sig ? SIGNALED_PARSE : FIRST_PARSE); |
2280 | signal_SA_RESTART_empty_mask(SIGHUP, sighup_handler); |
2281 | } |
2282 | #endif |
2283 | |
2284 | enum { |
2285 | c_opt_config_file = 0, |
2286 | d_opt_decode_url, |
2287 | h_opt_home_httpd, |
2288 | USE_FEATURE_HTTPD_ENCODE_URL_STR(e_opt_encode_url,) |
2289 | USE_FEATURE_HTTPD_BASIC_AUTH( r_opt_realm ,) |
2290 | USE_FEATURE_HTTPD_AUTH_MD5( m_opt_md5 ,) |
2291 | USE_FEATURE_HTTPD_SETUID( u_opt_setuid ,) |
2292 | p_opt_port , |
2293 | p_opt_inetd , |
2294 | p_opt_foreground, |
2295 | p_opt_verbose , |
2296 | OPT_CONFIG_FILE = 1 << c_opt_config_file, |
2297 | OPT_DECODE_URL = 1 << d_opt_decode_url, |
2298 | OPT_HOME_HTTPD = 1 << h_opt_home_httpd, |
2299 | OPT_ENCODE_URL = USE_FEATURE_HTTPD_ENCODE_URL_STR((1 << e_opt_encode_url)) + 0, |
2300 | OPT_REALM = USE_FEATURE_HTTPD_BASIC_AUTH( (1 << r_opt_realm )) + 0, |
2301 | OPT_MD5 = USE_FEATURE_HTTPD_AUTH_MD5( (1 << m_opt_md5 )) + 0, |
2302 | OPT_SETUID = USE_FEATURE_HTTPD_SETUID( (1 << u_opt_setuid )) + 0, |
2303 | OPT_PORT = 1 << p_opt_port, |
2304 | OPT_INETD = 1 << p_opt_inetd, |
2305 | OPT_FOREGROUND = 1 << p_opt_foreground, |
2306 | OPT_VERBOSE = 1 << p_opt_verbose, |
2307 | }; |
2308 | |
2309 | |
2310 | int httpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
2311 | int httpd_main(int argc UNUSED_PARAM, char **argv) |
2312 | { |
2313 | int server_socket = server_socket; /* for gcc */ |
2314 | unsigned opt; |
2315 | char *url_for_decode; |
2316 | USE_FEATURE_HTTPD_ENCODE_URL_STR(const char *url_for_encode;) |
2317 | USE_FEATURE_HTTPD_SETUID(const char *s_ugid = NULL;) |
2318 | USE_FEATURE_HTTPD_SETUID(struct bb_uidgid_t ugid;) |
2319 | USE_FEATURE_HTTPD_AUTH_MD5(const char *pass;) |
2320 | |
2321 | INIT_G(); |
2322 | |
2323 | #if ENABLE_LOCALE_SUPPORT |
2324 | /* Undo busybox.c: we want to speak English in http (dates etc) */ |
2325 | setlocale(LC_TIME, "C"); |
2326 | #endif |
2327 | |
2328 | home_httpd = xrealloc_getcwd_or_warn(NULL); |
2329 | /* -v counts, -i implies -f */ |
2330 | opt_complementary = "vv:if"; |
2331 | /* We do not "absolutize" path given by -h (home) opt. |
2332 | * If user gives relative path in -h, |
2333 | * $SCRIPT_FILENAME will not be set. */ |
2334 | opt = getopt32(argv, "c:d:h:" |
2335 | USE_FEATURE_HTTPD_ENCODE_URL_STR("e:") |
2336 | USE_FEATURE_HTTPD_BASIC_AUTH("r:") |
2337 | USE_FEATURE_HTTPD_AUTH_MD5("m:") |
2338 | USE_FEATURE_HTTPD_SETUID("u:") |
2339 | "p:ifv", |
2340 | &configFile, &url_for_decode, &home_httpd |
2341 | USE_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode) |
2342 | USE_FEATURE_HTTPD_BASIC_AUTH(, &g_realm) |
2343 | USE_FEATURE_HTTPD_AUTH_MD5(, &pass) |
2344 | USE_FEATURE_HTTPD_SETUID(, &s_ugid) |
2345 | , &bind_addr_or_port |
2346 | , &verbose |
2347 | ); |
2348 | if (opt & OPT_DECODE_URL) { |
2349 | fputs(decodeString(url_for_decode, 1), stdout); |
2350 | return 0; |
2351 | } |
2352 | #if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR |
2353 | if (opt & OPT_ENCODE_URL) { |
2354 | fputs(encodeString(url_for_encode), stdout); |
2355 | return 0; |
2356 | } |
2357 | #endif |
2358 | #if ENABLE_FEATURE_HTTPD_AUTH_MD5 |
2359 | if (opt & OPT_MD5) { |
2360 | puts(pw_encrypt(pass, "$1$", 1)); |
2361 | return 0; |
2362 | } |
2363 | #endif |
2364 | #if ENABLE_FEATURE_HTTPD_SETUID |
2365 | if (opt & OPT_SETUID) { |
2366 | xget_uidgid(&ugid, s_ugid); |
2367 | } |
2368 | #endif |
2369 | |
2370 | #if !BB_MMU |
2371 | if (!(opt & OPT_FOREGROUND)) { |
2372 | bb_daemonize_or_rexec(0, argv); /* don't change current directory */ |
2373 | } |
2374 | #endif |
2375 | |
2376 | xchdir(home_httpd); |
2377 | if (!(opt & OPT_INETD)) { |
2378 | signal(SIGCHLD, SIG_IGN); |
2379 | server_socket = openServer(); |
2380 | #if ENABLE_FEATURE_HTTPD_SETUID |
2381 | /* drop privileges */ |
2382 | if (opt & OPT_SETUID) { |
2383 | if (ugid.gid != (gid_t)-1) { |
2384 | if (setgroups(1, &ugid.gid) == -1) |
2385 | bb_perror_msg_and_die("setgroups"); |
2386 | xsetgid(ugid.gid); |
2387 | } |
2388 | xsetuid(ugid.uid); |
2389 | } |
2390 | #endif |
2391 | } |
2392 | |
2393 | #if 0 /*was #if ENABLE_FEATURE_HTTPD_CGI*/ |
2394 | /* User can do it himself: 'env - PATH="$PATH" httpd' |
2395 | * We don't do it because we don't want to screw users |
2396 | * which want to do |
2397 | * 'env - VAR1=val1 VAR2=val2 httpd' |
2398 | * and have VAR1 and VAR2 values visible in their CGIs. |
2399 | * Besides, it is also smaller. */ |
2400 | { |
2401 | char *p = getenv("PATH"); |
2402 | /* env strings themself are not freed, no need to xstrdup(p): */ |
2403 | clearenv(); |
2404 | if (p) |
2405 | putenv(p - 5); |
2406 | // if (!(opt & OPT_INETD)) |
2407 | // setenv_long("SERVER_PORT", ???); |
2408 | } |
2409 | #endif |
2410 | |
2411 | #if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP |
2412 | if (!(opt & OPT_INETD)) { |
2413 | /* runs parse_conf() inside */ |
2414 | sighup_handler(0); |
2415 | } else |
2416 | #endif |
2417 | { |
2418 | parse_conf(default_path_httpd_conf, FIRST_PARSE); |
2419 | } |
2420 | |
2421 | xfunc_error_retval = 0; |
2422 | if (opt & OPT_INETD) |
2423 | mini_httpd_inetd(); |
2424 | #if BB_MMU |
2425 | if (!(opt & OPT_FOREGROUND)) |
2426 | bb_daemonize(0); /* don't change current directory */ |
2427 | mini_httpd(server_socket); /* never returns */ |
2428 | #else |
2429 | mini_httpd_nommu(server_socket, argc, argv); /* never returns */ |
2430 | #endif |
2431 | /* return 0; */ |
2432 | } |