source: MondoRescue/branches/stable/mindi-busybox/networking/httpd.c@ 1770

Last change on this file since 1770 was 1770, checked in by Bruno Cornec, 16 years ago
  • Better output for mindi-busybox revision
  • Remove dummy file created on NFS - report from Arnaud Tiger <arnaud.tiger_at_hp.com>
  • strace useful for debug
  • fix new versions for pb (2.0.0 for mindi and 1.7.2 for mindi-busybox)
  • fix build process for mindi-busybox + options used in that version (dd for label-partitions-as-necessary)
  • fix typo in label-partitions-as-necessary which doesn't seem to work
  • Update to busybox 1.7.2
  • perl is now required at restore time to support uuid swap partitions (and will be used for many other thigs

in the future for sure)

  • next mindi version will be 2.0.0 due to all the changes made in it (udev may break working distros)
  • small optimization in mindi on keyboard handling (one single find instead of multiple)
  • better interaction for USB device when launching mindi manually
  • attempt to automatically guess block disk size for ramdisk
  • fix typos in bkphw
  • Fix the remaining problem with UUID support for swap partitions
  • Updates mondoarchive man page for USB support
  • Adds preliminary Hardware support to mindi (Proliant SSSTK)
  • Tries to add udev support also for rhel4
  • Fix UUID support which was still broken.
  • Be conservative in test for the start-nfs script
  • Update config file for mindi-busybox for 1.7.2 migration
  • Try to run around a busybox bug (1.2.2 pb on inexistant links)
  • Add build content for mindi-busybox in pb
  • Remove distributions content for mindi-busybox
  • Fix a warning on inexistant raidtab
  • Solve problem on tmpfs in restore init (Problem of inexistant symlink and busybox)
  • Create MONDO_CACHE and use it everywhere + creation at start
  • Really never try to eject a USB device
  • Fix a issue with &> usage (replaced with 1> and 2>)
  • Adds magic file to depllist in order to have file working + ldd which helps for debugging issues
  • tty modes correct to avoid sh error messages
  • Use ext3 normally and not ext2 instead
  • USB device should be corrected after reading (take 1st part)
  • Adds a mount_USB_here function derived from mount_CDROM_here
  • usb detection place before /dev detection in device name at restore time
  • Fix when restoring from USB: media is asked in interactive mode
  • Adds USB support for mondorestore
  • mount_cdrom => mount_media
  • elilo.efi is now searched throughout /boot/efi and not in a fixed place as there is no standard
  • untar-and-softlink => untar (+ interface change)
  • suppress useless softlinks creation/removal in boot process
  • avoids udevd messages on groups
  • Increase # of disks to 99 as in mindi at restore time (should be a conf file parameter)
  • skip existing big file creation
  • seems to work correctly for USB mindi boot
  • Adds group and tty link to udev conf
  • Always load usb-torage (even 2.6) to initiate USB bus discovery
  • Better printing of messages
  • Attempt to fix a bug in supporting OpenSusE 10.3 kernel for initramfs (mindi may now use multiple regex for kernel initrd detection)
  • Links were not correctly done as non relative for modules in mindi
  • exclusion of modules denied now works
  • Also create modules in their ordinary place, so that classical modprobe works + copy modules.dep
  • Fix bugs for DENY_MODS handling
  • Add device /dev/console for udev
  • ide-generic should now really be excluded
  • Fix a bug in major number for tty
  • If udev then adds modprobe/insmod to rootfs
  • tty0 is also cretaed with udev
  • ide-generic put rather in DENY_MODS
  • udevd remove from deplist s handled in mindi directly
  • better default for mindi when using --usb
  • Handles dynamically linked busybox (in case we want to use it soon ;-)
  • Adds fixed devices to create for udev
  • ide-generic should not be part of the initrd when using libata v2
  • support a dynamically linked udev (case on Ubuntu 7.10 and Mandriva 2008.0 so should be quite generic) This will give incitation to move to dyn. linked binaries in the initrd which will help for other tasks (ia6 4)
  • Improvement in udev support (do not use cl options not available in busybox)
  • Udev in mindi
    • auto creation of the right links at boot time with udev-links.conf(from Mandriva 2008.0)
    • rework startup of udev as current makes kernel crash (from Mandriva 2008.0)
    • add support for 64 bits udev
  • Try to render MyInsmod silent at boot time
  • Adds udev support (mandatory for newest distributions to avoid remapping of devices in a different way as on the original system)
  • We also need vaft format support for USB boot
  • Adds libusual support (Ubuntu 7.10 needs it for USB)
  • Improve Ubuntu/Debian keyboard detection and support
  • pbinit adapted to new pb (0.8.10). Filtering of docs done in it
  • Suppress some mondo warnings and errors on USB again
  • Tries to fix lack of files in deb mindi package
  • Verify should now work for USB devices
  • More log/mesages improvement for USB support
  • - Supress g_erase_tmpdir_and_scratchdir
  • Improve some log messages for USB support
  • Try to improve install in mindi to avoid issues with isolinux.cfg not installed vene if in the pkg :-(
  • Improve mindi-busybox build
  • In conformity with pb 0.8.9
  • Add support for Ubuntu 7.10 in build process
  • Add USB Key button to Menu UI (CD streamer removed)
  • Attempt to fix error messages on tmp/scratch files at the end by removing those dir at the latest possible.
  • Fix a bug linked to the size of the -E param which could be used (Arnaud Tiger/René Ribaud).
  • Integrate ~/.pbrc content into mondorescue.pb (required project-builder >= 0.8.7)
  • Put mondorescue in conformity with new pb filtering rules
  • Add USB support at restore time (no test done yet). New start-usb script PB varibale added where useful
  • Unmounting USB device before removal of temporary scratchdir
  • Stil refining USB copy back to mondo (one command was not executed)
  • No need to have the image subdor in the csratchdir when USB.
  • umount the USB partition before attempting to use it
  • Remove useless copy from mindi to mondo at end of USB handling

(risky merge, we are raising the limits of 2 diverging branches. The status of stable is not completely sure as such. Will need lots of tests, but it's not yet done :-()
(merge -r1692:1769 $SVN_M/branches/2.2.5)

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