summaryrefslogtreecommitdiff
path: root/daemon/bd.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemon/bd.c')
-rw-r--r--daemon/bd.c511
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;
+}
+