busybox/networking/httpd.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * httpd implementation for busybox
   4 *
   5 * Copyright (C) 2002,2003 Glenn Engel <glenne@engel.org>
   6 * Copyright (C) 2003-2006 Vladimir Oleynik <dzo@simtreas.ru>
   7 *
   8 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
   9 *
  10 *****************************************************************************
  11 *
  12 * Typical usage:
  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
  22 * after setting QUERY_STRING and other environment variables.
  23 *
  24 * If directory URL is given, no index.html is found and CGI support is enabled,
  25 * cgi-bin/index.cgi will be run. Directory to list is ../$QUERY_STRING.
  26 * See httpd_indexcgi.c for an example GCI code.
  27 *
  28 * Doc:
  29 * "CGI Environment Variables": http://hoohoo.ncsa.uiuc.edu/cgi/env.html
  30 *
  31 * The applet can also be invoked as an url arg decoder and html text encoder
  32 * as follows:
  33 *      foo=`httpd -d $foo`             # decode "Hello%20World" as "Hello World"
  34 *      bar=`httpd -e "<Hello World>"`  # encode as "&#60Hello&#32World&#62"
  35 * Note that url encoding for arguments is not the same as html encoding for
  36 * presentation.  -d decodes an url-encoded argument while -e encodes in html
  37 * for page display.
  38 *
  39 * httpd.conf has the following format:
  40 *
  41 * H:/serverroot     # define the server root. It will override -h
  42 * A:172.20.         # Allow address from 172.20.0.0/16
  43 * A:10.0.0.0/25     # Allow any address from 10.0.0.0-10.0.0.127
  44 * A:10.0.0.0/255.255.255.128  # Allow any address that previous set
  45 * A:127.0.0.1       # Allow local loopback connections
  46 * D:*               # Deny from other IP connections
  47 * E404:/path/e404.html # /path/e404.html is the 404 (not found) error page
  48 * I:index.html      # Show index.html when a directory is requested
  49 *
  50 * P:/url:[http://]hostname[:port]/new/path
  51 *                   # When /urlXXXXXX is requested, reverse proxy
  52 *                   # it to http://hostname[:port]/new/pathXXXXXX
  53 *
  54 * /cgi-bin:foo:bar  # Require user foo, pwd bar on urls starting with /cgi-bin/
  55 * /adm:admin:setup  # Require user admin, pwd setup on urls starting with /adm/
  56 * /adm:toor:PaSsWd  # or user toor, pwd PaSsWd on urls starting with /adm/
  57 * /adm:root:*       # or user root, pwd from /etc/passwd on urls starting with /adm/
  58 * /wiki:*:*         # or any user from /etc/passwd with according pwd on urls starting with /wiki/
  59 * .au:audio/basic   # additional mime type for audio.au files
  60 * *.php:/path/php   # run xxx.php through an interpreter
  61 *
  62 * A/D may be as a/d or allow/deny - only first char matters.
  63 * Deny/Allow IP logic:
  64 *  - Default is to allow all (Allow all (A:*) is a no-op).
  65 *  - Deny rules take precedence over allow rules.
  66 *  - "Deny all" rule (D:*) is applied last.
  67 *
  68 * Example:
  69 *   1. Allow only specified addresses
  70 *     A:172.20          # Allow any address that begins with 172.20.
  71 *     A:10.10.          # Allow any address that begins with 10.10.
  72 *     A:127.0.0.1       # Allow local loopback connections
  73 *     D:*               # Deny from other IP connections
  74 *
  75 *   2. Only deny specified addresses
  76 *     D:1.2.3.        # deny from 1.2.3.0 - 1.2.3.255
  77 *     D:2.3.4.        # deny from 2.3.4.0 - 2.3.4.255
  78 *     A:*             # (optional line added for clarity)
  79 *
  80 * If a sub directory contains config file, it is parsed and merged with
  81 * any existing settings as if it was appended to the original configuration.
  82 *
  83 * subdir paths are relative to the containing subdir and thus cannot
  84 * affect the parent rules.
  85 *
  86 * Note that since the sub dir is parsed in the forked thread servicing the
  87 * subdir http request, any merge is discarded when the process exits.  As a
  88 * result, the subdir settings only have a lifetime of a single request.
  89 *
  90 * Custom error pages can contain an absolute path or be relative to
  91 * 'home_httpd'. Error pages are to be static files (no CGI or script). Error
  92 * page can only be defined in the root configuration file and are not taken
  93 * into account in local (directories) config files.
  94 *
  95 * If -c is not set, an attempt will be made to open the default
  96 * root configuration file.  If -c is set and the file is not found, the
  97 * server exits with an error.
  98 *
  99 */
 100 /* TODO: use TCP_CORK, parse_config() */
 101
 102//usage:#define httpd_trivial_usage
 103//usage:       "[-ifv[v]]"
 104//usage:       " [-c CONFFILE]"
 105//usage:       " [-p [IP:]PORT]"
 106//usage:        IF_FEATURE_HTTPD_SETUID(" [-u USER[:GRP]]")
 107//usage:        IF_FEATURE_HTTPD_BASIC_AUTH(" [-r REALM]")
 108//usage:       " [-h HOME]\n"
 109//usage:       "or httpd -d/-e" IF_FEATURE_HTTPD_AUTH_MD5("/-m") " STRING"
 110//usage:#define httpd_full_usage "\n\n"
 111//usage:       "Listen for incoming HTTP requests\n"
 112//usage:     "\n        -i              Inetd mode"
 113//usage:     "\n        -f              Don't daemonize"
 114//usage:     "\n        -v[v]           Verbose"
 115//usage:     "\n        -p [IP:]PORT    Bind to IP:PORT (default *:80)"
 116//usage:        IF_FEATURE_HTTPD_SETUID(
 117//usage:     "\n        -u USER[:GRP]   Set uid/gid after binding to port")
 118//usage:        IF_FEATURE_HTTPD_BASIC_AUTH(
 119//usage:     "\n        -r REALM        Authentication Realm for Basic Authentication")
 120//usage:     "\n        -h HOME         Home directory (default .)"
 121//usage:     "\n        -c FILE         Configuration file (default {/etc,HOME}/httpd.conf)"
 122//usage:        IF_FEATURE_HTTPD_AUTH_MD5(
 123//usage:     "\n        -m STRING       MD5 crypt STRING")
 124//usage:     "\n        -e STRING       HTML encode STRING"
 125//usage:     "\n        -d STRING       URL decode STRING"
 126
 127#include "libbb.h"
 128#if ENABLE_PAM
 129/* PAM may include <locale.h>. We may need to undefine bbox's stub define: */
 130# undef setlocale
 131/* For some obscure reason, PAM is not in pam/xxx, but in security/xxx.
 132 * Apparently they like to confuse people. */
 133# include <security/pam_appl.h>
 134# include <security/pam_misc.h>
 135#endif
 136#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
 137# include <sys/sendfile.h>
 138#endif
 139/* amount of buffering in a pipe */
 140#ifndef PIPE_BUF
 141# define PIPE_BUF 4096
 142#endif
 143
 144#define DEBUG 0
 145
 146#define IOBUF_SIZE 8192
 147#if PIPE_BUF >= IOBUF_SIZE
 148# error "PIPE_BUF >= IOBUF_SIZE"
 149#endif
 150
 151#define HEADER_READ_TIMEOUT 60
 152
 153static const char DEFAULT_PATH_HTTPD_CONF[] ALIGN1 = "/etc";
 154static const char HTTPD_CONF[] ALIGN1 = "httpd.conf";
 155static const char HTTP_200[] ALIGN1 = "HTTP/1.0 200 OK\r\n";
 156static const char index_html[] ALIGN1 = "index.html";
 157
 158typedef struct has_next_ptr {
 159        struct has_next_ptr *next;
 160} has_next_ptr;
 161
 162/* Must have "next" as a first member */
 163typedef struct Htaccess {
 164        struct Htaccess *next;
 165        char *after_colon;
 166        char before_colon[1];  /* really bigger, must be last */
 167} Htaccess;
 168
 169/* Must have "next" as a first member */
 170typedef struct Htaccess_IP {
 171        struct Htaccess_IP *next;
 172        unsigned ip;
 173        unsigned mask;
 174        int allow_deny;
 175} Htaccess_IP;
 176
 177/* Must have "next" as a first member */
 178typedef struct Htaccess_Proxy {
 179        struct Htaccess_Proxy *next;
 180        char *url_from;
 181        char *host_port;
 182        char *url_to;
 183} Htaccess_Proxy;
 184
 185enum {
 186        HTTP_OK = 200,
 187        HTTP_PARTIAL_CONTENT = 206,
 188        HTTP_MOVED_TEMPORARILY = 302,
 189        HTTP_BAD_REQUEST = 400,       /* malformed syntax */
 190        HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */
 191        HTTP_NOT_FOUND = 404,
 192        HTTP_FORBIDDEN = 403,
 193        HTTP_REQUEST_TIMEOUT = 408,
 194        HTTP_NOT_IMPLEMENTED = 501,   /* used for unrecognized requests */
 195        HTTP_INTERNAL_SERVER_ERROR = 500,
 196        HTTP_CONTINUE = 100,
 197#if 0   /* future use */
 198        HTTP_SWITCHING_PROTOCOLS = 101,
 199        HTTP_CREATED = 201,
 200        HTTP_ACCEPTED = 202,
 201        HTTP_NON_AUTHORITATIVE_INFO = 203,
 202        HTTP_NO_CONTENT = 204,
 203        HTTP_MULTIPLE_CHOICES = 300,
 204        HTTP_MOVED_PERMANENTLY = 301,
 205        HTTP_NOT_MODIFIED = 304,
 206        HTTP_PAYMENT_REQUIRED = 402,
 207        HTTP_BAD_GATEWAY = 502,
 208        HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
 209#endif
 210};
 211
 212static const uint16_t http_response_type[] ALIGN2 = {
 213        HTTP_OK,
 214#if ENABLE_FEATURE_HTTPD_RANGES
 215        HTTP_PARTIAL_CONTENT,
 216#endif
 217        HTTP_MOVED_TEMPORARILY,
 218        HTTP_REQUEST_TIMEOUT,
 219        HTTP_NOT_IMPLEMENTED,
 220#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
 221        HTTP_UNAUTHORIZED,
 222#endif
 223        HTTP_NOT_FOUND,
 224        HTTP_BAD_REQUEST,
 225        HTTP_FORBIDDEN,
 226        HTTP_INTERNAL_SERVER_ERROR,
 227#if 0   /* not implemented */
 228        HTTP_CREATED,
 229        HTTP_ACCEPTED,
 230        HTTP_NO_CONTENT,
 231        HTTP_MULTIPLE_CHOICES,
 232        HTTP_MOVED_PERMANENTLY,
 233        HTTP_NOT_MODIFIED,
 234        HTTP_BAD_GATEWAY,
 235        HTTP_SERVICE_UNAVAILABLE,
 236#endif
 237};
 238
 239static const struct {
 240        const char *name;
 241        const char *info;
 242} http_response[ARRAY_SIZE(http_response_type)] = {
 243        { "OK", NULL },
 244#if ENABLE_FEATURE_HTTPD_RANGES
 245        { "Partial Content", NULL },
 246#endif
 247        { "Found", NULL },
 248        { "Request Timeout", "No request appeared within 60 seconds" },
 249        { "Not Implemented", "The requested method is not recognized" },
 250#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
 251        { "Unauthorized", "" },
 252#endif
 253        { "Not Found", "The requested URL was not found" },
 254        { "Bad Request", "Unsupported method" },
 255        { "Forbidden", ""  },
 256        { "Internal Server Error", "Internal Server Error" },
 257#if 0   /* not implemented */
 258        { "Created" },
 259        { "Accepted" },
 260        { "No Content" },
 261        { "Multiple Choices" },
 262        { "Moved Permanently" },
 263        { "Not Modified" },
 264        { "Bad Gateway", "" },
 265        { "Service Unavailable", "" },
 266#endif
 267};
 268
 269struct globals {
 270        int verbose;            /* must be int (used by getopt32) */
 271        smallint flg_deny_all;
 272
 273        unsigned rmt_ip;        /* used for IP-based allow/deny rules */
 274        time_t last_mod;
 275        char *rmt_ip_str;       /* for $REMOTE_ADDR and $REMOTE_PORT */
 276        const char *bind_addr_or_port;
 277
 278        const char *g_query;
 279        const char *opt_c_configFile;
 280        const char *home_httpd;
 281        const char *index_page;
 282
 283        const char *found_mime_type;
 284        const char *found_moved_temporarily;
 285        Htaccess_IP *ip_a_d;    /* config allow/deny lines */
 286
 287        IF_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;)
 288        IF_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;)
 289        IF_FEATURE_HTTPD_CGI(char *referer;)
 290        IF_FEATURE_HTTPD_CGI(char *user_agent;)
 291        IF_FEATURE_HTTPD_CGI(char *host;)
 292        IF_FEATURE_HTTPD_CGI(char *http_accept;)
 293        IF_FEATURE_HTTPD_CGI(char *http_accept_language;)
 294
 295        off_t file_size;        /* -1 - unknown */
 296#if ENABLE_FEATURE_HTTPD_RANGES
 297        off_t range_start;
 298        off_t range_end;
 299        off_t range_len;
 300#endif
 301
 302#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
 303        Htaccess *g_auth;       /* config user:password lines */
 304#endif
 305        Htaccess *mime_a;       /* config mime types */
 306#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
 307        Htaccess *script_i;     /* config script interpreters */
 308#endif
 309        char *iobuf;            /* [IOBUF_SIZE] */
 310#define hdr_buf bb_common_bufsiz1
 311        char *hdr_ptr;
 312        int hdr_cnt;
 313#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
 314        const char *http_error_page[ARRAY_SIZE(http_response_type)];
 315#endif
 316#if ENABLE_FEATURE_HTTPD_PROXY
 317        Htaccess_Proxy *proxy;
 318#endif
 319#if ENABLE_FEATURE_HTTPD_GZIP
 320        /* client can handle gzip / we are going to send gzip */
 321        smallint content_gzip;
 322#endif
 323};
 324#define G (*ptr_to_globals)
 325#define verbose           (G.verbose          )
 326#define flg_deny_all      (G.flg_deny_all     )
 327#define rmt_ip            (G.rmt_ip           )
 328#define bind_addr_or_port (G.bind_addr_or_port)
 329#define g_query           (G.g_query          )
 330#define opt_c_configFile  (G.opt_c_configFile )
 331#define home_httpd        (G.home_httpd       )
 332#define index_page        (G.index_page       )
 333#define found_mime_type   (G.found_mime_type  )
 334#define found_moved_temporarily (G.found_moved_temporarily)
 335#define last_mod          (G.last_mod         )
 336#define ip_a_d            (G.ip_a_d           )
 337#define g_realm           (G.g_realm          )
 338#define remoteuser        (G.remoteuser       )
 339#define referer           (G.referer          )
 340#define user_agent        (G.user_agent       )
 341#define host              (G.host             )
 342#define http_accept       (G.http_accept      )
 343#define http_accept_language (G.http_accept_language)
 344#define file_size         (G.file_size        )
 345#if ENABLE_FEATURE_HTTPD_RANGES
 346#define range_start       (G.range_start      )
 347#define range_end         (G.range_end        )
 348#define range_len         (G.range_len        )
 349#else
 350enum {
 351        range_start = -1,
 352        range_end = MAXINT(off_t) - 1,
 353        range_len = MAXINT(off_t),
 354};
 355#endif
 356#define rmt_ip_str        (G.rmt_ip_str       )
 357#define g_auth            (G.g_auth           )
 358#define mime_a            (G.mime_a           )
 359#define script_i          (G.script_i         )
 360#define iobuf             (G.iobuf            )
 361#define hdr_ptr           (G.hdr_ptr          )
 362#define hdr_cnt           (G.hdr_cnt          )
 363#define http_error_page   (G.http_error_page  )
 364#define proxy             (G.proxy            )
 365#if ENABLE_FEATURE_HTTPD_GZIP
 366# define content_gzip     (G.content_gzip     )
 367#else
 368# define content_gzip     0
 369#endif
 370#define INIT_G() do { \
 371        SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
 372        IF_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
 373        IF_FEATURE_HTTPD_RANGES(range_start = -1;) \
 374        bind_addr_or_port = "80"; \
 375        index_page = index_html; \
 376        file_size = -1; \
 377} while (0)
 378
 379
 380#define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1)
 381
 382/* Prototypes */
 383enum {
 384        SEND_HEADERS     = (1 << 0),
 385        SEND_BODY        = (1 << 1),
 386        SEND_HEADERS_AND_BODY = SEND_HEADERS + SEND_BODY,
 387};
 388static void send_file_and_exit(const char *url, int what) NORETURN;
 389
 390static void free_llist(has_next_ptr **pptr)
 391{
 392        has_next_ptr *cur = *pptr;
 393        while (cur) {
 394                has_next_ptr *t = cur;
 395                cur = cur->next;
 396                free(t);
 397        }
 398        *pptr = NULL;
 399}
 400
 401static ALWAYS_INLINE void free_Htaccess_list(Htaccess **pptr)
 402{
 403        free_llist((has_next_ptr**)pptr);
 404}
 405
 406static ALWAYS_INLINE void free_Htaccess_IP_list(Htaccess_IP **pptr)
 407{
 408        free_llist((has_next_ptr**)pptr);
 409}
 410
 411/* Returns presumed mask width in bits or < 0 on error.
 412 * Updates strp, stores IP at provided pointer */
 413static int scan_ip(const char **strp, unsigned *ipp, unsigned char endc)
 414{
 415        const char *p = *strp;
 416        int auto_mask = 8;
 417        unsigned ip = 0;
 418        int j;
 419
 420        if (*p == '/')
 421                return -auto_mask;
 422
 423        for (j = 0; j < 4; j++) {
 424                unsigned octet;
 425
 426                if ((*p < '0' || *p > '9') && *p != '/' && *p)
 427                        return -auto_mask;
 428                octet = 0;
 429                while (*p >= '0' && *p <= '9') {
 430                        octet *= 10;
 431                        octet += *p - '0';
 432                        if (octet > 255)
 433                                return -auto_mask;
 434                        p++;
 435                }
 436                if (*p == '.')
 437                        p++;
 438                if (*p != '/' && *p)
 439                        auto_mask += 8;
 440                ip = (ip << 8) | octet;
 441        }
 442        if (*p) {
 443                if (*p != endc)
 444                        return -auto_mask;
 445                p++;
 446                if (*p == '\0')
 447                        return -auto_mask;
 448        }
 449        *ipp = ip;
 450        *strp = p;
 451        return auto_mask;
 452}
 453
 454/* Returns 0 on success. Stores IP and mask at provided pointers */
 455static int scan_ip_mask(const char *str, unsigned *ipp, unsigned *maskp)
 456{
 457        int i;
 458        unsigned mask;
 459        char *p;
 460
 461        i = scan_ip(&str, ipp, '/');
 462        if (i < 0)
 463                return i;
 464
 465        if (*str) {
 466                /* there is /xxx after dotted-IP address */
 467                i = bb_strtou(str, &p, 10);
 468                if (*p == '.') {
 469                        /* 'xxx' itself is dotted-IP mask, parse it */
 470                        /* (return 0 (success) only if it has N.N.N.N form) */
 471                        return scan_ip(&str, maskp, '\0') - 32;
 472                }
 473                if (*p)
 474                        return -1;
 475        }
 476
 477        if (i > 32)
 478                return -1;
 479
 480        if (sizeof(unsigned) == 4 && i == 32) {
 481                /* mask >>= 32 below may not work */
 482                mask = 0;
 483        } else {
 484                mask = 0xffffffff;
 485                mask >>= i;
 486        }
 487        /* i == 0 -> *maskp = 0x00000000
 488         * i == 1 -> *maskp = 0x80000000
 489         * i == 4 -> *maskp = 0xf0000000
 490         * i == 31 -> *maskp = 0xfffffffe
 491         * i == 32 -> *maskp = 0xffffffff */
 492        *maskp = (uint32_t)(~mask);
 493        return 0;
 494}
 495
 496/*
 497 * Parse configuration file into in-memory linked list.
 498 *
 499 * Any previous IP rules are discarded.
 500 * If the flag argument is not SUBDIR_PARSE then all /path and mime rules
 501 * are also discarded.  That is, previous settings are retained if flag is
 502 * SUBDIR_PARSE.
 503 * Error pages are only parsed on the main config file.
 504 *
 505 * path   Path where to look for httpd.conf (without filename).
 506 * flag   Type of the parse request.
 507 */
 508/* flag param: */
 509enum {
 510        FIRST_PARSE    = 0, /* path will be "/etc" */
 511        SIGNALED_PARSE = 1, /* path will be "/etc" */
 512        SUBDIR_PARSE   = 2, /* path will be derived from URL */
 513};
 514static void parse_conf(const char *path, int flag)
 515{
 516        /* internally used extra flag state */
 517        enum { TRY_CURDIR_PARSE = 3 };
 518
 519        FILE *f;
 520        const char *filename;
 521        char buf[160];
 522
 523        /* discard old rules */
 524        free_Htaccess_IP_list(&ip_a_d);
 525        flg_deny_all = 0;
 526        /* retain previous auth and mime config only for subdir parse */
 527        if (flag != SUBDIR_PARSE) {
 528                free_Htaccess_list(&mime_a);
 529#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
 530                free_Htaccess_list(&g_auth);
 531#endif
 532#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
 533                free_Htaccess_list(&script_i);
 534#endif
 535        }
 536
 537        filename = opt_c_configFile;
 538        if (flag == SUBDIR_PARSE || filename == NULL) {
 539                filename = alloca(strlen(path) + sizeof(HTTPD_CONF) + 2);
 540                sprintf((char *)filename, "%s/%s", path, HTTPD_CONF);
 541        }
 542
 543        while ((f = fopen_for_read(filename)) == NULL) {
 544                if (flag >= SUBDIR_PARSE) { /* SUBDIR or TRY_CURDIR */
 545                        /* config file not found, no changes to config */
 546                        return;
 547                }
 548                if (flag == FIRST_PARSE) {
 549                        /* -c CONFFILE given, but CONFFILE doesn't exist? */
 550                        if (opt_c_configFile)
 551                                bb_simple_perror_msg_and_die(opt_c_configFile);
 552                        /* else: no -c, thus we looked at /etc/httpd.conf,
 553                         * and it's not there. try ./httpd.conf: */
 554                }
 555                flag = TRY_CURDIR_PARSE;
 556                filename = HTTPD_CONF;
 557        }
 558
 559#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
 560        /* in "/file:user:pass" lines, we prepend path in subdirs */
 561        if (flag != SUBDIR_PARSE)
 562                path = "";
 563#endif
 564        /* The lines can be:
 565         *
 566         * I:default_index_file
 567         * H:http_home
 568         * [AD]:IP[/mask]   # allow/deny, * for wildcard
 569         * Ennn:error.html  # error page for status nnn
 570         * P:/url:[http://]hostname[:port]/new/path # reverse proxy
 571         * .ext:mime/type   # mime type
 572         * *.php:/path/php  # run xxx.php through an interpreter
 573         * /file:user:pass  # username and password
 574         */
 575        while (fgets(buf, sizeof(buf), f) != NULL) {
 576                unsigned strlen_buf;
 577                unsigned char ch;
 578                char *after_colon;
 579
 580                { /* remove all whitespace, and # comments */
 581                        char *p, *p0;
 582
 583                        p0 = buf;
 584                        /* skip non-whitespace beginning. Often the whole line
 585                         * is non-whitespace. We want this case to work fast,
 586                         * without needless copying, therefore we don't merge
 587                         * this operation into next while loop. */
 588                        while ((ch = *p0) != '\0' && ch != '\n' && ch != '#'
 589                         && ch != ' ' && ch != '\t'
 590                        ) {
 591                                p0++;
 592                        }
 593                        p = p0;
 594                        /* if we enter this loop, we have some whitespace.
 595                         * discard it */
 596                        while (ch != '\0' && ch != '\n' && ch != '#') {
 597                                if (ch != ' ' && ch != '\t') {
 598                                        *p++ = ch;
 599                                }
 600                                ch = *++p0;
 601                        }
 602                        *p = '\0';
 603                        strlen_buf = p - buf;
 604                        if (strlen_buf == 0)
 605                                continue; /* empty line */
 606                }
 607
 608                after_colon = strchr(buf, ':');
 609                /* strange line? */
 610                if (after_colon == NULL || *++after_colon == '\0')
 611                        goto config_error;
 612
 613                ch = (buf[0] & ~0x20); /* toupper if it's a letter */
 614
 615                if (ch == 'I') {
 616                        if (index_page != index_html)
 617                                free((char*)index_page);
 618                        index_page = xstrdup(after_colon);
 619                        continue;
 620                }
 621
 622                /* do not allow jumping around using H in subdir's configs */
 623                if (flag == FIRST_PARSE && ch == 'H') {
 624                        home_httpd = xstrdup(after_colon);
 625                        xchdir(home_httpd);
 626                        continue;
 627                }
 628
 629                if (ch == 'A' || ch == 'D') {
 630                        Htaccess_IP *pip;
 631
 632                        if (*after_colon == '*') {
 633                                if (ch == 'D') {
 634                                        /* memorize "deny all" */
 635                                        flg_deny_all = 1;
 636                                }
 637                                /* skip assumed "A:*", it is a default anyway */
 638                                continue;
 639                        }
 640                        /* store "allow/deny IP/mask" line */
 641                        pip = xzalloc(sizeof(*pip));
 642                        if (scan_ip_mask(after_colon, &pip->ip, &pip->mask)) {
 643                                /* IP{/mask} syntax error detected, protect all */
 644                                ch = 'D';
 645                                pip->mask = 0;
 646                        }
 647                        pip->allow_deny = ch;
 648                        if (ch == 'D') {
 649                                /* Deny:from_IP - prepend */
 650                                pip->next = ip_a_d;
 651                                ip_a_d = pip;
 652                        } else {
 653                                /* A:from_IP - append (thus all D's precedes A's) */
 654                                Htaccess_IP *prev_IP = ip_a_d;
 655                                if (prev_IP == NULL) {
 656                                        ip_a_d = pip;
 657                                } else {
 658                                        while (prev_IP->next)
 659                                                prev_IP = prev_IP->next;
 660                                        prev_IP->next = pip;
 661                                }
 662                        }
 663                        continue;
 664                }
 665
 666#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
 667                if (flag == FIRST_PARSE && ch == 'E') {
 668                        unsigned i;
 669                        int status = atoi(buf + 1); /* error status code */
 670
 671                        if (status < HTTP_CONTINUE) {
 672                                goto config_error;
 673                        }
 674                        /* then error page; find matching status */
 675                        for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
 676                                if (http_response_type[i] == status) {
 677                                        /* We chdir to home_httpd, thus no need to
 678                                         * concat_path_file(home_httpd, after_colon)
 679                                         * here */
 680                                        http_error_page[i] = xstrdup(after_colon);
 681                                        break;
 682                                }
 683                        }
 684                        continue;
 685                }
 686#endif
 687
 688#if ENABLE_FEATURE_HTTPD_PROXY
 689                if (flag == FIRST_PARSE && ch == 'P') {
 690                        /* P:/url:[http://]hostname[:port]/new/path */
 691                        char *url_from, *host_port, *url_to;
 692                        Htaccess_Proxy *proxy_entry;
 693
 694                        url_from = after_colon;
 695                        host_port = strchr(after_colon, ':');
 696                        if (host_port == NULL) {
 697                                goto config_error;
 698                        }
 699                        *host_port++ = '\0';
 700                        if (strncmp(host_port, "http://", 7) == 0)
 701                                host_port += 7;
 702                        if (*host_port == '\0') {
 703                                goto config_error;
 704                        }
 705                        url_to = strchr(host_port, '/');
 706                        if (url_to == NULL) {
 707                                goto config_error;
 708                        }
 709                        *url_to = '\0';
 710                        proxy_entry = xzalloc(sizeof(*proxy_entry));
 711                        proxy_entry->url_from = xstrdup(url_from);
 712                        proxy_entry->host_port = xstrdup(host_port);
 713                        *url_to = '/';
 714                        proxy_entry->url_to = xstrdup(url_to);
 715                        proxy_entry->next = proxy;
 716                        proxy = proxy_entry;
 717                        continue;
 718                }
 719#endif
 720                /* the rest of directives are non-alphabetic,
 721                 * must avoid using "toupper'ed" ch */
 722                ch = buf[0];
 723
 724                if (ch == '.' /* ".ext:mime/type" */
 725#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
 726                 || (ch == '*' && buf[1] == '.') /* "*.php:/path/php" */
 727#endif
 728                ) {
 729                        char *p;
 730                        Htaccess *cur;
 731
 732                        cur = xzalloc(sizeof(*cur) /* includes space for NUL */ + strlen_buf);
 733                        strcpy(cur->before_colon, buf);
 734                        p = cur->before_colon + (after_colon - buf);
 735                        p[-1] = '\0';
 736                        cur->after_colon = p;
 737                        if (ch == '.') {
 738                                /* .mime line: prepend to mime_a list */
 739                                cur->next = mime_a;
 740                                mime_a = cur;
 741                        }
 742#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
 743                        else {
 744                                /* script interpreter line: prepend to script_i list */
 745                                cur->next = script_i;
 746                                script_i = cur;
 747                        }
 748#endif
 749                        continue;
 750                }
 751
 752#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
 753                if (ch == '/') { /* "/file:user:pass" */
 754                        char *p;
 755                        Htaccess *cur;
 756                        unsigned file_len;
 757
 758                        /* note: path is "" unless we are in SUBDIR parse,
 759                         * otherwise it does NOT start with "/" */
 760                        cur = xzalloc(sizeof(*cur) /* includes space for NUL */
 761                                + 1 + strlen(path)
 762                                + strlen_buf
 763                                );
 764                        /* form "/path/file" */
 765                        sprintf(cur->before_colon, "/%s%.*s",
 766                                path,
 767                                (int) (after_colon - buf - 1), /* includes "/", but not ":" */
 768                                buf);
 769                        /* canonicalize it */
 770                        p = bb_simplify_abs_path_inplace(cur->before_colon);
 771                        file_len = p - cur->before_colon;
 772                        /* add "user:pass" after NUL */
 773                        strcpy(++p, after_colon);
 774                        cur->after_colon = p;
 775
 776                        /* insert cur into g_auth */
 777                        /* g_auth is sorted by decreased filename length */
 778                        {
 779                                Htaccess *auth, **authp;
 780
 781                                authp = &g_auth;
 782                                while ((auth = *authp) != NULL) {
 783                                        if (file_len >= strlen(auth->before_colon)) {
 784                                                /* insert cur before auth */
 785                                                cur->next = auth;
 786                                                break;
 787                                        }
 788                                        authp = &auth->next;
 789                                }
 790                                *authp = cur;
 791                        }
 792                        continue;
 793                }
 794#endif /* BASIC_AUTH */
 795
 796                /* the line is not recognized */
 797 config_error:
 798                bb_error_msg("config error '%s' in '%s'", buf, filename);
 799        } /* while (fgets) */
 800
 801        fclose(f);
 802}
 803
 804#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR
 805/*
 806 * Given a string, html-encode special characters.
 807 * This is used for the -e command line option to provide an easy way
 808 * for scripts to encode result data without confusing browsers.  The
 809 * returned string pointer is memory allocated by malloc().
 810 *
 811 * Returns a pointer to the encoded string (malloced).
 812 */
 813static char *encodeString(const char *string)
 814{
 815        /* take the simple route and encode everything */
 816        /* could possibly scan once to get length.     */
 817        int len = strlen(string);
 818        char *out = xmalloc(len * 6 + 1);
 819        char *p = out;
 820        char ch;
 821
 822        while ((ch = *string++) != '\0') {
 823                /* very simple check for what to encode */
 824                if (isalnum(ch))
 825                        *p++ = ch;
 826                else
 827                        p += sprintf(p, "&#%d;", (unsigned char) ch);
 828        }
 829        *p = '\0';
 830        return out;
 831}
 832#endif
 833
 834#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
 835/*
 836 * Decode a base64 data stream as per rfc1521.
 837 * Note that the rfc states that non base64 chars are to be ignored.
 838 * Since the decode always results in a shorter size than the input,
 839 * it is OK to pass the input arg as an output arg.
 840 * Parameter: a pointer to a base64 encoded string.
 841 * Decoded data is stored in-place.
 842 */
 843static void decodeBase64(char *Data)
 844{
 845        const unsigned char *in = (const unsigned char *)Data;
 846        /* The decoded size will be at most 3/4 the size of the encoded */
 847        unsigned ch = 0;
 848        int i = 0;
 849
 850        while (*in) {
 851                int t = *in++;
 852
 853                if (t >= '0' && t <= '9')
 854                        t = t - '0' + 52;
 855                else if (t >= 'A' && t <= 'Z')
 856                        t = t - 'A';
 857                else if (t >= 'a' && t <= 'z')
 858                        t = t - 'a' + 26;
 859                else if (t == '+')
 860                        t = 62;
 861                else if (t == '/')
 862                        t = 63;
 863                else if (t == '=')
 864                        t = 0;
 865                else
 866                        continue;
 867
 868                ch = (ch << 6) | t;
 869                i++;
 870                if (i == 4) {
 871                        *Data++ = (char) (ch >> 16);
 872                        *Data++ = (char) (ch >> 8);
 873                        *Data++ = (char) ch;
 874                        i = 0;
 875                }
 876        }
 877        *Data = '\0';
 878}
 879#endif
 880
 881/*
 882 * Create a listen server socket on the designated port.
 883 */
 884static int openServer(void)
 885{
 886        unsigned n = bb_strtou(bind_addr_or_port, NULL, 10);
 887        if (!errno && n && n <= 0xffff)
 888                n = create_and_bind_stream_or_die(NULL, n);
 889        else
 890                n = create_and_bind_stream_or_die(bind_addr_or_port, 80);
 891        xlisten(n, 9);
 892        return n;
 893}
 894
 895/*
 896 * Log the connection closure and exit.
 897 */
 898static void log_and_exit(void) NORETURN;
 899static void log_and_exit(void)
 900{
 901        /* Paranoia. IE said to be buggy. It may send some extra data
 902         * or be confused by us just exiting without SHUT_WR. Oh well. */
 903        shutdown(1, SHUT_WR);
 904        /* Why??
 905        (this also messes up stdin when user runs httpd -i from terminal)
 906        ndelay_on(0);
 907        while (read(STDIN_FILENO, iobuf, IOBUF_SIZE) > 0)
 908                continue;
 909        */
 910
 911        if (verbose > 2)
 912                bb_error_msg("closed");
 913        _exit(xfunc_error_retval);
 914}
 915
 916/*
 917 * Create and send HTTP response headers.
 918 * The arguments are combined and sent as one write operation.  Note that
 919 * IE will puke big-time if the headers are not sent in one packet and the
 920 * second packet is delayed for any reason.
 921 * responseNum - the result code to send.
 922 */
 923static void send_headers(int responseNum)
 924{
 925        static const char RFC1123FMT[] ALIGN1 = "%a, %d %b %Y %H:%M:%S GMT";
 926
 927        const char *responseString = "";
 928        const char *infoString = NULL;
 929        const char *mime_type;
 930#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
 931        const char *error_page = NULL;
 932#endif
 933        unsigned i;
 934        time_t timer = time(NULL);
 935        char tmp_str[80];
 936        int len;
 937
 938        for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
 939                if (http_response_type[i] == responseNum) {
 940                        responseString = http_response[i].name;
 941                        infoString = http_response[i].info;
 942#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
 943                        error_page = http_error_page[i];
 944#endif
 945                        break;
 946                }
 947        }
 948        /* error message is HTML */
 949        mime_type = responseNum == HTTP_OK ?
 950                                found_mime_type : "text/html";
 951
 952        if (verbose)
 953                bb_error_msg("response:%u", responseNum);
 954
 955        /* emit the current date */
 956        strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&timer));
 957        len = sprintf(iobuf,
 958                        "HTTP/1.0 %d %s\r\nContent-type: %s\r\n"
 959                        "Date: %s\r\nConnection: close\r\n",
 960                        responseNum, responseString, mime_type, tmp_str);
 961
 962#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
 963        if (responseNum == HTTP_UNAUTHORIZED) {
 964                len += sprintf(iobuf + len,
 965                                "WWW-Authenticate: Basic realm=\"%s\"\r\n",
 966                                g_realm);
 967        }
 968#endif
 969        if (responseNum == HTTP_MOVED_TEMPORARILY) {
 970                len += sprintf(iobuf + len, "Location: %s/%s%s\r\n",
 971                                found_moved_temporarily,
 972                                (g_query ? "?" : ""),
 973                                (g_query ? g_query : ""));
 974        }
 975
 976#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
 977        if (error_page && access(error_page, R_OK) == 0) {
 978                strcat(iobuf, "\r\n");
 979                len += 2;
 980
 981                if (DEBUG)
 982                        fprintf(stderr, "headers: '%s'\n", iobuf);
 983                full_write(STDOUT_FILENO, iobuf, len);
 984                if (DEBUG)
 985                        fprintf(stderr, "writing error page: '%s'\n", error_page);
 986                return send_file_and_exit(error_page, SEND_BODY);
 987        }
 988#endif
 989
 990        if (file_size != -1) {    /* file */
 991                strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&last_mod));
 992#if ENABLE_FEATURE_HTTPD_RANGES
 993                if (responseNum == HTTP_PARTIAL_CONTENT) {
 994                        len += sprintf(iobuf + len, "Content-Range: bytes %"OFF_FMT"u-%"OFF_FMT"u/%"OFF_FMT"u\r\n",
 995                                        range_start,
 996                                        range_end,
 997                                        file_size);
 998                        file_size = range_end - range_start + 1;
 999                }
