source: MondoRescue/branches/2.2.5/mindi-busybox/networking/httpd.c @ 1765

Last change on this file since 1765 was 1765, checked in by Bruno Cornec, 12 years ago

Update to busybox 1.7.2

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.