Contents of /trunk/mkinitrd-magellan/busybox/networking/httpd.c
Parent Directory | Revision Log
Revision 532 -
(show annotations)
(download)
Sat Sep 1 22:45:15 2007 UTC (17 years ago) by niro
File MIME type: text/plain
File size: 54720 byte(s)
Sat Sep 1 22:45:15 2007 UTC (17 years ago) by niro
File MIME type: text/plain
File size: 54720 byte(s)
-import if magellan mkinitrd; it is a fork of redhats mkinitrd-5.0.8 with all magellan patches and features; deprecates magellan-src/mkinitrd
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 contains "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 server 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 | * A:172.20. # Allow address from 172.20.0.0/16 |
41 | * A:10.0.0.0/25 # Allow any address from 10.0.0.0-10.0.0.127 |
42 | * A:10.0.0.0/255.255.255.128 # Allow any address that previous set |
43 | * A:127.0.0.1 # Allow local loopback connections |
44 | * D:* # Deny from other IP connections |
45 | * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin/ |
46 | * /adm:admin:setup # Require user admin, pwd setup on urls starting with /adm/ |
47 | * /adm:toor:PaSsWd # or user toor, pwd PaSsWd on urls starting with /adm/ |
48 | * .au:audio/basic # additional mime type for audio.au files |
49 | * *.php:/path/php # running cgi.php scripts through an interpreter |
50 | * |
51 | * A/D may be as a/d or allow/deny - first char case insensitive |
52 | * Deny IP rules take precedence over allow rules. |
53 | * |
54 | * |
55 | * The Deny/Allow IP logic: |
56 | * |
57 | * - Default is to allow all. No addresses are denied unless |
58 | * denied with a D: rule. |
59 | * - Order of Deny/Allow rules is significant |
60 | * - Deny rules take precedence over allow rules. |
61 | * - If a deny all rule (D:*) is used it acts as a catch-all for unmatched |
62 | * addresses. |
63 | * - Specification of Allow all (A:*) is a no-op |
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 | * |
88 | * If -c is not set, an attempt will be made to open the default |
89 | * root configuration file. If -c is set and the file is not found, the |
90 | * server exits with an error. |
91 | * |
92 | */ |
93 | |
94 | #include "busybox.h" |
95 | |
96 | /* amount of buffering in a pipe */ |
97 | #ifndef PIPE_BUF |
98 | # define PIPE_BUF 4096 |
99 | #endif |
100 | |
101 | static const char httpdVersion[] = "busybox httpd/1.35 6-Oct-2004"; |
102 | static const char default_path_httpd_conf[] = "/etc"; |
103 | static const char httpd_conf[] = "httpd.conf"; |
104 | static const char home[] = "./"; |
105 | |
106 | #define TIMEOUT 60 |
107 | |
108 | // Note: busybox xfuncs are not used because we want the server to keep running |
109 | // if something bad happens due to a malformed user request. |
110 | // As a result, all memory allocation after daemonize |
111 | // is checked rigorously |
112 | |
113 | //#define DEBUG 1 |
114 | #define DEBUG 0 |
115 | |
116 | #define MAX_MEMORY_BUFF 8192 /* IO buffer */ |
117 | |
118 | typedef struct HT_ACCESS { |
119 | char *after_colon; |
120 | struct HT_ACCESS *next; |
121 | char before_colon[1]; /* really bigger, must last */ |
122 | } Htaccess; |
123 | |
124 | typedef struct HT_ACCESS_IP { |
125 | unsigned int ip; |
126 | unsigned int mask; |
127 | int allow_deny; |
128 | struct HT_ACCESS_IP *next; |
129 | } Htaccess_IP; |
130 | |
131 | typedef struct { |
132 | char buf[MAX_MEMORY_BUFF]; |
133 | |
134 | USE_FEATURE_HTTPD_BASIC_AUTH(const char *realm;) |
135 | USE_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;) |
136 | |
137 | const char *query; |
138 | |
139 | USE_FEATURE_HTTPD_CGI(char *referer;) |
140 | |
141 | const char *configFile; |
142 | |
143 | unsigned int rmt_ip; |
144 | #if ENABLE_FEATURE_HTTPD_CGI || DEBUG |
145 | char *rmt_ip_str; /* for set env REMOTE_ADDR */ |
146 | #endif |
147 | unsigned port; /* server initial port and for |
148 | set env REMOTE_PORT */ |
149 | const char *found_mime_type; |
150 | const char *found_moved_temporarily; |
151 | |
152 | off_t ContentLength; /* -1 - unknown */ |
153 | time_t last_mod; |
154 | |
155 | Htaccess_IP *ip_a_d; /* config allow/deny lines */ |
156 | int flg_deny_all; |
157 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
158 | Htaccess *auth; /* config user:password lines */ |
159 | #endif |
160 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES |
161 | Htaccess *mime_a; /* config mime types */ |
162 | #endif |
163 | |
164 | int server_socket; |
165 | int accepted_socket; |
166 | volatile int alarm_signaled; |
167 | |
168 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
169 | Htaccess *script_i; /* config script interpreters */ |
170 | #endif |
171 | } HttpdConfig; |
172 | |
173 | static HttpdConfig *config; |
174 | |
175 | static const char request_GET[] = "GET"; /* size algorithmic optimize */ |
176 | |
177 | static const char* const suffixTable [] = { |
178 | /* Warning: shorted equivalent suffix in one line must be first */ |
179 | ".htm.html", "text/html", |
180 | ".jpg.jpeg", "image/jpeg", |
181 | ".gif", "image/gif", |
182 | ".png", "image/png", |
183 | ".txt.h.c.cc.cpp", "text/plain", |
184 | ".css", "text/css", |
185 | ".wav", "audio/wav", |
186 | ".avi", "video/x-msvideo", |
187 | ".qt.mov", "video/quicktime", |
188 | ".mpe.mpeg", "video/mpeg", |
189 | ".mid.midi", "audio/midi", |
190 | ".mp3", "audio/mpeg", |
191 | #if 0 /* unpopular */ |
192 | ".au", "audio/basic", |
193 | ".pac", "application/x-ns-proxy-autoconfig", |
194 | ".vrml.wrl", "model/vrml", |
195 | #endif |
196 | 0, "application/octet-stream" /* default */ |
197 | }; |
198 | |
199 | typedef enum { |
200 | HTTP_OK = 200, |
201 | HTTP_MOVED_TEMPORARILY = 302, |
202 | HTTP_BAD_REQUEST = 400, /* malformed syntax */ |
203 | HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */ |
204 | HTTP_NOT_FOUND = 404, |
205 | HTTP_FORBIDDEN = 403, |
206 | HTTP_REQUEST_TIMEOUT = 408, |
207 | HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */ |
208 | HTTP_INTERNAL_SERVER_ERROR = 500, |
209 | #if 0 /* future use */ |
210 | HTTP_CONTINUE = 100, |
211 | HTTP_SWITCHING_PROTOCOLS = 101, |
212 | HTTP_CREATED = 201, |
213 | HTTP_ACCEPTED = 202, |
214 | HTTP_NON_AUTHORITATIVE_INFO = 203, |
215 | HTTP_NO_CONTENT = 204, |
216 | HTTP_MULTIPLE_CHOICES = 300, |
217 | HTTP_MOVED_PERMANENTLY = 301, |
218 | HTTP_NOT_MODIFIED = 304, |
219 | HTTP_PAYMENT_REQUIRED = 402, |
220 | HTTP_BAD_GATEWAY = 502, |
221 | HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */ |
222 | HTTP_RESPONSE_SETSIZE = 0xffffffff |
223 | #endif |
224 | } HttpResponseNum; |
225 | |
226 | typedef struct { |
227 | HttpResponseNum type; |
228 | const char *name; |
229 | const char *info; |
230 | } HttpEnumString; |
231 | |
232 | static const HttpEnumString httpResponseNames[] = { |
233 | { HTTP_OK, "OK", NULL }, |
234 | { HTTP_MOVED_TEMPORARILY, "Found", "Directories must end with a slash." }, |
235 | { HTTP_REQUEST_TIMEOUT, "Request Timeout", |
236 | "No request appeared within a reasonable time period." }, |
237 | { HTTP_NOT_IMPLEMENTED, "Not Implemented", |
238 | "The requested method is not recognized by this server." }, |
239 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
240 | { HTTP_UNAUTHORIZED, "Unauthorized", "" }, |
241 | #endif |
242 | { HTTP_NOT_FOUND, "Not Found", |
243 | "The requested URL was not found on this server." }, |
244 | { HTTP_BAD_REQUEST, "Bad Request", "Unsupported method." }, |
245 | { HTTP_FORBIDDEN, "Forbidden", "" }, |
246 | { HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error", |
247 | "Internal Server Error" }, |
248 | #if 0 /* not implemented */ |
249 | { HTTP_CREATED, "Created" }, |
250 | { HTTP_ACCEPTED, "Accepted" }, |
251 | { HTTP_NO_CONTENT, "No Content" }, |
252 | { HTTP_MULTIPLE_CHOICES, "Multiple Choices" }, |
253 | { HTTP_MOVED_PERMANENTLY, "Moved Permanently" }, |
254 | { HTTP_NOT_MODIFIED, "Not Modified" }, |
255 | { HTTP_BAD_GATEWAY, "Bad Gateway", "" }, |
256 | { HTTP_SERVICE_UNAVAILABLE, "Service Unavailable", "" }, |
257 | #endif |
258 | }; |
259 | |
260 | |
261 | static const char RFC1123FMT[] = "%a, %d %b %Y %H:%M:%S GMT"; |
262 | |
263 | |
264 | #define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1) |
265 | |
266 | |
267 | static int scan_ip(const char **ep, unsigned int *ip, unsigned char endc) |
268 | { |
269 | const char *p = *ep; |
270 | int auto_mask = 8; |
271 | int j; |
272 | |
273 | *ip = 0; |
274 | for (j = 0; j < 4; j++) { |
275 | unsigned int octet; |
276 | |
277 | if ((*p < '0' || *p > '9') && (*p != '/' || j == 0) && *p != 0) |
278 | return -auto_mask; |
279 | octet = 0; |
280 | while (*p >= '0' && *p <= '9') { |
281 | octet *= 10; |
282 | octet += *p - '0'; |
283 | if (octet > 255) |
284 | return -auto_mask; |
285 | p++; |
286 | } |
287 | if (*p == '.') |
288 | p++; |
289 | if (*p != '/' && *p != 0) |
290 | auto_mask += 8; |
291 | *ip = ((*ip) << 8) | octet; |
292 | } |
293 | if (*p != 0) { |
294 | if (*p != endc) |
295 | return -auto_mask; |
296 | p++; |
297 | if (*p == 0) |
298 | return -auto_mask; |
299 | } |
300 | *ep = p; |
301 | return auto_mask; |
302 | } |
303 | |
304 | static int scan_ip_mask(const char *ipm, unsigned int *ip, unsigned int *mask) |
305 | { |
306 | int i; |
307 | unsigned int msk; |
308 | |
309 | i = scan_ip(&ipm, ip, '/'); |
310 | if (i < 0) |
311 | return i; |
312 | if (*ipm) { |
313 | const char *p = ipm; |
314 | |
315 | i = 0; |
316 | while (*p) { |
317 | if (*p < '0' || *p > '9') { |
318 | if (*p == '.') { |
319 | i = scan_ip(&ipm, mask, 0); |
320 | return i != 32; |
321 | } |
322 | return -1; |
323 | } |
324 | i *= 10; |
325 | i += *p - '0'; |
326 | p++; |
327 | } |
328 | } |
329 | if (i > 32 || i < 0) |
330 | return -1; |
331 | msk = 0x80000000; |
332 | *mask = 0; |
333 | while (i > 0) { |
334 | *mask |= msk; |
335 | msk >>= 1; |
336 | i--; |
337 | } |
338 | return 0; |
339 | } |
340 | |
341 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH \ |
342 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \ |
343 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
344 | static void free_config_lines(Htaccess **pprev) |
345 | { |
346 | Htaccess *prev = *pprev; |
347 | |
348 | while (prev) { |
349 | Htaccess *cur = prev; |
350 | |
351 | prev = cur->next; |
352 | free(cur); |
353 | } |
354 | *pprev = NULL; |
355 | } |
356 | #endif |
357 | |
358 | /* flag */ |
359 | #define FIRST_PARSE 0 |
360 | #define SUBDIR_PARSE 1 |
361 | #define SIGNALED_PARSE 2 |
362 | #define FIND_FROM_HTTPD_ROOT 3 |
363 | /**************************************************************************** |
364 | * |
365 | > $Function: parse_conf() |
366 | * |
367 | * $Description: parse configuration file into in-memory linked list. |
368 | * |
369 | * The first non-white character is examined to determine if the config line |
370 | * is one of the following: |
371 | * .ext:mime/type # new mime type not compiled into httpd |
372 | * [adAD]:from # ip address allow/deny, * for wildcard |
373 | * /path:user:pass # username/password |
374 | * |
375 | * Any previous IP rules are discarded. |
376 | * If the flag argument is not SUBDIR_PARSE then all /path and mime rules |
377 | * are also discarded. That is, previous settings are retained if flag is |
378 | * SUBDIR_PARSE. |
379 | * |
380 | * $Parameters: |
381 | * (const char *) path . . null for ip address checks, path for password |
382 | * checks. |
383 | * (int) flag . . . . . . the source of the parse request. |
384 | * |
385 | * $Return: (None) |
386 | * |
387 | ****************************************************************************/ |
388 | static void parse_conf(const char *path, int flag) |
389 | { |
390 | FILE *f; |
391 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
392 | Htaccess *prev; |
393 | #endif |
394 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH \ |
395 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \ |
396 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
397 | Htaccess *cur; |
398 | #endif |
399 | |
400 | const char *cf = config->configFile; |
401 | char buf[160]; |
402 | char *p0 = NULL; |
403 | char *c, *p; |
404 | |
405 | /* free previous ip setup if present */ |
406 | Htaccess_IP *pip = config->ip_a_d; |
407 | |
408 | while (pip) { |
409 | Htaccess_IP *cur_ipl = pip; |
410 | |
411 | pip = cur_ipl->next; |
412 | free(cur_ipl); |
413 | } |
414 | config->ip_a_d = NULL; |
415 | |
416 | config->flg_deny_all = 0; |
417 | |
418 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH \ |
419 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \ |
420 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
421 | /* retain previous auth and mime config only for subdir parse */ |
422 | if (flag != SUBDIR_PARSE) { |
423 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
424 | free_config_lines(&config->auth); |
425 | #endif |
426 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES |
427 | free_config_lines(&config->mime_a); |
428 | #endif |
429 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
430 | free_config_lines(&config->script_i); |
431 | #endif |
432 | } |
433 | #endif |
434 | |
435 | if (flag == SUBDIR_PARSE || cf == NULL) { |
436 | cf = alloca(strlen(path) + sizeof(httpd_conf) + 2); |
437 | if (cf == NULL) { |
438 | if (flag == FIRST_PARSE) |
439 | bb_error_msg_and_die(bb_msg_memory_exhausted); |
440 | return; |
441 | } |
442 | sprintf((char *)cf, "%s/%s", path, httpd_conf); |
443 | } |
444 | |
445 | while ((f = fopen(cf, "r")) == NULL) { |
446 | if (flag == SUBDIR_PARSE || flag == FIND_FROM_HTTPD_ROOT) { |
447 | /* config file not found, no changes to config */ |
448 | return; |
449 | } |
450 | if (config->configFile && flag == FIRST_PARSE) /* if -c option given */ |
451 | bb_perror_msg_and_die("%s", cf); |
452 | flag = FIND_FROM_HTTPD_ROOT; |
453 | cf = httpd_conf; |
454 | } |
455 | |
456 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
457 | prev = config->auth; |
458 | #endif |
459 | /* This could stand some work */ |
460 | while ((p0 = fgets(buf, sizeof(buf), f)) != NULL) { |
461 | c = NULL; |
462 | for (p = p0; *p0 != 0 && *p0 != '#'; p0++) { |
463 | if (!isspace(*p0)) { |
464 | *p++ = *p0; |
465 | if (*p0 == ':' && c == NULL) |
466 | c = p; |
467 | } |
468 | } |
469 | *p = 0; |
470 | |
471 | /* test for empty or strange line */ |
472 | if (c == NULL || *c == 0) |
473 | continue; |
474 | p0 = buf; |
475 | if (*p0 == 'd') |
476 | *p0 = 'D'; |
477 | if (*c == '*') { |
478 | if (*p0 == 'D') { |
479 | /* memorize deny all */ |
480 | config->flg_deny_all++; |
481 | } |
482 | /* skip default other "word:*" config lines */ |
483 | continue; |
484 | } |
485 | |
486 | if (*p0 == 'a') |
487 | *p0 = 'A'; |
488 | else if (*p0 != 'D' && *p0 != 'A' |
489 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
490 | && *p0 != '/' |
491 | #endif |
492 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES |
493 | && *p0 != '.' |
494 | #endif |
495 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
496 | && *p0 != '*' |
497 | #endif |
498 | ) |
499 | continue; |
500 | if (*p0 == 'A' || *p0 == 'D') { |
501 | /* storing current config IP line */ |
502 | pip = xzalloc(sizeof(Htaccess_IP)); |
503 | if (pip) { |
504 | if (scan_ip_mask(c, &(pip->ip), &(pip->mask))) { |
505 | /* syntax IP{/mask} error detected, protect all */ |
506 | *p0 = 'D'; |
507 | pip->mask = 0; |
508 | } |
509 | pip->allow_deny = *p0; |
510 | if (*p0 == 'D') { |
511 | /* Deny:form_IP move top */ |
512 | pip->next = config->ip_a_d; |
513 | config->ip_a_d = pip; |
514 | } else { |
515 | /* add to bottom A:form_IP config line */ |
516 | Htaccess_IP *prev_IP = config->ip_a_d; |
517 | |
518 | if (prev_IP == NULL) { |
519 | config->ip_a_d = pip; |
520 | } else { |
521 | while (prev_IP->next) |
522 | prev_IP = prev_IP->next; |
523 | prev_IP->next = pip; |
524 | } |
525 | } |
526 | } |
527 | continue; |
528 | } |
529 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
530 | if (*p0 == '/') { |
531 | /* make full path from httpd root / curent_path / config_line_path */ |
532 | cf = flag == SUBDIR_PARSE ? path : ""; |
533 | p0 = malloc(strlen(cf) + (c - buf) + 2 + strlen(c)); |
534 | if (p0 == NULL) |
535 | continue; |
536 | c[-1] = 0; |
537 | sprintf(p0, "/%s%s", cf, buf); |
538 | |
539 | /* another call bb_simplify_path */ |
540 | cf = p = p0; |
541 | |
542 | do { |
543 | if (*p == '/') { |
544 | if (*cf == '/') { /* skip duplicate (or initial) slash */ |
545 | continue; |
546 | } else if (*cf == '.') { |
547 | if (cf[1] == '/' || cf[1] == 0) { /* remove extra '.' */ |
548 | continue; |
549 | } else if ((cf[1] == '.') && (cf[2] == '/' || cf[2] == 0)) { |
550 | ++cf; |
551 | if (p > p0) { |
552 | while (*--p != '/') /* omit previous dir */; |
553 | } |
554 | continue; |
555 | } |
556 | } |
557 | } |
558 | *++p = *cf; |
559 | } while (*++cf); |
560 | |
561 | if ((p == p0) || (*p != '/')) { /* not a trailing slash */ |
562 | ++p; /* so keep last character */ |
563 | } |
564 | *p = 0; |
565 | sprintf(p0, "%s:%s", p0, c); |
566 | } |
567 | #endif |
568 | |
569 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH \ |
570 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \ |
571 | || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
572 | /* storing current config line */ |
573 | cur = xzalloc(sizeof(Htaccess) + strlen(p0)); |
574 | if (cur) { |
575 | cf = strcpy(cur->before_colon, p0); |
576 | c = strchr(cf, ':'); |
577 | *c++ = 0; |
578 | cur->after_colon = c; |
579 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES |
580 | if (*cf == '.') { |
581 | /* config .mime line move top for overwrite previous */ |
582 | cur->next = config->mime_a; |
583 | config->mime_a = cur; |
584 | continue; |
585 | } |
586 | #endif |
587 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
588 | if (*cf == '*' && cf[1] == '.') { |
589 | /* config script interpreter line move top for overwrite previous */ |
590 | cur->next = config->script_i; |
591 | config->script_i = cur; |
592 | continue; |
593 | } |
594 | #endif |
595 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
596 | free(p0); |
597 | if (prev == NULL) { |
598 | /* first line */ |
599 | config->auth = prev = cur; |
600 | } else { |
601 | /* sort path, if current lenght eq or bigger then move up */ |
602 | Htaccess *prev_hti = config->auth; |
603 | size_t l = strlen(cf); |
604 | Htaccess *hti; |
605 | |
606 | for (hti = prev_hti; hti; hti = hti->next) { |
607 | if (l >= strlen(hti->before_colon)) { |
608 | /* insert before hti */ |
609 | cur->next = hti; |
610 | if (prev_hti != hti) { |
611 | prev_hti->next = cur; |
612 | } else { |
613 | /* insert as top */ |
614 | config->auth = cur; |
615 | } |
616 | break; |
617 | } |
618 | if (prev_hti != hti) |
619 | prev_hti = prev_hti->next; |
620 | } |
621 | if (!hti) { /* not inserted, add to bottom */ |
622 | prev->next = cur; |
623 | prev = cur; |
624 | } |
625 | } |
626 | #endif |
627 | } |
628 | #endif |
629 | } |
630 | fclose(f); |
631 | } |
632 | |
633 | #if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR |
634 | /**************************************************************************** |
635 | * |
636 | > $Function: encodeString() |
637 | * |
638 | * $Description: Given a string, html encode special characters. |
639 | * This is used for the -e command line option to provide an easy way |
640 | * for scripts to encode result data without confusing browsers. The |
641 | * returned string pointer is memory allocated by malloc(). |
642 | * |
643 | * $Parameters: |
644 | * (const char *) string . . The first string to encode. |
645 | * |
646 | * $Return: (char *) . . . .. . . A pointer to the encoded string. |
647 | * |
648 | * $Errors: Returns a null string ("") if memory is not available. |
649 | * |
650 | ****************************************************************************/ |
651 | static char *encodeString(const char *string) |
652 | { |
653 | /* take the simple route and encode everything */ |
654 | /* could possibly scan once to get length. */ |
655 | int len = strlen(string); |
656 | char *out = malloc(len * 6 + 1); |
657 | char *p = out; |
658 | char ch; |
659 | |
660 | if (!out) return ""; |
661 | while ((ch = *string++)) { |
662 | // very simple check for what to encode |
663 | if (isalnum(ch)) *p++ = ch; |
664 | else p += sprintf(p, "&#%d;", (unsigned char) ch); |
665 | } |
666 | *p = 0; |
667 | return out; |
668 | } |
669 | #endif /* FEATURE_HTTPD_ENCODE_URL_STR */ |
670 | |
671 | /**************************************************************************** |
672 | * |
673 | > $Function: decodeString() |
674 | * |
675 | * $Description: Given a URL encoded string, convert it to plain ascii. |
676 | * Since decoding always makes strings smaller, the decode is done in-place. |
677 | * Thus, callers should strdup() the argument if they do not want the |
678 | * argument modified. The return is the original pointer, allowing this |
679 | * function to be easily used as arguments to other functions. |
680 | * |
681 | * $Parameters: |
682 | * (char *) string . . . The first string to decode. |
683 | * (int) option_d . . 1 if called for httpd -d |
684 | * |
685 | * $Return: (char *) . . . . A pointer to the decoded string (same as input). |
686 | * |
687 | * $Errors: None |
688 | * |
689 | ****************************************************************************/ |
690 | static char *decodeString(char *orig, int option_d) |
691 | { |
692 | /* note that decoded string is always shorter than original */ |
693 | char *string = orig; |
694 | char *ptr = string; |
695 | char c; |
696 | |
697 | while ((c = *ptr++) != '\0') { |
698 | unsigned value1, value2; |
699 | |
700 | if (option_d && c == '+') { |
701 | *string++ = ' '; |
702 | continue; |
703 | } |
704 | if (c != '%') { |
705 | *string++ = c; |
706 | continue; |
707 | } |
708 | if (sscanf(ptr, "%1X", &value1) != 1 |
709 | || sscanf(ptr+1, "%1X", &value2) != 1 |
710 | ) { |
711 | if (!option_d) |
712 | return NULL; |
713 | *string++ = '%'; |
714 | continue; |
715 | } |
716 | value1 = value1 * 16 + value2; |
717 | if (!option_d && (value1 == '/' || value1 == '\0')) { |
718 | /* caller takes it as indication of invalid |
719 | * (dangerous wrt exploits) chars */ |
720 | return orig + 1; |
721 | } |
722 | *string++ = value1; |
723 | ptr += 2; |
724 | } |
725 | *string = '\0'; |
726 | return orig; |
727 | } |
728 | |
729 | |
730 | #if ENABLE_FEATURE_HTTPD_CGI |
731 | /**************************************************************************** |
732 | * setenv helpers |
733 | ****************************************************************************/ |
734 | static void setenv1(const char *name, const char *value) |
735 | { |
736 | if (!value) |
737 | value = ""; |
738 | setenv(name, value, 1); |
739 | } |
740 | static void setenv_long(const char *name, long value) |
741 | { |
742 | char buf[sizeof(value)*3 + 1]; |
743 | sprintf(buf, "%ld", value); |
744 | setenv(name, buf, 1); |
745 | } |
746 | #endif |
747 | |
748 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
749 | /**************************************************************************** |
750 | * |
751 | > $Function: decodeBase64() |
752 | * |
753 | > $Description: Decode a base 64 data stream as per rfc1521. |
754 | * Note that the rfc states that none base64 chars are to be ignored. |
755 | * Since the decode always results in a shorter size than the input, it is |
756 | * OK to pass the input arg as an output arg. |
757 | * |
758 | * $Parameter: |
759 | * (char *) Data . . . . A pointer to a base64 encoded string. |
760 | * Where to place the decoded data. |
761 | * |
762 | * $Return: void |
763 | * |
764 | * $Errors: None |
765 | * |
766 | ****************************************************************************/ |
767 | static void decodeBase64(char *Data) |
768 | { |
769 | |
770 | const unsigned char *in = (const unsigned char *)Data; |
771 | // The decoded size will be at most 3/4 the size of the encoded |
772 | unsigned long ch = 0; |
773 | int i = 0; |
774 | |
775 | while (*in) { |
776 | int t = *in++; |
777 | |
778 | if (t >= '0' && t <= '9') |
779 | t = t - '0' + 52; |
780 | else if (t >= 'A' && t <= 'Z') |
781 | t = t - 'A'; |
782 | else if (t >= 'a' && t <= 'z') |
783 | t = t - 'a' + 26; |
784 | else if (t == '+') |
785 | t = 62; |
786 | else if (t == '/') |
787 | t = 63; |
788 | else if (t == '=') |
789 | t = 0; |
790 | else |
791 | continue; |
792 | |
793 | ch = (ch << 6) | t; |
794 | i++; |
795 | if (i == 4) { |
796 | *Data++ = (char) (ch >> 16); |
797 | *Data++ = (char) (ch >> 8); |
798 | *Data++ = (char) ch; |
799 | i = 0; |
800 | } |
801 | } |
802 | *Data = 0; |
803 | } |
804 | #endif |
805 | |
806 | |
807 | /**************************************************************************** |
808 | * |
809 | > $Function: openServer() |
810 | * |
811 | * $Description: create a listen server socket on the designated port. |
812 | * |
813 | * $Return: (int) . . . A connection socket. -1 for errors. |
814 | * |
815 | * $Errors: None |
816 | * |
817 | ****************************************************************************/ |
818 | static int openServer(void) |
819 | { |
820 | int fd; |
821 | |
822 | /* create the socket right now */ |
823 | fd = create_and_bind_stream_or_die(NULL, config->port); |
824 | xlisten(fd, 9); |
825 | return fd; |
826 | } |
827 | |
828 | /**************************************************************************** |
829 | * |
830 | > $Function: sendHeaders() |
831 | * |
832 | * $Description: Create and send HTTP response headers. |
833 | * The arguments are combined and sent as one write operation. Note that |
834 | * IE will puke big-time if the headers are not sent in one packet and the |
835 | * second packet is delayed for any reason. |
836 | * |
837 | * $Parameter: |
838 | * (HttpResponseNum) responseNum . . . The result code to send. |
839 | * |
840 | * $Return: (int) . . . . writing errors |
841 | * |
842 | ****************************************************************************/ |
843 | static int sendHeaders(HttpResponseNum responseNum) |
844 | { |
845 | char *buf = config->buf; |
846 | const char *responseString = ""; |
847 | const char *infoString = 0; |
848 | const char *mime_type; |
849 | unsigned i; |
850 | time_t timer = time(0); |
851 | char timeStr[80]; |
852 | int len; |
853 | enum { |
854 | numNames = sizeof(httpResponseNames) / sizeof(httpResponseNames[0]) |
855 | }; |
856 | |
857 | for (i = 0; i < numNames; i++) { |
858 | if (httpResponseNames[i].type == responseNum) { |
859 | responseString = httpResponseNames[i].name; |
860 | infoString = httpResponseNames[i].info; |
861 | break; |
862 | } |
863 | } |
864 | /* error message is HTML */ |
865 | mime_type = responseNum == HTTP_OK ? |
866 | config->found_mime_type : "text/html"; |
867 | |
868 | /* emit the current date */ |
869 | strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&timer)); |
870 | len = sprintf(buf, |
871 | "HTTP/1.0 %d %s\r\nContent-type: %s\r\n" |
872 | "Date: %s\r\nConnection: close\r\n", |
873 | responseNum, responseString, mime_type, timeStr); |
874 | |
875 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
876 | if (responseNum == HTTP_UNAUTHORIZED) { |
877 | len += sprintf(buf+len, "WWW-Authenticate: Basic realm=\"%s\"\r\n", |
878 | config->realm); |
879 | } |
880 | #endif |
881 | if (responseNum == HTTP_MOVED_TEMPORARILY) { |
882 | len += sprintf(buf+len, "Location: %s/%s%s\r\n", |
883 | config->found_moved_temporarily, |
884 | (config->query ? "?" : ""), |
885 | (config->query ? config->query : "")); |
886 | } |
887 | |
888 | if (config->ContentLength != -1) { /* file */ |
889 | strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&config->last_mod)); |
890 | len += sprintf(buf+len, "Last-Modified: %s\r\n%s %"OFF_FMT"d\r\n", |
891 | timeStr, "Content-length:", config->ContentLength); |
892 | } |
893 | strcat(buf, "\r\n"); |
894 | len += 2; |
895 | if (infoString) { |
896 | len += sprintf(buf+len, |
897 | "<HEAD><TITLE>%d %s</TITLE></HEAD>\n" |
898 | "<BODY><H1>%d %s</H1>\n%s\n</BODY>\n", |
899 | responseNum, responseString, |
900 | responseNum, responseString, infoString); |
901 | } |
902 | if (DEBUG) |
903 | fprintf(stderr, "headers: '%s'\n", buf); |
904 | i = config->accepted_socket; |
905 | if (i == 0) i++; /* write to fd# 1 in inetd mode */ |
906 | return full_write(i, buf, len); |
907 | } |
908 | |
909 | /**************************************************************************** |
910 | * |
911 | > $Function: getLine() |
912 | * |
913 | * $Description: Read from the socket until an end of line char found. |
914 | * |
915 | * Characters are read one at a time until an eol sequence is found. |
916 | * |
917 | * $Return: (int) . . . . number of characters read. -1 if error. |
918 | * |
919 | ****************************************************************************/ |
920 | static int getLine(void) |
921 | { |
922 | int count = 0; |
923 | char *buf = config->buf; |
924 | |
925 | while (read(config->accepted_socket, buf + count, 1) == 1) { |
926 | if (buf[count] == '\r') continue; |
927 | if (buf[count] == '\n') { |
928 | buf[count] = 0; |
929 | return count; |
930 | } |
931 | if (count < (MAX_MEMORY_BUFF-1)) /* check overflow */ |
932 | count++; |
933 | } |
934 | if (count) return count; |
935 | else return -1; |
936 | } |
937 | |
938 | #if ENABLE_FEATURE_HTTPD_CGI |
939 | /**************************************************************************** |
940 | * |
941 | > $Function: sendCgi() |
942 | * |
943 | * $Description: Execute a CGI script and send it's stdout back |
944 | * |
945 | * Environment variables are set up and the script is invoked with pipes |
946 | * for stdin/stdout. If a post is being done the script is fed the POST |
947 | * data in addition to setting the QUERY_STRING variable (for GETs or POSTs). |
948 | * |
949 | * $Parameters: |
950 | * (const char *) url . . . . . . The requested URL (with leading /). |
951 | * (int bodyLen) . . . . . . . . Length of the post body. |
952 | * (const char *cookie) . . . . . For set HTTP_COOKIE. |
953 | * (const char *content_type) . . For set CONTENT_TYPE. |
954 | |
955 | * |
956 | * $Return: (char *) . . . . A pointer to the decoded string (same as input). |
957 | * |
958 | * $Errors: None |
959 | * |
960 | ****************************************************************************/ |
961 | static int sendCgi(const char *url, |
962 | const char *request, int bodyLen, const char *cookie, |
963 | const char *content_type) |
964 | { |
965 | int fromCgi[2]; /* pipe for reading data from CGI */ |
966 | int toCgi[2]; /* pipe for sending data to CGI */ |
967 | |
968 | static char * argp[] = { 0, 0 }; |
969 | int pid = 0; |
970 | int inFd; |
971 | int outFd; |
972 | int firstLine = 1; |
973 | int status; |
974 | size_t post_read_size, post_read_idx; |
975 | |
976 | if (pipe(fromCgi) != 0) |
977 | return 0; |
978 | if (pipe(toCgi) != 0) |
979 | return 0; |
980 | |
981 | pid = fork(); |
982 | if (pid < 0) |
983 | return 0; |
984 | |
985 | if (!pid) { |
986 | /* child process */ |
987 | char *script; |
988 | char *purl = xstrdup(url); |
989 | char realpath_buff[MAXPATHLEN]; |
990 | |
991 | if (purl == NULL) |
992 | _exit(242); |
993 | |
994 | inFd = toCgi[0]; |
995 | outFd = fromCgi[1]; |
996 | |
997 | dup2(inFd, 0); // replace stdin with the pipe |
998 | dup2(outFd, 1); // replace stdout with the pipe |
999 | if (!DEBUG) |
1000 | dup2(outFd, 2); // replace stderr with the pipe |
1001 | |
1002 | close(toCgi[0]); |
1003 | close(toCgi[1]); |
1004 | close(fromCgi[0]); |
1005 | close(fromCgi[1]); |
1006 | |
1007 | close(config->accepted_socket); |
1008 | close(config->server_socket); |
1009 | |
1010 | /* |
1011 | * Find PATH_INFO. |
1012 | */ |
1013 | script = purl; |
1014 | while ((script = strchr(script + 1, '/')) != NULL) { |
1015 | /* have script.cgi/PATH_INFO or dirs/script.cgi[/PATH_INFO] */ |
1016 | struct stat sb; |
1017 | |
1018 | *script = '\0'; |
1019 | if (is_directory(purl + 1, 1, &sb) == 0) { |
1020 | /* not directory, found script.cgi/PATH_INFO */ |
1021 | *script = '/'; |
1022 | break; |
1023 | } |
1024 | *script = '/'; /* is directory, find next '/' */ |
1025 | } |
1026 | setenv1("PATH_INFO", script); /* set /PATH_INFO or "" */ |
1027 | /* setenv1("PATH", getenv("PATH")); redundant */ |
1028 | setenv1("REQUEST_METHOD", request); |
1029 | if (config->query) { |
1030 | char *uri = alloca(strlen(purl) + 2 + strlen(config->query)); |
1031 | if (uri) |
1032 | sprintf(uri, "%s?%s", purl, config->query); |
1033 | setenv1("REQUEST_URI", uri); |
1034 | } else { |
1035 | setenv1("REQUEST_URI", purl); |
1036 | } |
1037 | if (script != NULL) |
1038 | *script = '\0'; /* cut off /PATH_INFO */ |
1039 | /* SCRIPT_FILENAME required by PHP in CGI mode */ |
1040 | if (!realpath(purl + 1, realpath_buff)) |
1041 | goto error_execing_cgi; |
1042 | setenv1("SCRIPT_FILENAME", realpath_buff); |
1043 | /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */ |
1044 | setenv1("SCRIPT_NAME", purl); |
1045 | /* http://hoohoo.ncsa.uiuc.edu/cgi/env.html: |
1046 | * QUERY_STRING: The information which follows the ? in the URL |
1047 | * which referenced this script. This is the query information. |
1048 | * It should not be decoded in any fashion. This variable |
1049 | * should always be set when there is query information, |
1050 | * regardless of command line decoding. */ |
1051 | /* (Older versions of bbox seem to do some decoding) */ |
1052 | setenv1("QUERY_STRING", config->query); |
1053 | setenv1("SERVER_SOFTWARE", httpdVersion); |
1054 | putenv("SERVER_PROTOCOL=HTTP/1.0"); |
1055 | putenv("GATEWAY_INTERFACE=CGI/1.1"); |
1056 | /* Having _separate_ variables for IP and port defeats |
1057 | * the purpose of having socket abstraction. Which "port" |
1058 | * are you using on Unix domain socket? |
1059 | * IOW - REMOTE_PEER="1.2.3.4:56" makes much more sense. |
1060 | * Oh well... */ |
1061 | { |
1062 | char *p = config->rmt_ip_str ? : ""; |
1063 | char *cp = strrchr(p, ':'); |
1064 | if (ENABLE_FEATURE_IPV6 && cp && strchr(cp, ']')) |
1065 | cp = NULL; |
1066 | if (cp) *cp = '\0'; /* delete :PORT */ |
1067 | setenv1("REMOTE_ADDR", p); |
1068 | } |
1069 | #if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV |
1070 | setenv_long("REMOTE_PORT", config->port); |
1071 | #endif |
1072 | if (bodyLen) |
1073 | setenv_long("CONTENT_LENGTH", bodyLen); |
1074 | if (cookie) |
1075 | setenv1("HTTP_COOKIE", cookie); |
1076 | if (content_type) |
1077 | setenv1("CONTENT_TYPE", content_type); |
1078 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
1079 | if (config->remoteuser) { |
1080 | setenv1("REMOTE_USER", config->remoteuser); |
1081 | putenv("AUTH_TYPE=Basic"); |
1082 | } |
1083 | #endif |
1084 | if (config->referer) |
1085 | setenv1("HTTP_REFERER", config->referer); |
1086 | |
1087 | /* set execve argp[0] without path */ |
1088 | argp[0] = strrchr(purl, '/') + 1; |
1089 | /* but script argp[0] must have absolute path and chdiring to this */ |
1090 | script = strrchr(realpath_buff, '/'); |
1091 | if (!script) |
1092 | goto error_execing_cgi; |
1093 | *script = '\0'; |
1094 | if (chdir(realpath_buff) == 0) { |
1095 | // Now run the program. If it fails, |
1096 | // use _exit() so no destructors |
1097 | // get called and make a mess. |
1098 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
1099 | char *interpr = NULL; |
1100 | char *suffix = strrchr(purl, '.'); |
1101 | |
1102 | if (suffix) { |
1103 | Htaccess *cur; |
1104 | for (cur = config->script_i; cur; cur = cur->next) { |
1105 | if (strcmp(cur->before_colon + 1, suffix) == 0) { |
1106 | interpr = cur->after_colon; |
1107 | break; |
1108 | } |
1109 | } |
1110 | } |
1111 | #endif |
1112 | *script = '/'; |
1113 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR |
1114 | if (interpr) |
1115 | execv(interpr, argp); |
1116 | else |
1117 | #endif |
1118 | execv(realpath_buff, argp); |
1119 | } |
1120 | error_execing_cgi: |
1121 | /* send to stdout (even if we are not from inetd) */ |
1122 | config->accepted_socket = 1; |
1123 | sendHeaders(HTTP_NOT_FOUND); |
1124 | _exit(242); |
1125 | } /* end child */ |
1126 | |
1127 | /* parent process */ |
1128 | |
1129 | post_read_size = 0; |
1130 | post_read_idx = 0; /* for gcc */ |
1131 | inFd = fromCgi[0]; |
1132 | outFd = toCgi[1]; |
1133 | close(fromCgi[1]); |
1134 | close(toCgi[0]); |
1135 | signal(SIGPIPE, SIG_IGN); |
1136 | |
1137 | while (1) { |
1138 | fd_set readSet; |
1139 | fd_set writeSet; |
1140 | char wbuf[128]; |
1141 | int nfound; |
1142 | int count; |
1143 | |
1144 | FD_ZERO(&readSet); |
1145 | FD_ZERO(&writeSet); |
1146 | FD_SET(inFd, &readSet); |
1147 | if (bodyLen > 0 || post_read_size > 0) { |
1148 | FD_SET(outFd, &writeSet); |
1149 | nfound = outFd > inFd ? outFd : inFd; |
1150 | if (post_read_size == 0) { |
1151 | FD_SET(config->accepted_socket, &readSet); |
1152 | if (nfound < config->accepted_socket) |
1153 | nfound = config->accepted_socket; |
1154 | } |
1155 | /* Now wait on the set of sockets! */ |
1156 | nfound = select(nfound + 1, &readSet, &writeSet, NULL, NULL); |
1157 | } else { |
1158 | if (!bodyLen) { |
1159 | close(outFd); /* no more POST data to CGI */ |
1160 | bodyLen = -1; |
1161 | } |
1162 | nfound = select(inFd + 1, &readSet, NULL, NULL, NULL); |
1163 | } |
1164 | |
1165 | if (nfound <= 0) { |
1166 | if (waitpid(pid, &status, WNOHANG) <= 0) |
1167 | /* Weird. CGI didn't exit and no fd's |
1168 | * are ready, yet select returned?! */ |
1169 | continue; |
1170 | close(inFd); |
1171 | if (DEBUG && WIFEXITED(status)) |
1172 | bb_error_msg("piped has exited with status=%d", WEXITSTATUS(status)); |
1173 | if (DEBUG && WIFSIGNALED(status)) |
1174 | bb_error_msg("piped has exited with signal=%d", WTERMSIG(status)); |
1175 | break; |
1176 | } |
1177 | |
1178 | if (post_read_size > 0 && FD_ISSET(outFd, &writeSet)) { |
1179 | /* Have data from peer and can write to CGI */ |
1180 | // huh? why full_write? what if we will block? |
1181 | // (imagine that CGI does not read its stdin...) |
1182 | count = full_write(outFd, wbuf + post_read_idx, post_read_size); |
1183 | if (count > 0) { |
1184 | post_read_idx += count; |
1185 | post_read_size -= count; |
1186 | } else { |
1187 | post_read_size = bodyLen = 0; /* broken pipe to CGI */ |
1188 | } |
1189 | } else if (bodyLen > 0 && post_read_size == 0 |
1190 | && FD_ISSET(config->accepted_socket, &readSet) |
1191 | ) { |
1192 | /* We expect data, prev data portion is eaten by CGI |
1193 | * and there *is* data to read from the peer |
1194 | * (POST data?) */ |
1195 | count = bodyLen > (int)sizeof(wbuf) ? (int)sizeof(wbuf) : bodyLen; |
1196 | count = safe_read(config->accepted_socket, wbuf, count); |
1197 | if (count > 0) { |
1198 | post_read_size = count; |
1199 | post_read_idx = 0; |
1200 | bodyLen -= count; |
1201 | } else { |
1202 | bodyLen = 0; /* closed */ |
1203 | } |
1204 | } |
1205 | |
1206 | if (FD_ISSET(inFd, &readSet)) { |
1207 | /* There is something to read from CGI */ |
1208 | int s = config->accepted_socket; |
1209 | char *rbuf = config->buf; |
1210 | #define PIPESIZE PIPE_BUF |
1211 | #if PIPESIZE >= MAX_MEMORY_BUFF |
1212 | # error "PIPESIZE >= MAX_MEMORY_BUFF" |
1213 | #endif |
1214 | /* NB: was safe_read. If it *has to be* safe_read, */ |
1215 | /* please explain why in this comment... */ |
1216 | count = full_read(inFd, rbuf, PIPESIZE); |
1217 | if (count == 0) |
1218 | break; /* closed */ |
1219 | if (count < 0) |
1220 | continue; /* huh, error, why continue?? */ |
1221 | |
1222 | if (firstLine) { |
1223 | /* full_read (above) avoids |
1224 | * "chopped up into small chunks" syndrome here */ |
1225 | rbuf[count] = '\0'; |
1226 | /* check to see if the user script added headers */ |
1227 | #define HTTP_200 "HTTP/1.0 200 OK\r\n\r\n" |
1228 | if (memcmp(rbuf, HTTP_200, 4) != 0) { |
1229 | /* there is no "HTTP", do it ourself */ |
1230 | full_write(s, HTTP_200, sizeof(HTTP_200)-1); |
1231 | } |
1232 | #undef HTTP_200 |
1233 | /* Example of valid GCI without "Content-type:" |
1234 | * echo -en "HTTP/1.0 302 Found\r\n" |
1235 | * echo -en "Location: http://www.busybox.net\r\n" |
1236 | * echo -en "\r\n" |
1237 | */ |
1238 | //if (!strstr(rbuf, "ontent-")) { |
1239 | // full_write(s, "Content-type: text/plain\r\n\r\n", 28); |
1240 | //} |
1241 | firstLine = 0; |
1242 | } |
1243 | if (full_write(s, rbuf, count) != count) |
1244 | break; |
1245 | if (DEBUG) |
1246 | fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf); |
1247 | } /* if (FD_ISSET(inFd)) */ |
1248 | } /* while (1) */ |
1249 | return 0; |
1250 | } |
1251 | #endif /* FEATURE_HTTPD_CGI */ |
1252 | |
1253 | /**************************************************************************** |
1254 | * |
1255 | > $Function: sendFile() |
1256 | * |
1257 | * $Description: Send a file response to a HTTP request |
1258 | * |
1259 | * $Parameter: |
1260 | * (const char *) url . . The URL requested. |
1261 | * |
1262 | * $Return: (int) . . . . . . Always 0. |
1263 | * |
1264 | ****************************************************************************/ |
1265 | static int sendFile(const char *url) |
1266 | { |
1267 | char * suffix; |
1268 | int f; |
1269 | const char * const * table; |
1270 | const char * try_suffix; |
1271 | |
1272 | suffix = strrchr(url, '.'); |
1273 | |
1274 | for (table = suffixTable; *table; table += 2) |
1275 | if (suffix != NULL && (try_suffix = strstr(*table, suffix)) != 0) { |
1276 | try_suffix += strlen(suffix); |
1277 | if (*try_suffix == 0 || *try_suffix == '.') |
1278 | break; |
1279 | } |
1280 | /* also, if not found, set default as "application/octet-stream"; */ |
1281 | config->found_mime_type = table[1]; |
1282 | #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES |
1283 | if (suffix) { |
1284 | Htaccess * cur; |
1285 | |
1286 | for (cur = config->mime_a; cur; cur = cur->next) { |
1287 | if (strcmp(cur->before_colon, suffix) == 0) { |
1288 | config->found_mime_type = cur->after_colon; |
1289 | break; |
1290 | } |
1291 | } |
1292 | } |
1293 | #endif /* FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES */ |
1294 | |
1295 | if (DEBUG) |
1296 | fprintf(stderr, "sending file '%s' content-type: %s\n", |
1297 | url, config->found_mime_type); |
1298 | |
1299 | f = open(url, O_RDONLY); |
1300 | if (f >= 0) { |
1301 | int count; |
1302 | char *buf = config->buf; |
1303 | |
1304 | sendHeaders(HTTP_OK); |
1305 | /* TODO: sendfile() */ |
1306 | while ((count = full_read(f, buf, MAX_MEMORY_BUFF)) > 0) { |
1307 | int fd = config->accepted_socket; |
1308 | if (fd == 0) fd++; /* write to fd# 1 in inetd mode */ |
1309 | if (full_write(fd, buf, count) != count) |
1310 | break; |
1311 | } |
1312 | close(f); |
1313 | } else { |
1314 | if (DEBUG) |
1315 | bb_perror_msg("cannot open '%s'", url); |
1316 | sendHeaders(HTTP_NOT_FOUND); |
1317 | } |
1318 | |
1319 | return 0; |
1320 | } |
1321 | |
1322 | static int checkPermIP(void) |
1323 | { |
1324 | Htaccess_IP * cur; |
1325 | |
1326 | /* This could stand some work */ |
1327 | for (cur = config->ip_a_d; cur; cur = cur->next) { |
1328 | #if ENABLE_FEATURE_HTTPD_CGI && DEBUG |
1329 | fprintf(stderr, "checkPermIP: '%s' ? ", config->rmt_ip_str); |
1330 | #endif |
1331 | #if DEBUG |
1332 | fprintf(stderr, "'%u.%u.%u.%u/%u.%u.%u.%u'\n", |
1333 | (unsigned char)(cur->ip >> 24), |
1334 | (unsigned char)(cur->ip >> 16), |
1335 | (unsigned char)(cur->ip >> 8), |
1336 | (unsigned char)(cur->ip), |
1337 | (unsigned char)(cur->mask >> 24), |
1338 | (unsigned char)(cur->mask >> 16), |
1339 | (unsigned char)(cur->mask >> 8), |
1340 | (unsigned char)(cur->mask) |
1341 | ); |
1342 | #endif |
1343 | if ((config->rmt_ip & cur->mask) == cur->ip) |
1344 | return cur->allow_deny == 'A'; /* Allow/Deny */ |
1345 | } |
1346 | |
1347 | /* if unconfigured, return 1 - access from all */ |
1348 | return !config->flg_deny_all; |
1349 | } |
1350 | |
1351 | /**************************************************************************** |
1352 | * |
1353 | > $Function: checkPerm() |
1354 | * |
1355 | * $Description: Check the permission file for access password protected. |
1356 | * |
1357 | * If config file isn't present, everything is allowed. |
1358 | * Entries are of the form you can see example from header source |
1359 | * |
1360 | * $Parameters: |
1361 | * (const char *) path . . . . The file path. |
1362 | * (const char *) request . . . User information to validate. |
1363 | * |
1364 | * $Return: (int) . . . . . . . . . 1 if request OK, 0 otherwise. |
1365 | * |
1366 | ****************************************************************************/ |
1367 | |
1368 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
1369 | static int checkPerm(const char *path, const char *request) |
1370 | { |
1371 | Htaccess * cur; |
1372 | const char *p; |
1373 | const char *p0; |
1374 | |
1375 | const char *prev = NULL; |
1376 | |
1377 | /* This could stand some work */ |
1378 | for (cur = config->auth; cur; cur = cur->next) { |
1379 | size_t l; |
1380 | |
1381 | p0 = cur->before_colon; |
1382 | if (prev != NULL && strcmp(prev, p0) != 0) |
1383 | continue; /* find next identical */ |
1384 | p = cur->after_colon; |
1385 | if (DEBUG) |
1386 | fprintf(stderr, "checkPerm: '%s' ? '%s'\n", p0, request); |
1387 | |
1388 | l = strlen(p0); |
1389 | if (strncmp(p0, path, l) == 0 |
1390 | && (l == 1 || path[l] == '/' || path[l] == '\0') |
1391 | ) { |
1392 | char *u; |
1393 | /* path match found. Check request */ |
1394 | /* for check next /path:user:password */ |
1395 | prev = p0; |
1396 | u = strchr(request, ':'); |
1397 | if (u == NULL) { |
1398 | /* bad request, ':' required */ |
1399 | break; |
1400 | } |
1401 | |
1402 | if (ENABLE_FEATURE_HTTPD_AUTH_MD5) { |
1403 | char *cipher; |
1404 | char *pp; |
1405 | |
1406 | if (strncmp(p, request, u-request) != 0) { |
1407 | /* user uncompared */ |
1408 | continue; |
1409 | } |
1410 | pp = strchr(p, ':'); |
1411 | if (pp && pp[1] == '$' && pp[2] == '1' && |
1412 | pp[3] == '$' && pp[4]) { |
1413 | pp++; |
1414 | cipher = pw_encrypt(u+1, pp); |
1415 | if (strcmp(cipher, pp) == 0) |
1416 | goto set_remoteuser_var; /* Ok */ |
1417 | /* unauthorized */ |
1418 | continue; |
1419 | } |
1420 | } |
1421 | |
1422 | if (strcmp(p, request) == 0) { |
1423 | set_remoteuser_var: |
1424 | config->remoteuser = strdup(request); |
1425 | if (config->remoteuser) |
1426 | config->remoteuser[(u - request)] = 0; |
1427 | return 1; /* Ok */ |
1428 | } |
1429 | /* unauthorized */ |
1430 | } |
1431 | } /* for */ |
1432 | |
1433 | return prev == NULL; |
1434 | } |
1435 | |
1436 | #endif /* FEATURE_HTTPD_BASIC_AUTH */ |
1437 | |
1438 | /**************************************************************************** |
1439 | * |
1440 | > $Function: handle_sigalrm() |
1441 | * |
1442 | * $Description: Handle timeouts |
1443 | * |
1444 | ****************************************************************************/ |
1445 | |
1446 | static void handle_sigalrm(int sig) |
1447 | { |
1448 | sendHeaders(HTTP_REQUEST_TIMEOUT); |
1449 | config->alarm_signaled = sig; |
1450 | } |
1451 | |
1452 | /**************************************************************************** |
1453 | * |
1454 | > $Function: handleIncoming() |
1455 | * |
1456 | * $Description: Handle an incoming http request. |
1457 | * |
1458 | ****************************************************************************/ |
1459 | static void handleIncoming(void) |
1460 | { |
1461 | char *buf = config->buf; |
1462 | char *url; |
1463 | char *purl; |
1464 | int blank = -1; |
1465 | char *test; |
1466 | struct stat sb; |
1467 | int ip_allowed; |
1468 | #if ENABLE_FEATURE_HTTPD_CGI |
1469 | const char *prequest = request_GET; |
1470 | unsigned long length = 0; |
1471 | char *cookie = 0; |
1472 | char *content_type = 0; |
1473 | #endif |
1474 | fd_set s_fd; |
1475 | struct timeval tv; |
1476 | int retval; |
1477 | struct sigaction sa; |
1478 | |
1479 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
1480 | int credentials = -1; /* if not required this is Ok */ |
1481 | #endif |
1482 | |
1483 | sa.sa_handler = handle_sigalrm; |
1484 | sigemptyset(&sa.sa_mask); |
1485 | sa.sa_flags = 0; /* no SA_RESTART */ |
1486 | sigaction(SIGALRM, &sa, NULL); |
1487 | |
1488 | do { |
1489 | int count; |
1490 | |
1491 | (void) alarm(TIMEOUT); |
1492 | if (getLine() <= 0) |
1493 | break; /* closed */ |
1494 | |
1495 | purl = strpbrk(buf, " \t"); |
1496 | if (purl == NULL) { |
1497 | BAD_REQUEST: |
1498 | sendHeaders(HTTP_BAD_REQUEST); |
1499 | break; |
1500 | } |
1501 | *purl = '\0'; |
1502 | #if ENABLE_FEATURE_HTTPD_CGI |
1503 | if (strcasecmp(buf, prequest) != 0) { |
1504 | prequest = "POST"; |
1505 | if (strcasecmp(buf, prequest) != 0) { |
1506 | sendHeaders(HTTP_NOT_IMPLEMENTED); |
1507 | break; |
1508 | } |
1509 | } |
1510 | #else |
1511 | if (strcasecmp(buf, request_GET) != 0) { |
1512 | sendHeaders(HTTP_NOT_IMPLEMENTED); |
1513 | break; |
1514 | } |
1515 | #endif |
1516 | *purl = ' '; |
1517 | count = sscanf(purl, " %[^ ] HTTP/%d.%*d", buf, &blank); |
1518 | |
1519 | if (count < 1 || buf[0] != '/') { |
1520 | /* Garbled request/URL */ |
1521 | goto BAD_REQUEST; |
1522 | } |
1523 | url = alloca(strlen(buf) + sizeof("/index.html")); |
1524 | if (url == NULL) { |
1525 | sendHeaders(HTTP_INTERNAL_SERVER_ERROR); |
1526 | break; |
1527 | } |
1528 | strcpy(url, buf); |
1529 | /* extract url args if present */ |
1530 | test = strchr(url, '?'); |
1531 | config->query = NULL; |
1532 | if (test) { |
1533 | *test++ = '\0'; |
1534 | config->query = test; |
1535 | } |
1536 | |
1537 | test = decodeString(url, 0); |
1538 | if (test == NULL) |
1539 | goto BAD_REQUEST; |
1540 | if (test == url+1) { |
1541 | /* '/' or NUL is encoded */ |
1542 | sendHeaders(HTTP_NOT_FOUND); |
1543 | break; |
1544 | } |
1545 | |
1546 | /* algorithm stolen from libbb bb_simplify_path(), |
1547 | but don't strdup and reducing trailing slash and protect out root */ |
1548 | purl = test = url; |
1549 | do { |
1550 | if (*purl == '/') { |
1551 | /* skip duplicate (or initial) slash */ |
1552 | if (*test == '/') { |
1553 | continue; |
1554 | } |
1555 | if (*test == '.') { |
1556 | /* skip extra '.' */ |
1557 | if (test[1] == '/' || test[1] == 0) { |
1558 | continue; |
1559 | } else |
1560 | /* '..': be careful */ |
1561 | if (test[1] == '.' && (test[2] == '/' || test[2] == 0)) { |
1562 | ++test; |
1563 | if (purl == url) { |
1564 | /* protect out root */ |
1565 | goto BAD_REQUEST; |
1566 | } |
1567 | while (*--purl != '/') /* omit previous dir */; |
1568 | continue; |
1569 | } |
1570 | } |
1571 | } |
1572 | *++purl = *test; |
1573 | } while (*++test); |
1574 | *++purl = '\0'; /* so keep last character */ |
1575 | test = purl; /* end ptr */ |
1576 | |
1577 | /* If URL is directory, adding '/' */ |
1578 | if (test[-1] != '/') { |
1579 | if (is_directory(url + 1, 1, &sb)) { |
1580 | config->found_moved_temporarily = url; |
1581 | } |
1582 | } |
1583 | if (DEBUG) |
1584 | fprintf(stderr, "url='%s', args=%s\n", url, config->query); |
1585 | |
1586 | test = url; |
1587 | ip_allowed = checkPermIP(); |
1588 | while (ip_allowed && (test = strchr(test + 1, '/')) != NULL) { |
1589 | /* have path1/path2 */ |
1590 | *test = '\0'; |
1591 | if (is_directory(url + 1, 1, &sb)) { |
1592 | /* may be having subdir config */ |
1593 | parse_conf(url + 1, SUBDIR_PARSE); |
1594 | ip_allowed = checkPermIP(); |
1595 | } |
1596 | *test = '/'; |
1597 | } |
1598 | if (blank >= 0) { |
1599 | /* read until blank line for HTTP version specified, else parse immediate */ |
1600 | while (1) { |
1601 | alarm(TIMEOUT); |
1602 | count = getLine(); |
1603 | if (count <= 0) |
1604 | break; |
1605 | |
1606 | if (DEBUG) |
1607 | fprintf(stderr, "header: '%s'\n", buf); |
1608 | |
1609 | #if ENABLE_FEATURE_HTTPD_CGI |
1610 | /* try and do our best to parse more lines */ |
1611 | if ((STRNCASECMP(buf, "Content-length:") == 0)) { |
1612 | /* extra read only for POST */ |
1613 | if (prequest != request_GET) { |
1614 | test = buf + sizeof("Content-length:")-1; |
1615 | if (!test[0]) |
1616 | goto bail_out; |
1617 | errno = 0; |
1618 | /* not using strtoul: it ignores leading munis! */ |
1619 | length = strtol(test, &test, 10); |
1620 | /* length is "ulong", but we need to pass it to int later */ |
1621 | /* so we check for negative or too large values in one go: */ |
1622 | /* (long -> ulong conv caused negatives to be seen as > INT_MAX) */ |
1623 | if (test[0] || errno || length > INT_MAX) |
1624 | goto bail_out; |
1625 | } |
1626 | } else if ((STRNCASECMP(buf, "Cookie:") == 0)) { |
1627 | cookie = strdup(skip_whitespace(buf + sizeof("Cookie:")-1)); |
1628 | } else if ((STRNCASECMP(buf, "Content-Type:") == 0)) { |
1629 | content_type = strdup(skip_whitespace(buf + sizeof("Content-Type:")-1)); |
1630 | } else if ((STRNCASECMP(buf, "Referer:") == 0)) { |
1631 | config->referer = strdup(skip_whitespace(buf + sizeof("Referer:")-1)); |
1632 | } |
1633 | #endif |
1634 | |
1635 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
1636 | if (STRNCASECMP(buf, "Authorization:") == 0) { |
1637 | /* We only allow Basic credentials. |
1638 | * It shows up as "Authorization: Basic <userid:password>" where |
1639 | * the userid:password is base64 encoded. |
1640 | */ |
1641 | test = skip_whitespace(buf + sizeof("Authorization:")-1); |
1642 | if (STRNCASECMP(test, "Basic") != 0) |
1643 | continue; |
1644 | test += sizeof("Basic")-1; |
1645 | /* decodeBase64() skips whitespace itself */ |
1646 | decodeBase64(test); |
1647 | credentials = checkPerm(url, test); |
1648 | } |
1649 | #endif /* FEATURE_HTTPD_BASIC_AUTH */ |
1650 | |
1651 | } /* while extra header reading */ |
1652 | } |
1653 | alarm(0); |
1654 | if (config->alarm_signaled) |
1655 | break; |
1656 | |
1657 | if (strcmp(strrchr(url, '/') + 1, httpd_conf) == 0 || ip_allowed == 0) { |
1658 | /* protect listing [/path]/httpd_conf or IP deny */ |
1659 | #if ENABLE_FEATURE_HTTPD_CGI |
1660 | FORBIDDEN: /* protect listing /cgi-bin */ |
1661 | #endif |
1662 | sendHeaders(HTTP_FORBIDDEN); |
1663 | break; |
1664 | } |
1665 | |
1666 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
1667 | if (credentials <= 0 && checkPerm(url, ":") == 0) { |
1668 | sendHeaders(HTTP_UNAUTHORIZED); |
1669 | break; |
1670 | } |
1671 | #endif |
1672 | |
1673 | if (config->found_moved_temporarily) { |
1674 | sendHeaders(HTTP_MOVED_TEMPORARILY); |
1675 | /* clear unforked memory flag */ |
1676 | config->found_moved_temporarily = NULL; |
1677 | break; |
1678 | } |
1679 | |
1680 | test = url + 1; /* skip first '/' */ |
1681 | |
1682 | #if ENABLE_FEATURE_HTTPD_CGI |
1683 | if (strncmp(test, "cgi-bin", 7) == 0) { |
1684 | if (test[7] == '/' && test[8] == 0) |
1685 | goto FORBIDDEN; /* protect listing cgi-bin/ */ |
1686 | sendCgi(url, prequest, length, cookie, content_type); |
1687 | break; |
1688 | } |
1689 | if (prequest != request_GET) { |
1690 | sendHeaders(HTTP_NOT_IMPLEMENTED); |
1691 | break; |
1692 | } |
1693 | #endif /* FEATURE_HTTPD_CGI */ |
1694 | if (purl[-1] == '/') |
1695 | strcpy(purl, "index.html"); |
1696 | if (stat(test, &sb) == 0) { |
1697 | /* It's a dir URL and there is index.html */ |
1698 | config->ContentLength = sb.st_size; |
1699 | config->last_mod = sb.st_mtime; |
1700 | } |
1701 | #if ENABLE_FEATURE_HTTPD_CGI |
1702 | else if (purl[-1] == '/') { |
1703 | /* It's a dir URL and there is no index.html |
1704 | * Try cgi-bin/index.cgi */ |
1705 | if (access("/cgi-bin/index.cgi"+1, X_OK) == 0) { |
1706 | purl[0] = '\0'; |
1707 | config->query = url; |
1708 | sendCgi("/cgi-bin/index.cgi", prequest, length, cookie, content_type); |
1709 | break; |
1710 | } |
1711 | } |
1712 | #endif /* FEATURE_HTTPD_CGI */ |
1713 | sendFile(test); |
1714 | config->ContentLength = -1; |
1715 | } while (0); |
1716 | |
1717 | #if ENABLE_FEATURE_HTTPD_CGI |
1718 | bail_out: |
1719 | #endif |
1720 | |
1721 | if (DEBUG) |
1722 | fprintf(stderr, "closing socket\n\n"); |
1723 | #if ENABLE_FEATURE_HTTPD_CGI |
1724 | free(cookie); |
1725 | free(content_type); |
1726 | free(config->referer); |
1727 | config->referer = NULL; |
1728 | # if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
1729 | free(config->remoteuser); |
1730 | config->remoteuser = NULL; |
1731 | # endif |
1732 | #endif |
1733 | shutdown(config->accepted_socket, SHUT_WR); |
1734 | |
1735 | /* Properly wait for remote to closed */ |
1736 | FD_ZERO(&s_fd); |
1737 | FD_SET(config->accepted_socket, &s_fd); |
1738 | |
1739 | do { |
1740 | tv.tv_sec = 2; |
1741 | tv.tv_usec = 0; |
1742 | retval = select(config->accepted_socket + 1, &s_fd, NULL, NULL, &tv); |
1743 | } while (retval > 0 && read(config->accepted_socket, buf, sizeof(config->buf) > 0)); |
1744 | |
1745 | shutdown(config->accepted_socket, SHUT_RD); |
1746 | /* In inetd case, we close fd 1 (stdout) here. We will exit soon anyway */ |
1747 | close(config->accepted_socket); |
1748 | } |
1749 | |
1750 | /**************************************************************************** |
1751 | * |
1752 | > $Function: miniHttpd() |
1753 | * |
1754 | * $Description: The main http server function. |
1755 | * |
1756 | * Given an open socket fildes, listen for new connections and farm out |
1757 | * the processing as a forked process. |
1758 | * |
1759 | * $Parameters: |
1760 | * (int) server. . . The server socket fildes. |
1761 | * |
1762 | * $Return: (int) . . . . Always 0. |
1763 | * |
1764 | ****************************************************************************/ |
1765 | static int miniHttpd(int server) |
1766 | { |
1767 | static const int on = 1; |
1768 | |
1769 | fd_set readfd, portfd; |
1770 | |
1771 | FD_ZERO(&portfd); |
1772 | FD_SET(server, &portfd); |
1773 | |
1774 | /* copy the ports we are watching to the readfd set */ |
1775 | while (1) { |
1776 | int s; |
1777 | union { |
1778 | struct sockaddr sa; |
1779 | struct sockaddr_in sin; |
1780 | USE_FEATURE_IPV6(struct sockaddr_in6 sin6;) |
1781 | } fromAddr; |
1782 | socklen_t fromAddrLen = sizeof(fromAddr); |
1783 | |
1784 | /* Now wait INDEFINITELY on the set of sockets! */ |
1785 | readfd = portfd; |
1786 | if (select(server + 1, &readfd, 0, 0, 0) <= 0) |
1787 | continue; |
1788 | if (!FD_ISSET(server, &readfd)) |
1789 | continue; |
1790 | s = accept(server, &fromAddr.sa, &fromAddrLen); |
1791 | if (s < 0) |
1792 | continue; |
1793 | config->accepted_socket = s; |
1794 | config->rmt_ip = 0; |
1795 | config->port = 0; |
1796 | #if ENABLE_FEATURE_HTTPD_CGI || DEBUG |
1797 | free(config->rmt_ip_str); |
1798 | config->rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr.sa, fromAddrLen); |
1799 | #if DEBUG |
1800 | bb_error_msg("connection from '%s'", config->rmt_ip_str); |
1801 | #endif |
1802 | #endif /* FEATURE_HTTPD_CGI */ |
1803 | if (fromAddr.sa.sa_family == AF_INET) { |
1804 | config->rmt_ip = ntohl(fromAddr.sin.sin_addr.s_addr); |
1805 | config->port = ntohs(fromAddr.sin.sin_port); |
1806 | } |
1807 | #if ENABLE_FEATURE_IPV6 |
1808 | if (fromAddr.sa.sa_family == AF_INET6) { |
1809 | //config->rmt_ip = ntohl(fromAddr.sin.sin_addr.s_addr); |
1810 | config->port = ntohs(fromAddr.sin6.sin6_port); |
1811 | } |
1812 | #endif |
1813 | |
1814 | /* set the KEEPALIVE option to cull dead connections */ |
1815 | setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)); |
1816 | |
1817 | if (DEBUG || fork() == 0) { |
1818 | /* child */ |
1819 | #if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP |
1820 | /* protect reload config, may be confuse checking */ |
1821 | signal(SIGHUP, SIG_IGN); |
1822 | #endif |
1823 | handleIncoming(); |
1824 | if (!DEBUG) |
1825 | exit(0); |
1826 | } |
1827 | close(s); |
1828 | } /* while (1) */ |
1829 | return 0; |
1830 | } |
1831 | |
1832 | /* from inetd */ |
1833 | static int miniHttpd_inetd(void) |
1834 | { |
1835 | union { |
1836 | struct sockaddr sa; |
1837 | struct sockaddr_in sin; |
1838 | USE_FEATURE_IPV6(struct sockaddr_in6 sin6;) |
1839 | } fromAddr; |
1840 | socklen_t fromAddrLen = sizeof(fromAddr); |
1841 | |
1842 | getpeername(0, &fromAddr.sa, &fromAddrLen); |
1843 | config->rmt_ip = 0; |
1844 | config->port = 0; |
1845 | #if ENABLE_FEATURE_HTTPD_CGI || DEBUG |
1846 | free(config->rmt_ip_str); |
1847 | config->rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr.sa, fromAddrLen); |
1848 | #endif |
1849 | if (fromAddr.sa.sa_family == AF_INET) { |
1850 | config->rmt_ip = ntohl(fromAddr.sin.sin_addr.s_addr); |
1851 | config->port = ntohs(fromAddr.sin.sin_port); |
1852 | } |
1853 | #if ENABLE_FEATURE_IPV6 |
1854 | if (fromAddr.sa.sa_family == AF_INET6) { |
1855 | //config->rmt_ip = ntohl(fromAddr.sin.sin_addr.s_addr); |
1856 | config->port = ntohs(fromAddr.sin6.sin6_port); |
1857 | } |
1858 | #endif |
1859 | handleIncoming(); |
1860 | return 0; |
1861 | } |
1862 | |
1863 | #if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP |
1864 | static void sighup_handler(int sig) |
1865 | { |
1866 | /* set and reset */ |
1867 | struct sigaction sa; |
1868 | |
1869 | parse_conf(default_path_httpd_conf, sig == SIGHUP ? SIGNALED_PARSE : FIRST_PARSE); |
1870 | sa.sa_handler = sighup_handler; |
1871 | sigemptyset(&sa.sa_mask); |
1872 | sa.sa_flags = SA_RESTART; |
1873 | sigaction(SIGHUP, &sa, NULL); |
1874 | } |
1875 | #endif |
1876 | |
1877 | enum { |
1878 | c_opt_config_file = 0, |
1879 | d_opt_decode_url, |
1880 | h_opt_home_httpd, |
1881 | USE_FEATURE_HTTPD_ENCODE_URL_STR(e_opt_encode_url,) |
1882 | USE_FEATURE_HTTPD_BASIC_AUTH( r_opt_realm ,) |
1883 | USE_FEATURE_HTTPD_AUTH_MD5( m_opt_md5 ,) |
1884 | USE_FEATURE_HTTPD_SETUID( u_opt_setuid ,) |
1885 | p_opt_port , |
1886 | p_opt_inetd , |
1887 | p_opt_foreground, |
1888 | OPT_CONFIG_FILE = 1 << c_opt_config_file, |
1889 | OPT_DECODE_URL = 1 << d_opt_decode_url, |
1890 | OPT_HOME_HTTPD = 1 << h_opt_home_httpd, |
1891 | OPT_ENCODE_URL = USE_FEATURE_HTTPD_ENCODE_URL_STR((1 << e_opt_encode_url)) + 0, |
1892 | OPT_REALM = USE_FEATURE_HTTPD_BASIC_AUTH( (1 << r_opt_realm )) + 0, |
1893 | OPT_MD5 = USE_FEATURE_HTTPD_AUTH_MD5( (1 << m_opt_md5 )) + 0, |
1894 | OPT_SETUID = USE_FEATURE_HTTPD_SETUID( (1 << u_opt_setuid )) + 0, |
1895 | OPT_PORT = 1 << p_opt_port, |
1896 | OPT_INETD = 1 << p_opt_inetd, |
1897 | OPT_FOREGROUND = 1 << p_opt_foreground, |
1898 | }; |
1899 | |
1900 | static const char httpd_opts[] = "c:d:h:" |
1901 | USE_FEATURE_HTTPD_ENCODE_URL_STR("e:") |
1902 | USE_FEATURE_HTTPD_BASIC_AUTH("r:") |
1903 | USE_FEATURE_HTTPD_AUTH_MD5("m:") |
1904 | USE_FEATURE_HTTPD_SETUID("u:") |
1905 | "p:if"; |
1906 | |
1907 | |
1908 | int httpd_main(int argc, char *argv[]) |
1909 | { |
1910 | unsigned opt; |
1911 | const char *home_httpd = home; |
1912 | char *url_for_decode; |
1913 | USE_FEATURE_HTTPD_ENCODE_URL_STR(const char *url_for_encode;) |
1914 | const char *s_port; |
1915 | USE_FEATURE_HTTPD_SETUID(const char *s_ugid = NULL;) |
1916 | USE_FEATURE_HTTPD_SETUID(struct bb_uidgid_t ugid;) |
1917 | USE_FEATURE_HTTPD_AUTH_MD5(const char *pass;) |
1918 | |
1919 | #if ENABLE_LOCALE_SUPPORT |
1920 | /* Undo busybox.c: we want to speak English in http (dates etc) */ |
1921 | setlocale(LC_TIME, "C"); |
1922 | #endif |
1923 | |
1924 | config = xzalloc(sizeof(*config)); |
1925 | #if ENABLE_FEATURE_HTTPD_BASIC_AUTH |
1926 | config->realm = "Web Server Authentication"; |
1927 | #endif |
1928 | config->port = 80; |
1929 | config->ContentLength = -1; |
1930 | |
1931 | opt = getopt32(argc, argv, httpd_opts, |
1932 | &(config->configFile), &url_for_decode, &home_httpd |
1933 | USE_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode) |
1934 | USE_FEATURE_HTTPD_BASIC_AUTH(, &(config->realm)) |
1935 | USE_FEATURE_HTTPD_AUTH_MD5(, &pass) |
1936 | USE_FEATURE_HTTPD_SETUID(, &s_ugid) |
1937 | , &s_port |
1938 | ); |
1939 | if (opt & OPT_DECODE_URL) { |
1940 | printf("%s", decodeString(url_for_decode, 1)); |
1941 | return 0; |
1942 | } |
1943 | #if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR |
1944 | if (opt & OPT_ENCODE_URL) { |
1945 | printf("%s", encodeString(url_for_encode)); |
1946 | return 0; |
1947 | } |
1948 | #endif |
1949 | #if ENABLE_FEATURE_HTTPD_AUTH_MD5 |
1950 | if (opt & OPT_MD5) { |
1951 | puts(pw_encrypt(pass, "$1$")); |
1952 | return 0; |
1953 | } |
1954 | #endif |
1955 | if (opt & OPT_PORT) |
1956 | config->port = xatou16(s_port); |
1957 | |
1958 | #if ENABLE_FEATURE_HTTPD_SETUID |
1959 | if (opt & OPT_SETUID) { |
1960 | if (!get_uidgid(&ugid, s_ugid, 1)) |
1961 | bb_error_msg_and_die("unrecognized user[:group] " |
1962 | "name '%s'", s_ugid); |
1963 | } |
1964 | #endif |
1965 | |
1966 | xchdir(home_httpd); |
1967 | if (!(opt & OPT_INETD)) { |
1968 | signal(SIGCHLD, SIG_IGN); |
1969 | config->server_socket = openServer(); |
1970 | #if ENABLE_FEATURE_HTTPD_SETUID |
1971 | /* drop privileges */ |
1972 | if (opt & OPT_SETUID) { |
1973 | if (ugid.gid != (gid_t)-1) { |
1974 | if (setgroups(1, &ugid.gid) == -1) |
1975 | bb_perror_msg_and_die("setgroups"); |
1976 | xsetgid(ugid.gid); |
1977 | } |
1978 | xsetuid(ugid.uid); |
1979 | } |
1980 | #endif |
1981 | } |
1982 | |
1983 | #if ENABLE_FEATURE_HTTPD_CGI |
1984 | { |
1985 | char *p = getenv("PATH"); |
1986 | p = xstrdup(p); /* if gets NULL, returns NULL */ |
1987 | clearenv(); |
1988 | if (p) |
1989 | setenv1("PATH", p); |
1990 | if (!(opt & OPT_INETD)) |
1991 | setenv_long("SERVER_PORT", config->port); |
1992 | } |
1993 | #endif |
1994 | |
1995 | #if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP |
1996 | sighup_handler(0); |
1997 | #else |
1998 | parse_conf(default_path_httpd_conf, FIRST_PARSE); |
1999 | #endif |
2000 | |
2001 | if (opt & OPT_INETD) |
2002 | return miniHttpd_inetd(); |
2003 | |
2004 | if (!(opt & OPT_FOREGROUND)) |
2005 | xdaemon(1, 0); /* don't change current directory */ |
2006 | return miniHttpd(config->server_socket); |
2007 | } |