From 0cb3f6098d959479a96c26a92d91becc2110b30d Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Wed, 11 Jun 2008 21:48:27 +0000 Subject: Support getting groups from the server and limiting access based on LDAP groups. See #112 --- daemon/bd.c | 679 ++++++++++++++++++++++++++++++++---------------------------- 1 file changed, 364 insertions(+), 315 deletions(-) (limited to 'daemon/bd.c') diff --git a/daemon/bd.c b/daemon/bd.c index e11a56b..eb0bec0 100644 --- a/daemon/bd.c +++ b/daemon/bd.c @@ -29,6 +29,7 @@ #include "basic.h" #include "md5.h" #include "bd.h" +#include "stringx.h" static unsigned char g_digest_secret[DIGEST_SECRET_LEN]; @@ -38,193 +39,277 @@ static unsigned char g_digest_secret[DIGEST_SECRET_LEN]; * Defaults and Constants */ -#define BASIC_ESTABLISHED (void*)1 +enum { + RECORD_TYPE_BASIC, + RECORD_TYPE_DIGEST +}; /* Kept by the us for validating the client */ -typedef struct digest_record -{ - unsigned char nonce[DIGEST_NONCE_LEN]; - unsigned char userhash[MD5_LEN]; - unsigned char ha1[MD5_LEN]; - unsigned int nc; -} -digest_record_t; +typedef struct bd_record { + int type; + + /* Used for Digest */ + unsigned char nonce[DIGEST_NONCE_LEN]; + unsigned char userhash[MD5_LEN]; + unsigned char ha1[MD5_LEN]; + unsigned int nc; + + /* Used for both */ + char **groups; +} bd_record_t; /* ------------------------------------------------------------------------------- * Internal Functions */ -static void free_hash_object(void* arg, void* val) +static void +free_hash_object (void *arg, void *val) { - if(val && val != BASIC_ESTABLISHED) - free(val); + bd_record_t *rec = val; + if (!rec) + return; + str_array_free (rec->groups); + free (rec); } -static digest_record_t* get_cached_digest(bd_context_t* ctx, ha_context_t* c, - unsigned char* nonce) +static int +have_cached_basic (bd_context_t* ctx, unsigned char* key) { - digest_record_t* rec; + bd_record_t *rec; - ASSERT(ctx && c && nonce); + ASSERT (ctx && key); - if(c->cache_max == 0) - return NULL; + ha_lock(NULL); - ha_lock(NULL); - - rec = (digest_record_t*)hsh_get(ctx->cache, nonce); + rec = (bd_record_t*)hsh_get (ctx->cache, key); + if (rec && rec->type != RECORD_TYPE_BASIC) + rec = NULL; + else + hsh_touch (ctx->cache, key); - /* Just in case it's a basic :) */ - if(rec && rec != BASIC_ESTABLISHED) - hsh_rem(ctx->cache, nonce); - - ha_unlock(NULL); + ha_unlock(NULL); - ASSERT(!rec || memcmp(nonce, rec->nonce, DIGEST_NONCE_LEN) == 0); - return rec; + return rec != NULL; } -static int have_cached_basic(bd_context_t* ctx, unsigned char* key) +static int +add_cached_basic (bd_context_t *ctx, unsigned char *key, char **groups) { - int ret = 0; + bd_record_t *rec; + int r; - ASSERT(ctx && key); + ASSERT (ctx && key); - ha_lock(NULL); + rec = (bd_record_t*)malloc (sizeof (*rec)); + if (!rec) { + str_array_free (groups); + ha_memerr (NULL); + return HA_CRITERROR; + } - ret = (hsh_get(ctx->cache, key) == BASIC_ESTABLISHED); + memset (rec, 0, sizeof (*rec)); + rec->type = RECORD_TYPE_BASIC; + rec->groups = groups; - ha_unlock(NULL); + ha_lock (NULL); - return ret; -} + while (hsh_count (ctx->cache) >= ctx->cache_max) + hsh_bump (ctx->cache); -static int save_cached_digest(bd_context_t* ctx, ha_context_t* c, - digest_record_t* rec) -{ - int r; + r = hsh_set (ctx->cache, key, rec); - ASSERT(ctx && rec); + ha_unlock (NULL); - if(c->cache_max == 0) - { - free_hash_object(NULL, rec); - return HA_FALSE; - } + if (!r) { + free_hash_object (NULL, rec); + ha_memerr (NULL); + return HA_CRITERROR; + } - ha_lock(NULL); + return HA_OK; +} - while(hsh_count(ctx->cache) >= c->cache_max) - hsh_bump(ctx->cache); +static int +prepare_digest_from_cached (bd_context_t *ctx, digest_context_t *dg, + ha_request_t *rq, unsigned char *nonce) +{ + bd_record_t *rec; + int ret; + + ASSERT (dg && rq && nonce); + + ha_lock (NULL); + + rec = (bd_record_t*)hsh_get (ctx->cache, nonce); + if (rec && rec->type == RECORD_TYPE_DIGEST) { + ASSERT (memcmp (nonce, rec->nonce, DIGEST_NONCE_LEN) == 0); + memcpy (dg->server_userhash, rec->userhash, sizeof (dg->server_userhash)); + memcpy (dg->server_ha1, rec->ha1, sizeof (dg->server_ha1)); + dg->server_nc = ++(rec->nc); + hsh_touch (ctx->cache, nonce); + ret = HA_OK; + } else { + memset (dg->server_userhash, 0, sizeof (dg->server_userhash)); + memset (dg->server_ha1, 0, sizeof (dg->server_ha1)); + dg->server_nc = 1; + ret = HA_FALSE; + } + + ha_unlock (NULL); + + dg->server_uri = rq->req_args[AUTH_ARG_URI]; + dg->server_method = rq->req_args[AUTH_ARG_METHOD]; + + return ret; +} - r = hsh_set(ctx->cache, rec->nonce, rec); +static int +add_digest_rec (bd_context_t *ctx, unsigned char *nonce, + const char *user, digest_context_t *dg, char **groups) +{ + bd_record_t* rec = (bd_record_t*)malloc (sizeof (*rec)); + int r; - ha_unlock(NULL); + ASSERT (nonce && user); - if(!r) - { + if(!rec) { + str_array_free (groups); ha_memerr(NULL); - return HA_CRITERROR; - } + return HA_CRITERROR; + } - return HA_OK; -} + memset (rec, 0, sizeof (*rec)); + rec->type = RECORD_TYPE_DIGEST; + memcpy (rec->nonce, nonce, DIGEST_NONCE_LEN); + memcpy (rec->ha1, dg->server_ha1, MD5_LEN); -static int add_cached_basic(bd_context_t* ctx, ha_context_t* c, - unsigned char* key) -{ - int r; + /* We take ownership of groups */ + rec->groups = groups; + rec->nc = dg->server_nc; - ASSERT(ctx && c && key); + md5_string (rec->userhash, user); - if(c->cache_max == 0) - return HA_FALSE; + ha_lock (NULL); - ha_lock(NULL); + while (hsh_count (ctx->cache) >= ctx->cache_max) + hsh_bump (ctx->cache); - while(hsh_count(ctx->cache) >= c->cache_max) - hsh_bump(ctx->cache); + r = hsh_set (ctx->cache, nonce, rec); - r = hsh_set(ctx->cache, key, BASIC_ESTABLISHED); + ha_unlock (NULL); - ha_unlock(NULL); - - if(!r) - { - ha_memerr(NULL); - return HA_CRITERROR; - } + if (!r) { + free_hash_object (NULL, rec); + ha_memerr (NULL); + return HA_CRITERROR; + } - return HA_OK; + return HA_OK; } -digest_record_t* make_digest_rec(unsigned char* nonce, const char* user) +static int +include_group_headers (bd_context_t *ctx, ha_request_t *rq, unsigned char *key) { - digest_record_t* rec = (digest_record_t*)malloc(sizeof(*rec)); - - ASSERT(nonce && user); - - if(!rec) - { - ha_memerr(NULL); - return NULL; - } - - memset(rec, 0, sizeof(*rec)); - memcpy(rec->nonce, nonce, DIGEST_NONCE_LEN); - - md5_string(rec->userhash, user); - return rec; + bd_record_t *rec; + int have, all = 0; + char *header; + char **ug; + char **rg; + + if (!rq->requested_groups || !rq->requested_groups[0]) + return HA_OK; + + ha_lock (NULL); + + /* This starts a new block to join */ + ha_bufcpy (rq->buf, ""); + + rec = hsh_get (ctx->cache, key); + if (rec) { + for (ug = rec->groups; ug && *ug; ++ug) { + have = all; + for (rg = rq->requested_groups; rg && *rg && !have; ++rg) { + if (strcasecmp (*rg, "*") == 0) { + have = all = 1; + break; + } else if (strcasecmp (*rg, *ug) == 0) { + have = 1; + break; + } + } + + if (have) { + ha_bufjoin (rq->buf); + ha_bufcpy (rq->buf, "\""); + ha_bufjoin (rq->buf); + digest_escape (rq->buf, *ug); + ha_bufjoin (rq->buf); + ha_bufcpy (rq->buf, "\" "); + } + } + } + + ha_unlock (NULL); + + header = ha_bufdata (rq->buf); + if (!header) + return HA_CRITERROR; + + ha_addheader (rq, "X-HttpAuth-Groups", header); + return HA_OK; } -static int do_basic_response(ha_request_t* rq, bd_context_t* ctx, const char* header) +static int +do_basic_response (ha_request_t* rq, bd_context_t* ctx, const char* header) { - basic_header_t basic; - int ret = HA_FALSE; + basic_header_t basic; + char **groups = NULL; + int ret = HA_FALSE; - ASSERT(header && rq); + ASSERT (header && rq); - if((ret = basic_parse(header, rq->buf, &basic)) < 0) - return ret; + if ((ret = basic_parse (header, rq->buf, &basic)) < 0) + return ret; - /* Past this point we don't return directly */ + /* 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(rq, LOG_NOTICE, "validated basic user against cache: %s", - basic.user); - RETURN(HA_OK); - } + /* Check and see if this connection is in the cache */ + if (have_cached_basic (ctx, basic.key)) { + ha_messagex (rq, LOG_NOTICE, "validated basic user against cache: %s", + basic.user); + RETURN (HA_OK); + } - /* If we have a user name and password */ - if(!basic.user || !basic.user[0] || - !basic.password || !basic.password[0]) - { - ha_messagex(rq, LOG_NOTICE, "no valid basic auth info"); - RETURN(HA_FALSE); - } + /* If we have a user name and password */ + if(!basic.user || !basic.user[0] || + !basic.password || !basic.password[0]) { + ha_messagex(rq, LOG_NOTICE, "no valid basic auth info"); + RETURN(HA_FALSE); + } - ASSERT(ctx->f_validate_basic); - ret = ctx->f_validate_basic(rq, basic.user, basic.password); + ASSERT (ctx->f_validate_basic); + ret = ctx->f_validate_basic (rq, basic.user, basic.password, &groups); -finally: + /* + * We put this connection into the successful connections. + * This takes ownership of groups. + */ + if (ret == HA_OK) + ret = add_cached_basic (ctx, basic.key, groups); - if(ret == HA_OK) - { - rq->resp_code = HA_SERVER_OK; - rq->resp_detail = basic.user; +finally: - /* We put this connection into the successful connections */ - ret = add_cached_basic(ctx, rq->context, basic.key); - } + if (ret == HA_OK) { + rq->resp_code = HA_SERVER_OK; + rq->resp_detail = basic.user; + include_group_headers (ctx, rq, basic.key); + } - return ret; + return ret; } static int do_digest_challenge(ha_request_t* rq, bd_context_t* ctx, int stale) { - unsigned char nonce[DIGEST_NONCE_LEN]; const char* nonce_str; const char* header; @@ -264,208 +349,172 @@ static int do_digest_challenge(ha_request_t* rq, bd_context_t* ctx, int stale) static int do_digest_response(ha_request_t* rq, bd_context_t* ctx, const char* header) { - unsigned char nonce[DIGEST_NONCE_LEN]; - digest_context_t dg; - digest_record_t* rec = NULL; - const char* t; - time_t expiry; - int ret = HA_FALSE; - int stale = 0; - int r; + unsigned char nonce[DIGEST_NONCE_LEN]; + digest_context_t dg; + const char *t; + char **groups = NULL; + time_t expiry; + int ret = HA_FALSE; + int stale = 0; + int r, cached; - ASSERT(ctx && header && rq); + ASSERT (ctx && header && rq); - /* We use this below to send a default response */ - rq->resp_code = -1; + /* We use this below to send a default response */ + rq->resp_code = -1; - if((r = digest_parse(header, rq->buf, &(dg.client))) < 0) - return r; + if ((r = digest_parse (header, rq->buf, &(dg.client))) < 0) + return r; - if(!dg.client.username) - { - ha_messagex(rq, LOG_WARNING, "digest response contains no user name"); - RETURN(HA_FALSE); - } + if (!dg.client.username) { + ha_messagex(rq, LOG_WARNING, "digest response contains no user name"); + RETURN(HA_FALSE); + } #ifdef _DEBUG - if(rq->context->digest_debugnonce) - { - if(dg.client.nonce && strcmp(dg.client.nonce, rq->context->digest_debugnonce) != 0) - { - ha_messagex(rq, LOG_WARNING, "digest response contains invalid nonce"); - RETURN(HA_FALSE); - } - - /* Do a rough hash into the real nonce, for use as a key */ - md5_string(nonce, rq->context->digest_debugnonce); - - /* Debug nonce's never expire */ - expiry = time(NULL); - } - else + if (rq->context->digest_debugnonce) { + if (dg.client.nonce && strcmp (dg.client.nonce, rq->context->digest_debugnonce) != 0) { + ha_messagex(rq, LOG_WARNING, "digest response contains invalid nonce"); + RETURN(HA_FALSE); + } + + /* Do a rough hash into the real nonce, for use as a key */ + md5_string (nonce, rq->context->digest_debugnonce); + + /* Debug nonce's never expire */ + expiry = time (NULL); + } else #endif - { - /* Parse out the nonce from that header */ - memset(nonce, 0, DIGEST_NONCE_LEN); - - if(dg.client.nonce) - { - size_t len = DIGEST_NONCE_LEN; - void* d = ha_bufdechex(rq->buf, dg.client.nonce, &len); - - if(d && len == DIGEST_NONCE_LEN) - memcpy(nonce, d, DIGEST_NONCE_LEN); - } - - r = digest_checknonce(nonce, g_digest_secret, &expiry); - if(r != HA_OK) - { - if(r == HA_FALSE) - ha_messagex(rq, LOG_WARNING, "digest response contains invalid nonce"); - - RETURN(r); - } - - /* Check to see if we're stale */ - if((expiry + rq->context->cache_timeout) <= time(NULL)) - { - ha_messagex(rq, LOG_INFO, "nonce expired, sending stale challenge: %s", - dg.client.username); - - stale = 1; - RETURN(HA_FALSE); - } - } - - /* See if we have this one from before. */ - rec = get_cached_digest(ctx, rq->context, nonce); - - /* - * Fill in the required fields. - */ - dg.server_nc = rec ? ++(rec->nc) : 0; /* Note bumping up nc */ - dg.server_uri = rq->req_args[AUTH_ARG_URI]; - dg.server_method = rq->req_args[AUTH_ARG_METHOD]; - - /* Check the majority of the fields */ - ret = digest_pre_check(&dg, rq->context, rq->buf); - - if(ret != HA_OK) - { - if(ret == HA_BADREQ) - { - ret = HA_FALSE; - rq->resp_code = HA_SERVER_BADREQ; - } - - RETURN(ret); - } - - /* - * If this is the first instance then we pass off to our derived - * handler for validation and completion of the ha1. This completes - * the authentication, and leaves us the ha1 caching. - */ - if(!rec) - { - ha_messagex(rq, LOG_INFO, "no record in cache, creating one: %s", dg.client.username); - - /* - * If we're valid but don't have a record in the - * cache then complete the record properly. - */ - - rec = make_digest_rec(nonce, dg.client.username); - if(!rec) - RETURN(HA_CRITERROR); - - - ASSERT(ctx->f_validate_digest); - r = ctx->f_validate_digest(rq, dg.client.username, &dg); - if(r != HA_OK) - RETURN(r); - - /* Save away pertinent information when successful*/ - memcpy(rec->ha1, dg.ha1, MD5_LEN); - } - - /* We had a record so ... */ - else - { - /* Bump up the ncount */ - rec->nc++; - - /* Check the user name */ - if(md5_strcmp(rec->userhash, dg.client.username) != 0) - { - ha_messagex(NULL, LOG_ERR, "digest response contains invalid username"); - RETURN(HA_FALSE); - } - - /* And do the validation ourselves */ - memcpy(dg.ha1, rec->ha1, MD5_LEN); - - ret = digest_complete_check(&dg, rq->context, rq->buf); - - if(ret != HA_OK) - { - if(ret == HA_BADREQ) - { - ret = HA_FALSE; - rq->resp_code = HA_SERVER_BADREQ; - } - - if(ret == HA_FALSE) - ha_messagex(NULL, LOG_WARNING, "digest re-authentication failed for user: %s", - dg.client.username); - - RETURN(ret); - } - } - - - rq->resp_code = HA_SERVER_OK; - rq->resp_detail = dg.client.username; - - /* Figure out if we need a new nonce */ - if((expiry + (rq->context->cache_timeout - - (rq->context->cache_timeout / 8))) < time(NULL)) - { - ha_messagex(rq, LOG_INFO, "nonce almost expired, creating new one: %s", - dg.client.username); - - digest_makenonce(nonce, g_digest_secret, NULL); - stale = 1; - } - - t = digest_respond(&dg, rq->buf, stale ? nonce : NULL); - if(!t) - RETURN(HA_CRITERROR); - - if(t[0]) - ha_addheader(rq, "Authentication-Info", t); - - ha_messagex(rq, LOG_NOTICE, "validated digest user: %s", dg.client.username); - - /* Put the connection into the cache */ - if((ret = save_cached_digest(ctx, rq->context, rec)) == HA_OK) - rec = NULL; + { + /* Parse out the nonce from that header */ + memset (nonce, 0, DIGEST_NONCE_LEN); + + if (dg.client.nonce) { + size_t len = DIGEST_NONCE_LEN; + void* d = ha_bufdechex (rq->buf, dg.client.nonce, &len); + + if (d && len == DIGEST_NONCE_LEN) + memcpy (nonce, d, DIGEST_NONCE_LEN); + } + + r = digest_checknonce (nonce, g_digest_secret, &expiry); + if (r != HA_OK) { + if (r == HA_FALSE) + ha_messagex (rq, LOG_WARNING, "digest response contains invalid nonce"); + stale = 1; + RETURN (r); + } + + /* Check to see if we're stale */ + if ((expiry + rq->context->cache_timeout) <= time (NULL)) { + ha_messagex(rq, LOG_INFO, "nonce expired, sending stale challenge: %s", + dg.client.username); + + stale = 1; + RETURN (HA_FALSE); + } + } + + /* + * Fill in all the required fields from any cached response, + * and check the user name if cached. Otherwise initializes + * to default values. + */ + cached = (prepare_digest_from_cached (ctx, &dg, rq, nonce) == HA_OK); + + /* Check the majority of the fields */ + ret = digest_pre_check (&dg, rq->context, rq->buf); + if (ret != HA_OK) { + if (ret == HA_BADREQ) { + ret = HA_FALSE; + rq->resp_code = HA_SERVER_BADREQ; + } + + RETURN (ret); + } + + /* + * If this is the first instance then we pass off to our derived + * handler for validation and completion of the ha1. This completes + * the authentication, and leaves us the ha1 caching. + */ + if (!cached) { + ha_messagex (rq, LOG_INFO, "no record in cache, creating one: %s", + dg.client.username); + + /* + * If we're valid but don't have a record in the + * cache then complete the record properly. + */ + + ASSERT (ctx->f_validate_digest); + r = ctx->f_validate_digest (rq, dg.client.username, &dg, &groups); + if (r != HA_OK) + RETURN (r); + + /* Add the digest record to the cache */ + r = add_digest_rec (ctx, nonce, dg.client.username, &dg, groups); + if (r != HA_OK) + RETURN (r); + + /* We had a record so ... */ + } else { + + /* Check the user name */ + if (md5_strcmp (dg.server_userhash, dg.client.username) != 0) { + ha_messagex(NULL, LOG_ERR, "digest response contains invalid username"); + RETURN(HA_FALSE); + } + + /* And do the validation ourselves */ + ret = digest_complete_check (&dg, rq->context, rq->buf); + + if (ret != HA_OK) { + if (ret == HA_BADREQ) { + ret = HA_FALSE; + rq->resp_code = HA_SERVER_BADREQ; + } + + if (ret == HA_FALSE) + ha_messagex (NULL, LOG_WARNING, "digest re-authentication failed for user: %s", + dg.client.username); + + RETURN (ret); + } + } + + rq->resp_code = HA_SERVER_OK; + rq->resp_detail = dg.client.username; + include_group_headers (ctx, rq, nonce); + + /* Figure out if we need a new nonce */ + if ((expiry + (rq->context->cache_timeout - + (rq->context->cache_timeout / 8))) < time (NULL)) { + ha_messagex (rq, LOG_INFO, "nonce almost expired, creating new one: %s", + dg.client.username); + + digest_makenonce (nonce, g_digest_secret, NULL); + stale = 1; + } + + t = digest_respond (&dg, rq->buf, stale ? nonce : NULL); + if (!t) + RETURN (HA_CRITERROR); + + if (t[0]) + ha_addheader(rq, "Authentication-Info", t); + + ha_messagex(rq, LOG_NOTICE, "validated digest user: %s", dg.client.username); 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(ret == HA_FALSE && rq->resp_code == -1) - return do_digest_challenge(rq, ctx, stale); + /* If nobody above responded then challenge the client again */ + if (ret == HA_FALSE && rq->resp_code == -1) + return do_digest_challenge (rq, ctx, stale); - return ret; + return ret; } - - const char* bd_substitute(const ha_request_t* rq, const char* user, const char* str) { bd_context_t* ctx = (bd_context_t*)rq->context->ctx_data; @@ -559,6 +608,7 @@ int bd_init(ha_context_t* context) htc.f_freeval = free_hash_object; htc.arg = NULL; hsh_set_table_calls(ctx->cache, &htc); + ctx->cache_max = context->cache_max; } return HA_OK; @@ -567,7 +617,6 @@ int bd_init(ha_context_t* context) void bd_destroy(ha_context_t* context) { bd_context_t* ctx; - int i; if(!context) return; -- cgit v1.2.3