last_patch95 from vodz:
[oweals/busybox.git] / networking / httpd.c
index df99f1c8ba0db4605d3d3b6c94b2faa7fbaeca15..d58414b558328abf3372ea7b817bd7372d8a0069 100644 (file)
  *
  * 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 "&#60Hello&#32World&#62"
+ * 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           */
@@ -86,10 +122,10 @@ 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.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.
@@ -117,12 +153,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_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
@@ -132,6 +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_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
@@ -160,7 +199,7 @@ void bb_show_usage(void)
 
 /* 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
@@ -197,6 +236,7 @@ typedef struct
   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
@@ -325,7 +365,31 @@ static void free_config_lines(Htaccess **pprev)
 #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;
@@ -335,17 +399,19 @@ static void parse_conf(const char *path, int flag)
 #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
@@ -362,11 +428,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 +442,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 +456,32 @@ 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'
 #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));
@@ -468,12 +532,12 @@ static void parse_conf(const char *path, int flag)
                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) {
@@ -509,12 +573,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;
@@ -587,11 +650,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++; }
@@ -631,23 +695,22 @@ static void addEnv(const char *name_before_underline,
                        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)
 {
@@ -656,6 +719,7 @@ 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
@@ -681,9 +745,12 @@ static void addEnvCgi(const char *pargs)
 {
   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;
@@ -696,8 +763,14 @@ static void addEnvCgi(const char *pargs)
     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 */
 
@@ -1050,10 +1123,11 @@ static int sendCgi(const char *url,
            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);
                }
            }
       }
@@ -1255,14 +1329,44 @@ static int checkPerm(const char *path, const char *request)
            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;
 }
 
@@ -1285,7 +1389,7 @@ static int checkPermIP(const char *request)
     }
 
     /* 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 */
@@ -1648,22 +1752,73 @@ static void sighup_handler(int sig)
 }
 #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";
@@ -1675,71 +1830,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 = 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);