1000#endif
1001                len += sprintf(iobuf + len,
1002#if ENABLE_FEATURE_HTTPD_RANGES
1003                        "Accept-Ranges: bytes\r\n"
1004#endif
1005                        "Last-Modified: %s\r\n%s %"OFF_FMT"u\r\n",
1006                                tmp_str,
1007                                content_gzip ? "Transfer-length:" : "Content-length:",
1008                                file_size
1009                );
1010        }
1011
1012        if (content_gzip)
1013                len += sprintf(iobuf + len, "Content-Encoding: gzip\r\n");
1014
1015        iobuf[len++] = '\r';
1016        iobuf[len++] = '\n';
1017        if (infoString) {
1018                len += sprintf(iobuf + len,
1019                                "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>\n"
1020                                "<BODY><H1>%d %s</H1>\n%s\n</BODY></HTML>\n",
1021                                responseNum, responseString,
1022                                responseNum, responseString, infoString);
1023        }
1024        if (DEBUG)
1025                fprintf(stderr, "headers: '%s'\n", iobuf);
1026        if (full_write(STDOUT_FILENO, iobuf, len) != len) {
1027                if (verbose > 1)
1028                        bb_perror_msg("error");
1029                log_and_exit();
1030        }
1031}
1032
1033static void send_headers_and_exit(int responseNum) NORETURN;
1034static void send_headers_and_exit(int responseNum)
1035{
1036        IF_FEATURE_HTTPD_GZIP(content_gzip = 0;)
1037        send_headers(responseNum);
1038        log_and_exit();
1039}
1040
1041/*
1042 * Read from the socket until '\n' or EOF. '\r' chars are removed.
1043 * '\n' is replaced with NUL.
1044 * Return number of characters read or 0 if nothing is read
1045 * ('\r' and '\n' are not counted).
1046 * Data is returned in iobuf.
1047 */
1048static int get_line(void)
1049{
1050        int count = 0;
1051        char c;
1052
1053        alarm(HEADER_READ_TIMEOUT);
1054        while (1) {
1055                if (hdr_cnt <= 0) {
1056                        hdr_cnt = safe_read(STDIN_FILENO, hdr_buf, sizeof(hdr_buf));
1057                        if (hdr_cnt <= 0)
1058                                break;
1059                        hdr_ptr = hdr_buf;
1060                }
1061                iobuf[count] = c = *hdr_ptr++;
1062                hdr_cnt--;
1063
1064                if (c == '\r')
1065                        continue;
1066                if (c == '\n') {
1067                        iobuf[count] = '\0';
1068                        break;
1069                }
1070                if (count < (IOBUF_SIZE - 1))      /* check overflow */
1071                        count++;
1072        }
1073        return count;
1074}
1075
1076#if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY
1077
1078/* gcc 4.2.1 fares better with NOINLINE */
1079static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) NORETURN;
1080static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len)
1081{
1082        enum { FROM_CGI = 1, TO_CGI = 2 }; /* indexes in pfd[] */
1083        struct pollfd pfd[3];
1084        int out_cnt; /* we buffer a bit of initial CGI output */
1085        int count;
1086
1087        /* iobuf is used for CGI -> network data,
1088         * hdr_buf is for network -> CGI data (POSTDATA) */
1089
1090        /* If CGI dies, we still want to correctly finish reading its output
1091         * and send it to the peer. So please no SIGPIPEs! */
1092        signal(SIGPIPE, SIG_IGN);
1093
1094        // We inconsistently handle a case when more POSTDATA from network
1095        // is coming than we expected. We may give *some part* of that
1096        // extra data to CGI.
1097
1098        //if (hdr_cnt > post_len) {
1099        //      /* We got more POSTDATA from network than we expected */
1100        //      hdr_cnt = post_len;
1101        //}
1102        post_len -= hdr_cnt;
1103        /* post_len - number of POST bytes not yet read from network */
1104
1105        /* NB: breaking out of this loop jumps to log_and_exit() */
1106        out_cnt = 0;
1107        pfd[FROM_CGI].fd = fromCgi_rd;
1108        pfd[FROM_CGI].events = POLLIN;
1109        pfd[TO_CGI].fd = toCgi_wr;
1110        while (1) {
1111                /* Note: even pfd[0].events == 0 won't prevent
1112                 * revents == POLLHUP|POLLERR reports from closed stdin.
1113                 * Setting fd to -1 works: */
1114                pfd[0].fd = -1;
1115                pfd[0].events = POLLIN;
1116                pfd[0].revents = 0; /* probably not needed, paranoia */
1117
1118                /* We always poll this fd, thus kernel always sets revents: */
1119                /*pfd[FROM_CGI].events = POLLIN; - moved out of loop */
1120                /*pfd[FROM_CGI].revents = 0; - not needed */
1121
1122                /* gcc-4.8.0 still doesnt fill two shorts with one insn :( */
1123                /* http://gcc.gnu.org/bugzilla/show_bug.cgi?id=47059 */
1124                /* hopefully one day it will... */
1125                pfd[TO_CGI].events = POLLOUT;
1126                pfd[TO_CGI].revents = 0; /* needed! */
1127
1128                if (toCgi_wr && hdr_cnt <= 0) {
1129                        if (post_len > 0) {
1130                                /* Expect more POST data from network */
1131                                pfd[0].fd = 0;
1132                        } else {
1133                                /* post_len <= 0 && hdr_cnt <= 0:
1134                                 * no more POST data to CGI,
1135                                 * let CGI see EOF on CGI's stdin */
1136                                if (toCgi_wr != fromCgi_rd)
1137                                        close(toCgi_wr);
1138                                toCgi_wr = 0;
1139                        }
1140                }
1141
1142                /* Now wait on the set of sockets */
1143                count = safe_poll(pfd, hdr_cnt > 0 ? TO_CGI+1 : FROM_CGI+1, -1);
1144                if (count <= 0) {
1145#if 0
1146                        if (safe_waitpid(pid, &status, WNOHANG) <= 0) {
1147                                /* Weird. CGI didn't exit and no fd's
1148                                 * are ready, yet poll returned?! */
1149                                continue;
1150                        }
1151                        if (DEBUG && WIFEXITED(status))
1152                                bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status));
1153                        if (DEBUG && WIFSIGNALED(status))
1154                                bb_error_msg("CGI killed, signal=%d", WTERMSIG(status));
1155#endif
1156                        break;
1157                }
1158
1159                if (pfd[TO_CGI].revents) {
1160                        /* hdr_cnt > 0 here due to the way poll() called */
1161                        /* Have data from peer and can write to CGI */
1162                        count = safe_write(toCgi_wr, hdr_ptr, hdr_cnt);
1163                        /* Doesn't happen, we dont use nonblocking IO here
1164                         *if (count < 0 && errno == EAGAIN) {
1165                         *      ...
1166                         *} else */
1167                        if (count > 0) {
1168                                hdr_ptr += count;
1169                                hdr_cnt -= count;
1170                        } else {
1171                                /* EOF/broken pipe to CGI, stop piping POST data */
1172                                hdr_cnt = post_len = 0;
1173                        }
1174                }
1175
1176                if (pfd[0].revents) {
1177                        /* post_len > 0 && hdr_cnt == 0 here */
1178                        /* We expect data, prev data portion is eaten by CGI
1179                         * and there *is* data to read from the peer
1180                         * (POSTDATA) */
1181                        //count = post_len > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : post_len;
1182                        //count = safe_read(STDIN_FILENO, hdr_buf, count);
1183                        count = safe_read(STDIN_FILENO, hdr_buf, sizeof(hdr_buf));
1184                        if (count > 0) {
1185                                hdr_cnt = count;
1186                                hdr_ptr = hdr_buf;
1187                                post_len -= count;
1188                        } else {
1189                                /* no more POST data can be read */
1190                                post_len = 0;
1191                        }
1192                }
1193
1194                if (pfd[FROM_CGI].revents) {
1195                        /* There is something to read from CGI */
1196                        char *rbuf = iobuf;
1197
1198                        /* Are we still buffering CGI output? */
1199                        if (out_cnt >= 0) {
1200                                /* HTTP_200[] has single "\r\n" at the end.
1201                                 * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html,
1202                                 * CGI scripts MUST send their own header terminated by
1203                                 * empty line, then data. That's why we have only one
1204                                 * <cr><lf> pair here. We will output "200 OK" line
1205                                 * if needed, but CGI still has to provide blank line
1206                                 * between header and body */
1207
1208                                /* Must use safe_read, not full_read, because
1209                                 * CGI may output a few first bytes and then wait
1210                                 * for POSTDATA without closing stdout.
1211                                 * With full_read we may wait here forever. */
1212                                count = safe_read(fromCgi_rd, rbuf + out_cnt, PIPE_BUF - 8);
1213                                if (count <= 0) {
1214                                        /* eof (or error) and there was no "HTTP",
1215                                         * so write it, then write received data */
1216                                        if (out_cnt) {
1217                                                full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1);
1218                                                full_write(STDOUT_FILENO, rbuf, out_cnt);
1219                                        }
1220                                        break; /* CGI stdout is closed, exiting */
1221                                }
1222                                out_cnt += count;
1223                                count = 0;
1224                                /* "Status" header format is: "Status: 302 Redirected\r\n" */
1225                                if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) {
1226                                        /* send "HTTP/1.0 " */
1227                                        if (full_write(STDOUT_FILENO, HTTP_200, 9) != 9)
1228                                                break;
1229                                        rbuf += 8; /* skip "Status: " */
1230                                        count = out_cnt - 8;
1231                                        out_cnt = -1; /* buffering off */
1232                                } else if (out_cnt >= 4) {
1233                                        /* Did CGI add "HTTP"? */
1234                                        if (memcmp(rbuf, HTTP_200, 4) != 0) {
1235                                                /* there is no "HTTP", do it ourself */
1236                                                if (full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1)
1237                                                        break;
1238                                        }
1239                                        /* Commented out:
1240                                        if (!strstr(rbuf, "ontent-")) {
1241                                                full_write(s, "Content-type: text/plain\r\n\r\n", 28);
1242                                        }
1243                                         * Counter-example of valid CGI without Content-type:
1244                                         * echo -en "HTTP/1.0 302 Found\r\n"
1245                                         * echo -en "Location: http://www.busybox.net\r\n"
1246                                         * echo -en "\r\n"
1247                                         */
1248                                        count = out_cnt;
1249                                        out_cnt = -1; /* buffering off */
1250                                }
1251                        } else {
1252                                count = safe_read(fromCgi_rd, rbuf, PIPE_BUF);
1253                                if (count <= 0)
1254                                        break;  /* eof (or error) */
1255                        }
1256                        if (full_write(STDOUT_FILENO, rbuf, count) != count)
1257                                break;
1258                        if (DEBUG)
1259                                fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf);
1260                } /* if (pfd[FROM_CGI].revents) */
1261        } /* while (1) */
1262        log_and_exit();
1263}
1264#endif
1265
1266#if ENABLE_FEATURE_HTTPD_CGI
1267
1268static void setenv1(const char *name, const char *value)
1269{
1270        setenv(name, value ? value : "", 1);
1271}
1272
1273/*
1274 * Spawn CGI script, forward CGI's stdin/out <=> network
1275 *
1276 * Environment variables are set up and the script is invoked with pipes
1277 * for stdin/stdout.  If a POST is being done the script is fed the POST
1278 * data in addition to setting the QUERY_STRING variable (for GETs or POSTs).
1279 *
1280 * Parameters:
1281 * const char *url              The requested URL (with leading /).
1282 * const char *orig_uri         The original URI before rewriting (if any)
1283 * int post_len                 Length of the POST body.
1284 * const char *cookie           For set HTTP_COOKIE.
1285 * const char *content_type     For set CONTENT_TYPE.
1286 */
1287static void send_cgi_and_exit(
1288                const char *url,
1289                const char *orig_uri,
1290                const char *request,
1291                int post_len,
1292                const char *cookie,
1293                const char *content_type) NORETURN;
1294static void send_cgi_and_exit(
1295                const char *url,
1296                const char *orig_uri,
1297                const char *request,
1298                int post_len,
1299                const char *cookie,
1300                const char *content_type)
1301{
1302        struct fd_pair fromCgi;  /* CGI -> httpd pipe */
1303        struct fd_pair toCgi;    /* httpd -> CGI pipe */
1304        char *script, *last_slash;
1305        int pid;
1306
1307        /* Make a copy. NB: caller guarantees:
1308         * url[0] == '/', url[1] != '/' */
1309        url = xstrdup(url);
1310
1311        /*
1312         * We are mucking with environment _first_ and then vfork/exec,
1313         * this allows us to use vfork safely. Parent doesn't care about
1314         * these environment changes anyway.
1315         */
1316
1317        /* Check for [dirs/]script.cgi/PATH_INFO */
1318        last_slash = script = (char*)url;
1319        while ((script = strchr(script + 1, '/')) != NULL) {
1320                int dir;
1321                *script = '\0';
1322                dir = is_directory(url + 1, /*followlinks:*/ 1);
1323                *script = '/';
1324                if (!dir) {
1325                        /* not directory, found script.cgi/PATH_INFO */
1326                        break;
1327                }
1328                /* is directory, find next '/' */
1329                last_slash = script;
1330        }
1331        setenv1("PATH_INFO", script);   /* set to /PATH_INFO or "" */
1332        setenv1("REQUEST_METHOD", request);
1333        if (g_query) {
1334                putenv(xasprintf("%s=%s?%s", "REQUEST_URI", orig_uri, g_query));
1335        } else {
1336                setenv1("REQUEST_URI", orig_uri);
1337        }
1338        if (script != NULL)
1339                *script = '\0';         /* cut off /PATH_INFO */
1340
1341        /* SCRIPT_FILENAME is required by PHP in CGI mode */
1342        if (home_httpd[0] == '/') {
1343                char *fullpath = concat_path_file(home_httpd, url);
1344                setenv1("SCRIPT_FILENAME", fullpath);
1345        }
1346        /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */
1347        setenv1("SCRIPT_NAME", url);
1348        /* http://hoohoo.ncsa.uiuc.edu/cgi/env.html:
1349         * QUERY_STRING: The information which follows the ? in the URL
1350         * which referenced this script. This is the query information.
1351         * It should not be decoded in any fashion. This variable
1352         * should always be set when there is query information,
1353         * regardless of command line decoding. */
1354        /* (Older versions of bbox seem to do some decoding) */
1355        setenv1("QUERY_STRING", g_query);
1356        putenv((char*)"SERVER_SOFTWARE=busybox httpd/"BB_VER);
1357        putenv((char*)"SERVER_PROTOCOL=HTTP/1.0");
1358        putenv((char*)"GATEWAY_INTERFACE=CGI/1.1");
1359        /* Having _separate_ variables for IP and port defeats
1360         * the purpose of having socket abstraction. Which "port"
1361         * are you using on Unix domain socket?
1362         * IOW - REMOTE_PEER="1.2.3.4:56" makes much more sense.
1363         * Oh well... */
1364        {
1365                char *p = rmt_ip_str ? rmt_ip_str : (char*)"";
1366                char *cp = strrchr(p, ':');
1367                if (ENABLE_FEATURE_IPV6 && cp && strchr(cp, ']'))
1368                        cp = NULL;
1369                if (cp) *cp = '\0'; /* delete :PORT */
1370                setenv1("REMOTE_ADDR", p);
1371                if (cp) {
1372                        *cp = ':';
1373#if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
1374                        setenv1("REMOTE_PORT", cp + 1);
1375#endif
1376                }
1377        }
1378        setenv1("HTTP_USER_AGENT", user_agent);
1379        if (http_accept)
1380                setenv1("HTTP_ACCEPT", http_accept);
1381        if (http_accept_language)
1382                setenv1("HTTP_ACCEPT_LANGUAGE", http_accept_language);
1383        if (post_len)
1384                putenv(xasprintf("CONTENT_LENGTH=%d", post_len));
1385        if (cookie)
1386                setenv1("HTTP_COOKIE", cookie);
1387        if (content_type)
1388                setenv1("CONTENT_TYPE", content_type);
1389#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1390        if (remoteuser) {
1391                setenv1("REMOTE_USER", remoteuser);
1392                putenv((char*)"AUTH_TYPE=Basic");
1393        }
1394#endif
1395        if (referer)
1396                setenv1("HTTP_REFERER", referer);
1397        setenv1("HTTP_HOST", host); /* set to "" if NULL */
1398        /* setenv1("SERVER_NAME", safe_gethostname()); - don't do this,
1399         * just run "env SERVER_NAME=xyz httpd ..." instead */
1400
1401        xpiped_pair(fromCgi);
1402        xpiped_pair(toCgi);
1403
1404        pid = vfork();
1405        if (pid < 0) {
1406                /* TODO: log perror? */
1407                log_and_exit();
1408        }
1409
1410        if (pid == 0) {
1411                /* Child process */
1412                char *argv[3];
1413
1414                xfunc_error_retval = 242;
1415
1416                /* NB: close _first_, then move fds! */
1417                close(toCgi.wr);
1418                close(fromCgi.rd);
1419                xmove_fd(toCgi.rd, 0);  /* replace stdin with the pipe */
1420                xmove_fd(fromCgi.wr, 1);  /* replace stdout with the pipe */
1421                /* User seeing stderr output can be a security problem.
1422                 * If CGI really wants that, it can always do dup itself. */
1423                /* dup2(1, 2); */
1424
1425                /* Chdiring to script's dir */
1426                script = last_slash;
1427                if (script != url) { /* paranoia */
1428                        *script = '\0';
1429                        if (chdir(url + 1) != 0) {
1430                                bb_perror_msg("can't change directory to '%s'", url + 1);
1431                                goto error_execing_cgi;
1432                        }
1433                        // not needed: *script = '/';
1434                }
1435                script++;
1436
1437                /* set argv[0] to name without path */
1438                argv[0] = script;
1439                argv[1] = NULL;
1440
1441#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
1442                {
1443                        char *suffix = strrchr(script, '.');
1444
1445                        if (suffix) {
1446                                Htaccess *cur;
1447                                for (cur = script_i; cur; cur = cur->next) {
1448                                        if (strcmp(cur->before_colon + 1, suffix) == 0) {
1449                                                /* found interpreter name */
1450                                                argv[0] = cur->after_colon;
1451                                                argv[1] = script;
1452                                                argv[2] = NULL;
1453                                                break;
1454                                        }
1455                                }
1456                        }
1457                }
1458#endif
1459                /* restore default signal dispositions for CGI process */
1460                bb_signals(0
1461                        | (1 << SIGCHLD)
1462                        | (1 << SIGPIPE)
1463                        | (1 << SIGHUP)
1464                        , SIG_DFL);
1465
1466                /* _NOT_ execvp. We do not search PATH. argv[0] is a filename
1467                 * without any dir components and will only match a file
1468                 * in the current directory */
1469                execv(argv[0], argv);
1470                if (verbose)
1471                        bb_perror_msg("can't execute '%s'", argv[0]);
1472 error_execing_cgi:
1473                /* send to stdout
1474                 * (we are CGI here, our stdout is pumped to the net) */
1475                send_headers_and_exit(HTTP_NOT_FOUND);
1476        } /* end child */
1477
1478        /* Parent process */
1479
1480        /* Restore variables possibly changed by child */
1481        xfunc_error_retval = 0;
1482
1483        /* Pump data */
1484        close(fromCgi.wr);
1485        close(toCgi.rd);
1486        cgi_io_loop_and_exit(fromCgi.rd, toCgi.wr, post_len);
1487}
1488
1489#endif          /* FEATURE_HTTPD_CGI */
1490
1491/*
1492 * Send a file response to a HTTP request, and exit
1493 *
1494 * Parameters:
1495 * const char *url  The requested URL (with leading /).
1496 * what             What to send (headers/body/both).
1497 */
1498static NOINLINE void send_file_and_exit(const char *url, int what)
1499{
1500        char *suffix;
1501        int fd;
1502        ssize_t count;
1503
1504        if (content_gzip) {
1505                /* does <url>.gz exist? Then use it instead */
1506                char *gzurl = xasprintf("%s.gz", url);
1507                fd = open(gzurl, O_RDONLY);
1508                free(gzurl);
1509                if (fd != -1) {
1510                        struct stat sb;
1511                        fstat(fd, &sb);
1512                        file_size = sb.st_size;
1513                        last_mod = sb.st_mtime;
1514                } else {
1515                        IF_FEATURE_HTTPD_GZIP(content_gzip = 0;)
1516                        fd = open(url, O_RDONLY);
1517                }
1518        } else {
1519                fd = open(url, O_RDONLY);
1520        }
1521        if (fd < 0) {
1522                if (DEBUG)
1523                        bb_perror_msg("can't open '%s'", url);
1524                /* Error pages are sent by using send_file_and_exit(SEND_BODY).
1525                 * IOW: it is unsafe to call send_headers_and_exit
1526                 * if what is SEND_BODY! Can recurse! */
1527                if (what != SEND_BODY)
1528                        send_headers_and_exit(HTTP_NOT_FOUND);
1529                log_and_exit();
1530        }
1531        /* If you want to know about EPIPE below
1532         * (happens if you abort downloads from local httpd): */
1533        signal(SIGPIPE, SIG_IGN);
1534
1535        /* If not found, default is "application/octet-stream" */
1536        found_mime_type = "application/octet-stream";
1537        suffix = strrchr(url, '.');
1538        if (suffix) {
1539                static const char suffixTable[] ALIGN1 =
1540                        /* Shorter suffix must be first:
1541                         * ".html.htm" will fail for ".htm"
1542                         */
1543                        ".txt.h.c.cc.cpp\0" "text/plain\0"
1544                        /* .htm line must be after .h line */
1545                        ".htm.html\0" "text/html\0"
1546                        ".jpg.jpeg\0" "image/jpeg\0"
1547                        ".gif\0"      "image/gif\0"
1548                        ".png\0"      "image/png\0"
1549                        /* .css line must be after .c line */
1550                        ".css\0"      "text/css\0"
1551                        ".wav\0"      "audio/wav\0"
1552                        ".avi\0"      "video/x-msvideo\0"
1553                        ".qt.mov\0"   "video/quicktime\0"
1554                        ".mpe.mpeg\0" "video/mpeg\0"
1555                        ".mid.midi\0" "audio/midi\0"
1556                        ".mp3\0"      "audio/mpeg\0"
1557#if 0  /* unpopular */
1558                        ".au\0"       "audio/basic\0"
1559                        ".pac\0"      "application/x-ns-proxy-autoconfig\0"
1560                        ".vrml.wrl\0" "model/vrml\0"
1561#endif
1562                        /* compiler adds another "\0" here */
1563                ;
1564                Htaccess *cur;
1565
1566                /* Examine built-in table */
1567                const char *table = suffixTable;
1568                const char *table_next;
1569                for (; *table; table = table_next) {
1570                        const char *try_suffix;
1571                        const char *mime_type;
1572                        mime_type  = table + strlen(table) + 1;
1573                        table_next = mime_type + strlen(mime_type) + 1;
1574                        try_suffix = strstr(table, suffix);
1575                        if (!try_suffix)
1576                                continue;
1577                        try_suffix += strlen(suffix);
1578                        if (*try_suffix == '\0' || *try_suffix == '.') {
1579                                found_mime_type = mime_type;
1580                                break;
1581                        }
1582                        /* Example: strstr(table, ".av") != NULL, but it
1583                         * does not match ".avi" after all and we end up here.
1584                         * The table is arranged so that in this case we know
1585                         * that it can't match anything in the following lines,
1586                         * and we stop the search: */
1587                        break;
1588                }
1589                /* ...then user's table */
1590                for (cur = mime_a; cur; cur = cur->next) {
1591                        if (strcmp(cur->before_colon, suffix) == 0) {
1592                                found_mime_type = cur->after_colon;
1593                                break;
1594                        }
1595                }
1596        }
1597
1598        if (DEBUG)
1599                bb_error_msg("sending file '%s' content-type: %s",
1600                        url, found_mime_type);
1601
1602#if ENABLE_FEATURE_HTTPD_RANGES
1603        if (what == SEND_BODY /* err pages and ranges don't mix */
1604         || content_gzip /* we are sending compressed page: can't do ranges */  ///why?
1605        ) {
1606                range_start = -1;
1607        }
1608        range_len = MAXINT(off_t);
1609        if (range_start >= 0) {
1610                if (!range_end || range_end > file_size - 1) {
1611                        range_end = file_size - 1;
1612                }
1613                if (range_end < range_start
1614                 || lseek(fd, range_start, SEEK_SET) != range_start
1615                ) {
1616                        lseek(fd, 0, SEEK_SET);
1617                        range_start = -1;
1618                } else {
1619                        range_len = range_end - range_start + 1;
1620                        send_headers(HTTP_PARTIAL_CONTENT);
1621                        what = SEND_BODY;
1622                }
1623        }
1624#endif
1625        if (what & SEND_HEADERS)
1626                send_headers(HTTP_OK);
1627#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
1628        {
1629                off_t offset = range_start;
1630                while (1) {
1631                        /* sz is rounded down to 64k */
1632                        ssize_t sz = MAXINT(ssize_t) - 0xffff;
1633                        IF_FEATURE_HTTPD_RANGES(if (sz > range_len) sz = range_len;)
1634                        count = sendfile(STDOUT_FILENO, fd, &offset, sz);
1635                        if (count < 0) {
1636                                if (offset == range_start)
1637                                        break; /* fall back to read/write loop */
1638                                goto fin;
1639                        }
1640                        IF_FEATURE_HTTPD_RANGES(range_len -= count;)
1641                        if (count == 0 || range_len == 0)
1642                                log_and_exit();
1643                }
1644        }
1645#endif
1646        while ((count = safe_read(fd, iobuf, IOBUF_SIZE)) > 0) {
1647                ssize_t n;
1648                IF_FEATURE_HTTPD_RANGES(if (count > range_len) count = range_len;)
1649                n = full_write(STDOUT_FILENO, iobuf, count);
1650                if (count != n)
1651                        break;
1652                IF_FEATURE_HTTPD_RANGES(range_len -= count;)
1653                if (range_len == 0)
1654                        break;
1655        }
1656        if (count < 0) {
1657 IF_FEATURE_HTTPD_USE_SENDFILE(fin:)
1658                if (verbose > 1)
1659                        bb_perror_msg("error");
1660        }
1661        log_and_exit();
1662}
1663
1664static int checkPermIP(void)
1665{
1666        Htaccess_IP *cur;
1667
1668        for (cur = ip_a_d; cur; cur = cur->next) {
1669#if DEBUG
1670                fprintf(stderr,
1671                        "checkPermIP: '%s' ? '%u.%u.%u.%u/%u.%u.%u.%u'\n",
1672                        rmt_ip_str,
1673                        (unsigned char)(cur->ip >> 24),
1674                        (unsigned char)(cur->ip >> 16),
1675                        (unsigned char)(cur->ip >> 8),
1676                        (unsigned char)(cur->ip),
1677                        (unsigned char)(cur->mask >> 24),
1678                        (unsigned char)(cur->mask >> 16),
1679                        (unsigned char)(cur->mask >> 8),
1680                        (unsigned char)(cur->mask)
1681                );
1682#endif
1683                if ((rmt_ip & cur->mask) == cur->ip)
1684                        return (cur->allow_deny == 'A'); /* A -> 1 */
1685        }
1686
1687        return !flg_deny_all; /* depends on whether we saw "D:*" */
1688}
1689
1690#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1691
1692# if ENABLE_PAM
1693struct pam_userinfo {
1694        const char *name;
1695        const char *pw;
1696};
1697
1698static int pam_talker(int num_msg,
1699                const struct pam_message **msg,
1700                struct pam_response **resp,
1701                void *appdata_ptr)
1702{
1703        int i;
1704        struct pam_userinfo *userinfo = (struct pam_userinfo *) appdata_ptr;
1705        struct pam_response *response;
1706
1707        if (!resp || !msg || !userinfo)
1708                return PAM_CONV_ERR;
1709
1710        /* allocate memory to store response */
1711        response = xzalloc(num_msg * sizeof(*response));
1712
1713        /* copy values */
1714        for (i = 0; i < num_msg; i++) {
1715                const char *s;
1716
1717                switch (msg[i]->msg_style) {
1718                case PAM_PROMPT_ECHO_ON:
1719                        s = userinfo->name;
1720                        break;
1721                case PAM_PROMPT_ECHO_OFF:
1722                        s = userinfo->pw;
1723                        break;
1724                case PAM_ERROR_MSG:
1725                case PAM_TEXT_INFO:
1726                        s = "";
1727                        break;
1728                default:
1729                        free(response);
1730                        return PAM_CONV_ERR;
1731                }
1732                response[i].resp = xstrdup(s);
1733                if (PAM_SUCCESS != 0)
1734                        response[i].resp_retcode = PAM_SUCCESS;
1735        }
1736        *resp = response;
1737        return PAM_SUCCESS;
1738}
1739# endif
1740
1741/*
1742 * Config file entries are of the form "/<path>:<user>:<passwd>".
1743 * If config file has no prefix match for path, access is allowed.
1744 *
1745 * path                 The file path
1746 * user_and_passwd      "user:passwd" to validate
1747 *
1748 * Returns 1 if user_and_passwd is OK.
1749 */
1750static int check_user_passwd(const char *path, char *user_and_passwd)
1751{
1752        Htaccess *cur;
1753        const char *prev = NULL;
1754
1755        for (cur = g_auth; cur; cur = cur->next) {
1756                const char *dir_prefix;
1757                size_t len;
1758                int r;
1759
1760                dir_prefix = cur->before_colon;
1761
1762                /* WHY? */
1763                /* If already saw a match, don't accept other different matches */
1764                if (prev && strcmp(prev, dir_prefix) != 0)
1765                        continue;
1766
1767                if (DEBUG)
1768                        fprintf(stderr, "checkPerm: '%s' ? '%s'\n", dir_prefix, user_and_passwd);
1769
1770                /* If it's not a prefix match, continue searching */
1771                len = strlen(dir_prefix);
1772                if (len != 1 /* dir_prefix "/" matches all, don't need to check */
1773                 && (strncmp(dir_prefix, path, len) != 0
1774                    || (path[len] != '/' && path[len] != '\0')
1775                    )
1776                ) {
1777                        continue;
1778                }
1779
1780                /* Path match found */
1781                prev = dir_prefix;
1782
1783                if (ENABLE_FEATURE_HTTPD_AUTH_MD5) {
1784                        char *colon_after_user;
1785                        const char *passwd;
1786# if ENABLE_FEATURE_SHADOWPASSWDS && !ENABLE_PAM
1787                        char sp_buf[256];
1788# endif
1789
1790                        colon_after_user = strchr(user_and_passwd, ':');
1791                        if (!colon_after_user)
1792                                goto bad_input;
1793
1794                        /* compare "user:" */
1795                        if (cur->after_colon[0] != '*'
1796                         && strncmp(cur->after_colon, user_and_passwd,
1797                                        colon_after_user - user_and_passwd + 1) != 0
1798                        ) {
1799                                continue;
1800                        }
1801                        /* this cfg entry is '*' or matches username from peer */
1802
1803                        passwd = strchr(cur->after_colon, ':');
1804                        if (!passwd)
1805                                goto bad_input;
1806                        passwd++;
1807                        if (passwd[0] == '*') {
1808# if ENABLE_PAM
1809                                struct pam_userinfo userinfo;
1810                                struct pam_conv conv_info = { &pam_talker, (void *) &userinfo };
1811                                pam_handle_t *pamh;
1812
1813                                *colon_after_user = '\0';
1814                                userinfo.name = user_and_passwd;
1815                                userinfo.pw = colon_after_user + 1;
1816                                r = pam_start("httpd", user_and_passwd, &conv_info, &pamh) != PAM_SUCCESS;
1817                                if (r == 0) {
1818                                        r = pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK) != PAM_SUCCESS
1819                                         || pam_acct_mgmt(pamh, PAM_DISALLOW_NULL_AUTHTOK)    != PAM_SUCCESS
1820                                        ;
1821                                        pam_end(pamh, PAM_SUCCESS);
1822                                }
1823                                *colon_after_user = ':';
1824                                goto end_check_passwd;
1825# else
1826#  if ENABLE_FEATURE_SHADOWPASSWDS
1827                                /* Using _r function to avoid pulling in static buffers */
1828                                struct spwd spw;
1829#  endif
1830                                struct passwd *pw;
1831
1832                                *colon_after_user = '\0';
1833                                pw = getpwnam(user_and_passwd);
1834                                *colon_after_user = ':';
1835                                if (!pw || !pw->pw_passwd)
1836                                        continue;
1837                                passwd = pw->pw_passwd;
1838#  if ENABLE_FEATURE_SHADOWPASSWDS
1839                                if ((passwd[0] == 'x' || passwd[0] == '*') && !passwd[1]) {
1840                                        /* getspnam_r may return 0 yet set result to NULL.
1841                                         * At least glibc 2.4 does this. Be extra paranoid here. */
1842                                        struct spwd *result = NULL;
1843                                        r = getspnam_r(pw->pw_name, &spw, sp_buf, sizeof(sp_buf), &result);
1844                                        if (r == 0 && result)
1845                                                passwd = result->sp_pwdp;
1846                                }
1847#  endif
1848                                /* In this case, passwd is ALWAYS encrypted:
1849                                 * it came from /etc/passwd or /etc/shadow!
1850                                 */
1851                                goto check_encrypted;
1852# endif /* ENABLE_PAM */
1853                        }
1854                        /* Else: passwd is from httpd.conf, it is either plaintext or encrypted */
1855
1856                        if (passwd[0] == '$' && isdigit(passwd[1])) {
1857                                char *encrypted;
1858# if !ENABLE_PAM
1859 check_encrypted:
1860# endif
1861                                /* encrypt pwd from peer and check match with local one */
1862                                encrypted = pw_encrypt(
1863                                        /* pwd (from peer): */  colon_after_user + 1,
1864                                        /* salt: */ passwd,
1865                                        /* cleanup: */ 0
1866                                );
1867                                r = strcmp(encrypted, passwd);
1868                                free(encrypted);
1869                        } else {
1870                                /* local passwd is from httpd.conf and it's plaintext */
1871                                r = strcmp(colon_after_user + 1, passwd);
1872                        }
1873                        goto end_check_passwd;
1874                }
1875 bad_input:
1876                /* Comparing plaintext "user:pass" in one go */
1877                r = strcmp(cur->after_colon, user_and_passwd);
1878 end_check_passwd:
1879                if (r == 0) {
1880                        remoteuser = xstrndup(user_and_passwd,
1881                                strchrnul(user_and_passwd, ':') - user_and_passwd
1882                        );
1883                        return 1; /* Ok */
1884                }
1885        } /* for */
1886
1887        /* 0(bad) if prev is set: matches were found but passwd was wrong */
1888        return (prev == NULL);
1889}
1890#endif  /* FEATURE_HTTPD_BASIC_AUTH */
1891
1892#if ENABLE_FEATURE_HTTPD_PROXY
1893static Htaccess_Proxy *find_proxy_entry(const char *url)
1894{
1895        Htaccess_Proxy *p;
1896        for (p = proxy; p; p = p->next) {
1897                if (strncmp(url, p->url_from, strlen(p->url_from)) == 0)
1898                        return p;
1899        }
1900        return NULL;
1901}
1902#endif
1903
1904/*
1905 * Handle timeouts
1906 */
1907static void send_REQUEST_TIMEOUT_and_exit(int sig) NORETURN;
1908static void send_REQUEST_TIMEOUT_and_exit(int sig UNUSED_PARAM)
1909{
1910        send_headers_and_exit(HTTP_REQUEST_TIMEOUT);
1911}
1912
1913/*
1914 * Handle an incoming http request and exit.
1915 */
1916static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) NORETURN;
1917static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
1918{
1919        static const char request_GET[] ALIGN1 = "GET";
1920        struct stat sb;
1921        char *urlcopy;
1922        char *urlp;
1923        char *tptr;
1924#if ENABLE_FEATURE_HTTPD_CGI
1925        static const char request_HEAD[] ALIGN1 = "HEAD";
1926        const char *prequest;
1927        char *cookie = NULL;
1928        char *content_type = NULL;
1929        unsigned long length = 0;
1930#elif ENABLE_FEATURE_HTTPD_PROXY
1931#define prequest request_GET
1932        unsigned long length = 0;
1933#endif
1934#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1935        smallint authorized = -1;
1936#endif
1937        smallint ip_allowed;
1938        char http_major_version;
1939#if ENABLE_FEATURE_HTTPD_PROXY
1940        char http_minor_version;
1941        char *header_buf = header_buf; /* for gcc */
1942        char *header_ptr = header_ptr;
1943        Htaccess_Proxy *proxy_entry;
1944#endif
1945
1946        /* Allocation of iobuf is postponed until now
1947         * (IOW, server process doesn't need to waste 8k) */
1948        iobuf = xmalloc(IOBUF_SIZE);
1949
1950        rmt_ip = 0;
1951        if (fromAddr->u.sa.sa_family == AF_INET) {
1952                rmt_ip = ntohl(fromAddr->u.sin.sin_addr.s_addr);
1953        }
1954#if ENABLE_FEATURE_IPV6
1955        if (fromAddr->u.sa.sa_family == AF_INET6
1956         && fromAddr->u.sin6.sin6_addr.s6_addr32[0] == 0
1957         && fromAddr->u.sin6.sin6_addr.s6_addr32[1] == 0
1958         && ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[2]) == 0xffff)
1959                rmt_ip = ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[3]);
1960#endif
1961        if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) {
1962                /* NB: can be NULL (user runs httpd -i by hand?) */
1963                rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr->u.sa);
1964        }
1965        if (verbose) {
1966                /* this trick makes -v logging much simpler */
1967                if (rmt_ip_str)
1968                        applet_name = rmt_ip_str;
1969                if (verbose > 2)
1970                        bb_error_msg("connected");
1971        }
1972
1973        /* Install timeout handler. get_line() needs it. */
1974        signal(SIGALRM, send_REQUEST_TIMEOUT_and_exit);
1975
1976        if (!get_line()) /* EOF or error or empty line */
1977                send_headers_and_exit(HTTP_BAD_REQUEST);
1978
1979        /* Determine type of request (GET/POST) */
1980        // rfc2616: method and URI is separated by exactly one space
1981        //urlp = strpbrk(iobuf, " \t"); - no, tab isn't allowed
1982        urlp = strchr(iobuf, ' ');
1983        if (urlp == NULL)
1984                send_headers_and_exit(HTTP_BAD_REQUEST);
1985        *urlp++ = '\0';
1986#if ENABLE_FEATURE_HTTPD_CGI
1987        prequest = request_GET;
1988        if (strcasecmp(iobuf, prequest) != 0) {
1989                prequest = request_HEAD;
1990                if (strcasecmp(iobuf, prequest) != 0) {
1991                        prequest = "POST";
1992                        if (strcasecmp(iobuf, prequest) != 0)
1993                                send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
1994                }
1995        }
1996#else
1997        if (strcasecmp(iobuf, request_GET) != 0)
1998                send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
1999#endif
2000        // rfc2616: method and URI is separated by exactly one space
2001        //urlp = skip_whitespace(urlp); - should not be necessary
2002        if (urlp[0] != '/')
2003                send_headers_and_exit(HTTP_BAD_REQUEST);
2004
2005        /* Find end of URL and parse HTTP version, if any */
2006        http_major_version = '0';
2007        IF_FEATURE_HTTPD_PROXY(http_minor_version = '0';)
2008        tptr = strchrnul(urlp, ' ');
2009        /* Is it " HTTP/"? */
2010        if (tptr[0] && strncmp(tptr + 1, HTTP_200, 5) == 0) {
2011                http_major_version = tptr[6];
2012                IF_FEATURE_HTTPD_PROXY(http_minor_version = tptr[8];)
2013        }
2014        *tptr = '\0';
2015
2016        /* Copy URL from after "GET "/"POST " to stack-allocated char[] */
2017        urlcopy = alloca((tptr - urlp) + 2 + strlen(index_page));
2018        /*if (urlcopy == NULL)
2019         *      send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);*/
2020        strcpy(urlcopy, urlp);
2021        /* NB: urlcopy ptr is never changed after this */
2022
2023        /* Extract url args if present */
2024        /* g_query = NULL; - already is */
2025        tptr = strchr(urlcopy, '?');
2026        if (tptr) {
2027                *tptr++ = '\0';
2028                g_query = tptr;
2029        }
2030
2031        /* Decode URL escape sequences */
2032        tptr = percent_decode_in_place(urlcopy, /*strict:*/ 1);
2033        if (tptr == NULL)
2034                send_headers_and_exit(HTTP_BAD_REQUEST);
2035        if (tptr == urlcopy + 1) {
2036                /* '/' or NUL is encoded */
2037                send_headers_and_exit(HTTP_NOT_FOUND);
2038        }
2039
2040        /* Canonicalize path */
2041        /* Algorithm stolen from libbb bb_simplify_path(),
2042         * but don't strdup, retain trailing slash, protect root */
2043        urlp = tptr = urlcopy;
2044        for (;;) {
2045                if (*urlp == '/') {
2046                        /* skip duplicate (or initial) slash */
2047                        if (*tptr == '/') {
2048                                goto next_char;
2049                        }
2050                        if (*tptr == '.') {
2051                                if (tptr[1] == '.' && (tptr[2] == '/' || tptr[2] == '\0')) {
2052                                        /* "..": be careful */
2053                                        /* protect root */
2054                                        if (urlp == urlcopy)
2055                                                send_headers_and_exit(HTTP_BAD_REQUEST);
2056                                        /* omit previous dir */
2057                                        while (*--urlp != '/')
2058                                                continue;
2059                                        /* skip to "./" or ".<NUL>" */
2060                                        tptr++;
2061                                }
2062                                if (tptr[1] == '/' || tptr[1] == '\0') {
2063                                        /* skip extra "/./" */
2064                                        goto next_char;
2065                                }
2066                        }
2067                }
2068                *++urlp = *tptr;
2069                if (*urlp == '\0')
2070                        break;
2071 next_char:
2072                tptr++;
2073        }
2074
2075        /* If URL is a directory, add '/' */
2076        if (urlp[-1] != '/') {
2077                if (is_directory(urlcopy + 1, /*followlinks:*/ 1)) {
2078                        found_moved_temporarily = urlcopy;
2079                }
2080        }
2081
2082        /* Log it */
2083        if (verbose > 1)
2084                bb_error_msg("url:%s", urlcopy);
2085
2086        tptr = urlcopy;
2087        ip_allowed = checkPermIP();
2088        while (ip_allowed && (tptr = strchr(tptr + 1, '/')) != NULL) {
2089                /* have path1/path2 */
2090                *tptr = '\0';
2091                if (is_directory(urlcopy + 1, /*followlinks:*/ 1)) {
2092                        /* may have subdir config */
2093                        parse_conf(urlcopy + 1, SUBDIR_PARSE);
2094                        ip_allowed = checkPermIP();
2095                }
2096                *tptr = '/';
2097        }
2098
2099#if ENABLE_FEATURE_HTTPD_PROXY
2100        proxy_entry = find_proxy_entry(urlcopy);
2101        if (proxy_entry)
2102                header_buf = header_ptr = xmalloc(IOBUF_SIZE);
2103#endif
2104
2105        if (http_major_version >= '0') {
2106                /* Request was with "... HTTP/nXXX", and n >= 0 */
2107
2108                /* Read until blank line */
2109                while (1) {
2110                        if (!get_line())
2111                                break; /* EOF or error or empty line */
2112                        if (DEBUG)
2113                                bb_error_msg("header: '%s'", iobuf);
2114
2115#if ENABLE_FEATURE_HTTPD_PROXY
2116                        /* We need 2 more bytes for yet another "\r\n" -
2117                         * see near fdprintf(proxy_fd...) further below */
2118                        if (proxy_entry && (header_ptr - header_buf) < IOBUF_SIZE - 2) {
2119                                int len = strlen(iobuf);
2120                                if (len > IOBUF_SIZE - (header_ptr - header_buf) - 4)
2121                                        len = IOBUF_SIZE - (header_ptr - header_buf) - 4;
2122                                memcpy(header_ptr, iobuf, len);
2123                                header_ptr += len;
2124                                header_ptr[0] = '\r';
2125                                header_ptr[1] = '\n';
2126                                header_ptr += 2;
2127                        }
2128#endif
2129
2130#if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY
2131                        /* Try and do our best to parse more lines */
2132                        if ((STRNCASECMP(iobuf, "Content-length:") == 0)) {
2133                                /* extra read only for POST */
2134                                if (prequest != request_GET
2135# if ENABLE_FEATURE_HTTPD_CGI
2136                                 && prequest != request_HEAD
2137# endif
2138                                ) {
2139                                        tptr = skip_whitespace(iobuf + sizeof("Content-length:") - 1);
2140                                        if (!tptr[0])
2141                                                send_headers_and_exit(HTTP_BAD_REQUEST);
2142                                        /* not using strtoul: it ignores leading minus! */
2143                                        length = bb_strtou(tptr, NULL, 10);
2144                                        /* length is "ulong", but we need to pass it to int later */
2145                                        if (errno || length > INT_MAX)
2146                                                send_headers_and_exit(HTTP_BAD_REQUEST);
2147                                }
2148                        }
2149#endif
2150#if ENABLE_FEATURE_HTTPD_CGI
2151                        else if (STRNCASECMP(iobuf, "Cookie:") == 0) {
2152                                cookie = xstrdup(skip_whitespace(iobuf + sizeof("Cookie:")-1));
2153                        } else if (STRNCASECMP(iobuf, "Content-Type:") == 0) {
2154                                content_type = xstrdup(skip_whitespace(iobuf + sizeof("Content-Type:")-1));
2155                        } else if (STRNCASECMP(iobuf, "Referer:") == 0) {
2156                                referer = xstrdup(skip_whitespace(iobuf + sizeof("Referer:")-1));
2157                        } else if (STRNCASECMP(iobuf, "User-Agent:") == 0) {
2158                                user_agent = xstrdup(skip_whitespace(iobuf + sizeof("User-Agent:")-1));
2159                        } else if (STRNCASECMP(iobuf, "Host:") == 0) {
2160                                host = xstrdup(skip_whitespace(iobuf + sizeof("Host:")-1));
2161                        } else if (STRNCASECMP(iobuf, "Accept:") == 0) {
2162                                http_accept = xstrdup(skip_whitespace(iobuf + sizeof("Accept:")-1));
2163                        } else if (STRNCASECMP(iobuf, "Accept-Language:") == 0) {
2164                                http_accept_language = xstrdup(skip_whitespace(iobuf + sizeof("Accept-Language:")-1));
2165                        }
2166#endif
2167#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
2168                        if (STRNCASECMP(iobuf, "Authorization:") == 0) {
2169                                /* We only allow Basic credentials.
2170                                 * It shows up as "Authorization: Basic <user>:<passwd>" where
2171                                 * "<user>:<passwd>" is base64 encoded.
2172                                 */
2173                                tptr = skip_whitespace(iobuf + sizeof("Authorization:")-1);
2174                                if (STRNCASECMP(tptr, "Basic") != 0)
2175                                        continue;
2176                                tptr += sizeof("Basic")-1;
2177                                /* decodeBase64() skips whitespace itself */
2178                                decodeBase64(tptr);
2179                                authorized = check_user_passwd(urlcopy, tptr);
2180                        }
2181#endif
2182#if ENABLE_FEATURE_HTTPD_RANGES
2183                        if (STRNCASECMP(iobuf, "Range:") == 0) {
2184                                /* We know only bytes=NNN-[MMM] */
2185                                char *s = skip_whitespace(iobuf + sizeof("Range:")-1);
2186                                if (strncmp(s, "bytes=", 6) == 0) {
2187                                        s += sizeof("bytes=")-1;
2188                                        range_start = BB_STRTOOFF(s, &s, 10);
2189                                        if (s[0] != '-' || range_start < 0) {
2190                                                range_start = -1;
2191                                        } else if (s[1]) {
2192                                                range_end = BB_STRTOOFF(s+1, NULL, 10);
2193                                                if (errno || range_end < range_start)
2194                                                        range_start = -1;
2195                                        }
2196                                }
2197                        }
2198#endif
2199#if ENABLE_FEATURE_HTTPD_GZIP
2200                        if (STRNCASECMP(iobuf, "Accept-Encoding:") == 0) {
2201                                /* Note: we do not support "gzip;q=0"
2202                                 * method of _disabling_ gzip
2203                                 * delivery. No one uses that, though */
2204                                const char *s = strstr(iobuf, "gzip");
2205                                if (s) {
2206                                        // want more thorough checks?
2207                                        //if (s[-1] == ' '
2208                                        // || s[-1] == ','
2209                                        // || s[-1] == ':'
2210                                        //) {
2211                                                content_gzip = 1;
2212                                        //}
2213                                }
2214                        }
2215#endif
2216                } /* while extra header reading */
2217        }
2218
2219        /* We are done reading headers, disable peer timeout */
2220        alarm(0);
2221
2222        if (strcmp(bb_basename(urlcopy), HTTPD_CONF) == 0 || !ip_allowed) {
2223                /* protect listing [/path]/httpd.conf or IP deny */
2224                send_headers_and_exit(HTTP_FORBIDDEN);
2225        }
2226
2227#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
2228        /* Case: no "Authorization:" was seen, but page might require passwd.
2229         * Check that with dummy user:pass */
2230        if (authorized < 0)
2231                authorized = check_user_passwd(urlcopy, (char *) "");
2232        if (!authorized)
2233                send_headers_and_exit(HTTP_UNAUTHORIZED);
2234#endif
2235
2236        if (found_moved_temporarily) {
2237                send_headers_and_exit(HTTP_MOVED_TEMPORARILY);
2238        }
2239
2240#if ENABLE_FEATURE_HTTPD_PROXY
2241        if (proxy_entry != NULL) {
2242                int proxy_fd;
2243                len_and_sockaddr *lsa;
2244
2245                proxy_fd = socket(AF_INET, SOCK_STREAM, 0);
2246                if (proxy_fd < 0)
2247                        send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
2248                lsa = host2sockaddr(proxy_entry->host_port, 80);
2249                if (lsa == NULL)
2250                        send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
2251                if (connect(proxy_fd, &lsa->u.sa, lsa->len) < 0)
2252                        send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
2253                fdprintf(proxy_fd, "%s %s%s%s%s HTTP/%c.%c\r\n",
2254                                prequest, /* GET or POST */
2255                                proxy_entry->url_to, /* url part 1 */
2256                                urlcopy + strlen(proxy_entry->url_from), /* url part 2 */
2257                                (g_query ? "?" : ""), /* "?" (maybe) */
2258                                (g_query ? g_query : ""), /* query string (maybe) */
2259                                http_major_version, http_minor_version);
2260                header_ptr[0] = '\r';
2261                header_ptr[1] = '\n';
2262                header_ptr += 2;
2263                write(proxy_fd, header_buf, header_ptr - header_buf);
2264                free(header_buf); /* on the order of 8k, free it */
2265                cgi_io_loop_and_exit(proxy_fd, proxy_fd, length);
2266        }
2267#endif
2268
2269        tptr = urlcopy + 1;      /* skip first '/' */
2270
2271#if ENABLE_FEATURE_HTTPD_CGI
2272        if (strncmp(tptr, "cgi-bin/", 8) == 0) {
2273                if (tptr[8] == '\0') {
2274                        /* protect listing "cgi-bin/" */
2275                        send_headers_and_exit(HTTP_FORBIDDEN);
2276                }
2277                send_cgi_and_exit(urlcopy, urlcopy, prequest, length, cookie, content_type);
2278        }
2279#endif
2280
2281        if (urlp[-1] == '/') {
2282                /* When index_page string is appended to <dir>/ URL, it overwrites
2283                 * the query string. If we fall back to call /cgi-bin/index.cgi,
2284                 * query string would be lost and not available to the CGI.
2285                 * Work around it by making a deep copy.
2286                 */
2287                if (ENABLE_FEATURE_HTTPD_CGI)
2288                        g_query = xstrdup(g_query); /* ok for NULL too */
2289                strcpy(urlp, index_page);
2290        }
2291        if (stat(tptr, &sb) == 0) {
2292#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
2293                char *suffix = strrchr(tptr, '.');
2294                if (suffix) {
2295                        Htaccess *cur;
2296                        for (cur = script_i; cur; cur = cur->next) {
2297                                if (strcmp(cur->before_colon + 1, suffix) == 0) {
2298                                        send_cgi_and_exit(urlcopy, urlcopy, prequest, length, cookie, content_type);
2299                                }
2300                        }
2301                }
2302#endif
2303                file_size = sb.st_size;
2304                last_mod = sb.st_mtime;
2305        }
2306#if ENABLE_FEATURE_HTTPD_CGI
2307        else if (urlp[-1] == '/') {
2308                /* It's a dir URL and there is no index.html
2309                 * Try cgi-bin/index.cgi */
2310                if (access("/cgi-bin/index.cgi"+1, X_OK) == 0) {
2311                        urlp[0] = '\0'; /* remove index_page */
2312                        send_cgi_and_exit("/cgi-bin/index.cgi", urlcopy, prequest, length, cookie, content_type);
2313                }
2314        }
2315        /* else fall through to send_file, it errors out if open fails: */
2316
2317        if (prequest != request_GET && prequest != request_HEAD) {
2318                /* POST for files does not make sense */
2319                send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
2320        }
2321        send_file_and_exit(tptr,
2322                (prequest != request_HEAD ? SEND_HEADERS_AND_BODY : SEND_HEADERS)
2323        );
2324#else
2325        send_file_and_exit(tptr, SEND_HEADERS_AND_BODY);
2326#endif
2327}
2328
2329/*
2330 * The main http server function.
2331 * Given a socket, listen for new connections and farm out
2332 * the processing as a [v]forked process.
2333 * Never returns.
2334 */
2335#if BB_MMU
2336static void mini_httpd(int server_socket) NORETURN;
2337static void mini_httpd(int server_socket)
2338{
2339        /* NB: it's best to not use xfuncs in this loop before fork().
2340         * Otherwise server may die on transient errors (temporary
2341         * out-of-memory condition, etc), which is Bad(tm).
2342         * Try to do any dangerous calls after fork.
2343         */
2344        while (1) {
2345                int n;
2346                len_and_sockaddr fromAddr;
2347
2348                /* Wait for connections... */
2349                fromAddr.len = LSA_SIZEOF_SA;
2350                n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len);
2351                if (n < 0)
2352                        continue;
2353
2354                /* set the KEEPALIVE option to cull dead connections */
2355                setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
2356
2357                if (fork() == 0) {
2358                        /* child */
2359                        /* Do not reload config on HUP */
2360                        signal(SIGHUP, SIG_IGN);
2361                        close(server_socket);
2362                        xmove_fd(n, 0);
2363                        xdup2(0, 1);
2364
2365                        handle_incoming_and_exit(&fromAddr);
2366                }
2367                /* parent, or fork failed */
2368                close(n);
2369        } /* while (1) */
2370        /* never reached */
2371}
2372#else
2373static void mini_httpd_nommu(int server_socket, int argc, char **argv) NORETURN;
2374static void mini_httpd_nommu(int server_socket, int argc, char **argv)
2375{
2376        char *argv_copy[argc + 2];
2377
2378        argv_copy[0] = argv[0];
2379        argv_copy[1] = (char*)"-i";
2380        memcpy(&argv_copy[2], &argv[1], argc * sizeof(argv[0]));
2381
2382        /* NB: it's best to not use xfuncs in this loop before vfork().
2383         * Otherwise server may die on transient errors (temporary
2384         * out-of-memory condition, etc), which is Bad(tm).
2385         * Try to do any dangerous calls after fork.
2386         */
2387        while (1) {
2388                int n;
2389                len_and_sockaddr fromAddr;
2390
2391                /* Wait for connections... */
2392                fromAddr.len = LSA_SIZEOF_SA;
2393                n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len);
2394                if (n < 0)
2395                        continue;
2396
2397                /* set the KEEPALIVE option to cull dead connections */
2398                setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
2399
2400                if (vfork() == 0) {
2401                        /* child */
2402                        /* Do not reload config on HUP */
2403                        signal(SIGHUP, SIG_IGN);
2404                        close(server_socket);
2405                        xmove_fd(n, 0);
2406                        xdup2(0, 1);
2407
2408                        /* Run a copy of ourself in inetd mode */
2409                        re_exec(argv_copy);
2410                }
2411                argv_copy[0][0] &= 0x7f;
2412                /* parent, or vfork failed */
2413                close(n);
2414        } /* while (1) */
2415        /* never reached */
2416}
2417#endif
2418
2419/*
2420 * Process a HTTP connection on stdin/out.
2421 * Never returns.
2422 */
2423static void mini_httpd_inetd(void) NORETURN;
2424static void mini_httpd_inetd(void)
2425{
2426        len_and_sockaddr fromAddr;
2427
2428        memset(&fromAddr, 0, sizeof(fromAddr));
2429        fromAddr.len = LSA_SIZEOF_SA;
2430        /* NB: can fail if user runs it by hand and types in http cmds */
2431        getpeername(0, &fromAddr.u.sa, &fromAddr.len);
2432        handle_incoming_and_exit(&fromAddr);
2433}
2434
2435static void sighup_handler(int sig UNUSED_PARAM)
2436{
2437        parse_conf(DEFAULT_PATH_HTTPD_CONF, SIGNALED_PARSE);
2438}
2439
2440enum {
2441        c_opt_config_file = 0,
2442        d_opt_decode_url,
2443        h_opt_home_httpd,
2444        IF_FEATURE_HTTPD_ENCODE_URL_STR(e_opt_encode_url,)
2445        IF_FEATURE_HTTPD_BASIC_AUTH(    r_opt_realm     ,)
2446        IF_FEATURE_HTTPD_AUTH_MD5(      m_opt_md5       ,)
2447        IF_FEATURE_HTTPD_SETUID(        u_opt_setuid    ,)
2448        p_opt_port      ,
2449        p_opt_inetd     ,
2450        p_opt_foreground,
2451        p_opt_verbose   ,
2452        OPT_CONFIG_FILE = 1 << c_opt_config_file,
2453        OPT_DECODE_URL  = 1 << d_opt_decode_url,
2454        OPT_HOME_HTTPD  = 1 << h_opt_home_httpd,
2455        OPT_ENCODE_URL  = IF_FEATURE_HTTPD_ENCODE_URL_STR((1 << e_opt_encode_url)) + 0,
2456        OPT_REALM       = IF_FEATURE_HTTPD_BASIC_AUTH(    (1 << r_opt_realm     )) + 0,
2457        OPT_MD5         = IF_FEATURE_HTTPD_AUTH_MD5(      (1 << m_opt_md5       )) + 0,
2458        OPT_SETUID      = IF_FEATURE_HTTPD_SETUID(        (1 << u_opt_setuid    )) + 0,
2459        OPT_PORT        = 1 << p_opt_port,
2460        OPT_INETD       = 1 << p_opt_inetd,
2461        OPT_FOREGROUND  = 1 << p_opt_foreground,
2462        OPT_VERBOSE     = 1 << p_opt_verbose,
2463};
2464
2465
2466int httpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
2467int httpd_main(int argc UNUSED_PARAM, char **argv)
2468{
2469        int server_socket = server_socket; /* for gcc */
2470        unsigned opt;
2471        char *url_for_decode;
2472        IF_FEATURE_HTTPD_ENCODE_URL_STR(const char *url_for_encode;)
2473        IF_FEATURE_HTTPD_SETUID(const char *s_ugid = NULL;)
2474        IF_FEATURE_HTTPD_SETUID(struct bb_uidgid_t ugid;)
2475        IF_FEATURE_HTTPD_AUTH_MD5(const char *pass;)
2476
2477        INIT_G();
2478
2479#if ENABLE_LOCALE_SUPPORT
2480        /* Undo busybox.c: we want to speak English in http (dates etc) */
2481        setlocale(LC_TIME, "C");
2482#endif
2483
2484        home_httpd = xrealloc_getcwd_or_warn(NULL);
2485        /* -v counts, -i implies -f */
2486        opt_complementary = "vv:if";
2487        /* We do not "absolutize" path given by -h (home) opt.
2488         * If user gives relative path in -h,
2489         * $SCRIPT_FILENAME will not be set. */
2490        opt = getopt32(argv, "c:d:h:"
2491                        IF_FEATURE_HTTPD_ENCODE_URL_STR("e:")
2492                        IF_FEATURE_HTTPD_BASIC_AUTH("r:")
2493                        IF_FEATURE_HTTPD_AUTH_MD5("m:")
2494                        IF_FEATURE_HTTPD_SETUID("u:")
2495                        "p:ifv",
2496                        &opt_c_configFile, &url_for_decode, &home_httpd
2497                        IF_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode)
2498                        IF_FEATURE_HTTPD_BASIC_AUTH(, &g_realm)
2499                        IF_FEATURE_HTTPD_AUTH_MD5(, &pass)
2500                        IF_FEATURE_HTTPD_SETUID(, &s_ugid)
2501                        , &bind_addr_or_port
2502                        , &verbose
2503                );
2504        if (opt & OPT_DECODE_URL) {
2505                fputs(percent_decode_in_place(url_for_decode, /*strict:*/ 0), stdout);
2506                return 0;
2507        }
2508#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR
2509        if (opt & OPT_ENCODE_URL) {
2510                fputs(encodeString(url_for_encode), stdout);
2511                return 0;
2512        }
2513#endif
2514#if ENABLE_FEATURE_HTTPD_AUTH_MD5
2515        if (opt & OPT_MD5) {
2516                char salt[sizeof("$1$XXXXXXXX")];
2517                salt[0] = '$';
2518                salt[1] = '1';
2519                salt[2] = '$';
2520                crypt_make_salt(salt + 3, 4);
2521                puts(pw_encrypt(pass, salt, /*cleanup:*/ 0));
2522                return 0;
2523        }
2524#endif
2525#if ENABLE_FEATURE_HTTPD_SETUID
2526        if (opt & OPT_SETUID) {
2527                xget_uidgid(&ugid, s_ugid);
2528        }
2529#endif
2530
2531#if !BB_MMU
2532        if (!(opt & OPT_FOREGROUND)) {
2533                bb_daemonize_or_rexec(0, argv); /* don't change current directory */
2534        }
2535#endif
2536
2537        xchdir(home_httpd);
2538        if (!(opt & OPT_INETD)) {
2539                signal(SIGCHLD, SIG_IGN);
2540                server_socket = openServer();
2541#if ENABLE_FEATURE_HTTPD_SETUID
2542                /* drop privileges */
2543                if (opt & OPT_SETUID) {
2544                        if (ugid.gid != (gid_t)-1) {
2545                                if (setgroups(1, &ugid.gid) == -1)
2546                                        bb_perror_msg_and_die("setgroups");
2547                                xsetgid(ugid.gid);
2548                        }
2549                        xsetuid(ugid.uid);
2550                }
2551#endif
2552        }
2553
2554#if 0
2555        /* User can do it himself: 'env - PATH="$PATH" httpd'
2556         * We don't do it because we don't want to screw users
2557         * which want to do
2558         * 'env - VAR1=val1 VAR2=val2 httpd'
2559         * and have VAR1 and VAR2 values visible in their CGIs.
2560         * Besides, it is also smaller. */
2561        {
2562                char *p = getenv("PATH");
2563                /* env strings themself are not freed, no need to xstrdup(p): */
2564                clearenv();
2565                if (p)
2566                        putenv(p - 5);
2567//              if (!(opt & OPT_INETD))
2568//                      setenv_long("SERVER_PORT", ???);
2569        }
2570#endif
2571
2572        parse_conf(DEFAULT_PATH_HTTPD_CONF, FIRST_PARSE);
2573        if (!(opt & OPT_INETD))
2574                signal(SIGHUP, sighup_handler);
2575
2576        xfunc_error_retval = 0;
2577        if (opt & OPT_INETD)
2578                mini_httpd_inetd();
2579#if BB_MMU
2580        if (!(opt & OPT_FOREGROUND))
2581                bb_daemonize(0); /* don't change current directory */
2582        mini_httpd(server_socket); /* never returns */
2583#else
2584        mini_httpd_nommu(server_socket, argc, argv); /* never returns */
2585#endif
2586        /* return 0; */
2587}
2588