X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=networking%2Fhttpd.c;h=ba65e13611c5521451e3859d6347f55c7ec03307;hb=0d6d88a2058d191c34d25a8709aca40311bb0c2e;hp=bceb89ba372f5f23d88d8cb1b1de74e74c4e953a;hpb=58c708af23d6ad855ea0cd1b61c2b87f26fc6988;p=oweals%2Fbusybox.git diff --git a/networking/httpd.c b/networking/httpd.c index bceb89ba3..ba65e1361 100644 --- a/networking/httpd.c +++ b/networking/httpd.c @@ -1,8 +1,10 @@ /* * httpd implementation for busybox * - * Copyright (C) 2002 Glenn Engel + * Copyright (C) 2002,2003 Glenn Engel + * Copyright (C) 2003 Vladimir Oleynik * + * simplify patch stolen from libbb without using strdup * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,100 +22,263 @@ * ***************************************************************************** * - * Typical usage: - * cd /var/www - * httpd - * This is equivalent to - * cd /var/www - * httpd -p 80 -c /etc/httpd.conf -r "Web Server Authentication" + * Typical usage: + * for non root user + * httpd -p 8080 -h $HOME/public_html + * or for daemon start from rc script with uid=0: + * httpd -u www + * This is equivalent if www user have uid=80 to + * httpd -p 80 -u 80 -h /www -c /etc/httpd.conf -r "Web Server Authentication" + * * * When a url contains "cgi-bin" it is assumed to be a cgi script. The * server changes directory to the location of the script and executes it - * after setting QUERY_STRING and other environment variables. If url args - * are included in the url or as a post, the args are placed into decoded - * environment variables. e.g. /cgi-bin/setup?foo=Hello%20World will set - * the $CGI_foo environment variable to "Hello World". + * after setting QUERY_STRING and other environment variables. * * The server can also be invoked as a url arg decoder and html text encoder * as follows: * foo=`httpd -d $foo` # decode "Hello%20World" as "Hello World" * bar=`httpd -e ""` # encode as "<Hello World>" + * Note that url encoding for arguments is not the same as html encoding for + * presenation. -d decodes a url-encoded argument while -e encodes in html + * for page display. * * httpd.conf has the following format: - -ip:10.10. # Allow any address that begins with 10.10. -ip:172.20. # Allow 172.20.x.x -ip:127.0.0.1 # Allow local loopback connections -/cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin -/:admin:setup # Require user admin, pwd setup on urls starting with / + * + * A:172.20. # Allow address from 172.20.0.0/16 + * A:10.0.0.0/25 # Allow any address from 10.0.0.0-10.0.0.127 + * A:10.0.0.0/255.255.255.128 # Allow any address that previous set + * A:127.0.0.1 # Allow local loopback connections + * D:* # Deny from other IP connections + * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin/ + * /adm:admin:setup # Require user admin, pwd setup on urls starting with /adm/ + * /adm:toor:PaSsWd # or user toor, pwd PaSsWd on urls starting with /adm/ + * .au:audio/basic # additional mime type for audio.au files + * + * A/D may be as a/d or allow/deny - first char case unsensitive + * Deny IP rules take precedence over allow rules. + * + * + * The Deny/Allow IP logic: + * + * - Default is to allow all. No addresses are denied unless + * denied with a D: rule. + * - Order of Deny/Allow rules is significant + * - Deny rules take precedence over allow rules. + * - If a deny all rule (D:*) is used it acts as a catch-all for unmatched + * addresses. + * - Specification of Allow all (A:*) is a no-op + * + * Example: + * 1. Allow only specified addresses + * A:172.20 # Allow any address that begins with 172.20. + * A:10.10. # Allow any address that begins with 10.10. + * A:127.0.0.1 # Allow local loopback connections + * D:* # Deny from other IP connections + * + * 2. Only deny specified addresses + * D:1.2.3. # deny from 1.2.3.0 - 1.2.3.255 + * D:2.3.4. # deny from 2.3.4.0 - 2.3.4.255 + * A:* # (optional line added for clarity) + * + * If a sub directory contains a config file it is parsed and merged with + * any existing settings as if it was appended to the original configuration. + * + * subdir paths are relative to the containing subdir and thus cannot + * affect the parent rules. + * + * Note that since the sub dir is parsed in the forked thread servicing the + * subdir http request, any merge is discarded when the process exits. As a + * result, the subdir settings only have a lifetime of a single request. + * + * + * If -c is not set, an attempt will be made to open the default + * root configuration file. If -c is set and the file is not found, the + * server exits with an error. + * +*/ + - * - * To open up the server: - * ip:* # Allow any IP address - * /:* # no password required for urls starting with / (all) - * - * Processing of the file stops on the first sucessful match. If the file - * is not found, the server is assumed to be wide open. - * - ***************************************************************************** - * - * Desired enhancements: - * cache httpd.conf - * support tinylogin - * - */ #include #include /* for isspace */ -#include /* for varargs */ -#include /* for strerror */ +#include #include /* for malloc */ #include -#include #include /* for close */ #include #include #include /* for connect and socket*/ #include /* for sockaddr_in */ -#include +#include #include #include -#include +#include /* for open modes */ +#include "busybox.h" -static const char httpdVersion[] = "busybox httpd/1.13 3-Jan-2003"; -// #define DEBUG 1 -#ifndef HTTPD_STANDALONE -#include -#include -// Note: xfuncs are not used because we want the server to keep running -// if something bad happens due to a malformed user request. -// As a result, all memory allocation is checked rigorously +static const char httpdVersion[] = "busybox httpd/1.34 2-Oct-2003"; +static const char default_path_httpd_conf[] = "/etc"; +static const char httpd_conf[] = "httpd.conf"; +static const char home[] = "./"; + +#ifdef CONFIG_LFS +# define cont_l_fmt "%lld" #else -/* standalone */ +# define cont_l_fmt "%ld" +#endif + +// Note: bussybox xfuncs are not used because we want the server to keep running +// if something bad happens due to a malformed user request. +// As a result, all memory allocation after daemonize +// is checked rigorously + +//#define DEBUG 1 + +/* Configure options, disabled by default as custom httpd feature */ + +/* disabled as optional features */ +//#define CONFIG_FEATURE_HTTPD_ENCODE_URL_STR +//#define CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV +//#define CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES +//#define CONFIG_FEATURE_HTTPD_SETUID +//#define CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + +/* If set, use this server from internet superserver only */ +//#define CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + +/* You can use this server as standalone, require libbb.a for linking */ +//#define HTTPD_STANDALONE + +/* Config options, disable this for do very small module */ +//#define CONFIG_FEATURE_HTTPD_CGI +//#define CONFIG_FEATURE_HTTPD_BASIC_AUTH +//#define CONFIG_FEATURE_HTTPD_AUTH_MD5 + +#ifdef HTTPD_STANDALONE +/* standalone, enable all features */ +#undef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY +/* unset config option for remove warning as redefined */ +#undef CONFIG_FEATURE_HTTPD_BASIC_AUTH +#undef CONFIG_FEATURE_HTTPD_AUTH_MD5 +#undef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR +#undef CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV +#undef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES +#undef CONFIG_FEATURE_HTTPD_CGI +#undef CONFIG_FEATURE_HTTPD_SETUID +#undef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP +/* enable all features now */ #define CONFIG_FEATURE_HTTPD_BASIC_AUTH -void show_usage() +#define CONFIG_FEATURE_HTTPD_AUTH_MD5 +#define CONFIG_FEATURE_HTTPD_ENCODE_URL_STR +#define CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV +#define CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES +#define CONFIG_FEATURE_HTTPD_CGI +#define CONFIG_FEATURE_HTTPD_SETUID +#define CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + +/* require from libbb.a for linking */ +const char *bb_applet_name = "httpd"; + +void bb_show_usage(void) { - fprintf(stderr,"Usage: httpd [-p ] [-c configFile] [-d/-e ] [-r realm]\n"); + fprintf(stderr, "Usage: %s [-p ] [-c configFile] [-d/-e ] " + "[-r realm] [-u user] [-h homedir]\n", bb_applet_name); + exit(1); } #endif -/* minimal global vars for busybox */ -#ifndef ENVSIZE -#define ENVSIZE 50 +#ifdef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY +#undef CONFIG_FEATURE_HTTPD_SETUID /* use inetd user.group config settings */ +#undef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP /* so is not daemon */ +/* inetd set stderr to accepted socket and we can`t true see debug messages */ +#undef DEBUG +#endif + +#define MAX_MEMORY_BUFF 8192 /* IO buffer */ + +typedef struct HT_ACCESS { + char *after_colon; + struct HT_ACCESS *next; + char before_colon[1]; /* really bigger, must last */ +} Htaccess; + +typedef struct HT_ACCESS_IP { + unsigned int ip; + unsigned int mask; + int allow_deny; + struct HT_ACCESS_IP *next; +} Htaccess_IP; + +typedef struct +{ + char buf[MAX_MEMORY_BUFF]; + +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + const char *realm; + char *remoteuser; +#endif + +#ifdef CONFIG_FEATURE_HTTPD_CGI + char *referer; +#endif + + const char *configFile; + + unsigned int rmt_ip; +#if defined(CONFIG_FEATURE_HTTPD_CGI) || defined(DEBUG) + char rmt_ip_str[16]; /* for set env REMOTE_ADDR */ +#endif + unsigned port; /* server initial port and for + set env REMOTE_PORT */ + + const char *found_mime_type; + off_t ContentLength; /* -1 - unknown */ + time_t last_mod; + + Htaccess_IP *ip_a_d; /* config allow/deny lines */ + int flg_deny_all; +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + Htaccess *auth; /* config user:password lines */ +#endif +#ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + Htaccess *mime_a; /* config mime types */ +#endif + +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + int accepted_socket; +#define a_c_r config->accepted_socket +#define a_c_w config->accepted_socket + int debugHttpd; /* if seted, don`t stay daemon */ +#else +#define a_c_r 0 +#define a_c_w 1 #endif -int debugHttpd; -static char **envp; -static int envCount; -static char *realm = "Web Server Authentication"; -static char *configFile; +} HttpdConfig; + +static HttpdConfig *config; + +static const char request_GET[] = "GET"; /* size algorithic optimize */ static const char* const suffixTable [] = { +/* Warning: shorted equalent suffix in one line must be first */ ".htm.html", "text/html", ".jpg.jpeg", "image/jpeg", ".gif", "image/gif", ".png", "image/png", ".txt.h.c.cc.cpp", "text/plain", - 0,0 + ".css", "text/css", + ".wav", "audio/wav", + ".avi", "video/x-msvideo", + ".qt.mov", "video/quicktime", + ".mpe.mpeg", "video/mpeg", + ".mid.midi", "audio/midi", + ".mp3", "audio/mpeg", +#if 0 /* unpopular */ + ".au", "audio/basic", + ".pac", "application/x-ns-proxy-autoconfig", + ".vrml.wrl", "model/vrml", +#endif + 0, "application/octet-stream" /* default */ }; typedef enum @@ -121,9 +286,10 @@ typedef enum HTTP_OK = 200, HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */ HTTP_NOT_FOUND = 404, + HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */ + HTTP_BAD_REQUEST = 400, /* malformed syntax */ + HTTP_FORBIDDEN = 403, HTTP_INTERNAL_SERVER_ERROR = 500, - HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */ - HTTP_BAD_REQUEST = 400, /* malformed syntax */ #if 0 /* future use */ HTTP_CONTINUE = 100, HTTP_SWITCHING_PROTOCOLS = 101, @@ -136,7 +302,6 @@ typedef enum HTTP_MOVED_TEMPORARILY = 302, HTTP_NOT_MODIFIED = 304, HTTP_PAYMENT_REQUIRED = 402, - HTTP_FORBIDDEN = 403, HTTP_BAD_GATEWAY = 502, HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */ HTTP_RESPONSE_SETSIZE=0xffffffff @@ -152,16 +317,18 @@ typedef struct static const HttpEnumString httpResponseNames[] = { { HTTP_OK, "OK" }, - { HTTP_NOT_IMPLEMENTED, "Not Implemented", + { HTTP_NOT_IMPLEMENTED, "Not Implemented", "The requested method is not recognized by this server." }, +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH { HTTP_UNAUTHORIZED, "Unauthorized", "" }, +#endif { HTTP_NOT_FOUND, "Not Found", "The requested URL was not found on this server." }, - { HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error" + { HTTP_BAD_REQUEST, "Bad Request", "Unsupported method." }, + { HTTP_FORBIDDEN, "Forbidden", "" }, + { HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error", "Internal Server Error" }, - { HTTP_BAD_REQUEST, "Bad Request" , - "Unsupported method.\n" }, -#if 0 +#if 0 /* not implemented */ { HTTP_CREATED, "Created" }, { HTTP_ACCEPTED, "Accepted" }, { HTTP_NO_CONTENT, "No Content" }, @@ -169,12 +336,362 @@ static const HttpEnumString httpResponseNames[] = { { HTTP_MOVED_PERMANENTLY, "Moved Permanently" }, { HTTP_MOVED_TEMPORARILY, "Moved Temporarily" }, { HTTP_NOT_MODIFIED, "Not Modified" }, - { HTTP_FORBIDDEN, "Forbidden", "" }, { HTTP_BAD_GATEWAY, "Bad Gateway", "" }, { HTTP_SERVICE_UNAVAILABLE, "Service Unavailable", "" }, #endif }; + +static const char RFC1123FMT[] = "%a, %d %b %Y %H:%M:%S GMT"; +static const char Content_length[] = "Content-length:"; + + +static int +scan_ip (const char **ep, unsigned int *ip, unsigned char endc) +{ + const char *p = *ep; + int auto_mask = 8; + int j; + + *ip = 0; + for (j = 0; j < 4; j++) { + unsigned int octet; + + if ((*p < '0' || *p > '9') && (*p != '/' || j == 0) && *p != 0) + return -auto_mask; + octet = 0; + while (*p >= '0' && *p <= '9') { + octet *= 10; + octet += *p - '0'; + if (octet > 255) + return -auto_mask; + p++; + } + if (*p == '.') + p++; + if (*p != '/' && *p != 0) + auto_mask += 8; + *ip = ((*ip) << 8) | octet; + } + if (*p != 0) { + if (*p != endc) + return -auto_mask; + p++; + if(*p == 0) + return -auto_mask; + } + *ep = p; + return auto_mask; +} + +static int +scan_ip_mask (const char *ipm, unsigned int *ip, unsigned int *mask) +{ + int i; + unsigned int msk; + + i = scan_ip(&ipm, ip, '/'); + if(i < 0) + return i; + if(*ipm) { + const char *p = ipm; + + i = 0; + while (*p) { + if (*p < '0' || *p > '9') { + if (*p == '.') { + i = scan_ip (&ipm, mask, 0); + return i != 32; + } + return -1; + } + i *= 10; + i += *p - '0'; + p++; + } + } + if (i > 32 || i < 0) + return -1; + msk = 0x80000000; + *mask = 0; + while (i > 0) { + *mask |= msk; + msk >>= 1; + i--; + } + return 0; +} + +#if defined(CONFIG_FEATURE_HTTPD_BASIC_AUTH) || defined(CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES) +static void free_config_lines(Htaccess **pprev) +{ + Htaccess *prev = *pprev; + + while( prev ) { + Htaccess *cur = prev; + + prev = cur->next; + free(cur); + } + *pprev = NULL; +} +#endif + +/* flag */ +#define FIRST_PARSE 0 +#define SUBDIR_PARSE 1 +#define SIGNALED_PARSE 2 +#define FIND_FROM_HTTPD_ROOT 3 +/**************************************************************************** + * + > $Function: parse_conf() + * + * $Description: parse configuration file into in-memory linked list. + * + * The first non-white character is examined to determine if the config line + * is one of the following: + * .ext:mime/type # new mime type not compiled into httpd + * [adAD]:from # ip address allow/deny, * for wildcard + * /path:user:pass # username/password + * + * Any previous IP rules are discarded. + * If the flag argument is not SUBDIR_PARSE then all /path and mime rules + * are also discarded. That is, previous settings are retained if flag is + * SUBDIR_PARSE. + * + * $Parameters: + * (const char *) path . . null for ip address checks, path for password + * checks. + * (int) flag . . . . . . the source of the parse request. + * + * $Return: (None) + * + ****************************************************************************/ +static void parse_conf(const char *path, int flag) +{ + FILE *f; +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + Htaccess *prev, *cur; +#elif CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + Htaccess *cur; +#endif + + const char *cf = config->configFile; + char buf[160]; + char *p0 = NULL; + char *c, *p; + + /* free previous ip setup if present */ + Htaccess_IP *pip = config->ip_a_d; + + while( pip ) { + Htaccess_IP *cur_ipl = pip; + + pip = cur_ipl->next; + free(cur_ipl); + } + config->ip_a_d = NULL; + + config->flg_deny_all = 0; + +#if defined(CONFIG_FEATURE_HTTPD_BASIC_AUTH) || defined(CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES) + /* retain previous auth and mime config only for subdir parse */ + if(flag != SUBDIR_PARSE) { +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + free_config_lines(&config->auth); +#endif +#ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + free_config_lines(&config->mime_a); +#endif + } +#endif + + if(flag == SUBDIR_PARSE || cf == NULL) { + cf = alloca(strlen(path) + sizeof(httpd_conf) + 2); + if(cf == NULL) { + if(flag == FIRST_PARSE) + bb_error_msg_and_die(bb_msg_memory_exhausted); + return; + } + sprintf((char *)cf, "%s/%s", path, httpd_conf); + } + + while((f = fopen(cf, "r")) == NULL) { + if(flag == SUBDIR_PARSE || flag == FIND_FROM_HTTPD_ROOT) { + /* config file not found, no changes to config */ + return; + } + if(config->configFile && flag == FIRST_PARSE) /* if -c option given */ + bb_perror_msg_and_die("%s", cf); + flag = FIND_FROM_HTTPD_ROOT; + cf = httpd_conf; + } + +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + prev = config->auth; +#endif + /* This could stand some work */ + while ( (p0 = fgets(buf, sizeof(buf), f)) != NULL) { + c = NULL; + for(p = p0; *p0 != 0 && *p0 != '#'; p0++) { + if(!isspace(*p0)) { + *p++ = *p0; + if(*p0 == ':' && c == NULL) + c = p; + } + } + *p = 0; + + /* test for empty or strange line */ + if (c == NULL || *c == 0) + continue; + p0 = buf; + if(*p0 == 'd') + *p0 = 'D'; + if(*c == '*') { + if(*p0 == 'D') { + /* memorize deny all */ + config->flg_deny_all++; + } + /* skip default other "word:*" config lines */ + continue; + } + + if(*p0 == 'a') + *p0 = 'A'; + else if(*p0 != 'D' && *p0 != 'A' +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + && *p0 != '/' +#endif +#ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + && *p0 != '.' +#endif + ) + continue; + if(*p0 == 'A' || *p0 == 'D') { + /* storing current config IP line */ + pip = calloc(1, sizeof(Htaccess_IP)); + if(pip) { + if(scan_ip_mask (c, &(pip->ip), &(pip->mask))) { + /* syntax IP{/mask} error detected, protect all */ + *p0 = 'D'; + pip->mask = 0; + } + pip->allow_deny = *p0; + if(*p0 == 'D') { + /* Deny:form_IP move top */ + pip->next = config->ip_a_d; + config->ip_a_d = pip; + } else { + /* add to bottom A:form_IP config line */ + Htaccess_IP *prev_IP = config->ip_a_d; + + if(prev_IP == NULL) { + config->ip_a_d = pip; + } else { + while(prev_IP->next) + prev_IP = prev_IP->next; + prev_IP->next = pip; + } + } + } + continue; + } +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + if(*p0 == '/') { + /* make full path from httpd root / curent_path / config_line_path */ + cf = flag == SUBDIR_PARSE ? path : ""; + p0 = malloc(strlen(cf) + (c - buf) + 2 + strlen(c)); + if(p0 == NULL) + continue; + c[-1] = 0; + sprintf(p0, "/%s%s", cf, buf); + + /* another call bb_simplify_path */ + cf = p = p0; + + do { + if (*p == '/') { + if (*cf == '/') { /* skip duplicate (or initial) slash */ + continue; + } else if (*cf == '.') { + if (cf[1] == '/' || cf[1] == 0) { /* remove extra '.' */ + continue; + } else if ((cf[1] == '.') && (cf[2] == '/' || cf[2] == 0)) { + ++cf; + if (p > p0) { + while (*--p != '/'); /* omit previous dir */ + } + continue; + } + } + } + *++p = *cf; + } while (*++cf); + + if ((p == p0) || (*p != '/')) { /* not a trailing slash */ + ++p; /* so keep last character */ + } + *p = 0; + sprintf(p0, "%s:%s", p0, c); + } +#endif + +#if defined(CONFIG_FEATURE_HTTPD_BASIC_AUTH) || defined(CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES) + /* storing current config line */ + cur = calloc(1, sizeof(Htaccess) + strlen(p0)); + if(cur) { + cf = strcpy(cur->before_colon, p0); + c = strchr(cf, ':'); + *c++ = 0; + cur->after_colon = c; +#ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + if(*cf == '.') { + /* config .mime line move top for overwrite previous */ + cur->next = config->mime_a; + config->mime_a = cur; + continue; + } +#endif +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + free(p0); + if(prev == NULL) { + /* first line */ + config->auth = prev = cur; + } else { + /* sort path, if current lenght eq or bigger then move up */ + Htaccess *prev_hti = config->auth; + int l = strlen(cf); + Htaccess *hti; + + for(hti = prev_hti; hti; hti = hti->next) { + if(l >= strlen(hti->before_colon)) { + /* insert before hti */ + cur->next = hti; + if(prev_hti != hti) { + prev_hti->next = cur; + } else { + /* insert as top */ + config->auth = cur; + } + break; + } + if(prev_hti != hti) + prev_hti = prev_hti->next; + } + if(!hti) { /* not inserted, add to bottom */ + prev->next = cur; + prev = cur; + } + } +#endif + } +#endif + } + fclose(f); +} + +#ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR /**************************************************************************** * > $Function: encodeString() @@ -197,19 +714,20 @@ static char *encodeString(const char *string) /* take the simple route and encode everything */ /* could possibly scan once to get length. */ int len = strlen(string); - char *out = (char*)malloc(len*5 +1); + char *out = malloc(len*5 +1); char *p=out; char ch; + if (!out) return ""; - while ((ch = *string++)) - { + while ((ch = *string++)) { // very simple check for what to encode if (isalnum(ch)) *p++ = ch; - else p += sprintf(p,"&#%d", (unsigned char) ch); + else p += sprintf(p, "&#%d", (unsigned char) ch); } *p=0; return out; } +#endif /* CONFIG_FEATURE_HTTPD_ENCODE_URL_STR */ /**************************************************************************** * @@ -223,25 +741,26 @@ static char *encodeString(const char *string) * * $Parameters: * (char *) string . . . The first string to decode. + * (int) flag . . . 1 if require decode '+' as ' ' for CGI * * $Return: (char *) . . . . A pointer to the decoded string (same as input). * * $Errors: None * ****************************************************************************/ -static char *decodeString(char *string) +static char *decodeString(char *orig, int flag_plus_to_space) { /* note that decoded string is always shorter than original */ - char *orig = string; + char *string = orig; char *ptr = string; + while (*ptr) { - if (*ptr == '+') { *string++ = ' '; ptr++; } + if (*ptr == '+' && flag_plus_to_space) { *string++ = ' '; ptr++; } else if (*ptr != '%') *string++ = *ptr++; - else - { + else { unsigned int value; - sscanf(ptr+1,"%2X",&value); + sscanf(ptr+1, "%2X", &value); *string++ = value; ptr += 3; } @@ -251,6 +770,7 @@ static char *decodeString(char *string) } +#ifdef CONFIG_FEATURE_HTTPD_CGI /**************************************************************************** * > $Function: addEnv() @@ -260,107 +780,45 @@ static char *decodeString(char *string) * environment settings passed to the cgi execution script. * * $Parameters: - * (char *) name . . . The environment variable name. - * (char *) value . . The value to which the env variable is set. + * (char *) name_before_underline - The first part environment variable name. + * (char *) name_after_underline - The second part environment variable name. + * (char *) value . . The value to which the env variable is set. * - * $Return: (void) + * $Return: (void) * * $Errors: Silently returns if the env runs out of space to hold the new item * ****************************************************************************/ -static void addEnv(const char *name, const char *value) +static void addEnv(const char *name_before_underline, + const char *name_after_underline, const char *value) { - char *s; - if (envCount >= ENVSIZE) return; - if (!value) value = ""; - s=(char*)malloc(strlen(name)+strlen(value)+2); - if (s) - { - sprintf(s,"%s=%s",name, value); - envp[envCount++]=s; - envp[envCount]=0; + char *s = NULL; + const char *underline; + + if (!value) + value = ""; + underline = *name_after_underline ? "_" : ""; + asprintf(&s, "%s%s%s=%s", name_before_underline, underline, + name_after_underline, value); + if(s) { + putenv(s); } } -/**************************************************************************** - * - > $Function: addEnvCgi - * - * $Description: Create environment variables given a URL encoded arg list. - * For each variable setting the URL encoded arg list, create a corresponding - * environment variable. URL encoded arguments have the form - * name1=value1&name2=value2&name3=value3 - * - * $Parameters: - * (char *) pargs . . . . A pointer to the URL encoded arguments. - * - * $Return: None - * - * $Errors: None - * - ****************************************************************************/ -static void addEnvCgi(const char *pargs) +#if defined(CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV) || !defined(CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY) +/* set environs SERVER_PORT and REMOTE_PORT */ +static void addEnvPort(const char *port_name) { - char *args; - if (pargs==0) return; - - /* args are a list of name=value&name2=value2 sequences */ - args = strdup(pargs); - while (args && *args) - { - char *sep; - char *name=args; - char *value=strchr(args,'='); - char *cginame; - if (!value) break; - *value++=0; - sep=strchr(value,'&'); - if (sep) - { - *sep=0; - args=sep+1; - } - else - { - sep = value + strlen(value); - args = 0; /* no more */ - } - cginame=(char*)malloc(strlen(decodeString(name))+5); - if (!cginame) break; - sprintf(cginame,"CGI_%s",name); - addEnv(cginame,decodeString(value)); - free(cginame); - } + char buf[16]; + + sprintf(buf, "%u", config->port); + addEnv(port_name, "PORT", buf); } +#endif +#endif /* CONFIG_FEATURE_HTTPD_CGI */ -#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH -static const unsigned char base64ToBin[] = { - 255 /* ' ' */, 255 /* '!' */, 255 /* '"' */, 255 /* '#' */, - 1 /* '$' */, 255 /* '%' */, 255 /* '&' */, 255 /* ''' */, - 255 /* '(' */, 255 /* ')' */, 255 /* '*' */, 62 /* '+' */, - 255 /* ',' */, 255 /* '-' */, 255 /* '.' */, 63 /* '/' */, - 52 /* '0' */, 53 /* '1' */, 54 /* '2' */, 55 /* '3' */, - 56 /* '4' */, 57 /* '5' */, 58 /* '6' */, 59 /* '7' */, - 60 /* '8' */, 61 /* '9' */, 255 /* ':' */, 255 /* ';' */, - 255 /* '<' */, 00 /* '=' */, 255 /* '>' */, 255 /* '?' */, - 255 /* '@' */, 00 /* 'A' */, 01 /* 'B' */, 02 /* 'C' */, - 03 /* 'D' */, 04 /* 'E' */, 05 /* 'F' */, 06 /* 'G' */, - 7 /* 'H' */, 8 /* 'I' */, 9 /* 'J' */, 10 /* 'K' */, - 11 /* 'L' */, 12 /* 'M' */, 13 /* 'N' */, 14 /* 'O' */, - 15 /* 'P' */, 16 /* 'Q' */, 17 /* 'R' */, 18 /* 'S' */, - 19 /* 'T' */, 20 /* 'U' */, 21 /* 'V' */, 22 /* 'W' */, - 23 /* 'X' */, 24 /* 'Y' */, 25 /* 'Z' */, 255 /* '[' */, - 255 /* '\' */, 255 /* ']' */, 255 /* '^' */, 255 /* '_' */, - 255 /* '`' */, 26 /* 'a' */, 27 /* 'b' */, 28 /* 'c' */, - 29 /* 'd' */, 30 /* 'e' */, 31 /* 'f' */, 32 /* 'g' */, - 33 /* 'h' */, 34 /* 'i' */, 35 /* 'j' */, 36 /* 'k' */, - 37 /* 'l' */, 38 /* 'm' */, 39 /* 'n' */, 40 /* 'o' */, - 41 /* 'p' */, 42 /* 'q' */, 43 /* 'r' */, 44 /* 's' */, - 45 /* 't' */, 46 /* 'u' */, 47 /* 'v' */, 48 /* 'w' */, - 49 /* 'x' */, 50 /* 'y' */, 51 /* 'z' */, 255 /* '{' */, - 255 /* '|' */, 255 /* '}' */, 255 /* '~' */, 255 /* '' */ -}; +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH /**************************************************************************** * > $Function: decodeBase64() @@ -370,186 +828,100 @@ static const unsigned char base64ToBin[] = { * Since the decode always results in a shorter size than the input, it is * OK to pass the input arg as an output arg. * - * $Parameters: - * (void *) outData. . . Where to place the decoded data. - * (size_t) outDataLen . The length of the output data string. - * (void *) inData . . . A pointer to a base64 encoded string. - * (size_t) inDataLen . The length of the input data string. + * $Parameter: + * (char *) Data . . . . A pointer to a base64 encoded string. + * Where to place the decoded data. * - * $Return: (char *) . . . . A pointer to the decoded string (same as input). + * $Return: void * * $Errors: None * ****************************************************************************/ -static size_t decodeBase64(void *outData, size_t outDataLen, - void *inData, size_t inDataLen) +static void decodeBase64(char *Data) { - int i = 0; - unsigned char *in = inData; - unsigned char *out = outData; + + const unsigned char *in = Data; + // The decoded size will be at most 3/4 the size of the encoded unsigned long ch = 0; - while (inDataLen && outDataLen) - { - unsigned char conv = 0; - unsigned char newch; - - while (inDataLen) - { - inDataLen--; - newch = *in++; - if ((newch < '0') || (newch > 'z')) continue; - conv = base64ToBin[newch - 32]; - if (conv == 255) continue; - break; - } - ch = (ch << 6) | conv; + int i = 0; + + while (*in) { + int t = *in++; + + if(t >= '0' && t <= '9') + t = t - '0' + 52; + else if(t >= 'A' && t <= 'Z') + t = t - 'A'; + else if(t >= 'a' && t <= 'z') + t = t - 'a' + 26; + else if(t == '+') + t = 62; + else if(t == '/') + t = 63; + else if(t == '=') + t = 0; + else + continue; + + ch = (ch << 6) | t; i++; - if (i== 4) - { - if (outDataLen >= 3) - { - *(out++) = (unsigned char) (ch >> 16); - *(out++) = (unsigned char) (ch >> 8); - *(out++) = (unsigned char) ch; - outDataLen-=3; - } - - i = 0; + if (i == 4) { + *Data++ = (char) (ch >> 16); + *Data++ = (char) (ch >> 8); + *Data++ = (char) ch; + i = 0; } - - if ((inDataLen == 0) && (i != 0)) - { - /* error - non multiple of 4 chars on input */ - break; - } - } - - /* return the actual number of chars in output array */ - return out-(unsigned char*) outData; + *Data = 0; } #endif -/**************************************************************************** - * - > $Function: perror_and_exit() - * - > $Description: A helper function to print an error and exit. - * - * $Parameters: - * (const char *) msg . . . A 'context' message to include. - * - * $Return: None - * - * $Errors: None - * - ****************************************************************************/ -static void perror_exit(const char *msg) -{ - perror(msg); - exit(1); -} - - -/**************************************************************************** - * - > $Function: strncmpi() - * - * $Description: compare two strings without regard to case. - * - * $Parameters: - * (char *) a . . . . . The first string. - * (char *) b . . . . . The second string. - * (int) n . . . . . . The number of chars to compare. - * - * $Return: (int) . . . . . . 0 if strings equal. 1 if a>b, -1 if b < a. - * - * $Errors: None - * - ****************************************************************************/ -#define __toupper(c) ((('a' <= (c))&&((c) <= 'z')) ? ((c) - 'a' + 'A') : (c)) -#define __tolower(c) ((('A' <= (c))&&((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) -static int strncmpi(const char *a, const char *b,int n) -{ - char a1,b1; - a1 = b1 = 0; - - while(n-- && ((a1 = *a++) != '\0') && ((b1 = *b++) != '\0')) - { - if(a1 == b1) continue; /* No need to convert */ - a1 = __tolower(a1); - b1 = __tolower(b1); - if(a1 != b1) break; /* No match, abort */ - } - if (n>=0) - { - if(a1 > b1) return 1; - if(a1 < b1) return -1; - } - return 0; -} +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY /**************************************************************************** * > $Function: openServer() * * $Description: create a listen server socket on the designated port. * - * $Parameters: - * (int) port . . . The port to listen on for connections. - * * $Return: (int) . . . A connection socket. -1 for errors. * * $Errors: None * ****************************************************************************/ -static int openServer(int port) +static int openServer(void) { struct sockaddr_in lsocket; int fd; - + /* create the socket right now */ /* inet_addr() returns a value that is already in network order */ memset(&lsocket, 0, sizeof(lsocket)); lsocket.sin_family = AF_INET; lsocket.sin_addr.s_addr = INADDR_ANY; - lsocket.sin_port = htons(port) ; + lsocket.sin_port = htons(config->port) ; fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd >= 0) - { + if (fd >= 0) { /* tell the OS it's OK to reuse a previous address even though */ /* it may still be in a close down state. Allows bind to succeed. */ - int one = 1; + int on = 1; #ifdef SO_REUSEPORT - setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char*)&one, sizeof(one)) ; + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (void *)&on, sizeof(on)) ; #else - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) ; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) ; #endif - if (bind(fd, (struct sockaddr *)&lsocket, sizeof(lsocket)) == 0) - { + if (bind(fd, (struct sockaddr *)&lsocket, sizeof(lsocket)) == 0) { listen(fd, 9); signal(SIGCHLD, SIG_IGN); /* prevent zombie (defunct) processes */ + } else { + bb_perror_msg_and_die("bind"); } - else - { - perror("failure to bind to server port"); - shutdown(fd,0); - close(fd); - fd = -1; - } - } - else - { - fprintf(stderr,"httpd: unable to create socket \n"); - } + } else { + bb_perror_msg_and_die("create socket"); + } return fd; } - -static int sendBuf(int s, char *buf, int len) -{ - if (len == -1) len = strlen(buf); - return send(s, buf, len, 0); -} +#endif /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */ /**************************************************************************** * @@ -558,84 +930,67 @@ static int sendBuf(int s, char *buf, int len) * $Description: Create and send HTTP response headers. * The arguments are combined and sent as one write operation. Note that * IE will puke big-time if the headers are not sent in one packet and the - * second packet is delayed for any reason. If contentType is null the - * content type is assumed to be text/html + * second packet is delayed for any reason. * - * $Parameters: - * (int) s . . . The http socket. + * $Parameter: * (HttpResponseNum) responseNum . . . The result code to send. - * (const char *) contentType . . . . A string indicating the type. - * (int) contentLength . . . . . . . . Content length. -1 if unknown. - * (time_t) expire . . . . . . . . . . Expiration time (secs since 1970) * - * $Return: (int) . . . . Always 0 - * - * $Errors: None + * $Return: (int) . . . . writing errors * ****************************************************************************/ -static int sendHeaders(int s, HttpResponseNum responseNum , - const char *contentType, - int contentLength, time_t expire) +static int sendHeaders(HttpResponseNum responseNum) { - char buf[1200]; + char *buf = config->buf; const char *responseString = ""; const char *infoString = 0; unsigned int i; time_t timer = time(0); char timeStr[80]; - for (i=0; - i < (sizeof(httpResponseNames)/sizeof(httpResponseNames[0])); i++) - { - if (httpResponseNames[i].type != responseNum) continue; - responseString = httpResponseNames[i].name; - infoString = httpResponseNames[i].info; - break; + int len; + + for (i = 0; + i < (sizeof(httpResponseNames)/sizeof(httpResponseNames[0])); i++) { + if (httpResponseNames[i].type == responseNum) { + responseString = httpResponseNames[i].name; + infoString = httpResponseNames[i].info; + break; + } } - if (infoString || !contentType) - { - contentType = "text/html"; + if (responseNum != HTTP_OK) { + config->found_mime_type = "text/html"; // error message is HTML } - - sprintf(buf, "HTTP/1.0 %d %s\nContent-type: %s\r\n", - responseNum, responseString, contentType); /* emit the current date */ - strftime(timeStr, sizeof(timeStr), - "%a, %d %b %Y %H:%M:%S GMT",gmtime(&timer)); - sprintf(buf+strlen(buf), "Date: %s\r\n", timeStr); - sprintf(buf+strlen(buf), "Connection: close\r\n"); - if (expire) - { - strftime(timeStr, sizeof(timeStr), - "%a, %d %b %Y %H:%M:%S GMT",gmtime(&expire)); - sprintf(buf+strlen(buf), "Expire: %s\r\n", timeStr); - } + strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&timer)); + len = sprintf(buf, + "HTTP/1.0 %d %s\nContent-type: %s\r\n" + "Date: %s\r\nConnection: close\r\n", + responseNum, responseString, config->found_mime_type, timeStr); - if (responseNum == HTTP_UNAUTHORIZED) - { - sprintf(buf+strlen(buf), - "WWW-Authenticate: Basic realm=\"%s\"\r\n", realm); +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + if (responseNum == HTTP_UNAUTHORIZED) { + len += sprintf(buf+len, "WWW-Authenticate: Basic realm=\"%s\"\r\n", + config->realm); } - if (contentLength != -1) - { - int len = strlen(buf); - sprintf(buf+len,"Content-length: %d\r\n", contentLength); +#endif + if (config->ContentLength != -1) { /* file */ + strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&config->last_mod)); + len += sprintf(buf+len, "Last-Modified: %s\r\n%s " cont_l_fmt "\r\n", + timeStr, Content_length, config->ContentLength); } - strcat(buf,"\r\n"); - if (infoString) - { - sprintf(buf+strlen(buf), + strcat(buf, "\r\n"); + len += 2; + if (infoString) { + len += sprintf(buf+len, "%d %s\n" "

