summaryrefslogtreecommitdiff
path: root/daemon/simple.c
diff options
context:
space:
mode:
authorStef Walter <stef@memberwebs.com>2004-04-24 22:38:50 +0000
committerStef Walter <stef@memberwebs.com>2004-04-24 22:38:50 +0000
commitcbbe71752d7f9c6204ab0f16600fe7f10490f203 (patch)
tree365e6e472d239d117b5f849c45f3c08fc6617c0a /daemon/simple.c
parentff76efc3e5e1b0e4ca3b10b7402406f619509bba (diff)
Completed implementation of ldap/ntlm/simple handlers
Diffstat (limited to 'daemon/simple.c')
-rw-r--r--daemon/simple.c663
1 files changed, 663 insertions, 0 deletions
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 <syslog.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+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 */
+};
+