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