%d %s

\n%s\n\n", responseNum, responseString, - responseNum, responseString, - infoString); + responseNum, responseString, infoString); } #ifdef DEBUG - if (debugHttpd) fprintf(stderr,"Headers:'%s'", buf); + if (config->debugHttpd) fprintf(stderr, "Headers: '%s'", buf); #endif - sendBuf(s, buf,-1); - return 0; + return bb_full_write(a_c_w, buf, len); } /**************************************************************************** @@ -646,31 +1001,28 @@ static int sendHeaders(int s, HttpResponseNum responseNum , * * Characters are read one at a time until an eol sequence is found. * - * $Parameters: - * (int) s . . . . . The socket fildes. - * (char *) buf . . Where to place the read result. - * (int) maxBuf . . Maximum number of chars to fit in buf. - * * $Return: (int) . . . . number of characters read. -1 if error. * ****************************************************************************/ -static int getLine(int s, char *buf, int maxBuf) +static int getLine(void) { int count = 0; - while (recv(s, buf+count, 1, 0) == 1) - { + char *buf = config->buf; + + while (read(a_c_r, buf + count, 1) == 1) { if (buf[count] == '\r') continue; - if (buf[count] == '\n') - { + if (buf[count] == '\n') { buf[count] = 0; return count; } - count++; + if(count < (MAX_MEMORY_BUFF-1)) /* check owerflow */ + count++; } if (count) return count; else return -1; } +#ifdef CONFIG_FEATURE_HTTPD_CGI /**************************************************************************** * > $Function: sendCgi() @@ -682,193 +1034,245 @@ static int getLine(int s, char *buf, int maxBuf) * data in addition to setting the QUERY_STRING variable (for GETs or POSTs). * * $Parameters: - * (int ) s . . . . . . . . The session socket. - * (const char *) url . . . The requested URL (with leading /). - * (const char *urlArgs). . Any URL arguments. - * (const char *body) . . . POST body contents. - * (int bodyLen) . . . . . Length of the post body. - + * (const char *) url . . . . . . The requested URL (with leading /). + * (const char *urlArgs). . . . . Any URL arguments. + * (int bodyLen) . . . . . . . . Length of the post body. + * (const char *cookie) . . . . . For set HTTP_COOKIE. + * (const char *content_type) . . For set CONTENT_TYPE. + * * $Return: (char *) . . . . A pointer to the decoded string (same as input). * * $Errors: None * ****************************************************************************/ -static int sendCgi(int s, const char *url, - const char *request, const char *urlArgs, - const char *body, int bodyLen) +static int sendCgi(const char *url, + const char *request, const char *urlArgs, + int bodyLen, const char *cookie, + const char *content_type) { int fromCgi[2]; /* pipe for reading data from CGI */ int toCgi[2]; /* pipe for sending data to CGI */ - - char *argp[] = { 0, 0 }; - int pid=0; - int inFd=inFd; + + static char * argp[] = { 0, 0 }; + int pid = 0; + int inFd; int outFd; - int firstLine=1; + int firstLine = 1; - do - { - if (pipe(fromCgi) != 0) - { + do { + if (pipe(fromCgi) != 0) { break; } - if (pipe(toCgi) != 0) - { + if (pipe(toCgi) != 0) { break; } pid = fork(); - if (pid < 0) - { + if (pid < 0) { pid = 0; - break;; + break; } - - if (!pid) - { + + if (!pid) { /* child process */ char *script; - char *directory; - inFd=toCgi[0]; - outFd=fromCgi[1]; + char *purl = strdup( url ); + char realpath_buff[MAXPATHLEN]; + + if(purl == NULL) + _exit(242); + + inFd = toCgi[0]; + outFd = fromCgi[1]; dup2(inFd, 0); // replace stdin with the pipe dup2(outFd, 1); // replace stdout with the pipe - if (!debugHttpd) dup2(outFd, 2); // replace stderr with the pipe + +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + if (!config->debugHttpd) +#endif + dup2(outFd, 2); // replace stderr with the pipe + close(toCgi[0]); close(toCgi[1]); close(fromCgi[0]); close(fromCgi[1]); -#if 0 - fcntl(0,F_SETFD, 1); - fcntl(1,F_SETFD, 1); - fcntl(2,F_SETFD, 1); -#endif - - script = (char*) malloc(strlen(url)+2); - if (!script) _exit(242); - sprintf(script,".%s",url); - - envCount=0; - addEnv("SCRIPT_NAME",script); - addEnv("REQUEST_METHOD",request); - addEnv("QUERY_STRING",urlArgs); - addEnv("SERVER_SOFTWARE",httpdVersion); - if (strncmpi(request,"POST",4)==0) addEnvCgi(body); - else addEnvCgi(urlArgs); - /* - * Most HTTP servers chdir to the cgi directory. + * Find PATH_INFO. */ - while (*url == '/') url++; // skip leading slash(s) - directory = strdup( url ); - if ( directory == (char*) 0 ) - script = (char*) (url); /* ignore errors */ - else - { - script = strrchr( directory, '/' ); - if ( script == (char*) 0 ) - script = directory; - else - { - *script++ = '\0'; - (void) chdir( directory ); /* ignore errors */ + script = purl; + while((script = strchr( script + 1, '/' )) != NULL) { + /* have script.cgi/PATH_INFO or dirs/script.cgi[/PATH_INFO] */ + struct stat sb; + + *script = '\0'; + if(is_directory(purl + 1, 1, &sb) == 0) { + /* not directory, found script.cgi/PATH_INFO */ + *script = '/'; + break; } + *script = '/'; /* is directory, find next '/' */ } - // now run the program. If it fails, use _exit() so no destructors - // get called and make a mess. - execve(script, argp, envp); - -#ifdef DEBUG - fprintf(stderr, "exec failed\n"); + addEnv("PATH", "INFO", script); /* set /PATH_INFO or NULL */ + addEnv("PATH", "", getenv("PATH")); + addEnv("REQUEST", "METHOD", request); + if(urlArgs) { + char *uri = alloca(strlen(purl) + 2 + strlen(urlArgs)); + if(uri) + sprintf(uri, "%s?%s", purl, urlArgs); + addEnv("REQUEST", "URI", uri); + } else { + addEnv("REQUEST", "URI", purl); + } + if(script != NULL) + *script = '\0'; /* reduce /PATH_INFO */ + /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */ + addEnv("SCRIPT_NAME", "", purl); + addEnv("QUERY_STRING", "", urlArgs); + addEnv("SERVER", "SOFTWARE", httpdVersion); + addEnv("SERVER", "PROTOCOL", "HTTP/1.0"); + addEnv("GATEWAY_INTERFACE", "", "CGI/1.1"); + addEnv("REMOTE", "ADDR", config->rmt_ip_str); +#ifdef CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV + addEnvPort("REMOTE"); #endif - close(2); - close(1); - close(0); + if(bodyLen) { + char sbl[32]; + + sprintf(sbl, "%d", bodyLen); + addEnv("CONTENT", "LENGTH", sbl); + } + if(cookie) + addEnv("HTTP", "COOKIE", cookie); + if(content_type) + addEnv("CONTENT", "TYPE", content_type); + if(config->remoteuser) { + addEnv("REMOTE", "USER", config->remoteuser); + addEnv("AUTH_TYPE", "", "Basic"); + } + if(config->referer) + addEnv("HTTP", "REFERER", config->referer); + + /* set execve argp[0] without path */ + argp[0] = strrchr( purl, '/' ) + 1; + /* but script argp[0] must have absolute path and chdiring to this */ + if(realpath(purl + 1, realpath_buff) != NULL) { + script = strrchr(realpath_buff, '/'); + if(script) { + *script = '\0'; + if(chdir(realpath_buff) == 0) { + *script = '/'; + // now run the program. If it fails, + // use _exit() so no destructors + // get called and make a mess. + execv(realpath_buff, argp); + } + } + } +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + config->accepted_socket = 1; /* send to stdout */ +#endif + sendHeaders(HTTP_NOT_FOUND); _exit(242); } /* end child */ + } while (0); + + if (pid) { /* parent process */ - inFd=fromCgi[0]; - outFd=toCgi[1]; + int status; + size_t post_readed_size = 0, post_readed_idx = 0; + + inFd = fromCgi[0]; + outFd = toCgi[1]; close(fromCgi[1]); close(toCgi[0]); - if (body) write(outFd, body, bodyLen); - close(outFd); + signal(SIGPIPE, SIG_IGN); - } while (0); - - if (pid) - { - int status; - pid_t dead_pid; - - while (1) - { - struct timeval timeout; + while (1) { fd_set readSet; - char buf[160]; + fd_set writeSet; + char wbuf[128]; int nfound; int count; - + FD_ZERO(&readSet); + FD_ZERO(&writeSet); FD_SET(inFd, &readSet); - + if(bodyLen > 0 || post_readed_size > 0) { + FD_SET(outFd, &writeSet); + nfound = outFd > inFd ? outFd : inFd; + if(post_readed_size == 0) { + FD_SET(a_c_r, &readSet); + if(nfound < a_c_r) + nfound = a_c_r; + } /* Now wait on the set of sockets! */ - timeout.tv_sec = 0; - timeout.tv_usec = 10000; - nfound = select(inFd+1, &readSet, 0, 0, &timeout); - - if (nfound <= 0) - { - dead_pid = waitpid(pid, &status, WNOHANG); - if (dead_pid != 0) - { - close(fromCgi[0]); - close(fromCgi[1]); - close(toCgi[0]); - close(toCgi[1]); + nfound = select(nfound + 1, &readSet, &writeSet, 0, NULL); + } else { + if(!bodyLen) { + close(outFd); + bodyLen = -1; + } + nfound = select(inFd + 1, &readSet, 0, 0, NULL); + } + + if (nfound <= 0) { + if (waitpid(pid, &status, WNOHANG) > 0) { + close(inFd); #ifdef DEBUG - if (debugHttpd) - { + if (config->debugHttpd) { if (WIFEXITED(status)) - fprintf(stderr,"piped has exited with status=%d\n", WEXITSTATUS(status)); + bb_error_msg("piped has exited with status=%d", WEXITSTATUS(status)); if (WIFSIGNALED(status)) - fprintf(stderr,"piped has exited with signal=%d\n", WTERMSIG(status)); + bb_error_msg("piped has exited with signal=%d", WTERMSIG(status)); } -#endif - pid = -1; +#endif break; } - } - else - { + } else if(post_readed_size > 0 && FD_ISSET(outFd, &writeSet)) { + count = bb_full_write(outFd, wbuf + post_readed_idx, post_readed_size); + if(count > 0) { + post_readed_size -= count; + post_readed_idx += count; + if(post_readed_size == 0) + post_readed_idx = 0; + } + } else if(bodyLen > 0 && post_readed_size == 0 && FD_ISSET(a_c_r, &readSet)) { + count = bodyLen > sizeof(wbuf) ? sizeof(wbuf) : bodyLen; + count = bb_full_read(a_c_r, wbuf, count); + if(count > 0) { + post_readed_size += count; + bodyLen -= count; + } else { + bodyLen = 0; /* closed */ + } + } else if(FD_ISSET(inFd, &readSet)) { + int s = a_c_w; + char *rbuf = config->buf; + // There is something to read - count = read(inFd,buf,sizeof(buf)-1); - // If a read returns 0 at this point then some type of error has - // occurred. Bail now. - if (count == 0) break; - if (count > 0) - { - if (firstLine) - { + count = bb_full_read(inFd, rbuf, MAX_MEMORY_BUFF-1); + if (count == 0) + break; /* closed */ + if (count > 0) { + if (firstLine) { + rbuf[count] = 0; /* check to see if the user script added headers */ - if (strcmp(buf,"HTTP")!= 0) - { - write(s,"HTTP/1.0 200 OK\n", 16); + if(strncmp(rbuf, "HTTP/1.0 200 OK\n", 4) != 0) { + bb_full_write(s, "HTTP/1.0 200 OK\n", 16); } - if (strstr(buf,"ontent-") == 0) - { - write(s,"Content-type: text/plain\n\n", 26); + if (strstr(rbuf, "ontent-") == 0) { + bb_full_write(s, "Content-type: text/plain\n\n", 26); } - - firstLine=0; + firstLine = 0; } - write(s,buf,count); + bb_full_write(s, rbuf, count); #ifdef DEBUG - if (debugHttpd) fprintf(stderr,"cgi read %d bytes\n", count); + if (config->debugHttpd) + fprintf(stderr, "cgi read %d bytes\n", count); #endif } } @@ -876,6 +1280,7 @@ static int sendCgi(int s, const char *url, } return 0; } +#endif /* CONFIG_FEATURE_HTTPD_CGI */ /**************************************************************************** * @@ -883,151 +1288,187 @@ static int sendCgi(int s, const char *url, * * $Description: Send a file response to an HTTP request * - * $Parameters: - * (int) s . . . . . . . The http session socket. + * $Parameter: * (const char *) url . . The URL requested. * * $Return: (int) . . . . . . Always 0. * ****************************************************************************/ -static int sendFile(int s, const char *url) +static int sendFile(const char *url) { - char *suffix = strrchr(url,'.'); - const char *content = "application/octet-stream"; + char * suffix; int f; + const char * const * table; + const char * try_suffix; - if (suffix) - { - const char ** table; - for (table = (const char **) &suffixTable[0]; - *table && (strstr(*table, suffix) == 0); table+=2); - if (table) content = *(table+1); + suffix = strrchr(url, '.'); + + for (table = suffixTable; *table; table += 2) + if(suffix != NULL && (try_suffix = strstr(*table, suffix)) != 0) { + try_suffix += strlen(suffix); + if(*try_suffix == 0 || *try_suffix == '.') + break; + } + /* also, if not found, set default as "application/octet-stream"; */ + config->found_mime_type = *(table+1); +#ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + if (suffix) { + Htaccess * cur; + + for (cur = config->mime_a; cur; cur = cur->next) { + if(strcmp(cur->before_colon, suffix) == 0) { + config->found_mime_type = cur->after_colon; + break; + } + } } - - if (*url == '/') url++; - suffix = strchr(url,'?'); - if (suffix) *suffix = 0; +#endif /* CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES */ #ifdef DEBUG - fprintf(stderr,"Sending file '%s'\n", url); + if (config->debugHttpd) + fprintf(stderr, "Sending file '%s' Content-type: %s\n", + url, config->found_mime_type); #endif - - f = open(url,O_RDONLY, 0444); - if (f >= 0) - { - char buf[1450]; - int count; - sendHeaders(s, HTTP_OK, content, -1, 0 ); - while ((count = read(f, buf, sizeof(buf)))) - { - sendBuf(s, buf, count); - } - close(f); - } - else - { + + f = open(url, O_RDONLY); + if (f >= 0) { + int count; + char *buf = config->buf; + + sendHeaders(HTTP_OK); + while ((count = bb_full_read(f, buf, MAX_MEMORY_BUFF)) > 0) { + bb_full_write(a_c_w, buf, count); + } + close(f); + } else { #ifdef DEBUG - fprintf(stderr,"Unable to open '%s'\n", url); + if (config->debugHttpd) + bb_perror_msg("Unable to open '%s'", url); #endif - sendHeaders(s, HTTP_NOT_FOUND, "text/html", -1, 0); + sendHeaders(HTTP_NOT_FOUND); } - + return 0; } +static int checkPermIP(void) +{ + Htaccess_IP * cur; + + /* This could stand some work */ + for (cur = config->ip_a_d; cur; cur = cur->next) { +#ifdef DEBUG + if (config->debugHttpd) { + fprintf(stderr, "checkPermIP: '%s' ? ", config->rmt_ip_str); + fprintf(stderr, "'%u.%u.%u.%u/%u.%u.%u.%u'\n", + (unsigned char)(cur->ip >> 24), + (unsigned char)(cur->ip >> 16), + (unsigned char)(cur->ip >> 8), + cur->ip & 0xff, + (unsigned char)(cur->mask >> 24), + (unsigned char)(cur->mask >> 16), + (unsigned char)(cur->mask >> 8), + cur->mask & 0xff); + } +#endif + if((config->rmt_ip & cur->mask) == cur->ip) + return cur->allow_deny == 'A'; /* Allow/Deny */ + } + + /* if uncofigured, return 1 - access from all */ + return !config->flg_deny_all; +} + /**************************************************************************** * > $Function: checkPerm() * - * $Description: Check the permission file for access. + * $Description: Check the permission file for access password protected. * - * Both IP addresses as well as url pathnames can be specified. If an IP - * address check is desired, the 'path' should be specified as "ip" and the - * dotted decimal IP address placed in request. - * - * For url pathnames, place the url (with leading /) in 'path' and any - * authentication information in request. e.g. "user:pass" - * - ******* - * - * Keep the algorithm simple. * If config file isn't present, everything is allowed. - * Run down /etc/httpd.hosts a line at a time. - * Stop if match is found. - * Entries are of the form: - * ip:10.10 # any address that begins with 10.10 - * dir:user:pass # dir security for dirs that start with 'dir' - * - * httpd.conf has the following format: - * ip:10.10. # Allow any address that begins with 10.10. - * ip:172.20. # Allow 172.20.x.x - * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin - * /:foo:bar # Require user foo, pwd bar on urls starting with / - * - * To open up the server: - * ip:* # Allow any IP address - * /:* # no password required for urls starting with / (all) + * Entries are of the form you can see example from header source * * $Parameters: - * (const char *) path . . . . The file path or "ip" for ip addresses. + * (const char *) path . . . . The file path. * (const char *) request . . . User information to validate. * * $Return: (int) . . . . . . . . . 1 if request OK, 0 otherwise. * ****************************************************************************/ + +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH static int checkPerm(const char *path, const char *request) { - FILE *f=NULL; - int rval; - char buf[80]; - char *p; - int ipaddr=0; - - /* If httpd.conf not there assume anyone can get in */ - if (configFile) f = fopen(configFile,"r"); - if(f == NULL) f = fopen("/etc/httpd.conf","r"); - if(f == NULL) f = fopen("httpd.conf","r"); - if(f == NULL) { - return(1); - } - if (strcmp("ip",path) == 0) ipaddr=1; + Htaccess * cur; + const char *p; + const char *p0; - rval=0; + const char *prev = NULL; /* This could stand some work */ - while ( fgets(buf, 80, f) != NULL) - { - if(buf[0] == '#') continue; - if(buf[0] == '\0') continue; - for(p = buf + (strlen(buf) - 1); p >= buf; p--) - { - if(isspace(*p)) *p = 0; - } - - p = strchr(buf,':'); - if (!p) continue; - *p++=0; + for (cur = config->auth; cur; cur = cur->next) { + p0 = cur->before_colon; + if(prev != NULL && strcmp(prev, p0) != 0) + continue; /* find next identical */ + p = cur->after_colon; #ifdef DEBUG - fprintf(stderr,"checkPerm: '%s' ? '%s'\n",buf,path); + if (config->debugHttpd) + fprintf(stderr,"checkPerm: '%s' ? '%s'\n", p0, request); #endif - if((ipaddr ? strcmp(buf,path) : strncmp(buf, path, strlen(buf))) == 0) { - /* match found. Check request */ - if ((strcmp("*",p) == 0) || - (strcmp(p, request) == 0) || - (ipaddr && (strncmp(p, request, strlen(p)) == 0))) - { - rval = 1; - break; - } + int l = strlen(p0); - /* reject on first failure for non ipaddresses */ - if (!ipaddr) break; + if(strncmp(p0, path, l) == 0 && + (l == 1 || path[l] == '/' || path[l] == 0)) { + char *u; + /* path match found. Check request */ + /* for check next /path:user:password */ + prev = p0; + u = strchr(request, ':'); + if(u == NULL) { + /* bad request, ':' required */ + break; + } + +#ifdef CONFIG_FEATURE_HTTPD_AUTH_MD5 + { + char *cipher; + char *pp; + + if(strncmp(p, request, u-request) != 0) { + /* user uncompared */ + continue; + } + pp = strchr(p, ':'); + if(pp && pp[1] == '$' && pp[2] == '1' && + pp[3] == '$' && pp[4]) { + pp++; + cipher = pw_encrypt(u+1, pp); + if (strcmp(cipher, pp) == 0) + goto set_remoteuser_var; /* Ok */ + /* unauthorized */ + continue; + } + } +#endif + if (strcmp(p, request) == 0) { +#ifdef CONFIG_FEATURE_HTTPD_AUTH_MD5 +set_remoteuser_var: +#endif + config->remoteuser = strdup(request); + if(config->remoteuser) + config->remoteuser[(u - request)] = 0; + return 1; /* Ok */ + } + /* unauthorized */ + } } - }; - fclose(f); - return(rval); -}; + } /* for */ + + return prev == NULL; +} + +#endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */ /**************************************************************************** @@ -1036,156 +1477,242 @@ static int checkPerm(const char *path, const char *request) * * $Description: Handle an incoming http request. * - * $Parameters: - * (s) s . . . . . The http request socket. - * - * $Return: (int) . . . Always 0. - * ****************************************************************************/ -static int handleIncoming(int s) +static void handleIncoming(void) { - char buf[8192]; - char url[8192]; /* hold args too initially */ - char credentials[80]; - char request[20]; - long length=0; - int major; - int minor; + char *buf = config->buf; + char *url; + char *purl; + int blank = -1; char *urlArgs; - char *body=0; +#ifdef CONFIG_FEATURE_HTTPD_CGI + const char *prequest = request_GET; + long length=0; + char *cookie = 0; + char *content_type = 0; +#endif + char *test; + struct stat sb; + int ip_allowed; - credentials[0] = 0; - do - { - int count = getLine(s, buf, sizeof(buf)); - int blank; - if (count <= 0) break; - count = sscanf(buf, "%9s %1000s HTTP/%d.%d", request, - url, &major, &minor); - - if (count < 2) - { - /* Garbled request/URL */ -#if 0 - genHttpHeader(&requestInfo, - HTTP_BAD_REQUEST, requestInfo.dataType, - HTTP_LENGTH_UNKNOWN); +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + int credentials = -1; /* if not requred this is Ok */ #endif + + do { + int count; + + if (getLine() <= 0) + break; /* closed */ + + purl = strpbrk(buf, " \t"); + if(purl == NULL) { +BAD_REQUEST: + sendHeaders(HTTP_BAD_REQUEST); break; } - - /* If no version info, assume 0.9 */ - if (count != 4) - { - major = 0; - minor = 9; + *purl = 0; +#ifdef CONFIG_FEATURE_HTTPD_CGI + if(strcasecmp(buf, prequest) != 0) { + prequest = "POST"; + if(strcasecmp(buf, prequest) != 0) { + sendHeaders(HTTP_NOT_IMPLEMENTED); + break; + } } +#else + if(strcasecmp(buf, request_GET) != 0) { + sendHeaders(HTTP_NOT_IMPLEMENTED); + break; + } +#endif + *purl = ' '; + count = sscanf(purl, " %[^ ] HTTP/%d.%*d", buf, &blank); + decodeString(buf, 0); + if (count < 1 || buf[0] != '/') { + /* Garbled request/URL */ + goto BAD_REQUEST; + } + url = alloca(strlen(buf) + 12); /* + sizeof("/index.html\0") */ + if(url == NULL) { + sendHeaders(HTTP_INTERNAL_SERVER_ERROR); + break; + } + strcpy(url, buf); /* extract url args if present */ - urlArgs = strchr(url,'?'); + urlArgs = strchr(url, '?'); if (urlArgs) - { - *urlArgs=0; - urlArgs++; + *urlArgs++ = 0; + + /* algorithm stolen from libbb bb_simplify_path(), + but don`t strdup and reducing trailing slash and protect out root */ + purl = test = url; + + do { + if (*purl == '/') { + if (*test == '/') { /* skip duplicate (or initial) slash */ + continue; + } else if (*test == '.') { + if (test[1] == '/' || test[1] == 0) { /* skip extra '.' */ + continue; + } else if ((test[1] == '.') && (test[2] == '/' || test[2] == 0)) { + ++test; + if (purl == url) { + /* protect out root */ + goto BAD_REQUEST; + } + while (*--purl != '/'); /* omit previous dir */ + continue; + } + } + } + *++purl = *test; + } while (*++test); + + *++purl = 0; /* so keep last character */ + test = purl; /* end ptr */ + + /* If URL is directory, adding '/' */ + if(test[-1] != '/') { + if ( is_directory(url + 1, 1, &sb) ) { + *test++ = '/'; + *test = 0; + purl = test; /* end ptr */ + } } - #ifdef DEBUG - if (debugHttpd) fprintf(stderr,"url='%s', args=%s\n", url, urlArgs); -#endif - - // read until blank line(s) - blank = 0; - while ((count = getLine(s, buf, sizeof(buf))) >= 0) - { - if (count == 0) - { - if (major > 0) break; - blank++; - if (blank == 2) break; - } + if (config->debugHttpd) + fprintf(stderr, "url='%s', args=%s\n", url, urlArgs); +#endif + + test = url; + ip_allowed = checkPermIP(); + while(ip_allowed && (test = strchr( test + 1, '/' )) != NULL) { + /* have path1/path2 */ + *test = '\0'; + if( is_directory(url + 1, 1, &sb) ) { + /* may be having subdir config */ + parse_conf(url + 1, SUBDIR_PARSE); + ip_allowed = checkPermIP(); + } + *test = '/'; + } + + // read until blank line for HTTP version specified, else parse immediate + while (blank >= 0 && (count = getLine()) > 0) { + #ifdef DEBUG - if (debugHttpd) fprintf(stderr,"Header: '%s'\n", buf); -#endif + if (config->debugHttpd) fprintf(stderr, "Header: '%s'\n", buf); +#endif +#ifdef CONFIG_FEATURE_HTTPD_CGI /* try and do our best to parse more lines */ - if ((strncmpi(buf, "Content-length:", 15) == 0)) - { - sscanf(buf, "%*s %ld", &length); - } + if ((strncasecmp(buf, Content_length, 15) == 0)) { + if(prequest != request_GET) + length = strtol(buf + 15, 0, 0); // extra read only for POST + } else if ((strncasecmp(buf, "Cookie:", 7) == 0)) { + for(test = buf + 7; isspace(*test); test++) + ; + cookie = strdup(test); + } else if ((strncasecmp(buf, "Content-Type:", 13) == 0)) { + for(test = buf + 13; isspace(*test); test++) + ; + content_type = strdup(test); + } else if ((strncasecmp(buf, "Referer:", 8) == 0)) { + for(test = buf + 8; isspace(*test); test++) + ; + config->referer = strdup(test); + } +#endif + #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH - else if (strncmpi(buf, "Authorization:", 14) == 0) - { + if (strncasecmp(buf, "Authorization:", 14) == 0) { /* We only allow Basic credentials. * It shows up as "Authorization: Basic " where * the userid:password is base64 encoded. */ - char *ptr = buf+14; - while (*ptr == ' ') ptr++; - if (strncmpi(ptr, "Basic", 5) != 0) break; - ptr += 5; - while (*ptr == ' ') ptr++; - memset(credentials, 0, sizeof(credentials)); - decodeBase64(credentials, - sizeof(credentials)-1, - ptr, - strlen(ptr) ); - + for(test = buf + 14; isspace(*test); test++) + ; + if (strncasecmp(test, "Basic", 5) != 0) + continue; + + test += 5; /* decodeBase64() skiping space self */ + decodeBase64(test); + credentials = checkPerm(url, test); } - } - if (!checkPerm(url, credentials)) - { - sendHeaders(s, HTTP_UNAUTHORIZED, 0, -1, 0); - length=-1; - break; /* no more processing */ - } -#else - } -#endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */ +#endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */ - /* we are done if an error occurred */ - if (length == -1) break; + } /* while extra header reading */ - if (strcmp(url,"/") == 0) strcpy(url,"/index.html"); - if (length>0) - { - body=(char*) malloc(length+1); - if (body) - { - length = read(s,body,length); - body[length]=0; // always null terminate for safety - urlArgs=body; - } - } - - if (strstr(url,"..") || strstr(url, "httpd.conf")) - { - /* protect from .. path creep */ - sendHeaders(s, HTTP_NOT_FOUND, "text/html", -1, 0); - } - else if (strstr(url,"cgi-bin")) - { - sendCgi(s, url, request, urlArgs, body, length); + if (strcmp(strrchr(url, '/') + 1, httpd_conf) == 0 || ip_allowed == 0) { + /* protect listing [/path]/httpd_conf or IP deny */ +#ifdef CONFIG_FEATURE_HTTPD_CGI +FORBIDDEN: /* protect listing /cgi-bin */ +#endif + sendHeaders(HTTP_FORBIDDEN); + break; } - else if (strncmpi(request,"GET",3) == 0) - { - sendFile(s, url); + +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + if (credentials <= 0 && checkPerm(url, ":") == 0) { + sendHeaders(HTTP_UNAUTHORIZED); + break; } - else - { - sendHeaders(s, HTTP_NOT_IMPLEMENTED, 0, -1, 0); +#endif + + test = url + 1; /* skip first '/' */ + +#ifdef CONFIG_FEATURE_HTTPD_CGI + /* if strange Content-Length */ + if (length < 0) + break; + + if (strncmp(test, "cgi-bin", 7) == 0) { + if(test[7] == '/' && test[8] == 0) + goto FORBIDDEN; // protect listing cgi-bin/ + sendCgi(url, prequest, urlArgs, length, cookie, content_type); + } else { + if (prequest != request_GET) + sendHeaders(HTTP_NOT_IMPLEMENTED); + else { +#endif /* CONFIG_FEATURE_HTTPD_CGI */ + if(purl[-1] == '/') + strcpy(purl, "index.html"); + if ( stat(test, &sb ) == 0 ) { + config->ContentLength = sb.st_size; + config->last_mod = sb.st_mtime; + } + sendFile(test); +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + /* unset if non inetd looped */ + config->ContentLength = -1; +#endif + +#ifdef CONFIG_FEATURE_HTTPD_CGI + } } +#endif + } while (0); -#ifdef DEBUG - if (debugHttpd) fprintf(stderr,"closing socket\n"); -#endif - if (body) free(body); - shutdown(s,SHUT_WR); - shutdown(s,SHUT_RD); - close(s); - - return 0; + +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY +/* from inetd don`t looping: freeing, closing automatic from exit always */ +# ifdef DEBUG + if (config->debugHttpd) fprintf(stderr, "closing socket\n"); +# endif +# ifdef CONFIG_FEATURE_HTTPD_CGI + free(cookie); + free(content_type); + free(config->remoteuser); + free(config->referer); +# endif + shutdown(a_c_w, SHUT_WR); + shutdown(a_c_r, SHUT_RD); + close(config->accepted_socket); +#endif /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */ } /**************************************************************************** @@ -1203,73 +1730,61 @@ static int handleIncoming(int s) * $Return: (int) . . . . Always 0. * ****************************************************************************/ +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY static int miniHttpd(int server) { fd_set readfd, portfd; - int nfound; - + FD_ZERO(&portfd); FD_SET(server, &portfd); - + /* copy the ports we are watching to the readfd set */ - while (1) - { - readfd = portfd ; - + while (1) { + readfd = portfd; + /* Now wait INDEFINATELY on the set of sockets! */ - nfound = select(server+1, &readfd, 0, 0, 0); - - switch (nfound) - { - case 0: - /* select timeout error! */ - break ; - case -1: - /* select error */ - break; - default: - if (FD_ISSET(server, &readfd)) - { - char on; + if (select(server + 1, &readfd, 0, 0, 0) > 0) { + if (FD_ISSET(server, &readfd)) { + int on; struct sockaddr_in fromAddr; - char rmt_ip[20]; - int addr; + socklen_t fromAddrLen = sizeof(fromAddr); int s = accept(server, - (struct sockaddr *)&fromAddr, &fromAddrLen) ; - if (s < 0) - { - continue; + (struct sockaddr *)&fromAddr, &fromAddrLen); + + if (s < 0) { + continue; } - addr = ntohl(fromAddr.sin_addr.s_addr); - sprintf(rmt_ip,"%u.%u.%u.%u", - (unsigned char)(addr >> 24), - (unsigned char)(addr >> 16), - (unsigned char)(addr >> 8), - (unsigned char)(addr >> 0)); + config->accepted_socket = s; + config->rmt_ip = ntohl(fromAddr.sin_addr.s_addr); +#if defined(CONFIG_FEATURE_HTTPD_CGI) || defined(DEBUG) + sprintf(config->rmt_ip_str, "%u.%u.%u.%u", + (unsigned char)(config->rmt_ip >> 24), + (unsigned char)(config->rmt_ip >> 16), + (unsigned char)(config->rmt_ip >> 8), + config->rmt_ip & 0xff); + config->port = ntohs(fromAddr.sin_port); #ifdef DEBUG - if (debugHttpd) - { - fprintf(stderr,"httpMini.cpp: connection from IP=%s, port %d\n", - rmt_ip, ntohs(fromAddr.sin_port)); - } -#endif - if(checkPerm("ip", rmt_ip) == 0) - { - close(s); - continue; + if (config->debugHttpd) { + bb_error_msg("connection from IP=%s, port %u\n", + config->rmt_ip_str, config->port); } - +#endif +#endif /* CONFIG_FEATURE_HTTPD_CGI */ + /* set the KEEPALIVE option to cull dead connections */ on = 1; - setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, - sizeof (on)); + setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof (on)); - if (fork() == 0) - { - /* This is the spawned thread */ - handleIncoming(s); - exit(0); + if (config->debugHttpd || fork() == 0) { + /* This is the spawned thread */ +#ifdef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + /* protect reload config, may be confuse checking */ + signal(SIGHUP, SIG_IGN); +#endif + handleIncoming(); + if(!config->debugHttpd) + exit(0); } close(s); } @@ -1278,72 +1793,214 @@ static int miniHttpd(int server) return 0; } +#else + /* from inetd */ + +static int miniHttpd(void) +{ + struct sockaddr_in fromAddrLen; + socklen_t sinlen = sizeof (struct sockaddr_in); + + getpeername (0, (struct sockaddr *)&fromAddrLen, &sinlen); + config->rmt_ip = ntohl(fromAddrLen.sin_addr.s_addr); +#if defined(CONFIG_FEATURE_HTTPD_CGI) || defined(DEBUG) + sprintf(config->rmt_ip_str, "%u.%u.%u.%u", + (unsigned char)(config->rmt_ip >> 24), + (unsigned char)(config->rmt_ip >> 16), + (unsigned char)(config->rmt_ip >> 8), + config->rmt_ip & 0xff); +#endif + config->port = ntohs(fromAddrLen.sin_port); + handleIncoming(); + return 0; +} +#endif /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */ + +#ifdef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP +static void sighup_handler(int sig) +{ + /* set and reset */ + struct sigaction sa; + + parse_conf(default_path_httpd_conf, + sig == SIGHUP ? SIGNALED_PARSE : FIRST_PARSE); + sa.sa_handler = sighup_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGHUP, &sa, NULL); +} +#endif + + +static const char httpd_opts[]="c:d:h:" +#ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR + "e:" +#define OPT_INC_1 1 +#else +#define OPT_INC_1 0 +#endif +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + "r:" +# ifdef CONFIG_FEATURE_HTTPD_AUTH_MD5 + "m:" +# define OPT_INC_2 2 +# else +# define OPT_INC_2 1 +#endif +#else +#define OPT_INC_2 0 +#endif +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + "p:v" +#ifdef CONFIG_FEATURE_HTTPD_SETUID + "u:" +#endif +#endif /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */ + ; + +#define OPT_CONFIG_FILE (1<<0) +#define OPT_DECODE_URL (1<<1) +#define OPT_HOME_HTTPD (1<<2) +#define OPT_ENCODE_URL (1<<(2+OPT_INC_1)) +#define OPT_REALM (1<<(3+OPT_INC_1)) +#define OPT_MD5 (1<<(4+OPT_INC_1)) +#define OPT_PORT (1<<(3+OPT_INC_1+OPT_INC_2)) +#define OPT_DEBUG (1<<(4+OPT_INC_1+OPT_INC_2)) +#define OPT_SETUID (1<<(5+OPT_INC_1+OPT_INC_2)) + + +#ifdef HTTPD_STANDALONE +int main(int argc, char *argv[]) +#else int httpd_main(int argc, char *argv[]) +#endif { + unsigned long opt; + const char *home_httpd = home; + char *url_for_decode; +#ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR + const char *url_for_encode; +#endif +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + const char *s_port; +#endif + +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY int server; - int port = 80; - int c; - - /* check if user supplied a port number */ - for (;;) { - c = getopt( argc, argv, "p:ve:d:" +#endif + +#ifdef CONFIG_FEATURE_HTTPD_SETUID + const char *s_uid; + long uid = -1; +#endif + +#ifdef CONFIG_FEATURE_HTTPD_AUTH_MD5 + const char *pass; +#endif + + config = xcalloc(1, sizeof(*config)); #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH - "r:c:" + config->realm = "Web Server Authentication"; +#endif + +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + config->port = 80; +#endif + + config->ContentLength = -1; + + opt = bb_getopt_ulflags(argc, argv, httpd_opts, + &(config->configFile), &url_for_decode, &home_httpd +#ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR + , &url_for_encode +#endif +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + , &(config->realm) +# ifdef CONFIG_FEATURE_HTTPD_AUTH_MD5 + , &pass +# endif +#endif +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + , &s_port +#ifdef CONFIG_FEATURE_HTTPD_SETUID + , &s_uid +#endif #endif ); - if (c == EOF) break; - switch (c) { - case 'v': - debugHttpd=1; - break; - case 'p': - port = atoi(optarg); - break; - case 'd': - printf("%s",decodeString(optarg)); + + if(opt & OPT_DECODE_URL) { + printf("%s", decodeString(url_for_decode, 1)); return 0; - case 'e': - printf("%s",encodeString(optarg)); + } +#ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR + if(opt & OPT_ENCODE_URL) { + printf("%s", encodeString(url_for_encode)); return 0; - case 'r': - realm = optarg; - break; - case 'c': - configFile = optarg; - break; - default: - fprintf(stderr,"%s\n", httpdVersion); - show_usage(); - exit(1); - } } +#endif +#ifdef CONFIG_FEATURE_HTTPD_AUTH_MD5 + if(opt & OPT_MD5) { + printf("%s\n", pw_encrypt(pass, "$1$")); + return 0; + } +#endif +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + if(opt & OPT_PORT) + config->port = bb_xgetlarg(s_port, 10, 1, 0xffff); + config->debugHttpd = opt & OPT_DEBUG; +#ifdef CONFIG_FEATURE_HTTPD_SETUID + if(opt & OPT_SETUID) { + char *e; - envp = (char**) malloc((ENVSIZE+1)*sizeof(char*)); - if (envp == 0) perror_exit("envp alloc"); - - server = openServer(port); - if (server < 0) exit(1); + uid = strtol(s_uid, &e, 0); + if(*e != '\0') { + /* not integer */ + uid = my_getpwnam(s_uid); + } + } +#endif +#endif - if (!debugHttpd) - { - /* remember our current pwd, daemonize, chdir back */ - char *dir = (char *) malloc(256); - if (dir == 0) perror_exit("out of memory for getpwd"); - if (getcwd(dir, 256) == 0) perror_exit("getcwd failed"); - if (daemon(0, 1) < 0) perror_exit("daemon"); - chdir(dir); - free(dir); + if(chdir(home_httpd)) { + bb_perror_msg_and_die("can`t chdir to %s", home_httpd); } +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + server = openServer(); +# ifdef CONFIG_FEATURE_HTTPD_SETUID + /* drop privilegies */ + if(uid > 0) + setuid(uid); +# endif +#endif - miniHttpd(server); - - return 0; -} +#ifdef CONFIG_FEATURE_HTTPD_CGI + { + char *p = getenv("PATH"); + if(p) { + p = bb_xstrdup(p); + } + clearenv(); + if(p) + setenv("PATH", p, 1); +# ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + addEnvPort("SERVER"); +# endif + } +#endif -#ifdef HTTPD_STANDALONE -int main(int argc, char *argv[]) -{ - return httpd_main(argc, argv); -} +#ifdef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + sighup_handler(0); +#else + parse_conf(default_path_httpd_conf, FIRST_PARSE); +#endif +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + if (!config->debugHttpd) { + if (daemon(1, 0) < 0) /* don`t change curent directory */ + bb_perror_msg_and_die("daemon"); + } + return miniHttpd(server); +#else + return miniHttpd(); #endif +}