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 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
9 *
10 *****************************************************************************
11 *
12 * Typical usage:
13 * For non root user:
14 * httpd -p 8080 -h $HOME/public_html
15 * For daemon start from rc script with uid=0:
16 * httpd -u www
17 * which is equivalent to (assuming user www has uid 80):
18 * httpd -p 80 -u 80 -h $PWD -c /etc/httpd.conf -r "Web Server Authentication"
19 *
20 * When an url starts with "/cgi-bin/" it is assumed to be a cgi script.
21 * The server changes directory to the location of the script and executes it
22 * after setting QUERY_STRING and other environment variables.
23 *
24 * If directory URL is given, no index.html is found and CGI support is enabled,
25 * cgi-bin/index.cgi will be run. Directory to list is ../$QUERY_STRING.
26 * See httpd_indexcgi.c for an example GCI code.
27 *
28 * Doc:
29 * "CGI Environment Variables": http://hoohoo.ncsa.uiuc.edu/cgi/env.html
30 *
31 * The applet can also be invoked as an url arg decoder and html text encoder
32 * as follows:
33 * foo=`httpd -d $foo` # decode "Hello%20World" as "Hello World"
34 * bar=`httpd -e "<Hello World>"` # encode as "<Hello World>"
35 * Note that url encoding for arguments is not the same as html encoding for
36 * presentation. -d decodes an url-encoded argument while -e encodes in html
37 * for page display.
38 *
39 * httpd.conf has the following format:
40 *
41 * H:/serverroot # define the server root. It will override -h
42 * A:172.20. # Allow address from 172.20.0.0/16
43 * A:10.0.0.0/25 # Allow any address from 10.0.0.0-10.0.0.127
44 * A:10.0.0.0/255.255.255.128 # Allow any address that previous set
45 * A:127.0.0.1 # Allow local loopback connections
46 * D:* # Deny from other IP connections
47 * E404:/path/e404.html # /path/e404.html is the 404 (not found) error page
48 * I:index.html # Show index.html when a directory is requested
49 *
50 * P:/url:[http://]hostname[:port]/new/path
51 * # When /urlXXXXXX is requested, reverse proxy
52 * # it to http://hostname[:port]/new/pathXXXXXX
53 *
54 * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin/
55 * /adm:admin:setup # Require user admin, pwd setup on urls starting with /adm/
56 * /adm:toor:PaSsWd # or user toor, pwd PaSsWd on urls starting with /adm/
57 * /adm:root:* # or user root, pwd from /etc/passwd on urls starting with /adm/
58 * /wiki:*:* # or any user from /etc/passwd with according pwd on urls starting with /wiki/
59 * .au:audio/basic # additional mime type for audio.au files
60 * *.php:/path/php # run xxx.php through an interpreter
61 *
62 * A/D may be as a/d or allow/deny - only first char matters.
63 * Deny/Allow IP logic:
64 * - Default is to allow all (Allow all (A:*) is a no-op).
65 * - Deny rules take precedence over allow rules.
66 * - "Deny all" rule (D:*) is applied last.
67 *
68 * Example:
69 * 1. Allow only specified addresses
70 * A:172.20 # Allow any address that begins with 172.20.
71 * A:10.10. # Allow any address that begins with 10.10.
72 * A:127.0.0.1 # Allow local loopback connections
73 * D:* # Deny from other IP connections
74 *
75 * 2. Only deny specified addresses
76 * D:1.2.3. # deny from 1.2.3.0 - 1.2.3.255
77 * D:2.3.4. # deny from 2.3.4.0 - 2.3.4.255
78 * A:* # (optional line added for clarity)
79 *
80 * If a sub directory contains config file, it is parsed and merged with
81 * any existing settings as if it was appended to the original configuration.
82 *
83 * subdir paths are relative to the containing subdir and thus cannot
84 * affect the parent rules.
85 *
86 * Note that since the sub dir is parsed in the forked thread servicing the
87 * subdir http request, any merge is discarded when the process exits. As a
88 * result, the subdir settings only have a lifetime of a single request.
89 *
90 * Custom error pages can contain an absolute path or be relative to
91 * 'home_httpd'. Error pages are to be static files (no CGI or script). Error
92 * page can only be defined in the root configuration file and are not taken
93 * into account in local (directories) config files.
94 *
95 * If -c is not set, an attempt will be made to open the default
96 * root configuration file. If -c is set and the file is not found, the
97 * server exits with an error.
98 */
99 //config:config HTTPD
100 //config: bool "httpd (32 kb)"
101 //config: default y
102 //config: help
103 //config: HTTP server.
104 //config:
105 //config:config FEATURE_HTTPD_PORT_DEFAULT
106 //config: int "Default port"
107 //config: default 80
108 //config: range 1 65535
109 //config: depends on HTTPD
110 //config:
111 //config:config FEATURE_HTTPD_RANGES
112 //config: bool "Support 'Ranges:' header"
113 //config: default y
114 //config: depends on HTTPD
115 //config: help
116 //config: Makes httpd emit "Accept-Ranges: bytes" header and understand
117 //config: "Range: bytes=NNN-[MMM]" header. Allows for resuming interrupted
118 //config: downloads, seeking in multimedia players etc.
119 //config:
120 //config:config FEATURE_HTTPD_SETUID
121 //config: bool "Enable -u <user> option"
122 //config: default y
123 //config: depends on HTTPD
124 //config: help
125 //config: This option allows the server to run as a specific user
126 //config: rather than defaulting to the user that starts the server.
127 //config: Use of this option requires special privileges to change to a
128 //config: different user.
129 //config:
130 //config:config FEATURE_HTTPD_BASIC_AUTH
131 //config: bool "Enable HTTP authentication"
132 //config: default y
133 //config: depends on HTTPD
134 //config: help
135 //config: Utilizes password settings from /etc/httpd.conf for basic
136 //config: authentication on a per url basis.
137 //config: Example for httpd.conf file:
138 //config: /adm:toor:PaSsWd
139 //config:
140 //config:config FEATURE_HTTPD_AUTH_MD5
141 //config: bool "Support MD5-encrypted passwords in HTTP authentication"
142 //config: default y
143 //config: depends on FEATURE_HTTPD_BASIC_AUTH
144 //config: help
145 //config: Enables encrypted passwords, and wildcard user/passwords
146 //config: in httpd.conf file.
147 //config: User '*' means 'any system user name is ok',
148 //config: password of '*' means 'use system password for this user'
149 //config: Examples:
150 //config: /adm:toor:$1$P/eKnWXS$aI1aPGxT.dJD5SzqAKWrF0
151 //config: /adm:root:*
152 //config: /wiki:*:*
153 //config:
154 //config:config FEATURE_HTTPD_CGI
155 //config: bool "Support Common Gateway Interface (CGI)"
156 //config: default y
157 //config: depends on HTTPD
158 //config: help
159 //config: This option allows scripts and executables to be invoked
160 //config: when specific URLs are requested.
161 //config:
162 //config:config FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
163 //config: bool "Support running scripts through an interpreter"
164 //config: default y
165 //config: depends on FEATURE_HTTPD_CGI
166 //config: help
167 //config: This option enables support for running scripts through an
168 //config: interpreter. Turn this on if you want PHP scripts to work
169 //config: properly. You need to supply an additional line in your
170 //config: httpd.conf file:
171 //config: *.php:/path/to/your/php
172 //config:
173 //config:config FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
174 //config: bool "Set REMOTE_PORT environment variable for CGI"
175 //config: default y
176 //config: depends on FEATURE_HTTPD_CGI
177 //config: help
178 //config: Use of this option can assist scripts in generating
179 //config: references that contain a unique port number.
180 //config:
181 //config:config FEATURE_HTTPD_ENCODE_URL_STR
182 //config: bool "Enable -e option (useful for CGIs written as shell scripts)"
183 //config: default y
184 //config: depends on HTTPD
185 //config: help
186 //config: This option allows html encoding of arbitrary strings for display
187 //config: by the browser. Output goes to stdout.
188 //config: For example, httpd -e "<Hello World>" produces
189 //config: "<Hello World>".
190 //config:
191 //config:config FEATURE_HTTPD_ERROR_PAGES
192 //config: bool "Support custom error pages"
193 //config: default y
194 //config: depends on HTTPD
195 //config: help
196 //config: This option allows you to define custom error pages in
197 //config: the configuration file instead of the default HTTP status
198 //config: error pages. For instance, if you add the line:
199 //config: E404:/path/e404.html
200 //config: in the config file, the server will respond the specified
201 //config: '/path/e404.html' file instead of the terse '404 NOT FOUND'
202 //config: message.
203 //config:
204 //config:config FEATURE_HTTPD_PROXY
205 //config: bool "Support reverse proxy"
206 //config: default y
207 //config: depends on HTTPD
208 //config: help
209 //config: This option allows you to define URLs that will be forwarded
210 //config: to another HTTP server. To setup add the following line to the
211 //config: configuration file
212 //config: P:/url/:http://hostname[:port]/new/path/
213 //config: Then a request to /url/myfile will be forwarded to
214 //config: http://hostname[:port]/new/path/myfile.
215 //config:
216 //config:config FEATURE_HTTPD_GZIP
217 //config: bool "Support GZIP content encoding"
218 //config: default y
219 //config: depends on HTTPD
220 //config: help
221 //config: Makes httpd send files using GZIP content encoding if the
222 //config: client supports it and a pre-compressed <file>.gz exists.
223 //config:
224 //config:config FEATURE_HTTPD_ETAG
225 //config: bool "Support caching via ETag header"
226 //config: default y
227 //config: depends on HTTPD
228 //config: help
229 //config: If server responds with ETag then next time client (browser)
230 //config: resend it via If-None-Match header.
231 //config: Then httpd will check if file wasn't modified and if not,
232 //config: return 304 Not Modified status code.
233 //config: The ETag value is constructed from last modification date
234 //config: in unix epoch, and size: "hex(last_mod)-hex(file_size)".
235 //config: It's not completely reliable as hash functions but fair enough.
236 //config:
237 //config:config FEATURE_HTTPD_LAST_MODIFIED
238 //config: bool "Add Last-Modified header to response"
239 //config: default y
240 //config: depends on HTTPD
241 //config: help
242 //config: The Last-Modified header is used for cache validation.
243 //config: The client sends last seen mtime to server in If-Modified-Since.
244 //config: Both headers MUST be an RFC 1123 formatted, which is hard to parse.
245 //config: Use ETag header instead.
246 //config:
247 //config:config FEATURE_HTTPD_DATE
248 //config: bool "Add Date header to response"
249 //config: default y
250 //config: depends on HTTPD
251 //config: help
252 //config: RFC2616 says that server MUST add Date header to response.
253 //config: But it is almost useless and can be omitted.
254 //config:
255 //config:config FEATURE_HTTPD_ACL_IP
256 //config: bool "ACL IP"
257 //config: default y
258 //config: depends on HTTPD
259 //config: help
260 //config: Support IP deny/allow rules
261
262 //applet:IF_HTTPD(APPLET(httpd, BB_DIR_USR_SBIN, BB_SUID_DROP))
263
264 //kbuild:lib-$(CONFIG_HTTPD) += httpd.o
265
266 //usage:#define httpd_trivial_usage
267 //usage: "[-ifv[v]]"
268 //usage: " [-c CONFFILE]"
269 //usage: " [-p [IP:]PORT]"
270 //usage: IF_FEATURE_HTTPD_SETUID(" [-u USER[:GRP]]")
271 //usage: IF_FEATURE_HTTPD_BASIC_AUTH(" [-r REALM]")
272 //usage: " [-h HOME]\n"
273 //usage: "or httpd -d/-e" IF_FEATURE_HTTPD_AUTH_MD5("/-m") " STRING"
274 //usage:#define httpd_full_usage "\n\n"
275 //usage: "Listen for incoming HTTP requests\n"
276 //usage: "\n -i Inetd mode"
277 //usage: "\n -f Run in foreground"
278 //usage: "\n -v[v] Verbose"
279 //usage: "\n -p [IP:]PORT Bind to IP:PORT (default *:"STR(CONFIG_FEATURE_HTTPD_PORT_DEFAULT)")"
280 //usage: IF_FEATURE_HTTPD_SETUID(
281 //usage: "\n -u USER[:GRP] Set uid/gid after binding to port")
282 //usage: IF_FEATURE_HTTPD_BASIC_AUTH(
283 //usage: "\n -r REALM Authentication Realm for Basic Authentication")
284 //usage: "\n -h HOME Home directory (default .)"
285 //usage: "\n -c FILE Configuration file (default {/etc,HOME}/httpd.conf)"
286 //usage: IF_FEATURE_HTTPD_AUTH_MD5(
287 //usage: "\n -m STRING MD5 crypt STRING")
288 //usage: "\n -e STRING HTML encode STRING"
289 //usage: "\n -d STRING URL decode STRING"
290
291 /* TODO: use TCP_CORK, parse_config() */
292
293 #include "libbb.h"
294 #include "common_bufsiz.h"
295 #if ENABLE_PAM
296 /* PAM may include <locale.h>. We may need to undefine bbox's stub define: */
297 # undef setlocale
298 /* For some obscure reason, PAM is not in pam/xxx, but in security/xxx.
299 * Apparently they like to confuse people. */
300 # include <security/pam_appl.h>
301 # include <security/pam_misc.h>
302 #endif
303 #if ENABLE_FEATURE_USE_SENDFILE
304 # include <sys/sendfile.h>
305 #endif
306
307 /* see sys/netinet6/in6.h */
308 #if defined(__FreeBSD__)
309 # define s6_addr32 __u6_addr.__u6_addr32
310 #endif
311
312 #define DEBUG 0
313
314 #if DEBUG
315 # define dbg(...) fprintf(stderr, __VA_ARGS__)
316 #else
317 # define dbg(...) ((void)0)
318 #endif
319
320 #define IOBUF_SIZE 8192
321 #define MAX_HTTP_HEADERS_SIZE (32*1024)
322
323 #define HEADER_READ_TIMEOUT 60
324
325 #define STR1(s) #s
326 #define STR(s) STR1(s)
327
328 static const char DEFAULT_PATH_HTTPD_CONF[] ALIGN1 = "/etc";
329 static const char HTTPD_CONF[] ALIGN1 = "httpd.conf";
330 static const char HTTP_200[] ALIGN1 = "HTTP/1.1 200 OK\r\n";
331 static const char index_html[] ALIGN1 = "index.html";
332
333 typedef struct has_next_ptr {
334 struct has_next_ptr *next;
335 } has_next_ptr;
336
337 /* Must have "next" as a first member */
338 typedef struct Htaccess {
339 struct Htaccess *next;
340 char *after_colon;
341 char before_colon[1]; /* really bigger, must be last */
342 } Htaccess;
343
344 #if ENABLE_FEATURE_HTTPD_ACL_IP
345 /* Must have "next" as a first member */
346 typedef struct Htaccess_IP {
347 struct Htaccess_IP *next;
348 unsigned ip;
349 unsigned mask;
350 int allow_deny;
351 } Htaccess_IP;
352 #endif
353
354 /* Must have "next" as a first member */
355 typedef struct Htaccess_Proxy {
356 struct Htaccess_Proxy *next;
357 char *url_from;
358 char *host_port;
359 char *url_to;
360 } Htaccess_Proxy;
361
362 enum {
363 HTTP_OK = 200,
364 HTTP_PARTIAL_CONTENT = 206,
365 HTTP_MOVED_TEMPORARILY = 302,
366 HTTP_NOT_MODIFIED = 304,
367 HTTP_BAD_REQUEST = 400, /* malformed syntax */
368 HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */
369 HTTP_NOT_FOUND = 404,
370 HTTP_FORBIDDEN = 403,
371 HTTP_REQUEST_TIMEOUT = 408,
372 HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */
373 HTTP_INTERNAL_SERVER_ERROR = 500,
374 HTTP_ENTITY_TOO_LARGE = 413,
375 HTTP_CONTINUE = 100,
376 #if 0 /* future use */
377 HTTP_SWITCHING_PROTOCOLS = 101,
378 HTTP_CREATED = 201,
379 HTTP_ACCEPTED = 202,
380 HTTP_NON_AUTHORITATIVE_INFO = 203,
381 HTTP_NO_CONTENT = 204,
382 HTTP_MULTIPLE_CHOICES = 300,
383 HTTP_MOVED_PERMANENTLY = 301,
384 HTTP_PAYMENT_REQUIRED = 402,
385 HTTP_BAD_GATEWAY = 502,
386 HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
387 #endif
388 };
389
390 static const uint16_t http_response_type[] ALIGN2 = {
391 HTTP_OK,
392 #if ENABLE_FEATURE_HTTPD_RANGES
393 HTTP_PARTIAL_CONTENT,
394 #endif
395 HTTP_MOVED_TEMPORARILY,
396 #if ENABLE_FEATURE_HTTPD_ETAG
397 HTTP_NOT_MODIFIED,
398 #endif
399 HTTP_REQUEST_TIMEOUT,
400 HTTP_NOT_IMPLEMENTED,
401 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
402 HTTP_UNAUTHORIZED,
403 #endif
404 HTTP_NOT_FOUND,
405 HTTP_BAD_REQUEST,
406 HTTP_FORBIDDEN,
407 HTTP_INTERNAL_SERVER_ERROR,
408 HTTP_ENTITY_TOO_LARGE,
409 #if 0 /* not implemented */
410 HTTP_CREATED,
411 HTTP_ACCEPTED,
412 HTTP_NO_CONTENT,
413 HTTP_MULTIPLE_CHOICES,
414 HTTP_MOVED_PERMANENTLY,
415 HTTP_BAD_GATEWAY,
416 HTTP_SERVICE_UNAVAILABLE,
417 #endif
418 };
419
420 static const struct {
421 const char *name;
422 const char *info;
423 } http_response[ARRAY_SIZE(http_response_type)] = {
424 { "OK", NULL },
425 #if ENABLE_FEATURE_HTTPD_RANGES
426 { "Partial Content", NULL },
427 #endif
428 { "Found", NULL },
429 #if ENABLE_FEATURE_HTTPD_ETAG
430 { "Not Modified" },
431 #endif
432 { "Request Timeout", "No request appeared within 60 seconds" },
433 { "Not Implemented", "The requested method is not recognized" },
434 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
435 { "Unauthorized", "" },
436 #endif
437 { "Not Found", "The requested URL was not found" },
438 { "Bad Request", "Unsupported method" },
439 { "Forbidden", "" },
440 { "Internal Server Error", "Internal Server Error" },
441 { "Entity Too Large", "Entity Too Large" },
442 #if 0 /* not implemented */
443 { "Created" },
444 { "Accepted" },
445 { "No Content" },
446 { "Multiple Choices" },
447 { "Moved Permanently" },
448 { "Bad Gateway", "" },
449 { "Service Unavailable", "" },
450 #endif
451 };
452
453 struct globals {
454 int verbose; /* must be int (used by getopt32) */
455 smallint flg_deny_all;
456 #if ENABLE_FEATURE_HTTPD_GZIP
457 /* client can handle gzip / we are going to send gzip */
458 smallint content_gzip;
459 #endif
460 time_t last_mod;
461 #if ENABLE_FEATURE_HTTPD_ETAG
462 char *if_none_match;
463 #endif
464 char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */
465 const char *bind_addr_or_port;
466
467 char *g_query;
468 const char *opt_c_configFile;
469 const char *home_httpd;
470 const char *index_page;
471
472 const char *found_mime_type;
473 const char *found_moved_temporarily;
474 #if ENABLE_FEATURE_HTTPD_ACL_IP
475 Htaccess_IP *ip_a_d; /* config allow/deny lines */
476 #endif
477
478 IF_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;)
479 IF_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;)
480
481 off_t file_size; /* -1 - unknown */
482 #if ENABLE_FEATURE_HTTPD_RANGES
483 off_t range_start;
484 off_t range_end;
485 off_t range_len;
486 #endif
487
488 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
489 Htaccess *g_auth; /* config user:password lines */
490 #endif
491 Htaccess *mime_a; /* config mime types */
492 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
493 Htaccess *script_i; /* config script interpreters */
494 #endif
495 char *iobuf; /* [IOBUF_SIZE] */
496 #define hdr_buf bb_common_bufsiz1
497 #define sizeof_hdr_buf COMMON_BUFSIZE
498 char *hdr_ptr;
499 int hdr_cnt;
500 #if ENABLE_FEATURE_HTTPD_ETAG
501 char etag[sizeof("'%llx-%llx'") + 2 * sizeof(long long)*3];
502 #endif
503 #if ENABLE_FEATURE_HTTPD_ERROR_PAGES
504 const char *http_error_page[ARRAY_SIZE(http_response_type)];
505 #endif
506 #if ENABLE_FEATURE_HTTPD_PROXY
507 Htaccess_Proxy *proxy;
508 #endif
509 };
510 #define G (*ptr_to_globals)
511 #define verbose (G.verbose )
512 #define flg_deny_all (G.flg_deny_all )
513 #if ENABLE_FEATURE_HTTPD_GZIP
514 # define content_gzip (G.content_gzip )
515 #else
516 # define content_gzip 0
517 #endif
518 #define bind_addr_or_port (G.bind_addr_or_port)
519 #define g_query (G.g_query )
520 #define opt_c_configFile (G.opt_c_configFile )
521 #define home_httpd (G.home_httpd )
522 #define index_page (G.index_page )
523 #define found_mime_type (G.found_mime_type )
524 #define found_moved_temporarily (G.found_moved_temporarily)
525 #define last_mod (G.last_mod )
526 #define g_realm (G.g_realm )
527 #define remoteuser (G.remoteuser )
528 #define file_size (G.file_size )
529 #if ENABLE_FEATURE_HTTPD_RANGES
530 #define range_start (G.range_start )
531 #define range_end (G.range_end )
532 #define range_len (G.range_len )
533 #else
534 enum {
535 range_start = -1,
536 range_end = MAXINT(off_t) - 1,
537 range_len = MAXINT(off_t),
538 };
539 #endif
540 #define rmt_ip_str (G.rmt_ip_str )
541 #define g_auth (G.g_auth )
542 #define mime_a (G.mime_a )
543 #define script_i (G.script_i )
544 #define iobuf (G.iobuf )
545 #define hdr_ptr (G.hdr_ptr )
546 #define hdr_cnt (G.hdr_cnt )
547 #define http_error_page (G.http_error_page )
548 #define proxy (G.proxy )
549 #define INIT_G() do { \
550 setup_common_bufsiz(); \
551 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
552 IF_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
553 IF_FEATURE_HTTPD_RANGES(range_start = -1;) \
554 bind_addr_or_port = STR(CONFIG_FEATURE_HTTPD_PORT_DEFAULT); \
555 index_page = index_html; \
556 file_size = -1; \
557 } while (0)
558
559
560 #define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1)
561
562 /* Prototypes */
563 enum {
564 SEND_HEADERS = (1 << 0),
565 SEND_BODY = (1 << 1),
566 };
567 static void send_file_and_exit(const char *url, int what) NORETURN;
568
free_llist(has_next_ptr ** pptr)569 static void free_llist(has_next_ptr **pptr)
570 {
571 has_next_ptr *cur = *pptr;
572 while (cur) {
573 has_next_ptr *t = cur;
574 cur = cur->next;
575 free(t);
576 }
577 *pptr = NULL;
578 }
579
free_Htaccess_list(Htaccess ** pptr)580 static ALWAYS_INLINE void free_Htaccess_list(Htaccess **pptr)
581 {
582 free_llist((has_next_ptr**)pptr);
583 }
584
585 #if ENABLE_FEATURE_HTTPD_ACL_IP
free_Htaccess_IP_list(Htaccess_IP ** pptr)586 static ALWAYS_INLINE void free_Htaccess_IP_list(Htaccess_IP **pptr)
587 {
588 free_llist((has_next_ptr**)pptr);
589 }
590 #endif
591
592 #if ENABLE_FEATURE_HTTPD_ACL_IP
593 /* Returns presumed mask width in bits or < 0 on error.
594 * Updates strp, stores IP at provided pointer */
scan_ip(const char ** strp,unsigned * ipp,unsigned char endc)595 static int scan_ip(const char **strp, unsigned *ipp, unsigned char endc)
596 {
597 const char *p = *strp;
598 int auto_mask = 8;
599 unsigned ip = 0;
600 int j;
601
602 if (*p == '/')
603 return -auto_mask;
604
605 for (j = 0; j < 4; j++) {
606 unsigned octet;
607
608 if ((*p < '0' || *p > '9') && *p != '/' && *p)
609 return -auto_mask;
610 octet = 0;
611 while (*p >= '0' && *p <= '9') {
612 octet *= 10;
613 octet += *p - '0';
614 if (octet > 255)
615 return -auto_mask;
616 p++;
617 }
618 if (*p == '.')
619 p++;
620 if (*p != '/' && *p)
621 auto_mask += 8;
622 ip = (ip << 8) | octet;
623 }
624 if (*p) {
625 if (*p != endc)
626 return -auto_mask;
627 p++;
628 if (*p == '\0')
629 return -auto_mask;
630 }
631 *ipp = ip;
632 *strp = p;
633 return auto_mask;
634 }
635
636 /* Returns 0 on success. Stores IP and mask at provided pointers */
scan_ip_mask(const char * str,unsigned * ipp,unsigned * maskp)637 static int scan_ip_mask(const char *str, unsigned *ipp, unsigned *maskp)
638 {
639 int i;
640 unsigned mask;
641 char *p;
642
643 i = scan_ip(&str, ipp, '/');
644 if (i < 0)
645 return i;
646
647 if (*str) {
648 /* there is /xxx after dotted-IP address */
649 i = bb_strtou(str, &p, 10);
650 if (*p == '.') {
651 /* 'xxx' itself is dotted-IP mask, parse it */
652 /* (return 0 (success) only if it has N.N.N.N form) */
653 return scan_ip(&str, maskp, '\0') - 32;
654 }
655 if (*p)
656 return -1;
657 }
658
659 if (i > 32)
660 return -1;
661
662 if (sizeof(unsigned) == 4 && i == 32) {
663 /* mask >>= 32 below may not work */
664 mask = 0;
665 } else {
666 mask = 0xffffffff;
667 mask >>= i;
668 }
669 /* i == 0 -> *maskp = 0x00000000
670 * i == 1 -> *maskp = 0x80000000
671 * i == 4 -> *maskp = 0xf0000000
672 * i == 31 -> *maskp = 0xfffffffe
673 * i == 32 -> *maskp = 0xffffffff */
674 *maskp = (uint32_t)(~mask);
675 return 0;
676 }
677 #endif
678
679 /*
680 * Parse configuration file into in-memory linked list.
681 *
682 * Any previous IP rules are discarded.
683 * If the flag argument is not SUBDIR_PARSE then all /path and mime rules
684 * are also discarded. That is, previous settings are retained if flag is
685 * SUBDIR_PARSE.
686 * Error pages are only parsed on the main config file.
687 *
688 * path Path where to look for httpd.conf (without filename).
689 * flag Type of the parse request.
690 */
691 /* flag param: */
692 enum {
693 FIRST_PARSE = 0, /* path will be "/etc" */
694 SIGNALED_PARSE = 1, /* path will be "/etc" */
695 SUBDIR_PARSE = 2, /* path will be derived from URL */
696 };
parse_conf(const char * path,int flag)697 static int parse_conf(const char *path, int flag)
698 {
699 /* internally used extra flag state */
700 enum { TRY_CURDIR_PARSE = 3 };
701
702 FILE *f;
703 const char *filename;
704 char buf[160];
705
706 /* discard old rules */
707 #if ENABLE_FEATURE_HTTPD_ACL_IP
708 free_Htaccess_IP_list(&G.ip_a_d);
709 #endif
710 flg_deny_all = 0;
711 /* retain previous auth and mime config only for subdir parse */
712 if (flag != SUBDIR_PARSE) {
713 free_Htaccess_list(&mime_a);
714 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
715 free_Htaccess_list(&g_auth);
716 #endif
717 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
718 free_Htaccess_list(&script_i);
719 #endif
720 }
721
722 filename = opt_c_configFile;
723 if (flag == SUBDIR_PARSE || filename == NULL) {
724 filename = alloca(strlen(path) + sizeof(HTTPD_CONF) + 2);
725 sprintf((char *)filename, "%s/%s", path, HTTPD_CONF);
726 }
727
728 while ((f = fopen_for_read(filename)) == NULL) {
729 if (flag >= SUBDIR_PARSE) { /* SUBDIR or TRY_CURDIR */
730 /* config file not found, no changes to config */
731 return -1;
732 }
733 if (flag == FIRST_PARSE) {
734 /* -c CONFFILE given, but CONFFILE doesn't exist? */
735 if (opt_c_configFile)
736 bb_simple_perror_msg_and_die(opt_c_configFile);
737 /* else: no -c, thus we looked at /etc/httpd.conf,
738 * and it's not there. try ./httpd.conf: */
739 }
740 flag = TRY_CURDIR_PARSE;
741 filename = HTTPD_CONF;
742 }
743
744 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
745 /* in "/file:user:pass" lines, we prepend path in subdirs */
746 if (flag != SUBDIR_PARSE)
747 path = "";
748 #endif
749 /* The lines can be:
750 *
751 * I:default_index_file
752 * H:http_home
753 * [AD]:IP[/mask] # allow/deny, * for wildcard
754 * Ennn:error.html # error page for status nnn
755 * P:/url:[http://]hostname[:port]/new/path # reverse proxy
756 * .ext:mime/type # mime type
757 * *.php:/path/php # run xxx.php through an interpreter
758 * /file:user:pass # username and password
759 */
760 while (fgets(buf, sizeof(buf), f) != NULL) {
761 unsigned strlen_buf;
762 unsigned char ch;
763 char *after_colon;
764
765 { /* remove all whitespace, and # comments */
766 char *p, *p0;
767
768 p0 = buf;
769 /* skip non-whitespace beginning. Often the whole line
770 * is non-whitespace. We want this case to work fast,
771 * without needless copying, therefore we don't merge
772 * this operation into next while loop. */
773 while ((ch = *p0) != '\0' && ch != '\n' && ch != '#'
774 && ch != ' ' && ch != '\t'
775 ) {
776 p0++;
777 }
778 p = p0;
779 /* if we enter this loop, we have some whitespace.
780 * discard it */
781 while (ch != '\0' && ch != '\n' && ch != '#') {
782 if (ch != ' ' && ch != '\t') {
783 *p++ = ch;
784 }
785 ch = *++p0;
786 }
787 *p = '\0';
788 strlen_buf = p - buf;
789 if (strlen_buf == 0)
790 continue; /* empty line */
791 }
792
793 after_colon = strchr(buf, ':');
794 /* strange line? */
795 if (after_colon == NULL || *++after_colon == '\0')
796 goto config_error;
797
798 ch = (buf[0] & ~0x20); /* toupper if it's a letter */
799
800 if (ch == 'I') {
801 if (index_page != index_html)
802 free((char*)index_page);
803 index_page = xstrdup(after_colon);
804 continue;
805 }
806
807 /* do not allow jumping around using H in subdir's configs */
808 if (flag == FIRST_PARSE && ch == 'H') {
809 home_httpd = xstrdup(after_colon);
810 xchdir(home_httpd);
811 continue;
812 }
813
814 #if ENABLE_FEATURE_HTTPD_ACL_IP
815 if (ch == 'A' || ch == 'D') {
816 Htaccess_IP *pip;
817
818 if (*after_colon == '*') {
819 if (ch == 'D') {
820 /* memorize "deny all" */
821 flg_deny_all = 1;
822 }
823 /* skip assumed "A:*", it is a default anyway */
824 continue;
825 }
826 /* store "allow/deny IP/mask" line */
827 pip = xzalloc(sizeof(*pip));
828 if (scan_ip_mask(after_colon, &pip->ip, &pip->mask)) {
829 /* IP{/mask} syntax error detected, protect all */
830 ch = 'D';
831 pip->mask = 0;
832 }
833 pip->allow_deny = ch;
834 if (ch == 'D') {
835 /* Deny:from_IP - prepend */
836 pip->next = G.ip_a_d;
837 G.ip_a_d = pip;
838 } else {
839 /* A:from_IP - append (thus all D's precedes A's) */
840 Htaccess_IP *prev_IP = G.ip_a_d;
841 if (prev_IP == NULL) {
842 G.ip_a_d = pip;
843 } else {
844 while (prev_IP->next)
845 prev_IP = prev_IP->next;
846 prev_IP->next = pip;
847 }
848 }
849 continue;
850 }
851 #endif
852
853 #if ENABLE_FEATURE_HTTPD_ERROR_PAGES
854 if (flag == FIRST_PARSE && ch == 'E') {
855 unsigned i;
856 int status = atoi(buf + 1); /* error status code */
857
858 if (status < HTTP_CONTINUE) {
859 goto config_error;
860 }
861 /* then error page; find matching status */
862 for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
863 if (http_response_type[i] == status) {
864 /* We chdir to home_httpd, thus no need to
865 * concat_path_file(home_httpd, after_colon)
866 * here */
867 http_error_page[i] = xstrdup(after_colon);
868 break;
869 }
870 }
871 continue;
872 }
873 #endif
874
875 #if ENABLE_FEATURE_HTTPD_PROXY
876 if (flag == FIRST_PARSE && ch == 'P') {
877 /* P:/url:[http://]hostname[:port]/new/path */
878 char *url_from, *host_port, *url_to;
879 Htaccess_Proxy *proxy_entry;
880
881 url_from = after_colon;
882 host_port = strchr(after_colon, ':');
883 if (host_port == NULL) {
884 goto config_error;
885 }
886 *host_port++ = '\0';
887 if (is_prefixed_with(host_port, "http://"))
888 host_port += 7;
889 if (*host_port == '\0') {
890 goto config_error;
891 }
892 url_to = strchr(host_port, '/');
893 if (url_to == NULL) {
894 goto config_error;
895 }
896 *url_to = '\0';
897 proxy_entry = xzalloc(sizeof(*proxy_entry));
898 proxy_entry->url_from = xstrdup(url_from);
899 proxy_entry->host_port = xstrdup(host_port);
900 *url_to = '/';
901 proxy_entry->url_to = xstrdup(url_to);
902 proxy_entry->next = proxy;
903 proxy = proxy_entry;
904 continue;
905 }
906 #endif
907 /* the rest of directives are non-alphabetic,
908 * must avoid using "toupper'ed" ch */
909 ch = buf[0];
910
911 if (ch == '.' /* ".ext:mime/type" */
912 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
913 || (ch == '*' && buf[1] == '.') /* "*.php:/path/php" */
914 #endif
915 ) {
916 char *p;
917 Htaccess *cur;
918
919 cur = xzalloc(sizeof(*cur) /* includes space for NUL */ + strlen_buf);
920 strcpy(cur->before_colon, buf);
921 p = cur->before_colon + (after_colon - buf);
922 p[-1] = '\0';
923 cur->after_colon = p;
924 if (ch == '.') {
925 /* .mime line: prepend to mime_a list */
926 cur->next = mime_a;
927 mime_a = cur;
928 }
929 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
930 else {
931 /* script interpreter line: prepend to script_i list */
932 cur->next = script_i;
933 script_i = cur;
934 }
935 #endif
936 continue;
937 }
938
939 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
940 if (ch == '/') { /* "/file:user:pass" */
941 char *p;
942 Htaccess *cur;
943 unsigned file_len;
944
945 /* note: path is "" unless we are in SUBDIR parse,
946 * otherwise it does NOT start with "/" */
947 cur = xzalloc(sizeof(*cur) /* includes space for NUL */
948 + 1 + strlen(path)
949 + strlen_buf
950 );
951 /* form "/path/file" */
952 sprintf(cur->before_colon, "/%s%.*s",
953 path,
954 (int) (after_colon - buf - 1), /* includes "/", but not ":" */
955 buf);
956 /* canonicalize it */
957 p = bb_simplify_abs_path_inplace(cur->before_colon);
958 file_len = p - cur->before_colon;
959 /* add "user:pass" after NUL */
960 strcpy(++p, after_colon);
961 cur->after_colon = p;
962
963 /* insert cur into g_auth */
964 /* g_auth is sorted by decreased filename length */
965 {
966 Htaccess *auth, **authp;
967
968 authp = &g_auth;
969 while ((auth = *authp) != NULL) {
970 if (file_len >= strlen(auth->before_colon)) {
971 /* insert cur before auth */
972 cur->next = auth;
973 break;
974 }
975 authp = &auth->next;
976 }
977 *authp = cur;
978 }
979 continue;
980 }
981 #endif /* BASIC_AUTH */
982
983 /* the line is not recognized */
984 config_error:
985 bb_error_msg("config error '%s' in '%s'", buf, filename);
986 } /* while (fgets) */
987
988 fclose(f);
989 return 0;
990 }
991
992 #if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR
993 /*
994 * Given a string, html-encode special characters.
995 * This is used for the -e command line option to provide an easy way
996 * for scripts to encode result data without confusing browsers. The
997 * returned string pointer is memory allocated by malloc().
998 *
999 * Returns a pointer to the encoded string (malloced).
1000 */
encodeString(const char * string)1001 static char *encodeString(const char *string)
1002 {
1003 /* take the simple route and encode everything */
1004 /* could possibly scan once to get length. */
1005 int len = strlen(string);
1006 char *out = xmalloc(len * 6 + 1);
1007 char *p = out;
1008 char ch;
1009
1010 while ((ch = *string++) != '\0') {
1011 /* very simple check for what to encode */
1012 if (isalnum(ch))
1013 *p++ = ch;
1014 else
1015 p += sprintf(p, "&#%u;", (unsigned char) ch);
1016 }
1017 *p = '\0';
1018 return out;
1019 }
1020 #endif
1021
1022 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1023 /*
1024 * Decode a base64 data stream as per rfc1521.
1025 * Note that the rfc states that non base64 chars are to be ignored.
1026 * Since the decode always results in a shorter size than the input,
1027 * it is OK to pass the input arg as an output arg.
1028 * Parameter: a pointer to a base64 encoded string.
1029 * Decoded data is stored in-place.
1030 */
decodeBase64(char * data)1031 static void decodeBase64(char *data)
1032 {
1033 decode_base64(data, NULL)[0] = '\0';
1034 }
1035 #endif
1036
1037 /*
1038 * Create a listen server socket on the designated port.
1039 */
openServer(void)1040 static int openServer(void)
1041 {
1042 unsigned n = bb_strtou(bind_addr_or_port, NULL, 10);
1043 if (!errno && n && n <= 0xffff)
1044 n = create_and_bind_stream_or_die(NULL, n);
1045 else
1046 n = create_and_bind_stream_or_die(bind_addr_or_port, 80);
1047 xlisten(n, 9);
1048 return n;
1049 }
1050
1051 /*
1052 * Log the connection closure and exit.
1053 */
1054 static void log_and_exit(void) NORETURN;
log_and_exit(void)1055 static void log_and_exit(void)
1056 {
1057 /* Paranoia. IE said to be buggy. It may send some extra data
1058 * or be confused by us just exiting without SHUT_WR. Oh well. */
1059 shutdown(1, SHUT_WR);
1060 /* Why??
1061 (this also messes up stdin when user runs httpd -i from terminal)
1062 ndelay_on(0);
1063 while (read(STDIN_FILENO, iobuf, IOBUF_SIZE) > 0)
1064 continue;
1065 */
1066
1067 if (verbose > 2)
1068 bb_simple_error_msg("closed");
1069 _exit(xfunc_error_retval);
1070 }
1071
1072 /*
1073 * Create and send HTTP response headers.
1074 * The arguments are combined and sent as one write operation. Note that
1075 * IE will puke big-time if the headers are not sent in one packet and the
1076 * second packet is delayed for any reason.
1077 * responseNum - the result code to send.
1078 */
send_headers(unsigned responseNum)1079 static void send_headers(unsigned responseNum)
1080 {
1081 #if ENABLE_FEATURE_HTTPD_DATE || ENABLE_FEATURE_HTTPD_LAST_MODIFIED
1082 static const char RFC1123FMT[] ALIGN1 = "%a, %d %b %Y %H:%M:%S GMT";
1083 /* Fixed size 29-byte string. Example: Sun, 06 Nov 1994 08:49:37 GMT */
1084 char date_str[40]; /* using a bit larger buffer to paranoia reasons */
1085 struct tm tm;
1086 #endif
1087 const char *responseString = "";
1088 const char *infoString = NULL;
1089 #if ENABLE_FEATURE_HTTPD_ERROR_PAGES
1090 const char *error_page = NULL;
1091 #endif
1092 unsigned len;
1093 unsigned i;
1094
1095 for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
1096 if (http_response_type[i] == responseNum) {
1097 responseString = http_response[i].name;
1098 infoString = http_response[i].info;
1099 #if ENABLE_FEATURE_HTTPD_ERROR_PAGES
1100 error_page = http_error_page[i];
1101 #endif
1102 break;
1103 }
1104 }
1105
1106 if (verbose)
1107 bb_error_msg("response:%u", responseNum);
1108
1109 /* We use sprintf, not snprintf (it's less code).
1110 * iobuf[] is several kbytes long and all headers we generate
1111 * always fit into those kbytes.
1112 */
1113
1114 {
1115 #if ENABLE_FEATURE_HTTPD_DATE
1116 time_t timer = time(NULL);
1117 strftime(date_str, sizeof(date_str), RFC1123FMT, gmtime_r(&timer, &tm));
1118 /* ^^^ using gmtime_r() instead of gmtime() to not use static data */
1119 #endif
1120 len = sprintf(iobuf,
1121 "HTTP/1.1 %u %s\r\n"
1122 #if ENABLE_FEATURE_HTTPD_DATE
1123 "Date: %s\r\n"
1124 #endif
1125 "Connection: close\r\n",
1126 responseNum, responseString
1127 #if ENABLE_FEATURE_HTTPD_DATE
1128 , date_str
1129 #endif
1130 );
1131 }
1132
1133 if (responseNum != HTTP_OK || found_mime_type) {
1134 len += sprintf(iobuf + len,
1135 "Content-type: %s\r\n",
1136 /* if it's error message, then it's HTML */
1137 (responseNum != HTTP_OK ? "text/html" : found_mime_type)
1138 );
1139 }
1140
1141 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1142 if (responseNum == HTTP_UNAUTHORIZED) {
1143 len += sprintf(iobuf + len,
1144 "WWW-Authenticate: Basic realm=\"%.999s\"\r\n",
1145 g_realm /* %.999s protects from overflowing iobuf[] */
1146 );
1147 }
1148 #endif
1149 if (responseNum == HTTP_MOVED_TEMPORARILY) {
1150 /* Responding to "GET /dir" with
1151 * "HTTP/1.1 302 Found" "Location: /dir/"
1152 * - IOW, asking them to repeat with a slash.
1153 * Here, overflow IS possible, can't use sprintf:
1154 * mkdir test
1155 * python -c 'print("get /test?" + ("x" * 8192))' | busybox httpd -i -h .
1156 */
1157 len += snprintf(iobuf + len, IOBUF_SIZE-3 - len,
1158 "Location: %s/%s%s\r\n",
1159 found_moved_temporarily,
1160 (g_query ? "?" : ""),
1161 (g_query ? g_query : "")
1162 );
1163 if (len > IOBUF_SIZE-3)
1164 len = IOBUF_SIZE-3;
1165 }
1166
1167 #if ENABLE_FEATURE_HTTPD_ERROR_PAGES
1168 if (error_page && access(error_page, R_OK) == 0) {
1169 iobuf[len++] = '\r';
1170 iobuf[len++] = '\n';
1171 if (DEBUG) {
1172 iobuf[len] = '\0';
1173 fprintf(stderr, "headers: '%s'\n", iobuf);
1174 }
1175 full_write(STDOUT_FILENO, iobuf, len);
1176 dbg("writing error page: '%s'\n", error_page);
1177 return send_file_and_exit(error_page, SEND_BODY);
1178 }
1179 #endif
1180
1181 if (file_size != -1) { /* file */
1182 #if ENABLE_FEATURE_HTTPD_LAST_MODIFIED
1183 strftime(date_str, sizeof(date_str), RFC1123FMT, gmtime_r(&last_mod, &tm));
1184 #endif
1185 #if ENABLE_FEATURE_HTTPD_RANGES
1186 if (responseNum == HTTP_PARTIAL_CONTENT) {
1187 len += sprintf(iobuf + len,
1188 "Content-Range: bytes %"OFF_FMT"u-%"OFF_FMT"u/%"OFF_FMT"u\r\n",
1189 range_start,
1190 range_end,
1191 file_size
1192 );
1193 file_size = range_end - range_start + 1;
1194 }
1195 #endif
1196
1197 //RFC 2616 4.4 Message Length
1198 // The transfer-length of a message is the length of the message-body as
1199 // it appears in the message; that is, after any transfer-codings have
1200 // been applied. When a message-body is included with a message, the
1201 // transfer-length of that body is determined by one of the following
1202 // (in order of precedence):
1203 // 1.Any response message which "MUST NOT" include a message-body (such
1204 // as the 1xx, 204, and 304 responses and any response to a HEAD
1205 // request) is always terminated by the first empty line after the
1206 // header fields, regardless of the entity-header fields present in
1207 // the message.
1208 // 2.If a Transfer-Encoding header field (section 14.41) is present and
1209 // has any value other than "identity", then the transfer-length is
1210 // defined by use of the "chunked" transfer-coding (section 3.6),
1211 // unless the message is terminated by closing the connection.
1212 // 3.If a Content-Length header field (section 14.13) is present, its
1213 // decimal value in OCTETs represents both the entity-length and the
1214 // transfer-length. The Content-Length header field MUST NOT be sent
1215 // if these two lengths are different (i.e., if a Transfer-Encoding
1216 // header field is present). If a message is received with both a
1217 // Transfer-Encoding header field and a Content-Length header field,
1218 // the latter MUST be ignored.
1219 // 4.If the message uses the media type "multipart/byteranges" ...
1220 // 5.By the server closing the connection.
1221 //
1222 // (NB: standards do not define "Transfer-Length:" _header_,
1223 // transfer-length above is just a concept).
1224
1225 len += sprintf(iobuf + len,
1226 #if ENABLE_FEATURE_HTTPD_RANGES
1227 "Accept-Ranges: bytes\r\n"
1228 #endif
1229 #if ENABLE_FEATURE_HTTPD_LAST_MODIFIED
1230 "Last-Modified: %s\r\n"
1231 #endif
1232 #if ENABLE_FEATURE_HTTPD_ETAG
1233 "ETag: %s\r\n"
1234 #endif
1235
1236 /* Because of 4.4 (5), we can forgo sending of "Content-Length"
1237 * since we close connection afterwards, but it helps clients
1238 * to e.g. estimate download times, show progress bars etc.
1239 * Theoretically we should not send it if page is compressed,
1240 * but de-facto standard is to send it (see comment below).
1241 */
1242 "Content-Length: %"OFF_FMT"u\r\n",
1243 #if ENABLE_FEATURE_HTTPD_LAST_MODIFIED
1244 date_str,
1245 #endif
1246 #if ENABLE_FEATURE_HTTPD_ETAG
1247 G.etag,
1248 #endif
1249 file_size
1250 );
1251 }
1252
1253 /* This should be "Transfer-Encoding", not "Content-Encoding":
1254 * "data is compressed for transfer", not "data is an archive".
1255 * But many clients were not handling "Transfer-Encoding" correctly
1256 * (they were not uncompressing gzipped pages, tried to show
1257 * raw compressed data), and servers worked around it by using
1258 * "Content-Encoding" instead... and this become de-facto standard.
1259 * https://bugzilla.mozilla.org/show_bug.cgi?id=68517
1260 * https://bugs.chromium.org/p/chromium/issues/detail?id=94730
1261 */
1262 if (content_gzip)
1263 len += sprintf(iobuf + len, "Content-Encoding: gzip\r\n");
1264
1265 iobuf[len++] = '\r';
1266 iobuf[len++] = '\n';
1267 if (infoString) {
1268 len += sprintf(iobuf + len,
1269 "<HTML><HEAD><TITLE>%u %s</TITLE></HEAD>\n"
1270 "<BODY><H1>%u %s</H1>\n"
1271 "%s\n"
1272 "</BODY></HTML>\n",
1273 responseNum, responseString,
1274 responseNum, responseString,
1275 infoString
1276 );
1277 }
1278 if (DEBUG) {
1279 iobuf[len] = '\0';
1280 fprintf(stderr, "headers: '%s'\n", iobuf);
1281 }
1282 if (full_write(STDOUT_FILENO, iobuf, len) != len) {
1283 if (verbose > 1)
1284 bb_simple_perror_msg("error");
1285 log_and_exit();
1286 }
1287 }
1288
1289 static void send_headers_and_exit(int responseNum) NORETURN;
send_headers_and_exit(int responseNum)1290 static void send_headers_and_exit(int responseNum)
1291 {
1292 IF_FEATURE_HTTPD_GZIP(content_gzip = 0;)
1293 file_size = -1; /* no Last-Modified:, ETag:, Content-Length: */
1294 send_headers(responseNum);
1295 log_and_exit();
1296 }
1297
1298 /*
1299 * Read from the socket until '\n' or EOF.
1300 * '\r' chars are removed.
1301 * '\n' is replaced with NUL.
1302 * Return number of characters read or 0 if nothing is read
1303 * ('\r' and '\n' are not counted).
1304 * Data is returned in iobuf.
1305 */
get_line(void)1306 static unsigned get_line(void)
1307 {
1308 unsigned count;
1309 char c;
1310
1311 count = 0;
1312 while (1) {
1313 if (hdr_cnt <= 0) {
1314 alarm(HEADER_READ_TIMEOUT);
1315 hdr_cnt = safe_read(STDIN_FILENO, hdr_buf, sizeof_hdr_buf);
1316 if (hdr_cnt <= 0)
1317 goto ret;
1318 hdr_ptr = hdr_buf;
1319 }
1320 hdr_cnt--;
1321 c = *hdr_ptr++;
1322 if (c == '\r')
1323 continue;
1324 if (c == '\n')
1325 break;
1326 iobuf[count] = c;
1327 if (count < (IOBUF_SIZE - 1)) /* check overflow */
1328 count++;
1329 }
1330 ret:
1331 iobuf[count] = '\0';
1332 return count;
1333 }
1334
1335 #if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY
1336
1337 /* gcc 4.2.1 fares better with NOINLINE */
1338 static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) NORETURN;
cgi_io_loop_and_exit(int fromCgi_rd,int toCgi_wr,int post_len)1339 static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len)
1340 {
1341 enum { FROM_CGI = 1, TO_CGI = 2 }; /* indexes in pfd[] */
1342 struct pollfd pfd[3];
1343 int out_cnt; /* we buffer a bit of initial CGI output */
1344 int count;
1345
1346 /* iobuf is used for CGI -> network data,
1347 * hdr_buf is for network -> CGI data (POSTDATA) */
1348
1349 /* If CGI dies, we still want to correctly finish reading its output
1350 * and send it to the peer. So please no SIGPIPEs! */
1351 signal(SIGPIPE, SIG_IGN);
1352
1353 // We inconsistently handle a case when more POSTDATA from network
1354 // is coming than we expected. We may give *some part* of that
1355 // extra data to CGI.
1356
1357 //if (hdr_cnt > post_len) {
1358 // /* We got more POSTDATA from network than we expected */
1359 // hdr_cnt = post_len;
1360 //}
1361 post_len -= hdr_cnt;
1362 /* post_len - number of POST bytes not yet read from network */
1363
1364 /* NB: breaking out of this loop jumps to log_and_exit() */
1365 out_cnt = 0;
1366 pfd[FROM_CGI].fd = fromCgi_rd;
1367 pfd[FROM_CGI].events = POLLIN;
1368 pfd[TO_CGI].fd = toCgi_wr;
1369 while (1) {
1370 /* Note: even pfd[0].events == 0 won't prevent
1371 * revents == POLLHUP|POLLERR reports from closed stdin.
1372 * Setting fd to -1 works: */
1373 pfd[0].fd = -1;
1374 pfd[0].events = POLLIN;
1375 pfd[0].revents = 0; /* probably not needed, paranoia */
1376
1377 /* We always poll this fd, thus kernel always sets revents: */
1378 /*pfd[FROM_CGI].events = POLLIN; - moved out of loop */
1379 /*pfd[FROM_CGI].revents = 0; - not needed */
1380
1381 /* gcc-4.8.0 still doesnt fill two shorts with one insn :( */
1382 /* http://gcc.gnu.org/bugzilla/show_bug.cgi?id=47059 */
1383 /* hopefully one day it will... */
1384 pfd[TO_CGI].events = POLLOUT;
1385 pfd[TO_CGI].revents = 0; /* needed! */
1386
1387 if (toCgi_wr && hdr_cnt <= 0) {
1388 if (post_len > 0) {
1389 /* Expect more POST data from network */
1390 pfd[0].fd = 0;
1391 } else {
1392 /* post_len <= 0 && hdr_cnt <= 0:
1393 * no more POST data to CGI,
1394 * let CGI see EOF on CGI's stdin */
1395 if (toCgi_wr != fromCgi_rd)
1396 close(toCgi_wr);
1397 toCgi_wr = 0;
1398 }
1399 }
1400
1401 /* Now wait on the set of sockets */
1402 count = safe_poll(pfd, hdr_cnt > 0 ? TO_CGI+1 : FROM_CGI+1, -1);
1403 if (count <= 0) {
1404 #if 0
1405 if (safe_waitpid(pid, &status, WNOHANG) <= 0) {
1406 /* Weird. CGI didn't exit and no fd's
1407 * are ready, yet poll returned?! */
1408 continue;
1409 }
1410 if (DEBUG && WIFEXITED(status))
1411 bb_error_msg("CGI exited, status=%u", WEXITSTATUS(status));
1412 if (DEBUG && WIFSIGNALED(status))
1413 bb_error_msg("CGI killed, signal=%u", WTERMSIG(status));
1414 #endif
1415 break;
1416 }
1417
1418 if (pfd[TO_CGI].revents) {
1419 /* hdr_cnt > 0 here due to the way poll() called */
1420 /* Have data from peer and can write to CGI */
1421 count = safe_write(toCgi_wr, hdr_ptr, hdr_cnt);
1422 /* Doesn't happen, we dont use nonblocking IO here
1423 *if (count < 0 && errno == EAGAIN) {
1424 * ...
1425 *} else */
1426 if (count > 0) {
1427 hdr_ptr += count;
1428 hdr_cnt -= count;
1429 } else {
1430 /* EOF/broken pipe to CGI, stop piping POST data */
1431 hdr_cnt = post_len = 0;
1432 }
1433 }
1434
1435 if (pfd[0].revents) {
1436 /* post_len > 0 && hdr_cnt == 0 here */
1437 /* We expect data, prev data portion is eaten by CGI
1438 * and there *is* data to read from the peer
1439 * (POSTDATA) */
1440 //count = post_len > (int)sizeof_hdr_buf ? (int)sizeof_hdr_buf : post_len;
1441 //count = safe_read(STDIN_FILENO, hdr_buf, count);
1442 count = safe_read(STDIN_FILENO, hdr_buf, sizeof_hdr_buf);
1443 if (count > 0) {
1444 hdr_cnt = count;
1445 hdr_ptr = hdr_buf;
1446 post_len -= count;
1447 } else {
1448 /* no more POST data can be read */
1449 post_len = 0;
1450 }
1451 }
1452
1453 if (pfd[FROM_CGI].revents) {
1454 /* There is something to read from CGI */
1455 char *rbuf = iobuf;
1456
1457 /* Are we still buffering CGI output? */
1458 if (out_cnt >= 0) {
1459 /* HTTP_200[] has single "\r\n" at the end.
1460 * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html,
1461 * CGI scripts MUST send their own header terminated by
1462 * empty line, then data. That's why we have only one
1463 * <cr><lf> pair here. We will output "200 OK" line
1464 * if needed, but CGI still has to provide blank line
1465 * between header and body */
1466
1467 /* Must use safe_read, not full_read, because
1468 * CGI may output a few first bytes and then wait
1469 * for POSTDATA without closing stdout.
1470 * With full_read we may wait here forever. */
1471 count = safe_read(fromCgi_rd, rbuf + out_cnt, IOBUF_SIZE - 8);
1472 if (count <= 0) {
1473 /* eof (or error) and there was no "HTTP",
1474 * send "HTTP/1.1 200 OK\r\n", then send received data */
1475 if (out_cnt) {
1476 full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1);
1477 full_write(STDOUT_FILENO, rbuf, out_cnt);
1478 }
1479 break; /* CGI stdout is closed, exiting */
1480 }
1481 out_cnt += count;
1482 count = 0;
1483 /* "Status" header format is: "Status: 302 Redirected\r\n" */
1484 if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) {
1485 /* send "HTTP/1.1 " */
1486 if (full_write(STDOUT_FILENO, HTTP_200, 9) != 9)
1487 break;
1488 /* skip "Status: " (including space, sending "HTTP/1.1 NNN" is wrong) */
1489 rbuf += 8;
1490 count = out_cnt - 8;
1491 out_cnt = -1; /* buffering off */
1492 } else if (out_cnt >= 4) {
1493 /* Did CGI add "HTTP"? */
1494 if (memcmp(rbuf, HTTP_200, 4) != 0) {
1495 /* there is no "HTTP", do it ourself */
1496 if (full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1)
1497 break;
1498 }
1499 /* Commented out:
1500 if (!strstr(rbuf, "ontent-")) {
1501 full_write(s, "Content-type: text/plain\r\n\r\n", 28);
1502 }
1503 * Counter-example of valid CGI without Content-type:
1504 * echo -en "HTTP/1.1 302 Found\r\n"
1505 * echo -en "Location: http://www.busybox.net\r\n"
1506 * echo -en "\r\n"
1507 */
1508 count = out_cnt;
1509 out_cnt = -1; /* buffering off */
1510 }
1511 } else {
1512 count = safe_read(fromCgi_rd, rbuf, IOBUF_SIZE);
1513 if (count <= 0)
1514 break; /* eof (or error) */
1515 }
1516 if (full_write(STDOUT_FILENO, rbuf, count) != count)
1517 break;
1518 dbg("cgi read %d bytes: '%.*s'\n", count, count, rbuf);
1519 } /* if (pfd[FROM_CGI].revents) */
1520 } /* while (1) */
1521 log_and_exit();
1522 }
1523 #endif
1524
1525 #if ENABLE_FEATURE_HTTPD_CGI
1526
setenv1(const char * name,const char * value)1527 static void setenv1(const char *name, const char *value)
1528 {
1529 setenv(name, value ? value : "", 1);
1530 }
1531
1532 /*
1533 * Spawn CGI script, forward CGI's stdin/out <=> network
1534 *
1535 * Environment variables are set up and the script is invoked with pipes
1536 * for stdin/stdout. If a POST is being done the script is fed the POST
1537 * data in addition to setting the QUERY_STRING variable (for GETs or POSTs).
1538 *
1539 * Parameters:
1540 * const char *url The requested URL (with leading /).
1541 * const char *orig_uri The original URI before rewriting (if any)
1542 * int post_len Length of the POST body.
1543 */
1544 static void send_cgi_and_exit(
1545 const char *url,
1546 const char *orig_uri,
1547 const char *request,
1548 int post_len) NORETURN;
send_cgi_and_exit(const char * url,const char * orig_uri,const char * request,int post_len)1549 static void send_cgi_and_exit(
1550 const char *url,
1551 const char *orig_uri,
1552 const char *request,
1553 int post_len)
1554 {
1555 struct fd_pair fromCgi; /* CGI -> httpd pipe */
1556 struct fd_pair toCgi; /* httpd -> CGI pipe */
1557 char *script, *last_slash;
1558 int pid;
1559
1560 /* Make a copy. NB: caller guarantees:
1561 * url[0] == '/', url[1] != '/' */
1562 url = xstrdup(url);
1563
1564 /*
1565 * We are mucking with environment _first_ and then vfork/exec,
1566 * this allows us to use vfork safely. Parent doesn't care about
1567 * these environment changes anyway.
1568 */
1569
1570 /* Check for [dirs/]script.cgi/PATH_INFO */
1571 last_slash = script = (char*)url;
1572 while ((script = strchr(script + 1, '/')) != NULL) {
1573 int dir;
1574 *script = '\0';
1575 dir = is_directory(url + 1, /*followlinks:*/ 1);
1576 *script = '/';
1577 if (!dir) {
1578 /* not directory, found script.cgi/PATH_INFO */
1579 break;
1580 }
1581 /* is directory, find next '/' */
1582 last_slash = script;
1583 }
1584 setenv1("PATH_INFO", script); /* set to /PATH_INFO or "" */
1585 setenv1("REQUEST_METHOD", request);
1586 if (g_query) {
1587 putenv(xasprintf("%s=%s?%s", "REQUEST_URI", orig_uri, g_query));
1588 } else {
1589 setenv1("REQUEST_URI", orig_uri);
1590 }
1591 if (script != NULL)
1592 *script = '\0'; /* cut off /PATH_INFO */
1593
1594 /* SCRIPT_FILENAME is required by PHP in CGI mode */
1595 if (home_httpd[0] == '/') {
1596 char *fullpath = concat_path_file(home_httpd, url);
1597 setenv1("SCRIPT_FILENAME", fullpath);
1598 }
1599 /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */
1600 setenv1("SCRIPT_NAME", url);
1601 /* http://hoohoo.ncsa.uiuc.edu/cgi/env.html:
1602 * QUERY_STRING: The information which follows the ? in the URL
1603 * which referenced this script. This is the query information.
1604 * It should not be decoded in any fashion. This variable
1605 * should always be set when there is query information,
1606 * regardless of command line decoding. */
1607 /* (Older versions of bbox seem to do some decoding) */
1608 setenv1("QUERY_STRING", g_query);
1609 putenv((char*)"SERVER_SOFTWARE=busybox httpd/"BB_VER);
1610 putenv((char*)"SERVER_PROTOCOL=HTTP/1.1");
1611 putenv((char*)"GATEWAY_INTERFACE=CGI/1.1");
1612 /* Having _separate_ variables for IP and port defeats
1613 * the purpose of having socket abstraction. Which "port"
1614 * are you using on Unix domain socket?
1615 * IOW - REMOTE_PEER="1.2.3.4:56" makes much more sense.
1616 * Oh well... */
1617 {
1618 char *p = rmt_ip_str ? rmt_ip_str : (char*)"";
1619 char *cp = strrchr(p, ':');
1620 if (ENABLE_FEATURE_IPV6 && cp && strchr(cp, ']'))
1621 cp = NULL;
1622 if (cp) *cp = '\0'; /* delete :PORT */
1623 setenv1("REMOTE_ADDR", p);
1624 if (cp) {
1625 *cp = ':';
1626 #if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
1627 setenv1("REMOTE_PORT", cp + 1);
1628 #endif
1629 }
1630 }
1631 if (post_len)
1632 putenv(xasprintf("CONTENT_LENGTH=%u", post_len));
1633 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1634 if (remoteuser) {
1635 setenv1("REMOTE_USER", remoteuser);
1636 putenv((char*)"AUTH_TYPE=Basic");
1637 }
1638 #endif
1639 /* setenv1("SERVER_NAME", safe_gethostname()); - don't do this,
1640 * just run "env SERVER_NAME=xyz httpd ..." instead */
1641
1642 xpiped_pair(fromCgi);
1643 xpiped_pair(toCgi);
1644
1645 pid = vfork();
1646 if (pid < 0) {
1647 /* TODO: log perror? */
1648 log_and_exit();
1649 }
1650
1651 if (pid == 0) {
1652 /* Child process */
1653 char *argv[3];
1654
1655 xfunc_error_retval = 242;
1656
1657 /* NB: close _first_, then move fds! */
1658 close(toCgi.wr);
1659 close(fromCgi.rd);
1660 xmove_fd(toCgi.rd, 0); /* replace stdin with the pipe */
1661 xmove_fd(fromCgi.wr, 1); /* replace stdout with the pipe */
1662 /* User seeing stderr output can be a security problem.
1663 * If CGI really wants that, it can always do dup itself. */
1664 /* dup2(1, 2); */
1665
1666 /* Chdiring to script's dir */
1667 script = last_slash;
1668 if (script != url) { /* paranoia */
1669 *script = '\0';
1670 if (chdir(url + 1) != 0) {
1671 bb_perror_msg("can't change directory to '%s'", url + 1);
1672 goto error_execing_cgi;
1673 }
1674 // not needed: *script = '/';
1675 }
1676 script++;
1677
1678 /* set argv[0] to name without path */
1679 argv[0] = script;
1680 argv[1] = NULL;
1681
1682 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
1683 {
1684 char *suffix = strrchr(script, '.');
1685
1686 if (suffix) {
1687 Htaccess *cur;
1688 for (cur = script_i; cur; cur = cur->next) {
1689 if (strcmp(cur->before_colon + 1, suffix) == 0) {
1690 /* found interpreter name */
1691 argv[0] = cur->after_colon;
1692 argv[1] = script;
1693 argv[2] = NULL;
1694 break;
1695 }
1696 }
1697 }
1698 }
1699 #endif
1700 /* restore default signal dispositions for CGI process */
1701 bb_signals(0
1702 | (1 << SIGCHLD)
1703 | (1 << SIGPIPE)
1704 | (1 << SIGHUP)
1705 , SIG_DFL);
1706
1707 /* _NOT_ execvp. We do not search PATH. argv[0] is a filename
1708 * without any dir components and will only match a file
1709 * in the current directory */
1710 execv(argv[0], argv);
1711 if (verbose)
1712 bb_perror_msg("can't execute '%s'", argv[0]);
1713 error_execing_cgi:
1714 /* send to stdout
1715 * (we are CGI here, our stdout is pumped to the net) */
1716 send_headers_and_exit(HTTP_NOT_FOUND);
1717 } /* end child */
1718
1719 /* Parent process */
1720
1721 /* Restore variables possibly changed by child */
1722 xfunc_error_retval = 0;
1723
1724 /* Pump data */
1725 close(fromCgi.wr);
1726 close(toCgi.rd);
1727 cgi_io_loop_and_exit(fromCgi.rd, toCgi.wr, post_len);
1728 }
1729
1730 #endif /* FEATURE_HTTPD_CGI */
1731
1732 /*
1733 * Send a file response to a HTTP request, and exit
1734 *
1735 * Parameters:
1736 * const char *url The requested URL (with leading /).
1737 * what What to send (headers/body/both).
1738 */
send_file_and_exit(const char * url,int what)1739 static NOINLINE void send_file_and_exit(const char *url, int what)
1740 {
1741 char *suffix;
1742 int fd;
1743 ssize_t count;
1744
1745 if (content_gzip) {
1746 /* does <url>.gz exist? Then use it instead */
1747 char *gzurl = xasprintf("%s.gz", url);
1748 fd = open(gzurl, O_RDONLY);
1749 free(gzurl);
1750 if (fd != -1) {
1751 struct stat sb;
1752 fstat(fd, &sb);
1753 file_size = sb.st_size;
1754 last_mod = sb.st_mtime;
1755 } else {
1756 IF_FEATURE_HTTPD_GZIP(content_gzip = 0;)
1757 fd = open(url, O_RDONLY);
1758 }
1759 } else {
1760 fd = open(url, O_RDONLY);
1761 /* file_size and last_mod are already populated */
1762 }
1763 if (fd < 0) {
1764 dbg("can't open '%s'\n", url);
1765 /* Error pages are sent by using send_file_and_exit(SEND_BODY).
1766 * IOW: it is unsafe to call send_headers_and_exit
1767 * if what is SEND_BODY! Can recurse! */
1768 if (what != SEND_BODY)
1769 send_headers_and_exit(HTTP_NOT_FOUND);
1770 log_and_exit();
1771 }
1772 #if ENABLE_FEATURE_HTTPD_ETAG
1773 /* ETag is "hex(last_mod)-hex(file_size)" e.g. "5e132e20-417" */
1774 sprintf(G.etag, "\"%llx-%llx\"", (unsigned long long)last_mod, (unsigned long long)file_size);
1775
1776 if (G.if_none_match) {
1777 dbg("If-None-Match:'%s' file's ETag:'%s'\n", G.if_none_match, G.etag);
1778 /* Weak ETag comparision.
1779 * If-None-Match may have many ETags but they are quoted so we can use simple substring search */
1780 if (strstr(G.if_none_match, G.etag))
1781 send_headers_and_exit(HTTP_NOT_MODIFIED);
1782 }
1783 #endif
1784 /* If you want to know about EPIPE below
1785 * (happens if you abort downloads from local httpd): */
1786 signal(SIGPIPE, SIG_IGN);
1787
1788 /* If not found, default is to not send "Content-type:" */
1789 /*found_mime_type = NULL; - already is */
1790 suffix = strrchr(url, '.');
1791 if (suffix) {
1792 static const char suffixTable[] ALIGN1 =
1793 /* Shorter suffix must be first:
1794 * ".html.htm" will fail for ".htm"
1795 */
1796 ".txt.h.c.cc.cpp\0" "text/plain\0"
1797 /* .htm line must be after .h line */
1798 ".htm.html\0" "text/html\0"
1799 ".jpg.jpeg\0" "image/jpeg\0"
1800 ".gif\0" "image/gif\0"
1801 ".png\0" "image/png\0"
1802 ".svg\0" "image/svg+xml\0"
1803 /* .css line must be after .c line */
1804 ".css\0" "text/css\0"
1805 ".js\0" "application/javascript\0"
1806 ".wav\0" "audio/wav\0"
1807 ".avi\0" "video/x-msvideo\0"
1808 ".qt.mov\0" "video/quicktime\0"
1809 ".mpe.mpeg\0" "video/mpeg\0"
1810 ".mid.midi\0" "audio/midi\0"
1811 ".mp3\0" "audio/mpeg\0"
1812 #if 0 /* unpopular */
1813 ".au\0" "audio/basic\0"
1814 ".pac\0" "application/x-ns-proxy-autoconfig\0"
1815 ".vrml.wrl\0" "model/vrml\0"
1816 #endif
1817 /* compiler adds another "\0" here */
1818 ;
1819 Htaccess *cur;
1820
1821 /* Examine built-in table */
1822 const char *table = suffixTable;
1823 const char *table_next;
1824 for (; *table; table = table_next) {
1825 const char *try_suffix;
1826 const char *mime_type;
1827 mime_type = table + strlen(table) + 1;
1828 table_next = mime_type + strlen(mime_type) + 1;
1829 try_suffix = strstr(table, suffix);
1830 if (!try_suffix)
1831 continue;
1832 try_suffix += strlen(suffix);
1833 if (*try_suffix == '\0' || *try_suffix == '.') {
1834 found_mime_type = mime_type;
1835 break;
1836 }
1837 /* Example: strstr(table, ".av") != NULL, but it
1838 * does not match ".avi" after all and we end up here.
1839 * The table is arranged so that in this case we know
1840 * that it can't match anything in the following lines,
1841 * and we stop the search: */
1842 break;
1843 }
1844 /* ...then user's table */
1845 for (cur = mime_a; cur; cur = cur->next) {
1846 if (strcmp(cur->before_colon, suffix) == 0) {
1847 found_mime_type = cur->after_colon;
1848 break;
1849 }
1850 }
1851 }
1852
1853 dbg("sending file '%s' content-type:%s\n", url, found_mime_type);
1854
1855 #if ENABLE_FEATURE_HTTPD_RANGES
1856 if (what == SEND_BODY /* err pages and ranges don't mix */
1857 || content_gzip /* we are sending compressed page: can't do ranges */ ///why?
1858 ) {
1859 range_start = -1;
1860 }
1861 range_len = MAXINT(off_t);
1862 if (range_start >= 0) {
1863 if (!range_end || range_end > file_size - 1) {
1864 range_end = file_size - 1;
1865 }
1866 if (range_end < range_start
1867 || lseek(fd, range_start, SEEK_SET) != range_start
1868 ) {
1869 lseek(fd, 0, SEEK_SET);
1870 range_start = -1;
1871 } else {
1872 range_len = range_end - range_start + 1;
1873 send_headers(HTTP_PARTIAL_CONTENT);
1874 what = SEND_BODY;
1875 }
1876 }
1877 #endif
1878 if (what & SEND_HEADERS)
1879 send_headers(HTTP_OK);
1880 #if ENABLE_FEATURE_USE_SENDFILE
1881 {
1882 off_t offset;
1883 # if ENABLE_FEATURE_HTTPD_RANGES
1884 if (range_start < 0)
1885 range_start = 0;
1886 offset = range_start;
1887 # else
1888 offset = 0;
1889 # endif
1890 while (1) {
1891 /* sz is rounded down to 64k */
1892 ssize_t sz = MAXINT(ssize_t) - 0xffff;
1893 IF_FEATURE_HTTPD_RANGES(if (sz > range_len) sz = range_len;)
1894 count = sendfile(STDOUT_FILENO, fd, &offset, sz);
1895 if (count < 0) {
1896 if (offset == range_start) /* was it the very 1st sendfile? */
1897 break; /* fall back to read/write loop */
1898 goto fin;
1899 }
1900 IF_FEATURE_HTTPD_RANGES(range_len -= count;)
1901 if (count == 0 || range_len == 0)
1902 log_and_exit();
1903 }
1904 }
1905 #endif
1906 while ((count = safe_read(fd, iobuf, IOBUF_SIZE)) > 0) {
1907 ssize_t n;
1908 IF_FEATURE_HTTPD_RANGES(if (count > range_len) count = range_len;)
1909 n = full_write(STDOUT_FILENO, iobuf, count);
1910 if (count != n)
1911 break;
1912 IF_FEATURE_HTTPD_RANGES(range_len -= count;)
1913 if (range_len == 0)
1914 break;
1915 }
1916 if (count < 0) {
1917 IF_FEATURE_USE_SENDFILE(fin:)
1918 if (verbose > 1)
1919 bb_simple_perror_msg("error");
1920 }
1921 log_and_exit();
1922 }
1923
1924 #if ENABLE_FEATURE_HTTPD_ACL_IP
if_ip_denied_send_HTTP_FORBIDDEN_and_exit(unsigned remote_ip)1925 static void if_ip_denied_send_HTTP_FORBIDDEN_and_exit(unsigned remote_ip)
1926 {
1927 Htaccess_IP *cur;
1928
1929 for (cur = G.ip_a_d; cur; cur = cur->next) {
1930 dbg("checkPermIP: '%s' ? '%u.%u.%u.%u/%u.%u.%u.%u'\n",
1931 rmt_ip_str,
1932 (unsigned char)(cur->ip >> 24),
1933 (unsigned char)(cur->ip >> 16),
1934 (unsigned char)(cur->ip >> 8),
1935 (unsigned char)(cur->ip),
1936 (unsigned char)(cur->mask >> 24),
1937 (unsigned char)(cur->mask >> 16),
1938 (unsigned char)(cur->mask >> 8),
1939 (unsigned char)(cur->mask)
1940 );
1941 if ((remote_ip & cur->mask) == cur->ip) {
1942 if (cur->allow_deny == 'A')
1943 return;
1944 send_headers_and_exit(HTTP_FORBIDDEN);
1945 }
1946 }
1947
1948 if (flg_deny_all) /* depends on whether we saw "D:*" */
1949 send_headers_and_exit(HTTP_FORBIDDEN);
1950 }
1951 #else
1952 # define if_ip_denied_send_HTTP_FORBIDDEN_and_exit(arg) ((void)0)
1953 #endif
1954
1955 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1956
1957 # if ENABLE_PAM
1958 struct pam_userinfo {
1959 const char *name;
1960 const char *pw;
1961 };
1962
pam_talker(int num_msg,const struct pam_message ** msg,struct pam_response ** resp,void * appdata_ptr)1963 static int pam_talker(int num_msg,
1964 const struct pam_message **msg,
1965 struct pam_response **resp,
1966 void *appdata_ptr)
1967 {
1968 int i;
1969 struct pam_userinfo *userinfo = (struct pam_userinfo *) appdata_ptr;
1970 struct pam_response *response;
1971
1972 if (!resp || !msg || !userinfo)
1973 return PAM_CONV_ERR;
1974
1975 /* allocate memory to store response */
1976 response = xzalloc(num_msg * sizeof(*response));
1977
1978 /* copy values */
1979 for (i = 0; i < num_msg; i++) {
1980 const char *s;
1981
1982 switch (msg[i]->msg_style) {
1983 case PAM_PROMPT_ECHO_ON:
1984 s = userinfo->name;
1985 break;
1986 case PAM_PROMPT_ECHO_OFF:
1987 s = userinfo->pw;
1988 break;
1989 case PAM_ERROR_MSG:
1990 case PAM_TEXT_INFO:
1991 s = "";
1992 break;
1993 default:
1994 free(response);
1995 return PAM_CONV_ERR;
1996 }
1997 response[i].resp = xstrdup(s);
1998 if (PAM_SUCCESS != 0)
1999 response[i].resp_retcode = PAM_SUCCESS;
2000 }
2001 *resp = response;
2002 return PAM_SUCCESS;
2003 }
2004 # endif
2005
2006 /*
2007 * Config file entries are of the form "/<path>:<user>:<passwd>".
2008 * If config file has no prefix match for path, access is allowed.
2009 *
2010 * path The file path
2011 * user_and_passwd "user:passwd" to validate
2012 *
2013 * Returns 1 if user_and_passwd is OK.
2014 */
check_user_passwd(const char * path,char * user_and_passwd)2015 static int check_user_passwd(const char *path, char *user_and_passwd)
2016 {
2017 Htaccess *cur;
2018 const char *prev = NULL;
2019
2020 for (cur = g_auth; cur; cur = cur->next) {
2021 const char *dir_prefix;
2022 size_t len;
2023 int r;
2024
2025 dir_prefix = cur->before_colon;
2026
2027 /* WHY? */
2028 /* If already saw a match, don't accept other different matches */
2029 if (prev && strcmp(prev, dir_prefix) != 0)
2030 continue;
2031
2032 dbg("checkPerm: '%s' ? '%s'\n", dir_prefix, user_and_passwd);
2033
2034 /* If it's not a prefix match, continue searching */
2035 len = strlen(dir_prefix);
2036 if (len != 1 /* dir_prefix "/" matches all, don't need to check */
2037 && (strncmp(dir_prefix, path, len) != 0
2038 || (path[len] != '/' && path[len] != '\0')
2039 )
2040 ) {
2041 continue;
2042 }
2043
2044 /* Path match found */
2045 prev = dir_prefix;
2046
2047 if (ENABLE_FEATURE_HTTPD_AUTH_MD5) {
2048 char *colon_after_user;
2049 const char *passwd;
2050 # if ENABLE_FEATURE_SHADOWPASSWDS && !ENABLE_PAM
2051 char sp_buf[256];
2052 # endif
2053
2054 colon_after_user = strchr(user_and_passwd, ':');
2055 if (!colon_after_user)
2056 goto bad_input;
2057
2058 /* compare "user:" */
2059 if (cur->after_colon[0] != '*'
2060 && strncmp(cur->after_colon, user_and_passwd,
2061 colon_after_user - user_and_passwd + 1) != 0
2062 ) {
2063 continue;
2064 }
2065 /* this cfg entry is '*' or matches username from peer */
2066
2067 passwd = strchr(cur->after_colon, ':');
2068 if (!passwd)
2069 goto bad_input;
2070 passwd++;
2071 if (passwd[0] == '*') {
2072 # if ENABLE_PAM
2073 struct pam_userinfo userinfo;
2074 struct pam_conv conv_info = { &pam_talker, (void *) &userinfo };
2075 pam_handle_t *pamh;
2076
2077 *colon_after_user = '\0';
2078 userinfo.name = user_and_passwd;
2079 userinfo.pw = colon_after_user + 1;
2080 r = pam_start("httpd", user_and_passwd, &conv_info, &pamh) != PAM_SUCCESS;
2081 if (r == 0) {
2082 r = pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK) != PAM_SUCCESS
2083 || pam_acct_mgmt(pamh, PAM_DISALLOW_NULL_AUTHTOK) != PAM_SUCCESS
2084 ;
2085 pam_end(pamh, PAM_SUCCESS);
2086 }
2087 *colon_after_user = ':';
2088 goto end_check_passwd;
2089 # else
2090 # if ENABLE_FEATURE_SHADOWPASSWDS
2091 /* Using _r function to avoid pulling in static buffers */
2092 struct spwd spw;
2093 # endif
2094 struct passwd *pw;
2095
2096 *colon_after_user = '\0';
2097 pw = getpwnam(user_and_passwd);
2098 *colon_after_user = ':';
2099 if (!pw || !pw->pw_passwd)
2100 continue;
2101 passwd = pw->pw_passwd;
2102 # if ENABLE_FEATURE_SHADOWPASSWDS
2103 if ((passwd[0] == 'x' || passwd[0] == '*') && !passwd[1]) {
2104 /* getspnam_r may return 0 yet set result to NULL.
2105 * At least glibc 2.4 does this. Be extra paranoid here. */
2106 struct spwd *result = NULL;
2107 r = getspnam_r(pw->pw_name, &spw, sp_buf, sizeof(sp_buf), &result);
2108 if (r == 0 && result)
2109 passwd = result->sp_pwdp;
2110 }
2111 # endif
2112 /* In this case, passwd is ALWAYS encrypted:
2113 * it came from /etc/passwd or /etc/shadow!
2114 */
2115 goto check_encrypted;
2116 # endif /* ENABLE_PAM */
2117 }
2118 /* Else: passwd is from httpd.conf, it is either plaintext or encrypted */
2119
2120 if (passwd[0] == '$' && isdigit(passwd[1])) {
2121 char *encrypted;
2122 # if !ENABLE_PAM
2123 check_encrypted:
2124 # endif
2125 /* encrypt pwd from peer and check match with local one */
2126 encrypted = pw_encrypt(
2127 /* pwd (from peer): */ colon_after_user + 1,
2128 /* salt: */ passwd,
2129 /* cleanup: */ 0
2130 );
2131 r = strcmp(encrypted, passwd);
2132 free(encrypted);
2133 } else {
2134 /* local passwd is from httpd.conf and it's plaintext */
2135 r = strcmp(colon_after_user + 1, passwd);
2136 }
2137 goto end_check_passwd;
2138 }
2139 bad_input:
2140 /* Comparing plaintext "user:pass" in one go */
2141 r = strcmp(cur->after_colon, user_and_passwd);
2142 end_check_passwd:
2143 if (r == 0) {
2144 remoteuser = xstrndup(user_and_passwd,
2145 strchrnul(user_and_passwd, ':') - user_and_passwd
2146 );
2147 return 1; /* Ok */
2148 }
2149 } /* for */
2150
2151 /* 0(bad) if prev is set: matches were found but passwd was wrong */
2152 return (prev == NULL);
2153 }
2154 #endif /* FEATURE_HTTPD_BASIC_AUTH */
2155
2156 #if ENABLE_FEATURE_HTTPD_PROXY
find_proxy_entry(const char * url)2157 static Htaccess_Proxy *find_proxy_entry(const char *url)
2158 {
2159 Htaccess_Proxy *p;
2160 for (p = proxy; p; p = p->next) {
2161 if (is_prefixed_with(url, p->url_from))
2162 return p;
2163 }
2164 return NULL;
2165 }
2166 #endif
2167
2168 /*
2169 * Handle timeouts
2170 */
2171 static void send_REQUEST_TIMEOUT_and_exit(int sig) NORETURN;
send_REQUEST_TIMEOUT_and_exit(int sig UNUSED_PARAM)2172 static void send_REQUEST_TIMEOUT_and_exit(int sig UNUSED_PARAM)
2173 {
2174 send_headers_and_exit(HTTP_REQUEST_TIMEOUT);
2175 }
2176
2177 /*
2178 * Handle an incoming http request and exit.
2179 */
2180 static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) NORETURN;
handle_incoming_and_exit(const len_and_sockaddr * fromAddr)2181 static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
2182 {
2183 struct stat sb;
2184 char *urlcopy;
2185 char *urlp;
2186 char *tptr;
2187 #if ENABLE_FEATURE_HTTPD_ACL_IP
2188 unsigned remote_ip;
2189 #endif
2190 #if ENABLE_FEATURE_HTTPD_CGI
2191 unsigned total_headers_len;
2192 #endif
2193 const char *prequest;
2194 static const char request_GET[] ALIGN1 = "GET";
2195 static const char request_HEAD[] ALIGN1 = "HEAD";
2196 #if ENABLE_FEATURE_HTTPD_CGI
2197 static const char request_POST[] ALIGN1 = "POST";
2198 unsigned long POST_length;
2199 enum CGI_type {
2200 CGI_NONE = 0,
2201 CGI_NORMAL,
2202 CGI_INDEX,
2203 CGI_INTERPRETER,
2204 } cgi_type = CGI_NONE;
2205 #endif
2206 #if ENABLE_FEATURE_HTTPD_PROXY
2207 Htaccess_Proxy *proxy_entry;
2208 #endif
2209 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
2210 smallint authorized = -1;
2211 #endif
2212 char *HTTP_slash;
2213
2214 /* Allocation of iobuf is postponed until now
2215 * (IOW, server process doesn't need to waste 8k) */
2216 iobuf = xmalloc(IOBUF_SIZE);
2217
2218 if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) {
2219 /* NB: can be NULL (user runs httpd -i by hand?) */
2220 rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr->u.sa);
2221 }
2222 if (verbose) {
2223 /* this trick makes -v logging much simpler */
2224 if (rmt_ip_str)
2225 applet_name = rmt_ip_str;
2226 if (verbose > 2)
2227 bb_simple_error_msg("connected");
2228 }
2229 #if ENABLE_FEATURE_HTTPD_ACL_IP
2230 remote_ip = 0;
2231 if (fromAddr->u.sa.sa_family == AF_INET) {
2232 remote_ip = ntohl(fromAddr->u.sin.sin_addr.s_addr);
2233 }
2234 # if ENABLE_FEATURE_IPV6
2235 if (fromAddr->u.sa.sa_family == AF_INET6
2236 && fromAddr->u.sin6.sin6_addr.s6_addr32[0] == 0
2237 && fromAddr->u.sin6.sin6_addr.s6_addr32[1] == 0
2238 && ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[2]) == 0xffff)
2239 remote_ip = ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[3]);
2240 # endif
2241 if_ip_denied_send_HTTP_FORBIDDEN_and_exit(remote_ip);
2242 #endif
2243
2244 /* Install timeout handler. get_line() needs it. */
2245 signal(SIGALRM, send_REQUEST_TIMEOUT_and_exit);
2246
2247 if (!get_line()) { /* EOF or error or empty line */
2248 /* Observed Firefox to "speculatively" open
2249 * extra connections to a new site on first access,
2250 * they are closed in ~5 seconds with nothing
2251 * being sent at all.
2252 * (Presumably it's a method to decrease latency?)
2253 */
2254 if (verbose > 2)
2255 bb_simple_error_msg("eof on read, closing");
2256 /* Don't bother generating error page in this case,
2257 * just close the socket.
2258 */
2259 //send_headers_and_exit(HTTP_BAD_REQUEST);
2260 _exit(xfunc_error_retval);
2261 }
2262 dbg("Request:'%s'\n", iobuf);
2263
2264 /* Find URL */
2265 // rfc2616: method and URI is separated by exactly one space
2266 //urlp = strpbrk(iobuf, " \t"); - no, tab isn't allowed
2267 urlp = strchr(iobuf, ' ');
2268 if (urlp == NULL)
2269 send_headers_and_exit(HTTP_BAD_REQUEST);
2270 *urlp++ = '\0';
2271 //urlp = skip_whitespace(urlp); - should not be necessary
2272 if (urlp[0] != '/')
2273 send_headers_and_exit(HTTP_BAD_REQUEST);
2274 /* Find end of URL */
2275 HTTP_slash = strchr(urlp, ' ');
2276 /* Is it " HTTP/"? */
2277 if (!HTTP_slash || strncmp(HTTP_slash + 1, HTTP_200, 5) != 0)
2278 send_headers_and_exit(HTTP_BAD_REQUEST);
2279 *HTTP_slash++ = '\0';
2280
2281 #if ENABLE_FEATURE_HTTPD_PROXY
2282 proxy_entry = find_proxy_entry(urlp);
2283 if (proxy_entry) {
2284 int proxy_fd;
2285 len_and_sockaddr *lsa;
2286
2287 if (verbose > 1)
2288 bb_error_msg("proxy:%s", urlp);
2289 lsa = host2sockaddr(proxy_entry->host_port, 80);
2290 if (!lsa)
2291 send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
2292 proxy_fd = socket(lsa->u.sa.sa_family, SOCK_STREAM, 0);
2293 if (proxy_fd < 0)
2294 send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
2295 if (connect(proxy_fd, &lsa->u.sa, lsa->len) < 0)
2296 send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
2297 /* Disable peer header reading timeout */
2298 alarm(0);
2299 /* Config directive was of the form:
2300 * P:/url:[http://]hostname[:port]/new/path
2301 * When /urlSFX is requested, reverse proxy it
2302 * to http://hostname[:port]/new/pathSFX
2303 */
2304 fdprintf(proxy_fd, "%s %s%s %s\r\n",
2305 iobuf, /* "GET" / "POST" / etc */
2306 proxy_entry->url_to, /* "/new/path" */
2307 urlp + strlen(proxy_entry->url_from), /* "SFX" */
2308 HTTP_slash /* "HTTP/xyz" */
2309 );
2310 cgi_io_loop_and_exit(proxy_fd, proxy_fd, /*max POST length:*/ INT_MAX);
2311 }
2312 #endif
2313
2314 /* Determine type of request (GET/POST/...) */
2315 prequest = request_GET;
2316 if (strcasecmp(iobuf, prequest) == 0)
2317 goto found;
2318 prequest = request_HEAD;
2319 if (strcasecmp(iobuf, prequest) == 0)
2320 goto found;
2321 #if !ENABLE_FEATURE_HTTPD_CGI
2322 send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
2323 #else
2324 prequest = request_POST;
2325 if (strcasecmp(iobuf, prequest) == 0)
2326 goto found;
2327 /* For CGI, allow DELETE, PUT, OPTIONS, etc too */
2328 prequest = alloca(16);
2329 safe_strncpy((char*)prequest, iobuf, 16);
2330 #endif
2331 found:
2332 /* Copy URL to stack-allocated char[] */
2333 urlcopy = alloca((HTTP_slash - urlp) + 2 + strlen(index_page));
2334 strcpy(urlcopy, urlp);
2335 /* NB: urlcopy ptr is never changed after this */
2336
2337 /* Extract url args if present */
2338 g_query = strchr(urlcopy, '?');
2339 if (g_query)
2340 *g_query++ = '\0';
2341
2342 /* Decode URL escape sequences */
2343 tptr = percent_decode_in_place(urlcopy, /*strict:*/ 1);
2344 if (tptr == NULL)
2345 send_headers_and_exit(HTTP_BAD_REQUEST);
2346 if (tptr == urlcopy + 1) {
2347 /* '/' or NUL is encoded */
2348 send_headers_and_exit(HTTP_NOT_FOUND);
2349 }
2350
2351 /* Canonicalize path */
2352 /* Algorithm stolen from libbb bb_simplify_path(),
2353 * but don't strdup, retain trailing slash, protect root */
2354 urlp = tptr = urlcopy;
2355 while (1) {
2356 if (*urlp == '/') {
2357 /* skip duplicate (or initial) slash */
2358 if (*tptr == '/') {
2359 goto next_char;
2360 }
2361 if (*tptr == '.') {
2362 if (tptr[1] == '.' && (tptr[2] == '/' || tptr[2] == '\0')) {
2363 /* "..": be careful */
2364 /* protect root */
2365 if (urlp == urlcopy)
2366 send_headers_and_exit(HTTP_BAD_REQUEST);
2367 /* omit previous dir */
2368 while (*--urlp != '/')
2369 continue;
2370 /* skip to "./" or ".<NUL>" */
2371 tptr++;
2372 }
2373 if (tptr[1] == '/' || tptr[1] == '\0') {
2374 /* skip extra "/./" */
2375 goto next_char;
2376 }
2377 }
2378 }
2379 *++urlp = *tptr;
2380 if (*tptr == '\0')
2381 break;
2382 next_char:
2383 tptr++;
2384 }
2385
2386 /* Log it */
2387 if (verbose > 1)
2388 bb_error_msg("url:%s", urlcopy);
2389
2390 tptr = urlcopy;
2391 while ((tptr = strchr(tptr + 1, '/')) != NULL) {
2392 /* have path1/path2 */
2393 *tptr = '\0';
2394 /* may have subdir config */
2395 if (parse_conf(urlcopy + 1, SUBDIR_PARSE) == 0)
2396 if_ip_denied_send_HTTP_FORBIDDEN_and_exit(remote_ip);
2397 *tptr = '/';
2398 }
2399
2400 tptr = urlcopy + 1; /* skip first '/' */
2401
2402 #if ENABLE_FEATURE_HTTPD_CGI
2403 if (is_prefixed_with(tptr, "cgi-bin/")) {
2404 if (tptr[8] == '\0') {
2405 /* protect listing "cgi-bin/" */
2406 send_headers_and_exit(HTTP_FORBIDDEN);
2407 }
2408 cgi_type = CGI_NORMAL;
2409 }
2410 #endif
2411
2412 if (urlp[-1] == '/') {
2413 /* When index_page string is appended to <dir>/ URL, it overwrites
2414 * the query string. If we fall back to call /cgi-bin/index.cgi,
2415 * query string would be lost and not available to the CGI.
2416 * Work around it by making a deep copy.
2417 */
2418 if (ENABLE_FEATURE_HTTPD_CGI)
2419 g_query = xstrdup(g_query); /* ok for NULL too */
2420 strcpy(urlp, index_page);
2421 }
2422 if (stat(tptr, &sb) == 0) {
2423 /* If URL is a directory with no slash, set up
2424 * "HTTP/1.1 302 Found" "Location: /dir/" reply */
2425 if (urlp[-1] != '/' && S_ISDIR(sb.st_mode)) {
2426 found_moved_temporarily = urlcopy;
2427 } else {
2428 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
2429 char *suffix = strrchr(tptr, '.');
2430 if (suffix) {
2431 Htaccess *cur;
2432 for (cur = script_i; cur; cur = cur->next) {
2433 if (strcmp(cur->before_colon + 1, suffix) == 0) {
2434 cgi_type = CGI_INTERPRETER;
2435 break;
2436 }
2437 }
2438 }
2439 #endif
2440 file_size = sb.st_size;
2441 last_mod = sb.st_mtime;
2442 }
2443 }
2444 #if ENABLE_FEATURE_HTTPD_CGI
2445 else if (urlp[-1] == '/') {
2446 /* It's a dir URL and there is no index.html */
2447 /* Is there cgi-bin/index.cgi? */
2448 if (access("/cgi-bin/index.cgi"+1, X_OK) != 0)
2449 send_headers_and_exit(HTTP_NOT_FOUND); /* no */
2450 cgi_type = CGI_INDEX;
2451 }
2452 #endif
2453
2454 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH || ENABLE_FEATURE_HTTPD_CGI
2455 /* check_user_passwd() would be confused by added .../index.html, truncate it */
2456 urlp[0] = '\0';
2457 #endif
2458
2459 #if ENABLE_FEATURE_HTTPD_CGI
2460 total_headers_len = 0;
2461 POST_length = 0;
2462 #endif
2463
2464 /* Read until blank line */
2465 while (1) {
2466 unsigned iobuf_len = get_line();
2467 if (!iobuf_len)
2468 break; /* EOF or error or empty line */
2469 #if ENABLE_FEATURE_HTTPD_CGI
2470 /* Prevent unlimited growth of HTTP_xyz envvars */
2471 total_headers_len += iobuf_len;
2472 if (total_headers_len >= MAX_HTTP_HEADERS_SIZE)
2473 send_headers_and_exit(HTTP_ENTITY_TOO_LARGE);
2474 #endif
2475 dbg("header:'%s'\n", iobuf);
2476 #if ENABLE_FEATURE_HTTPD_CGI
2477 /* Only POST needs to know POST_length */
2478 if (prequest == request_POST && STRNCASECMP(iobuf, "Content-Length:") == 0) {
2479 tptr = skip_whitespace(iobuf + sizeof("Content-Length:") - 1);
2480 if (!tptr[0])
2481 send_headers_and_exit(HTTP_BAD_REQUEST);
2482 /* not using strtoul: it ignores leading minus! */
2483 POST_length = bb_strtou(tptr, NULL, 10);
2484 /* length is "ulong", but we need to pass it to int later */
2485 if (errno || POST_length > INT_MAX)
2486 send_headers_and_exit(HTTP_BAD_REQUEST);
2487 continue;
2488 }
2489 #endif
2490 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
2491 if (STRNCASECMP(iobuf, "Authorization:") == 0) {
2492 /* We only allow Basic credentials.
2493 * It shows up as "Authorization: Basic <user>:<passwd>" where
2494 * "<user>:<passwd>" is base64 encoded.
2495 */
2496 tptr = skip_whitespace(iobuf + sizeof("Authorization:")-1);
2497 if (STRNCASECMP(tptr, "Basic") == 0) {
2498 tptr += sizeof("Basic")-1;
2499 /* decodeBase64() skips whitespace itself */
2500 decodeBase64(tptr);
2501 authorized = check_user_passwd(urlcopy, tptr);
2502 continue;
2503 }
2504 }
2505 #endif
2506 #if ENABLE_FEATURE_HTTPD_RANGES
2507 if (STRNCASECMP(iobuf, "Range:") == 0) {
2508 /* We know only bytes=NNN-[MMM] */
2509 char *s = skip_whitespace(iobuf + sizeof("Range:")-1);
2510 s = is_prefixed_with(s, "bytes=");
2511 if (s) {
2512 range_start = BB_STRTOOFF(s, &s, 10);
2513 if (s[0] != '-' || range_start < 0) {
2514 range_start = -1;
2515 } else if (s[1]) {
2516 range_end = BB_STRTOOFF(s+1, NULL, 10);
2517 if (errno || range_end < range_start)
2518 range_start = -1;
2519 }
2520 }
2521 continue;
2522 }
2523 #endif
2524 #if ENABLE_FEATURE_HTTPD_GZIP
2525 if (STRNCASECMP(iobuf, "Accept-Encoding:") == 0) {
2526 /* Note: we do not support "gzip;q=0"
2527 * method of _disabling_ gzip
2528 * delivery. No one uses that, though */
2529 const char *s = strstr(iobuf, "gzip");
2530 if (s) {
2531 // want more thorough checks?
2532 //if (s[-1] == ' '
2533 // || s[-1] == ','
2534 // || s[-1] == ':'
2535 //) {
2536 content_gzip = 1;
2537 //}
2538 }
2539 continue;
2540 }
2541 #endif
2542 #if ENABLE_FEATURE_HTTPD_ETAG
2543 if (STRNCASECMP(iobuf, "If-None-Match:") == 0) {
2544 free(G.if_none_match);
2545 G.if_none_match = xstrdup(skip_whitespace(iobuf + sizeof("If-None-Match:") - 1));
2546 continue;
2547 }
2548 #endif
2549 #if ENABLE_FEATURE_HTTPD_CGI
2550 if (cgi_type != CGI_NONE) {
2551 bool ct = (STRNCASECMP(iobuf, "Content-Type:") == 0);
2552 char *cp;
2553 char *colon = strchr(iobuf, ':');
2554
2555 if (!colon)
2556 continue;
2557 cp = iobuf;
2558 while (cp < colon) {
2559 /* a-z => A-Z, not-alnum => _ */
2560 char c = (*cp & ~0x20); /* toupper for A-Za-z, undef for others */
2561 if ((unsigned)(c - 'A') <= ('Z' - 'A')) {
2562 *cp++ = c;
2563 continue;
2564 }
2565 if (!isdigit(*cp))
2566 *cp = '_';
2567 cp++;
2568 }
2569 /* "Content-Type:" gets no HTTP_ prefix, all others do */
2570 cp = xasprintf(ct ? "HTTP_%.*s=%s" + 5 : "HTTP_%.*s=%s",
2571 (int)(colon - iobuf), iobuf,
2572 skip_whitespace(colon + 1)
2573 );
2574 putenv(cp);
2575 }
2576 #endif
2577 } /* while extra header reading */
2578
2579 /* We are done reading headers, disable peer timeout */
2580 alarm(0);
2581
2582 if (strcmp(bb_basename(urlcopy), HTTPD_CONF) == 0) {
2583 /* protect listing [/path]/httpd.conf or IP deny */
2584 send_headers_and_exit(HTTP_FORBIDDEN);
2585 }
2586
2587 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
2588 /* Case: no "Authorization:" was seen, but page might require passwd.
2589 * Check that with dummy user:pass */
2590 if (authorized < 0)
2591 authorized = check_user_passwd(urlcopy, (char *) "");
2592 if (!authorized)
2593 send_headers_and_exit(HTTP_UNAUTHORIZED);
2594 #endif
2595
2596 if (found_moved_temporarily)
2597 send_headers_and_exit(HTTP_MOVED_TEMPORARILY);
2598
2599 #if ENABLE_FEATURE_HTTPD_CGI
2600 if (cgi_type != CGI_NONE) {
2601 send_cgi_and_exit(
2602 (cgi_type == CGI_INDEX) ? "/cgi-bin/index.cgi"
2603 /*CGI_NORMAL or CGI_INTERPRETER*/ : urlcopy,
2604 urlcopy, prequest, POST_length
2605 );
2606 }
2607 #endif
2608
2609 #if ENABLE_FEATURE_HTTPD_CGI
2610 if (prequest != request_GET && prequest != request_HEAD) {
2611 /* POST / DELETE / PUT / OPTIONS for files do not make sense */
2612 send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
2613 }
2614 #else
2615 /* !CGI: it can be only GET or HEAD */
2616 #endif
2617
2618 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
2619 /* Restore truncated .../index.html */
2620 if (urlp[-1] == '/')
2621 urlp[0] = index_page[0];
2622 #endif
2623 send_file_and_exit(urlcopy + 1,
2624 (prequest != request_HEAD ? (SEND_HEADERS + SEND_BODY) : SEND_HEADERS)
2625 );
2626 }
2627
2628 /*
2629 * The main http server function.
2630 * Given a socket, listen for new connections and farm out
2631 * the processing as a [v]forked process.
2632 * Never returns.
2633 */
2634 #if BB_MMU
2635 static void mini_httpd(int server_socket) NORETURN;
mini_httpd(int server_socket)2636 static void mini_httpd(int server_socket)
2637 {
2638 /* NB: it's best to not use xfuncs in this loop before fork().
2639 * Otherwise server may die on transient errors (temporary
2640 * out-of-memory condition, etc), which is Bad(tm).
2641 * Try to do any dangerous calls after fork.
2642 */
2643 while (1) {
2644 int n;
2645 len_and_sockaddr fromAddr;
2646
2647 /* Wait for connections... */
2648 fromAddr.len = LSA_SIZEOF_SA;
2649 n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len);
2650 if (n < 0)
2651 continue;
2652 //TODO: we can reject connects from denied IPs right away;
2653 //also, we might want to do one MSG_DONTWAIT'ed recv() here
2654 //to detect immediate EOF,
2655 //to avoid forking a whole new process for attackers
2656 //who open and close lots of connections.
2657 //(OTOH, the real mitigtion for this sort of thing is
2658 //to ratelimit connects in iptables)
2659
2660 /* set the KEEPALIVE option to cull dead connections */
2661 setsockopt_keepalive(n);
2662
2663 if (fork() == 0) {
2664 /* child */
2665 /* Do not reload config on HUP */
2666 signal(SIGHUP, SIG_IGN);
2667 close(server_socket);
2668 xmove_fd(n, 0);
2669 xdup2(0, 1);
2670
2671 handle_incoming_and_exit(&fromAddr);
2672 }
2673 /* parent, or fork failed */
2674 close(n);
2675 } /* while (1) */
2676 /* never reached */
2677 }
2678 #else
2679 static void mini_httpd_nommu(int server_socket, int argc, char **argv) NORETURN;
mini_httpd_nommu(int server_socket,int argc,char ** argv)2680 static void mini_httpd_nommu(int server_socket, int argc, char **argv)
2681 {
2682 char *argv_copy[argc + 2];
2683
2684 argv_copy[0] = argv[0];
2685 argv_copy[1] = (char*)"-i";
2686 memcpy(&argv_copy[2], &argv[1], argc * sizeof(argv[0]));
2687
2688 /* NB: it's best to not use xfuncs in this loop before vfork().
2689 * Otherwise server may die on transient errors (temporary
2690 * out-of-memory condition, etc), which is Bad(tm).
2691 * Try to do any dangerous calls after fork.
2692 */
2693 while (1) {
2694 int n;
2695
2696 /* Wait for connections... */
2697 n = accept(server_socket, NULL, NULL);
2698 if (n < 0)
2699 continue;
2700
2701 /* set the KEEPALIVE option to cull dead connections */
2702 setsockopt_keepalive(n);
2703
2704 if (vfork() == 0) {
2705 /* child */
2706 /* Do not reload config on HUP */
2707 signal(SIGHUP, SIG_IGN);
2708 close(server_socket);
2709 xmove_fd(n, 0);
2710 xdup2(0, 1);
2711
2712 /* Run a copy of ourself in inetd mode */
2713 re_exec(argv_copy);
2714 }
2715 argv_copy[0][0] &= 0x7f;
2716 /* parent, or vfork failed */
2717 close(n);
2718 } /* while (1) */
2719 /* never reached */
2720 }
2721 #endif
2722
2723 /*
2724 * Process a HTTP connection on stdin/out.
2725 * Never returns.
2726 */
2727 static void mini_httpd_inetd(void) NORETURN;
mini_httpd_inetd(void)2728 static void mini_httpd_inetd(void)
2729 {
2730 len_and_sockaddr fromAddr;
2731
2732 memset(&fromAddr, 0, sizeof(fromAddr));
2733 fromAddr.len = LSA_SIZEOF_SA;
2734 /* NB: can fail if user runs it by hand and types in http cmds */
2735 getpeername(0, &fromAddr.u.sa, &fromAddr.len);
2736 handle_incoming_and_exit(&fromAddr);
2737 }
2738
sighup_handler(int sig UNUSED_PARAM)2739 static void sighup_handler(int sig UNUSED_PARAM)
2740 {
2741 int sv = errno;
2742 parse_conf(DEFAULT_PATH_HTTPD_CONF, SIGNALED_PARSE);
2743 errno = sv;
2744 }
2745
2746 enum {
2747 c_opt_config_file = 0,
2748 d_opt_decode_url,
2749 h_opt_home_httpd,
2750 IF_FEATURE_HTTPD_ENCODE_URL_STR(e_opt_encode_url,)
2751 IF_FEATURE_HTTPD_BASIC_AUTH( r_opt_realm ,)
2752 IF_FEATURE_HTTPD_AUTH_MD5( m_opt_md5 ,)
2753 IF_FEATURE_HTTPD_SETUID( u_opt_setuid ,)
2754 p_opt_port ,
2755 p_opt_inetd ,
2756 p_opt_foreground,
2757 p_opt_verbose ,
2758 OPT_CONFIG_FILE = 1 << c_opt_config_file,
2759 OPT_DECODE_URL = 1 << d_opt_decode_url,
2760 OPT_HOME_HTTPD = 1 << h_opt_home_httpd,
2761 OPT_ENCODE_URL = IF_FEATURE_HTTPD_ENCODE_URL_STR((1 << e_opt_encode_url)) + 0,
2762 OPT_REALM = IF_FEATURE_HTTPD_BASIC_AUTH( (1 << r_opt_realm )) + 0,
2763 OPT_MD5 = IF_FEATURE_HTTPD_AUTH_MD5( (1 << m_opt_md5 )) + 0,
2764 OPT_SETUID = IF_FEATURE_HTTPD_SETUID( (1 << u_opt_setuid )) + 0,
2765 OPT_PORT = 1 << p_opt_port,
2766 OPT_INETD = 1 << p_opt_inetd,
2767 OPT_FOREGROUND = 1 << p_opt_foreground,
2768 OPT_VERBOSE = 1 << p_opt_verbose,
2769 };
2770
2771
2772 int httpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
httpd_main(int argc UNUSED_PARAM,char ** argv)2773 int httpd_main(int argc UNUSED_PARAM, char **argv)
2774 {
2775 int server_socket = server_socket; /* for gcc */
2776 unsigned opt;
2777 char *url_for_decode;
2778 IF_FEATURE_HTTPD_ENCODE_URL_STR(const char *url_for_encode;)
2779 IF_FEATURE_HTTPD_SETUID(const char *s_ugid = NULL;)
2780 IF_FEATURE_HTTPD_SETUID(struct bb_uidgid_t ugid;)
2781 IF_FEATURE_HTTPD_AUTH_MD5(const char *pass;)
2782
2783 INIT_G();
2784
2785 #if ENABLE_LOCALE_SUPPORT
2786 /* Undo busybox.c: we want to speak English in http (dates etc) */
2787 setlocale(LC_TIME, "C");
2788 #endif
2789
2790 home_httpd = xrealloc_getcwd_or_warn(NULL);
2791 /* We do not "absolutize" path given by -h (home) opt.
2792 * If user gives relative path in -h,
2793 * $SCRIPT_FILENAME will not be set. */
2794 opt = getopt32(argv, "^"
2795 "c:d:h:"
2796 IF_FEATURE_HTTPD_ENCODE_URL_STR("e:")
2797 IF_FEATURE_HTTPD_BASIC_AUTH("r:")
2798 IF_FEATURE_HTTPD_AUTH_MD5("m:")
2799 IF_FEATURE_HTTPD_SETUID("u:")
2800 "p:ifv"
2801 "\0"
2802 /* -v counts, -i implies -f */
2803 "vv:if",
2804 &opt_c_configFile, &url_for_decode, &home_httpd
2805 IF_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode)
2806 IF_FEATURE_HTTPD_BASIC_AUTH(, &g_realm)
2807 IF_FEATURE_HTTPD_AUTH_MD5(, &pass)
2808 IF_FEATURE_HTTPD_SETUID(, &s_ugid)
2809 , &bind_addr_or_port
2810 , &verbose
2811 );
2812 if (opt & OPT_DECODE_URL) {
2813 fputs_stdout(percent_decode_in_place(url_for_decode, /*strict:*/ 0));
2814 return 0;
2815 }
2816 #if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR
2817 if (opt & OPT_ENCODE_URL) {
2818 fputs_stdout(encodeString(url_for_encode));
2819 return 0;
2820 }
2821 #endif
2822 #if ENABLE_FEATURE_HTTPD_AUTH_MD5
2823 if (opt & OPT_MD5) {
2824 char salt[sizeof("$1$XXXXXXXX")];
2825 salt[0] = '$';
2826 salt[1] = '1';
2827 salt[2] = '$';
2828 crypt_make_salt(salt + 3, 4);
2829 puts(pw_encrypt(pass, salt, /*cleanup:*/ 0));
2830 return 0;
2831 }
2832 #endif
2833 #if ENABLE_FEATURE_HTTPD_SETUID
2834 if (opt & OPT_SETUID) {
2835 xget_uidgid(&ugid, s_ugid);
2836 }
2837 #endif
2838
2839 #if !BB_MMU
2840 if (!(opt & OPT_FOREGROUND)) {
2841 bb_daemonize_or_rexec(0, argv); /* don't change current directory */
2842 re_execed = 0; /* for the following chdir to work */
2843 }
2844 #endif
2845 /* Chdir to home (unless we were re_exec()ed for NOMMU case
2846 * in mini_httpd_nommu(): we are already in the home dir then).
2847 */
2848 if (!re_execed)
2849 xchdir(home_httpd);
2850
2851 if (!(opt & OPT_INETD)) {
2852 signal(SIGCHLD, SIG_IGN);
2853 server_socket = openServer();
2854 #if ENABLE_FEATURE_HTTPD_SETUID
2855 /* drop privileges */
2856 if (opt & OPT_SETUID) {
2857 if (ugid.gid != (gid_t)-1) {
2858 if (setgroups(1, &ugid.gid) == -1)
2859 bb_simple_perror_msg_and_die("setgroups");
2860 xsetgid(ugid.gid);
2861 }
2862 xsetuid(ugid.uid);
2863 }
2864 #endif
2865 }
2866
2867 #if 0
2868 /* User can do it himself: 'env - PATH="$PATH" httpd'
2869 * We don't do it because we don't want to screw users
2870 * which want to do
2871 * 'env - VAR1=val1 VAR2=val2 httpd'
2872 * and have VAR1 and VAR2 values visible in their CGIs.
2873 * Besides, it is also smaller. */
2874 {
2875 char *p = getenv("PATH");
2876 /* env strings themself are not freed, no need to xstrdup(p): */
2877 clearenv();
2878 if (p)
2879 putenv(p - 5);
2880 // if (!(opt & OPT_INETD))
2881 // setenv_long("SERVER_PORT", ???);
2882 }
2883 #endif
2884
2885 parse_conf(DEFAULT_PATH_HTTPD_CONF, FIRST_PARSE);
2886 if (!(opt & OPT_INETD))
2887 signal(SIGHUP, sighup_handler);
2888
2889 xfunc_error_retval = 0;
2890 if (opt & OPT_INETD)
2891 mini_httpd_inetd(); /* never returns */
2892 #if BB_MMU
2893 if (!(opt & OPT_FOREGROUND))
2894 bb_daemonize(0); /* don't change current directory */
2895 mini_httpd(server_socket); /* never returns */
2896 #else
2897 mini_httpd_nommu(server_socket, argc, argv); /* never returns */
2898 #endif
2899 /* return 0; */
2900 }
2901