uhttpd: add basic auth infrastructure
authorJo-Philipp Wich <jow@openwrt.org>
Sat, 20 Mar 2010 23:51:51 +0000 (23:51 +0000)
committerJo-Philipp Wich <jow@openwrt.org>
Sat, 20 Mar 2010 23:51:51 +0000 (23:51 +0000)
contrib/package/uhttpd/src/uhttpd-cgi.c
contrib/package/uhttpd/src/uhttpd-file.c
contrib/package/uhttpd/src/uhttpd-utils.c
contrib/package/uhttpd/src/uhttpd-utils.h
contrib/package/uhttpd/src/uhttpd.c
contrib/package/uhttpd/src/uhttpd.h

index a89dffa612c15c2a0231e6807910089eec96ba76..9e9e964102e12816f34b07fb5b879da9e3c4787c 100644 (file)
@@ -248,6 +248,10 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf
                                /* request url */
                                setenv("REQUEST_URI", req->url, 1);
 
+                               /* remote user */
+                               if( req->realm )
+                                       setenv("REMOTE_USER", req->realm->user, 1);
+
                                /* request message headers */
                                foreach_header(i, req->headers)
                                {
index 63c2d7e4fdee91cfb5d09eb7636074f71a8aff72..e8ea1746493b784d087ee497e3d16a466d669698 100644 (file)
@@ -16,8 +16,8 @@
  *  limitations under the License.
  */
 
-#define _XOPEN_SOURCE 500      /* strptime() ... */
-#define _BSD_SOURCE                    /* scandir() ... */
+#define _XOPEN_SOURCE 500      /* strptime() */
+#define _BSD_SOURCE                    /* scandir(), timegm() */
 
 #include "uhttpd.h"
 #include "uhttpd-utils.h"
index 12d68ff8622747068145f353bb5749341d183f5c..19918da16620c975e397fbfa0b7521c23f19c62c 100644 (file)
@@ -16,6 +16,9 @@
  *  limitations under the License.
  */
 
+#define _XOPEN_SOURCE 500      /* crypt() */
+#define _BSD_SOURCE                    /* strcasecmp(), strncasecmp() */
+
 #include "uhttpd.h"
 #include "uhttpd-utils.h"
 
@@ -304,50 +307,52 @@ int uh_urlencode(char *buf, int blen, const char *src, int slen)
        return len;
 }
 
-int uh_path_normalize(char *buf, int blen, const char *src, int slen)
+int uh_b64decode(char *buf, int blen, const unsigned char *src, int slen)
 {
-       int i, skip;
+       int i = 0;
        int len = 0;
 
-       for( i = 0, skip = 1; (i <= slen) && (src[i] != 0); i++ )
-       {
-               /* collapse multiple "/" into one */
-               if( src[i] == '/' )
-               {
-                       /* collapse "/../" to "/" */
-                       if( ((i+2) <= slen) && (src[i+1] == '.') && (src[i+2] == '.') &&
-                               (((i+3) > slen) || (src[i+3] == '/'))
-                       ) {
-                               i += 2;
-                               continue;
-                       }
+       unsigned int cin  = 0;
+       unsigned int cout = 0;
 
-                       /* collapse "/./" to "/" */
-                       else if( ((i+1) <= slen) && (src[i+1] == '.') &&
-                           (((i+2) > slen) || (src[i+2] == '/'))
-                       ) {
-                               i += 1;
-                               continue;
-                       }
 
-                       /* skip repeating "/" */
-                       else if( skip )
-                       {
-                               continue;
-                       }
+       for( i = 0; (i <= slen) && (src[i] != 0); i++ )
+       {
+               cin = src[i];
+
+               if( (cin >= '0') && (cin <= '9') )
+                       cin = cin - '0' + 52;
+               else if( (cin >= 'A') && (cin <= 'Z') )
+                       cin = cin - 'A';
+               else if( (cin >= 'a') && (cin <= 'z') )
+                       cin = cin - 'a' + 26;
+               else if( cin == '+' )
+                       cin = 62;
+               else if( cin == '/' )
+                       cin = 63;
+               else if( cin == '=' )
+                       cin = 0;
+               else
+                       continue;
 
-                       skip++;
-               }
+               cout = (cout << 6) | cin;
 
-               /* finally a harmless char */
-               else
+               if( (i % 4) == 3 )
                {
-                       skip = 0;
+                       if( (len + 3) < blen )
+                       {
+                               buf[len++] = (char)(cout >> 16);
+                               buf[len++] = (char)(cout >> 8);
+                               buf[len++] = (char)(cout);
+                       }
+                       else
+                       {
+                               break;
+                       }
                }
-
-               buf[len++] = src[i];
        }
 
+       buf[len++] = 0;
        return len;
 }
 
@@ -372,7 +377,8 @@ struct path_info * uh_path_lookup(struct client *cl, const char *url)
        memset(&p, 0, sizeof(p));
 
        /* copy docroot */
-       memcpy(buffer, docroot, sizeof(buffer));
+       memcpy(buffer, docroot,
+               min(strlen(docroot), sizeof(buffer) - 1));
 
        /* separate query string from url */
        if( (pathptr = strchr(url, '?')) != NULL )
@@ -473,6 +479,170 @@ struct path_info * uh_path_lookup(struct client *cl, const char *url)
 }
 
 
