*
* 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 "<Hello World>"` # 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:
+ *
+ * A:172.20. # Allow any address that begins with 172.20
+ * A:10.10. # Allow any address that begins with 10.10.
+ * A:10.20 # Allow any address that previous set and 10.200-209.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
+ * 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: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
+ *
+ * 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
+ * except that all previous IP config rules are discarded.
+ *
+ * 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 <stdio.h>
#include <ctype.h> /* for isspace */
#include "busybox.h"
-static const char httpdVersion[] = "busybox httpd/1.25 10-May-2003";
+static const char httpdVersion[] = "busybox httpd/1.28 22-Jun-2003";
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
// if something bad happens due to a malformed user request.
/* 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_SET_CGI_VARS_TO_ENV
#undef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR
#undef CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
#undef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
/* enable all features now */
#define CONFIG_FEATURE_HTTPD_BASIC_AUTH
+#define CONFIG_FEATURE_HTTPD_AUTH_MD5
#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
/* CGI environ size */
#ifdef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV
-#define ENVSIZE 50 /* set max 35 CGI_variable */
+#define ENVSIZE 70 /* set max CGI variable */
#else
#define ENVSIZE 15 /* minimal requires */
#endif
time_t last_mod;
Htaccess *ip_a_d; /* config allow/deny lines */
+ int flg_deny_all;
#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
Htaccess *auth; /* config user:password lines */
#endif
#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;
#endif
const char *cf = config->configFile;
- char buf[80];
+ char buf[160];
char *p0 = NULL;
char *c, *p;
- /* free previous setuped */
+ /* free previous ip setup if present */
free_config_lines(&config->ip_a_d);
+ config->flg_deny_all = 0;
+ /* 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
- ; /* syntax confuse */
+ ; /* appease compiler warnings if option is not set */
#ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
free_config_lines(&config->mime_a);
#endif
}
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;
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)) {
/* 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'
#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
- && *p0 != '/'
+ && *p0 != '/'
#endif
#ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
- && *p0 != '.'
+ && *p0 != '.'
#endif
- )
+ )
continue;
- if(*p0 == 'A' && *c == 0) {
- /* skip default A:* */
- 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));
free(p0);
#endif
if(*cf == 'A' || *cf == 'D') {
- if(*cf == 'D' && *c) {
+ if(*cf == 'D') {
/* Deny:form_IP move top */
cur->next = config->ip_a_d;
config->ip_a_d = cur;
} else {
- /* add to bottom current IP config line */
+ /* add to bottom A:form_IP config line */
Htaccess *prev_IP = config->ip_a_d;
if(prev_IP == NULL) {
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;
* $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++; }
const char *name_after_underline, const char *value)
{
char *s;
+ 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);
+ if(s) {
config->envp[config->envCount++] = s;
config->envp[config->envCount] = 0;
}
}
+#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)
{
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
{
char *args;
char *memargs;
+ char *namelist; /* space separated list of arg names */
if (pargs==0) return;
/* args are a list of name=value&name2=value2 sequences */
+ namelist = (char *) malloc(strlen(pargs));
+ if (namelist) namelist[0]=0;
memargs = args = strdup(pargs);
while (args && *args) {
const char *name = args;
if (args)
*args++ = 0;
addEnv("CGI", name, decodeString(value, 1));
+ if (*namelist) strcat(namelist, " ");
+ strcat(namelist, name);
}
free(memargs);
+ if (namelist) {
+ addEnv("CGI", "ARGLIST_", namelist);
+ free(namelist);
+ }
}
#endif /* CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV */
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.
+ execve(realpath_buff, argp, config->envp);
}
}
}
if(strncmp(p0, path, l) == 0 &&
(l == 1 || path[l] == '/' || path[l] == 0)) {
/* path match found. Check request */
+
+ /* for check next /path:user:password */
+ prev = p0;
+#ifdef CONFIG_FEATURE_HTTPD_AUTH_MD5
+ {
+ char *cipher;
+ char *pp;
+ char *u = strchr(request, ':');
+
+ if(u == NULL) {
+ /* bad request, ':' required */
+ continue;
+ }
+ 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)
+ return 1; /* Ok */
+ /* unauthorized */
+ continue;
+ }
+ }
+#endif
if (strcmp(p, request) == 0)
return 1; /* Ok */
- /* unauthorized, but check next /path:user:password */
- prev = p0;
+ /* unauthorized */
}
}
} /* for */
+ if(ipaddr)
+ return !config->flg_deny_all;
return prev == NULL;
}
}
/* if uncofigured, return 1 - access from all */
- return 1;
+ return !config->flg_deny_all;
}
#define checkPerm(null, request) checkPermIP(request)
#endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */
}
#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;
#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";
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 = my_getpwnam(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);