X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;ds=sidebyside;f=networking%2Fhttpd.c;h=11e89f64a5c4e3ac9d0aec1ba2ae94634b6080e7;hb=999af20d3e025741197b8aebea9aeb4e435c9ebf;hp=df99f1c8ba0db4605d3d3b6c94b2faa7fbaeca15;hpb=874e3381dbe1d74c6249d4b0eb5e319602557e48;p=oweals%2Fbusybox.git diff --git a/networking/httpd.c b/networking/httpd.c index df99f1c8b..11e89f64a 100644 --- a/networking/httpd.c +++ b/networking/httpd.c @@ -2,7 +2,7 @@ * httpd implementation for busybox * * Copyright (C) 2002,2003 Glenn Engel - * Copyright (C) 2003 Vladimir Oleynik + * Copyright (C) 2003,2004 Vladimir Oleynik * * simplify patch stolen from libbb without using strdup * @@ -33,41 +33,71 @@ * * 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" while - * CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV enabled. + * 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" + * 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 + * presentation. -d decodes a url-encoded argument while -e encodes in html + * for page display. * * httpd.conf has the following format: + * + * 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 insensitive + * 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. + * +*/ -A:172.20. # Allow any address that begins with 172.20 -A:10.10. # Allow any address that begins with 10.10. -A:10.10 # Allow any address that previous set and 10.100-109.X.X -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 parsed only. - -Each subdir can have config file. -You can set less IP allow from subdir config. -Password protection from subdir config can rewriten previous sets for -current or/and next subpathes. -For protect as user:pass current subdir and subpathes set from subdir config: -/:user:pass -/subpath:user2:pass2 - - If -c don`t setted, used httpd root config, else httpd root config skiped. - */ #include #include /* for isspace */ @@ -86,12 +116,20 @@ For protect as user:pass current subdir and subpathes set from subdir config: #include "busybox.h" -static const char httpdVersion[] = "busybox httpd/1.25 10-May-2003"; +static const char httpdVersion[] = "busybox httpd/1.35 6-Oct-2004"; static const char default_path_httpd_conf[] = "/etc"; static const char httpd_conf[] = "httpd.conf"; -static const char home[] = "/www"; +static const char home[] = "./"; -// Note: bussybox xfuncs are not used because we want the server to keep running +#ifdef CONFIG_LFS +# define cont_l_fmt "%lld" +#else +# define cont_l_fmt "%ld" +#endif + +#define TIMEOUT 60 + +// Note: busybox 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 @@ -101,7 +139,6 @@ static const char home[] = "/www"; /* Configure options, disabled by default as custom httpd feature */ /* disabled as optional features */ -//#define CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV //#define CONFIG_FEATURE_HTTPD_ENCODE_URL_STR //#define CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV //#define CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES @@ -117,13 +154,14 @@ static const char home[] = "/www"; /* 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_SET_CGI_VARS_TO_ENV +#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 @@ -132,7 +170,7 @@ static const char home[] = "/www"; #undef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP /* enable all features now */ #define CONFIG_FEATURE_HTTPD_BASIC_AUTH -#define CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV +#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 @@ -158,15 +196,6 @@ void bb_show_usage(void) #undef DEBUG #endif -/* CGI environ size */ -#ifdef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV -#define ENVSIZE 50 /* set max 35 CGI_variable */ -#else -#define ENVSIZE 15 /* minimal requires */ -#endif - -#define MAX_POST_SIZE (64*1024) /* 64k. Its Small? May be ;) */ - #define MAX_MEMORY_BUFF 8192 /* IO buffer */ typedef struct HT_ACCESS { @@ -175,28 +204,46 @@ typedef struct HT_ACCESS { 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 { -#ifdef CONFIG_FEATURE_HTTPD_CGI - char *envp[ENVSIZE+1]; - int envCount; -#endif char buf[MAX_MEMORY_BUFF]; #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH const char *realm; + char *remoteuser; #endif + + const char *query; + +#ifdef CONFIG_FEATURE_HTTPD_CGI + char *referer; +#endif + const char *configFile; - char rmt_ip[16]; /* for set env REMOTE_ADDR */ + 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 */ + union HTTPD_FOUND { + const char *found_mime_type; + const char *found_moved_temporarily; + } httpd_found; - const char *found_mime_type; off_t ContentLength; /* -1 - unknown */ time_t last_mod; - Htaccess *ip_a_d; /* config allow/deny lines */ + 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 @@ -213,6 +260,8 @@ typedef struct #define a_c_r 0 #define a_c_w 1 #endif + volatile int alarm_signaled; + } HttpdConfig; static HttpdConfig *config; @@ -220,7 +269,7 @@ 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 */ +/* Warning: shorted equivalent suffix in one line must be first */ ".htm.html", "text/html", ".jpg.jpeg", "image/jpeg", ".gif", "image/gif", @@ -244,11 +293,13 @@ static const char* const suffixTable [] = { typedef enum { HTTP_OK = 200, + HTTP_MOVED_TEMPORARILY = 302, + HTTP_BAD_REQUEST = 400, /* malformed syntax */ 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_REQUEST_TIMEOUT = 408, + HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */ HTTP_INTERNAL_SERVER_ERROR = 500, #if 0 /* future use */ HTTP_CONTINUE = 100, @@ -259,7 +310,6 @@ typedef enum HTTP_NO_CONTENT = 204, HTTP_MULTIPLE_CHOICES = 300, HTTP_MOVED_PERMANENTLY = 301, - HTTP_MOVED_TEMPORARILY = 302, HTTP_NOT_MODIFIED = 304, HTTP_PAYMENT_REQUIRED = 402, HTTP_BAD_GATEWAY = 502, @@ -276,7 +326,10 @@ typedef struct } HttpEnumString; static const HttpEnumString httpResponseNames[] = { - { HTTP_OK, "OK" }, + { HTTP_OK, "OK", NULL }, + { HTTP_MOVED_TEMPORARILY, "Found", "Directories must end with a slash." }, + { HTTP_REQUEST_TIMEOUT, "Request Timeout", + "No request appeared within a reasonable time period." }, { HTTP_NOT_IMPLEMENTED, "Not Implemented", "The requested method is not recognized by this server." }, #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH @@ -294,7 +347,6 @@ static const HttpEnumString httpResponseNames[] = { { HTTP_NO_CONTENT, "No Content" }, { HTTP_MULTIPLE_CHOICES, "Multiple Choices" }, { HTTP_MOVED_PERMANENTLY, "Moved Permanently" }, - { HTTP_MOVED_TEMPORARILY, "Moved Temporarily" }, { HTTP_NOT_MODIFIED, "Not Modified" }, { HTTP_BAD_GATEWAY, "Bad Gateway", "" }, { HTTP_SERVICE_UNAVAILABLE, "Service Unavailable", "" }, @@ -306,7 +358,83 @@ 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; @@ -319,37 +447,76 @@ static void free_config_lines(Htaccess **pprev) } *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; - Htaccess *cur; #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH - Htaccess *prev; + Htaccess *prev, *cur; +#elif CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + Htaccess *cur; #endif const char *cf = config->configFile; - char buf[80]; + char buf[160]; char *p0 = NULL; char *c, *p; - /* free previous setuped */ - free_config_lines(&config->ip_a_d); + /* 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) + free_config_lines(&config->auth); #endif - ; /* syntax confuse */ #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); @@ -362,11 +529,11 @@ static void parse_conf(const char *path, int flag) } while((f = fopen(cf, "r")) == NULL) { - if(flag != FIRST_PARSE) { - /* config file not found */ + if(flag == SUBDIR_PARSE || flag == FIND_FROM_HTTPD_ROOT) { + /* config file not found, no changes to config */ return; } - if(config->configFile) /* if -c option given */ + 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; @@ -376,7 +543,7 @@ static void parse_conf(const char *path, int flag) prev = config->auth; #endif /* This could stand some work */ - while ( (p0 = fgets(buf, 80, f)) != NULL) { + while ( (p0 = fgets(buf, sizeof(buf), f)) != NULL) { c = NULL; for(p = p0; *p0 != 0 && *p0 != '#'; p0++) { if(!isspace(*p0)) { @@ -390,34 +557,60 @@ static void parse_conf(const char *path, int flag) /* test for empty or strange line */ if (c == NULL || *c == 0) continue; - if(*c == '*') - *c = 0; /* Allow all */ p0 = buf; - if(*p0 == 'a') - *p0 = 'A'; if(*p0 == 'd') *p0 = 'D'; - if(*p0 != 'A' && *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 != '/' + && *p0 != '/' #endif #ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES - && *p0 != '.' + && *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(*p0 == 'A' && *c == 0) { - /* skip default A:* */ - continue; + if(prev_IP == NULL) { + config->ip_a_d = pip; + } else { + while(prev_IP->next) + prev_IP = prev_IP->next; + prev_IP->next = pip; + } + } + } + continue; } - p0 = buf; #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH if(*p0 == '/') { - if(*c == 0) { - /* skip /path:* */ - continue; - } /* 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)); @@ -455,52 +648,32 @@ static void parse_conf(const char *path, int flag) sprintf(p0, "%s:%s", p0, c); } #endif - /* storing current config line */ +#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_BASIC_AUTH - if(*cf == '/') - free(p0); -#endif - if(*cf == 'A' || *cf == 'D') { - if(*cf == 'D' && *c) { - /* Deny:form_IP move top */ - cur->next = config->ip_a_d; - config->ip_a_d = cur; - } else { - /* add to bottom current IP config line */ - Htaccess *prev_IP = config->ip_a_d; - - if(prev_IP == NULL) { - config->ip_a_d = cur; - } else { - while(prev_IP->next) - prev_IP = prev_IP->next; - prev_IP->next = cur; - } - } - } #ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES - else if(*cf == '.') { + 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 - else if(prev == NULL) { + 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); + size_t l = strlen(cf); Htaccess *hti; for(hti = prev_hti; hti; hti = hti->next) { @@ -509,12 +682,11 @@ static void parse_conf(const char *path, int flag) cur->next = hti; if(prev_hti != hti) { prev_hti->next = cur; - break; } else { /* insert as top */ config->auth = cur; - break; } + break; } if(prev_hti != hti) prev_hti = prev_hti->next; @@ -526,6 +698,7 @@ static void parse_conf(const char *path, int flag) } #endif } +#endif } fclose(f); } @@ -561,7 +734,7 @@ static char *encodeString(const char *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; @@ -587,11 +760,12 @@ static char *encodeString(const char *string) * $Errors: None * ****************************************************************************/ -static char *decodeString(char *string, int flag_plus_to_space) +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 == '+' && flag_plus_to_space) { *string++ = ' '; ptr++; } @@ -613,7 +787,7 @@ static char *decodeString(char *string, int flag_plus_to_space) * > $Function: addEnv() * - * $Description: Add an enviornment variable setting to the global list. + * $Description: Add an environment variable setting to the global list. * A NAME=VALUE string is allocated, filled, and added to the list of * environment settings passed to the cgi execution script. * @@ -630,24 +804,20 @@ static char *decodeString(char *string, int flag_plus_to_space) static void addEnv(const char *name_before_underline, const char *name_after_underline, const char *value) { - char *s; + char *s = NULL; + const char *underline; - if (config->envCount >= ENVSIZE) - return; if (!value) value = ""; - s = malloc(strlen(name_before_underline) + strlen(name_after_underline) + - strlen(value) + 3); - if (s) { - const char *underline = *name_after_underline ? "_" : ""; - - sprintf(s,"%s%s%s=%s", name_before_underline, underline, + underline = *name_after_underline ? "_" : ""; + asprintf(&s, "%s%s%s=%s", name_before_underline, underline, name_after_underline, value); - config->envp[config->envCount++] = s; - config->envp[config->envCount] = 0; + if(s) { + putenv(s); } } +#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) { @@ -656,51 +826,9 @@ static void addEnvPort(const char *port_name) sprintf(buf, "%u", config->port); addEnv(port_name, "PORT", buf); } +#endif #endif /* CONFIG_FEATURE_HTTPD_CGI */ -#ifdef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV -/**************************************************************************** - * - > $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=&ignores - * from this example, name3 set empty value, tail without '=' skiping - * - * $Parameters: - * (char *) pargs . . . . A pointer to the URL encoded arguments. - * - * $Return: None - * - * $Errors: None - * - ****************************************************************************/ -static void addEnvCgi(const char *pargs) -{ - char *args; - char *memargs; - if (pargs==0) return; - - /* args are a list of name=value&name2=value2 sequences */ - memargs = args = strdup(pargs); - while (args && *args) { - const char *name = args; - char *value = strchr(args, '='); - - if (!value) /* &XXX without '=' */ - break; - *value++ = 0; - args = strchr(value, '&'); - if (args) - *args++ = 0; - addEnv("CGI", name, decodeString(value, 1)); - } - free(memargs); -} -#endif /* CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV */ - #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH /**************************************************************************** @@ -724,7 +852,7 @@ static void addEnvCgi(const char *pargs) static void decodeBase64(char *Data) { - const unsigned char *in = Data; + const unsigned char *in = (const unsigned char *)Data; // The decoded size will be at most 3/4 the size of the encoded unsigned long ch = 0; int i = 0; @@ -827,6 +955,7 @@ static int sendHeaders(HttpResponseNum responseNum) char *buf = config->buf; const char *responseString = ""; const char *infoString = 0; + const char *mime_type; unsigned int i; time_t timer = time(0); char timeStr[80]; @@ -840,16 +969,16 @@ static int sendHeaders(HttpResponseNum responseNum) break; } } - if (responseNum != HTTP_OK) { - config->found_mime_type = "text/html"; // error message is HTML - } + /* error message is HTML */ + mime_type = responseNum == HTTP_OK ? + config->httpd_found.found_mime_type : "text/html"; /* emit the current date */ 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); + responseNum, responseString, mime_type, timeStr); #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH if (responseNum == HTTP_UNAUTHORIZED) { @@ -857,9 +986,16 @@ static int sendHeaders(HttpResponseNum responseNum) config->realm); } #endif + if(responseNum == HTTP_MOVED_TEMPORARILY) { + len += sprintf(buf+len, "Location: %s/%s%s\r\n", + config->httpd_found.found_moved_temporarily, + (config->query ? "?" : ""), + (config->query ? config->query : "")); + } + if (config->ContentLength != -1) { /* file */ strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&config->last_mod)); - len += sprintf(buf+len, "Last-Modified: %s\r\n%s %ld\r\n", + len += sprintf(buf+len, "Last-Modified: %s\r\n%s " cont_l_fmt "\r\n", timeStr, Content_length, config->ContentLength); } strcat(buf, "\r\n"); @@ -885,15 +1021,13 @@ static int sendHeaders(HttpResponseNum responseNum) * * Characters are read one at a time until an eol sequence is found. * - * $Parameters: - * (char *) buf . . Where to place the read result. - * * $Return: (int) . . . . number of characters read. -1 if error. * ****************************************************************************/ -static int getLine(char *buf) +static int getLine(void) { int count = 0; + char *buf = config->buf; while (read(a_c_r, buf + count, 1) == 1) { if (buf[count] == '\r') continue; @@ -920,11 +1054,10 @@ static int getLine(char *buf) * data in addition to setting the QUERY_STRING variable (for GETs or POSTs). * * $Parameters: - * (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 *cookie) . . For set HTTP_COOKIE. + * (const char *) url . . . . . . The requested URL (with leading /). + * (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). @@ -933,8 +1066,8 @@ static int getLine(char *buf) * ****************************************************************************/ static int sendCgi(const char *url, - const char *request, const char *urlArgs, - const char *body, int bodyLen, const char *cookie) + const char *request, 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 */ @@ -1003,57 +1136,63 @@ static int sendCgi(const char *url, 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(config->query) { + char *uri = alloca(strlen(purl) + 2 + strlen(config->query)); if(uri) - sprintf(uri, "%s?%s", purl, urlArgs); + sprintf(uri, "%s?%s", purl, config->query); addEnv("REQUEST", "URI", uri); } else { addEnv("REQUEST", "URI", purl); } if(script != NULL) *script = '\0'; /* reduce /PATH_INFO */ + /* SCRIPT_FILENAME required by PHP in CGI mode */ + if(realpath(purl + 1, realpath_buff)) + addEnv("SCRIPT", "FILENAME", realpath_buff); + else + *realpath_buff = 0; /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */ addEnv("SCRIPT_NAME", "", purl); - addEnv("QUERY_STRING", "", urlArgs); + addEnv("QUERY_STRING", "", config->query); 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 - addEnv("REMOTE", "ADDR", config->rmt_ip); addEnvPort("REMOTE"); -#else - addEnv("REMOTE_ADDR", "", config->rmt_ip); #endif if(bodyLen) { char sbl[32]; sprintf(sbl, "%d", bodyLen); - addEnv("CONTENT_LENGTH", "", sbl); + addEnv("CONTENT", "LENGTH", sbl); } if(cookie) - addEnv("HTTP_COOKIE", "", cookie); - -#ifdef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV - if (request != request_GET) { - addEnvCgi(body); - } else { - addEnvCgi(urlArgs); + addEnv("HTTP", "COOKIE", cookie); + if(content_type) + addEnv("CONTENT", "TYPE", content_type); +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + if(config->remoteuser) { + addEnv("REMOTE", "USER", config->remoteuser); + addEnv("AUTH_TYPE", "", "Basic"); } #endif + 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) { + if(*realpath_buff) { 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. - execve(realpath_buff, argp, config->envp); + *script = '/'; + // now run the program. If it fails, + // use _exit() so no destructors + // get called and make a mess. + execv(realpath_buff, argp); } } } @@ -1069,28 +1208,41 @@ static int sendCgi(const char *url, if (pid) { /* parent process */ 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) bb_full_write(outFd, body, bodyLen); - close(outFd); + signal(SIGPIPE, SIG_IGN); while (1) { - struct timeval timeout; 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); + 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) { @@ -1103,29 +1255,60 @@ static int sendCgi(const char *url, bb_error_msg("piped has exited with signal=%d", WTERMSIG(status)); } #endif - pid = -1; 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 { + post_readed_size = post_readed_idx = bodyLen = 0; /* broken pipe to CGI */ + } + } else if(bodyLen > 0 && post_readed_size == 0 && FD_ISSET(a_c_r, &readSet)) { + count = bodyLen > (int)sizeof(wbuf) ? (int)sizeof(wbuf) : bodyLen; + count = safe_read(a_c_r, wbuf, count); + if(count > 0) { + post_readed_size += count; + bodyLen -= count; + } else { + bodyLen = 0; /* closed */ + } + } + if(FD_ISSET(inFd, &readSet)) { int s = a_c_w; + char *rbuf = config->buf; + +#ifndef PIPE_BUF +# define PIPESIZE 4096 /* amount of buffering in a pipe */ +#else +# define PIPESIZE PIPE_BUF +#endif +#if PIPESIZE >= MAX_MEMORY_BUFF +# error "PIPESIZE >= MAX_MEMORY_BUFF" +#endif // There is something to read - count = bb_full_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; + count = safe_read(inFd, rbuf, PIPESIZE); + if (count == 0) + break; /* closed */ if (count > 0) { if (firstLine) { + rbuf[count] = 0; /* check to see if the user script added headers */ - if (strncmp(buf, "HTTP/1.0 200 OK\n", 4) != 0) { + 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) { + if (strstr(rbuf, "ontent-") == 0) { bb_full_write(s, "Content-type: text/plain\n\n", 26); } - firstLine=0; + firstLine = 0; } - bb_full_write(s, buf, count); + if (bb_full_write(s, rbuf, count) != count) + break; + #ifdef DEBUG if (config->debugHttpd) fprintf(stderr, "cgi read %d bytes\n", count); @@ -1146,12 +1329,11 @@ static int sendCgi(const char *url, * * $Parameter: * (const char *) url . . The URL requested. - * (char *) buf . . . . . The stack buffer. * * $Return: (int) . . . . . . Always 0. * ****************************************************************************/ -static int sendFile(const char *url, char *buf) +static int sendFile(const char *url) { char * suffix; int f; @@ -1167,14 +1349,14 @@ static int sendFile(const char *url, char *buf) break; } /* also, if not found, set default as "application/octet-stream"; */ - config->found_mime_type = *(table+1); + config->httpd_found.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; + config->httpd_found.found_mime_type = cur->after_colon; break; } } @@ -1184,16 +1366,18 @@ static int sendFile(const char *url, char *buf) #ifdef DEBUG if (config->debugHttpd) fprintf(stderr, "Sending file '%s' Content-type: %s\n", - url, config->found_mime_type); + url, config->httpd_found.found_mime_type); #endif 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); + if (bb_full_write(a_c_w, buf, count) != count) + break; } close(f); } else { @@ -1207,17 +1391,45 @@ static int sendFile(const char *url, char *buf) 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 unconfigured, 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. * * If config file isn't present, everything is allowed. * Entries are of the form you can see example from header source * * $Parameters: - * (const char *) path . . . . The file path or NULL for ip addresses. + * (const char *) path . . . . The file path. * (const char *) request . . . User information to validate. * * $Return: (int) . . . . . . . . . 1 if request OK, 0 otherwise. @@ -1231,34 +1443,64 @@ static int checkPerm(const char *path, const char *request) const char *p; const char *p0; - int ipaddr = path == NULL; const char *prev = NULL; /* This could stand some work */ - for (cur = ipaddr ? config->ip_a_d : config->auth; cur; cur = cur->next) { + 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 if (config->debugHttpd) - fprintf(stderr,"checkPerm: '%s' ? '%s'\n", - (ipaddr ? (*p ? p : "*") : p0), request); + fprintf(stderr,"checkPerm: '%s' ? '%s'\n", p0, request); #endif - if(ipaddr) { - if(strncmp(p, request, strlen(p)) != 0) - continue; - return *p0 == 'A'; /* Allow/Deny */ - } else { - int l = strlen(p0); + { + size_t l = strlen(p0); if(strncmp(p0, path, l) == 0 && (l == 1 || path[l] == '/' || path[l] == 0)) { + char *u; /* path match found. Check request */ - if (strcmp(p, request) == 0) - return 1; /* Ok */ - /* unauthorized, but check next /path:user:password */ + /* 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 */ } } } /* for */ @@ -1266,30 +1508,22 @@ static int checkPerm(const char *path, const char *request) return prev == NULL; } -#else /* ifndef CONFIG_FEATURE_HTTPD_BASIC_AUTH */ -static int checkPermIP(const char *request) -{ - Htaccess * cur; - const char *p; +#endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */ - /* This could stand some work */ - for (cur = config->ip_a_d; cur; cur = cur->next) { - p = cur->after_colon; -#ifdef DEBUG - if (config->debugHttpd) - fprintf(stderr, "checkPerm: '%s' ? '%s'\n", - (*p ? p : "*"), request); -#endif - if(strncmp(p, request, strlen(p)) == 0) - return *cur->before_colon == 'A'; /* Allow/Deny */ - } +/**************************************************************************** + * + > $Function: handleIncoming() + * + * $Description: Handle an incoming http request. + * + ****************************************************************************/ - /* if uncofigured, return 1 - access from all */ - return 1; +static void +handle_sigalrm( int sig ) +{ + sendHeaders(HTTP_REQUEST_TIMEOUT); + config->alarm_signaled = sig; } -#define checkPerm(null, request) checkPermIP(request) -#endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */ - /**************************************************************************** * @@ -1304,25 +1538,36 @@ static void handleIncoming(void) char *url; char *purl; int blank = -1; - char *urlArgs; + char *test; + struct stat sb; + int ip_allowed; #ifdef CONFIG_FEATURE_HTTPD_CGI const char *prequest = request_GET; - char *body = 0; long length=0; char *cookie = 0; + char *content_type = 0; #endif - char *test; - struct stat sb; - int ip_allowed; +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + fd_set s_fd; + struct timeval tv; + int retval; +#endif + struct sigaction sa; #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH int credentials = -1; /* if not requred this is Ok */ #endif + sa.sa_handler = handle_sigalrm; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; /* no SA_RESTART */ + sigaction(SIGALRM, &sa, NULL); + do { int count; - if (getLine(buf) <= 0) + (void) alarm( TIMEOUT ); + if (getLine() <= 0) break; /* closed */ purl = strpbrk(buf, " \t"); @@ -1361,9 +1606,11 @@ BAD_REQUEST: } strcpy(url, buf); /* extract url args if present */ - urlArgs = strchr(url, '?'); - if (urlArgs) - *urlArgs++ = 0; + test = strchr(url, '?'); + if (test) { + *test++ = 0; + config->query = test; + } /* algorithm stolen from libbb bb_simplify_path(), but don`t strdup and reducing trailing slash and protect out root */ @@ -1393,70 +1640,84 @@ BAD_REQUEST: *++purl = 0; /* so keep last character */ test = purl; /* end ptr */ + /* If URL is directory, adding '/' */ /* If URL is directory, adding '/' */ if(test[-1] != '/') { if ( is_directory(url + 1, 1, &sb) ) { - *test++ = '/'; - *test = 0; - purl = test; /* end ptr */ + config->httpd_found.found_moved_temporarily = url; } } #ifdef DEBUG if (config->debugHttpd) - fprintf(stderr, "url='%s', args=%s\n", url, urlArgs); + fprintf(stderr, "url='%s', args=%s\n", url, config->query); #endif test = url; - ip_allowed = checkPerm(NULL, config->rmt_ip); + 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 = checkPerm(NULL, config->rmt_ip); + ip_allowed = checkPermIP(); } *test = '/'; } - - // read until blank line for HTTP version specified, else parse immediate - while (blank >= 0 && (count = getLine(buf)) > 0) { + if(blank >= 0) { + // read until blank line for HTTP version specified, else parse immediate + while(1) { + alarm(TIMEOUT); + count = getLine(); + if(count <= 0) + break; #ifdef DEBUG - if (config->debugHttpd) fprintf(stderr, "Header: '%s'\n", buf); + 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 ((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); - } + /* try and do our best to parse more lines */ + 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 - if (strncasecmp(buf, "Authorization:", 14) == 0) { - /* We only allow Basic credentials. - * It shows up as "Authorization: Basic " where - * the userid:password is base64 encoded. - */ - 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 (strncasecmp(buf, "Authorization:", 14) == 0) { + /* We only allow Basic credentials. + * It shows up as "Authorization: Basic " where + * the userid:password is base64 encoded. + */ + 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); + } #endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */ - } /* while extra header reading */ - + } /* while extra header reading */ + } + (void) alarm( 0 ); + if(config->alarm_signaled) + break; if (strcmp(strrchr(url, '/') + 1, httpd_conf) == 0 || ip_allowed == 0) { /* protect listing [/path]/httpd_conf or IP deny */ @@ -1474,27 +1735,27 @@ FORBIDDEN: /* protect listing /cgi-bin */ } #endif + if(config->httpd_found.found_moved_temporarily) { + sendHeaders(HTTP_MOVED_TEMPORARILY); +#ifdef DEBUG + /* clear unforked memory flag */ + if(config->debugHttpd) + config->httpd_found.found_moved_temporarily = NULL; +#endif + break; + } + test = url + 1; /* skip first '/' */ #ifdef CONFIG_FEATURE_HTTPD_CGI /* if strange Content-Length */ - if (length < 0 || length > MAX_POST_SIZE) + if (length < 0) break; - if (length > 0) { - body = malloc(length + 1); - if (body) { - length = bb_full_read(a_c_r, body, length); - if(length < 0) // closed - length = 0; - body[length] = 0; // always null terminate for safety - } - } - if (strncmp(test, "cgi-bin", 7) == 0) { if(test[7] == '/' && test[8] == 0) goto FORBIDDEN; // protect listing cgi-bin/ - sendCgi(url, prequest, urlArgs, body, length, cookie); + sendCgi(url, prequest, length, cookie, content_type); } else { if (prequest != request_GET) sendHeaders(HTTP_NOT_IMPLEMENTED); @@ -1506,7 +1767,7 @@ FORBIDDEN: /* protect listing /cgi-bin */ config->ContentLength = sb.st_size; config->last_mod = sb.st_mtime; } - sendFile(test, buf); + sendFile(test); #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY /* unset if non inetd looped */ config->ContentLength = -1; @@ -1526,10 +1787,25 @@ FORBIDDEN: /* protect listing /cgi-bin */ if (config->debugHttpd) fprintf(stderr, "closing socket\n"); # endif # ifdef CONFIG_FEATURE_HTTPD_CGI - free(body); free(cookie); + free(content_type); + free(config->referer); +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + free(config->remoteuser); +#endif # endif shutdown(a_c_w, SHUT_WR); + + /* Properly wait for remote to closed */ + FD_ZERO (&s_fd) ; + FD_SET (a_c_w, &s_fd) ; + + do { + tv.tv_sec = 2 ; + tv.tv_usec = 0 ; + retval = select (a_c_w + 1, &s_fd, NULL, NULL, &tv); + } while (retval > 0 && (read (a_c_w, buf, sizeof (config->buf)) > 0)); + shutdown(a_c_r, SHUT_RD); close(config->accepted_socket); #endif /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */ @@ -1562,13 +1838,12 @@ static int miniHttpd(int server) while (1) { readfd = portfd; - /* Now wait INDEFINATELY on the set of sockets! */ + /* Now wait INDEFINITELY on the set of sockets! */ if (select(server + 1, &readfd, 0, 0, 0) > 0) { if (FD_ISSET(server, &readfd)) { int on; struct sockaddr_in fromAddr; - unsigned int addr; socklen_t fromAddrLen = sizeof(fromAddr); int s = accept(server, (struct sockaddr *)&fromAddr, &fromAddrLen); @@ -1577,19 +1852,22 @@ static int miniHttpd(int server) continue; } config->accepted_socket = s; - addr = ntohl(fromAddr.sin_addr.s_addr); - sprintf(config->rmt_ip, "%u.%u.%u.%u", - (unsigned char)(addr >> 24), - (unsigned char)(addr >> 16), - (unsigned char)(addr >> 8), - addr & 0xff); + 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 (config->debugHttpd) { bb_error_msg("connection from IP=%s, port %u\n", - config->rmt_ip, config->port); + 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, (void *)&on, sizeof (on)); @@ -1618,15 +1896,16 @@ static int miniHttpd(void) { struct sockaddr_in fromAddrLen; socklen_t sinlen = sizeof (struct sockaddr_in); - unsigned int addr; getpeername (0, (struct sockaddr *)&fromAddrLen, &sinlen); - addr = ntohl(fromAddrLen.sin_addr.s_addr); - sprintf(config->rmt_ip, "%u.%u.%u.%u", - (unsigned char)(addr >> 24), - (unsigned char)(addr >> 16), - (unsigned char)(addr >> 8), - addr & 0xff); + 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; @@ -1639,31 +1918,80 @@ 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); - parse_conf(default_path_httpd_conf, - sig == SIGHUP ? SIGNALED_PARSE : FIRST_PARSE); } #endif + +static const char httpd_opts[]="c:d:h:" +#ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR + "e:" +#endif +#define OPT_INC_1 ENABLE_FEATURE_HTTPD_ENCODE_URL_STR + +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + "r:" +#endif +#define OPT_INC_2 ENABLE_FEATURE_HTTPD_BASIC_AUTH + +#ifdef CONFIG_FEATURE_HTTPD_AUTH_MD5 + "m:" +#endif +#define OPT_INC_3 ENABLE_FEATURE_HTTPD_AUTH_MD5 + +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + "p:v" +#endif +#ifdef CONFIG_FEATURE_HTTPD_SETUID + "u:" +#endif + ; + +#define OPT_CONFIG_FILE (1<<0) /* c */ +#define OPT_DECODE_URL (1<<1) /* d */ +#define OPT_HOME_HTTPD (1<<2) /* h */ +#define OPT_ENCODE_URL (1<<(2+OPT_INC_1)) /* e */ +#define OPT_REALM (1<<(2+OPT_INC_1+OPT_INC_2)) /* r */ +#define OPT_MD5 (1<<(2+OPT_INC_1+OPT_INC_2+OPT_INC_3)) /* m */ +#define OPT_PORT (1<<(3+OPT_INC_1+OPT_INC_2+OPT_INC_3)) /* p */ +#define OPT_DEBUG (1<<(4+OPT_INC_1+OPT_INC_2+OPT_INC_3)) /* v */ +#define OPT_SETUID (1<<(5+OPT_INC_1+OPT_INC_2+OPT_INC_3)) /* u */ + + #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; #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 config->realm = "Web Server Authentication"; @@ -1675,71 +2003,57 @@ int httpd_main(int argc, char *argv[]) config->ContentLength = -1; - /* check if user supplied a port number */ - for (;;) { - int c = getopt( argc, argv, "c:d:h:" -#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY - "p:v" -#endif + opt = bb_getopt_ulflags(argc, argv, httpd_opts, + &(config->configFile), &url_for_decode, &home_httpd #ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR - "e:" + , &url_for_encode #endif #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH - "r:" + , &(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 - "u:" + , &s_uid #endif - ); - if (c == EOF) break; - switch (c) { - case 'c': - config->configFile = optarg; - break; - case 'h': - home_httpd = optarg; - break; -#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY - case 'v': - config->debugHttpd = 1; - break; - case 'p': - config->port = atoi(optarg); - if(config->port <= 0 || config->port > 0xffff) - bb_error_msg_and_die("invalid %s for -p", optarg); - break; #endif - case 'd': - printf("%s", decodeString(optarg, 1)); + ); + + if(opt & OPT_DECODE_URL) { + printf("%s", decodeString(url_for_decode, 1)); return 0; + } #ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR - case 'e': - printf("%s", encodeString(optarg)); + if(opt & OPT_ENCODE_URL) { + printf("%s", encodeString(url_for_encode)); return 0; + } #endif -#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH - case 'r': - config->realm = optarg; - break; +#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 - case 'u': - { + if(opt & OPT_SETUID) { char *e; - uid = strtol(optarg, &e, 0); + uid = strtol(s_uid, &e, 0); if(*e != '\0') { /* not integer */ - uid = my_getpwnam(optarg); + uid = bb_xgetpwnam(s_uid); } } - break; #endif - default: - bb_error_msg("%s", httpdVersion); - bb_show_usage(); - } - } +#endif if(chdir(home_httpd)) { bb_perror_msg_and_die("can`t chdir to %s", home_httpd); @@ -1747,13 +2061,25 @@ int httpd_main(int argc, char *argv[]) #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY server = openServer(); # ifdef CONFIG_FEATURE_HTTPD_SETUID - /* drop privilegies */ + /* drop privileges */ if(uid > 0) setuid(uid); # endif -# ifdef CONFIG_FEATURE_HTTPD_CGI - addEnvPort("SERVER"); +#endif + +#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 CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP