diff options
Diffstat (limited to 'daemon/bd.c')
-rw-r--r-- | daemon/bd.c | 511 |
1 files changed, 511 insertions, 0 deletions
diff --git a/daemon/bd.c b/daemon/bd.c new file mode 100644 index 0000000..916c453 --- /dev/null +++ b/daemon/bd.c @@ -0,0 +1,511 @@ + +/* TODO: Include attribution for ideas, and code from mod_auth_digest */ + +#include "usuals.h" +#include "httpauthd.h" +#include "hash.h" +#include "defaults.h" +#include "digest.h" +#include "basic.h" +#include "md5.h" +#include "bd.h" + +static unsigned char g_digest_secret[DIGEST_SECRET_LEN]; + +/* ------------------------------------------------------------------------------- + * Defaults and Constants + */ + +#define BASIC_ESTABLISHED (void*)1 + + +/* ------------------------------------------------------------------------------- + * 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(bd_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(bd_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(bd_context_t* ctx, ha_context_t* c, + digest_record_t* rec) +{ + int r; + + ASSERT(ctx && 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) + { + ha_messagex(LOG_CRIT, "out of memory"); + return HA_CRITERROR; + } + + return HA_OK; +} + +static int add_cached_basic(bd_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 do_basic_response(bd_context_t* ctx, const char* header, + const ha_request_t* req, ha_response_t* resp) +{ + basic_header_t basic; + int ret = HA_FALSE; + + ASSERT(header && resp && req); + + if((ret = basic_parse(header, req->buf, &basic)) < 0) + return ret; + + /* 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, "bd: validated basic user against cache: %s", + basic.user); + ret = HA_OK; + goto finally; + } + + /* If we have a user name and password */ + if(!basic.user || !basic.user[0] || + !basic.password || !basic.password[0]) + { + ha_messagex(LOG_NOTICE, "bd: no valid basic auth info"); + ret = HA_FALSE; + goto finally; + } + + ASSERT(ctx->f_validate_basic); + ret = ctx->f_validate_basic(req, basic.user, basic.password); + +finally: + + 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 do_digest_challenge(bd_context_t* ctx, const ha_request_t* req, + ha_response_t* resp, int stale) +{ + unsigned char nonce[DIGEST_NONCE_LEN]; + const char* nonce_str; + const char* header; + + ASSERT(ctx && resp && req); + +#ifdef _DEBUG + if(req->context->digest_debugnonce) + { + nonce_str = req->context->digest_debugnonce; + ha_messagex(LOG_WARNING, "bd: using debug nonce. security non-existant."); + } + else +#endif + { + unsigned char nonce[DIGEST_NONCE_LEN]; + digest_makenonce(nonce, g_digest_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); + + ha_messagex(LOG_DEBUG, "bd: created digest challenge with nonce: %s", nonce_str); + return HA_OK; +} + +static int do_digest_response(bd_context_t* ctx, const char* header, + const ha_request_t* req, ha_response_t* resp) +{ + 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, "bd: 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_digest_secret, &expiry); + if(r != HA_OK) + { + if(r == HA_FALSE) + ha_messagex(LOG_WARNING, "bd: digest response contains invalid nonce"); + + goto finally; + } + } + + 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, "bd: nonce expired, sending stale challenge: %s", + dg.username); + + stale = 1; + goto finally; + } + + if(!rec) + { + ha_messagex(LOG_INFO, "bd: no record in cache, creating one: %s", dg.username); + + /* + * If we're valid but don't have a record in the + * cache then complete the record properly. + */ + + rec = digest_makerec(nonce, dg.username); + if(!rec) + { + ret = HA_CRITERROR; + goto finally; + } + + ASSERT(ctx->f_complete_digest); + r = ctx->f_complete_digest(req, dg.username, rec->ha1); + 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, "bd: nonce almost expired, creating new one: %s", + dg.username); + + digest_makenonce(nonce, g_digest_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, "bd: validated digest user: %s", dg.username); + + /* Put the connection into the cache */ + if((r = save_cached_digest(ctx, req->context, rec)) < 0) + ret = r; + + rec = NULL; + } + +finally: + + /* If the record wasn't stored away then free it */ + if(rec) + free(rec); + + /* If nobody above responded then challenge the client again */ + if(resp->code == -1) + return do_digest_challenge(ctx, req, resp, stale); + + return ret; +} + + +/* ------------------------------------------------------------------------------- + * Handler Functions + */ + +int bd_init(ha_context_t* context) +{ + /* Global initialization */ + if(!context) + { + ha_messagex(LOG_DEBUG, "bd: generating secret"); + return ha_genrandom(g_digest_secret, DIGEST_SECRET_LEN); + } + + /* Context specific initialization */ + else + { + bd_context_t* ctx = (bd_context_t*)(context->ctx_data); + hsh_table_calls_t htc; + + 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, "bd: module configured, but does not implement any " + "configured authentication type."); + return HA_FAILED; + } + + /* 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, "ldap: initialized handler"); + } + + return HA_OK; +} + +void bd_destroy(ha_context_t* context) +{ + bd_context_t* ctx; + int i; + + if(!context) + return; + + /* Note: We don't need to be thread safe here anymore */ + ctx = (bd_context_t*)(context->ctx_data); + + ASSERT(ctx); + + if(ctx->cache) + hsh_free(ctx->cache); + + ha_messagex(LOG_INFO, "bd: uninitialized handler"); +} + +int bd_process(const ha_request_t* req, ha_response_t* resp) +{ + bd_context_t* ctx = (bd_context_t*)req->context->ctx_data; + time_t t = time(NULL); + const char* header = NULL; + int ret, r; + + ASSERT(req && resp); + ASSERT(req->args[AUTH_ARG_METHOD]); + ASSERT(req->args[AUTH_ARG_URI]); + + ha_lock(NULL); + + /* Purge out stale connection stuff. */ + r = hsh_purge(ctx->cache, t - req->context->cache_timeout); + + ha_unlock(NULL); + + if(r > 0) + ha_messagex(LOG_DEBUG, "ldap: 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, "ldap: processing digest auth header"); + ret = do_digest_response(ctx, header, req, resp); + if(ret < 0) + return ret; + } + } + + /* Or a basic authentication */ + if(!header && req->context->allowed_types & HA_TYPE_BASIC) + { + header = ha_getheader(req, "Authorization", HA_PREFIX_BASIC); + if(header) + { + ha_messagex(LOG_DEBUG, "bd: processing basic auth header"); + ret = do_basic_response(ctx, header, req, resp); + if(ret < 0) + return ret; + } + } + + + /* Send a default response if that's what we need */ + if(resp->code == -1) + { + resp->code = HA_SERVER_DECLINE; + + if(req->context->allowed_types & HA_TYPE_BASIC) + { + ha_bufmcat(req->buf, "BASIC realm=\"", req->context->realm , "\"", NULL); + + if(ha_buferr(req->buf)) + return HA_CRITERROR; + + ha_addheader(resp, "WWW-Authenticate", ha_bufdata(req->buf)); + ha_messagex(LOG_DEBUG, "bd: sent basic auth request"); + } + + if(req->context->allowed_types & HA_TYPE_DIGEST) + { + ret = do_digest_challenge(ctx, req, resp, 0); + if(ret < 0) + return ret; + } + } + + return ret; +} + |