From cbbe71752d7f9c6204ab0f16600fe7f10490f203 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Sat, 24 Apr 2004 22:38:50 +0000 Subject: Completed implementation of ldap/ntlm/simple handlers --- daemon/simple.c | 663 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 663 insertions(+) create mode 100644 daemon/simple.c (limited to 'daemon/simple.c') diff --git a/daemon/simple.c b/daemon/simple.c new file mode 100644 index 0000000..cd1b812 --- /dev/null +++ b/daemon/simple.c @@ -0,0 +1,663 @@ + +/* On some linux this is required to get crypt to show itself */ +#define _XOPEN_SOURCE + +#include "usuals.h" +#include "httpauthd.h" +#include "defaults.h" +#include "basic.h" +#include "digest.h" +#include "hash.h" +#include "md5.h" + +#include +#include +#include + +unsigned char g_simple_secret[DIGEST_SECRET_LEN]; + +#define SIMPLE_MAXLINE 256 +#define BASIC_ESTABLISHED (void*)1 + +/* ------------------------------------------------------------------------------- + * Structures + */ + +typedef struct simple_context +{ + const char* filename; /* The file name with the user names */ + const char* realm; /* The realm for basic authentication */ + const char* domains; /* Domains for which digest auth is valid */ + int cache_max; /* Maximum number of connections at once */ + + /* Context ----------------------------------------------------------- */ + hash_t* cache; /* Some cached records or basic */ +} +simple_context_t; + +/* The defaults for the context */ +static const simple_context_t simple_defaults = +{ + NULL, /* filename */ + NULL, /* realm */ + NULL, /* domains */ + 1000, /* cache_max */ + NULL /* cache */ +}; + + +/* ------------------------------------------------------------------------------- + * 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, unsigned char* nonce) +{ + digest_record_t* rec; + + if(ctx->cache_max == 0) + return NULL; + + ha_lock(NULL); + + rec = (digest_record_t*)hash_get(ctx->cache, nonce); + + /* Just in case it's a basic :) */ + if(rec && rec != BASIC_ESTABLISHED) + hash_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; + + ha_lock(NULL); + + ret = (hash_get(ctx->cache, key) == BASIC_ESTABLISHED); + + ha_unlock(NULL); + + return ret; +} + +static int save_cached_digest(simple_context_t* ctx, digest_record_t* rec) +{ + int r; + + if(ctx->cache_max == 0) + return HA_FALSE; + + ha_lock(NULL); + + while(hash_count(ctx->cache) >= ctx->cache_max) + hash_bump(ctx->cache); + + r = hash_set(ctx->cache, rec->nonce, rec); + + ha_unlock(NULL); + + if(!r) + { + ha_messagex(LOG_CRIT, "out of memory"); + return HA_ERROR; + } + + return HA_OK; +} + +static int add_cached_basic(simple_context_t* ctx, unsigned char* key) +{ + int r; + + if(ctx->cache_max == 0) + return HA_FALSE; + + ha_lock(NULL); + + while(hash_count(ctx->cache) >= ctx->cache_max) + hash_bump(ctx->cache); + + r = hash_set(ctx->cache, key, BASIC_ESTABLISHED); + + ha_unlock(NULL); + + if(!r) + { + ha_messagex(LOG_CRIT, "out of memory"); + return HA_ERROR; + } + + return HA_OK; +} + +static int complete_digest_ha1(simple_context_t* ctx, digest_record_t* rec, + ha_buffer_t* buf, const char* user, int* code) +{ + FILE* f; + int found = 0; + char* t; + char line[SIMPLE_MAXLINE]; + + f = fopen(ctx->filename, "r"); + if(!f) + { + ha_message(LOG_ERR, "can't open file for basic auth: %s", ctx->filename); + *code = HA_SERVER_ERROR; + return HA_FALSE; + } + + /* + * 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, "error reading basic password file"); + *code = HA_SERVER_ERROR; + break; + } + + t = strchr(line, ':'); + if(t) + { + /* Split the line */ + *t = 0; + t++; + + /* Check the user */ + if(strcmp(line, user) == 0) + { + /* Now decode the rest of the line and see if it matches up */ + t = ha_bufdechex(buf, t, MD5_LEN); + + if(t != NULL) + { + memcpy(rec->ha1, t, MD5_LEN); + found = 1; + break; + } + + else + { + if(ha_buferr(buf)) + break; + + ha_messagex(LOG_WARNING, "user '%s' found in file, but password not in digest format", user); + } + } + } + + fclose(f); + } + + if(ha_buferr(buf)) + return HA_ERROR; + + return found ? HA_FALSE : HA_OK; +} + +static int validate_user_password(simple_context_t* ctx, ha_buffer_t* buf, + const char* user, const char* clearpw, int* code) +{ + FILE* f; + int found = 0; + char line[SIMPLE_MAXLINE]; + unsigned char ha1[MD5_LEN]; + char* t; + char* t2; + + f = fopen(ctx->filename, "r"); + if(!f) + { + ha_message(LOG_ERR, "can't open file for basic auth: %s", ctx->filename); + *code = HA_SERVER_ERROR; + return HA_FALSE; + } + + digest_makeha1(ha1, user, ctx->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)) + { + ha_message(LOG_ERR, "error reading basic password file"); + *code = HA_SERVER_ERROR; + break; + } + + t = strchr(line, ':'); + if(t) + { + /* Split the line */ + *t = 0; + t++; + + /* Check the user */ + if(strcmp(line, user) == 0) + { + /* We can validate against an ha1, so check if it decodes as one */ + t2 = ha_bufdechex(buf, t, MD5_LEN); + + if(t2 && memcmp(ha1, t2, MD5_LEN) == 0) + { + memcpy(ha1, t2, MD5_LEN); + found = 1; + break; + } + + /* Otherwise we try a nice crypt style password */ + else + { + /* Not sure if crypt is thread safe so we lock */ + ha_lock(); + + /* Check the password */ + if(strcmp(crypt(clearpw, t), t) == 0) + found = 1; + + ha_unlock(); + + if(found) + break; + } + + if(ha_buferr(buf)) + break; + } + } + + fclose(f); + } + + if(ha_buferr(buf)) + return HA_ERROR; + + return found ? HA_FALSE : HA_OK; +} + +static int simple_basic_response(simple_context_t* ctx, const char* header, + ha_response_t* resp, ha_buffer_t* buf) +{ + basic_header_t basic; + int ret = HA_FALSE; + int found = 0; + int r; + + ASSERT(buf && header && resp && buf); + + if(basic_parse(header, buf, &basic) == HA_ERROR) + return HA_ERROR; + + /* 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)) + { + 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, buf, basic.user, basic.password, &(resp->code)); + +finally: + + if(resp->code == HA_SERVER_ACCEPT) + { + resp->detail = basic.user; + + /* We put this connection into the successful connections */ + ret = add_cached_basic(ctx, basic.key); + } + + return ret; +} + +static int simple_digest_challenge(simple_context_t* ctx, ha_response_t* resp, + ha_buffer_t* buf, int stale) +{ + unsigned char nonce[DIGEST_NONCE_LEN]; + const char* header; + + /* Generate an nonce */ + digest_makenonce(nonce, g_simple_secret, NULL); + + /* Now generate a message to send */ + header = digest_challenge(buf, nonce, ctx->realm, ctx->domains, stale); + + if(!header) + return HA_ERROR; + + /* And append it nicely */ + resp->code = HA_SERVER_DECLINE; + ha_addheader(resp, "WWW-Authenticate", header); + + return HA_OK; +} + +static int simple_digest_response(simple_context_t* ctx, const char* header, + const char* method, const char* uri, int timeout, + ha_response_t* resp, ha_buffer_t* buf) +{ + 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; + + /* We use this below to send a default response */ + resp->code = -1; + + if(digest_parse(header, buf, &dg, nonce) == HA_ERROR) + return HA_ERROR; + + r = digest_checknonce(nonce, g_simple_secret, &expiry); + if(r != HA_OK) + { + if(r == HA_FALSE) + ha_messagex(LOG_WARNING, "digest response contains invalid nonce"); + + ret = r; + goto finally; + } + + rec = get_cached_digest(ctx, nonce); + + /* Check to see if we're stale */ + if((expiry + timeout) <= time(NULL)) + { + stale = 1; + goto finally; + } + + if(!rec) + { + /* + * 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_ERROR; + goto finally; + } + + r = complete_digest_ha1(ctx, rec, buf, dg.username, &(resp->code)); + if(r != HA_OK) + { + ret = r; + goto finally; + } + } + + /* Increment our nonce count */ + rec->nc++; + + ret = digest_check(ctx->realm, method, uri, buf, &dg, rec); + + if(ret == HA_OK) + { + resp->code = HA_SERVER_ACCEPT; + resp->detail = dg.username; + + /* Figure out if we need a new nonce */ + if((expiry + (timeout - (timeout / 8))) < time(NULL)) + { + digest_makenonce(nonce, g_simple_secret, NULL); + stale = 1; + } + + t = digest_respond(buf, &dg, rec, stale ? nonce : NULL); + if(!t) + { + ret = HA_ERROR; + goto finally; + } + + if(t[0]) + ha_addheader(resp, "Authentication-Info", t); + + /* Put the connection into the cache */ + if(save_cached_digest(ctx, rec) == HA_ERROR) + ret = HA_ERROR; + else + 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 simple_digest_challenge(ctx, resp, buf, stale); + + 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->data); + + if(strcmp(name, "passwordfile") == 0) + { + ctx->filename = value; + return HA_OK; + } + + else if(strcmp(name, "realm") == 0) + { + ctx->realm = value; + return HA_OK; + } + + else if(strcmp(name, "digestdomains") == 0) + { + ctx->domains = value; + return HA_OK; + } + + else if(strcmp(name, "cachemax") == 0) + { + return ha_confint(name, value, 0, 0x7FFFFFFF, &(ctx->cache_max)); + } + + return HA_FALSE; +} + +int simple_init(ha_context_t* context) +{ + /* Global initialization */ + if(!context) + { + return ha_genrandom(g_simple_secret, DIGEST_SECRET_LEN); + } + + /* Context specific initialization */ + else + { + simple_context_t* ctx = (simple_context_t*)(context->data); + int fd; + + /* Make sure there are some types of authentication we can do */ + if(!(context->types & (HA_TYPE_BASIC | HA_TYPE_DIGEST))) + { + ha_messagex(LOG_ERR, "Simple module configured, but does not implement any " + "configured authentication type."); + return HA_ERROR; + } + + + /* Check to make sure the file exists */ + if(!ctx->filename) + { + ha_messagex(LOG_ERR, "Basic configuration incomplete. " + "Must have a PasswordFile configured."); + return HA_ERROR; + } + + fd = open(ctx->filename, O_RDONLY); + if(fd == -1) + { + ha_message(LOG_ERR, "can't open file for simple authentication: %s", ctx->filename); + return HA_ERROR; + } + + close(fd); + + /* The cache for digest records and basic */ + if(!(ctx->cache = hash_create(MD5_LEN, free_hash_object, NULL))) + { + ha_messagex(LOG_CRIT, "out of memory"); + return HA_ERROR; + } + } + + return HA_OK; +} + +void simple_destroy(ha_context_t* context) +{ + /* Per context destroy */ + if(context) + { + simple_context_t* ctx = (simple_context_t*)(context->data); + + /* Note: We don't need to be thread safe here anymore */ + hash_free(ctx->cache); + } +} + +int simple_process(ha_context_t* context, ha_request_t* req, + ha_response_t* resp, ha_buffer_t* buf) +{ + simple_context_t* ctx = (simple_context_t*)(context->data); + const char* header; + int ret = HA_FALSE; + int found = 0; + basic_header_t basic; + + + ha_lock(NULL); + + /* Purge the cache */ + hash_purge(ctx->cache, time(NULL) - context->timeout); + + ha_unlock(NULL); + + + /* 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(context->types & HA_TYPE_DIGEST) + { + header = ha_getheader(req, "Authorization", HA_PREFIX_DIGEST); + if(header) + { + ret = simple_digest_response(ctx, header, req->args[AUTH_ARG_METHOD], + req->args[AUTH_ARG_URI], context->timeout, + resp, buf); + if(ret == HA_ERROR) + return ret; + } + } + + /* Or a basic authentication */ + if(!header && context->types & HA_TYPE_BASIC) + { + header = ha_getheader(req, "Authorization", HA_PREFIX_BASIC); + if(header) + { + ret = simple_basic_response(ctx, header, resp, buf); + if(ret == HA_ERROR) + return ret; + } + } + + + /* Send a default response if that's what we need */ + if(resp->code == -1) + { + resp->code = HA_SERVER_DECLINE; + + if(context->types & HA_TYPE_DIGEST) + { + ret = simple_digest_challenge(ctx, resp, buf, 0); + if(ret == HA_ERROR) + return ret; + } + + if(context->types & HA_TYPE_BASIC) + { + ha_bufmcat(buf, "BASIC realm=\"", ctx->realm , "\"", NULL); + + if(ha_buferr(buf)) + return HA_ERROR; + + ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf)); + } + } + + return ret; +} + + +/* ------------------------------------------------------------------------------- + * Handler Definition + */ + +ha_handler_t simple_handler = +{ + "SIMPLE", /* The type */ + simple_init, /* Initialization function */ + simple_destroy, /* Uninitialization routine */ + simple_config, /* Config routine */ + simple_process, /* Processing routine */ + &simple_defaults, /* A default context */ + sizeof(simple_context_t) /* Size of the context */ +}; + -- cgit v1.2.3