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

Last change on this file since 1842 was 1842, checked in by Bruno Cornec, 12 years ago
  • Fix lack of /etc/raidtab at restore rime in the right place
  • Adds support for nls_utf8 and pata_serverworks Patches from Dirk Husung (husung_at_tu-harburg.de)
  • Add efibootmgr to deplist.txt in order to be able to modify EFI menu at restore time on ia64
  • Usage of the new testver param of pb 0.8.12 to deliver 2.2.5 packages in a test dir.
  • Update pbcl files for upcoming delivery of 2.2.5
  • Fix an issue on parted output (K instead of KB) in parted2fdisk at restore time
  • Adds support for alb/aft types of bonding on Intel cards (Mark Pinkerton <Mark.Pinkerton_at_emageon.com>)
  • Escape variables in a perl script like mindi-bkphw
  • Better Proliant support at restore time now.
  • Fix a label bug where the variable label should also be re-initialized at each loop
  • start-nfs should NOT do exit => kernel panix as init exits
  • some other fixes for init on denymods
  • Adds boot options denymods (for udev) and forcemods (if not udev)
  • kqemu is denied. Probably should also be done for kvm stuff
  • Fix again the bug on modules link at root of the initrd. Hopefully for good.
  • Wait more around the USB CD emulated by iLO as it takes up to 5 seconds to come up
  • Attempt to fix udev support for RHEL 5.1
  • Better support for automatic iLO Virtual Media with udev
  • Some VMWare support improvements (however, it seems that VMWare support won't be possible anytime soon)
  • during init at restore time, copy all static evices availble for udev, as some distro implementation are no

t yet supported correctly for udev. Not very elegant, but should solve our current issues

  • Fedora 8 now supported as a build target for pb
  • svn.log removed.
  • dependencies reviewd for rhel_3
  • Attempt to fix the lack of modules loaded at restore time - especially fs device drivers
  • removes bkphw dir before potential creation
  • render mount command more vebose at restore time
  • Should fix #217
  • Increase BOOT_SIZE and EXTRA_SIZE to support features such as HW recovery
  • Handles udev.files files which could be symlinks
  • Improve udev support for distro with compressed modules (mdv e.g.)
  • Fix modules.dep copy
  • /sbin/pam_console_apply is needed by mdv udev conf
  • Adds support for nohw boot option to avoid re-setuping the HW conf
  • Improved support for Proliant on cpqacuxe
  • RstHW should be called after driver initialization
  • For debian/ubuntu mindi dpends on mindi-busybox
  • Update mindi-busybox pbcl for correct debian changelog generation, and up2date infos
  • Should fix #215
  • Fix #214 (Thanks to xdelaruelle)
  • Fix again svn.log not used anymore with new pb version
  • Adaptation of build process to upcoming 0.8.12 version of pb
  • Remove MONOTONIC clock to continue to support 2.4 kernels (RHEL 3 e.g. or ESX)
  • Attempt to solve ia64 BMC access through the serial port
  • parted2fdisk binary no more generated
  • do not require perl modules (strict.pm) at restore time
  • try to avoid modprobe messages at restore time
  • on ia64 now use the perl script parted2fdisk at retore time also
  • Also modprobe modules for udev after decompressing the additional ones
  • replace gzip -v9 by gzip -c9. Fix a bug at least on ia64
  • For all modules supported, create symlinks under the mountpoint and extract dev files as some are not automat ically created at the moment
  • Support /vmfs/volumes for ESX
  • Finally do not use vdf for ESX. Only creates issues.
  • Avoids continuing hw support if no product name found
  • Improves ia64 support for bootable image build
  • Fix a potential problem with ramdisk_blocksize param
  • MAKEDEV should also be included in deplist.txt
  • More fixes for udev support for Debian
  • Do not mount /boot if it's already mounted - avoids ESX hang
  • Fix NICs renumbering on Debian at least
  • Udev startup improvement to support iLO + NFS so modprobing all modules seems required as of now
  • Update to version 1.7.3 of busybox for pb
  • Load USB earlier in order to support KBD such as with iLO
  • Do busybox ldd first in order to create a potential /lib64 link correctly and then use it later in the image
  • Use variable DF in mindi to be able to support the ESX vdf
  • Fix mindi for Debian x86_64 where /lib64 is a link
  • Fix issue at restore time for ext2fs params not reused (Fix from Klaus Ade Johnstad <klaus_at_skolelinux.no>)
  • Do not copy udev files if they do not exist In udev case do not insert modules at all (udev should do it alone) May avoid the issue with rhel4.5 kernel ? To be tested
  • Update dependencies for rpm base build as well
  • And also perl is a debian/ubuntu dep
  • Better debian/ubuntu dependecies requirements (mtools)
  • Fix modes on mindi-bkphw (Thanks Phil Walker phil.walker_at_hp.com)
  • Complete rev [1771] for upper case RESTORE cli with syslinux as well
  • Update mindi-busybox to 1.7.3
  • Use RESTORE consistently across mondo to restore without interaction (report from Takeshi Shoji t.shoji_at_tripodw.jp)

(merge -r1769:1841 $SVN_M/branches/2.2.5)

File size: 57.2 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    /* Accound for POSTDATA already in hdr_buf */
1190    bodyLen -= hdr_cnt;
1191
1192    /* This loop still looks messy. What is an exit criteria?
1193     * "CGI's output closed"? Or "CGI has exited"?
1194     * What to do if CGI has closed both input and output, but
1195     * didn't exit? etc... */
1196
1197    /* NB: breaking out of this loop jumps to log_and_exit() */
1198    while (1) {
1199        fd_set readSet;
1200        fd_set writeSet;
1201        int nfound;
1202        int count;
1203
1204        FD_ZERO(&readSet);
1205        FD_ZERO(&writeSet);
1206        FD_SET(fromCgi.rd, &readSet);
1207        if (bodyLen > 0 || hdr_cnt > 0) {
1208            FD_SET(toCgi.wr, &writeSet);
1209            nfound = toCgi.wr > fromCgi.rd ? toCgi.wr : fromCgi.rd;
1210            if (hdr_cnt <= 0)
1211                FD_SET(0, &readSet);
1212            /* Now wait on the set of sockets! */
1213            nfound = select(nfound + 1, &readSet, &writeSet, NULL, NULL);
1214        } else {
1215            if (!bodyLen) {
1216                close(toCgi.wr); /* no more POST data to CGI */
1217                bodyLen = -1;
1218            }
1219            nfound = select(fromCgi.rd + 1, &readSet, NULL, NULL, NULL);
1220        }
1221
1222        if (nfound <= 0) {
1223            if (waitpid(pid, &status, WNOHANG) <= 0) {
1224                /* Weird. CGI didn't exit and no fd's
1225                 * are ready, yet select returned?! */
1226                continue;
1227            }
1228            close(fromCgi.rd);
1229            if (DEBUG && WIFEXITED(status))
1230                bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status));
1231            if (DEBUG && WIFSIGNALED(status))
1232                bb_error_msg("CGI killed, signal=%d", WTERMSIG(status));
1233            break;
1234        }
1235
1236        if (hdr_cnt > 0 && FD_ISSET(toCgi.wr, &writeSet)) {
1237            /* Have data from peer and can write to CGI */
1238            count = safe_write(toCgi.wr, hdr_ptr, hdr_cnt);
1239            /* Doesn't happen, we dont use nonblocking IO here
1240             *if (count < 0 && errno == EAGAIN) {
1241             *  ...
1242             *} else */
1243            if (count > 0) {
1244                hdr_ptr += count;
1245                hdr_cnt -= count;
1246            } else {
1247                hdr_cnt = bodyLen = 0; /* EOF/broken pipe to CGI */
1248            }
1249        } else if (bodyLen > 0 && hdr_cnt == 0
1250         && FD_ISSET(0, &readSet)
1251        ) {
1252            /* We expect data, prev data portion is eaten by CGI
1253             * and there *is* data to read from the peer
1254             * (POSTDATA?) */
1255            count = bodyLen > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : bodyLen;
1256            count = safe_read(0, hdr_buf, count);
1257            if (count > 0) {
1258                hdr_cnt = count;
1259                hdr_ptr = hdr_buf;
1260                bodyLen -= count;
1261            } else {
1262                bodyLen = 0; /* closed */
1263            }
1264        }
1265
1266#define PIPESIZE PIPE_BUF
1267#if PIPESIZE >= IOBUF_SIZE
1268# error "PIPESIZE >= IOBUF_SIZE"
1269#endif
1270        if (FD_ISSET(fromCgi.rd, &readSet)) {
1271            /* There is something to read from CGI */
1272            char *rbuf = iobuf;
1273
1274            /* Are we still buffering CGI output? */
1275            if (buf_count >= 0) {
1276                /* HTTP_200[] has single "\r\n" at the end.
1277                 * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html,
1278                 * CGI scripts MUST send their own header terminated by
1279                 * empty line, then data. That's why we have only one
1280                 * <cr><lf> pair here. We will output "200 OK" line
1281                 * if needed, but CGI still has to provide blank line
1282                 * between header and body */
1283
1284                /* Must use safe_read, not full_read, because
1285                 * CGI may output a few first bytes and then wait
1286                 * for POSTDATA without closing stdout.
1287                 * With full_read we may wait here forever. */
1288                count = safe_read(fromCgi.rd, rbuf + buf_count, PIPESIZE - 8);
1289                if (count <= 0) {
1290                    /* eof (or error) and there was no "HTTP",
1291                     * so write it, then write received data */
1292                    if (buf_count) {
1293                        full_write(1, HTTP_200, sizeof(HTTP_200)-1);
1294                        full_write(1, rbuf, buf_count);
1295                    }
1296                    break; /* CGI stdout is closed, exiting */
1297                }
1298                buf_count += count;
1299                count = 0;
1300                /* "Status" header format is: "Status: 302 Redirected\r\n" */
1301                if (buf_count >= 8 && memcmp(rbuf, "Status: ", 8) == 0) {
1302                    /* send "HTTP/1.0 " */
1303                    if (full_write(1, HTTP_200, 9) != 9)
1304                        break;
1305                    rbuf += 8; /* skip "Status: " */
1306                    count = buf_count - 8;
1307                    buf_count = -1; /* buffering off */
1308                } else if (buf_count >= 4) {
1309                    /* Did CGI add "HTTP"? */
1310                    if (memcmp(rbuf, HTTP_200, 4) != 0) {
1311                        /* there is no "HTTP", do it ourself */
1312                        if (full_write(1, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1)
1313                            break;
1314                    }
1315                    /* Commented out:
1316                    if (!strstr(rbuf, "ontent-")) {
1317                        full_write(s, "Content-type: text/plain\r\n\r\n", 28);
1318                    }
1319                     * Counter-example of valid CGI without Content-type:
1320                     * echo -en "HTTP/1.0 302 Found\r\n"
1321                     * echo -en "Location: http://www.busybox.net\r\n"
1322                     * echo -en "\r\n"
1323                     */
1324                    count = buf_count;
1325                    buf_count = -1; /* buffering off */
1326                }
1327            } else {
1328                count = safe_read(fromCgi.rd, rbuf, PIPESIZE);
1329                if (count <= 0)
1330                    break;  /* eof (or error) */
1331            }
1332            if (full_write(1, rbuf, count) != count)
1333                break;
1334            if (DEBUG)
1335                fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf);
1336        } /* if (FD_ISSET(fromCgi.rd)) */
1337    } /* while (1) */
1338    log_and_exit();
1339}
1340#endif          /* FEATURE_HTTPD_CGI */
1341
1342/*
1343 * Send a file response to a HTTP request, and exit
1344 *
1345 * Parameters:
1346 * const char *url    The requested URL (with leading /).
1347 * headers            Don't send headers before if FALSE.
1348 */
1349static void send_file_and_exit(const char *url, int headers)
1350{
1351    static const char *const suffixTable[] = {
1352    /* Warning: shorter equivalent suffix in one line must be first */
1353        ".htm.html", "text/html",
1354        ".jpg.jpeg", "image/jpeg",
1355        ".gif",      "image/gif",
1356        ".png",      "image/png",
1357        ".txt.h.c.cc.cpp", "text/plain",
1358        ".css",      "text/css",
1359        ".wav",      "audio/wav",
1360        ".avi",      "video/x-msvideo",
1361        ".qt.mov",   "video/quicktime",
1362        ".mpe.mpeg", "video/mpeg",
1363        ".mid.midi", "audio/midi",
1364        ".mp3",      "audio/mpeg",
1365#if 0                        /* unpopular */
1366        ".au",       "audio/basic",
1367        ".pac",      "application/x-ns-proxy-autoconfig",
1368        ".vrml.wrl", "model/vrml",
1369#endif
1370        NULL
1371    };
1372
1373    char *suffix;
1374    int f;
1375    const char *const *table;
1376    const char *try_suffix;
1377    ssize_t count;
1378#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
1379    off_t offset = 0;
1380#endif
1381
1382    suffix = strrchr(url, '.');
1383
1384    /* If not found, set default as "application/octet-stream";  */
1385    found_mime_type = "application/octet-stream";
1386    if (suffix) {
1387#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
1388        Htaccess *cur;
1389#endif
1390        for (table = suffixTable; *table; table += 2) {
1391            try_suffix = strstr(table[0], suffix);
1392            if (try_suffix) {
1393                try_suffix += strlen(suffix);
1394                if (*try_suffix == '\0' || *try_suffix == '.') {
1395                    found_mime_type = table[1];
1396                    break;
1397                }
1398            }
1399        }
1400#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
1401        for (cur = mime_a; cur; cur = cur->next) {
1402            if (strcmp(cur->before_colon, suffix) == 0) {
1403                found_mime_type = cur->after_colon;
1404                break;
1405            }
1406        }
1407#endif
1408    }
1409
1410    if (DEBUG)
1411        bb_error_msg("sending file '%s' content-type: %s",
1412            url, found_mime_type);
1413
1414    f = open(url, O_RDONLY);
1415    if (f < 0) {
1416        if (DEBUG)
1417            bb_perror_msg("cannot open '%s'", url);
1418        if (headers)
1419            send_headers_and_exit(HTTP_NOT_FOUND);
1420    }
1421
1422    if (headers)
1423        send_headers(HTTP_OK);
1424
1425    /* If you want to know about EPIPE below
1426     * (happens if you abort downloads from local httpd): */
1427    signal(SIGPIPE, SIG_IGN);
1428
1429#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
1430    do {
1431        /* byte count (3rd arg) is rounded down to 64k */
1432        count = sendfile(1, f, &offset, MAXINT(ssize_t) - 0xffff);
1433        if (count < 0) {
1434            if (offset == 0)
1435                goto fallback;
1436            goto fin;
1437        }
1438    } while (count > 0);
1439    log_and_exit();
1440
1441 fallback:
1442#endif
1443    while ((count = safe_read(f, iobuf, IOBUF_SIZE)) > 0) {
1444        ssize_t n = count;
1445        count = full_write(1, iobuf, count);
1446        if (count != n)
1447            break;
1448    }
1449#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
1450 fin:
1451#endif
1452    if (count < 0 && verbose > 1)
1453        bb_perror_msg("error");
1454    log_and_exit();
1455}
1456
1457static int checkPermIP(void)
1458{
1459    Htaccess_IP *cur;
1460
1461    /* This could stand some work */
1462    for (cur = ip_a_d; cur; cur = cur->next) {
1463#if DEBUG
1464        fprintf(stderr,
1465            "checkPermIP: '%s' ? '%u.%u.%u.%u/%u.%u.%u.%u'\n",
1466            rmt_ip_str,
1467            (unsigned char)(cur->ip >> 24),
1468            (unsigned char)(cur->ip >> 16),
1469            (unsigned char)(cur->ip >> 8),
1470            (unsigned char)(cur->ip),
1471            (unsigned char)(cur->mask >> 24),
1472            (unsigned char)(cur->mask >> 16),
1473            (unsigned char)(cur->mask >> 8),
1474            (unsigned char)(cur->mask)
1475        );
1476#endif
1477        if ((rmt_ip & cur->mask) == cur->ip)
1478            return cur->allow_deny == 'A';   /* Allow/Deny */
1479    }
1480
1481    /* if unconfigured, return 1 - access from all */
1482    return !flg_deny_all;
1483}
1484
1485#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1486/*
1487 * Check the permission file for access password protected.
1488 *
1489 * If config file isn't present, everything is allowed.
1490 * Entries are of the form you can see example from header source
1491 *
1492 * path      The file path.
1493 * request   User information to validate.
1494 *
1495 * Returns 1 if request is OK.
1496 */
1497static int checkPerm(const char *path, const char *request)
1498{
1499    Htaccess *cur;
1500    const char *p;
1501    const char *p0;
1502
1503    const char *prev = NULL;
1504
1505    /* This could stand some work */
1506    for (cur = g_auth; cur; cur = cur->next) {
1507        size_t l;
1508
1509        p0 = cur->before_colon;
1510        if (prev != NULL && strcmp(prev, p0) != 0)
1511            continue;       /* find next identical */
1512        p = cur->after_colon;
1513        if (DEBUG)
1514            fprintf(stderr, "checkPerm: '%s' ? '%s'\n", p0, request);
1515
1516        l = strlen(p0);
1517        if (strncmp(p0, path, l) == 0
1518         && (l == 1 || path[l] == '/' || path[l] == '\0')
1519        ) {
1520            char *u;
1521            /* path match found.  Check request */
1522            /* for check next /path:user:password */
1523            prev = p0;
1524            u = strchr(request, ':');
1525            if (u == NULL) {
1526                /* bad request, ':' required */
1527                break;
1528            }
1529
1530            if (ENABLE_FEATURE_HTTPD_AUTH_MD5) {
1531                char *cipher;
1532                char *pp;
1533
1534                if (strncmp(p, request, u - request) != 0) {
1535                    /* user doesn't match */
1536                    continue;
1537                }
1538                pp = strchr(p, ':');
1539                if (pp && pp[1] == '$' && pp[2] == '1'
1540                 && pp[3] == '$' && pp[4]
1541                ) {
1542                    pp++;
1543                    cipher = pw_encrypt(u+1, pp);
1544                    if (strcmp(cipher, pp) == 0)
1545                        goto set_remoteuser_var;   /* Ok */
1546                    /* unauthorized */
1547                    continue;
1548                }
1549            }
1550
1551            if (strcmp(p, request) == 0) {
1552 set_remoteuser_var:
1553                remoteuser = strdup(request);
1554                if (remoteuser)
1555                    remoteuser[u - request] = '\0';
1556                return 1;   /* Ok */
1557            }
1558            /* unauthorized */
1559        }
1560    } /* for */
1561
1562    return prev == NULL;
1563}
1564#endif  /* FEATURE_HTTPD_BASIC_AUTH */
1565
1566/*
1567 * Handle timeouts
1568 */
1569static void exit_on_signal(int sig) ATTRIBUTE_NORETURN;
1570static void exit_on_signal(int sig)
1571{
1572    send_headers_and_exit(HTTP_REQUEST_TIMEOUT);
1573}
1574
1575/*
1576 * Handle an incoming http request and exit.
1577 */
1578static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) ATTRIBUTE_NORETURN;
1579static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
1580{
1581    static const char request_GET[] ALIGN1 = "GET";
1582
1583    struct stat sb;
1584    char *urlcopy;
1585    char *urlp;
1586    char *tptr;
1587    int http_major_version;
1588    int ip_allowed;
1589#if ENABLE_FEATURE_HTTPD_CGI
1590    const char *prequest;
1591    unsigned long length = 0;
1592    char *cookie = 0;
1593    char *content_type = 0;
1594#endif
1595    struct sigaction sa;
1596#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1597    int credentials = -1;  /* if not required this is Ok */
1598#endif
1599
1600    /* Allocation of iobuf is postponed until now
1601     * (IOW, server process doesn't need to waste 8k) */
1602    iobuf = xmalloc(IOBUF_SIZE);
1603
1604    rmt_ip = 0;
1605    if (fromAddr->sa.sa_family == AF_INET) {
1606        rmt_ip = ntohl(fromAddr->sin.sin_addr.s_addr);
1607    }
1608#if ENABLE_FEATURE_IPV6
1609    if (fromAddr->sa.sa_family == AF_INET6
1610     && fromAddr->sin6.sin6_addr.s6_addr32[0] == 0
1611     && fromAddr->sin6.sin6_addr.s6_addr32[1] == 0
1612     && ntohl(fromAddr->sin6.sin6_addr.s6_addr32[2]) == 0xffff)
1613        rmt_ip = ntohl(fromAddr->sin6.sin6_addr.s6_addr32[3]);
1614#endif
1615    if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) {
1616        rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr->sa);
1617    }
1618    if (verbose) {
1619        /* this trick makes -v logging much simpler */
1620        applet_name = rmt_ip_str;
1621        if (verbose > 2)
1622            bb_error_msg("connected");
1623    }
1624
1625    /* Install timeout handler */
1626    memset(&sa, 0, sizeof(sa));
1627    sa.sa_handler = exit_on_signal;
1628    /* sigemptyset(&sa.sa_mask); - memset should be enough */
1629    /*sa.sa_flags = 0; - no SA_RESTART */
1630    sigaction(SIGALRM, &sa, NULL);
1631    alarm(HEADER_READ_TIMEOUT);
1632
1633    if (!get_line()) /* EOF or error or empty line */
1634        send_headers_and_exit(HTTP_BAD_REQUEST);
1635
1636    /* Determine type of request (GET/POST) */
1637    urlp = strpbrk(iobuf, " \t");
1638    if (urlp == NULL)
1639        send_headers_and_exit(HTTP_BAD_REQUEST);
1640    *urlp++ = '\0';
1641#if ENABLE_FEATURE_HTTPD_CGI
1642    prequest = request_GET;
1643    if (strcasecmp(iobuf, prequest) != 0) {
1644        prequest = "POST";
1645        if (strcasecmp(iobuf, prequest) != 0)
1646            send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
1647    }
1648#else
1649    if (strcasecmp(iobuf, request_GET) != 0)
1650        send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
1651#endif
1652    urlp = skip_whitespace(urlp);
1653    if (urlp[0] != '/')
1654        send_headers_and_exit(HTTP_BAD_REQUEST);
1655
1656    /* Find end of URL and parse HTTP version, if any */
1657    http_major_version = -1;
1658    tptr = strchrnul(urlp, ' ');
1659    /* Is it " HTTP/"? */
1660    if (tptr[0] && strncmp(tptr + 1, HTTP_200, 5) == 0)
1661        http_major_version = (tptr[6] - '0');
1662    *tptr = '\0';
1663
1664    /* Copy URL from after "GET "/"POST " to stack-allocated char[] */
1665    urlcopy = alloca((tptr - urlp) + sizeof("/index.html"));
1666    /*if (urlcopy == NULL)
1667     *  send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);*/
1668    strcpy(urlcopy, urlp);
1669    /* NB: urlcopy ptr is never changed after this */
1670
1671    /* Extract url args if present */
1672    tptr = strchr(urlcopy, '?');
1673    g_query = NULL;
1674    if (tptr) {
1675        *tptr++ = '\0';
1676        g_query = tptr;
1677    }
1678
1679    /* Decode URL escape sequences */
1680    tptr = decodeString(urlcopy, 0);
1681    if (tptr == NULL)
1682        send_headers_and_exit(HTTP_BAD_REQUEST);
1683    if (tptr == urlcopy + 1) {
1684        /* '/' or NUL is encoded */
1685        send_headers_and_exit(HTTP_NOT_FOUND);
1686    }
1687
1688    /* Canonicalize path */
1689    /* Algorithm stolen from libbb bb_simplify_path(),
1690     * but don't strdup and reducing trailing slash and protect out root */
1691    urlp = tptr = urlcopy;
1692    do {
1693        if (*urlp == '/') {
1694            /* skip duplicate (or initial) slash */
1695            if (*tptr == '/') {
1696                continue;
1697            }
1698            if (*tptr == '.') {
1699                /* skip extra '.' */
1700                if (tptr[1] == '/' || !tptr[1]) {
1701                    continue;
1702                }
1703                /* '..': be careful */
1704                if (tptr[1] == '.' && (tptr[2] == '/' || !tptr[2])) {
1705                    ++tptr;
1706                    if (urlp == urlcopy) /* protect root */
1707                        send_headers_and_exit(HTTP_BAD_REQUEST);
1708                    while (*--urlp != '/') /* omit previous dir */;
1709                        continue;
1710                }
1711            }
1712        }
1713        *++urlp = *tptr;
1714    } while (*++tptr);
1715    *++urlp = '\0';       /* so keep last character */
1716    tptr = urlp;          /* end ptr */
1717
1718    /* If URL is a directory, add '/' */
1719    if (tptr[-1] != '/') {
1720        if (is_directory(urlcopy + 1, 1, &sb)) {
1721            found_moved_temporarily = urlcopy;
1722        }
1723    }
1724
1725    /* Log it */
1726    if (verbose > 1)
1727        bb_error_msg("url:%s", urlcopy);
1728
1729    tptr = urlcopy;
1730    ip_allowed = checkPermIP();
1731    while (ip_allowed && (tptr = strchr(tptr + 1, '/')) != NULL) {
1732        /* have path1/path2 */
1733        *tptr = '\0';
1734        if (is_directory(urlcopy + 1, 1, &sb)) {
1735            /* may be having subdir config */
1736            parse_conf(urlcopy + 1, SUBDIR_PARSE);
1737            ip_allowed = checkPermIP();
1738        }
1739        *tptr = '/';
1740    }
1741    if (http_major_version >= 0) {
1742        /* Request was with "... HTTP/nXXX", and n >= 0 */
1743
1744        /* Read until blank line for HTTP version specified, else parse immediate */
1745        while (1) {
1746            alarm(HEADER_READ_TIMEOUT);
1747            if (!get_line())
1748                break; /* EOF or error or empty line */
1749            if (DEBUG)
1750                bb_error_msg("header: '%s'", iobuf);
1751
1752#if ENABLE_FEATURE_HTTPD_CGI
1753            /* try and do our best to parse more lines */
1754            if ((STRNCASECMP(iobuf, "Content-length:") == 0)) {
1755                /* extra read only for POST */
1756                if (prequest != request_GET) {
1757                    tptr = iobuf + sizeof("Content-length:") - 1;
1758                    if (!tptr[0])
1759                        send_headers_and_exit(HTTP_BAD_REQUEST);
1760                    errno = 0;
1761                    /* not using strtoul: it ignores leading minus! */
1762                    length = strtol(tptr, &tptr, 10);
1763                    /* length is "ulong", but we need to pass it to int later */
1764                    /* so we check for negative or too large values in one go: */
1765                    /* (long -> ulong conv caused negatives to be seen as > INT_MAX) */
1766                    if (tptr[0] || errno || length > INT_MAX)
1767                        send_headers_and_exit(HTTP_BAD_REQUEST);
1768                }
1769            } else if (STRNCASECMP(iobuf, "Cookie:") == 0) {
1770                cookie = strdup(skip_whitespace(iobuf + sizeof("Cookie:")-1));
1771            } else if (STRNCASECMP(iobuf, "Content-Type:") == 0) {
1772                content_type = strdup(skip_whitespace(iobuf + sizeof("Content-Type:")-1));
1773            } else if (STRNCASECMP(iobuf, "Referer:") == 0) {
1774                referer = strdup(skip_whitespace(iobuf + sizeof("Referer:")-1));
1775            } else if (STRNCASECMP(iobuf, "User-Agent:") == 0) {
1776                user_agent = strdup(skip_whitespace(iobuf + sizeof("User-Agent:")-1));
1777            }
1778#endif
1779#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1780            if (STRNCASECMP(iobuf, "Authorization:") == 0) {
1781                /* We only allow Basic credentials.
1782                 * It shows up as "Authorization: Basic <userid:password>" where
1783                 * the userid:password is base64 encoded.
1784                 */
1785                tptr = skip_whitespace(iobuf + sizeof("Authorization:")-1);
1786                if (STRNCASECMP(tptr, "Basic") != 0)
1787                    continue;
1788                tptr += sizeof("Basic")-1;
1789                /* decodeBase64() skips whitespace itself */
1790                decodeBase64(tptr);
1791                credentials = checkPerm(urlcopy, tptr);
1792            }
1793#endif          /* FEATURE_HTTPD_BASIC_AUTH */
1794        } /* while extra header reading */
1795    }
1796
1797    /* We read headers, disable peer timeout */
1798    alarm(0);
1799
1800    if (strcmp(bb_basename(urlcopy), httpd_conf) == 0 || ip_allowed == 0) {
1801        /* protect listing [/path]/httpd_conf or IP deny */
1802        send_headers_and_exit(HTTP_FORBIDDEN);
1803    }
1804
1805#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1806    if (credentials <= 0 && checkPerm(urlcopy, ":") == 0) {
1807        send_headers_and_exit(HTTP_UNAUTHORIZED);
1808    }
1809#endif
1810
1811    if (found_moved_temporarily) {
1812        send_headers_and_exit(HTTP_MOVED_TEMPORARILY);
1813    }
1814
1815    tptr = urlcopy + 1;      /* skip first '/' */
1816
1817#if ENABLE_FEATURE_HTTPD_CGI
1818    if (strncmp(tptr, "cgi-bin/", 8) == 0) {
1819        if (tptr[8] == '\0') {
1820            /* protect listing "cgi-bin/" */
1821            send_headers_and_exit(HTTP_FORBIDDEN);
1822        }
1823        send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type);
1824    }
1825#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
1826    {
1827        char *suffix = strrchr(tptr, '.');
1828        if (suffix) {
1829            Htaccess *cur;
1830            for (cur = script_i; cur; cur = cur->next) {
1831                if (strcmp(cur->before_colon + 1, suffix) == 0) {
1832                    send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type);
1833                }
1834            }
1835        }
1836    }
1837#endif
1838    if (prequest != request_GET) {
1839        send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
1840    }
1841#endif  /* FEATURE_HTTPD_CGI */
1842
1843    if (urlp[-1] == '/')
1844        strcpy(urlp, "index.html");
1845    if (stat(tptr, &sb) == 0) {
1846        /* It's a dir URL and there is index.html */
1847        ContentLength = sb.st_size;
1848        last_mod = sb.st_mtime;
1849    }
1850#if ENABLE_FEATURE_HTTPD_CGI
1851    else if (urlp[-1] == '/') {
1852        /* It's a dir URL and there is no index.html
1853         * Try cgi-bin/index.cgi */
1854        if (access("/cgi-bin/index.cgi"+1, X_OK) == 0) {
1855            urlp[0] = '\0';
1856            g_query = urlcopy;
1857            send_cgi_and_exit("/cgi-bin/index.cgi", prequest, length, cookie, content_type);
1858        }
1859    }
1860#endif
1861    /* else {
1862     *  fall through to send_file, it errors out if open fails
1863     * }
1864     */
1865
1866    send_file_and_exit(tptr, TRUE);
1867}
1868
1869/*
1870 * The main http server function.
1871 * Given a socket, listen for new connections and farm out
1872 * the processing as a [v]forked process.
1873 * Never returns.
1874 */
1875#if BB_MMU
1876static void mini_httpd(int server_socket) ATTRIBUTE_NORETURN;
1877static void mini_httpd(int server_socket)
1878{
1879    /* NB: it's best to not use xfuncs in this loop before fork().
1880     * Otherwise server may die on transient errors (temporary
1881     * out-of-memory condition, etc), which is Bad(tm).
1882     * Try to do any dangerous calls after fork.
1883     */
1884    while (1) {
1885        int n;
1886        len_and_sockaddr fromAddr;
1887       
1888        /* Wait for connections... */
1889        fromAddr.len = LSA_SIZEOF_SA;
1890        n = accept(server_socket, &fromAddr.sa, &fromAddr.len);
1891
1892        if (n < 0)
1893            continue;
1894        /* set the KEEPALIVE option to cull dead connections */
1895        setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
1896
1897        if (fork() == 0) {
1898            /* child */
1899#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
1900            /* Do not reload config on HUP */
1901            signal(SIGHUP, SIG_IGN);
1902#endif
1903            close(server_socket);
1904            xmove_fd(n, 0);
1905            xdup2(0, 1);
1906
1907            handle_incoming_and_exit(&fromAddr);
1908        }
1909        /* parent, or fork failed */
1910        close(n);
1911    } /* while (1) */
1912    /* never reached */
1913}
1914#else
1915static void mini_httpd_nommu(int server_socket, int argc, char **argv) ATTRIBUTE_NORETURN;
1916static void mini_httpd_nommu(int server_socket, int argc, char **argv)
1917{
1918    char *argv_copy[argc + 2];
1919
1920    argv_copy[0] = argv[0];
1921    argv_copy[1] = (char*)"-i";
1922    memcpy(&argv_copy[2], &argv[1], argc * sizeof(argv[0]));
1923
1924    /* NB: it's best to not use xfuncs in this loop before vfork().
1925     * Otherwise server may die on transient errors (temporary
1926     * out-of-memory condition, etc), which is Bad(tm).
1927     * Try to do any dangerous calls after fork.
1928     */
1929    while (1) {
1930        int n;
1931        len_and_sockaddr fromAddr;
1932       
1933        /* Wait for connections... */
1934        fromAddr.len = LSA_SIZEOF_SA;
1935        n = accept(server_socket, &fromAddr.sa, &fromAddr.len);
1936
1937        if (n < 0)
1938            continue;
1939        /* set the KEEPALIVE option to cull dead connections */
1940        setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
1941
1942        if (vfork() == 0) {
1943            /* child */
1944#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
1945            /* Do not reload config on HUP */
1946            signal(SIGHUP, SIG_IGN);
1947#endif
1948            close(server_socket);
1949            xmove_fd(n, 0);
1950            xdup2(0, 1);
1951
1952            /* Run a copy of ourself in inetd mode */
1953            re_exec(argv_copy);
1954        }
1955        /* parent, or vfork failed */
1956        close(n);
1957    } /* while (1) */
1958    /* never reached */
1959}
1960#endif
1961
1962/*
1963 * Process a HTTP connection on stdin/out.
1964 * Never returns.
1965 */
1966static void mini_httpd_inetd(void) ATTRIBUTE_NORETURN;
1967static void mini_httpd_inetd(void)
1968{
1969    len_and_sockaddr fromAddr;
1970
1971    fromAddr.len = LSA_SIZEOF_SA;
1972    getpeername(0, &fromAddr.sa, &fromAddr.len);
1973    handle_incoming_and_exit(&fromAddr);
1974}
1975
1976#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
1977static void sighup_handler(int sig)
1978{
1979    struct sigaction sa;
1980
1981    parse_conf(default_path_httpd_conf, sig == SIGHUP ? SIGNALED_PARSE : FIRST_PARSE);
1982
1983    memset(&sa, 0, sizeof(sa));
1984    sa.sa_handler = sighup_handler;
1985    /*sigemptyset(&sa.sa_mask); - memset should be enough */
1986    sa.sa_flags = SA_RESTART;
1987    sigaction(SIGHUP, &sa, NULL);
1988}
1989#endif
1990
1991enum {
1992    c_opt_config_file = 0,
1993    d_opt_decode_url,
1994    h_opt_home_httpd,
1995    USE_FEATURE_HTTPD_ENCODE_URL_STR(e_opt_encode_url,)
1996    USE_FEATURE_HTTPD_BASIC_AUTH(    r_opt_realm     ,)
1997    USE_FEATURE_HTTPD_AUTH_MD5(      m_opt_md5       ,)
1998    USE_FEATURE_HTTPD_SETUID(        u_opt_setuid    ,)
1999    p_opt_port      ,
2000    p_opt_inetd     ,
2001    p_opt_foreground,
2002    p_opt_verbose   ,
2003    OPT_CONFIG_FILE = 1 << c_opt_config_file,
2004    OPT_DECODE_URL  = 1 << d_opt_decode_url,
2005    OPT_HOME_HTTPD  = 1 << h_opt_home_httpd,
2006    OPT_ENCODE_URL  = USE_FEATURE_HTTPD_ENCODE_URL_STR((1 << e_opt_encode_url)) + 0,
2007    OPT_REALM       = USE_FEATURE_HTTPD_BASIC_AUTH(    (1 << r_opt_realm     )) + 0,
2008    OPT_MD5         = USE_FEATURE_HTTPD_AUTH_MD5(      (1 << m_opt_md5       )) + 0,
2009    OPT_SETUID      = USE_FEATURE_HTTPD_SETUID(        (1 << u_opt_setuid    )) + 0,
2010    OPT_PORT        = 1 << p_opt_port,
2011    OPT_INETD       = 1 << p_opt_inetd,
2012    OPT_FOREGROUND  = 1 << p_opt_foreground,
2013    OPT_VERBOSE     = 1 << p_opt_verbose,
2014};
2015
2016
2017int httpd_main(int argc, char **argv);
2018int httpd_main(int argc, char **argv)
2019{
2020    int server_socket = server_socket; /* for gcc */
2021    unsigned opt;
2022    char *url_for_decode;
2023    USE_FEATURE_HTTPD_ENCODE_URL_STR(const char *url_for_encode;)
2024    USE_FEATURE_HTTPD_SETUID(const char *s_ugid = NULL;)
2025    USE_FEATURE_HTTPD_SETUID(struct bb_uidgid_t ugid;)
2026    USE_FEATURE_HTTPD_AUTH_MD5(const char *pass;)
2027
2028    INIT_G();
2029
2030#if ENABLE_LOCALE_SUPPORT
2031    /* Undo busybox.c: we want to speak English in http (dates etc) */
2032    setlocale(LC_TIME, "C");
2033#endif
2034
2035    home_httpd = xrealloc_getcwd_or_warn(NULL);
2036    /* -v counts, -i implies -f */
2037    opt_complementary = "vv:if";
2038    /* We do not "absolutize" path given by -h (home) opt.
2039     * If user gives relative path in -h, $SCRIPT_FILENAME can end up
2040     * relative too. */
2041    opt = getopt32(argv, "c:d:h:"
2042            USE_FEATURE_HTTPD_ENCODE_URL_STR("e:")
2043            USE_FEATURE_HTTPD_BASIC_AUTH("r:")
2044            USE_FEATURE_HTTPD_AUTH_MD5("m:")
2045            USE_FEATURE_HTTPD_SETUID("u:")
2046            "p:ifv",
2047            &configFile, &url_for_decode, &home_httpd
2048            USE_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode)
2049            USE_FEATURE_HTTPD_BASIC_AUTH(, &g_realm)
2050            USE_FEATURE_HTTPD_AUTH_MD5(, &pass)
2051            USE_FEATURE_HTTPD_SETUID(, &s_ugid)
2052            , &bind_addr_or_port
2053            , &verbose
2054        );
2055    if (opt & OPT_DECODE_URL) {
2056        fputs(decodeString(url_for_decode, 1), stdout);
2057        return 0;
2058    }
2059#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR
2060    if (opt & OPT_ENCODE_URL) {
2061        fputs(encodeString(url_for_encode), stdout);
2062        return 0;
2063    }
2064#endif
2065#if ENABLE_FEATURE_HTTPD_AUTH_MD5
2066    if (opt & OPT_MD5) {
2067        puts(pw_encrypt(pass, "$1$"));
2068        return 0;
2069    }
2070#endif
2071#if ENABLE_FEATURE_HTTPD_SETUID
2072    if (opt & OPT_SETUID) {
2073        if (!get_uidgid(&ugid, s_ugid, 1))
2074            bb_error_msg_and_die("unrecognized user[:group] "
2075                        "name '%s'", s_ugid);
2076    }
2077#endif
2078
2079#if !BB_MMU
2080    if (!(opt & OPT_FOREGROUND)) {
2081        bb_daemonize_or_rexec(0, argv); /* don't change current directory */
2082    }
2083#endif
2084
2085    xchdir(home_httpd);
2086    if (!(opt & OPT_INETD)) {
2087        signal(SIGCHLD, SIG_IGN);
2088        server_socket = openServer();
2089#if ENABLE_FEATURE_HTTPD_SETUID
2090        /* drop privileges */
2091        if (opt & OPT_SETUID) {
2092            if (ugid.gid != (gid_t)-1) {
2093                if (setgroups(1, &ugid.gid) == -1)
2094                    bb_perror_msg_and_die("setgroups");
2095                xsetgid(ugid.gid);
2096            }
2097            xsetuid(ugid.uid);
2098        }
2099#endif
2100    }
2101
2102#if ENABLE_FEATURE_HTTPD_CGI
2103    {
2104        char *p = getenv("PATH");
2105        /* env strings themself are not freed, no need to strdup(p): */
2106        clearenv();
2107        if (p)
2108            putenv(p - 5);
2109//      if (!(opt & OPT_INETD))
2110//          setenv_long("SERVER_PORT", ???);
2111    }
2112#endif
2113
2114#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
2115    if (!(opt & OPT_INETD))
2116        sighup_handler(0);
2117    else /* do not install HUP handler in inetd mode */
2118#endif
2119        parse_conf(default_path_httpd_conf, FIRST_PARSE);
2120
2121    xfunc_error_retval = 0;
2122    if (opt & OPT_INETD)
2123        mini_httpd_inetd();
2124#if BB_MMU
2125    if (!(opt & OPT_FOREGROUND))
2126        bb_daemonize(0); /* don't change current directory */
2127    mini_httpd(server_socket); /* never returns */
2128#else
2129    mini_httpd_nommu(server_socket, argc, argv); /* never returns */
2130#endif
2131    /* return 0; */
2132}
Note: See TracBrowser for help on using the repository browser.