1 /* vi: set sw=4 ts=4: */
3 * httpd implementation for busybox
5 * Copyright (C) 2002,2003 Glenn Engel <glenne@engel.org>
6 * Copyright (C) 2003-2006 Vladimir Oleynik <dzo@simtreas.ru>
8 * simplify patch stolen from libbb without using strdup
10 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
12 *****************************************************************************
16 * httpd -p 8080 -h $HOME/public_html
17 * or for daemon start from rc script with uid=0:
19 * This is equivalent if www user have uid=80 to
20 * httpd -p 80 -u 80 -h /www -c /etc/httpd.conf -r "Web Server Authentication"
23 * When a url starts by "/cgi-bin/" it is assumed to be a cgi script. The
24 * server changes directory to the location of the script and executes it
25 * after setting QUERY_STRING and other environment variables.
28 * "CGI Environment Variables": http://hoohoo.ncsa.uiuc.edu/cgi/env.html
30 * The applet can also be invoked as a url arg decoder and html text encoder
32 * foo=`httpd -d $foo` # decode "Hello%20World" as "Hello World"
33 * bar=`httpd -e "<Hello World>"` # encode as "<Hello World>"
34 * Note that url encoding for arguments is not the same as html encoding for
35 * presentation. -d decodes an url-encoded argument while -e encodes in html
38 * httpd.conf has the following format:
40 * H:/serverroot # define the server root. It will override -h
41 * A:172.20. # Allow address from 172.20.0.0/16
42 * A:10.0.0.0/25 # Allow any address from 10.0.0.0-10.0.0.127
43 * A:10.0.0.0/255.255.255.128 # Allow any address that previous set
44 * A:127.0.0.1 # Allow local loopback connections
45 * D:* # Deny from other IP connections
46 * E404:/path/e404.html # /path/e404.html is the 404 (not found) error page
47 * I:index.html # Show index.html when a directory is requested
49 * P:/url:[http://]hostname[:port]/new/path
50 * # When /urlXXXXXX is requested, reverse proxy
51 * # it to http://hostname[:port]/new/pathXXXXXX
53 * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin/
54 * /adm:admin:setup # Require user admin, pwd setup on urls starting with /adm/
55 * /adm:toor:PaSsWd # or user toor, pwd PaSsWd on urls starting with /adm/
56 * .au:audio/basic # additional mime type for audio.au files
57 * *.php:/path/php # run xxx.php through an interpreter
59 * A/D may be as a/d or allow/deny - only first char matters.
60 * Deny/Allow IP logic:
61 * - Default is to allow all (Allow all (A:*) is a no-op).
62 * - Deny rules take precedence over allow rules.
63 * - "Deny all" rule (D:*) is applied last.
66 * 1. Allow only specified addresses
67 * A:172.20 # Allow any address that begins with 172.20.
68 * A:10.10. # Allow any address that begins with 10.10.
69 * A:127.0.0.1 # Allow local loopback connections
70 * D:* # Deny from other IP connections
72 * 2. Only deny specified addresses
73 * D:1.2.3. # deny from 1.2.3.0 - 1.2.3.255
74 * D:2.3.4. # deny from 2.3.4.0 - 2.3.4.255
75 * A:* # (optional line added for clarity)
77 * If a sub directory contains a config file it is parsed and merged with
78 * any existing settings as if it was appended to the original configuration.
80 * subdir paths are relative to the containing subdir and thus cannot
81 * affect the parent rules.
83 * Note that since the sub dir is parsed in the forked thread servicing the
84 * subdir http request, any merge is discarded when the process exits. As a
85 * result, the subdir settings only have a lifetime of a single request.
87 * Custom error pages can contain an absolute path or be relative to
88 * 'home_httpd'. Error pages are to be static files (no CGI or script). Error
89 * page can only be defined in the root configuration file and are not taken
90 * into account in local (directories) config files.
92 * If -c is not set, an attempt will be made to open the default
93 * root configuration file. If -c is set and the file is not found, the
94 * server exits with an error.
97 /* TODO: use TCP_CORK, parse_config() */
100 #if ENABLE_FEATURE_HTTPD_USE_SENDFILE
101 # include <sys/sendfile.h>
106 #define IOBUF_SIZE 8192 /* IO buffer */
108 /* amount of buffering in a pipe */
110 # define PIPE_BUF 4096
112 #if PIPE_BUF >= IOBUF_SIZE
113 # error "PIPE_BUF >= IOBUF_SIZE"
116 #define HEADER_READ_TIMEOUT 60
118 static const char DEFAULT_PATH_HTTPD_CONF
[] ALIGN1
= "/etc";
119 static const char HTTPD_CONF
[] ALIGN1
= "httpd.conf";
120 static const char HTTP_200
[] ALIGN1
= "HTTP/1.0 200 OK\r\n";
122 typedef struct has_next_ptr
{
123 struct has_next_ptr
*next
;
126 /* Must have "next" as a first member */
127 typedef struct Htaccess
{
128 struct Htaccess
*next
;
130 char before_colon
[1]; /* really bigger, must be last */
133 /* Must have "next" as a first member */
134 typedef struct Htaccess_IP
{
135 struct Htaccess_IP
*next
;
141 /* Must have "next" as a first member */
142 typedef struct Htaccess_Proxy
{
143 struct Htaccess_Proxy
*next
;
151 HTTP_PARTIAL_CONTENT
= 206,
152 HTTP_MOVED_TEMPORARILY
= 302,
153 HTTP_BAD_REQUEST
= 400, /* malformed syntax */
154 HTTP_UNAUTHORIZED
= 401, /* authentication needed, respond with auth hdr */
155 HTTP_NOT_FOUND
= 404,
156 HTTP_FORBIDDEN
= 403,
157 HTTP_REQUEST_TIMEOUT
= 408,
158 HTTP_NOT_IMPLEMENTED
= 501, /* used for unrecognized requests */
159 HTTP_INTERNAL_SERVER_ERROR
= 500,
161 #if 0 /* future use */
162 HTTP_SWITCHING_PROTOCOLS
= 101,
165 HTTP_NON_AUTHORITATIVE_INFO
= 203,
166 HTTP_NO_CONTENT
= 204,
167 HTTP_MULTIPLE_CHOICES
= 300,
168 HTTP_MOVED_PERMANENTLY
= 301,
169 HTTP_NOT_MODIFIED
= 304,
170 HTTP_PAYMENT_REQUIRED
= 402,
171 HTTP_BAD_GATEWAY
= 502,
172 HTTP_SERVICE_UNAVAILABLE
= 503, /* overload, maintenance */
173 HTTP_RESPONSE_SETSIZE
= 0xffffffff
177 static const uint16_t http_response_type
[] ALIGN2
= {
179 #if ENABLE_FEATURE_HTTPD_RANGES
180 HTTP_PARTIAL_CONTENT
,
182 HTTP_MOVED_TEMPORARILY
,
183 HTTP_REQUEST_TIMEOUT
,
184 HTTP_NOT_IMPLEMENTED
,
185 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
191 HTTP_INTERNAL_SERVER_ERROR
,
192 #if 0 /* not implemented */
196 HTTP_MULTIPLE_CHOICES
,
197 HTTP_MOVED_PERMANENTLY
,
200 HTTP_SERVICE_UNAVAILABLE
,
204 static const struct {
207 } http_response
[ARRAY_SIZE(http_response_type
)] = {
209 #if ENABLE_FEATURE_HTTPD_RANGES
210 { "Partial Content", NULL
},
213 { "Request Timeout", "No request appeared within 60 seconds" },
214 { "Not Implemented", "The requested method is not recognized" },
215 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
216 { "Unauthorized", "" },
218 { "Not Found", "The requested URL was not found" },
219 { "Bad Request", "Unsupported method" },
221 { "Internal Server Error", "Internal Server Error" },
222 #if 0 /* not implemented */
226 { "Multiple Choices" },
227 { "Moved Permanently" },
229 { "Bad Gateway", "" },
230 { "Service Unavailable", "" },
236 int verbose
; /* must be int (used by getopt32) */
237 smallint flg_deny_all
;
239 unsigned rmt_ip
; /* used for IP-based allow/deny rules */
241 char *rmt_ip_str
; /* for $REMOTE_ADDR and $REMOTE_PORT */
242 const char *bind_addr_or_port
;
245 const char *opt_c_configFile
;
246 const char *home_httpd
;
247 const char *index_page
;
249 const char *found_mime_type
;
250 const char *found_moved_temporarily
;
251 Htaccess_IP
*ip_a_d
; /* config allow/deny lines */
253 USE_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm
;)
254 USE_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser
;)
255 USE_FEATURE_HTTPD_CGI(char *referer
;)
256 USE_FEATURE_HTTPD_CGI(char *user_agent
;)
257 USE_FEATURE_HTTPD_CGI(char *host
;)
258 USE_FEATURE_HTTPD_CGI(char *http_accept
;)
259 USE_FEATURE_HTTPD_CGI(char *http_accept_language
;)
261 off_t file_size
; /* -1 - unknown */
262 #if ENABLE_FEATURE_HTTPD_RANGES
268 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
269 Htaccess
*g_auth
; /* config user:password lines */
271 Htaccess
*mime_a
; /* config mime types */
272 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
273 Htaccess
*script_i
; /* config script interpreters */
275 char *iobuf
; /* [IOBUF_SIZE] */
276 #define hdr_buf bb_common_bufsiz1
279 #if ENABLE_FEATURE_HTTPD_ERROR_PAGES
280 const char *http_error_page
[ARRAY_SIZE(http_response_type
)];
282 #if ENABLE_FEATURE_HTTPD_PROXY
283 Htaccess_Proxy
*proxy
;
286 #define G (*ptr_to_globals)
287 #define verbose (G.verbose )
288 #define flg_deny_all (G.flg_deny_all )
289 #define rmt_ip (G.rmt_ip )
290 #define bind_addr_or_port (G.bind_addr_or_port)
291 #define g_query (G.g_query )
292 #define opt_c_configFile (G.opt_c_configFile )
293 #define home_httpd (G.home_httpd )
294 #define index_page (G.index_page )
295 #define found_mime_type (G.found_mime_type )
296 #define found_moved_temporarily (G.found_moved_temporarily)
297 #define last_mod (G.last_mod )
298 #define ip_a_d (G.ip_a_d )
299 #define g_realm (G.g_realm )
300 #define remoteuser (G.remoteuser )
301 #define referer (G.referer )
302 #define user_agent (G.user_agent )
303 #define host (G.host )
304 #define http_accept (G.http_accept )
305 #define http_accept_language (G.http_accept_language)
306 #define file_size (G.file_size )
307 #if ENABLE_FEATURE_HTTPD_RANGES
308 #define range_start (G.range_start )
309 #define range_end (G.range_end )
310 #define range_len (G.range_len )
314 range_end
= MAXINT(off_t
) - 1,
315 range_len
= MAXINT(off_t
),
318 #define rmt_ip_str (G.rmt_ip_str )
319 #define g_auth (G.g_auth )
320 #define mime_a (G.mime_a )
321 #define script_i (G.script_i )
322 #define iobuf (G.iobuf )
323 #define hdr_ptr (G.hdr_ptr )
324 #define hdr_cnt (G.hdr_cnt )
325 #define http_error_page (G.http_error_page )
326 #define proxy (G.proxy )
327 #define INIT_G() do { \
328 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
329 USE_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
330 bind_addr_or_port = "80"; \
331 index_page = "index.html"; \
336 #define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1)
340 SEND_HEADERS
= (1 << 0),
341 SEND_BODY
= (1 << 1),
342 SEND_HEADERS_AND_BODY
= SEND_HEADERS
+ SEND_BODY
,
344 static void send_file_and_exit(const char *url
, int what
) NORETURN
;
346 static void free_llist(has_next_ptr
**pptr
)
348 has_next_ptr
*cur
= *pptr
;
350 has_next_ptr
*t
= cur
;
357 static ALWAYS_INLINE
void free_Htaccess_list(Htaccess
**pptr
)
359 free_llist((has_next_ptr
**)pptr
);
362 static ALWAYS_INLINE
void free_Htaccess_IP_list(Htaccess_IP
**pptr
)
364 free_llist((has_next_ptr
**)pptr
);
367 /* Returns presumed mask width in bits or < 0 on error.
368 * Updates strp, stores IP at provided pointer */
369 static int scan_ip(const char **strp
, unsigned *ipp
, unsigned char endc
)
371 const char *p
= *strp
;
379 for (j
= 0; j
< 4; j
++) {
382 if ((*p
< '0' || *p
> '9') && *p
!= '/' && *p
)
385 while (*p
>= '0' && *p
<= '9') {
396 ip
= (ip
<< 8) | octet
;
410 /* Returns 0 on success. Stores IP and mask at provided pointers */
411 static int scan_ip_mask(const char *str
, unsigned *ipp
, unsigned *maskp
)
417 i
= scan_ip(&str
, ipp
, '/');
422 /* there is /xxx after dotted-IP address */
423 i
= bb_strtou(str
, &p
, 10);
425 /* 'xxx' itself is dotted-IP mask, parse it */
426 /* (return 0 (success) only if it has N.N.N.N form) */
427 return scan_ip(&str
, maskp
, '\0') - 32;
436 if (sizeof(unsigned) == 4 && i
== 32) {
437 /* mask >>= 32 below may not work */
443 /* i == 0 -> *maskp = 0x00000000
444 * i == 1 -> *maskp = 0x80000000
445 * i == 4 -> *maskp = 0xf0000000
446 * i == 31 -> *maskp = 0xfffffffe
447 * i == 32 -> *maskp = 0xffffffff */
448 *maskp
= (uint32_t)(~mask
);
453 * Parse configuration file into in-memory linked list.
455 * Any previous IP rules are discarded.
456 * If the flag argument is not SUBDIR_PARSE then all /path and mime rules
457 * are also discarded. That is, previous settings are retained if flag is
459 * Error pages are only parsed on the main config file.
461 * path Path where to look for httpd.conf (without filename).
462 * flag Type of the parse request.
466 FIRST_PARSE
= 0, /* path will be "/etc" */
467 SIGNALED_PARSE
= 1, /* path will be "/etc" */
468 SUBDIR_PARSE
= 2, /* path will be derived from URL */
470 static void parse_conf(const char *path
, int flag
)
472 /* internally used extra flag state */
473 enum { TRY_CURDIR_PARSE
= 3 };
476 const char *filename
;
479 /* discard old rules */
480 free_Htaccess_IP_list(&ip_a_d
);
482 /* retain previous auth and mime config only for subdir parse */
483 if (flag
!= SUBDIR_PARSE
) {
484 free_Htaccess_list(&mime_a
);
485 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
486 free_Htaccess_list(&g_auth
);
488 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
489 free_Htaccess_list(&script_i
);
493 filename
= opt_c_configFile
;
494 if (flag
== SUBDIR_PARSE
|| filename
== NULL
) {
495 filename
= alloca(strlen(path
) + sizeof(HTTPD_CONF
) + 2);
496 sprintf((char *)filename
, "%s/%s", path
, HTTPD_CONF
);
499 while ((f
= fopen_for_read(filename
)) == NULL
) {
500 if (flag
>= SUBDIR_PARSE
) { /* SUBDIR or TRY_CURDIR */
501 /* config file not found, no changes to config */
504 if (flag
== FIRST_PARSE
) {
505 /* -c CONFFILE given, but CONFFILE doesn't exist? */
506 if (opt_c_configFile
)
507 bb_simple_perror_msg_and_die(opt_c_configFile
);
508 /* else: no -c, thus we looked at /etc/httpd.conf,
509 * and it's not there. try ./httpd.conf: */
511 flag
= TRY_CURDIR_PARSE
;
512 filename
= HTTPD_CONF
;
515 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
516 /* in "/file:user:pass" lines, we prepend path in subdirs */
517 if (flag
!= SUBDIR_PARSE
)
522 * I:default_index_file
524 * [AD]:IP[/mask] # allow/deny, * for wildcard
525 * Ennn:error.html # error page for status nnn
526 * P:/url:[http://]hostname[:port]/new/path # reverse proxy
527 * .ext:mime/type # mime type
528 * *.php:/path/php # run xxx.php through an interpreter
529 * /file:user:pass # username and password
531 while (fgets(buf
, sizeof(buf
), f
) != NULL
) {
536 { /* remove all whitespace, and # comments */
540 /* skip non-whitespace beginning. Often the whole line
541 * is non-whitespace. We want this case to work fast,
542 * without needless copying, therefore we don't merge
543 * this operation into next while loop. */
544 while ((ch
= *p0
) != '\0' && ch
!= '\n' && ch
!= '#'
545 && ch
!= ' ' && ch
!= '\t'
550 /* if we enter this loop, we have some whitespace.
552 while (ch
!= '\0' && ch
!= '\n' && ch
!= '#') {
553 if (ch
!= ' ' && ch
!= '\t') {
559 strlen_buf
= p
- buf
;
561 continue; /* empty line */
564 after_colon
= strchr(buf
, ':');
566 if (after_colon
== NULL
|| *++after_colon
== '\0')
569 ch
= (buf
[0] & ~0x20); /* toupper if it's a letter */
572 index_page
= xstrdup(after_colon
);
576 /* do not allow jumping around using H in subdir's configs */
577 if (flag
== FIRST_PARSE
&& ch
== 'H') {
578 home_httpd
= xstrdup(after_colon
);
583 if (ch
== 'A' || ch
== 'D') {
586 if (*after_colon
== '*') {
588 /* memorize "deny all" */
591 /* skip assumed "A:*", it is a default anyway */
594 /* store "allow/deny IP/mask" line */
595 pip
= xzalloc(sizeof(*pip
));
596 if (scan_ip_mask(after_colon
, &pip
->ip
, &pip
->mask
)) {
597 /* IP{/mask} syntax error detected, protect all */
601 pip
->allow_deny
= ch
;
603 /* Deny:from_IP - prepend */
607 /* A:from_IP - append (thus all D's precedes A's) */
608 Htaccess_IP
*prev_IP
= ip_a_d
;
609 if (prev_IP
== NULL
) {
612 while (prev_IP
->next
)
613 prev_IP
= prev_IP
->next
;
620 #if ENABLE_FEATURE_HTTPD_ERROR_PAGES
621 if (flag
== FIRST_PARSE
&& ch
== 'E') {
623 int status
= atoi(buf
+ 1); /* error status code */
625 if (status
< HTTP_CONTINUE
) {
628 /* then error page; find matching status */
629 for (i
= 0; i
< ARRAY_SIZE(http_response_type
); i
++) {
630 if (http_response_type
[i
] == status
) {
631 /* We chdir to home_httpd, thus no need to
632 * concat_path_file(home_httpd, after_colon)
634 http_error_page
[i
] = xstrdup(after_colon
);
642 #if ENABLE_FEATURE_HTTPD_PROXY
643 if (flag
== FIRST_PARSE
&& ch
== 'P') {
644 /* P:/url:[http://]hostname[:port]/new/path */
645 char *url_from
, *host_port
, *url_to
;
646 Htaccess_Proxy
*proxy_entry
;
648 url_from
= after_colon
;
649 host_port
= strchr(after_colon
, ':');
650 if (host_port
== NULL
) {
654 if (strncmp(host_port
, "http://", 7) == 0)
656 if (*host_port
== '\0') {
659 url_to
= strchr(host_port
, '/');
660 if (url_to
== NULL
) {
664 proxy_entry
= xzalloc(sizeof(*proxy_entry
));
665 proxy_entry
->url_from
= xstrdup(url_from
);
666 proxy_entry
->host_port
= xstrdup(host_port
);
668 proxy_entry
->url_to
= xstrdup(url_to
);
669 proxy_entry
->next
= proxy
;
674 /* the rest of directives are non-alphabetic,
675 * must avoid using "toupper'ed" ch */
678 if (ch
== '.' /* ".ext:mime/type" */
679 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
680 || (ch
== '*' && buf
[1] == '.') /* "*.php:/path/php" */
686 cur
= xzalloc(sizeof(*cur
) /* includes space for NUL */ + strlen_buf
);
687 strcpy(cur
->before_colon
, buf
);
688 p
= cur
->before_colon
+ (after_colon
- buf
);
690 cur
->after_colon
= p
;
692 /* .mime line: prepend to mime_a list */
696 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
698 /* script interpreter line: prepend to script_i list */
699 cur
->next
= script_i
;
706 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
707 if (ch
== '/') { /* "/file:user:pass" */
712 /* note: path is "" unless we are in SUBDIR parse,
713 * otherwise it does NOT start with "/" */
714 cur
= xzalloc(sizeof(*cur
) /* includes space for NUL */
718 /* form "/path/file" */
719 sprintf(cur
->before_colon
, "/%s%.*s",
721 (int) (after_colon
- buf
- 1), /* includes "/", but not ":" */
723 /* canonicalize it */
724 p
= bb_simplify_abs_path_inplace(cur
->before_colon
);
725 file_len
= p
- cur
->before_colon
;
726 /* add "user:pass" after NUL */
727 strcpy(++p
, after_colon
);
728 cur
->after_colon
= p
;
730 /* insert cur into g_auth */
731 /* g_auth is sorted by decreased filename length */
733 Htaccess
*auth
, **authp
;
736 while ((auth
= *authp
) != NULL
) {
737 if (file_len
>= strlen(auth
->before_colon
)) {
738 /* insert cur before auth */
748 #endif /* BASIC_AUTH */
750 /* the line is not recognized */
752 bb_error_msg("config error '%s' in '%s'", buf
, filename
);
753 } /* while (fgets) */
758 #if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR
760 * Given a string, html-encode special characters.
761 * This is used for the -e command line option to provide an easy way
762 * for scripts to encode result data without confusing browsers. The
763 * returned string pointer is memory allocated by malloc().
765 * Returns a pointer to the encoded string (malloced).
767 static char *encodeString(const char *string
)
769 /* take the simple route and encode everything */
770 /* could possibly scan once to get length. */
771 int len
= strlen(string
);
772 char *out
= xmalloc(len
* 6 + 1);
776 while ((ch
= *string
++)) {
777 /* very simple check for what to encode */
781 p
+= sprintf(p
, "&#%d;", (unsigned char) ch
);
786 #endif /* FEATURE_HTTPD_ENCODE_URL_STR */
789 * Given a URL encoded string, convert it to plain ascii.
790 * Since decoding always makes strings smaller, the decode is done in-place.
791 * Thus, callers should xstrdup() the argument if they do not want the
792 * argument modified. The return is the original pointer, allowing this
793 * function to be easily used as arguments to other functions.
795 * string The first string to decode.
796 * option_d 1 if called for httpd -d
798 * Returns a pointer to the decoded string (same as input).
800 static unsigned hex_to_bin(unsigned char c
)
807 /* c | 0x20: letters to lower case, non-letters
808 * to (potentially different) non-letters */
809 v
= (unsigned)(c
| 0x20) - 'a';
815 void t(char c) { printf("'%c'(%u) %u\n", c, c, hex_to_bin(c)); }
816 int main() { t(0x10); t(0x20); t('0'); t('9'); t('A'); t('F'); t('a'); t('f');
817 t('0'-1); t('9'+1); t('A'-1); t('F'+1); t('a'-1); t('f'+1); return 0; }
819 static char *decodeString(char *orig
, int option_d
)
821 /* note that decoded string is always shorter than original */
826 while ((c
= *ptr
++) != '\0') {
829 if (option_d
&& c
== '+') {
837 v
= hex_to_bin(ptr
[0]);
845 v
= (v
* 16) | hex_to_bin(ptr
[1]);
848 if (!option_d
&& (v
== '/' || v
== '\0')) {
849 /* caller takes it as indication of invalid
850 * (dangerous wrt exploits) chars */
860 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
862 * Decode a base64 data stream as per rfc1521.
863 * Note that the rfc states that non base64 chars are to be ignored.
864 * Since the decode always results in a shorter size than the input,
865 * it is OK to pass the input arg as an output arg.
866 * Parameter: a pointer to a base64 encoded string.
867 * Decoded data is stored in-place.
869 static void decodeBase64(char *Data
)
871 const unsigned char *in
= (const unsigned char *)Data
;
872 /* The decoded size will be at most 3/4 the size of the encoded */
879 if (t
>= '0' && t
<= '9')
881 else if (t
>= 'A' && t
<= 'Z')
883 else if (t
>= 'a' && t
<= 'z')
897 *Data
++ = (char) (ch
>> 16);
898 *Data
++ = (char) (ch
>> 8);
908 * Create a listen server socket on the designated port.
910 static int openServer(void)
912 unsigned n
= bb_strtou(bind_addr_or_port
, NULL
, 10);
913 if (!errno
&& n
&& n
<= 0xffff)
914 n
= create_and_bind_stream_or_die(NULL
, n
);
916 n
= create_and_bind_stream_or_die(bind_addr_or_port
, 80);
922 * Log the connection closure and exit.
924 static void log_and_exit(void) NORETURN
;
925 static void log_and_exit(void)
927 /* Paranoia. IE said to be buggy. It may send some extra data
928 * or be confused by us just exiting without SHUT_WR. Oh well. */
929 shutdown(1, SHUT_WR
);
931 (this also messes up stdin when user runs httpd -i from terminal)
933 while (read(STDIN_FILENO, iobuf, IOBUF_SIZE) > 0)
938 bb_error_msg("closed");
939 _exit(xfunc_error_retval
);
943 * Create and send HTTP response headers.
944 * The arguments are combined and sent as one write operation. Note that
945 * IE will puke big-time if the headers are not sent in one packet and the
946 * second packet is delayed for any reason.
947 * responseNum - the result code to send.
949 static void send_headers(int responseNum
)
951 static const char RFC1123FMT
[] ALIGN1
= "%a, %d %b %Y %H:%M:%S GMT";
953 const char *responseString
= "";
954 const char *infoString
= NULL
;
955 const char *mime_type
;
956 #if ENABLE_FEATURE_HTTPD_ERROR_PAGES
957 const char *error_page
= NULL
;
960 time_t timer
= time(NULL
);
964 for (i
= 0; i
< ARRAY_SIZE(http_response_type
); i
++) {
965 if (http_response_type
[i
] == responseNum
) {
966 responseString
= http_response
[i
].name
;
967 infoString
= http_response
[i
].info
;
968 #if ENABLE_FEATURE_HTTPD_ERROR_PAGES
969 error_page
= http_error_page
[i
];
974 /* error message is HTML */
975 mime_type
= responseNum
== HTTP_OK
?
976 found_mime_type
: "text/html";
979 bb_error_msg("response:%u", responseNum
);
981 /* emit the current date */
982 strftime(tmp_str
, sizeof(tmp_str
), RFC1123FMT
, gmtime(&timer
));
984 "HTTP/1.0 %d %s\r\nContent-type: %s\r\n"
985 "Date: %s\r\nConnection: close\r\n",
986 responseNum
, responseString
, mime_type
, tmp_str
);
988 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
989 if (responseNum
== HTTP_UNAUTHORIZED
) {
990 len
+= sprintf(iobuf
+ len
,
991 "WWW-Authenticate: Basic realm=\"%s\"\r\n",
995 if (responseNum
== HTTP_MOVED_TEMPORARILY
) {
996 len
+= sprintf(iobuf
+ len
, "Location: %s/%s%s\r\n",
997 found_moved_temporarily
,
998 (g_query
? "?" : ""),
999 (g_query
? g_query
: ""));
1002 #if ENABLE_FEATURE_HTTPD_ERROR_PAGES
1003 if (error_page
&& access(error_page
, R_OK
) == 0) {
1004 strcat(iobuf
, "\r\n");
1008 fprintf(stderr
, "headers: '%s'\n", iobuf
);
1009 full_write(STDOUT_FILENO
, iobuf
, len
);
1011 fprintf(stderr
, "writing error page: '%s'\n", error_page
);
1012 return send_file_and_exit(error_page
, SEND_BODY
);
1016 if (file_size
!= -1) { /* file */
1017 strftime(tmp_str
, sizeof(tmp_str
), RFC1123FMT
, gmtime(&last_mod
));
1018 #if ENABLE_FEATURE_HTTPD_RANGES
1019 if (responseNum
== HTTP_PARTIAL_CONTENT
) {
1020 len
+= sprintf(iobuf
+ len
, "Content-Range: bytes %"OFF_FMT
"d-%"OFF_FMT
"d/%"OFF_FMT
"d\r\n",
1024 file_size
= range_end
- range_start
+ 1;
1027 len
+= sprintf(iobuf
+ len
,
1028 #if ENABLE_FEATURE_HTTPD_RANGES
1029 "Accept-Ranges: bytes\r\n"
1031 "Last-Modified: %s\r\n%s %"OFF_FMT
"d\r\n",
1037 iobuf
[len
++] = '\r';
1038 iobuf
[len
++] = '\n';
1040 len
+= sprintf(iobuf
+ len
,
1041 "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>\n"
1042 "<BODY><H1>%d %s</H1>\n%s\n</BODY></HTML>\n",
1043 responseNum
, responseString
,
1044 responseNum
, responseString
, infoString
);
1047 fprintf(stderr
, "headers: '%s'\n", iobuf
);
1048 if (full_write(STDOUT_FILENO
, iobuf
, len
) != len
) {
1050 bb_perror_msg("error");
1055 static void send_headers_and_exit(int responseNum
) NORETURN
;
1056 static void send_headers_and_exit(int responseNum
)
1058 send_headers(responseNum
);
1063 * Read from the socket until '\n' or EOF. '\r' chars are removed.
1064 * '\n' is replaced with NUL.
1065 * Return number of characters read or 0 if nothing is read
1066 * ('\r' and '\n' are not counted).
1067 * Data is returned in iobuf.
1069 static int get_line(void)
1074 alarm(HEADER_READ_TIMEOUT
);
1077 hdr_cnt
= safe_read(STDIN_FILENO
, hdr_buf
, sizeof(hdr_buf
));
1082 iobuf
[count
] = c
= *hdr_ptr
++;
1088 iobuf
[count
] = '\0';
1091 if (count
< (IOBUF_SIZE
- 1)) /* check overflow */
1097 #if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY
1099 /* gcc 4.2.1 fares better with NOINLINE */
1100 static NOINLINE
void cgi_io_loop_and_exit(int fromCgi_rd
, int toCgi_wr
, int post_len
) NORETURN
;
1101 static NOINLINE
void cgi_io_loop_and_exit(int fromCgi_rd
, int toCgi_wr
, int post_len
)
1103 enum { FROM_CGI
= 1, TO_CGI
= 2 }; /* indexes in pfd[] */
1104 struct pollfd pfd
[3];
1105 int out_cnt
; /* we buffer a bit of initial CGI output */
1108 /* iobuf is used for CGI -> network data,
1109 * hdr_buf is for network -> CGI data (POSTDATA) */
1111 /* If CGI dies, we still want to correctly finish reading its output
1112 * and send it to the peer. So please no SIGPIPEs! */
1113 signal(SIGPIPE
, SIG_IGN
);
1115 // We inconsistently handle a case when more POSTDATA from network
1116 // is coming than we expected. We may give *some part* of that
1117 // extra data to CGI.
1119 //if (hdr_cnt > post_len) {
1120 // /* We got more POSTDATA from network than we expected */
1121 // hdr_cnt = post_len;
1123 post_len
-= hdr_cnt
;
1124 /* post_len - number of POST bytes not yet read from network */
1126 /* NB: breaking out of this loop jumps to log_and_exit() */
1129 memset(pfd
, 0, sizeof(pfd
));
1131 pfd
[FROM_CGI
].fd
= fromCgi_rd
;
1132 pfd
[FROM_CGI
].events
= POLLIN
;
1135 pfd
[TO_CGI
].fd
= toCgi_wr
;
1137 pfd
[TO_CGI
].events
= POLLOUT
;
1138 } else if (post_len
> 0) {
1139 pfd
[0].events
= POLLIN
;
1141 /* post_len <= 0 && hdr_cnt <= 0:
1142 * no more POST data to CGI,
1143 * let CGI see EOF on CGI's stdin */
1149 /* Now wait on the set of sockets */
1150 count
= safe_poll(pfd
, 3, -1);
1153 if (safe_waitpid(pid
, &status
, WNOHANG
) <= 0) {
1154 /* Weird. CGI didn't exit and no fd's
1155 * are ready, yet poll returned?! */
1158 if (DEBUG
&& WIFEXITED(status
))
1159 bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status
));
1160 if (DEBUG
&& WIFSIGNALED(status
))
1161 bb_error_msg("CGI killed, signal=%d", WTERMSIG(status
));
1166 if (pfd
[TO_CGI
].revents
) {
1167 /* hdr_cnt > 0 here due to the way pfd[TO_CGI].events set */
1168 /* Have data from peer and can write to CGI */
1169 count
= safe_write(toCgi_wr
, hdr_ptr
, hdr_cnt
);
1170 /* Doesn't happen, we dont use nonblocking IO here
1171 *if (count < 0 && errno == EAGAIN) {
1178 /* EOF/broken pipe to CGI, stop piping POST data */
1179 hdr_cnt
= post_len
= 0;
1183 if (pfd
[0].revents
) {
1184 /* post_len > 0 && hdr_cnt == 0 here */
1185 /* We expect data, prev data portion is eaten by CGI
1186 * and there *is* data to read from the peer
1188 //count = post_len > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : post_len;
1189 //count = safe_read(STDIN_FILENO, hdr_buf, count);
1190 count
= safe_read(STDIN_FILENO
, hdr_buf
, sizeof(hdr_buf
));
1196 /* no more POST data can be read */
1201 if (pfd
[FROM_CGI
].revents
) {
1202 /* There is something to read from CGI */
1205 /* Are we still buffering CGI output? */
1207 /* HTTP_200[] has single "\r\n" at the end.
1208 * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html,
1209 * CGI scripts MUST send their own header terminated by
1210 * empty line, then data. That's why we have only one
1211 * <cr><lf> pair here. We will output "200 OK" line
1212 * if needed, but CGI still has to provide blank line
1213 * between header and body */
1215 /* Must use safe_read, not full_read, because
1216 * CGI may output a few first bytes and then wait
1217 * for POSTDATA without closing stdout.
1218 * With full_read we may wait here forever. */
1219 count
= safe_read(fromCgi_rd
, rbuf
+ out_cnt
, PIPE_BUF
- 8);
1221 /* eof (or error) and there was no "HTTP",
1222 * so write it, then write received data */
1224 full_write(STDOUT_FILENO
, HTTP_200
, sizeof(HTTP_200
)-1);
1225 full_write(STDOUT_FILENO
, rbuf
, out_cnt
);
1227 break; /* CGI stdout is closed, exiting */
1231 /* "Status" header format is: "Status: 302 Redirected\r\n" */
1232 if (out_cnt
>= 8 && memcmp(rbuf
, "Status: ", 8) == 0) {
1233 /* send "HTTP/1.0 " */
1234 if (full_write(STDOUT_FILENO
, HTTP_200
, 9) != 9)
1236 rbuf
+= 8; /* skip "Status: " */
1237 count
= out_cnt
- 8;
1238 out_cnt
= -1; /* buffering off */
1239 } else if (out_cnt
>= 4) {
1240 /* Did CGI add "HTTP"? */
1241 if (memcmp(rbuf
, HTTP_200
, 4) != 0) {
1242 /* there is no "HTTP", do it ourself */
1243 if (full_write(STDOUT_FILENO
, HTTP_200
, sizeof(HTTP_200
)-1) != sizeof(HTTP_200
)-1)
1247 if (!strstr(rbuf, "ontent-")) {
1248 full_write(s, "Content-type: text/plain\r\n\r\n", 28);
1250 * Counter-example of valid CGI without Content-type:
1251 * echo -en "HTTP/1.0 302 Found\r\n"
1252 * echo -en "Location: http://www.busybox.net\r\n"
1256 out_cnt
= -1; /* buffering off */
1259 count
= safe_read(fromCgi_rd
, rbuf
, PIPE_BUF
);
1261 break; /* eof (or error) */
1263 if (full_write(STDOUT_FILENO
, rbuf
, count
) != count
)
1266 fprintf(stderr
, "cgi read %d bytes: '%.*s'\n", count
, count
, rbuf
);
1267 } /* if (pfd[FROM_CGI].revents) */
1273 #if ENABLE_FEATURE_HTTPD_CGI
1275 static void setenv1(const char *name
, const char *value
)
1277 setenv(name
, value
? value
: "", 1);
1281 * Spawn CGI script, forward CGI's stdin/out <=> network
1283 * Environment variables are set up and the script is invoked with pipes
1284 * for stdin/stdout. If a POST is being done the script is fed the POST
1285 * data in addition to setting the QUERY_STRING variable (for GETs or POSTs).
1288 * const char *url The requested URL (with leading /).
1289 * int post_len Length of the POST body.
1290 * const char *cookie For set HTTP_COOKIE.
1291 * const char *content_type For set CONTENT_TYPE.
1293 static void send_cgi_and_exit(
1295 const char *request
,
1298 const char *content_type
) NORETURN
;
1299 static void send_cgi_and_exit(
1301 const char *request
,
1304 const char *content_type
)
1306 struct fd_pair fromCgi
; /* CGI -> httpd pipe */
1307 struct fd_pair toCgi
; /* httpd -> CGI pipe */
1311 /* Make a copy. NB: caller guarantees:
1312 * url[0] == '/', url[1] != '/' */
1316 * We are mucking with environment _first_ and then vfork/exec,
1317 * this allows us to use vfork safely. Parent doesn't care about
1318 * these environment changes anyway.
1321 /* Check for [dirs/]script.cgi/PATH_INFO */
1322 script
= (char*)url
;
1323 while ((script
= strchr(script
+ 1, '/')) != NULL
) {
1327 if (!is_directory(url
+ 1, 1, &sb
)) {
1328 /* not directory, found script.cgi/PATH_INFO */
1332 *script
= '/'; /* is directory, find next '/' */
1334 setenv1("PATH_INFO", script
); /* set to /PATH_INFO or "" */
1335 setenv1("REQUEST_METHOD", request
);
1337 putenv(xasprintf("%s=%s?%s", "REQUEST_URI", url
, g_query
));
1339 setenv1("REQUEST_URI", url
);
1342 *script
= '\0'; /* cut off /PATH_INFO */
1344 /* SCRIPT_FILENAME is required by PHP in CGI mode */
1345 if (home_httpd
[0] == '/') {
1346 char *fullpath
= concat_path_file(home_httpd
, url
);
1347 setenv1("SCRIPT_FILENAME", fullpath
);
1349 /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */
1350 setenv1("SCRIPT_NAME", url
);
1351 /* http://hoohoo.ncsa.uiuc.edu/cgi/env.html:
1352 * QUERY_STRING: The information which follows the ? in the URL
1353 * which referenced this script. This is the query information.
1354 * It should not be decoded in any fashion. This variable
1355 * should always be set when there is query information,
1356 * regardless of command line decoding. */
1357 /* (Older versions of bbox seem to do some decoding) */
1358 setenv1("QUERY_STRING", g_query
);
1359 putenv((char*)"SERVER_SOFTWARE=busybox httpd/"BB_VER
);
1360 putenv((char*)"SERVER_PROTOCOL=HTTP/1.0");
1361 putenv((char*)"GATEWAY_INTERFACE=CGI/1.1");
1362 /* Having _separate_ variables for IP and port defeats
1363 * the purpose of having socket abstraction. Which "port"
1364 * are you using on Unix domain socket?
1365 * IOW - REMOTE_PEER="1.2.3.4:56" makes much more sense.
1368 char *p
= rmt_ip_str
? rmt_ip_str
: (char*)"";
1369 char *cp
= strrchr(p
, ':');
1370 if (ENABLE_FEATURE_IPV6
&& cp
&& strchr(cp
, ']'))
1372 if (cp
) *cp
= '\0'; /* delete :PORT */
1373 setenv1("REMOTE_ADDR", p
);
1376 #if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
1377 setenv1("REMOTE_PORT", cp
+ 1);
1381 setenv1("HTTP_USER_AGENT", user_agent
);
1383 setenv1("HTTP_ACCEPT", http_accept
);
1384 if (http_accept_language
)
1385 setenv1("HTTP_ACCEPT_LANGUAGE", http_accept_language
);
1387 putenv(xasprintf("CONTENT_LENGTH=%d", post_len
));
1389 setenv1("HTTP_COOKIE", cookie
);
1391 setenv1("CONTENT_TYPE", content_type
);
1392 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1394 setenv1("REMOTE_USER", remoteuser
);
1395 putenv((char*)"AUTH_TYPE=Basic");
1399 setenv1("HTTP_REFERER", referer
);
1400 setenv1("HTTP_HOST", host
); /* set to "" if NULL */
1401 /* setenv1("SERVER_NAME", safe_gethostname()); - don't do this,
1402 * just run "env SERVER_NAME=xyz httpd ..." instead */
1404 xpiped_pair(fromCgi
);
1409 /* TODO: log perror? */
1417 xfunc_error_retval
= 242;
1419 /* NB: close _first_, then move fds! */
1422 xmove_fd(toCgi
.rd
, 0); /* replace stdin with the pipe */
1423 xmove_fd(fromCgi
.wr
, 1); /* replace stdout with the pipe */
1424 /* User seeing stderr output can be a security problem.
1425 * If CGI really wants that, it can always do dup itself. */
1428 /* Chdiring to script's dir */
1429 script
= strrchr(url
, '/');
1430 if (script
!= url
) { /* paranoia */
1432 if (chdir(url
+ 1) != 0) {
1433 bb_perror_msg("chdir %s", url
+ 1);
1434 goto error_execing_cgi
;
1436 // not needed: *script = '/';
1440 /* set argv[0] to name without path */
1444 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
1446 char *suffix
= strrchr(script
, '.');
1450 for (cur
= script_i
; cur
; cur
= cur
->next
) {
1451 if (strcmp(cur
->before_colon
+ 1, suffix
) == 0) {
1452 /* found interpreter name */
1453 argv
[0] = cur
->after_colon
;
1462 /* restore default signal dispositions for CGI process */
1469 /* _NOT_ execvp. We do not search PATH. argv[0] is a filename
1470 * without any dir components and will only match a file
1471 * in the current directory */
1472 execv(argv
[0], argv
);
1474 bb_perror_msg("exec %s", argv
[0]);
1477 * (we are CGI here, our stdout is pumped to the net) */
1478 send_headers_and_exit(HTTP_NOT_FOUND
);
1481 /* Parent process */
1483 /* Restore variables possibly changed by child */
1484 xfunc_error_retval
= 0;
1489 cgi_io_loop_and_exit(fromCgi
.rd
, toCgi
.wr
, post_len
);
1492 #endif /* FEATURE_HTTPD_CGI */
1495 * Send a file response to a HTTP request, and exit
1498 * const char *url The requested URL (with leading /).
1499 * what What to send (headers/body/both).
1501 static NOINLINE
void send_file_and_exit(const char *url
, int what
)
1503 static const char *const suffixTable
[] = {
1504 /* Warning: shorter equivalent suffix in one line must be first */
1505 ".htm.html", "text/html",
1506 ".jpg.jpeg", "image/jpeg",
1507 ".gif", "image/gif",
1508 ".png", "image/png",
1509 ".txt.h.c.cc.cpp", "text/plain",
1511 ".wav", "audio/wav",
1512 ".avi", "video/x-msvideo",
1513 ".qt.mov", "video/quicktime",
1514 ".mpe.mpeg", "video/mpeg",
1515 ".mid.midi", "audio/midi",
1516 ".mp3", "audio/mpeg",
1517 #if 0 /* unpopular */
1518 ".au", "audio/basic",
1519 ".pac", "application/x-ns-proxy-autoconfig",
1520 ".vrml.wrl", "model/vrml",
1527 const char *const *table
;
1528 const char *try_suffix
;
1531 fd
= open(url
, O_RDONLY
);
1534 bb_perror_msg("can't open '%s'", url
);
1535 /* Error pages are sent by using send_file_and_exit(SEND_BODY).
1536 * IOW: it is unsafe to call send_headers_and_exit
1537 * if what is SEND_BODY! Can recurse! */
1538 if (what
!= SEND_BODY
)
1539 send_headers_and_exit(HTTP_NOT_FOUND
);
1542 /* If you want to know about EPIPE below
1543 * (happens if you abort downloads from local httpd): */
1544 signal(SIGPIPE
, SIG_IGN
);
1546 suffix
= strrchr(url
, '.');
1548 /* If not found, set default as "application/octet-stream"; */
1549 found_mime_type
= "application/octet-stream";
1552 for (table
= suffixTable
; *table
; table
+= 2) {
1553 try_suffix
= strstr(table
[0], suffix
);
1555 try_suffix
+= strlen(suffix
);
1556 if (*try_suffix
== '\0' || *try_suffix
== '.') {
1557 found_mime_type
= table
[1];
1562 for (cur
= mime_a
; cur
; cur
= cur
->next
) {
1563 if (strcmp(cur
->before_colon
, suffix
) == 0) {
1564 found_mime_type
= cur
->after_colon
;
1571 bb_error_msg("sending file '%s' content-type: %s",
1572 url
, found_mime_type
);
1574 #if ENABLE_FEATURE_HTTPD_RANGES
1575 if (what
== SEND_BODY
)
1576 range_start
= 0; /* err pages and ranges don't mix */
1577 range_len
= MAXINT(off_t
);
1580 range_end
= file_size
- 1;
1582 if (range_end
< range_start
1583 || lseek(fd
, range_start
, SEEK_SET
) != range_start
1585 lseek(fd
, 0, SEEK_SET
);
1588 range_len
= range_end
- range_start
+ 1;
1589 send_headers(HTTP_PARTIAL_CONTENT
);
1594 if (what
& SEND_HEADERS
)
1595 send_headers(HTTP_OK
);
1596 #if ENABLE_FEATURE_HTTPD_USE_SENDFILE
1598 off_t offset
= range_start
;
1600 /* sz is rounded down to 64k */
1601 ssize_t sz
= MAXINT(ssize_t
) - 0xffff;
1602 USE_FEATURE_HTTPD_RANGES(if (sz
> range_len
) sz
= range_len
;)
1603 count
= sendfile(STDOUT_FILENO
, fd
, &offset
, sz
);
1605 if (offset
== range_start
)
1606 break; /* fall back to read/write loop */
1609 USE_FEATURE_HTTPD_RANGES(range_len
-= sz
;)
1610 if (count
== 0 || range_len
== 0)
1615 while ((count
= safe_read(fd
, iobuf
, IOBUF_SIZE
)) > 0) {
1617 USE_FEATURE_HTTPD_RANGES(if (count
> range_len
) count
= range_len
;)
1618 n
= full_write(STDOUT_FILENO
, iobuf
, count
);
1621 USE_FEATURE_HTTPD_RANGES(range_len
-= count
;)
1626 USE_FEATURE_HTTPD_USE_SENDFILE(fin
:)
1628 bb_perror_msg("error");
1633 static int checkPermIP(void)
1637 for (cur
= ip_a_d
; cur
; cur
= cur
->next
) {
1640 "checkPermIP: '%s' ? '%u.%u.%u.%u/%u.%u.%u.%u'\n",
1642 (unsigned char)(cur
->ip
>> 24),
1643 (unsigned char)(cur
->ip
>> 16),
1644 (unsigned char)(cur
->ip
>> 8),
1645 (unsigned char)(cur
->ip
),
1646 (unsigned char)(cur
->mask
>> 24),
1647 (unsigned char)(cur
->mask
>> 16),
1648 (unsigned char)(cur
->mask
>> 8),
1649 (unsigned char)(cur
->mask
)
1652 if ((rmt_ip
& cur
->mask
) == cur
->ip
)
1653 return (cur
->allow_deny
== 'A'); /* A -> 1 */
1656 return !flg_deny_all
; /* depends on whether we saw "D:*" */
1659 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1661 * Config file entries are of the form "/<path>:<user>:<passwd>".
1662 * If config file has no prefix match for path, access is allowed.
1664 * path The file path
1665 * user_and_passwd "user:passwd" to validate
1667 * Returns 1 if user_and_passwd is OK.
1669 static int check_user_passwd(const char *path
, const char *user_and_passwd
)
1672 const char *prev
= NULL
;
1674 for (cur
= g_auth
; cur
; cur
= cur
->next
) {
1675 const char *dir_prefix
;
1678 dir_prefix
= cur
->before_colon
;
1681 /* If already saw a match, don't accept other different matches */
1682 if (prev
&& strcmp(prev
, dir_prefix
) != 0)
1686 fprintf(stderr
, "checkPerm: '%s' ? '%s'\n", dir_prefix
, user_and_passwd
);
1688 /* If it's not a prefix match, continue searching */
1689 len
= strlen(dir_prefix
);
1690 if (len
!= 1 /* dir_prefix "/" matches all, don't need to check */
1691 && (strncmp(dir_prefix
, path
, len
) != 0
1692 || (path
[len
] != '/' && path
[len
] != '\0'))
1697 /* Path match found */
1700 if (ENABLE_FEATURE_HTTPD_AUTH_MD5
) {
1703 md5_passwd
= strchr(cur
->after_colon
, ':');
1704 if (md5_passwd
&& md5_passwd
[1] == '$' && md5_passwd
[2] == '1'
1705 && md5_passwd
[3] == '$' && md5_passwd
[4]
1711 user_len_p1
= md5_passwd
- cur
->after_colon
;
1712 /* comparing "user:" */
1713 if (strncmp(cur
->after_colon
, user_and_passwd
, user_len_p1
) != 0) {
1717 encrypted
= pw_encrypt(
1718 user_and_passwd
+ user_len_p1
/* cleartext pwd from user */,
1719 md5_passwd
/*salt */, 1 /* cleanup */);
1720 r
= strcmp(encrypted
, md5_passwd
);
1723 goto set_remoteuser_var
; /* Ok */
1728 /* Comparing plaintext "user:pass" in one go */
1729 if (strcmp(cur
->after_colon
, user_and_passwd
) == 0) {
1731 remoteuser
= xstrndup(user_and_passwd
,
1732 strchrnul(user_and_passwd
, ':') - user_and_passwd
);
1737 /* 0(bad) if prev is set: matches were found but passwd was wrong */
1738 return (prev
== NULL
);
1740 #endif /* FEATURE_HTTPD_BASIC_AUTH */
1742 #if ENABLE_FEATURE_HTTPD_PROXY
1743 static Htaccess_Proxy
*find_proxy_entry(const char *url
)
1746 for (p
= proxy
; p
; p
= p
->next
) {
1747 if (strncmp(url
, p
->url_from
, strlen(p
->url_from
)) == 0)
1757 static void send_REQUEST_TIMEOUT_and_exit(int sig
) NORETURN
;
1758 static void send_REQUEST_TIMEOUT_and_exit(int sig UNUSED_PARAM
)
1760 send_headers_and_exit(HTTP_REQUEST_TIMEOUT
);
1764 * Handle an incoming http request and exit.
1766 static void handle_incoming_and_exit(const len_and_sockaddr
*fromAddr
) NORETURN
;
1767 static void handle_incoming_and_exit(const len_and_sockaddr
*fromAddr
)
1769 static const char request_GET
[] ALIGN1
= "GET";
1774 #if ENABLE_FEATURE_HTTPD_CGI
1775 static const char request_HEAD
[] ALIGN1
= "HEAD";
1776 const char *prequest
;
1777 char *cookie
= NULL
;
1778 char *content_type
= NULL
;
1779 unsigned long length
= 0;
1780 #elif ENABLE_FEATURE_HTTPD_PROXY
1781 #define prequest request_GET
1782 unsigned long length
= 0;
1784 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1785 smallint authorized
= -1;
1787 smallint ip_allowed
;
1788 char http_major_version
;
1789 #if ENABLE_FEATURE_HTTPD_PROXY
1790 char http_minor_version
;
1791 char *header_buf
= header_buf
; /* for gcc */
1792 char *header_ptr
= header_ptr
;
1793 Htaccess_Proxy
*proxy_entry
;
1796 /* Allocation of iobuf is postponed until now
1797 * (IOW, server process doesn't need to waste 8k) */
1798 iobuf
= xmalloc(IOBUF_SIZE
);
1801 if (fromAddr
->u
.sa
.sa_family
== AF_INET
) {
1802 rmt_ip
= ntohl(fromAddr
->u
.sin
.sin_addr
.s_addr
);
1804 #if ENABLE_FEATURE_IPV6
1805 if (fromAddr
->u
.sa
.sa_family
== AF_INET6
1806 && fromAddr
->u
.sin6
.sin6_addr
.s6_addr32
[0] == 0
1807 && fromAddr
->u
.sin6
.sin6_addr
.s6_addr32
[1] == 0
1808 && ntohl(fromAddr
->u
.sin6
.sin6_addr
.s6_addr32
[2]) == 0xffff)
1809 rmt_ip
= ntohl(fromAddr
->u
.sin6
.sin6_addr
.s6_addr32
[3]);
1811 if (ENABLE_FEATURE_HTTPD_CGI
|| DEBUG
|| verbose
) {
1812 /* NB: can be NULL (user runs httpd -i by hand?) */
1813 rmt_ip_str
= xmalloc_sockaddr2dotted(&fromAddr
->u
.sa
);
1816 /* this trick makes -v logging much simpler */
1818 applet_name
= rmt_ip_str
;
1820 bb_error_msg("connected");
1823 /* Install timeout handler. get_line() needs it. */
1824 signal(SIGALRM
, send_REQUEST_TIMEOUT_and_exit
);
1826 if (!get_line()) /* EOF or error or empty line */
1827 send_headers_and_exit(HTTP_BAD_REQUEST
);
1829 /* Determine type of request (GET/POST) */
1830 urlp
= strpbrk(iobuf
, " \t");
1832 send_headers_and_exit(HTTP_BAD_REQUEST
);
1834 #if ENABLE_FEATURE_HTTPD_CGI
1835 prequest
= request_GET
;
1836 if (strcasecmp(iobuf
, prequest
) != 0) {
1837 prequest
= request_HEAD
;
1838 if (strcasecmp(iobuf
, prequest
) != 0) {
1840 if (strcasecmp(iobuf
, prequest
) != 0)
1841 send_headers_and_exit(HTTP_NOT_IMPLEMENTED
);
1845 if (strcasecmp(iobuf
, request_GET
) != 0)
1846 send_headers_and_exit(HTTP_NOT_IMPLEMENTED
);
1848 urlp
= skip_whitespace(urlp
);
1850 send_headers_and_exit(HTTP_BAD_REQUEST
);
1852 /* Find end of URL and parse HTTP version, if any */
1853 http_major_version
= '0';
1854 USE_FEATURE_HTTPD_PROXY(http_minor_version
= '0';)
1855 tptr
= strchrnul(urlp
, ' ');
1856 /* Is it " HTTP/"? */
1857 if (tptr
[0] && strncmp(tptr
+ 1, HTTP_200
, 5) == 0) {
1858 http_major_version
= tptr
[6];
1859 USE_FEATURE_HTTPD_PROXY(http_minor_version
= tptr
[8];)
1863 /* Copy URL from after "GET "/"POST " to stack-allocated char[] */
1864 urlcopy
= alloca((tptr
- urlp
) + 2 + strlen(index_page
));
1865 /*if (urlcopy == NULL)
1866 * send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);*/
1867 strcpy(urlcopy
, urlp
);
1868 /* NB: urlcopy ptr is never changed after this */
1870 /* Extract url args if present */
1872 tptr
= strchr(urlcopy
, '?');
1878 /* Decode URL escape sequences */
1879 tptr
= decodeString(urlcopy
, 0);
1881 send_headers_and_exit(HTTP_BAD_REQUEST
);
1882 if (tptr
== urlcopy
+ 1) {
1883 /* '/' or NUL is encoded */
1884 send_headers_and_exit(HTTP_NOT_FOUND
);
1887 /* Canonicalize path */
1888 /* Algorithm stolen from libbb bb_simplify_path(),
1889 * but don't strdup, retain trailing slash, protect root */
1890 urlp
= tptr
= urlcopy
;
1893 /* skip duplicate (or initial) slash */
1898 /* skip extra "/./" */
1899 if (tptr
[1] == '/' || !tptr
[1]) {
1902 /* "..": be careful */
1903 if (tptr
[1] == '.' && (tptr
[2] == '/' || !tptr
[2])) {
1905 if (urlp
== urlcopy
) /* protect root */
1906 send_headers_and_exit(HTTP_BAD_REQUEST
);
1907 while (*--urlp
!= '/') /* omit previous dir */;
1914 *++urlp
= '\0'; /* terminate after last character */
1916 /* If URL is a directory, add '/' */
1917 if (urlp
[-1] != '/') {
1918 if (is_directory(urlcopy
+ 1, 1, &sb
)) {
1919 found_moved_temporarily
= urlcopy
;
1925 bb_error_msg("url:%s", urlcopy
);
1928 ip_allowed
= checkPermIP();
1929 while (ip_allowed
&& (tptr
= strchr(tptr
+ 1, '/')) != NULL
) {
1930 /* have path1/path2 */
1932 if (is_directory(urlcopy
+ 1, 1, &sb
)) {
1933 /* may have subdir config */
1934 parse_conf(urlcopy
+ 1, SUBDIR_PARSE
);
1935 ip_allowed
= checkPermIP();
1940 #if ENABLE_FEATURE_HTTPD_PROXY
1941 proxy_entry
= find_proxy_entry(urlcopy
);
1943 header_buf
= header_ptr
= xmalloc(IOBUF_SIZE
);
1946 if (http_major_version
>= '0') {
1947 /* Request was with "... HTTP/nXXX", and n >= 0 */
1949 /* Read until blank line for HTTP version specified, else parse immediate */
1952 break; /* EOF or error or empty line */
1954 bb_error_msg("header: '%s'", iobuf
);
1956 #if ENABLE_FEATURE_HTTPD_PROXY
1957 /* We need 2 more bytes for yet another "\r\n" -
1958 * see near fdprintf(proxy_fd...) further below */
1959 if (proxy_entry
&& (header_ptr
- header_buf
) < IOBUF_SIZE
- 2) {
1960 int len
= strlen(iobuf
);
1961 if (len
> IOBUF_SIZE
- (header_ptr
- header_buf
) - 4)
1962 len
= IOBUF_SIZE
- (header_ptr
- header_buf
) - 4;
1963 memcpy(header_ptr
, iobuf
, len
);
1965 header_ptr
[0] = '\r';
1966 header_ptr
[1] = '\n';
1971 #if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY
1972 /* Try and do our best to parse more lines */
1973 if ((STRNCASECMP(iobuf
, "Content-length:") == 0)) {
1974 /* extra read only for POST */
1975 if (prequest
!= request_GET
1976 #if ENABLE_FEATURE_HTTPD_CGI
1977 && prequest
!= request_HEAD
1980 tptr
= skip_whitespace(iobuf
+ sizeof("Content-length:") - 1);
1982 send_headers_and_exit(HTTP_BAD_REQUEST
);
1983 /* not using strtoul: it ignores leading minus! */
1984 length
= bb_strtou(tptr
, NULL
, 10);
1985 /* length is "ulong", but we need to pass it to int later */
1986 if (errno
|| length
> INT_MAX
)
1987 send_headers_and_exit(HTTP_BAD_REQUEST
);
1991 #if ENABLE_FEATURE_HTTPD_CGI
1992 else if (STRNCASECMP(iobuf
, "Cookie:") == 0) {
1993 cookie
= xstrdup(skip_whitespace(iobuf
+ sizeof("Cookie:")-1));
1994 } else if (STRNCASECMP(iobuf
, "Content-Type:") == 0) {
1995 content_type
= xstrdup(skip_whitespace(iobuf
+ sizeof("Content-Type:")-1));
1996 } else if (STRNCASECMP(iobuf
, "Referer:") == 0) {
1997 referer
= xstrdup(skip_whitespace(iobuf
+ sizeof("Referer:")-1));
1998 } else if (STRNCASECMP(iobuf
, "User-Agent:") == 0) {
1999 user_agent
= xstrdup(skip_whitespace(iobuf
+ sizeof("User-Agent:")-1));
2000 } else if (STRNCASECMP(iobuf
, "Host:") == 0) {
2001 host
= xstrdup(skip_whitespace(iobuf
+ sizeof("Host:")-1));
2002 } else if (STRNCASECMP(iobuf
, "Accept:") == 0) {
2003 http_accept
= xstrdup(skip_whitespace(iobuf
+ sizeof("Accept:")-1));
2004 } else if (STRNCASECMP(iobuf
, "Accept-Language:") == 0) {
2005 http_accept_language
= xstrdup(skip_whitespace(iobuf
+ sizeof("Accept-Language:")-1));
2008 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
2009 if (STRNCASECMP(iobuf
, "Authorization:") == 0) {
2010 /* We only allow Basic credentials.
2011 * It shows up as "Authorization: Basic <user>:<passwd>" where
2012 * "<user>:<passwd>" is base64 encoded.
2014 tptr
= skip_whitespace(iobuf
+ sizeof("Authorization:")-1);
2015 if (STRNCASECMP(tptr
, "Basic") != 0)
2017 tptr
+= sizeof("Basic")-1;
2018 /* decodeBase64() skips whitespace itself */
2020 authorized
= check_user_passwd(urlcopy
, tptr
);
2023 #if ENABLE_FEATURE_HTTPD_RANGES
2024 if (STRNCASECMP(iobuf
, "Range:") == 0) {
2025 /* We know only bytes=NNN-[MMM] */
2026 char *s
= skip_whitespace(iobuf
+ sizeof("Range:")-1);
2027 if (strncmp(s
, "bytes=", 6) == 0) {
2028 s
+= sizeof("bytes=")-1;
2029 range_start
= BB_STRTOOFF(s
, &s
, 10);
2030 if (s
[0] != '-' || range_start
< 0) {
2033 range_end
= BB_STRTOOFF(s
+1, NULL
, 10);
2034 if (errno
|| range_end
< range_start
)
2040 } /* while extra header reading */
2043 /* We are done reading headers, disable peer timeout */
2046 if (strcmp(bb_basename(urlcopy
), HTTPD_CONF
) == 0 || !ip_allowed
) {
2047 /* protect listing [/path]/httpd.conf or IP deny */
2048 send_headers_and_exit(HTTP_FORBIDDEN
);
2051 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
2052 /* Case: no "Authorization:" was seen, but page does require passwd.
2053 * Check that with dummy user:pass */
2055 authorized
= check_user_passwd(urlcopy
, ":");
2057 send_headers_and_exit(HTTP_UNAUTHORIZED
);
2060 if (found_moved_temporarily
) {
2061 send_headers_and_exit(HTTP_MOVED_TEMPORARILY
);
2064 #if ENABLE_FEATURE_HTTPD_PROXY
2065 if (proxy_entry
!= NULL
) {
2067 len_and_sockaddr
*lsa
;
2069 proxy_fd
= socket(AF_INET
, SOCK_STREAM
, 0);
2071 send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR
);
2072 lsa
= host2sockaddr(proxy_entry
->host_port
, 80);
2074 send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR
);
2075 if (connect(proxy_fd
, &lsa
->u
.sa
, lsa
->len
) < 0)
2076 send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR
);
2077 fdprintf(proxy_fd
, "%s %s%s%s%s HTTP/%c.%c\r\n",
2078 prequest
, /* GET or POST */
2079 proxy_entry
->url_to
, /* url part 1 */
2080 urlcopy
+ strlen(proxy_entry
->url_from
), /* url part 2 */
2081 (g_query
? "?" : ""), /* "?" (maybe) */
2082 (g_query
? g_query
: ""), /* query string (maybe) */
2083 http_major_version
, http_minor_version
);
2084 header_ptr
[0] = '\r';
2085 header_ptr
[1] = '\n';
2087 write(proxy_fd
, header_buf
, header_ptr
- header_buf
);
2088 free(header_buf
); /* on the order of 8k, free it */
2089 /* cgi_io_loop_and_exit needs to have two distinct fds */
2090 cgi_io_loop_and_exit(proxy_fd
, dup(proxy_fd
), length
);
2094 tptr
= urlcopy
+ 1; /* skip first '/' */
2096 #if ENABLE_FEATURE_HTTPD_CGI
2097 if (strncmp(tptr
, "cgi-bin/", 8) == 0) {
2098 if (tptr
[8] == '\0') {
2099 /* protect listing "cgi-bin/" */
2100 send_headers_and_exit(HTTP_FORBIDDEN
);
2102 send_cgi_and_exit(urlcopy
, prequest
, length
, cookie
, content_type
);
2104 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
2106 char *suffix
= strrchr(tptr
, '.');
2109 for (cur
= script_i
; cur
; cur
= cur
->next
) {
2110 if (strcmp(cur
->before_colon
+ 1, suffix
) == 0) {
2111 send_cgi_and_exit(urlcopy
, prequest
, length
, cookie
, content_type
);
2117 if (prequest
!= request_GET
&& prequest
!= request_HEAD
) {
2118 send_headers_and_exit(HTTP_NOT_IMPLEMENTED
);
2120 #endif /* FEATURE_HTTPD_CGI */
2122 if (urlp
[-1] == '/')
2123 strcpy(urlp
, index_page
);
2124 if (stat(tptr
, &sb
) == 0) {
2125 file_size
= sb
.st_size
;
2126 last_mod
= sb
.st_mtime
;
2128 #if ENABLE_FEATURE_HTTPD_CGI
2129 else if (urlp
[-1] == '/') {
2130 /* It's a dir URL and there is no index.html
2131 * Try cgi-bin/index.cgi */
2132 if (access("/cgi-bin/index.cgi"+1, X_OK
) == 0) {
2135 send_cgi_and_exit("/cgi-bin/index.cgi", prequest
, length
, cookie
, content_type
);
2140 * fall through to send_file, it errors out if open fails
2144 send_file_and_exit(tptr
,
2145 #if ENABLE_FEATURE_HTTPD_CGI
2146 (prequest
!= request_HEAD
? SEND_HEADERS_AND_BODY
: SEND_HEADERS
)
2148 SEND_HEADERS_AND_BODY
2154 * The main http server function.
2155 * Given a socket, listen for new connections and farm out
2156 * the processing as a [v]forked process.
2160 static void mini_httpd(int server_socket
) NORETURN
;
2161 static void mini_httpd(int server_socket
)
2163 /* NB: it's best to not use xfuncs in this loop before fork().
2164 * Otherwise server may die on transient errors (temporary
2165 * out-of-memory condition, etc), which is Bad(tm).
2166 * Try to do any dangerous calls after fork.
2170 len_and_sockaddr fromAddr
;
2172 /* Wait for connections... */
2173 fromAddr
.len
= LSA_SIZEOF_SA
;
2174 n
= accept(server_socket
, &fromAddr
.u
.sa
, &fromAddr
.len
);
2178 /* set the KEEPALIVE option to cull dead connections */
2179 setsockopt(n
, SOL_SOCKET
, SO_KEEPALIVE
, &const_int_1
, sizeof(const_int_1
));
2183 /* Do not reload config on HUP */
2184 signal(SIGHUP
, SIG_IGN
);
2185 close(server_socket
);
2189 handle_incoming_and_exit(&fromAddr
);
2191 /* parent, or fork failed */
2197 static void mini_httpd_nommu(int server_socket
, int argc
, char **argv
) NORETURN
;
2198 static void mini_httpd_nommu(int server_socket
, int argc
, char **argv
)
2200 char *argv_copy
[argc
+ 2];
2202 argv_copy
[0] = argv
[0];
2203 argv_copy
[1] = (char*)"-i";
2204 memcpy(&argv_copy
[2], &argv
[1], argc
* sizeof(argv
[0]));
2206 /* NB: it's best to not use xfuncs in this loop before vfork().
2207 * Otherwise server may die on transient errors (temporary
2208 * out-of-memory condition, etc), which is Bad(tm).
2209 * Try to do any dangerous calls after fork.
2213 len_and_sockaddr fromAddr
;
2215 /* Wait for connections... */
2216 fromAddr
.len
= LSA_SIZEOF_SA
;
2217 n
= accept(server_socket
, &fromAddr
.u
.sa
, &fromAddr
.len
);
2221 /* set the KEEPALIVE option to cull dead connections */
2222 setsockopt(n
, SOL_SOCKET
, SO_KEEPALIVE
, &const_int_1
, sizeof(const_int_1
));
2226 /* Do not reload config on HUP */
2227 signal(SIGHUP
, SIG_IGN
);
2228 close(server_socket
);
2232 /* Run a copy of ourself in inetd mode */
2235 /* parent, or vfork failed */
2243 * Process a HTTP connection on stdin/out.
2246 static void mini_httpd_inetd(void) NORETURN
;
2247 static void mini_httpd_inetd(void)
2249 len_and_sockaddr fromAddr
;
2251 memset(&fromAddr
, 0, sizeof(fromAddr
));
2252 fromAddr
.len
= LSA_SIZEOF_SA
;
2253 /* NB: can fail if user runs it by hand and types in http cmds */
2254 getpeername(0, &fromAddr
.u
.sa
, &fromAddr
.len
);
2255 handle_incoming_and_exit(&fromAddr
);
2258 static void sighup_handler(int sig UNUSED_PARAM
)
2260 parse_conf(DEFAULT_PATH_HTTPD_CONF
, SIGNALED_PARSE
);
2264 c_opt_config_file
= 0,
2267 USE_FEATURE_HTTPD_ENCODE_URL_STR(e_opt_encode_url
,)
2268 USE_FEATURE_HTTPD_BASIC_AUTH( r_opt_realm
,)
2269 USE_FEATURE_HTTPD_AUTH_MD5( m_opt_md5
,)
2270 USE_FEATURE_HTTPD_SETUID( u_opt_setuid
,)
2275 OPT_CONFIG_FILE
= 1 << c_opt_config_file
,
2276 OPT_DECODE_URL
= 1 << d_opt_decode_url
,
2277 OPT_HOME_HTTPD
= 1 << h_opt_home_httpd
,
2278 OPT_ENCODE_URL
= USE_FEATURE_HTTPD_ENCODE_URL_STR((1 << e_opt_encode_url
)) + 0,
2279 OPT_REALM
= USE_FEATURE_HTTPD_BASIC_AUTH( (1 << r_opt_realm
)) + 0,
2280 OPT_MD5
= USE_FEATURE_HTTPD_AUTH_MD5( (1 << m_opt_md5
)) + 0,
2281 OPT_SETUID
= USE_FEATURE_HTTPD_SETUID( (1 << u_opt_setuid
)) + 0,
2282 OPT_PORT
= 1 << p_opt_port
,
2283 OPT_INETD
= 1 << p_opt_inetd
,
2284 OPT_FOREGROUND
= 1 << p_opt_foreground
,
2285 OPT_VERBOSE
= 1 << p_opt_verbose
,
2289 int httpd_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
2290 int httpd_main(int argc UNUSED_PARAM
, char **argv
)
2292 int server_socket
= server_socket
; /* for gcc */
2294 char *url_for_decode
;
2295 USE_FEATURE_HTTPD_ENCODE_URL_STR(const char *url_for_encode
;)
2296 USE_FEATURE_HTTPD_SETUID(const char *s_ugid
= NULL
;)
2297 USE_FEATURE_HTTPD_SETUID(struct bb_uidgid_t ugid
;)
2298 USE_FEATURE_HTTPD_AUTH_MD5(const char *pass
;)
2302 #if ENABLE_LOCALE_SUPPORT
2303 /* Undo busybox.c: we want to speak English in http (dates etc) */
2304 setlocale(LC_TIME
, "C");
2307 home_httpd
= xrealloc_getcwd_or_warn(NULL
);
2308 /* -v counts, -i implies -f */
2309 opt_complementary
= "vv:if";
2310 /* We do not "absolutize" path given by -h (home) opt.
2311 * If user gives relative path in -h,
2312 * $SCRIPT_FILENAME will not be set. */
2313 opt
= getopt32(argv
, "c:d:h:"
2314 USE_FEATURE_HTTPD_ENCODE_URL_STR("e:")
2315 USE_FEATURE_HTTPD_BASIC_AUTH("r:")
2316 USE_FEATURE_HTTPD_AUTH_MD5("m:")
2317 USE_FEATURE_HTTPD_SETUID("u:")
2319 &opt_c_configFile
, &url_for_decode
, &home_httpd
2320 USE_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode
)
2321 USE_FEATURE_HTTPD_BASIC_AUTH(, &g_realm
)
2322 USE_FEATURE_HTTPD_AUTH_MD5(, &pass
)
2323 USE_FEATURE_HTTPD_SETUID(, &s_ugid
)
2324 , &bind_addr_or_port
2327 if (opt
& OPT_DECODE_URL
) {
2328 fputs(decodeString(url_for_decode
, 1), stdout
);
2331 #if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR
2332 if (opt
& OPT_ENCODE_URL
) {
2333 fputs(encodeString(url_for_encode
), stdout
);
2337 #if ENABLE_FEATURE_HTTPD_AUTH_MD5
2338 if (opt
& OPT_MD5
) {
2339 puts(pw_encrypt(pass
, "$1$", 1));
2343 #if ENABLE_FEATURE_HTTPD_SETUID
2344 if (opt
& OPT_SETUID
) {
2345 xget_uidgid(&ugid
, s_ugid
);
2350 if (!(opt
& OPT_FOREGROUND
)) {
2351 bb_daemonize_or_rexec(0, argv
); /* don't change current directory */
2356 if (!(opt
& OPT_INETD
)) {
2357 signal(SIGCHLD
, SIG_IGN
);
2358 server_socket
= openServer();
2359 #if ENABLE_FEATURE_HTTPD_SETUID
2360 /* drop privileges */
2361 if (opt
& OPT_SETUID
) {
2362 if (ugid
.gid
!= (gid_t
)-1) {
2363 if (setgroups(1, &ugid
.gid
) == -1)
2364 bb_perror_msg_and_die("setgroups");
2373 /* User can do it himself: 'env - PATH="$PATH" httpd'
2374 * We don't do it because we don't want to screw users
2376 * 'env - VAR1=val1 VAR2=val2 httpd'
2377 * and have VAR1 and VAR2 values visible in their CGIs.
2378 * Besides, it is also smaller. */
2380 char *p
= getenv("PATH");
2381 /* env strings themself are not freed, no need to xstrdup(p): */
2385 // if (!(opt & OPT_INETD))
2386 // setenv_long("SERVER_PORT", ???);
2390 parse_conf(DEFAULT_PATH_HTTPD_CONF
, FIRST_PARSE
);
2391 if (!(opt
& OPT_INETD
))
2392 signal(SIGHUP
, sighup_handler
);
2394 xfunc_error_retval
= 0;
2395 if (opt
& OPT_INETD
)
2398 if (!(opt
& OPT_FOREGROUND
))
2399 bb_daemonize(0); /* don't change current directory */
2400 mini_httpd(server_socket
); /* never returns */
2402 mini_httpd_nommu(server_socket
, argc
, argv
); /* never returns */