Ignore:
Timestamp:
Feb 25, 2011, 9:26:54 PM (13 years ago)
Author:
Bruno Cornec
Message:
  • Update mindi-busybox to 1.18.3 to avoid problems with the tar command which is now failing on recent versions with busybox 1.7.3
File:
1 edited

Legend:

Unmodified
Added
Removed
  • branches/2.2.9/mindi-busybox/networking/httpd.c

    r1772 r2725  
    66 * Copyright (C) 2003-2006 Vladimir Oleynik <dzo@simtreas.ru>
    77 *
    8  * simplify patch stolen from libbb without using strdup
    9  *
    10  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
     8 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
    119 *
    1210 *****************************************************************************
    1311 *
    1412 * 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
     13 * For non root user:
     14 *      httpd -p 8080 -h $HOME/public_html
     15 * For daemon start from rc script with uid=0:
     16 *      httpd -u www
     17 * which is equivalent to (assuming user www has uid 80):
     18 *      httpd -p 80 -u 80 -h $PWD -c /etc/httpd.conf -r "Web Server Authentication"
     19 *
     20 * When an url starts with "/cgi-bin/" it is assumed to be a cgi script.
     21 * The server changes directory to the location of the script and executes it
    2522 * after setting QUERY_STRING and other environment variables.
    2623 *
     
    2825 * "CGI Environment Variables": http://hoohoo.ncsa.uiuc.edu/cgi/env.html
    2926 *
    30  * The server can also be invoked as a url arg decoder and html text encoder
     27 * The applet can also be invoked as an url arg decoder and html text encoder
    3128 * as follows:
    32  *  foo=`httpd -d $foo`           # decode "Hello%20World" as "Hello World"
    33  *  bar=`httpd -e "<Hello World>"`  # encode as "&#60Hello&#32World&#62"
     29 *      foo=`httpd -d $foo`             # decode "Hello%20World" as "Hello World"
     30 *      bar=`httpd -e "<Hello World>"`  # encode as "&#60Hello&#32World&#62"
    3431 * 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
     32 * presentation.  -d decodes an url-encoded argument while -e encodes in html
    3633 * for page display.
    3734 *
    3835 * httpd.conf has the following format:
    3936 *
     37 * H:/serverroot     # define the server root. It will override -h
    4038 * A:172.20.         # Allow address from 172.20.0.0/16
    4139 * A:10.0.0.0/25     # Allow any address from 10.0.0.0-10.0.0.127
     
    4442 * D:*               # Deny from other IP connections
    4543 * E404:/path/e404.html # /path/e404.html is the 404 (not found) error page
     44 * I:index.html      # Show index.html when a directory is requested
     45 *
     46 * P:/url:[http://]hostname[:port]/new/path
     47 *                   # When /urlXXXXXX is requested, reverse proxy
     48 *                   # it to http://hostname[:port]/new/pathXXXXXX
     49 *
    4650 * /cgi-bin:foo:bar  # Require user foo, pwd bar on urls starting with /cgi-bin/
    4751 * /adm:admin:setup  # Require user admin, pwd setup on urls starting with /adm/
    4852 * /adm:toor:PaSsWd  # or user toor, pwd PaSsWd on urls starting with /adm/
    4953 * .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
     54 * *.php:/path/php   # run xxx.php through an interpreter
     55 *
     56 * A/D may be as a/d or allow/deny - only first char matters.
     57 * Deny/Allow IP logic:
     58 *  - Default is to allow all (Allow all (A:*) is a no-op).
    6159 *  - 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
     60 *  - "Deny all" rule (D:*) is applied last.
    6561 *
    6662 * Example:
     
    9692 *
    9793 */
     94 /* TODO: use TCP_CORK, parse_config() */
    9895
    9996#include "libbb.h"
    10097#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
    101 #include <sys/sendfile.h>
    102 #endif
    103 
    104 //#define DEBUG 1
    105 #define DEBUG 0
    106 
     98# include <sys/sendfile.h>
     99#endif
    107100/* amount of buffering in a pipe */
    108101#ifndef PIPE_BUF
     
    110103#endif
    111104
    112 #define IOBUF_SIZE 8192    /* IO buffer */
     105#define DEBUG 0
     106
     107#define IOBUF_SIZE 8192
     108#if PIPE_BUF >= IOBUF_SIZE
     109# error "PIPE_BUF >= IOBUF_SIZE"
     110#endif
    113111
    114112#define HEADER_READ_TIMEOUT 60
    115113
    116 static const char default_path_httpd_conf[] ALIGN1 = "/etc";
    117 static const char httpd_conf[] ALIGN1 = "httpd.conf";
     114static const char DEFAULT_PATH_HTTPD_CONF[] ALIGN1 = "/etc";
     115static const char HTTPD_CONF[] ALIGN1 = "httpd.conf";
    118116static const char HTTP_200[] ALIGN1 = "HTTP/1.0 200 OK\r\n";
     117static const char index_html[] ALIGN1 = "index.html";
    119118
    120119typedef struct has_next_ptr {
     
    137136} Htaccess_IP;
    138137
     138/* Must have "next" as a first member */
     139typedef struct Htaccess_Proxy {
     140    struct Htaccess_Proxy *next;
     141    char *url_from;
     142    char *host_port;
     143    char *url_to;
     144} Htaccess_Proxy;
     145
    139146enum {
    140147    HTTP_OK = 200,
     148    HTTP_PARTIAL_CONTENT = 206,
    141149    HTTP_MOVED_TEMPORARILY = 302,
    142150    HTTP_BAD_REQUEST = 400,       /* malformed syntax */
     
    160168    HTTP_BAD_GATEWAY = 502,
    161169    HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
    162     HTTP_RESPONSE_SETSIZE = 0xffffffff
    163170#endif
    164171};
     
    166173static const uint16_t http_response_type[] ALIGN2 = {
    167174    HTTP_OK,
     175#if ENABLE_FEATURE_HTTPD_RANGES
     176    HTTP_PARTIAL_CONTENT,
     177#endif
    168178    HTTP_MOVED_TEMPORARILY,
    169179    HTTP_REQUEST_TIMEOUT,
    170180    HTTP_NOT_IMPLEMENTED,
    171 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH 
     181#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
    172182    HTTP_UNAUTHORIZED,
    173183#endif
     
    193203} http_response[ARRAY_SIZE(http_response_type)] = {
    194204    { "OK", NULL },
    195     { "Found", "Directories must end with a slash" }, /* ?? */
     205#if ENABLE_FEATURE_HTTPD_RANGES
     206    { "Partial Content", NULL },
     207#endif
     208    { "Found", NULL },
    196209    { "Request Timeout", "No request appeared within 60 seconds" },
    197210    { "Not Implemented", "The requested method is not recognized" },
     
    219232    smallint flg_deny_all;
    220233
    221     unsigned rmt_ip;    /* used for IP-based allow/deny rules */
     234    unsigned rmt_ip;        /* used for IP-based allow/deny rules */
    222235    time_t last_mod;
    223     off_t ContentLength;    /* -1 - unknown */
    224236    char *rmt_ip_str;       /* for $REMOTE_ADDR and $REMOTE_PORT */
    225237    const char *bind_addr_or_port;
    226238
    227239    const char *g_query;
    228     const char *configFile;
     240    const char *opt_c_configFile;
    229241    const char *home_httpd;
     242    const char *index_page;
    230243
    231244    const char *found_mime_type;
     
    233246    Htaccess_IP *ip_a_d;    /* config allow/deny lines */
    234247
    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;)
     248    IF_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;)
     249    IF_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;)
     250    IF_FEATURE_HTTPD_CGI(char *referer;)
     251    IF_FEATURE_HTTPD_CGI(char *user_agent;)
     252    IF_FEATURE_HTTPD_CGI(char *host;)
     253    IF_FEATURE_HTTPD_CGI(char *http_accept;)
     254    IF_FEATURE_HTTPD_CGI(char *http_accept_language;)
     255
     256    off_t file_size;        /* -1 - unknown */
     257#if ENABLE_FEATURE_HTTPD_RANGES
     258    off_t range_start;
     259    off_t range_end;
     260    off_t range_len;
     261#endif
    239262
    240263#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
    241264    Htaccess *g_auth;       /* config user:password lines */
    242265#endif
    243 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
    244266    Htaccess *mime_a;       /* config mime types */
    245 #endif
    246267#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
    247268    Htaccess *script_i;     /* config script interpreters */
    248269#endif
    249     char *iobuf;            /* [IOBUF_SIZE] */
     270    char *iobuf;            /* [IOBUF_SIZE] */
    250271#define hdr_buf bb_common_bufsiz1
    251272    char *hdr_ptr;
     
    253274#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
    254275    const char *http_error_page[ARRAY_SIZE(http_response_type)];
     276#endif
     277#if ENABLE_FEATURE_HTTPD_PROXY
     278    Htaccess_Proxy *proxy;
     279#endif
     280#if ENABLE_FEATURE_HTTPD_GZIP
     281    /* client can handle gzip / we are going to send gzip */
     282    smallint content_gzip;
    255283#endif
    256284};
     
    261289#define bind_addr_or_port (G.bind_addr_or_port)
    262290#define g_query           (G.g_query          )
    263 #define configFile        (G.configFile      )
     291#define opt_c_configFile  (G.opt_c_configFile )
    264292#define home_httpd        (G.home_httpd       )
     293#define index_page        (G.index_page       )
    265294#define found_mime_type   (G.found_mime_type  )
    266295#define found_moved_temporarily (G.found_moved_temporarily)
    267 #define ContentLength     (G.ContentLength    )
    268296#define last_mod          (G.last_mod         )
    269297#define ip_a_d            (G.ip_a_d           )
     
    272300#define referer           (G.referer          )
    273301#define user_agent        (G.user_agent       )
     302#define host              (G.host             )
     303#define http_accept       (G.http_accept      )
     304#define http_accept_language (G.http_accept_language)
     305#define file_size         (G.file_size        )
     306#if ENABLE_FEATURE_HTTPD_RANGES
     307#define range_start       (G.range_start      )
     308#define range_end         (G.range_end        )
     309#define range_len         (G.range_len        )
     310#else
     311enum {
     312    range_start = 0,
     313    range_end = MAXINT(off_t) - 1,
     314    range_len = MAXINT(off_t),
     315};
     316#endif
    274317#define rmt_ip_str        (G.rmt_ip_str       )
    275318#define g_auth            (G.g_auth           )
     
    280323#define hdr_cnt           (G.hdr_cnt          )
    281324#define http_error_page   (G.http_error_page  )
     325#define proxy             (G.proxy            )
     326#if ENABLE_FEATURE_HTTPD_GZIP
     327# define content_gzip     (G.content_gzip     )
     328#else
     329# define content_gzip     0
     330#endif
    282331#define INIT_G() do { \
    283     PTR_TO_GLOBALS = xzalloc(sizeof(G)); \
    284     USE_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
     332    SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
     333    IF_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
    285334    bind_addr_or_port = "80"; \
    286     ContentLength = -1; \
     335    index_page = index_html; \
     336    file_size = -1; \
    287337} while (0)
    288338
     
    291341
    292342/* Prototypes */
    293 static void send_file_and_exit(const char *url, int headers) ATTRIBUTE_NORETURN;
     343enum {
     344    SEND_HEADERS     = (1 << 0),
     345    SEND_BODY        = (1 << 1),
     346    SEND_HEADERS_AND_BODY = SEND_HEADERS + SEND_BODY,
     347};
     348static void send_file_and_exit(const char *url, int what) NORETURN;
    294349
    295350static void free_llist(has_next_ptr **pptr)
     
    304359}
    305360
    306 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
    307  || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
    308  || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
    309361static ALWAYS_INLINE void free_Htaccess_list(Htaccess **pptr)
    310362{
    311363    free_llist((has_next_ptr**)pptr);
    312364}
    313 #endif
    314365
    315366static ALWAYS_INLINE void free_Htaccess_IP_list(Htaccess_IP **pptr)
     
    406457 * Parse configuration file into in-memory linked list.
    407458 *
    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  *
    415459 * Any previous IP rules are discarded.
    416460 * If the flag argument is not SUBDIR_PARSE then all /path and mime rules
     
    422466 * flag   Type of the parse request.
    423467 */
    424 /* flag */
    425 #define FIRST_PARSE          0
    426 #define SUBDIR_PARSE         1
    427 #define SIGNALED_PARSE       2
    428 #define FIND_FROM_HTTPD_ROOT 3
     468/* flag param: */
     469enum {
     470    FIRST_PARSE    = 0, /* path will be "/etc" */
     471    SIGNALED_PARSE = 1, /* path will be "/etc" */
     472    SUBDIR_PARSE   = 2, /* path will be derived from URL */
     473};
    429474static void parse_conf(const char *path, int flag)
    430475{
     476    /* internally used extra flag state */
     477    enum { TRY_CURDIR_PARSE = 3 };
     478
    431479    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;
     480    const char *filename;
    441481    char buf[160];
    442     char *p0 = NULL;
    443     char *c, *p;
    444     Htaccess_IP *pip;
    445482
    446483    /* discard old rules */
    447484    free_Htaccess_IP_list(&ip_a_d);
    448485    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
    452486    /* retain previous auth and mime config only for subdir parse */
    453487    if (flag != SUBDIR_PARSE) {
     488        free_Htaccess_list(&mime_a);
    454489#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
    455490        free_Htaccess_list(&g_auth);
    456491#endif
    457 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
    458         free_Htaccess_list(&mime_a);
    459 #endif
    460492#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
    461493        free_Htaccess_list(&script_i);
    462494#endif
    463495    }
    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) {
     496
     497    filename = opt_c_configFile;
     498    if (flag == SUBDIR_PARSE || filename == NULL) {
     499        filename = alloca(strlen(path) + sizeof(HTTPD_CONF) + 2);
     500        sprintf((char *)filename, "%s/%s", path, HTTPD_CONF);
     501    }
     502
     503    while ((f = fopen_for_read(filename)) == NULL) {
     504        if (flag >= SUBDIR_PARSE) { /* SUBDIR or TRY_CURDIR */
    473505            /* config file not found, no changes to config */
    474506            return;
    475507        }
    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;
     508        if (flag == FIRST_PARSE) {
     509            /* -c CONFFILE given, but CONFFILE doesn't exist? */
     510            if (opt_c_configFile)
     511                bb_simple_perror_msg_and_die(opt_c_configFile);
     512            /* else: no -c, thus we looked at /etc/httpd.conf,
     513             * and it's not there. try ./httpd.conf: */
     514        }
     515        flag = TRY_CURDIR_PARSE;
     516        filename = HTTPD_CONF;
    480517    }
    481518
    482519#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')
     520    /* in "/file:user:pass" lines, we prepend path in subdirs */
     521    if (flag != SUBDIR_PARSE)
     522        path = "";
     523#endif
     524    /* The lines can be:
     525     *
     526     * I:default_index_file
     527     * H:http_home
     528     * [AD]:IP[/mask]   # allow/deny, * for wildcard
     529     * Ennn:error.html  # error page for status nnn
     530     * P:/url:[http://]hostname[:port]/new/path # reverse proxy
     531     * .ext:mime/type   # mime type
     532     * *.php:/path/php  # run xxx.php through an interpreter
     533     * /file:user:pass  # username and password
     534     */
     535    while (fgets(buf, sizeof(buf), f) != NULL) {
     536        unsigned strlen_buf;
     537        unsigned char ch;
     538        char *after_colon;
     539
     540        { /* remove all whitespace, and # comments */
     541            char *p, *p0;
     542
     543            p0 = buf;
     544            /* skip non-whitespace beginning. Often the whole line
     545             * is non-whitespace. We want this case to work fast,
     546             * without needless copying, therefore we don't merge
     547             * this operation into next while loop. */
     548            while ((ch = *p0) != '\0' && ch != '\n' && ch != '#'
     549             && ch != ' ' && ch != '\t'
     550            ) {
     551                p0++;
     552            }
     553            p = p0;
     554            /* if we enter this loop, we have some whitespace.
     555             * discard it */
     556            while (ch != '\0' && ch != '\n' && ch != '#') {
     557                if (ch != ' ' && ch != '\t') {
     558                    *p++ = ch;
     559                }
     560                ch = *++p0;
     561            }
     562            *p = '\0';
     563            strlen_buf = p - buf;
     564            if (strlen_buf == 0)
     565                continue; /* empty line */
     566        }
     567
     568        after_colon = strchr(buf, ':');
     569        /* strange line? */
     570        if (after_colon == NULL || *++after_colon == '\0')
     571            goto config_error;
     572
     573        ch = (buf[0] & ~0x20); /* toupper if it's a letter */
     574
     575        if (ch == 'I') {
     576            if (index_page != index_html)
     577                free((char*)index_page);
     578            index_page = xstrdup(after_colon);
    499579            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 */
     580        }
     581
     582        /* do not allow jumping around using H in subdir's configs */
     583        if (flag == FIRST_PARSE && ch == 'H') {
     584            home_httpd = xstrdup(after_colon);
     585            xchdir(home_httpd);
    509586            continue;
    510587        }
    511588
    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;
     589        if (ch == 'A' || ch == 'D') {
     590            Htaccess_IP *pip;
     591
     592            if (*after_colon == '*') {
     593                if (ch == 'D') {
     594                    /* memorize "deny all" */
     595                    flg_deny_all = 1;
    522596                }
    523                 pip->allow_deny = *p0;
    524                 if (*p0 == 'D') {
    525                     /* Deny:from_IP move top */
    526                     pip->next = ip_a_d;
     597                /* skip assumed "A:*", it is a default anyway */
     598                continue;
     599            }
     600            /* store "allow/deny IP/mask" line */
     601            pip = xzalloc(sizeof(*pip));
     602            if (scan_ip_mask(after_colon, &pip->ip, &pip->mask)) {
     603                /* IP{/mask} syntax error detected, protect all */
     604                ch = 'D';
     605                pip->mask = 0;
     606            }
     607            pip->allow_deny = ch;
     608            if (ch == 'D') {
     609                /* Deny:from_IP - prepend */
     610                pip->next = ip_a_d;
     611                ip_a_d = pip;
     612            } else {
     613                /* A:from_IP - append (thus all D's precedes A's) */
     614                Htaccess_IP *prev_IP = ip_a_d;
     615                if (prev_IP == NULL) {
    527616                    ip_a_d = pip;
    528617                } 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                     }
     618                    while (prev_IP->next)
     619                        prev_IP = prev_IP->next;
     620                    prev_IP->next = pip;
    539621                }
    540622            }
     
    543625
    544626#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++; */
     627        if (flag == FIRST_PARSE && ch == 'E') {
     628            unsigned i;
     629            int status = atoi(buf + 1); /* error status code */
     630
    551631            if (status < HTTP_CONTINUE) {
    552                 bb_error_msg("config error '%s' in '%s'", buf, cf);
    553                 continue;
    554             }
    555 
     632                goto config_error;
     633            }
    556634            /* then error page; find matching status */
    557635            for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
    558636                if (http_response_type[i] == status) {
    559                     http_error_page[i] = concat_path_file((*c == '/') ? NULL : home_httpd, c);
     637                    /* We chdir to home_httpd, thus no need to
     638                     * concat_path_file(home_httpd, after_colon)
     639                     * here */
     640                    http_error_page[i] = xstrdup(after_colon);
    560641                    break;
    561642                }
     
    565646#endif
    566647
    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 */
     648#if ENABLE_FEATURE_HTTPD_PROXY
     649        if (flag == FIRST_PARSE && ch == 'P') {
     650            /* P:/url:[http://]hostname[:port]/new/path */
     651            char *url_from, *host_port, *url_to;
     652            Htaccess_Proxy *proxy_entry;
     653
     654            url_from = after_colon;
     655            host_port = strchr(after_colon, ':');
     656            if (host_port == NULL) {
     657                goto config_error;
     658            }
     659            *host_port++ = '\0';
     660            if (strncmp(host_port, "http://", 7) == 0)
     661                host_port += 7;
     662            if (*host_port == '\0') {
     663                goto config_error;
     664            }
     665            url_to = strchr(host_port, '/');
     666            if (url_to == NULL) {
     667                goto config_error;
     668            }
     669            *url_to = '\0';
     670            proxy_entry = xzalloc(sizeof(*proxy_entry));
     671            proxy_entry->url_from = xstrdup(url_from);
     672            proxy_entry->host_port = xstrdup(host_port);
     673            *url_to = '/';
     674            proxy_entry->url_to = xstrdup(url_to);
     675            proxy_entry->next = proxy;
     676            proxy = proxy_entry;
     677            continue;
     678        }
     679#endif
     680        /* the rest of directives are non-alphabetic,
     681         * must avoid using "toupper'ed" ch */
     682        ch = buf[0];
     683
     684        if (ch == '.' /* ".ext:mime/type" */
     685#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
     686         || (ch == '*' && buf[1] == '.') /* "*.php:/path/php" */
     687#endif
     688        ) {
     689            char *p;
     690            Htaccess *cur;
     691
     692            cur = xzalloc(sizeof(*cur) /* includes space for NUL */ + strlen_buf);
     693            strcpy(cur->before_colon, buf);
     694            p = cur->before_colon + (after_colon - buf);
     695            p[-1] = '\0';
     696            cur->after_colon = p;
     697            if (ch == '.') {
     698                /* .mime line: prepend to mime_a list */
    618699                cur->next = mime_a;
    619700                mime_a = cur;
    620                 continue;
    621             }
    622 #endif
     701            }
    623702#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
    624             if (*cf == '*' && cf[1] == '.') {
    625                 /* config script interpreter line move top for overwrite previous */
     703            else {
     704                /* script interpreter line: prepend to script_i list */
    626705                cur->next = script_i;
    627706                script_i = cur;
    628                 continue;
    629             }
    630 #endif
     707            }
     708#endif
     709            continue;
     710        }
     711
    631712#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                         }
     713        if (ch == '/') { /* "/file:user:pass" */
     714            char *p;
     715            Htaccess *cur;
     716            unsigned file_len;
     717
     718            /* note: path is "" unless we are in SUBDIR parse,
     719             * otherwise it does NOT start with "/" */
     720            cur = xzalloc(sizeof(*cur) /* includes space for NUL */
     721                + 1 + strlen(path)
     722                + strlen_buf
     723                );
     724            /* form "/path/file" */
     725            sprintf(cur->before_colon, "/%s%.*s",
     726                path,
     727                (int) (after_colon - buf - 1), /* includes "/", but not ":" */
     728                buf);
     729            /* canonicalize it */
     730            p = bb_simplify_abs_path_inplace(cur->before_colon);
     731            file_len = p - cur->before_colon;
     732            /* add "user:pass" after NUL */
     733            strcpy(++p, after_colon);
     734            cur->after_colon = p;
     735
     736            /* insert cur into g_auth */
     737            /* g_auth is sorted by decreased filename length */
     738            {
     739                Htaccess *auth, **authp;
     740
     741                authp = &g_auth;
     742                while ((auth = *authp) != NULL) {
     743                    if (file_len >= strlen(auth->before_colon)) {
     744                        /* insert cur before auth */
     745                        cur->next = auth;
    652746                        break;
    653747                    }
    654                     if (prev_hti != hti)
    655                         prev_hti = prev_hti->next;
     748                    authp = &auth->next;
    656749                }
    657                 if (!hti) {       /* not inserted, add to bottom */
    658                     prev->next = cur;
    659                     prev = cur;
    660                 }
    661             }
    662 #endif
    663         }
    664 #endif
    665      }
     750                *authp = cur;
     751            }
     752            continue;
     753        }
     754#endif /* BASIC_AUTH */
     755
     756        /* the line is not recognized */
     757 config_error:
     758        bb_error_msg("config error '%s' in '%s'", buf, filename);
     759     } /* while (fgets) */
     760
    666761     fclose(f);
    667762}
     
    685780    char ch;
    686781
    687     while ((ch = *string++)) {
     782    while ((ch = *string++) != '\0') {
    688783        /* very simple check for what to encode */
    689784        if (isalnum(ch))
     
    695790    return out;
    696791}
    697 #endif          /* FEATURE_HTTPD_ENCODE_URL_STR */
     792#endif
    698793
    699794/*
    700795 * Given a URL encoded string, convert it to plain ascii.
    701796 * 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
     797 * Thus, callers should xstrdup() the argument if they do not want the
    703798 * argument modified.  The return is the original pointer, allowing this
    704799 * function to be easily used as arguments to other functions.
     
    722817        return v + 10;
    723818    return ~0;
    724 }
    725819/* For testing:
    726820void t(char c) { printf("'%c'(%u) %u\n", c, c, hex_to_bin(c)); }
     
    728822t('0'-1); t('9'+1); t('A'-1); t('F'+1); t('a'-1); t('f'+1); return 0; }
    729823*/
     824}
    730825static char *decodeString(char *orig, int option_d)
    731826{
     
    821916static int openServer(void)
    822917{
    823     int n = bb_strtou(bind_addr_or_port, NULL, 10);
     918    unsigned n = bb_strtou(bind_addr_or_port, NULL, 10);
    824919    if (!errno && n && n <= 0xffff)
    825920        n = create_and_bind_stream_or_die(NULL, n);
     
    833928 * Log the connection closure and exit.
    834929 */
    835 static void log_and_exit(void) ATTRIBUTE_NORETURN;
     930static void log_and_exit(void) NORETURN;
    836931static void log_and_exit(void)
    837932{
     
    839934     * or be confused by us just exiting without SHUT_WR. Oh well. */
    840935    shutdown(1, SHUT_WR);
     936    /* Why??
     937    (this also messes up stdin when user runs httpd -i from terminal)
    841938    ndelay_on(0);
    842     while (read(0, iobuf, IOBUF_SIZE) > 0)
     939    while (read(STDIN_FILENO, iobuf, IOBUF_SIZE) > 0)
    843940        continue;
     941    */
    844942
    845943    if (verbose > 2)
     
    863961    const char *mime_type;
    864962#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
    865     const char *error_page = 0;
     963    const char *error_page = NULL;
    866964#endif
    867965    unsigned i;
    868     time_t timer = time(0);
     966    time_t timer = time(NULL);
    869967    char tmp_str[80];
    870968    int len;
     
    9091007
    9101008#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
    911     if (error_page && !access(error_page, R_OK)) {
     1009    if (error_page && access(error_page, R_OK) == 0) {
    9121010        strcat(iobuf, "\r\n");
    9131011        len += 2;
     
    9151013        if (DEBUG)
    9161014            fprintf(stderr, "headers: '%s'\n", iobuf);
    917         full_write(1, iobuf, len);
     1015        full_write(STDOUT_FILENO, iobuf, len);
    9181016        if (DEBUG)
    9191017            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 */
     1018        return send_file_and_exit(error_page, SEND_BODY);
     1019    }
     1020#endif
     1021
     1022    if (file_size != -1) {    /* file */
    9251023        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     }
     1024#if ENABLE_FEATURE_HTTPD_RANGES
     1025        if (responseNum == HTTP_PARTIAL_CONTENT) {
     1026            len += sprintf(iobuf + len, "Content-Range: bytes %"OFF_FMT"u-%"OFF_FMT"u/%"OFF_FMT"u\r\n",
     1027                    range_start,
     1028                    range_end,
     1029                    file_size);
     1030            file_size = range_end - range_start + 1;
     1031        }
     1032#endif
     1033        len += sprintf(iobuf + len,
     1034#if ENABLE_FEATURE_HTTPD_RANGES
     1035            "Accept-Ranges: bytes\r\n"
     1036#endif
     1037            "Last-Modified: %s\r\n%s %"OFF_FMT"u\r\n",
     1038                tmp_str,
     1039                content_gzip ? "Transfer-length:" : "Content-length:",
     1040                file_size
     1041        );
     1042    }
     1043
     1044    if (content_gzip)
     1045        len += sprintf(iobuf + len, "Content-Encoding: gzip\r\n");
     1046
    9291047    iobuf[len++] = '\r';
    9301048    iobuf[len++] = '\n';
     
    9381056    if (DEBUG)
    9391057        fprintf(stderr, "headers: '%s'\n", iobuf);
    940     if (full_write(1, iobuf, len) != len) {
     1058    if (full_write(STDOUT_FILENO, iobuf, len) != len) {
    9411059        if (verbose > 1)
    9421060            bb_perror_msg("error");
     
    9451063}
    9461064
    947 static void send_headers_and_exit(int responseNum) ATTRIBUTE_NORETURN;
     1065static void send_headers_and_exit(int responseNum) NORETURN;
    9481066static void send_headers_and_exit(int responseNum)
    9491067{
     
    9641082    char c;
    9651083
     1084    alarm(HEADER_READ_TIMEOUT);
    9661085    while (1) {
    9671086        if (hdr_cnt <= 0) {
    968             hdr_cnt = safe_read(0, hdr_buf, sizeof(hdr_buf));
     1087            hdr_cnt = safe_read(STDIN_FILENO, hdr_buf, sizeof(hdr_buf));
    9691088            if (hdr_cnt <= 0)
    9701089                break;
     
    9781097        if (c == '\n') {
    9791098            iobuf[count] = '\0';
    980             return count;
     1099            break;
    9811100        }
    9821101        if (count < (IOBUF_SIZE - 1))      /* check overflow */
     
    9861105}
    9871106
     1107#if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY
     1108
     1109/* gcc 4.2.1 fares better with NOINLINE */
     1110static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) NORETURN;
     1111static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len)
     1112{
     1113    enum { FROM_CGI = 1, TO_CGI = 2 }; /* indexes in pfd[] */
     1114    struct pollfd pfd[3];
     1115    int out_cnt; /* we buffer a bit of initial CGI output */
     1116    int count;
     1117
     1118    /* iobuf is used for CGI -> network data,
     1119     * hdr_buf is for network -> CGI data (POSTDATA) */
     1120
     1121    /* If CGI dies, we still want to correctly finish reading its output
     1122     * and send it to the peer. So please no SIGPIPEs! */
     1123    signal(SIGPIPE, SIG_IGN);
     1124
     1125    // We inconsistently handle a case when more POSTDATA from network
     1126    // is coming than we expected. We may give *some part* of that
     1127    // extra data to CGI.
     1128
     1129    //if (hdr_cnt > post_len) {
     1130    //  /* We got more POSTDATA from network than we expected */
     1131    //  hdr_cnt = post_len;
     1132    //}
     1133    post_len -= hdr_cnt;
     1134    /* post_len - number of POST bytes not yet read from network */
     1135
     1136    /* NB: breaking out of this loop jumps to log_and_exit() */
     1137    out_cnt = 0;
     1138    while (1) {
     1139        memset(pfd, 0, sizeof(pfd));
     1140
     1141        pfd[FROM_CGI].fd = fromCgi_rd;
     1142        pfd[FROM_CGI].events = POLLIN;
     1143
     1144        if (toCgi_wr) {
     1145            pfd[TO_CGI].fd = toCgi_wr;
     1146            if (hdr_cnt > 0) {
     1147                pfd[TO_CGI].events = POLLOUT;
     1148            } else if (post_len > 0) {
     1149                pfd[0].events = POLLIN;
     1150            } else {
     1151                /* post_len <= 0 && hdr_cnt <= 0:
     1152                 * no more POST data to CGI,
     1153                 * let CGI see EOF on CGI's stdin */
     1154                if (toCgi_wr != fromCgi_rd)
     1155                    close(toCgi_wr);
     1156                toCgi_wr = 0;
     1157            }
     1158        }
     1159
     1160        /* Now wait on the set of sockets */
     1161        count = safe_poll(pfd, toCgi_wr ? TO_CGI+1 : FROM_CGI+1, -1);
     1162        if (count <= 0) {
     1163#if 0
     1164            if (safe_waitpid(pid, &status, WNOHANG) <= 0) {
     1165                /* Weird. CGI didn't exit and no fd's
     1166                 * are ready, yet poll returned?! */
     1167                continue;
     1168            }
     1169            if (DEBUG && WIFEXITED(status))
     1170                bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status));
     1171            if (DEBUG && WIFSIGNALED(status))
     1172                bb_error_msg("CGI killed, signal=%d", WTERMSIG(status));
     1173#endif
     1174            break;
     1175        }
     1176
     1177        if (pfd[TO_CGI].revents) {
     1178            /* hdr_cnt > 0 here due to the way pfd[TO_CGI].events set */
     1179            /* Have data from peer and can write to CGI */
     1180            count = safe_write(toCgi_wr, hdr_ptr, hdr_cnt);
     1181            /* Doesn't happen, we dont use nonblocking IO here
     1182             *if (count < 0 && errno == EAGAIN) {
     1183             *  ...
     1184             *} else */
     1185            if (count > 0) {
     1186                hdr_ptr += count;
     1187                hdr_cnt -= count;
     1188            } else {
     1189                /* EOF/broken pipe to CGI, stop piping POST data */
     1190                hdr_cnt = post_len = 0;
     1191            }
     1192        }
     1193
     1194        if (pfd[0].revents) {
     1195            /* post_len > 0 && hdr_cnt == 0 here */
     1196            /* We expect data, prev data portion is eaten by CGI
     1197             * and there *is* data to read from the peer
     1198             * (POSTDATA) */
     1199            //count = post_len > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : post_len;
     1200            //count = safe_read(STDIN_FILENO, hdr_buf, count);
     1201            count = safe_read(STDIN_FILENO, hdr_buf, sizeof(hdr_buf));
     1202            if (count > 0) {
     1203                hdr_cnt = count;
     1204                hdr_ptr = hdr_buf;
     1205                post_len -= count;
     1206            } else {
     1207                /* no more POST data can be read */
     1208                post_len = 0;
     1209            }
     1210        }
     1211
     1212        if (pfd[FROM_CGI].revents) {
     1213            /* There is something to read from CGI */
     1214            char *rbuf = iobuf;
     1215
     1216            /* Are we still buffering CGI output? */
     1217            if (out_cnt >= 0) {
     1218                /* HTTP_200[] has single "\r\n" at the end.
     1219                 * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html,
     1220                 * CGI scripts MUST send their own header terminated by
     1221                 * empty line, then data. That's why we have only one
     1222                 * <cr><lf> pair here. We will output "200 OK" line
     1223                 * if needed, but CGI still has to provide blank line
     1224                 * between header and body */
     1225
     1226                /* Must use safe_read, not full_read, because
     1227                 * CGI may output a few first bytes and then wait
     1228                 * for POSTDATA without closing stdout.
     1229                 * With full_read we may wait here forever. */
     1230                count = safe_read(fromCgi_rd, rbuf + out_cnt, PIPE_BUF - 8);
     1231                if (count <= 0) {
     1232                    /* eof (or error) and there was no "HTTP",
     1233                     * so write it, then write received data */
     1234                    if (out_cnt) {
     1235                        full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1);
     1236                        full_write(STDOUT_FILENO, rbuf, out_cnt);
     1237                    }
     1238                    break; /* CGI stdout is closed, exiting */
     1239                }
     1240                out_cnt += count;
     1241                count = 0;
     1242                /* "Status" header format is: "Status: 302 Redirected\r\n" */
     1243                if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) {
     1244                    /* send "HTTP/1.0 " */
     1245                    if (full_write(STDOUT_FILENO, HTTP_200, 9) != 9)
     1246                        break;
     1247                    rbuf += 8; /* skip "Status: " */
     1248                    count = out_cnt - 8;
     1249                    out_cnt = -1; /* buffering off */
     1250                } else if (out_cnt >= 4) {
     1251                    /* Did CGI add "HTTP"? */
     1252                    if (memcmp(rbuf, HTTP_200, 4) != 0) {
     1253                        /* there is no "HTTP", do it ourself */
     1254                        if (full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1)
     1255                            break;
     1256                    }
     1257                    /* Commented out:
     1258                    if (!strstr(rbuf, "ontent-")) {
     1259                        full_write(s, "Content-type: text/plain\r\n\r\n", 28);
     1260                    }
     1261                     * Counter-example of valid CGI without Content-type:
     1262                     * echo -en "HTTP/1.0 302 Found\r\n"
     1263                     * echo -en "Location: http://www.busybox.net\r\n"
     1264                     * echo -en "\r\n"
     1265                     */
     1266                    count = out_cnt;
     1267                    out_cnt = -1; /* buffering off */
     1268                }
     1269            } else {
     1270                count = safe_read(fromCgi_rd, rbuf, PIPE_BUF);
     1271                if (count <= 0)
     1272                    break;  /* eof (or error) */
     1273            }
     1274            if (full_write(STDOUT_FILENO, rbuf, count) != count)
     1275                break;
     1276            if (DEBUG)
     1277                fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf);
     1278        } /* if (pfd[FROM_CGI].revents) */
     1279    } /* while (1) */
     1280    log_and_exit();
     1281}
     1282#endif
     1283
    9881284#if ENABLE_FEATURE_HTTPD_CGI
     1285
    9891286static void setenv1(const char *name, const char *value)
    9901287{
     
    9961293 *
    9971294 * 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
     1295 * for stdin/stdout.  If a POST is being done the script is fed the POST
    9991296 * data in addition to setting the QUERY_STRING variable (for GETs or POSTs).
    10001297 *
    10011298 * Parameters:
    10021299 * const char *url              The requested URL (with leading /).
    1003  * int bodyLen                  Length of the post body.
     1300 * int post_len                 Length of the POST body.
    10041301 * const char *cookie           For set HTTP_COOKIE.
    10051302 * const char *content_type     For set CONTENT_TYPE.
     
    10081305        const char *url,
    10091306        const char *request,
    1010         int bodyLen,
     1307        int post_len,
    10111308        const char *cookie,
    1012         const char *content_type) ATTRIBUTE_NORETURN;
     1309        const char *content_type) NORETURN;
    10131310static void send_cgi_and_exit(
    10141311        const char *url,
    10151312        const char *request,
    1016         int bodyLen,
     1313        int post_len,
    10171314        const char *cookie,
    10181315        const char *content_type)
    10191316{
    1020     struct { int rd; int wr; } fromCgi;  /* CGI -> httpd pipe */
    1021     struct { int rd; int wr; } toCgi;    /* httpd -> CGI pipe */
    1022     char *fullpath;
     1317    struct fd_pair fromCgi;  /* CGI -> httpd pipe */
     1318    struct fd_pair toCgi;    /* httpd -> CGI pipe */
    10231319    char *script;
    1024     char *purl;
    1025     int buf_count;
    1026     int status;
    1027     int pid = 0;
     1320    int pid;
     1321
     1322    /* Make a copy. NB: caller guarantees:
     1323     * url[0] == '/', url[1] != '/' */
     1324    url = xstrdup(url);
    10281325
    10291326    /*
    10301327     * We are mucking with environment _first_ and then vfork/exec,
    1031      * this allows us to use vfork safely. Parent don't care about
     1328     * this allows us to use vfork safely. Parent doesn't care about
    10321329     * these environment changes anyway.
    10331330     */
    10341331
    1035     /*
    1036      * Find PATH_INFO.
    1037      */
    1038     purl = xstrdup(url);
    1039     script = purl;
     1332    /* Check for [dirs/]script.cgi/PATH_INFO */
     1333    script = (char*)url;
    10401334    while ((script = strchr(script + 1, '/')) != NULL) {
    1041         /* have script.cgi/PATH_INFO or dirs/script.cgi[/PATH_INFO] */
    1042         struct stat sb;
    1043 
    10441335        *script = '\0';
    1045         if (!is_directory(purl + 1, 1, &sb)) {
     1336        if (!is_directory(url + 1, 1, NULL)) {
    10461337            /* not directory, found script.cgi/PATH_INFO */
    10471338            *script = '/';
    10481339            break;
    10491340        }
    1050         *script = '/';          /* is directory, find next '/' */
    1051     }
    1052     setenv1("PATH_INFO", script);   /* set /PATH_INFO or "" */
     1341        *script = '/'; /* is directory, find next '/' */
     1342    }
     1343    setenv1("PATH_INFO", script);   /* set to /PATH_INFO or "" */
    10531344    setenv1("REQUEST_METHOD", request);
    10541345    if (g_query) {
    1055         putenv(xasprintf("%s=%s?%s", "REQUEST_URI", purl, g_query));
     1346        putenv(xasprintf("%s=%s?%s", "REQUEST_URI", url, g_query));
    10561347    } else {
    1057         setenv1("REQUEST_URI", purl);
     1348        setenv1("REQUEST_URI", url);
    10581349    }
    10591350    if (script != NULL)
    10601351        *script = '\0';         /* cut off /PATH_INFO */
    10611352
    1062     /* SCRIPT_FILENAME required by PHP in CGI mode */
    1063     fullpath = concat_path_file(home_httpd, purl);
    1064     setenv1("SCRIPT_FILENAME", fullpath);
     1353    /* SCRIPT_FILENAME is required by PHP in CGI mode */
     1354    if (home_httpd[0] == '/') {
     1355        char *fullpath = concat_path_file(home_httpd, url);
     1356        setenv1("SCRIPT_FILENAME", fullpath);
     1357    }
    10651358    /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */
    1066     setenv1("SCRIPT_NAME", purl);
     1359    setenv1("SCRIPT_NAME", url);
    10671360    /* http://hoohoo.ncsa.uiuc.edu/cgi/env.html:
    10681361     * QUERY_STRING: The information which follows the ? in the URL
     
    10961389    }
    10971390    setenv1("HTTP_USER_AGENT", user_agent);
    1098     if (bodyLen)
    1099         putenv(xasprintf("CONTENT_LENGTH=%d", bodyLen));
     1391    if (http_accept)
     1392        setenv1("HTTP_ACCEPT", http_accept);
     1393    if (http_accept_language)
     1394        setenv1("HTTP_ACCEPT_LANGUAGE", http_accept_language);
     1395    if (post_len)
     1396        putenv(xasprintf("CONTENT_LENGTH=%d", post_len));
    11001397    if (cookie)
    11011398        setenv1("HTTP_COOKIE", cookie);
     
    11101407    if (referer)
    11111408        setenv1("HTTP_REFERER", referer);
    1112 
    1113     xpipe(&fromCgi.rd);
    1114     xpipe(&toCgi.rd);
     1409    setenv1("HTTP_HOST", host); /* set to "" if NULL */
     1410    /* setenv1("SERVER_NAME", safe_gethostname()); - don't do this,
     1411     * just run "env SERVER_NAME=xyz httpd ..." instead */
     1412
     1413    xpiped_pair(fromCgi);
     1414    xpiped_pair(toCgi);
    11151415
    11161416    pid = vfork();
     
    11221422    if (!pid) {
    11231423        /* Child process */
     1424        char *argv[3];
     1425
    11241426        xfunc_error_retval = 242;
    11251427
     1428        /* NB: close _first_, then move fds! */
     1429        close(toCgi.wr);
     1430        close(fromCgi.rd);
    11261431        xmove_fd(toCgi.rd, 0);  /* replace stdin with the pipe */
    11271432        xmove_fd(fromCgi.wr, 1);  /* replace stdout with the pipe */
    1128         close(fromCgi.rd);
    1129         close(toCgi.wr);
    11301433        /* User seeing stderr output can be a security problem.
    11311434         * If CGI really wants that, it can always do dup itself. */
    11321435        /* dup2(1, 2); */
    11331436
    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];
     1437        /* Chdiring to script's dir */
     1438        script = strrchr(url, '/');
     1439        if (script != url) { /* paranoia */
     1440            *script = '\0';
     1441            if (chdir(url + 1) != 0) {
     1442                bb_perror_msg("chdir(%s)", url + 1);
     1443                goto error_execing_cgi;
     1444            }
     1445            // not needed: *script = '/';
     1446        }
     1447        script++;
     1448
     1449        /* set argv[0] to name without path */
     1450        argv[0] = script;
     1451        argv[1] = NULL;
     1452
    11421453#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
    1143             char *interpr = NULL;
    1144             char *suffix = strrchr(purl, '.');
     1454        {
     1455            char *suffix = strrchr(script, '.');
    11451456
    11461457            if (suffix) {
     
    11481459                for (cur = script_i; cur; cur = cur->next) {
    11491460                    if (strcmp(cur->before_colon + 1, suffix) == 0) {
    1150                         interpr = cur->after_colon;
     1461                        /* found interpreter name */
     1462                        argv[0] = cur->after_colon;
     1463                        argv[1] = script;
     1464                        argv[2] = NULL;
    11511465                        break;
    11521466                    }
    11531467                }
    11541468            }
    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         }
     1469        }
     1470#endif
     1471        /* restore default signal dispositions for CGI process */
     1472        bb_signals(0
     1473            | (1 << SIGCHLD)
     1474            | (1 << SIGPIPE)
     1475            | (1 << SIGHUP)
     1476            , SIG_DFL);
     1477
     1478        /* _NOT_ execvp. We do not search PATH. argv[0] is a filename
     1479         * without any dir components and will only match a file
     1480         * in the current directory */
     1481        execv(argv[0], argv);
     1482        if (verbose)
     1483            bb_perror_msg("can't execute '%s'", argv[0]);
    11671484 error_execing_cgi:
    11681485        /* send to stdout
     
    11731490    /* Parent process */
    11741491
    1175     /* First, restore variables possibly changed by child */
     1492    /* Restore variables possibly changed by child */
    11761493    xfunc_error_retval = 0;
    11771494
    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;
     1495    /* Pump data */
    11821496    close(fromCgi.wr);
    11831497    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 }
     1498    cgi_io_loop_and_exit(fromCgi.rd, toCgi.wr, post_len);
     1499}
     1500
    13401501#endif          /* FEATURE_HTTPD_CGI */
    13411502
    13421503/*
    13431504 * Send a file response to a HTTP request, and exit
    1344  * 
     1505 *
    13451506 * Parameters:
    1346  * const char *url    The requested URL (with leading /).
    1347  * headers            Don't send headers before if FALSE.
     1507 * const char *url  The requested URL (with leading /).
     1508 * what             What to send (headers/body/both).
    13481509 */
    1349 static 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 
     1510static NOINLINE void send_file_and_exit(const char *url, int what)
     1511{
    13731512    char *suffix;
    1374     int f;
    1375     const char *const *table;
    1376     const char *try_suffix;
     1513    int fd;
    13771514    ssize_t count;
    1378 #if ENABLE_FEATURE_HTTPD_USE_SENDFILE
    1379     off_t offset = 0;
    1380 #endif
    1381 
     1515
     1516    if (content_gzip) {
     1517        /* does <url>.gz exist? Then use it instead */
     1518        char *gzurl = xasprintf("%s.gz", url);
     1519        fd = open(gzurl, O_RDONLY);
     1520        free(gzurl);
     1521        if (fd != -1) {
     1522            struct stat sb;
     1523            fstat(fd, &sb);
     1524            file_size = sb.st_size;
     1525            last_mod = sb.st_mtime;
     1526        } else {
     1527            IF_FEATURE_HTTPD_GZIP(content_gzip = 0;)
     1528            fd = open(url, O_RDONLY);
     1529        }
     1530    } else {
     1531        fd = open(url, O_RDONLY);
     1532    }
     1533    if (fd < 0) {
     1534        if (DEBUG)
     1535            bb_perror_msg("can't open '%s'", url);
     1536        /* Error pages are sent by using send_file_and_exit(SEND_BODY).
     1537         * IOW: it is unsafe to call send_headers_and_exit
     1538         * if what is SEND_BODY! Can recurse! */
     1539        if (what != SEND_BODY)
     1540            send_headers_and_exit(HTTP_NOT_FOUND);
     1541        log_and_exit();
     1542    }
     1543    /* If you want to know about EPIPE below
     1544     * (happens if you abort downloads from local httpd): */
     1545    signal(SIGPIPE, SIG_IGN);
     1546
     1547    /* If not found, default is "application/octet-stream" */
     1548    found_mime_type = "application/octet-stream";
    13821549    suffix = strrchr(url, '.');
    1383 
    1384     /* If not found, set default as "application/octet-stream";  */
    1385     found_mime_type = "application/octet-stream";
    13861550    if (suffix) {
    1387 #if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
     1551        static const char suffixTable[] ALIGN1 =
     1552            /* Shorter suffix must be first:
     1553             * ".html.htm" will fail for ".htm"
     1554             */
     1555            ".txt.h.c.cc.cpp\0" "text/plain\0"
     1556            /* .htm line must be after .h line */
     1557            ".htm.html\0" "text/html\0"
     1558            ".jpg.jpeg\0" "image/jpeg\0"
     1559            ".gif\0"      "image/gif\0"
     1560            ".png\0"      "image/png\0"
     1561            /* .css line must be after .c line */
     1562            ".css\0"      "text/css\0"
     1563            ".wav\0"      "audio/wav\0"
     1564            ".avi\0"      "video/x-msvideo\0"
     1565            ".qt.mov\0"   "video/quicktime\0"
     1566            ".mpe.mpeg\0" "video/mpeg\0"
     1567            ".mid.midi\0" "audio/midi\0"
     1568            ".mp3\0"      "audio/mpeg\0"
     1569#if 0  /* unpopular */
     1570            ".au\0"       "audio/basic\0"
     1571            ".pac\0"      "application/x-ns-proxy-autoconfig\0"
     1572            ".vrml.wrl\0" "model/vrml\0"
     1573#endif
     1574            /* compiler adds another "\0" here */
     1575        ;
    13881576        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
     1577
     1578        /* Examine built-in table */
     1579        const char *table = suffixTable;
     1580        const char *table_next;
     1581        for (; *table; table = table_next) {
     1582            const char *try_suffix;
     1583            const char *mime_type;
     1584            mime_type  = table + strlen(table) + 1;
     1585            table_next = mime_type + strlen(mime_type) + 1;
     1586            try_suffix = strstr(table, suffix);
     1587            if (!try_suffix)
     1588                continue;
     1589            try_suffix += strlen(suffix);
     1590            if (*try_suffix == '\0' || *try_suffix == '.') {
     1591                found_mime_type = mime_type;
     1592                break;
     1593            }
     1594            /* Example: strstr(table, ".av") != NULL, but it
     1595             * does not match ".avi" after all and we end up here.
     1596             * The table is arranged so that in this case we know
     1597             * that it can't match anything in the following lines,
     1598             * and we stop the search: */
     1599            break;
     1600        }
     1601        /* ...then user's table */
    14011602        for (cur = mime_a; cur; cur = cur->next) {
    14021603            if (strcmp(cur->before_colon, suffix) == 0) {
     
    14051606            }
    14061607        }
    1407 #endif
    14081608    }
    14091609
     
    14121612            url, found_mime_type);
    14131613
    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)
     1614#if ENABLE_FEATURE_HTTPD_RANGES
     1615    if (what == SEND_BODY /* err pages and ranges don't mix */
     1616     || content_gzip /* we are sending compressed page: can't do ranges */  ///why?
     1617    ) {
     1618        range_start = 0;
     1619    }
     1620    range_len = MAXINT(off_t);
     1621    if (range_start) {
     1622        if (!range_end) {
     1623            range_end = file_size - 1;
     1624        }
     1625        if (range_end < range_start
     1626         || lseek(fd, range_start, SEEK_SET) != range_start
     1627        ) {
     1628            lseek(fd, 0, SEEK_SET);
     1629            range_start = 0;
     1630        } else {
     1631            range_len = range_end - range_start + 1;
     1632            send_headers(HTTP_PARTIAL_CONTENT);
     1633            what = SEND_BODY;
     1634        }
     1635    }
     1636#endif
     1637    if (what & SEND_HEADERS)
    14231638        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 
    14291639#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);
     1640    {
     1641        off_t offset = range_start;
     1642        while (1) {
     1643            /* sz is rounded down to 64k */
     1644            ssize_t sz = MAXINT(ssize_t) - 0xffff;
     1645            IF_FEATURE_HTTPD_RANGES(if (sz > range_len) sz = range_len;)
     1646            count = sendfile(STDOUT_FILENO, fd, &offset, sz);
     1647            if (count < 0) {
     1648                if (offset == range_start)
     1649                    break; /* fall back to read/write loop */
     1650                goto fin;
     1651            }
     1652            IF_FEATURE_HTTPD_RANGES(range_len -= sz;)
     1653            if (count == 0 || range_len == 0)
     1654                log_and_exit();
     1655        }
     1656    }
     1657#endif
     1658    while ((count = safe_read(fd, iobuf, IOBUF_SIZE)) > 0) {
     1659        ssize_t n;
     1660        IF_FEATURE_HTTPD_RANGES(if (count > range_len) count = range_len;)
     1661        n = full_write(STDOUT_FILENO, iobuf, count);
    14461662        if (count != n)
    14471663            break;
    1448     }
    1449 #if ENABLE_FEATURE_HTTPD_USE_SENDFILE
    1450  fin:
    1451 #endif
    1452     if (count < 0 && verbose > 1)
    1453         bb_perror_msg("error");
     1664        IF_FEATURE_HTTPD_RANGES(range_len -= count;)
     1665        if (range_len == 0)
     1666            break;
     1667    }
     1668    if (count < 0) {
     1669 IF_FEATURE_HTTPD_USE_SENDFILE(fin:)
     1670        if (verbose > 1)
     1671            bb_perror_msg("error");
     1672    }
    14541673    log_and_exit();
    14551674}
     
    14591678    Htaccess_IP *cur;
    14601679
    1461     /* This could stand some work */
    14621680    for (cur = ip_a_d; cur; cur = cur->next) {
    14631681#if DEBUG
     
    14761694#endif
    14771695        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;
     1696            return (cur->allow_deny == 'A'); /* A -> 1 */
     1697    }
     1698
     1699    return !flg_deny_all; /* depends on whether we saw "D:*" */
    14831700}
    14841701
    14851702#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
    14861703/*
    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.
     1704 * Config file entries are of the form "/<path>:<user>:<passwd>".
     1705 * If config file has no prefix match for path, access is allowed.
     1706 *
     1707 * path                 The file path
     1708 * user_and_passwd      "user:passwd" to validate
     1709 *
     1710 * Returns 1 if user_and_passwd is OK.
    14961711 */
    1497 static int checkPerm(const char *path, const char *request)
     1712static int check_user_passwd(const char *path, const char *user_and_passwd)
    14981713{
    14991714    Htaccess *cur;
    1500     const char *p;
    1501     const char *p0;
    1502 
    15031715    const char *prev = NULL;
    15041716
    1505     /* This could stand some work */
    15061717    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;
     1718        const char *dir_prefix;
     1719        size_t len;
     1720
     1721        dir_prefix = cur->before_colon;
     1722
     1723        /* WHY? */
     1724        /* If already saw a match, don't accept other different matches */
     1725        if (prev && strcmp(prev, dir_prefix) != 0)
     1726            continue;
     1727
    15131728        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')
     1729            fprintf(stderr, "checkPerm: '%s' ? '%s'\n", dir_prefix, user_and_passwd);
     1730
     1731        /* If it's not a prefix match, continue searching */
     1732        len = strlen(dir_prefix);
     1733        if (len != 1 /* dir_prefix "/" matches all, don't need to check */
     1734         && (strncmp(dir_prefix, path, len) != 0
     1735            || (path[len] != '/' && path[len] != '\0'))
    15191736        ) {
    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 */
     1737            continue;
     1738        }
     1739
     1740        /* Path match found */
     1741        prev = dir_prefix;
     1742
     1743        if (ENABLE_FEATURE_HTTPD_AUTH_MD5) {
     1744            char *md5_passwd;
     1745
     1746            md5_passwd = strchr(cur->after_colon, ':');
     1747            if (md5_passwd && md5_passwd[1] == '$' && md5_passwd[2] == '1'
     1748             && md5_passwd[3] == '$' && md5_passwd[4]
     1749            ) {
     1750                char *encrypted;
     1751                int r, user_len_p1;
     1752
     1753                md5_passwd++;
     1754                user_len_p1 = md5_passwd - cur->after_colon;
     1755                /* comparing "user:" */
     1756                if (strncmp(cur->after_colon, user_and_passwd, user_len_p1) != 0) {
    15361757                    continue;
    15371758                }
    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) {
     1759
     1760                encrypted = pw_encrypt(
     1761                    user_and_passwd + user_len_p1 /* cleartext pwd from user */,
     1762                    md5_passwd /*salt */, 1 /* cleanup */);
     1763                r = strcmp(encrypted, md5_passwd);
     1764                free(encrypted);
     1765                if (r == 0)
     1766                    goto set_remoteuser_var; /* Ok */
     1767                continue;
     1768            }
     1769        }
     1770
     1771        /* Comparing plaintext "user:pass" in one go */
     1772        if (strcmp(cur->after_colon, user_and_passwd) == 0) {
    15521773 set_remoteuser_var:
    1553                 remoteuser = strdup(request);
    1554                 if (remoteuser)
    1555                     remoteuser[u - request] = '\0';
    1556                 return 1;   /* Ok */
    1557             }
    1558             /* unauthorized */
     1774            remoteuser = xstrndup(user_and_passwd,
     1775                    strchrnul(user_and_passwd, ':') - user_and_passwd);
     1776            return 1; /* Ok */
    15591777        }
    15601778    } /* for */
    15611779
    1562     return prev == NULL;
     1780    /* 0(bad) if prev is set: matches were found but passwd was wrong */
     1781    return (prev == NULL);
    15631782}
    15641783#endif  /* FEATURE_HTTPD_BASIC_AUTH */
     1784
     1785#if ENABLE_FEATURE_HTTPD_PROXY
     1786static Htaccess_Proxy *find_proxy_entry(const char *url)
     1787{
     1788    Htaccess_Proxy *p;
     1789    for (p = proxy; p; p = p->next) {
     1790        if (strncmp(url, p->url_from, strlen(p->url_from)) == 0)
     1791            return p;
     1792    }
     1793    return NULL;
     1794}
     1795#endif
    15651796
    15661797/*
    15671798 * Handle timeouts
    15681799 */
    1569 static void exit_on_signal(int sig) ATTRIBUTE_NORETURN;
    1570 static void exit_on_signal(int sig)
     1800static void send_REQUEST_TIMEOUT_and_exit(int sig) NORETURN;
     1801static void send_REQUEST_TIMEOUT_and_exit(int sig UNUSED_PARAM)
    15711802{
    15721803    send_headers_and_exit(HTTP_REQUEST_TIMEOUT);
     
    15761807 * Handle an incoming http request and exit.
    15771808 */
    1578 static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) ATTRIBUTE_NORETURN;
     1809static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) NORETURN;
    15791810static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
    15801811{
    15811812    static const char request_GET[] ALIGN1 = "GET";
    1582 
    15831813    struct stat sb;
    15841814    char *urlcopy;
    15851815    char *urlp;
    15861816    char *tptr;
    1587     int http_major_version;
    1588     int ip_allowed;
    15891817#if ENABLE_FEATURE_HTTPD_CGI
     1818    static const char request_HEAD[] ALIGN1 = "HEAD";
    15901819    const char *prequest;
     1820    char *cookie = NULL;
     1821    char *content_type = NULL;
    15911822    unsigned long length = 0;
    1592     char *cookie = 0;
    1593     char *content_type = 0;
    1594 #endif
    1595     struct sigaction sa;
     1823#elif ENABLE_FEATURE_HTTPD_PROXY
     1824#define prequest request_GET
     1825    unsigned long length = 0;
     1826#endif
    15961827#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
    1597     int credentials = -1;  /* if not required this is Ok */
     1828    smallint authorized = -1;
     1829#endif
     1830    smallint ip_allowed;
     1831    char http_major_version;
     1832#if ENABLE_FEATURE_HTTPD_PROXY
     1833    char http_minor_version;
     1834    char *header_buf = header_buf; /* for gcc */
     1835    char *header_ptr = header_ptr;
     1836    Htaccess_Proxy *proxy_entry;
    15981837#endif
    15991838
     
    16031842
    16041843    rmt_ip = 0;
    1605     if (fromAddr->sa.sa_family == AF_INET) {
    1606         rmt_ip = ntohl(fromAddr->sin.sin_addr.s_addr);
     1844    if (fromAddr->u.sa.sa_family == AF_INET) {
     1845        rmt_ip = ntohl(fromAddr->u.sin.sin_addr.s_addr);
    16071846    }
    16081847#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]);
     1848    if (fromAddr->u.sa.sa_family == AF_INET6
     1849     && fromAddr->u.sin6.sin6_addr.s6_addr32[0] == 0
     1850     && fromAddr->u.sin6.sin6_addr.s6_addr32[1] == 0
     1851     && ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[2]) == 0xffff)
     1852        rmt_ip = ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[3]);
    16141853#endif
    16151854    if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) {
    1616         rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr->sa);
     1855        /* NB: can be NULL (user runs httpd -i by hand?) */
     1856        rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr->u.sa);
    16171857    }
    16181858    if (verbose) {
    16191859        /* this trick makes -v logging much simpler */
    1620         applet_name = rmt_ip_str;
     1860        if (rmt_ip_str)
     1861            applet_name = rmt_ip_str;
    16211862        if (verbose > 2)
    16221863            bb_error_msg("connected");
    16231864    }
    16241865
    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);
     1866    /* Install timeout handler. get_line() needs it. */
     1867    signal(SIGALRM, send_REQUEST_TIMEOUT_and_exit);
    16321868
    16331869    if (!get_line()) /* EOF or error or empty line */
     
    16421878    prequest = request_GET;
    16431879    if (strcasecmp(iobuf, prequest) != 0) {
    1644         prequest = "POST";
    1645         if (strcasecmp(iobuf, prequest) != 0)
    1646             send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
     1880        prequest = request_HEAD;
     1881        if (strcasecmp(iobuf, prequest) != 0) {
     1882            prequest = "POST";
     1883            if (strcasecmp(iobuf, prequest) != 0)
     1884                send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
     1885        }
    16471886    }
    16481887#else
     
    16551894
    16561895    /* Find end of URL and parse HTTP version, if any */
    1657     http_major_version = -1;
     1896    http_major_version = '0';
     1897    IF_FEATURE_HTTPD_PROXY(http_minor_version = '0';)
    16581898    tptr = strchrnul(urlp, ' ');
    16591899    /* Is it " HTTP/"? */
    1660     if (tptr[0] && strncmp(tptr + 1, HTTP_200, 5) == 0)
    1661         http_major_version = (tptr[6] - '0');
     1900    if (tptr[0] && strncmp(tptr + 1, HTTP_200, 5) == 0) {
     1901        http_major_version = tptr[6];
     1902        IF_FEATURE_HTTPD_PROXY(http_minor_version = tptr[8];)
     1903    }
    16621904    *tptr = '\0';
    16631905
    16641906    /* Copy URL from after "GET "/"POST " to stack-allocated char[] */
    1665     urlcopy = alloca((tptr - urlp) + sizeof("/index.html"));
     1907    urlcopy = alloca((tptr - urlp) + 2 + strlen(index_page));
    16661908    /*if (urlcopy == NULL)
    16671909     *  send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);*/
     
    16701912
    16711913    /* Extract url args if present */
     1914    g_query = NULL;
    16721915    tptr = strchr(urlcopy, '?');
    1673     g_query = NULL;
    16741916    if (tptr) {
    16751917        *tptr++ = '\0';
     
    16881930    /* Canonicalize path */
    16891931    /* Algorithm stolen from libbb bb_simplify_path(),
    1690      * but don't strdup and reducing trailing slash and protect out root */
     1932     * but don't strdup, retain trailing slash, protect root */
    16911933    urlp = tptr = urlcopy;
    16921934    do {
     
    16971939            }
    16981940            if (*tptr == '.') {
    1699                 /* skip extra '.' */
     1941                /* skip extra "/./" */
    17001942                if (tptr[1] == '/' || !tptr[1]) {
    17011943                    continue;
    17021944                }
    1703                 /* '..': be careful */
     1945                /* "..": be careful */
    17041946                if (tptr[1] == '.' && (tptr[2] == '/' || !tptr[2])) {
    17051947                    ++tptr;
     
    17131955        *++urlp = *tptr;
    17141956    } while (*++tptr);
    1715     *++urlp = '\0';       /* so keep last character */
    1716     tptr = urlp;          /* end ptr */
     1957    *++urlp = '\0';       /* terminate after last character */
    17171958
    17181959    /* If URL is a directory, add '/' */
    1719     if (tptr[-1] != '/') {
    1720         if (is_directory(urlcopy + 1, 1, &sb)) {
     1960    if (urlp[-1] != '/') {
     1961        if (is_directory(urlcopy + 1, 1, NULL)) {
    17211962            found_moved_temporarily = urlcopy;
    17221963        }
     
    17321973        /* have path1/path2 */
    17331974        *tptr = '\0';
    1734         if (is_directory(urlcopy + 1, 1, &sb)) {
    1735             /* may be having subdir config */
     1975        if (is_directory(urlcopy + 1, 1, NULL)) {
     1976            /* may have subdir config */
    17361977            parse_conf(urlcopy + 1, SUBDIR_PARSE);
    17371978            ip_allowed = checkPermIP();
     
    17391980        *tptr = '/';
    17401981    }
    1741     if (http_major_version >= 0) {
     1982
     1983#if ENABLE_FEATURE_HTTPD_PROXY
     1984    proxy_entry = find_proxy_entry(urlcopy);
     1985    if (proxy_entry)
     1986        header_buf = header_ptr = xmalloc(IOBUF_SIZE);
     1987#endif
     1988
     1989    if (http_major_version >= '0') {
    17421990        /* Request was with "... HTTP/nXXX", and n >= 0 */
    17431991
    1744         /* Read until blank line for HTTP version specified, else parse immediate */
     1992        /* Read until blank line */
    17451993        while (1) {
    1746             alarm(HEADER_READ_TIMEOUT);
    17471994            if (!get_line())
    17481995                break; /* EOF or error or empty line */
     
    17501997                bb_error_msg("header: '%s'", iobuf);
    17511998
    1752 #if ENABLE_FEATURE_HTTPD_CGI
    1753             /* try and do our best to parse more lines */
     1999#if ENABLE_FEATURE_HTTPD_PROXY
     2000            /* We need 2 more bytes for yet another "\r\n" -
     2001             * see near fdprintf(proxy_fd...) further below */
     2002            if (proxy_entry && (header_ptr - header_buf) < IOBUF_SIZE - 2) {
     2003                int len = strlen(iobuf);
     2004                if (len > IOBUF_SIZE - (header_ptr - header_buf) - 4)
     2005                    len = IOBUF_SIZE - (header_ptr - header_buf) - 4;
     2006                memcpy(header_ptr, iobuf, len);
     2007                header_ptr += len;
     2008                header_ptr[0] = '\r';
     2009                header_ptr[1] = '\n';
     2010                header_ptr += 2;
     2011            }
     2012#endif
     2013
     2014#if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY
     2015            /* Try and do our best to parse more lines */
    17542016            if ((STRNCASECMP(iobuf, "Content-length:") == 0)) {
    17552017                /* extra read only for POST */
    1756                 if (prequest != request_GET) {
    1757                     tptr = iobuf + sizeof("Content-length:") - 1;
     2018                if (prequest != request_GET
     2019# if ENABLE_FEATURE_HTTPD_CGI
     2020                 && prequest != request_HEAD
     2021# endif
     2022                ) {
     2023                    tptr = skip_whitespace(iobuf + sizeof("Content-length:") - 1);
    17582024                    if (!tptr[0])
    17592025                        send_headers_and_exit(HTTP_BAD_REQUEST);
    1760                     errno = 0;
    17612026                    /* not using strtoul: it ignores leading minus! */
    1762                     length = strtol(tptr, &tptr, 10);
     2027                    length = bb_strtou(tptr, NULL, 10);
    17632028                    /* 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)
     2029                    if (errno || length > INT_MAX)
    17672030                        send_headers_and_exit(HTTP_BAD_REQUEST);
    17682031                }
    1769             } else if (STRNCASECMP(iobuf, "Cookie:") == 0) {
    1770                 cookie = strdup(skip_whitespace(iobuf + sizeof("Cookie:")-1));
     2032            }
     2033#endif
     2034#if ENABLE_FEATURE_HTTPD_CGI
     2035            else if (STRNCASECMP(iobuf, "Cookie:") == 0) {
     2036                cookie = xstrdup(skip_whitespace(iobuf + sizeof("Cookie:")-1));
    17712037            } else if (STRNCASECMP(iobuf, "Content-Type:") == 0) {
    1772                 content_type = strdup(skip_whitespace(iobuf + sizeof("Content-Type:")-1));
     2038                content_type = xstrdup(skip_whitespace(iobuf + sizeof("Content-Type:")-1));
    17732039            } else if (STRNCASECMP(iobuf, "Referer:") == 0) {
    1774                 referer = strdup(skip_whitespace(iobuf + sizeof("Referer:")-1));
     2040                referer = xstrdup(skip_whitespace(iobuf + sizeof("Referer:")-1));
    17752041            } else if (STRNCASECMP(iobuf, "User-Agent:") == 0) {
    1776                 user_agent = strdup(skip_whitespace(iobuf + sizeof("User-Agent:")-1));
     2042                user_agent = xstrdup(skip_whitespace(iobuf + sizeof("User-Agent:")-1));
     2043            } else if (STRNCASECMP(iobuf, "Host:") == 0) {
     2044                host = xstrdup(skip_whitespace(iobuf + sizeof("Host:")-1));
     2045            } else if (STRNCASECMP(iobuf, "Accept:") == 0) {
     2046                http_accept = xstrdup(skip_whitespace(iobuf + sizeof("Accept:")-1));
     2047            } else if (STRNCASECMP(iobuf, "Accept-Language:") == 0) {
     2048                http_accept_language = xstrdup(skip_whitespace(iobuf + sizeof("Accept-Language:")-1));
    17772049            }
    17782050#endif
     
    17802052            if (STRNCASECMP(iobuf, "Authorization:") == 0) {
    17812053                /* We only allow Basic credentials.
    1782                  * It shows up as "Authorization: Basic <userid:password>" where
    1783                  * the userid:password is base64 encoded.
     2054                 * It shows up as "Authorization: Basic <user>:<passwd>" where
     2055                 * "<user>:<passwd>" is base64 encoded.
    17842056                 */
    17852057                tptr = skip_whitespace(iobuf + sizeof("Authorization:")-1);
     
    17892061                /* decodeBase64() skips whitespace itself */
    17902062                decodeBase64(tptr);
    1791                 credentials = checkPerm(urlcopy, tptr);
    1792             }
    1793 #endif          /* FEATURE_HTTPD_BASIC_AUTH */
     2063                authorized = check_user_passwd(urlcopy, tptr);
     2064            }
     2065#endif
     2066#if ENABLE_FEATURE_HTTPD_RANGES
     2067            if (STRNCASECMP(iobuf, "Range:") == 0) {
     2068                /* We know only bytes=NNN-[MMM] */
     2069                char *s = skip_whitespace(iobuf + sizeof("Range:")-1);
     2070                if (strncmp(s, "bytes=", 6) == 0) {
     2071                    s += sizeof("bytes=")-1;
     2072                    range_start = BB_STRTOOFF(s, &s, 10);
     2073                    if (s[0] != '-' || range_start < 0) {
     2074                        range_start = 0;
     2075                    } else if (s[1]) {
     2076                        range_end = BB_STRTOOFF(s+1, NULL, 10);
     2077                        if (errno || range_end < range_start)
     2078                            range_start = 0;
     2079                    }
     2080                }
     2081            }
     2082#endif
     2083#if ENABLE_FEATURE_HTTPD_GZIP
     2084            if (STRNCASECMP(iobuf, "Accept-Encoding:") == 0) {
     2085                /* Note: we do not support "gzip;q=0"
     2086                 * method of _disabling_ gzip
     2087                 * delivery. No one uses that, though */
     2088                const char *s = strstr(iobuf, "gzip");
     2089                if (s) {
     2090                    // want more thorough checks?
     2091                    //if (s[-1] == ' '
     2092                    // || s[-1] == ','
     2093                    // || s[-1] == ':'
     2094                    //) {
     2095                        content_gzip = 1;
     2096                    //}
     2097                }
     2098            }
     2099#endif
    17942100        } /* while extra header reading */
    17952101    }
    17962102
    1797     /* We read headers, disable peer timeout */
     2103    /* We are done reading headers, disable peer timeout */
    17982104    alarm(0);
    17992105
    1800     if (strcmp(bb_basename(urlcopy), httpd_conf) == 0 || ip_allowed == 0) {
    1801         /* protect listing [/path]/httpd_conf or IP deny */
     2106    if (strcmp(bb_basename(urlcopy), HTTPD_CONF) == 0 || !ip_allowed) {
     2107        /* protect listing [/path]/httpd.conf or IP deny */
    18022108        send_headers_and_exit(HTTP_FORBIDDEN);
    18032109    }
    18042110
    18052111#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
    1806     if (credentials <= 0 && checkPerm(urlcopy, ":") == 0) {
     2112    /* Case: no "Authorization:" was seen, but page does require passwd.
     2113     * Check that with dummy user:pass */
     2114    if (authorized < 0)
     2115        authorized = check_user_passwd(urlcopy, ":");
     2116    if (!authorized)
    18072117        send_headers_and_exit(HTTP_UNAUTHORIZED);
    1808     }
    18092118#endif
    18102119
     
    18122121        send_headers_and_exit(HTTP_MOVED_TEMPORARILY);
    18132122    }
     2123
     2124#if ENABLE_FEATURE_HTTPD_PROXY
     2125    if (proxy_entry != NULL) {
     2126        int proxy_fd;
     2127        len_and_sockaddr *lsa;
     2128
     2129        proxy_fd = socket(AF_INET, SOCK_STREAM, 0);
     2130        if (proxy_fd < 0)
     2131            send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
     2132        lsa = host2sockaddr(proxy_entry->host_port, 80);
     2133        if (lsa == NULL)
     2134            send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
     2135        if (connect(proxy_fd, &lsa->u.sa, lsa->len) < 0)
     2136            send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
     2137        fdprintf(proxy_fd, "%s %s%s%s%s HTTP/%c.%c\r\n",
     2138                prequest, /* GET or POST */
     2139                proxy_entry->url_to, /* url part 1 */
     2140                urlcopy + strlen(proxy_entry->url_from), /* url part 2 */
     2141                (g_query ? "?" : ""), /* "?" (maybe) */
     2142                (g_query ? g_query : ""), /* query string (maybe) */
     2143                http_major_version, http_minor_version);
     2144        header_ptr[0] = '\r';
     2145        header_ptr[1] = '\n';
     2146        header_ptr += 2;
     2147        write(proxy_fd, header_buf, header_ptr - header_buf);
     2148        free(header_buf); /* on the order of 8k, free it */
     2149        cgi_io_loop_and_exit(proxy_fd, proxy_fd, length);
     2150    }
     2151#endif
    18142152
    18152153    tptr = urlcopy + 1;      /* skip first '/' */
     
    18232161        send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type);
    18242162    }
     2163#endif
     2164
     2165    if (urlp[-1] == '/')
     2166        strcpy(urlp, index_page);
     2167    if (stat(tptr, &sb) == 0) {
    18252168#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
    1826     {
    18272169        char *suffix = strrchr(tptr, '.');
    18282170        if (suffix) {
     
    18342176            }
    18352177        }
    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;
     2178#endif
     2179        file_size = sb.st_size;
    18482180        last_mod = sb.st_mtime;
    18492181    }
     
    18582190        }
    18592191    }
    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);
     2192    /* else fall through to send_file, it errors out if open fails: */
     2193
     2194    if (prequest != request_GET && prequest != request_HEAD) {
     2195        /* POST for files does not make sense */
     2196        send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
     2197    }
     2198    send_file_and_exit(tptr,
     2199        (prequest != request_HEAD ? SEND_HEADERS_AND_BODY : SEND_HEADERS)
     2200    );
     2201#else
     2202    send_file_and_exit(tptr, SEND_HEADERS_AND_BODY);
     2203#endif
    18672204}
    18682205
     
    18742211 */
    18752212#if BB_MMU
    1876 static void mini_httpd(int server_socket) ATTRIBUTE_NORETURN;
     2213static void mini_httpd(int server_socket) NORETURN;
    18772214static void mini_httpd(int server_socket)
    18782215{
     
    18852222        int n;
    18862223        len_and_sockaddr fromAddr;
    1887        
     2224
    18882225        /* Wait for connections... */
    18892226        fromAddr.len = LSA_SIZEOF_SA;
    1890         n = accept(server_socket, &fromAddr.sa, &fromAddr.len);
    1891 
     2227        n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len);
    18922228        if (n < 0)
    18932229            continue;
     2230
    18942231        /* set the KEEPALIVE option to cull dead connections */
    18952232        setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
     
    18972234        if (fork() == 0) {
    18982235            /* child */
    1899 #if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
    19002236            /* Do not reload config on HUP */
    19012237            signal(SIGHUP, SIG_IGN);
    1902 #endif
    19032238            close(server_socket);
    19042239            xmove_fd(n, 0);
     
    19132248}
    19142249#else
    1915 static void mini_httpd_nommu(int server_socket, int argc, char **argv) ATTRIBUTE_NORETURN;
     2250static void mini_httpd_nommu(int server_socket, int argc, char **argv) NORETURN;
    19162251static void mini_httpd_nommu(int server_socket, int argc, char **argv)
    19172252{
     
    19302265        int n;
    19312266        len_and_sockaddr fromAddr;
    1932        
     2267
    19332268        /* Wait for connections... */
    19342269        fromAddr.len = LSA_SIZEOF_SA;
    1935         n = accept(server_socket, &fromAddr.sa, &fromAddr.len);
    1936 
     2270        n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len);
    19372271        if (n < 0)
    19382272            continue;
     2273
    19392274        /* set the KEEPALIVE option to cull dead connections */
    19402275        setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
     
    19422277        if (vfork() == 0) {
    19432278            /* child */
    1944 #if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
    19452279            /* Do not reload config on HUP */
    19462280            signal(SIGHUP, SIG_IGN);
    1947 #endif
    19482281            close(server_socket);
    19492282            xmove_fd(n, 0);
     
    19642297 * Never returns.
    19652298 */
    1966 static void mini_httpd_inetd(void) ATTRIBUTE_NORETURN;
     2299static void mini_httpd_inetd(void) NORETURN;
    19672300static void mini_httpd_inetd(void)
    19682301{
    19692302    len_and_sockaddr fromAddr;
    19702303
     2304    memset(&fromAddr, 0, sizeof(fromAddr));
    19712305    fromAddr.len = LSA_SIZEOF_SA;
    1972     getpeername(0, &fromAddr.sa, &fromAddr.len);
     2306    /* NB: can fail if user runs it by hand and types in http cmds */
     2307    getpeername(0, &fromAddr.u.sa, &fromAddr.len);
    19732308    handle_incoming_and_exit(&fromAddr);
    19742309}
    19752310
    1976 #if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
    1977 static 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
     2311static void sighup_handler(int sig UNUSED_PARAM)
     2312{
     2313    parse_conf(DEFAULT_PATH_HTTPD_CONF, SIGNALED_PARSE);
     2314}
    19902315
    19912316enum {
     
    19932318    d_opt_decode_url,
    19942319    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    ,)
     2320    IF_FEATURE_HTTPD_ENCODE_URL_STR(e_opt_encode_url,)
     2321    IF_FEATURE_HTTPD_BASIC_AUTH(    r_opt_realm     ,)
     2322    IF_FEATURE_HTTPD_AUTH_MD5(      m_opt_md5       ,)
     2323    IF_FEATURE_HTTPD_SETUID(        u_opt_setuid    ,)
    19992324    p_opt_port      ,
    20002325    p_opt_inetd     ,
     
    20042329    OPT_DECODE_URL  = 1 << d_opt_decode_url,
    20052330    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,
     2331    OPT_ENCODE_URL  = IF_FEATURE_HTTPD_ENCODE_URL_STR((1 << e_opt_encode_url)) + 0,
     2332    OPT_REALM       = IF_FEATURE_HTTPD_BASIC_AUTH(    (1 << r_opt_realm     )) + 0,
     2333    OPT_MD5         = IF_FEATURE_HTTPD_AUTH_MD5(      (1 << m_opt_md5       )) + 0,
     2334    OPT_SETUID      = IF_FEATURE_HTTPD_SETUID(        (1 << u_opt_setuid    )) + 0,
    20102335    OPT_PORT        = 1 << p_opt_port,
    20112336    OPT_INETD       = 1 << p_opt_inetd,
     
    20152340
    20162341
    2017 int httpd_main(int argc, char **argv);
    2018 int httpd_main(int argc, char **argv)
     2342int httpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
     2343int httpd_main(int argc UNUSED_PARAM, char **argv)
    20192344{
    20202345    int server_socket = server_socket; /* for gcc */
    20212346    unsigned opt;
    20222347    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;)
     2348    IF_FEATURE_HTTPD_ENCODE_URL_STR(const char *url_for_encode;)
     2349    IF_FEATURE_HTTPD_SETUID(const char *s_ugid = NULL;)
     2350    IF_FEATURE_HTTPD_SETUID(struct bb_uidgid_t ugid;)
     2351    IF_FEATURE_HTTPD_AUTH_MD5(const char *pass;)
    20272352
    20282353    INIT_G();
     
    20372362    opt_complementary = "vv:if";
    20382363    /* 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. */
     2364     * If user gives relative path in -h,
     2365     * $SCRIPT_FILENAME will not be set. */
    20412366    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:")
     2367            IF_FEATURE_HTTPD_ENCODE_URL_STR("e:")
     2368            IF_FEATURE_HTTPD_BASIC_AUTH("r:")
     2369            IF_FEATURE_HTTPD_AUTH_MD5("m:")
     2370            IF_FEATURE_HTTPD_SETUID("u:")
    20462371            "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)
     2372            &opt_c_configFile, &url_for_decode, &home_httpd
     2373            IF_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode)
     2374            IF_FEATURE_HTTPD_BASIC_AUTH(, &g_realm)
     2375            IF_FEATURE_HTTPD_AUTH_MD5(, &pass)
     2376            IF_FEATURE_HTTPD_SETUID(, &s_ugid)
    20522377            , &bind_addr_or_port
    20532378            , &verbose
     
    20652390#if ENABLE_FEATURE_HTTPD_AUTH_MD5
    20662391    if (opt & OPT_MD5) {
    2067         puts(pw_encrypt(pass, "$1$"));
     2392        char salt[sizeof("$1$XXXXXXXX")];
     2393        salt[0] = '$';
     2394        salt[1] = '1';
     2395        salt[2] = '$';
     2396        crypt_make_salt(salt + 3, 4, 0);
     2397        puts(pw_encrypt(pass, salt, 1));
    20682398        return 0;
    20692399    }
     
    20712401#if ENABLE_FEATURE_HTTPD_SETUID
    20722402    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);
     2403        xget_uidgid(&ugid, s_ugid);
    20762404    }
    20772405#endif
     
    21002428    }
    21012429
    2102 #if ENABLE_FEATURE_HTTPD_CGI
     2430#if 0
     2431    /* User can do it himself: 'env - PATH="$PATH" httpd'
     2432     * We don't do it because we don't want to screw users
     2433     * which want to do
     2434     * 'env - VAR1=val1 VAR2=val2 httpd'
     2435     * and have VAR1 and VAR2 values visible in their CGIs.
     2436     * Besides, it is also smaller. */
    21032437    {
    21042438        char *p = getenv("PATH");
    2105         /* env strings themself are not freed, no need to strdup(p): */
     2439        /* env strings themself are not freed, no need to xstrdup(p): */
    21062440        clearenv();
    21072441        if (p)
     
    21122446#endif
    21132447
    2114 #if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
     2448    parse_conf(DEFAULT_PATH_HTTPD_CONF, FIRST_PARSE);
    21152449    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);
     2450        signal(SIGHUP, sighup_handler);
    21202451
    21212452    xfunc_error_retval = 0;
Note: See TracChangeset for help on using the changeset viewer.