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

Last change on this file since 1770 was 1770, checked in by Bruno Cornec, 12 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.