From b0e50bbeb12e6247dd52dfd9e44c62f558c8a3a0 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Mon, 9 Aug 2004 16:57:46 +0000 Subject: - Seperated core Basic/Digest functionality into a base handler --- daemon/simple.c | 865 +++++++++++++------------------------------------------- 1 file changed, 199 insertions(+), 666 deletions(-) (limited to 'daemon/simple.c') diff --git a/daemon/simple.c b/daemon/simple.c index ee4fafa..d570a25 100644 --- a/daemon/simple.c +++ b/daemon/simple.c @@ -5,19 +5,14 @@ #include "usuals.h" #include "httpauthd.h" #include "defaults.h" -#include "basic.h" -#include "digest.h" #include "hash.h" +#include "bd.h" #include "md5.h" -#include +#include #include -#include - -unsigned char g_simple_secret[DIGEST_SECRET_LEN]; #define SIMPLE_MAXLINE 256 -#define BASIC_ESTABLISHED (void*)1 /* ------------------------------------------------------------------------------- * Structures @@ -25,736 +20,274 @@ unsigned char g_simple_secret[DIGEST_SECRET_LEN]; typedef struct simple_context { - /* Settings ----------------------------------------------------------- */ - const char* filename; /* The file name with the user names */ + /* Base Handler ------------------------------------------------------ */ + bd_context_t bd; - /* Context ----------------------------------------------------------- */ - hsh_t* cache; /* Some cached records or basic */ + /* Settings ---------------------------------------------------------- */ + const char* filename; /* The file name with the user names */ } simple_context_t; +/* Forward declarations for callbacks */ +static int complete_digest(const ha_request_t* req, const char* user, unsigned char* ha1); +static int validate_basic(const ha_request_t* req, const char* user, const char* password); + +/* The defaults for the context */ +static const simple_context_t simple_defaults = +{ + BD_CALLBACKS(complete_digest, validate_basic), + NULL /* filename */ +}; /* ------------------------------------------------------------------------------- * Internal Functions */ -static void free_hash_object(void* arg, void* val) -{ - if(val && val != BASIC_ESTABLISHED) - free(val); -} - -static digest_record_t* get_cached_digest(simple_context_t* ctx, ha_context_t* c, - unsigned char* nonce) -{ - digest_record_t* rec; - - ASSERT(ctx && c && nonce); - - if(c->cache_max == 0) - return NULL; - - ha_lock(NULL); - - rec = (digest_record_t*)hsh_get(ctx->cache, nonce); - - /* Just in case it's a basic :) */ - if(rec && rec != BASIC_ESTABLISHED) - hsh_rem(ctx->cache, nonce); - - ha_unlock(NULL); - - ASSERT(!rec || memcmp(nonce, rec->nonce, DIGEST_NONCE_LEN) == 0); - return rec; -} - -static int have_cached_basic(simple_context_t* ctx, unsigned char* key) -{ - int ret = 0; - - ASSERT(ctx && key); - - ha_lock(NULL); - - ret = (hsh_get(ctx->cache, key) == BASIC_ESTABLISHED); - - ha_unlock(NULL); - - return ret; -} - -static int save_cached_digest(simple_context_t* ctx, ha_context_t* c, - digest_record_t* rec) +static int complete_digest(const ha_request_t* req, const char* user, unsigned char* ha1) { - int r; - - ASSERT(ctx && c && rec); - - if(c->cache_max == 0) - { - free_hash_object(NULL, rec); - return HA_FALSE; - } - - ha_lock(NULL); - - while(hsh_count(ctx->cache) >= c->cache_max) - hsh_bump(ctx->cache); - - r = hsh_set(ctx->cache, rec->nonce, rec); - - ha_unlock(NULL); - - if(!r) - { - free_hash_object(NULL, rec); - ha_messagex(LOG_CRIT, "out of memory"); - return HA_CRITERROR; - } - - return HA_OK; -} - -static int add_cached_basic(simple_context_t* ctx, ha_context_t* c, - unsigned char* key) -{ - int r; - - ASSERT(ctx && c && key); - - if(c->cache_max == 0) - return HA_FALSE; - - ha_lock(NULL); - - while(hsh_count(ctx->cache) >= c->cache_max) - hsh_bump(ctx->cache); - - r = hsh_set(ctx->cache, key, BASIC_ESTABLISHED); - - ha_unlock(NULL); - - if(!r) - { - ha_messagex(LOG_CRIT, "out of memory"); - return HA_CRITERROR; - } - - return HA_OK; -} - -static int complete_digest_ha1(simple_context_t* ctx, digest_record_t* rec, - const ha_request_t* req, const char* user) -{ - FILE* f; - char* t; - char* t2; - size_t len; - char line[SIMPLE_MAXLINE]; - int ret = HA_FALSE; - - ASSERT(ctx && rec && req && user && user[0]); - ha_messagex(LOG_DEBUG, "searching password file for user's ha1: %s", user); - - f = fopen(ctx->filename, "r"); - if(!f) - { - ha_message(LOG_ERR, "simple: can't open file for basic auth: %s", ctx->filename); - return HA_FAILED; - } - - /* - * Note: There should be no returns or jumps between - * this point and the closing of the file below. - */ - - /* Now look through the whole file */ - while(!feof(f)) - { - fgets(line, SIMPLE_MAXLINE, f); - - if(ferror(f)) - { - ha_message(LOG_ERR, "simple: error reading basic password file"); - ret = HA_FAILED; - break; - } - - t = strchr(line, ':'); - if(t) - { - /* Split the line */ - *t = 0; - t++; - - /* Check the user */ - if(strcmp(line, user) == 0) - { - /* Otherwise it might be a digest type ha1. */ - t2 = strchr(t, ':'); - if(t2) - { - *t2 = 0; - t2++; - - /* Check the realm */ - if(strcmp(t, req->context->realm) == 0) - { - len = MD5_LEN; - - /* Now try antd decode the ha1 */ - t = ha_bufdechex(req->buf, t2, &len); - if(t && len == MD5_LEN) - { - ha_messagex(LOG_DEBUG, "simple: found ha1 for user: %s", user); - memcpy(rec->ha1, t, MD5_LEN); - ret = HA_OK; - break; - } - } - } - - if(!t2 || ret != HA_OK) - ha_messagex(LOG_WARNING, "simple: user '%s' found in file, but password not in digest format", user); - } - } - } - - fclose(f); - - if(ha_buferr(req->buf)) - return HA_CRITERROR; - - return ret; -} - -static int validate_user_password(simple_context_t* ctx, const ha_request_t* req, - const char* user, const char* clearpw) -{ - FILE* f; - char line[SIMPLE_MAXLINE]; - unsigned char ha1[MD5_LEN]; - char* t; - char* t2; - size_t len; - int ret = HA_FALSE; - - ASSERT(ctx && req); - ASSERT(user && user[0] && clearpw); - ha_messagex(LOG_DEBUG, "simple: validating user against password file: %s", user); - - f = fopen(ctx->filename, "r"); - if(!f) - { - ha_message(LOG_ERR, "simple: can't open file for basic auth: %s", ctx->filename); - return HA_FAILED; - } - - digest_makeha1(ha1, user, req->context->realm, clearpw); - - /* - * Note: There should be no returns or jumps between - * this point and the closing of the file below. - */ - - /* Now look through the whole file */ - while(!feof(f)) - { - fgets(line, SIMPLE_MAXLINE, f); - - if(ferror(f)) + simple_context_t* ctx = (simple_context_t*)req->context->ctx_data; + FILE* f; + char* t; + char* t2; + size_t len; + char line[SIMPLE_MAXLINE]; + int ret = HA_FALSE; + + ASSERT(ctx && req && user && user[0]); + ha_messagex(LOG_DEBUG, "searching password file for user's ha1: %s", user); + + f = fopen(ctx->filename, "r"); + if(!f) { - ha_message(LOG_ERR, "simple: error reading basic password file"); - ret = HA_FAILED; - break; + ha_message(LOG_ERR, "simple: can't open file for basic auth: %s", ctx->filename); + return HA_FAILED; } - /* Take white space off end of line */ - trim_end(line); + /* + * Note: There should be no returns or jumps between + * this point and the closing of the file below. + */ - t = strchr(line, ':'); - if(t) + /* Now look through the whole file */ + while(!feof(f)) { - /* Split the line */ - *t = 0; - t++; - - /* Check the user */ - if(strcmp(line, user) == 0) - { - /* Not sure if crypt is thread safe so we lock */ - ha_lock(NULL); + fgets(line, SIMPLE_MAXLINE, f); - /* Check the password */ - t2 = crypt(clearpw, t); - - ha_unlock(NULL); - - if(strcmp(crypt(clearpw, t), t) == 0) + if(ferror(f)) { - ha_messagex(LOG_DEBUG, "simple: found valid crypt password for user: %s", user); - ret = HA_OK; - break; + ha_message(LOG_ERR, "simple: error reading basic password file"); + ret = HA_FAILED; + break; } - /* Otherwise it might be a digest type ha1. */ - t2 = strchr(t, ':'); - if(t2) + t = strchr(line, ':'); + if(t) { - *t2 = 0; - t2++; - - /* Check the realm */ - if(strcmp(t, req->context->realm) == 0) - { - len = MD5_LEN; + /* Split the line */ + *t = 0; + t++; - /* Now try antd decode the ha1 */ - t = ha_bufdechex(req->buf, t2, &len); - if(t && len == MD5_LEN && memcmp(ha1, t, MD5_LEN) == 0) + /* Check the user */ + if(strcmp(line, user) == 0) { - ha_messagex(LOG_DEBUG, "simple: found valid ha1 for user: %s", user); - ret = HA_OK; - break; + /* Otherwise it might be a digest type ha1. */ + t2 = strchr(t, ':'); + if(t2) + { + *t2 = 0; + t2++; + + /* Check the realm */ + if(strcmp(t, req->context->realm) == 0) + { + len = MD5_LEN; + + /* Now try and decode the ha1 */ + t = ha_bufdechex(req->buf, t2, &len); + if(t && len == MD5_LEN) + { + ha_messagex(LOG_DEBUG, "simple: found ha1 for user: %s", user); + memcpy(ha1, t, MD5_LEN); + ret = HA_OK; + break; + } + } + } + + if(!t2 || ret != HA_OK) + ha_messagex(LOG_WARNING, "simple: user '%s' found in file, but password not in digest format", user); } - } } - - if(ha_buferr(req->buf)) - break; - } } - } - - fclose(f); - - if(ha_buferr(req->buf)) - return HA_CRITERROR; - - return ret; -} - -static int simple_basic_response(simple_context_t* ctx, const char* header, - const ha_request_t* req, ha_response_t* resp) -{ - basic_header_t basic; - int ret = HA_FALSE; - int found = 0; - int r; - - ASSERT(header && req && resp); - - if((r = basic_parse(header, req->buf, &basic)) < 0) - return r; - - /* Past this point we don't return directly */ - - /* Check and see if this connection is in the cache */ - if(have_cached_basic(ctx, basic.key)) - { - ha_messagex(LOG_NOTICE, "simple: validated basic user against cache: %s", basic.user); - found = 1; - ret = HA_OK; - goto finally; - } - - /* If we have a user name and password */ - if(!basic.user || !basic.user[0] || - !basic.password || !basic.password[0]) - goto finally; - - - ret = validate_user_password(ctx, req, basic.user, basic.password); - - if(ret == HA_OK) - ha_messagex(LOG_NOTICE, "simple: validated basic user against file: %s", basic.user); - - else - ha_messagex(LOG_WARNING, "simple: basic authentication failed for user: %s", basic.user); -finally: + fclose(f); - if(ret == HA_OK) - { - resp->code = HA_SERVER_OK; - resp->detail = basic.user; - - /* We put this connection into the successful connections */ - ret = add_cached_basic(ctx, req->context, basic.key); - } - - return ret; -} - -static int simple_digest_challenge(simple_context_t* ctx, const ha_request_t* req, - ha_response_t* resp, int stale) -{ - const char* nonce_str; - const char* header; - - ASSERT(ctx && req && resp); - - /* Generate an nonce */ - -#ifdef _DEBUG - if(req->context->digest_debugnonce) - { - nonce_str = req->context->digest_debugnonce; - ha_messagex(LOG_WARNING, "simple: using debug nonce. security non-existant."); - } - else -#endif - { - unsigned char nonce[DIGEST_NONCE_LEN]; - digest_makenonce(nonce, g_simple_secret, NULL); - - nonce_str = ha_bufenchex(req->buf, nonce, DIGEST_NONCE_LEN); - if(!nonce_str) - return HA_CRITERROR; - } - - - /* Now generate a message to send */ - header = digest_challenge(req->buf, nonce_str, req->context->realm, - req->digest_domain, stale); - - if(!header) - return HA_CRITERROR; - - /* And append it nicely */ - resp->code = HA_SERVER_DECLINE; - ha_addheader(resp, "WWW-Authenticate", header); + if(ha_buferr(req->buf)) + return HA_CRITERROR; - ha_messagex(LOG_DEBUG, "simple: created digest challenge with nonce: %s", nonce_str); - return HA_OK; + return ret; } -static int simple_digest_response(simple_context_t* ctx, const char* header, - const ha_request_t* req, ha_response_t* resp) +static int validate_basic(const ha_request_t* req, const char* user, const char* password) { - unsigned char nonce[DIGEST_NONCE_LEN]; - digest_header_t dg; - digest_record_t* rec = NULL; - const char* t; - time_t expiry; - int ret = HA_FALSE; - int stale = 0; - int r; - - ASSERT(ctx && header && req && resp); - - /* We use this below to send a default response */ - resp->code = -1; - - if((r = digest_parse(header, req->buf, &dg, nonce)) < 0) - return r; - -#ifdef _DEBUG - if(req->context->digest_debugnonce) - { - if(dg.nonce && strcmp(dg.nonce, req->context->digest_debugnonce) != 0) - { - ret = HA_FALSE; - ha_messagex(LOG_WARNING, "simple: digest response contains invalid nonce"); - goto finally; - } - - /* Do a rough hash into the real nonce, for use as a key */ - md5_string(nonce, req->context->digest_debugnonce); - - /* Debug nonce's never expire */ - expiry = time(NULL); - } - else -#endif - { - r = digest_checknonce(nonce, g_simple_secret, &expiry); - if(r != HA_OK) + simple_context_t* ctx = (simple_context_t*)req->context->ctx_data; + FILE* f; + char line[SIMPLE_MAXLINE]; + unsigned char ha1[MD5_LEN]; + char* t; + char* t2; + size_t len; + int ret = HA_FALSE; + + ASSERT(ctx && req); + ASSERT(user && user[0] && password); + ha_messagex(LOG_DEBUG, "simple: validating user against password file: %s", user); + + f = fopen(ctx->filename, "r"); + if(!f) { - if(r == HA_FALSE) - ha_messagex(LOG_WARNING, "simple: digest response contains invalid nonce"); - - ret = r; - goto finally; + ha_message(LOG_ERR, "simple: can't open file for basic auth: %s", ctx->filename); + return HA_FAILED; } - } - rec = get_cached_digest(ctx, req->context, nonce); - - /* Check to see if we're stale */ - if((expiry + req->context->cache_timeout) <= time(NULL)) - { - ha_messagex(LOG_INFO, "simple: nonce expired, sending stale challenge: %s", - dg.username); - - stale = 1; - goto finally; - } - - if(!rec) - { - ha_messagex(LOG_INFO, "simple: no record in cache, creating one: %s", dg.username); + digest_makeha1(ha1, user, req->context->realm, password); /* - * If we're valid but don't have a record in the - * cache then complete the record properly. + * Note: There should be no returns or jumps between + * this point and the closing of the file below. */ - rec = digest_makerec(nonce, dg.username); - if(!rec) + /* Now look through the whole file */ + while(!feof(f)) { - ret = HA_CRITERROR; - goto finally; - } + fgets(line, SIMPLE_MAXLINE, f); - r = complete_digest_ha1(ctx, rec, req, dg.username); - if(r != HA_OK) - { - ret = r; - goto finally; - } - } - - /* We had a record so ... */ - else - { - rec->nc++; - } - - ret = digest_check(&dg, rec, req->context, req->buf, - req->args[AUTH_ARG_METHOD], req->args[AUTH_ARG_URI]); - - if(ret == HA_BADREQ) - { - ret = HA_FALSE; - resp->code = HA_SERVER_BADREQ; - } - - else if(ret == HA_OK) - { - resp->code = HA_SERVER_OK; - resp->detail = dg.username; - - /* Figure out if we need a new nonce */ - if((expiry + (req->context->cache_timeout - - (req->context->cache_timeout / 8))) < time(NULL)) - { - ha_messagex(LOG_INFO, "simple: nonce almost expired, creating new one: %s", - dg.username); - - digest_makenonce(nonce, g_simple_secret, NULL); - stale = 1; - } - - t = digest_respond(req->buf, &dg, rec, stale ? nonce : NULL); - if(!t) - { - ret = HA_CRITERROR; - goto finally; - } - - if(t[0]) - ha_addheader(resp, "Authentication-Info", t); - - ha_messagex(LOG_NOTICE, "simple: validated digest user: %s", dg.username); + if(ferror(f)) + { + ha_message(LOG_ERR, "simple: error reading basic password file"); + ret = HA_FAILED; + break; + } - /* Put the connection into the cache */ - if((r = save_cached_digest(ctx, req->context, rec)) < 0) - ret = r; + /* Take white space off end of line */ + trim_end(line); - rec = NULL; - } + t = strchr(line, ':'); + if(t) + { + /* Split the line */ + *t = 0; + t++; -finally: + /* Check the user */ + if(strcmp(line, user) == 0) + { + /* Not sure if crypt is thread safe so we lock */ + ha_lock(NULL); + + /* Check the password */ + t2 = crypt(password, t); + + ha_unlock(NULL); + + if(strcmp(t2, t) == 0) + { + ha_messagex(LOG_DEBUG, "simple: found valid crypt password for user: %s", user); + ret = HA_OK; + break; + } + + /* Otherwise it might be a digest type ha1. */ + t2 = strchr(t, ':'); + if(t2) + { + *t2 = 0; + t2++; + + /* Check the realm */ + if(strcmp(t, req->context->realm) == 0) + { + len = MD5_LEN; + + /* Now try antd decode the ha1 */ + t = ha_bufdechex(req->buf, t2, &len); + if(t && len == MD5_LEN && memcmp(ha1, t, MD5_LEN) == 0) + { + ha_messagex(LOG_DEBUG, "simple: found valid ha1 for user: %s", user); + ret = HA_OK; + break; + } + } + } + + if(ha_buferr(req->buf)) + break; + } + } + } - /* If the record wasn't stored away then free it */ - if(rec) - free(rec); + fclose(f); - /* If nobody above responded then challenge the client again */ - if(resp->code == -1) - return simple_digest_challenge(ctx, req, resp, stale); + if(ha_buferr(req->buf)) + return HA_CRITERROR; - return ret; + return ret; } - /* ------------------------------------------------------------------------------- * Handler Functions */ int simple_config(ha_context_t* context, const char* name, const char* value) { - simple_context_t* ctx = (simple_context_t*)(context->ctx_data); - - ASSERT(name && name[0] && value && value[0]); - - if(strcmp(name, "passwordfile") == 0) - { - ctx->filename = value; - return HA_OK; - } - - return HA_FALSE; -} - -int simple_init(ha_context_t* context) -{ - /* Global initialization */ - if(!context) - { - ha_messagex(LOG_DEBUG, "simple: generating secret"); - return ha_genrandom(g_simple_secret, DIGEST_SECRET_LEN); - } - - /* Context specific initialization */ - else - { simple_context_t* ctx = (simple_context_t*)(context->ctx_data); - hsh_table_calls_t htc; - int fd; - - ASSERT(ctx); - - /* Make sure there are some types of authentication we can do */ - if(!(context->allowed_types & (HA_TYPE_BASIC | HA_TYPE_DIGEST))) - { - ha_messagex(LOG_ERR, "simple: module configured, but does not implement any " - "configured authentication type."); - return HA_FAILED; - } - - /* Check to make sure the file exists */ - if(!ctx->filename) - { - ha_messagex(LOG_ERR, "simple: configuration incomplete. " - "Must have a PasswordFile configured."); - return HA_FAILED; - } + ASSERT(name && name[0] && value && value[0]); - fd = open(ctx->filename, O_RDONLY); - if(fd == -1) + if(strcmp(name, "passwordfile") == 0) { - ha_message(LOG_ERR, "simple: can't open file for authentication: %s", ctx->filename); - return HA_FAILED; + ctx->filename = value; + return HA_OK; } - close(fd); - - ASSERT(!ctx->cache); - - /* The cache for digest records and basic */ - if(!(ctx->cache = hsh_create(MD5_LEN))) - { - ha_messagex(LOG_CRIT, "out of memory"); - return HA_CRITERROR; - } - - htc.f_freeval = free_hash_object; - htc.arg = NULL; - hsh_set_table_calls(ctx->cache, &htc); - - ha_messagex(LOG_INFO, "simple: initialized handler"); - } - - return HA_OK; -} - -void simple_destroy(ha_context_t* context) -{ - /* Per context destroy */ - if(context) - { - /* Note: We don't need to be thread safe here anymore */ - simple_context_t* ctx = (simple_context_t*)(context->ctx_data); - - if(ctx->cache) - hsh_free(ctx->cache); - - ha_messagex(LOG_INFO, "simple: uninitialized handler"); - } + return HA_FALSE; } -int simple_process(const ha_request_t* req, ha_response_t* resp) +int simple_init(ha_context_t* context) { - simple_context_t* ctx = (simple_context_t*)(req->context->ctx_data); - const char* header = NULL; - int ret = HA_FALSE; - int found = 0; - basic_header_t basic; - int r; - - ASSERT(req && resp); - ASSERT(req->args[AUTH_ARG_METHOD]); - ASSERT(req->args[AUTH_ARG_URI]); - - ha_lock(NULL); - - /* Purge the cache */ - r = hsh_purge(ctx->cache, time(NULL) - req->context->cache_timeout); + int r; - ha_unlock(NULL); + if((r = bd_init(context)) != HA_OK) + return r; - if(r > 0) - ha_messagex(LOG_DEBUG, "simple: purged cache records: %d", r); - - /* We use this below to detect whether to send a default response */ - resp->code = -1; - - - /* Check the headers and see if we got a response thingy */ - if(req->context->allowed_types & HA_TYPE_DIGEST) - { - header = ha_getheader(req, "Authorization", HA_PREFIX_DIGEST); - if(header) - { - ha_messagex(LOG_DEBUG, "simple: processing digest auth header"); - ret = simple_digest_response(ctx, header, req, resp); - if(ret < 0) - return ret; - } - } - - /* Or a basic authentication */ - if(!header && req->context->allowed_types & HA_TYPE_BASIC) - { - ha_messagex(LOG_DEBUG, "simple: processing basic auth header"); - header = ha_getheader(req, "Authorization", HA_PREFIX_BASIC); - if(header) + if(context) { - ret = simple_basic_response(ctx, header, req, resp); - if(ret < 0) - return ret; - } - } - + simple_context_t* ctx = (simple_context_t*)(context->ctx_data); + int fd; - /* Send a default response if that's what we need */ - if(resp->code == -1) - { - resp->code = HA_SERVER_DECLINE; + ASSERT(ctx); - if(req->context->allowed_types & HA_TYPE_BASIC) - { - ha_bufmcat(req->buf, "BASIC realm=\"", req->context->realm , "\"", NULL); + /* Check to make sure the file exists */ + if(!ctx->filename) + { + ha_messagex(LOG_ERR, "simple: configuration incomplete. " + "Must have a PasswordFile configured."); + return HA_FAILED; + } - if(ha_buferr(req->buf)) - return HA_CRITERROR; + fd = open(ctx->filename, O_RDONLY); + if(fd == -1) + { + ha_message(LOG_ERR, "simple: can't open file for authentication: %s", ctx->filename); + return HA_FAILED; + } - ha_addheader(resp, "WWW-Authenticate", ha_bufdata(req->buf)); - ha_messagex(LOG_DEBUG, "simple: sent basic auth request"); - } + close(fd); - if(req->context->allowed_types & HA_TYPE_DIGEST) - { - ret = simple_digest_challenge(ctx, req, resp, 0); - if(ret < 0) - return ret; + ha_messagex(LOG_INFO, "simple: initialized handler"); } - } - return ret; + return HA_OK; } @@ -766,9 +299,9 @@ ha_handler_t simple_handler = { "SIMPLE", /* The type */ simple_init, /* Initialization function */ - simple_destroy, /* Uninitialization routine */ + bd_destroy, /* Uninitialization routine */ simple_config, /* Config routine */ - simple_process, /* Processing routine */ + bd_process, /* Processing routine */ NULL, /* A default context */ sizeof(simple_context_t) /* Size of the context */ }; -- cgit v1.2.3