source: MondoRescue/branches/3.3/mindi-busybox/networking/httpd.c@ 3734

Last change on this file since 3734 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

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