"Fossies" - the Fresh Open Source Software archive 
Member "httpauth-0.9.5/daemon/bd.c" of archive httpauth-0.9.5.tar.gz:
/*
* HttpAuth
*
* Copyright (C) 2004 Stefan Walter
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
/* Ideas and Guidance from Apache 2.0's 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"
#include "stringx.h"
static unsigned char g_digest_secret[DIGEST_SECRET_LEN];
/* -------------------------------------------------------------------------------
* Defaults and Constants
*/
enum {
RECORD_TYPE_BASIC,
RECORD_TYPE_DIGEST
};
/* Kept by the us for validating the client */
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)
{
bd_record_t *rec = val;
if (!rec)
return;
str_array_free (rec->groups);
free (rec);
}
static int
have_cached_basic (bd_context_t* ctx, unsigned char* key)
{
bd_record_t *rec;
ASSERT (ctx && key);
ha_lock(NULL);
rec = (bd_record_t*)hsh_get (ctx->cache, key);
if (rec && rec->type != RECORD_TYPE_BASIC)
rec = NULL;
else
hsh_touch (ctx->cache, key);
ha_unlock(NULL);
return rec != NULL;
}
static int
add_cached_basic (bd_context_t *ctx, unsigned char *key, char **groups)
{
bd_record_t *rec;
int r;
ASSERT (ctx && key);
rec = (bd_record_t*)malloc (sizeof (*rec));
if (!rec) {
str_array_free (groups);
ha_memerr (NULL);
return HA_CRITERROR;
}
memset (rec, 0, sizeof (*rec));
rec->type = RECORD_TYPE_BASIC;
rec->groups = groups;
ha_lock (NULL);
while (hsh_count (ctx->cache) >= ctx->cache_max)
hsh_bump (ctx->cache);
r = hsh_set (ctx->cache, key, rec);
ha_unlock (NULL);
if (!r) {
free_hash_object (NULL, rec);
ha_memerr (NULL);
return HA_CRITERROR;
}
return HA_OK;
}
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;
}
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;
ASSERT (nonce && user);
if(!rec) {
str_array_free (groups);
ha_memerr(NULL);
return HA_CRITERROR;
}
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);
/* We take ownership of groups */
rec->groups = groups;
rec->nc = dg->server_nc;
md5_string (rec->userhash, user);
ha_lock (NULL);
while (hsh_count (ctx->cache) >= ctx->cache_max)
hsh_bump (ctx->cache);
r = hsh_set (ctx->cache, nonce, rec);
ha_unlock (NULL);
if (!r) {
free_hash_object (NULL, rec);
ha_memerr (NULL);
return HA_CRITERROR;
}
return HA_OK;
}
static int
include_group_headers (bd_context_t *ctx, ha_request_t *rq, unsigned char *key)
{
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)
{
basic_header_t basic;
char **groups = NULL;
int ret = HA_FALSE;
ASSERT (header && rq);
if ((ret = basic_parse (header, rq->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 (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);
}
ASSERT (ctx->f_validate_basic);
ret = ctx->f_validate_basic (rq, basic.user, basic.password, &groups);
/*
* 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);
finally:
if (ret == HA_OK) {
rq->resp_code = HA_SERVER_OK;
rq->resp_detail = basic.user;
include_group_headers (ctx, rq, basic.key);
}
return ret;
}
static int do_digest_challenge(ha_request_t* rq, bd_context_t* ctx, int stale)
{
const char* nonce_str;
const char* header;
ASSERT(ctx && rq);
#ifdef _DEBUG
if(rq->context->digest_debugnonce)
{
nonce_str = rq->context->digest_debugnonce;
ha_messagex(rq, LOG_WARNING, "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(rq->buf, nonce, DIGEST_NONCE_LEN);
if(!nonce_str)
return HA_CRITERROR;
}
/* Now generate a message to send */
header = digest_challenge(rq->buf, nonce_str, rq->context->realm,
rq->digest_domain, stale);
if(!header)
return HA_CRITERROR;
/* And append it nicely */
rq->resp_code = HA_SERVER_DECLINE;
ha_addheader(rq, "WWW-Authenticate", header);
ha_messagex(rq, LOG_DEBUG, "created digest challenge with nonce: %s", nonce_str);
return HA_OK;
}
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;
const char *t;
char **groups = NULL;
time_t expiry;
int ret = HA_FALSE;
int stale = 0;
int r, cached;
ASSERT (ctx && header && rq);
/* 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 (!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
#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");
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, &stale);
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 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;
}
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;
const char* t;
ASSERT(rq && user && str);
ASSERT(ctx->f_escape_value);
/* This starts a new block to join */
ha_bufcpy(rq->buf, "");
while(str[0])
{
t = strchr(str, '%');
if(!t)
{
ha_bufjoin(rq->buf);
ha_bufcpy(rq->buf, str);
break;
}
ha_bufjoin(rq->buf);
ha_bufncpy(rq->buf, str, t - str);
t++;
switch(t[0])
{
case 'u':
ha_bufjoin(rq->buf);
(ctx->f_escape_value)(rq, rq->buf, user);
t++;
break;
case 'r':
ha_bufjoin(rq->buf);
(ctx->f_escape_value)(rq, rq->buf, rq->context->realm);
t++;
break;
case '%':
ha_bufjoin(rq->buf);
ha_bufcpy(rq->buf, "%");
t++;
break;
};
str = t;
}
return ha_bufdata(rq->buf);
}
/* -------------------------------------------------------------------------------
* Handler Functions
*/
int bd_init(ha_context_t* context)
{
/* Global initialization */
if(!context)
{
ha_messagex(NULL, LOG_DEBUG, "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(NULL, LOG_ERR, "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_memerr(NULL);
return HA_CRITERROR;
}
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;
}
void bd_destroy(ha_context_t* context)
{
bd_context_t* ctx;
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(NULL, LOG_INFO, "uninitialized handler");
}
int bd_process(ha_request_t* rq)
{
bd_context_t* ctx = (bd_context_t*)rq->context->ctx_data;
time_t t = time(NULL);
const char* header = NULL;
int ret, r;
ASSERT(rq);
ASSERT(rq->req_args[AUTH_ARG_METHOD]);
ASSERT(rq->req_args[AUTH_ARG_URI]);
ha_lock(NULL);
/* Purge out stale connection stuff. */
r = hsh_purge(ctx->cache, t - rq->context->cache_timeout);
ha_unlock(NULL);
if(r > 0)
ha_messagex(rq, LOG_DEBUG, "purged cache records: %d", r);
/* We use this below to detect whether to send a default response */
rq->resp_code = -1;
/* Check the headers and see if we got a response thingy */
if(rq->context->allowed_types & HA_TYPE_DIGEST)
{
header = ha_getheader(rq, "Authorization", HA_PREFIX_DIGEST);
if(header)
{
ha_messagex(rq, LOG_DEBUG, "processing digest auth header");
ret = do_digest_response(rq, ctx, header);
if(ret < 0)
return ret;
}
}
/* Or a basic authentication */
if(!header && rq->context->allowed_types & HA_TYPE_BASIC)
{
header = ha_getheader(rq, "Authorization", HA_PREFIX_BASIC);
if(header)
{
ha_messagex(rq, LOG_DEBUG, "processing basic auth header");
ret = do_basic_response(rq, ctx, header);
if(ret < 0)
return ret;
}
}
/* Send a default response if that's what we need */
if(rq->resp_code == -1)
{
rq->resp_code = HA_SERVER_DECLINE;
if(rq->context->allowed_types & HA_TYPE_BASIC)
{
ha_bufmcat(rq->buf, "BASIC realm=\"", rq->context->realm , "\"", NULL);
if(CHECK_RBUF(rq))
return HA_CRITERROR;
ha_addheader(rq, "WWW-Authenticate", ha_bufdata(rq->buf));
ha_messagex(rq, LOG_DEBUG, "sent basic auth request");
ret = HA_OK;
}
if(rq->context->allowed_types & HA_TYPE_DIGEST)
{
ret = do_digest_challenge(rq, ctx, 0);
if(ret < 0)
return ret;
}
}
return ret;
}