+static char uh_realms[UH_LIMIT_AUTHREALMS * sizeof(struct auth_realm)] = { 0 };
+static int uh_realm_count = 0;
+
+struct auth_realm * uh_auth_add(
+       char *path, char *realm, char *user, char *pass
+) {
+       struct auth_realm *new = NULL;
+       struct passwd *pwd;
+       struct spwd *spwd;
+
+       if( uh_realm_count < UH_LIMIT_AUTHREALMS )
+       {
+               new = (struct auth_realm *)
+                       &uh_realms[uh_realm_count * sizeof(struct auth_realm)];
+
+               memset(new, 0, sizeof(struct auth_realm));
+
+               memcpy(new->realm, realm,
+                       min(strlen(realm), sizeof(new->realm) - 1));
+
+               memcpy(new->path, path,
+                       min(strlen(path), sizeof(new->path) - 1));
+
+               memcpy(new->user, user,
+                       min(strlen(user), sizeof(new->user) - 1));
+
+               /* given password refers to a passwd entry */
+               if( (strlen(pass) > 3) && !strncmp(pass, "$p$", 3) )
+               {
+                       /* try to resolve shadow entry */
+                       if( ((spwd = getspnam(&pass[3])) != NULL) && spwd->sp_pwdp )
+                       {
+                               memcpy(new->pass, spwd->sp_pwdp,
+                                       min(strlen(spwd->sp_pwdp), sizeof(new->pass) - 1));
+                       }
+
+                       /* try to resolve passwd entry */
+                       else if( ((pwd = getpwnam(&pass[3])) != NULL) && pwd->pw_passwd &&
+                               (pwd->pw_passwd[0] != '!') && (pwd->pw_passwd[0] != 0)
+                       ) {
+                               memcpy(new->pass, pwd->pw_passwd,
+                                       min(strlen(pwd->pw_passwd), sizeof(new->pass) - 1));
+                       }                       
+               }
+
+               /* ordinary pwd */
+               else
+               {
+                       memcpy(new->pass, pass,
+                               min(strlen(pass), sizeof(new->pass) - 1));
+               }
+
+               uh_realm_count++;
+       }
+
+       return new;
+}
+
+int uh_auth_check(
+       struct client *cl, struct http_request *req, struct path_info *pi
+) {
+       int i, plen, rlen, protected;
+       char buffer[UH_LIMIT_MSGHEAD];
+       char *user = NULL;
+       char *pass = NULL;
+
+       struct auth_realm *realm = NULL;
+
+       plen = strlen(pi->name);
+       protected = 0;
+
+       /* check whether at least one realm covers the requested url */
+       for( i = 0; i < uh_realm_count; i++ )
+       {
+               realm = (struct auth_realm *)
+                       &uh_realms[i * sizeof(struct auth_realm)];
+
+               rlen = strlen(realm->path);
+
+               if( (plen >= rlen) && !strncasecmp(pi->name, realm->path, rlen) )
+               {
+                       req->realm = realm;
+                       protected = 1;
+                       break;
+               }
+       }
+
+       /* requested resource is covered by a realm */
+       if( protected )
+       {
+               /* try to get client auth info */
+               foreach_header(i, req->headers)
+               {
+                       if( !strcasecmp(req->headers[i], "Authorization") &&
+                               (strlen(req->headers[i+1]) > 6) &&
+                               !strncasecmp(req->headers[i+1], "Basic ", 6)
+                       ) {
+                               memset(buffer, 0, sizeof(buffer));
+                               uh_b64decode(buffer, sizeof(buffer) - 1,
+                                       (unsigned char *) &req->headers[i+1][6],
+                                       strlen(req->headers[i+1]) - 6);
+
+                               if( (pass = strchr(buffer, ':')) != NULL )
+                               {
+                                       user = buffer;
+                                       *pass++ = 0;
+                               }
+
+                               break;
+                       }
+               }
+
+               /* have client auth */
+               if( user && pass )
+               {
+                       /* find matching realm */
+                       for( i = 0, realm = NULL; i < uh_realm_count; i++ )
+                       {
+                               realm = (struct auth_realm *)
+                                       &uh_realms[i * sizeof(struct auth_realm)];
+
+                               rlen = strlen(realm->path);
+
+                               if( (plen >= rlen) &&
+                                   !strncasecmp(pi->name, realm->path, rlen) &&
+                                   !strcmp(user, realm->user)
+                               ) {
+                                       req->realm = realm;
+                                       break;
+                               }
+
+                               realm = NULL;
+                       }
+
+                       /* found a realm matching the username */
+                       if( realm )
+                       {
+                               /* is a crypt passwd */
+                               if( realm->pass[0] == '$' )
+                                       pass = crypt(pass, realm->pass);
+
+                               /* check user pass */
+                               if( !strcmp(pass, realm->pass) )
+                                       return 1;
+                       }
+               }
+
+               /* 401 */
+               uh_http_sendf(cl, NULL,
+                       "HTTP/%.1f 401 Authorization Required\r\n"
+                       "WWW-Authenticate: Basic realm=\"%s\"\r\n"
+                       "Content-Type: text/plain\r\n"
+                       "Content-Length: 23\r\n\r\n"
+                       "Authorization Required\n",
+                               req->version, realm ? realm->realm : ""
+               );
+
+               return 0;
+       }
+
+       return 1;
+}
+
+
 static char uh_listeners[UH_LIMIT_LISTENERS * sizeof(struct listener)] = { 0 };
 static char uh_clients[UH_LIMIT_CLIENTS * sizeof(struct client)] = { 0 };
 
index 7d4bcf6f80ad93617fb7546dc62b266f36fcf923..62731dcc114d290870c37f31699250a8b3f880d2 100644 (file)
@@ -20,6 +20,8 @@
 
 #include <stdarg.h>
 #include <fcntl.h>
+#include <pwd.h>
+#include <shadow.h>
 #include <sys/stat.h>
 
 #define min(x, y) (((x) < (y)) ? (x) : (y))
@@ -51,7 +53,10 @@ int uh_tcp_send(struct client *cl, const char *buf, int len);
 int uh_tcp_peek(struct client *cl, char *buf, int len);
 int uh_tcp_recv(struct client *cl, char *buf, int len);
 
-int uh_http_sendhf(struct client *cl, int code, const char *summary, const char *fmt, ...);
+int uh_http_sendhf(
+       struct client *cl, int code, const char *summary,
+       const char *fmt, ...
+);
 
 #define uh_http_response(cl, code, message) \
        uh_http_sendhf(cl, code, message, message)
@@ -71,7 +76,17 @@ int uh_http_send(
 
 int uh_urldecode(char *buf, int blen, const char *src, int slen);
 int uh_urlencode(char *buf, int blen, const char *src, int slen);
-int uh_path_normalize(char *buf, int blen, const char *src, int slen);
+int uh_b64decode(char *buf, int blen, const unsigned char *src, int slen);
+
+
+struct auth_realm * uh_auth_add(
+       char *path, char *realm, char *user, char *pass
+);
+
+int uh_auth_check(
+       struct client *cl, struct http_request *req, struct path_info *pi
+);
+
 
 struct path_info * uh_path_lookup(struct client *cl, const char *url);
 
index ccbc9efc6b2a1b6d662ce845db0740b6456f87c7..7a5f42ad61d6dc97c935db91b4896eccfc063346 100644 (file)
@@ -268,7 +268,7 @@ static struct http_request * uh_http_header_parse(struct client *cl, char *buffe
 
 static struct http_request * uh_http_header_recv(struct client *cl)
 {
-       char buffer[UH_LIMIT_MSGHEAD];
+       static char buffer[UH_LIMIT_MSGHEAD];
        char *bufptr = &buffer[0];
        char *idxptr = NULL;
 
@@ -670,7 +670,7 @@ int main (int argc, char **argv)
                                        {
 #ifdef HAVE_LUA
                                                /* Lua request? */
-                                               if( strstr(req->url, conf.lua_prefix) == req->url )
+                                               if( L && strstr(req->url, conf.lua_prefix) == req->url )
                                                {
                                                        uh_lua_request(cl, req, L);
                                                }
@@ -679,15 +679,19 @@ int main (int argc, char **argv)
                                                /* dispatch request */
                                                if( (pin = uh_path_lookup(cl, req->url)) != NULL )
                                                {
-#ifdef HAVE_CGI
-                                                       if( strstr(pin->name, conf.cgi_prefix) == pin->name )
+                                                       /* auth ok? */
+                                                       if( uh_auth_check(cl, req, pin) )
                                                        {
-                                                               uh_cgi_request(cl, req, pin);
-                                                       }
-                                                       else
+#ifdef HAVE_CGI
+                                                               if( strstr(pin->name, conf.cgi_prefix) == pin->name )
+                                                               {
+                                                                       uh_cgi_request(cl, req, pin);
+                                                               }
+                                                               else
 #endif
-                                                       {
-                                                               uh_file_request(cl, req, pin);
+                                                               {
+                                                                       uh_file_request(cl, req, pin);
+                                                               }
                                                        }
                                                }
 
index aa9186782d46b37ed1c669a25a83dd0c1e7cc2e9..c6212109b48e36f80c9750a183b569e4c355eac3 100644 (file)
@@ -42,6 +42,7 @@
 
 #define UH_LIMIT_LISTENERS     16
 #define UH_LIMIT_CLIENTS       64
+#define UH_LIMIT_AUTHREALMS    8
 
 #define UH_HTTP_MSG_GET                0
 #define UH_HTTP_MSG_HEAD       1
@@ -85,11 +86,19 @@ struct client {
 #endif
 };
 
+struct auth_realm {
+       char path[PATH_MAX];
+       char realm[128];
+       char user[32];
+       char pass[128];
+};
+
 struct http_request {
        int     method;
        float version;
        char *url;
        char *headers[UH_LIMIT_HEADERS];
+       struct auth_realm *realm;
 };
 
 struct http_response {