summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStef Walter <stef@memberwebs.com>2004-08-09 16:57:46 +0000
committerStef Walter <stef@memberwebs.com>2004-08-09 16:57:46 +0000
commitb0e50bbeb12e6247dd52dfd9e44c62f558c8a3a0 (patch)
tree20863017407586634b5fedf5ccf793d1cb5176a4
parent6be6d1dd25f2e7f2f1de6c0091e9aeae2ea1918c (diff)
- Seperated core Basic/Digest functionality into a base handler
-rw-r--r--daemon/bd.c511
-rw-r--r--daemon/bd.h71
-rw-r--r--daemon/ldap.c1931
-rw-r--r--daemon/simple.c865
4 files changed, 1524 insertions, 1854 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;
+}
+
diff --git a/daemon/bd.h b/daemon/bd.h
new file mode 100644
index 0000000..450c8e1
--- /dev/null
+++ b/daemon/bd.h
@@ -0,0 +1,71 @@
+
+#ifndef BD_H
+#define BD_H
+
+#include "hash.h"
+
+/* ----------------------------------------------------------------------------------
+ * Callbacks
+ */
+
+/*
+ * A callback for completing a given user's digest ha1. In this function
+ * you're required to fill in the digest ha1 hash for the user. ha1 will
+ * always be an array of MD5_LEN bytes.
+ *
+ * Return Values
+ * HA_OK: completed successfully
+ * HA_FAILED: error retrieving hash (should have logged error)
+ */
+typedef int (*bd_complete_digest)(const ha_request_t* req,
+ const char* user, unsigned char* ha1);
+
+/*
+ * A callback for validating a given user's password.
+ *
+ * Return Values
+ * HA_OK: good password
+ * HA_FALSE: invalid password
+ * HA_FAILED: error validating (should have logged error)
+ */
+typedef int (*bd_validate_basic)(const ha_request_t* req,
+ const char* user, const char* password);
+
+/* ----------------------------------------------------------------------------------
+ * Base Context
+ */
+typedef struct bd_context
+{
+ hsh_t* cache; /* Some cached records or basic */
+
+ bd_complete_digest f_complete_digest;
+ bd_validate_basic f_validate_basic;
+}
+bd_context_t;
+
+#define BD_CALLBACKS(a, b) { NULL, (a), (b) }
+#define BD_DEFAULTS { NULL, NULL, NULL }
+
+/* ----------------------------------------------------------------------------------
+ * Base Handler Functions
+ */
+
+/*
+ * The base/digest initialize function. Call this from your
+ * 'derived' handler initialize function.
+ */
+int bd_init(ha_context_t* context);
+
+/*
+ * The base/digest uninitialize function. Call this from your
+ * 'derived' handler uninitialize function.
+ */
+void bd_destroy(ha_context_t* context);
+
+/*
+ * The base/digest processer for requests. Call this from your
+ * 'derived' handler process function.
+ */
+int bd_process(const ha_request_t* req, ha_response_t* resp);
+
+#endif /* BD_H */
diff --git a/daemon/ldap.c b/daemon/ldap.c
index 4398dc0..e0349fb 100644
--- a/daemon/ldap.c
+++ b/daemon/ldap.c
@@ -1,32 +1,21 @@
/* TODO: Include attribution for ideas, and code from mod_auth_digest */
-#define _XOPEN_SOURCE
-
#include "usuals.h"
#include "httpauthd.h"
-#include "hash.h"
-#include "defaults.h"
-#include "digest.h"
-#include "basic.h"
#include "md5.h"
#include "sha1.h"
+#include "bd.h"
#include <sys/time.h>
-#include <unistd.h>
-#include <syslog.h>
/* LDAP library */
#include <ldap.h>
-unsigned char g_ldap_secret[DIGEST_SECRET_LEN];
-
/* -------------------------------------------------------------------------------
* Defaults and Constants
*/
-#define BASIC_ESTABLISHED (void*)1
-
/* TODO: We need to support more password types */
#define LDAP_PW_CLEAR 0
#define LDAP_PW_CRYPT 1
@@ -36,17 +25,17 @@ unsigned char g_ldap_secret[DIGEST_SECRET_LEN];
typedef struct ldap_pw_type
{
- const char* name;
- int type;
+ const char* name;
+ int type;
}
ldap_pw_type_t;
static const ldap_pw_type_t kLDAPPWTypes[] =
{
- { "cleartext", LDAP_PW_CLEAR },
- { "crypt", LDAP_PW_CRYPT },
- { "md5", LDAP_PW_MD5 },
- { "sha", LDAP_PW_SHA }
+ { "cleartext", LDAP_PW_CLEAR },
+ { "crypt", LDAP_PW_CRYPT },
+ { "md5", LDAP_PW_MD5 },
+ { "sha", LDAP_PW_SHA }
};
@@ -57,50 +46,54 @@ static const ldap_pw_type_t kLDAPPWTypes[] =
/* Our hanler context */
typedef struct ldap_context
{
- /* Settings ---------------------------------------------------------- */
- const char* servers; /* Servers to authenticate against (required) */
- const char* filter; /* Filter (either this or dnmap must be set) */
- const char* base; /* Base for the filter */
- const char* pw_attr; /* The clear password attribute */
- const char* ha1_attr; /* Password for an encrypted Digest H(A1) */
- const char* user; /* User to bind as */
- const char* password; /* Password to bind with */
- const char* dnmap; /* For mapping users to dns */
- int port; /* Port to connect to LDAP server on */
- int scope; /* Scope for filter */
-
- int dobind; /* Bind to do simple authentication */
- int ldap_max; /* Number of open connections allowed */
- int ldap_timeout; /* Maximum amount of time to dedicate to an ldap query */
-
- /* Context ----------------------------------------------------------- */
- hsh_t* cache; /* Some cached records or basic */
-
- LDAP** pool; /* Pool of available connections */
- int pool_mark; /* Amount of connections allocated */
+ /* Base Handler ------------------------------------------------------ */
+ bd_context_t bd;
+
+ /* Settings ---------------------------------------------------------- */
+ const char* servers; /* Servers to authenticate against (required) */
+ const char* filter; /* Filter (either this or dnmap must be set) */
+ const char* base; /* Base for the filter */
+ const char* pw_attr; /* The clear password attribute */
+ const char* ha1_attr; /* Password for an encrypted Digest H(A1) */
+ const char* user; /* User to bind as */
+ const char* password; /* Password to bind with */
+ const char* dnmap; /* For mapping users to dns */
+ int port; /* Port to connect to LDAP server on */
+ int scope; /* Scope for filter */
+
+ int dobind; /* Bind to do simple authentication */
+ int ldap_max; /* Number of open connections allowed */
+ int ldap_timeout; /* Maximum amount of time to dedicate to an ldap query */
+
+ /* Context ----------------------------------------------------------- */
+ LDAP** pool; /* Pool of available connections */
+ int pool_mark; /* Amount of connections allocated */
}
ldap_context_t;
+/* Forward declarations for callbacks */
+static int complete_digest(const ha_request_t* req, const char* user, unsigned char* ha1);
+static int validate_basic(const ha_request_t* req, const char* user, const char* password);
/* The defaults for the context */
static const ldap_context_t ldap_defaults =
{
- NULL, /* servers */
- NULL, /* filter */
- NULL, /* base */
- "userPassword", /* pw_attr */
- NULL, /* ha1_attr */
- NULL, /* user */
- NULL, /* password */
- NULL, /* dnmap */
- 389, /* port */
- LDAP_SCOPE_SUBTREE, /* scope */
- 1, /* dobind */
- 10, /* ldap_max */
- 30, /* ldap_timeout */
- NULL, /* cache */
- NULL, /* pool */
- 0 /* pool_mark */
+ BD_CALLBACKS(complete_digest, validate_basic),
+ NULL, /* servers */
+ NULL, /* filter */
+ NULL, /* base */
+ "userPassword", /* pw_attr */
+ NULL, /* ha1_attr */
+ NULL, /* user */
+ NULL, /* password */
+ NULL, /* dnmap */
+ 389, /* port */
+ LDAP_SCOPE_SUBTREE, /* scope */
+ 1, /* dobind */
+ 10, /* ldap_max */
+ 30, /* ldap_timeout */
+ NULL, /* pool */
+ 0 /* pool_mark */
};
@@ -108,127 +101,22 @@ static const ldap_context_t ldap_defaults =
* Internal Functions
*/
-static void free_hash_object(void* arg, void* val)
-{
- if(val && val != BASIC_ESTABLISHED)
- free(val);
-}
-
static int report_ldap(const char* msg, int code)
{
- ASSERT(code != LDAP_SUCCESS);
-
- switch(code)
- {
- case LDAP_NO_MEMORY:
- ha_messagex(LOG_CRIT, "out of memory");
- return HA_CRITERROR;
-
- default:
- if(!msg)
- msg = "error";
-
- ha_messagex(LOG_ERR, "ldap: %s: %s", msg, ldap_err2string(code));
- return HA_FAILED;
- };
-}
-
-static digest_record_t* get_cached_digest(ldap_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(ldap_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(ldap_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(ldap_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);
+ ASSERT(code != LDAP_SUCCESS);
- if(!r)
- {
- ha_messagex(LOG_CRIT, "out of memory");
- return HA_CRITERROR;
- }
+ switch(code)
+ {
+ case LDAP_NO_MEMORY:
+ ha_messagex(LOG_CRIT, "out of memory");
+ return HA_CRITERROR;
- return HA_OK;
+ default:
+ if(!msg)
+ msg = "error";
+ ha_messagex(LOG_ERR, "ldap: %s: %s", msg, ldap_err2string(code));
+ return HA_FAILED;
+ };
}
#define LDAP_NO_ESCAPE "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_"
@@ -236,982 +124,757 @@ static int add_cached_basic(ldap_context_t* ctx, ha_context_t* c,
static const char* escape_ldap(ha_buffer_t* buf, const char* str)
{
- const char* t = str;
- size_t pos;
-
- ha_bufcpy(buf, "");
+ const char* t = str;
+ size_t pos;
- while(*t)
- {
- pos = strspn(t, LDAP_NO_ESCAPE);
+ ha_bufcpy(buf, "");
- if(pos > 0)
+ while(*t)
{
- ha_bufjoin(buf);
- ha_bufncpy(buf, t, pos);
+ pos = strspn(t, LDAP_NO_ESCAPE);
- t += pos;
- }
+ if(pos > 0)
+ {
+ ha_bufjoin(buf);
+ ha_bufncpy(buf, t, pos);
- while(*t && !strchr(LDAP_NO_ESCAPE, *t))
- {
- char hex[4];
- hex[0] = '\\';
- hex[1] = LDAP_HEX[*t >> 4 & 0xf];
- hex[2] = LDAP_HEX[*t & 0xf];
- hex[3] = '\0';
+ t += pos;
+ }
- ha_bufjoin(buf);
- ha_bufcpy(buf, hex);
+ while(*t && !strchr(LDAP_NO_ESCAPE, *t))
+ {
+ char hex[4];
+ hex[0] = '\\';
+ hex[1] = LDAP_HEX[*t >> 4 & 0xf];
+ hex[2] = LDAP_HEX[*t & 0xf];
+ hex[3] = '\0';
- t++;
+ ha_bufjoin(buf);
+ ha_bufcpy(buf, hex);
+
+ t++;
+ }
}
- }
- return ha_bufdata(buf);
+ return ha_bufdata(buf);
}
static const char* substitute_params(ldap_context_t* ctx, const ha_request_t* req,
const char* user, const char* str)
{
- const char* t;
+ const char* t;
- ASSERT(ctx && req && user && str);
+ ASSERT(ctx && req && user && str);
- /* TODO: We need to be escaping the user and realm properly */
- /* This starts a new block to join */
- ha_bufcpy(req->buf, "");
+ /* TODO: We need to be escaping the user and realm properly */
+ /* This starts a new block to join */
+ ha_bufcpy(req->buf, "");
- while(str[0])
- {
- t = strchr(str, '%');
- if(!t)
+ while(str[0])
{
- ha_bufjoin(req->buf);
- ha_bufcpy(req->buf, str);
- break;
+ t = strchr(str, '%');
+ if(!t)
+ {
+ ha_bufjoin(req->buf);
+ ha_bufcpy(req->buf, str);
+ break;
+ }
+
+ ha_bufjoin(req->buf);
+ ha_bufncpy(req->buf, str, t - str);
+
+ t++;
+
+ switch(t[0])
+ {
+ case 'u':
+ ha_bufjoin(req->buf);
+ escape_ldap(req->buf, user);
+ t++;
+ break;
+
+ case 'r':
+ ha_bufjoin(req->buf);
+ escape_ldap(req->buf, req->context->realm);
+ t++;
+ break;
+
+ case '%':
+ ha_bufjoin(req->buf);
+ ha_bufcpy(req->buf, "%");
+ t++;
+ break;
+ };
+
+ str = t;
}
- ha_bufjoin(req->buf);
- ha_bufncpy(req->buf, str, t - str);
-
- t++;
-
- switch(t[0])
- {
- case 'u':
- ha_bufjoin(req->buf);
- escape_ldap(req->buf, user);
- t++;
- break;
-
- case 'r':
- ha_bufjoin(req->buf);
- escape_ldap(req->buf, req->context->realm);
- t++;
- break;
-
- case '%':
- ha_bufjoin(req->buf);
- ha_bufcpy(req->buf, "%");
- t++;
- break;
- };
-
- str = t;
- }
-
- return ha_bufdata(req->buf);
+ return ha_bufdata(req->buf);
}
static const char* make_password_md5(ha_buffer_t* buf, const char* clearpw)
{
- md5_ctx_t md5;
- unsigned char digest[MD5_LEN];
+ md5_ctx_t md5;
+ unsigned char digest[MD5_LEN];
- ASSERT(buf && clearpw);
+ ASSERT(buf && clearpw);
- md5_init(&md5);
- md5_update(&md5, clearpw, strlen(clearpw));
- md5_final(digest, &md5);
+ md5_init(&md5);
+ md5_update(&md5, clearpw, strlen(clearpw));
+ md5_final(digest, &md5);
- return ha_bufenc64(buf, digest, MD5_LEN);
+ return ha_bufenc64(buf, digest, MD5_LEN);
}
static const char* make_password_sha(ha_buffer_t* buf, const char* clearpw)
{
- sha1_ctx_t sha;
- unsigned char digest[SHA1_LEN];
+ sha1_ctx_t sha;
+ unsigned char digest[SHA1_LEN];
- ASSERT(buf && clearpw);
+ ASSERT(buf && clearpw);
- sha1_init(&sha);
- sha1_update(&sha, clearpw, strlen(clearpw));
- sha1_final(digest, &sha);
+ sha1_init(&sha);
+ sha1_update(&sha, clearpw, strlen(clearpw));
+ sha1_final(digest, &sha);
- return ha_bufenc64(buf, digest, SHA1_LEN);
+ return ha_bufenc64(buf, digest, SHA1_LEN);
}
static int parse_ldap_password(const char** password)
{
- const char* pw;
- const char* scheme;
- int i;
-
- ASSERT(password && *password);
+ const char* pw;
+ const char* scheme;
+ int i;
- pw = *password;
+ ASSERT(password && *password);
- /* zero length passwords are clear */
- if(strlen(pw) == 0)
- return LDAP_PW_CLEAR;
+ pw = *password;
- /* passwords without a scheme are clear */
- if(pw[0] != '{')
- return LDAP_PW_CLEAR;
+ /* zero length passwords are clear */
+ if(strlen(pw) == 0)
+ return LDAP_PW_CLEAR;
- pw++;
- scheme = pw;
+ /* passwords without a scheme are clear */
+ if(pw[0] != '{')
+ return LDAP_PW_CLEAR;
- while(*pw && (isalpha(*pw) || isdigit(*pw) || *pw == '-'))
pw++;
+ scheme = pw;
- /* scheme should end in a brace */
- if(*pw != '}')
- return LDAP_PW_CLEAR;
+ while(*pw && (isalpha(*pw) || isdigit(*pw) || *pw == '-'))
+ pw++;
- *password = pw + 1;
+ /* scheme should end in a brace */
+ if(*pw != '}')
+ return LDAP_PW_CLEAR;
- /* find a scheme in our map */
- for(i = 0; i < countof(kLDAPPWTypes); i++)
- {
- if(strncasecmp(kLDAPPWTypes[i].name, scheme, pw - scheme) == 0)
- return kLDAPPWTypes[i].type;
- }
+ *password = pw + 1;
- return LDAP_PW_UNKNOWN;
+ /* find a scheme in our map */
+ for(i = 0; i < countof(kLDAPPWTypes); i++)
+ {
+ if(strncasecmp(kLDAPPWTypes[i].name, scheme, pw - scheme) == 0)
+ return kLDAPPWTypes[i].type;
+ }
+
+ return LDAP_PW_UNKNOWN;
}
static const char* find_cleartext_password(ha_buffer_t* buf, const char** pws)
{
- ASSERT(buf);
+ ASSERT(buf);
- for(; pws && *pws; pws++)
- {
- const char* pw = *pws;
+ for(; pws && *pws; pws++)
+ {
+ const char* pw = *pws;
- if(parse_ldap_password(&pw) == LDAP_PW_CLEAR)
- return pw;
- }
+ if(parse_ldap_password(&pw) == LDAP_PW_CLEAR)
+ return pw;
+ }
- return NULL;
+ return NULL;
}
static int parse_ldap_ha1(ha_buffer_t* buf, struct berval* bv, unsigned char* ha1)
{
- size_t len;
- void* d;
+ size_t len;
+ void* d;
- ASSERT(buf && bv && ha1);
+ ASSERT(buf && bv && ha1);
- /* Raw binary */
- if(bv->bv_len == MD5_LEN)
- {
- ha_messagex(LOG_DEBUG, "ldap: found ha1 in raw binary format");
- memcpy(ha1, bv->bv_val, MD5_LEN);
- return HA_OK;
- }
-
- /* Hex encoded */
- else if(bv->bv_len == (MD5_LEN * 2))
- {
- len = MD5_LEN;
- d = ha_bufdechex(buf, bv->bv_val, &len);
-
- if(d && len == MD5_LEN)
+ /* Raw binary */
+ if(bv->bv_len == MD5_LEN)
{
- ha_messagex(LOG_DEBUG, "ldap: found ha1 in hex encoded format");
- memcpy(ha1, d, MD5_LEN);
- return HA_OK;
+ ha_messagex(LOG_DEBUG, "ldap: found ha1 in raw binary format");
+ memcpy(ha1, bv->bv_val, MD5_LEN);
+ return HA_OK;
}
- }
- /* B64 Encoded */
- else
- {
- len = MD5_LEN;
- d = ha_bufdec64(buf, bv->bv_val, &len);
+ /* Hex encoded */
+ else if(bv->bv_len == (MD5_LEN * 2))
+ {
+ len = MD5_LEN;
+ d = ha_bufdechex(buf, bv->bv_val, &len);
+
+ if(d && len == MD5_LEN)
+ {
+ ha_messagex(LOG_DEBUG, "ldap: found ha1 in hex encoded format");
+ memcpy(ha1, d, MD5_LEN);
+ return HA_OK;
+ }
+ }
- if(d && len == MD5_LEN)
+ /* B64 Encoded */
+ else
{
- ha_messagex(LOG_DEBUG, "ldap: found ha1 in b64 encoded format");
- memcpy(ha1, ha_bufdata(buf), MD5_LEN);
- return HA_OK;
+ len = MD5_LEN;
+ d = ha_bufdec64(buf, bv->bv_val, &len);
+
+ if(d && len == MD5_LEN)
+ {
+ ha_messagex(LOG_DEBUG, "ldap: found ha1 in b64 encoded format");
+ memcpy(ha1, ha_bufdata(buf), MD5_LEN);
+ return HA_OK;
+ }
}
- }
- return ha_buferr(buf) ? HA_CRITERROR : HA_FALSE;
+ return ha_buferr(buf) ? HA_CRITERROR : HA_FALSE;
}
static int validate_ldap_password(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry,
ha_buffer_t* buf, const char* user, const char* clearpw)
{
- char** pws;
- char** t;
- const char* pw;
- const char* p;
- int type;
- int res = HA_FALSE;
- int unknown = 0;
-
- ASSERT(entry && ld && ctx && clearpw);
-
- ASSERT(ctx->pw_attr);
- pws = ldap_get_values(ld, entry, ctx->pw_attr);
-
- if(pws)
- {
- for(t = pws ; *t; t++)
- {
- pw = *t;
- type = parse_ldap_password(&pw);
-
- switch(type)
- {
- case LDAP_PW_CLEAR:
- p = clearpw;
- break;
+ char** pws;
+ char** t;
+ const char* pw;
+ const char* p;
+ int type;
+ int res = HA_FALSE;
+ int unknown = 0;
- case LDAP_PW_MD5:
- p = make_password_md5(buf, clearpw);
- break;
-
- case LDAP_PW_CRYPT:
+ ASSERT(entry && ld && ctx && clearpw);
- /* Not sure if crypt is thread safe */
- ha_lock(NULL);
- p = crypt(clearpw, pw);
- ha_unlock(NULL);
- break;
+ ASSERT(ctx->pw_attr);
+ pws = ldap_get_values(ld, entry, ctx->pw_attr);
- case LDAP_PW_SHA:
- p = make_password_sha(buf, clearpw);
- break;
-
- case LDAP_PW_UNKNOWN:
- unknown = 1;
- continue;
-
- default:
- /* Not reached */
- ASSERT(0);
- };
-
- if(!p)
- {
- res = HA_CRITERROR;
- break;
- }
-
- if(strcmp(pw, p) == 0)
- {
- ha_messagex(LOG_DEBUG, "ldap: successful validate against password");
- res = HA_OK;
- break;
- }
+ if(pws)
+ {
+ for(t = pws ; *t; t++)
+ {
+ pw = *t;
+ type = parse_ldap_password(&pw);
+
+ switch(type)
+ {
+ case LDAP_PW_CLEAR:
+ p = clearpw;
+ break;
+
+ case LDAP_PW_MD5:
+ p = make_password_md5(buf, clearpw);
+ break;
+
+ case LDAP_PW_CRYPT:
+ /* Not sure if crypt is thread safe */
+ ha_lock(NULL);
+ p = crypt(clearpw, pw);
+ ha_unlock(NULL);
+ break;
+
+ case LDAP_PW_SHA:
+ p = make_password_sha(buf, clearpw);
+ break;
+
+ case LDAP_PW_UNKNOWN:
+ unknown = 1;
+ continue;
+
+ default:
+ /* Not reached */
+ ASSERT(0);
+ };
+
+ if(!p)
+ {
+ res = HA_CRITERROR;
+ break;
+ }
+
+ if(strcmp(pw, p) == 0)
+ {
+ ha_messagex(LOG_DEBUG, "ldap: successful validate against password");
+ res = HA_OK;
+ break;
+ }
+ }
+
+ ldap_value_free(pws);
}
- ldap_value_free(pws);
- }
-
- if(res == HA_FALSE && unknown)
- ha_messagex(LOG_ERR, "ldap: server does not contain any compatible passwords for user: %s", user);
+ if(res == HA_FALSE && unknown)
+ ha_messagex(LOG_ERR, "ldap: server does not contain any compatible passwords for user: %s", user);
- return res;
+ return res;
}
static int validate_ldap_ha1(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry,
ha_buffer_t* buf, const char* user, const char* realm,
const char* clearpw)
{
- struct berval** ha1s;
- struct berval** b;
- unsigned char key[MD5_LEN];
- unsigned char k[MD5_LEN];
- int r, first = 1;
- int res = HA_FALSE;
+ struct berval** ha1s;
+ struct berval** b;
+ unsigned char key[MD5_LEN];
+ unsigned char k[MD5_LEN];
+ int r, first = 1;
+ int res = HA_FALSE;
- ASSERT(ctx && ld && entry && buf && user && clearpw);
+ ASSERT(ctx && ld && entry && buf && user && clearpw);
- if(!ctx->ha1_attr)
- return HA_FALSE;
-
- ha1s = ldap_get_values_len(ld, entry, ctx->ha1_attr);
+ if(!ctx->ha1_attr)
+ return HA_FALSE;
- if(ha1s)
- {
- digest_makeha1(key, user, realm, clearpw);
+ ha1s = ldap_get_values_len(ld, entry, ctx->ha1_attr);
- for(b = ha1s ; *b; b++)
+ if(ha1s)
{
- r = parse_ldap_ha1(buf, *b, k);
- if(r < 0)
- {
- res = r;
- break;
- }
-
- if(r == HA_FALSE)
- {
- if(first)
- ha_messagex(LOG_ERR, "ldap: server contains invalid HA1 digest hash for user: %s", user);
-
- first = 0;
- continue;
- }
-
- if(memcmp(key, k, MD5_LEN) == 0)
- {
- ha_messagex(LOG_DEBUG, "ldap: successful validate against ha1");
- res = HA_OK;
- break;
- }
+ digest_makeha1(key, user, realm, clearpw);
+
+ for(b = ha1s ; *b; b++)
+ {
+ r = parse_ldap_ha1(buf, *b, k);
+ if(r < 0)
+ {
+ res = r;
+ break;
+ }
+
+ if(r == HA_FALSE)
+ {
+ if(first)
+ ha_messagex(LOG_ERR, "ldap: server contains invalid HA1 digest hash for user: %s", user);
+
+ first = 0;
+ continue;
+ }
+
+ if(memcmp(key, k, MD5_LEN) == 0)
+ {
+ ha_messagex(LOG_DEBUG, "ldap: successful validate against ha1");
+ res = HA_OK;
+ break;
+ }
+ }
+
+ ldap_value_free_len(ha1s);
}
- ldap_value_free_len(ha1s);
- }
-
- return res;
+ return res;
}
static LDAP* get_ldap_connection(ldap_context_t* ctx)
{
- LDAP* ld;
- int i, r;
+ LDAP* ld;
+ int i, r;
- ASSERT(ctx);
+ ASSERT(ctx);
- for(i = 0; i < ctx->ldap_max; i++)
- {
- /* An open connection in the pool */
- if(ctx->pool[i])
+ for(i = 0; i < ctx->ldap_max; i++)
{
- ha_messagex(LOG_DEBUG, "ldap: using cached connection");
- ld = ctx->pool[i];
- ctx->pool[i] = NULL;
- return ld;
+ /* An open connection in the pool */
+ if(ctx->pool[i])
+ {
+ ha_messagex(LOG_DEBUG, "ldap: using cached connection");
+ ld = ctx->pool[i];
+ ctx->pool[i] = NULL;
+ return ld;
+ }
}
- }
- if(ctx->pool_mark >= ctx->ldap_max)
- {
- ha_messagex(LOG_ERR, "ldap: too many open connections");
- return NULL;
- }
+ if(ctx->pool_mark >= ctx->ldap_max)
+ {
+ ha_messagex(LOG_ERR, "ldap: too many open connections");
+ return NULL;
+ }
- ld = ldap_init(ctx->servers, ctx->port);
- if(!ld)
- {
- ha_message(LOG_ERR, "ldap: couldn't initialize connection");
- return NULL;
- }
+ ld = ldap_init(ctx->servers, ctx->port);
+ if(!ld)
+ {
+ ha_message(LOG_ERR, "ldap: couldn't initialize connection");
+ return NULL;
+ }
- if(ctx->user || ctx->password)
- {
- r = ldap_simple_bind_s(ld, ctx->user ? ctx->user : "",
- ctx->password ? ctx->password : "");
- if(r != LDAP_SUCCESS)
+ if(ctx->user || ctx->password)
{
- report_ldap("ldap: couldn't bind to LDAP server", r);
- ldap_unbind_s(ld);
- return NULL;
+ r = ldap_simple_bind_s(ld, ctx->user ? ctx->user : "",
+ ctx->password ? ctx->password : "");
+ if(r != LDAP_SUCCESS)
+ {
+ report_ldap("ldap: couldn't bind to LDAP server", r);
+ ldap_unbind_s(ld);
+ return NULL;
+ }
}
- }
- ctx->pool_mark++;
- ha_messagex(LOG_DEBUG, "ldap: opened new connection (total %d)", ctx->pool_mark);
- return ld;
+ ctx->pool_mark++;
+ ha_messagex(LOG_DEBUG, "ldap: opened new connection (total %d)", ctx->pool_mark);
+ return ld;
}
static void discard_ldap_connection(ldap_context_t* ctx, LDAP* ld)
{
- ldap_unbind_s(ld);
- ctx->pool_mark--;
- ha_messagex(LOG_DEBUG, "ldap: discarding connection (total %d)", ctx->pool_mark);
+ ldap_unbind_s(ld);
+ ctx->pool_mark--;
+ ha_messagex(LOG_DEBUG, "ldap: discarding connection (total %d)", ctx->pool_mark);
}
static void save_ldap_connection(ldap_context_t* ctx, LDAP* ld)
{
- int i, e;
+ int i, e;
- ASSERT(ctx);
-
- if(!ld)
- return;
+ ASSERT(ctx);
- ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &e);
+ if(!ld)
+ return;
- /* Make sure it's worth saving */
- switch(e)
- {
- case LDAP_SERVER_DOWN:
- case LDAP_LOCAL_ERROR:
- case LDAP_NO_MEMORY:
- discard_ldap_connection(ctx, ld);
- break;
+ ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &e);
- default:
- for(i = 0; i < ctx->ldap_max; i++)
+ /* Make sure it's worth saving */
+ switch(e)
{
- /* An open connection in the pool */
- if(!ctx->pool[i])
- {
- ha_messagex(LOG_DEBUG, "ldap: caching connection for later use");
- ctx->pool[i] = ld;
- ld = NULL;
+ case LDAP_SERVER_DOWN:
+ case LDAP_LOCAL_ERROR:
+ case LDAP_NO_MEMORY:
+ discard_ldap_connection(ctx, ld);
break;
- }
- }
- break;
- };
+ default:
+ for(i = 0; i < ctx->ldap_max; i++)
+ {
+ /* An open connection in the pool */
+ if(!ctx->pool[i])
+ {
+ ha_messagex(LOG_DEBUG, "ldap: caching connection for later use");
+ ctx->pool[i] = ld;
+ ld = NULL;
+ break;
+ }
+ }
+ break;
+ };
}
static int retrieve_user_entry(ldap_context_t* ctx, const ha_request_t* req, LDAP* ld,
const char* user, const char** dn,
LDAPMessage** entry, LDAPMessage** result)
{
- struct timeval tv;
- const char* filter;
- const char* attrs[3];
- const char* base;
- int scope;
- int r;
-
- ASSERT(ctx && req && ld && user && dn && entry && result);
-
- if(ctx->filter)
- {
- /* Filters can also have %u and %r */
- filter = substitute_params(ctx, req, user, ctx->filter);
- if(!filter)
- return HA_CRITERROR;
- }
- else
- {
- filter = "(objectClass=*)";
- }
-
- attrs[0] = ctx->dobind ? NULL : ctx->pw_attr;
- attrs[1] = ctx->dobind ? NULL : ctx->ha1_attr;
- attrs[2] = NULL;
-
- tv.tv_sec = ctx->ldap_timeout;
- tv.tv_usec = 0;
-
- base = *dn ? *dn : ctx->base;
- scope = *dn ? LDAP_SCOPE_BASE : ctx->scope;
-
- ha_messagex(LOG_DEBUG, "ldap: performing search: [ base: %s / scope: %d / filter: %s ]",
- base, scope, filter);
-
- r = ldap_search_st(ld, base, scope, filter, (char**)attrs, 0, &tv, result);
-
- if(r != LDAP_SUCCESS)
- {
- if(r == LDAP_NO_SUCH_OBJECT)
- {
- ha_messagex(LOG_WARNING, "ldap: user not found in LDAP: %s", user);
- return HA_FALSE;
- }
+ struct timeval tv;
+ const char* filter;
+ const char* attrs[3];
+ const char* base;
+ int scope;
+ int r;
- return report_ldap("couldn't search LDAP server", r);
- }
+ ASSERT(ctx && req && ld && user && dn && entry && result);
-
- /* Only one result should exist */
- switch(r = ldap_count_entries(ld, *result))
- {
- case 1:
- *entry = ldap_first_entry(ld, *result);
- if(!(*dn))
+ if(ctx->filter)
{
- *dn = ldap_get_dn(ld, *entry);
- ha_messagex(LOG_DEBUG, "ldap: found entry for user: %s", *dn);
+ /* Filters can also have %u and %r */
+ filter = substitute_params(ctx, req, user, ctx->filter);
+ if(!filter)
+ return HA_CRITERROR;
}
- return HA_OK;
-
- case 0:
- ha_messagex(LOG_WARNING, "ldap: user not found in LDAP: %s", user);
- break;
-
- default:
- ha_messagex(LOG_WARNING, "ldap: more than one user found for filter: %s", filter);
- break;
- };
-
- return HA_FALSE;
-}
-
-static int complete_digest_ha1(ldap_context_t* ctx, digest_record_t* rec,
- const ha_request_t* req, const char* user)
-{
- LDAP* ld = NULL; /* freed in finally */
- LDAPMessage* results = NULL; /* freed in finally */
- LDAPMessage* entry = NULL; /* no need to free */
- struct berval** ha1s = NULL; /* freed manually */
- char** pws;
- int ret = HA_FALSE;
- const char* dn = NULL;
- int r;
-
- ASSERT(ctx && req && rec && user);
-
- ld = get_ldap_connection(ctx);
- if(!ld)
- {
- ret = HA_FAILED;
- goto finally;
- }
-
- /*
- * Discover the DN of the user. If there's a DN map string
- * then we can do this really quickly here without querying
- * the LDAP tree
- */
- if(ctx->dnmap)
- {
- /* The map can have %u and %r to denote user and realm */
- dn = substitute_params(ctx, req, user, ctx->dnmap);
- if(!dn)
+ else
{
- ret = HA_CRITERROR;
- goto finally;
+ filter = "(objectClass=*)";
}
- ha_messagex(LOG_INFO, "ldap: mapped %s to %s", user, dn);
- }
-
- /* Okay now we contact the LDAP server. */
- r = retrieve_user_entry(ctx, req, ld, user, &dn, &entry, &results);
- if(r != HA_OK)
- {
- ret = r;
- goto finally;
- }
+ attrs[0] = ctx->dobind ? NULL : ctx->pw_attr;
+ attrs[1] = ctx->dobind ? NULL : ctx->ha1_attr;
+ attrs[2] = NULL;
- /* Figure out the users ha1 */
- if(ctx->ha1_attr)
- ha1s = ldap_get_values_len(ld, entry, ctx->ha1_attr);
-
- if(ha1s)
- {
- if(*ha1s)
- {
- ret = parse_ldap_ha1(req->buf, *ha1s, rec->ha1);
- if(ret != HA_OK)
- {
- if(ret == HA_FALSE)
- ha_messagex(LOG_ERR, "ldap: server contains invalid HA1 for user: %s", user);
- }
- }
+ tv.tv_sec = ctx->ldap_timeout;
+ tv.tv_usec = 0;
- ha_messagex(LOG_DEBUG, "ldap: using HA1 from ldap");
- ldap_value_free_len(ha1s);
- goto finally;
- }
+ base = *dn ? *dn : ctx->base;
+ scope = *dn ? LDAP_SCOPE_BASE : ctx->scope;
- /* If no ha1 set or none found, use password and make a HA1 */
- pws = ldap_get_values(ld, entry, ctx->pw_attr);
+ ha_messagex(LOG_DEBUG, "ldap: performing search: [ base: %s / scope: %d / filter: %s ]",
+ base, scope, filter);
- if(pws)
- {
- /* Find a cleartext password */
- const char* t = find_cleartext_password(req->buf, (const char**)pws);
+ r = ldap_search_st(ld, base, scope, filter, (char**)attrs, 0, &tv, result);
- if(t)
+ if(r != LDAP_SUCCESS)
{
- digest_makeha1(rec->ha1, user, req->context->realm, t);
- ret = HA_OK;
- }
+ if(r == LDAP_NO_SUCH_OBJECT)
+ {
+ ha_messagex(LOG_WARNING, "ldap: user not found in LDAP: %s", user);
+ return HA_FALSE;
+ }
- ldap_value_free(pws);
-
- if(ret == HA_OK)
- {
- ha_messagex(LOG_DEBUG, "ldap: using clear password from ldap");
- goto finally;
+ return report_ldap("couldn't search LDAP server", r);
}
- }
- ha_messagex(LOG_ERR, "ldap: server contains no clear password or HA1 for user: %s", user);
-finally:
-
- if(ld)
- save_ldap_connection(ctx, ld);
+ /* Only one result should exist */
+ switch(r = ldap_count_entries(ld, *result))
+ {
+ case 1:
+ *entry = ldap_first_entry(ld, *result);
+ if(!(*dn))
+ {
+ *dn = ldap_get_dn(ld, *entry);
+ ha_messagex(LOG_DEBUG, "ldap: found entry for user: %s", *dn);
+ }
+ return HA_OK;
+
+ case 0:
+ ha_messagex(LOG_WARNING, "ldap: user not found in LDAP: %s", user);
+ break;
- if(results)
- ldap_msgfree(results);
+ default:
+ ha_messagex(LOG_WARNING, "ldap: more than one user found for filter: %s", filter);
+ break;
+ };
- return ret;
+ return HA_FALSE;
}
-static int basic_ldap_response(ldap_context_t* ctx, const char* header,
- const ha_request_t* req, ha_response_t* resp)
+static int complete_digest(const ha_request_t* req, const char* user, unsigned char* ha1)
{
- basic_header_t basic;
- LDAP* ld = NULL;
- LDAPMessage* entry = NULL;
- LDAPMessage* results = NULL;
- const char* dn = NULL;
- int ret = HA_FALSE;
- int found = 0;
- int r;
-
- ASSERT(header && resp && req);
-
- if((r = basic_parse(header, req->buf, &basic)) < 0)
- return r;
-
- /* 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, "ldap: validated basic user against cache: %s",
- basic.user);
- 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])
- {
- ha_messagex(LOG_NOTICE, "ldap: no valid basic auth info");
- goto finally;
- }
-
-
- ld = get_ldap_connection(ctx);
- if(!ld)
- {
- ret = HA_FAILED;
- goto finally;
- }
-
-
- /*
- * Discover the DN of the user. If there's a DN map string
- * then we can do this really quickly here without querying
- * the LDAP tree
- */
- if(ctx->dnmap)
- {
- /* The map can have %u and %r to denote user and realm */
- dn = substitute_params(ctx, req, basic.user, ctx->dnmap);
- if(!dn)
+ ldap_context_t* ctx = (ldap_context_t*)req->context->ctx_data;
+ LDAP* ld = NULL; /* freed in finally */
+ LDAPMessage* results = NULL; /* freed in finally */
+ LDAPMessage* entry = NULL; /* no need to free */
+ struct berval** ha1s = NULL; /* freed manually */
+ char** pws;
+ int ret = HA_FALSE;
+ const char* dn = NULL;
+ int r;
+
+ ASSERT(req && ha1 && user);
+
+ ld = get_ldap_connection(ctx);
+ if(!ld)
{
- ret = HA_CRITERROR;
- goto finally;
+ ret = HA_FAILED;
+ goto finally;
}
- ha_messagex(LOG_INFO, "ldap: mapped %s to %s", basic.user, dn);
- }
-
-
- /**
- * Okay now we contact the LDAP server. There are many ways
- * this is used for different authentication modes:
- *
- * - If a dn has been mapped above, this can apply a
- * configured filter to narrow things down.
- * - If no dn has been mapped, then this maps out a dn
- * by using the single object the filter returns.
- * - If not in 'dobind' mode we also retrieve the password
- * here.
- *
- * All this results in only one query to the LDAP server,
- * except for the case of dobind without a dnmap.
- */
-
- if(!ctx->dobind || !dn || ctx->filter)
- {
- r = retrieve_user_entry(ctx, req, ld, basic.user, &dn, &entry, &results);
- if(r != HA_OK)
+ /*
+ * Discover the DN of the user. If there's a DN map string
+ * then we can do this really quickly here without querying
+ * the LDAP tree
+ */
+ if(ctx->dnmap)
{
- ret = r;
- goto finally;
+ /* The map can have %u and %r to denote user and realm */
+ dn = substitute_params(ctx, req, user, ctx->dnmap);
+ if(!dn)
+ {
+ ret = HA_FAILED;
+ goto finally;
+ }
+
+ ha_messagex(LOG_INFO, "ldap: mapped %s to %s", user, dn);
}
- }
-
-
- /* Now if in bind mode we try to bind as that user */
- if(ctx->dobind)
- {
- ASSERT(dn);
- r = ldap_simple_bind_s(ld, dn, basic.password);
- if(r != LDAP_SUCCESS)
+ /* Okay now we contact the LDAP server. */
+ r = retrieve_user_entry(ctx, req, ld, user, &dn, &entry, &results);
+ if(r != HA_OK)
{
- if(r == LDAP_INVALID_CREDENTIALS)
- ha_messagex(LOG_WARNING, "ldap: basic authentication (via bind) failed for user: %s",
- basic.user);
-
- else
- report_ldap("couldn't bind to LDAP server", r);
-
- goto finally;
+ ret = r;
+ goto finally;
}
- /* It worked! */
- ha_messagex(LOG_NOTICE, "ldap: validated basic user using bind: %s", basic.user);
- found = 1;
+ /* Figure out the users ha1 */
+ if(ctx->ha1_attr)
+ ha1s = ldap_get_values_len(ld, entry, ctx->ha1_attr);
- /* Now we have to rebind the connection back to the main user */
- r = ldap_simple_bind_s(ld, ctx->user ? ctx->user : "",
- ctx->password ? ctx->password : "");
- if(r != LDAP_SUCCESS)
+ if(ha1s)
{
- report_ldap("ldap: couldn't rebind LDAP connection back to auth credentials", r);
-
- /* Discard the connection since it's useless to us */
- discard_ldap_connection(ctx, ld);
- ld = NULL;
+ if(*ha1s)
+ {
+ ret = parse_ldap_ha1(req->buf, *ha1s, ha1);
+ if(ret != HA_OK)
+ {
+ if(ret == HA_FALSE)
+ ha_messagex(LOG_ERR, "ldap: server contains invalid HA1 for user: %s", user);
+ }
+ }
+
+ ha_messagex(LOG_DEBUG, "ldap: using HA1 from ldap");
+ ldap_value_free_len(ha1s);
+ goto finally;
}
- }
-
-
- /* Otherwise we compare the password attribute */
- else
- {
- ret = validate_ldap_password(ctx, ld, entry, req->buf, basic.user, basic.password);
- if(ret == HA_FALSE)
- ret = validate_ldap_ha1(ctx, ld, entry, req->buf, basic.user,
- req->context->realm, basic.password);
- if(ret == HA_OK)
- {
- ha_messagex(LOG_NOTICE, "ldap: validated basic user password/ha1: %s", basic.user);
- found = 1;
- }
+ /* If no ha1 set or none found, use password and make a HA1 */
+ pws = ldap_get_values(ld, entry, ctx->pw_attr);
- else
+ if(pws)
{
- ha_messagex(LOG_WARNING, "ldap: invalid, unreadable or unrecognized password for user: %s", basic.user);
+ /* Find a cleartext password */
+ const char* t = find_cleartext_password(req->buf, (const char**)pws);
+
+ if(t)
+ {
+ digest_makeha1(ha1, user, req->context->realm, t);
+ ret = HA_OK;
+ }
+
+ ldap_value_free(pws);
+
+ if(ret == HA_OK)
+ {
+ ha_messagex(LOG_DEBUG, "ldap: using clear password from ldap");
+ goto finally;
+ }
}
- }
+ ha_messagex(LOG_ERR, "ldap: server contains no clear password or HA1 for user: %s", user);
finally:
- if(ld)
- save_ldap_connection(ctx, ld);
-
- if(results)
- ldap_msgfree(results);
+ if(ld)
+ save_ldap_connection(ctx, ld);
- if(found && ret >= 0)
- {
- resp->code = HA_SERVER_OK;
- resp->detail = basic.user;
+ if(results)
+ ldap_msgfree(results);
- /* We put this connection into the successful connections */
- ret = add_cached_basic(ctx, req->context, basic.key);
- }
-
- return ret;
-}
-
-static int digest_ldap_challenge(ldap_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, "ldap: using debug nonce. security non-existant.");
- }
- else
-#endif
- {
- unsigned char nonce[DIGEST_NONCE_LEN];
- digest_makenonce(nonce, g_ldap_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, "ldap: created digest challenge with nonce: %s", nonce_str);
- return HA_OK;
+ return ret;
}
-static int digest_ldap_response(ldap_context_t* ctx, const char* header,
- const ha_request_t* req, ha_response_t* resp)
+static int validate_basic(const ha_request_t* req, const char* user, const char* password)
{
- 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)
+ ldap_context_t* ctx = (ldap_context_t*)req->context->ctx_data;
+ LDAP* ld = NULL;
+ LDAPMessage* entry = NULL;
+ LDAPMessage* results = NULL;
+ const char* dn = NULL;
+ int ret = HA_FALSE;
+ int found = 0;
+ int r;
+
+ ASSERT(req && user && password);
+
+ ld = get_ldap_connection(ctx);
+ if(!ld)
{
- ret = HA_FALSE;
- ha_messagex(LOG_WARNING, "ldap: digest response contains invalid nonce");
- goto finally;
+ ret = HA_FAILED;
+ 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_ldap_secret, &expiry);
- if(r != HA_OK)
+ /*
+ * Discover the DN of the user. If there's a DN map string
+ * then we can do this really quickly here without querying
+ * the LDAP tree
+ */
+ if(ctx->dnmap)
{
- if(r == HA_FALSE)
- ha_messagex(LOG_WARNING, "ldap: digest response contains invalid nonce");
-
- goto finally;
+ /* The map can have %u and %r to denote user and realm */
+ dn = substitute_params(ctx, req, user, ctx->dnmap);
+ if(!dn)
+ {
+ ret = HA_CRITERROR;
+ goto finally;
+ }
+
+ ha_messagex(LOG_INFO, "ldap: mapped %s to %s", user, dn);
}
- }
-
- 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, "ldap: nonce expired, sending stale challenge: %s",
- dg.username);
- stale = 1;
- goto finally;
- }
-
- if(!rec)
- {
- ha_messagex(LOG_INFO, "ldap: 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.
+ /**
+ * Okay now we contact the LDAP server. There are many ways
+ * this is used for different authentication modes:
+ *
+ * - If a dn has been mapped above, this can apply a
+ * configured filter to narrow things down.
+ * - If no dn has been mapped, then this maps out a dn
+ * by using the single object the filter returns.
+ * - If not in 'dobind' mode we also retrieve the password
+ * here.
+ *
+ * All this results in only one query to the LDAP server,
+ * except for the case of dobind without a dnmap.
*/
- rec = digest_makerec(nonce, dg.username);
- if(!rec)
+ if(!ctx->dobind || !dn || ctx->filter)
{
- ret = HA_CRITERROR;
- goto finally;
+ r = retrieve_user_entry(ctx, req, ld, user, &dn, &entry, &results);
+ if(r != HA_OK)
+ {
+ ret = r;
+ goto finally;
+ }
}
- r = complete_digest_ha1(ctx, rec, req, dg.username);
- if(r != HA_OK)
+
+ /* Now if in bind mode we try to bind as that user */
+ if(ctx->dobind)
{
- ret = r;
- goto finally;
+ ASSERT(dn);
+
+ r = ldap_simple_bind_s(ld, dn, password);
+ if(r != LDAP_SUCCESS)
+ {
+ if(r == LDAP_INVALID_CREDENTIALS)
+ ha_messagex(LOG_WARNING, "ldap: basic authentication (via bind) failed for user: %s",
+ user);
+
+ else
+ report_ldap("couldn't bind to LDAP server", r);
+
+ goto finally;
+ }
+
+ /* It worked! */
+ ha_messagex(LOG_NOTICE, "ldap: validated basic user using bind: %s", user);
+ found = 1;
+
+ /* Now we have to rebind the connection back to the main user */
+ r = ldap_simple_bind_s(ld, ctx->user ? ctx->user : "",
+ ctx->password ? ctx->password : "");
+ if(r != LDAP_SUCCESS)
+ {
+ report_ldap("ldap: couldn't rebind LDAP connection back to auth credentials", r);
+
+ /* Discard the connection since it's useless to us */
+ discard_ldap_connection(ctx, ld);
+ ld = NULL;
+ }
}
- }
-
- /* 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, "ldap: nonce almost expired, creating new one: %s",
- dg.username);
- digest_makenonce(nonce, g_ldap_secret, NULL);
- stale = 1;
- }
- t = digest_respond(req->buf, &dg, rec, stale ? nonce : NULL);
- if(!t)
+ /* Otherwise we compare the password attribute */
+ else
{
- ret = HA_CRITERROR;
- goto finally;
+ ret = validate_ldap_password(ctx, ld, entry, req->buf, user, password);
+ if(ret == HA_FALSE)
+ ret = validate_ldap_ha1(ctx, ld, entry, req->buf, user,
+ req->context->realm, password);
+
+ if(ret == HA_OK)
+ {
+ ha_messagex(LOG_NOTICE, "ldap: validated basic user password/ha1: %s", user);
+ found = 1;
+ }
+
+ else
+ {
+ ha_messagex(LOG_WARNING, "ldap: invalid, unreadable or unrecognized password for user: %s", user);
+ }
}
- if(t[0])
- ha_addheader(resp, "Authentication-Info", t);
-
- ha_messagex(LOG_NOTICE, "ldap: 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(ld)
+ save_ldap_connection(ctx, ld);
- /* If nobody above responded then challenge the client again */
- if(resp->code == -1)
- return digest_ldap_challenge(ctx, req, resp, stale);
+ if(results)
+ ldap_msgfree(results);
- return ret;
+ /* TODO: Check to make sure this fits within the return values */
+ return ret;
}
@@ -1221,292 +884,184 @@ finally:
int ldap_config(ha_context_t* context, const char* name, const char* value)
{
- ldap_context_t* ctx = (ldap_context_t*)(context->ctx_data);
-
- ASSERT(name && value && value[0]);
-
- if(strcmp(name, "ldapservers") == 0)
- {
- ctx->servers = value;
- return HA_OK;
- }
-
- else if(strcmp(name, "ldapfilter") == 0)
- {
- ctx->filter = value;
- return HA_OK;
- }
-
- else if(strcmp(name, "ldapbase") == 0)
- {
- ctx->base = value;
- return HA_OK;
- }
-
- else if(strcmp(name, "ldappwattr") == 0)
- {
- ctx->pw_attr = value;
- return HA_OK;
- }
-
- else if(strcmp(name, "ldapha1attr") == 0)
- {
- ctx->ha1_attr = value;
- return HA_OK;
- }
-
- else if(strcmp(name, "ldapuser") == 0)
- {
- ctx->user = value;
- return HA_OK;
- }
-
- else if(strcmp(name, "ldappassword") == 0)
- {
- ctx->password = value;
- return HA_OK;
- }
-
- else if(strcmp(name, "ldapdnmap") == 0)
- {
- ctx->dnmap = value;
- return HA_OK;
- }
+ ldap_context_t* ctx = (ldap_context_t*)(context->ctx_data);
- else if(strcmp(name, "ldapscope") == 0)
- {
- if(strcmp(value, "sub") == 0 || strcmp(value, "subtree") == 0)
- ctx->scope = LDAP_SCOPE_SUBTREE;
- else if(strcmp(value, "base") == 0)
- ctx->scope = LDAP_SCOPE_BASE;
- else if(strcmp(value, "one") == 0 || strcmp(value, "onelevel") == 0)
- ctx->scope = LDAP_SCOPE_ONELEVEL;
+ ASSERT(name && value && value[0]);
- else
+ if(strcmp(name, "ldapservers") == 0)
{
- ha_messagex(LOG_ERR, "invalid value for '%s' (must be 'sub', 'base' or 'one')", name);
- return HA_FAILED;
+ ctx->servers = value;
+ return HA_OK;
}
- return HA_OK;
- }
-
- else if(strcmp(name, "ldapdobind") == 0)
- {
- return ha_confbool(name, value, &(ctx->dobind));
- }
-
- else if(strcmp(name, "ldapmax") == 0)
- {
- return ha_confint(name, value, 1, 256, &(ctx->ldap_max));
- }
-
- else if(strcmp(name, "ldaptimeout") == 0)
- {
- return ha_confint(name, value, 0, 86400, &(ctx->ldap_timeout));
- }
-
- return HA_FALSE;
-}
-
-int ldap_inithand(ha_context_t* context)
-{
- /* Global initialization */
- if(!context)
- {
- ha_messagex(LOG_DEBUG, "ldap: generating secret");
- return ha_genrandom(g_ldap_secret, DIGEST_SECRET_LEN);
- }
-
-
- /* Context specific initialization */
- else
- {
- ldap_context_t* ctx = (ldap_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)))
+ else if(strcmp(name, "ldapfilter") == 0)
{
- ha_messagex(LOG_ERR, "ldap: module configured, but does not implement any "
- "configured authentication type.");
- return HA_FAILED;
+ ctx->filter = value;
+ return HA_OK;
}
- /* Check for mandatory configuration */
- if(!ctx->servers)
+ else if(strcmp(name, "ldapbase") == 0)
{
- ha_messagex(LOG_ERR, "ldap: configuration incomplete. "
- "Must have LDAPServers.");
- return HA_FAILED;
+ ctx->base = value;
+ return HA_OK;
}
- if(!ctx->dnmap && (!ctx->filter || !ctx->base))
+ else if(strcmp(name, "ldappwattr") == 0)
{
- ha_messagex(LOG_ERR, "ldap: configuration incomplete. "
- "When not using LDAPDNMap must specify LDAPBase and LDAPFilter.");
- return HA_FAILED;
+ ctx->pw_attr = value;
+ return HA_OK;
}
- /* The cache for digest records and basic */
- if(!(ctx->cache = hsh_create(MD5_LEN)))
+ else if(strcmp(name, "ldapha1attr") == 0)
{
- ha_messagex(LOG_CRIT, "out of memory");
- return HA_CRITERROR;
+ ctx->ha1_attr = value;
+ return HA_OK;
}
- htc.f_freeval = free_hash_object;
- htc.arg = NULL;
- hsh_set_table_calls(ctx->cache, &htc);
-
- ASSERT(!ctx->pool);
- ASSERT(ctx->ldap_max > 0);
-
- /*
- * Our connection pool. It's the size of our maximum
- * amount of pending connections as that's the max
- * we'd be able to use at a time anyway.
- */
- ctx->pool = (LDAP**)malloc(sizeof(LDAP*) * ctx->ldap_max);
- if(!ctx->pool)
+ else if(strcmp(name, "ldapuser") == 0)
{
- ha_messagex(LOG_CRIT, "out of memory");
- return HA_CRITERROR;
+ ctx->user = value;
+ return HA_OK;
}
- memset(ctx->pool, 0, sizeof(LDAP*) * ctx->ldap_max);
-
- ha_messagex(LOG_INFO, "ldap: initialized handler");
- }
-
- return HA_OK;
-}
-
-void ldap_destroy(ha_context_t* context)
-{
- ldap_context_t* ctx;
- int i;
-
- if(!context)
- return;
-
- /* Note: We don't need to be thread safe here anymore */
- ctx = (ldap_context_t*)(context->ctx_data);
-
- ASSERT(ctx);
-
- if(ctx->cache)
- hsh_free(ctx->cache);
-
- if(ctx->pool)
- {
- /* Close any connections we have open */
- for(i = 0; i < ctx->ldap_max; i++)
+ else if(strcmp(name, "ldappassword") == 0)
{
- if(ctx->pool[i])
- ldap_unbind_s(ctx->pool[i]);
+ ctx->password = value;
+ return HA_OK;
}
- /* And free the connection pool */
- free(ctx->pool);
- }
-
- ha_messagex(LOG_INFO, "ldap: uninitialized handler");
-}
-
-int ldap_process(const ha_request_t* req, ha_response_t* resp)
-{
- ldap_context_t* ctx = (ldap_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);
+ else if(strcmp(name, "ldapdnmap") == 0)
+ {
+ ctx->dnmap = value;
+ return HA_OK;
+ }
- /* We use this below to detect whether to send a default response */
- resp->code = -1;
+ else if(strcmp(name, "ldapscope") == 0)
+ {
+ if(strcmp(value, "sub") == 0 || strcmp(value, "subtree") == 0)
+ ctx->scope = LDAP_SCOPE_SUBTREE;
+ else if(strcmp(value, "base") == 0)
+ ctx->scope = LDAP_SCOPE_BASE;
+ else if(strcmp(value, "one") == 0 || strcmp(value, "onelevel") == 0)
+ ctx->scope = LDAP_SCOPE_ONELEVEL;
+ else
+ {
+ ha_messagex(LOG_ERR, "invalid value for '%s' (must be 'sub', 'base' or 'one')", name);
+ return HA_FAILED;
+ }
+
+ return HA_OK;
+ }
+ else if(strcmp(name, "ldapdobind") == 0)
+ {
+ return ha_confbool(name, value, &(ctx->dobind));
+ }
- /* 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)
+ else if(strcmp(name, "ldapmax") == 0)
{
- ha_messagex(LOG_DEBUG, "ldap: processing digest auth header");
- ret = digest_ldap_response(ctx, header, req, resp);
- if(ret < 0)
- return ret;
+ return ha_confint(name, value, 1, 256, &(ctx->ldap_max));
}
- }
- /* Or a basic authentication */
- if(!header && req->context->allowed_types & HA_TYPE_BASIC)
- {
- header = ha_getheader(req, "Authorization", HA_PREFIX_BASIC);
- if(header)
+ else if(strcmp(name, "ldaptimeout") == 0)
{
- ha_messagex(LOG_DEBUG, "ldap: processing basic auth header");
- ret = basic_ldap_response(ctx, header, req, resp);
- if(ret < 0)
- return ret;
+ return ha_confint(name, value, 0, 86400, &(ctx->ldap_timeout));
}
- }
+ return HA_FALSE;
+}
- /* Send a default response if that's what we need */
- if(resp->code == -1)
- {
- resp->code = HA_SERVER_DECLINE;
+int ldap_inithand(ha_context_t* context)
+{
+ int r;
- if(req->context->allowed_types & HA_TYPE_BASIC)
- {
- ha_bufmcat(req->buf, "BASIC realm=\"", req->context->realm , "\"", NULL);
+ if((r = bd_init(context)) != HA_OK)
+ return r;
- if(ha_buferr(req->buf))
- return HA_CRITERROR;
+ /* Context specific initialization */
+ if(context)
+ {
+ ldap_context_t* ctx = (ldap_context_t*)(context->ctx_data);
+ ASSERT(ctx);
- ha_addheader(resp, "WWW-Authenticate", ha_bufdata(req->buf));
- ha_messagex(LOG_DEBUG, "ldap: sent basic auth request");
+ /* Check for mandatory configuration */
+ if(!ctx->servers)
+ {
+ ha_messagex(LOG_ERR, "ldap: configuration incomplete. "
+ "Must have LDAPServers.");
+ return HA_FAILED;
+ }
+
+ if(!ctx->dnmap && (!ctx->filter || !ctx->base))
+ {
+ ha_messagex(LOG_ERR, "ldap: configuration incomplete. "
+ "When not using LDAPDNMap must specify LDAPBase and LDAPFilter.");
+ return HA_FAILED;
+ }
+
+ ASSERT(!ctx->pool);
+ ASSERT(ctx->ldap_max > 0);
+
+ /*
+ * Our connection pool. It's the size of our maximum
+ * amount of pending connections as that's the max
+ * we'd be able to use at a time anyway.
+ */
+ ctx->pool = (LDAP**)malloc(sizeof(LDAP*) * ctx->ldap_max);
+ if(!ctx->pool)
+ {
+ ha_messagex(LOG_CRIT, "out of memory");
+ return HA_CRITERROR;
+ }
+
+ memset(ctx->pool, 0, sizeof(LDAP*) * ctx->ldap_max);
+ ha_messagex(LOG_INFO, "ldap: initialized handler");
}
- if(req->context->allowed_types & HA_TYPE_DIGEST)
+ return HA_OK;
+}
+
+void ldap_destroy(ha_context_t* context)
+{
+ if(context)
{
- ret = digest_ldap_challenge(ctx, req, resp, 0);
- if(ret < 0)
- return ret;
+ /* Note: We don't need to be thread safe here anymore */
+ ldap_context_t* ctx = (ldap_context_t*)(context->ctx_data);
+ int i;
+
+ ASSERT(ctx);
+
+ if(ctx->pool)
+ {
+ /* Close any connections we have open */
+ for(i = 0; i < ctx->ldap_max; i++)
+ {
+ if(ctx->pool[i])
+ ldap_unbind_s(ctx->pool[i]);
+ }
+
+ /* And free the connection pool */
+ free(ctx->pool);
+ }
}
- }
- return ret;
+ bd_destroy(context);
+ ha_messagex(LOG_INFO, "ldap: uninitialized handler");
}
+
/* -------------------------------------------------------------------------------
* Handler Definition
*/
ha_handler_t ldap_handler =
{
- "LDAP", /* The type */
- ldap_inithand, /* Initialization function */
- ldap_destroy, /* Uninitialization routine */
- ldap_config, /* Config routine */
- ldap_process, /* Processing routine */
- &ldap_defaults, /* The context defaults */
- sizeof(ldap_context_t)
+ "LDAP", /* The type */
+ ldap_inithand, /* Initialization function */
+ ldap_destroy, /* Uninitialization routine */
+ ldap_config, /* Config routine */
+ bd_process, /* Processing routine */
+ &ldap_defaults, /* The context defaults */
+ sizeof(ldap_context_t)
};
diff --git a/daemon/simple.c b/daemon/simple.c
index ee4fafa..d570a25 100644
--- a/daemon/simple.c
+++ b/daemon/simple.c
@@ -5,19 +5,14 @@
#include "usuals.h"
#include "httpauthd.h"
#include "defaults.h"
-#include "basic.h"
-#include "digest.h"
#include "hash.h"
+#include "bd.h"
#include "md5.h"
-#include <syslog.h>
+#include <stdio.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
@@ -25,736 +20,274 @@ unsigned char g_simple_secret[DIGEST_SECRET_LEN];
typedef struct simple_context
{
- /* Settings ----------------------------------------------------------- */
- const char* filename; /* The file name with the user names */
+ /* Base Handler ------------------------------------------------------ */
+ bd_context_t bd;
- /* Context ----------------------------------------------------------- */
- hsh_t* cache; /* Some cached records or basic */
+ /* Settings ---------------------------------------------------------- */
+ const char* filename; /* The file name with the user names */
}
simple_context_t;
+/* Forward declarations for callbacks */
+static int complete_digest(const ha_request_t* req, const char* user, unsigned char* ha1);
+static int validate_basic(const ha_request_t* req, const char* user, const char* password);
+
+/* The defaults for the context */
+static const simple_context_t simple_defaults =
+{
+ BD_CALLBACKS(complete_digest, validate_basic),
+ NULL /* filename */
+};
/* -------------------------------------------------------------------------------
* 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, 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(simple_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(simple_context_t* ctx, ha_context_t* c,
- digest_record_t* rec)
+static int complete_digest(const ha_request_t* req, const char* user, unsigned char* ha1)
{
- int r;
-
- ASSERT(ctx && c && 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)
- {
- free_hash_object(NULL, rec);
- ha_messagex(LOG_CRIT, "out of memory");
- return HA_CRITERROR;
- }
-
- return HA_OK;
-}
-
-static int add_cached_basic(simple_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 complete_digest_ha1(simple_context_t* ctx, digest_record_t* rec,
- const ha_request_t* req, const char* user)
-{
- FILE* f;
- char* t;
- char* t2;
- size_t len;
- char line[SIMPLE_MAXLINE];
- int ret = HA_FALSE;
-
- ASSERT(ctx && rec && req && user && user[0]);
- ha_messagex(LOG_DEBUG, "searching password file for user's ha1: %s", user);
-
- f = fopen(ctx->filename, "r");
- if(!f)
- {
- ha_message(LOG_ERR, "simple: can't open file for basic auth: %s", ctx->filename);
- return HA_FAILED;
- }
-
- /*
- * 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, "simple: error reading basic password file");
- ret = HA_FAILED;
- break;
- }
-
- t = strchr(line, ':');
- if(t)
- {
- /* Split the line */
- *t = 0;
- t++;
-
- /* Check the user */
- if(strcmp(line, user) == 0)
- {
- /* Otherwise it might be a digest type ha1. */
- t2 = strchr(t, ':');
- if(t2)
- {
- *t2 = 0;
- t2++;
-
- /* Check the realm */
- if(strcmp(t, req->context->realm) == 0)
- {
- len = MD5_LEN;
-
- /* Now try antd decode the ha1 */
- t = ha_bufdechex(req->buf, t2, &len);
- if(t && len == MD5_LEN)
- {
- ha_messagex(LOG_DEBUG, "simple: found ha1 for user: %s", user);
- memcpy(rec->ha1, t, MD5_LEN);
- ret = HA_OK;
- break;
- }
- }
- }
-
- if(!t2 || ret != HA_OK)
- ha_messagex(LOG_WARNING, "simple: user '%s' found in file, but password not in digest format", user);
- }
- }
- }
-
- fclose(f);
-
- if(ha_buferr(req->buf))
- return HA_CRITERROR;
-
- return ret;
-}
-
-static int validate_user_password(simple_context_t* ctx, const ha_request_t* req,
- const char* user, const char* clearpw)
-{
- FILE* f;
- char line[SIMPLE_MAXLINE];
- unsigned char ha1[MD5_LEN];
- char* t;
- char* t2;
- size_t len;
- int ret = HA_FALSE;
-
- ASSERT(ctx && req);
- ASSERT(user && user[0] && clearpw);
- ha_messagex(LOG_DEBUG, "simple: validating user against password file: %s", user);
-
- f = fopen(ctx->filename, "r");
- if(!f)
- {
- ha_message(LOG_ERR, "simple: can't open file for basic auth: %s", ctx->filename);
- return HA_FAILED;
- }
-
- digest_makeha1(ha1, user, req->context->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))
+ simple_context_t* ctx = (simple_context_t*)req->context->ctx_data;
+ FILE* f;
+ char* t;
+ char* t2;
+ size_t len;
+ char line[SIMPLE_MAXLINE];
+ int ret = HA_FALSE;
+
+ ASSERT(ctx && req && user && user[0]);
+ ha_messagex(LOG_DEBUG, "searching password file for user's ha1: %s", user);
+
+ f = fopen(ctx->filename, "r");
+ if(!f)
{
- ha_message(LOG_ERR, "simple: error reading basic password file");
- ret = HA_FAILED;
- break;
+ ha_message(LOG_ERR, "simple: can't open file for basic auth: %s", ctx->filename);
+ return HA_FAILED;
}
- /* Take white space off end of line */
- trim_end(line);
+ /*
+ * Note: There should be no returns or jumps between
+ * this point and the closing of the file below.
+ */
- t = strchr(line, ':');
- if(t)
+ /* Now look through the whole file */
+ while(!feof(f))
{
- /* Split the line */
- *t = 0;
- t++;
-
- /* Check the user */
- if(strcmp(line, user) == 0)
- {
- /* Not sure if crypt is thread safe so we lock */
- ha_lock(NULL);
+ fgets(line, SIMPLE_MAXLINE, f);
- /* Check the password */
- t2 = crypt(clearpw, t);
-
- ha_unlock(NULL);
-
- if(strcmp(crypt(clearpw, t), t) == 0)
+ if(ferror(f))
{
- ha_messagex(LOG_DEBUG, "simple: found valid crypt password for user: %s", user);
- ret = HA_OK;
- break;
+ ha_message(LOG_ERR, "simple: error reading basic password file");
+ ret = HA_FAILED;
+ break;
}
- /* Otherwise it might be a digest type ha1. */
- t2 = strchr(t, ':');
- if(t2)
+ t = strchr(line, ':');
+ if(t)
{
- *t2 = 0;
- t2++;
-
- /* Check the realm */
- if(strcmp(t, req->context->realm) == 0)
- {
- len = MD5_LEN;
+ /* Split the line */
+ *t = 0;
+ t++;
- /* Now try antd decode the ha1 */
- t = ha_bufdechex(req->buf, t2, &len);
- if(t && len == MD5_LEN && memcmp(ha1, t, MD5_LEN) == 0)
+ /* Check the user */
+ if(strcmp(line, user) == 0)
{
- ha_messagex(LOG_DEBUG, "simple: found valid ha1 for user: %s", user);
- ret = HA_OK;
- break;
+ /* Otherwise it might be a digest type ha1. */
+ t2 = strchr(t, ':');
+ if(t2)
+ {
+ *t2 = 0;
+ t2++;
+
+ /* Check the realm */
+ if(strcmp(t, req->context->realm) == 0)
+ {
+ len = MD5_LEN;
+
+ /* Now try and decode the ha1 */
+ t = ha_bufdechex(req->buf, t2, &len);
+ if(t && len == MD5_LEN)
+ {
+ ha_messagex(LOG_DEBUG, "simple: found ha1 for user: %s", user);
+ memcpy(ha1, t, MD5_LEN);
+ ret = HA_OK;
+ break;
+ }
+ }
+ }
+
+ if(!t2 || ret != HA_OK)
+ ha_messagex(LOG_WARNING, "simple: user '%s' found in file, but password not in digest format", user);
}
- }
}
-
- if(ha_buferr(req->buf))
- break;
- }
}
- }
-
- fclose(f);
-
- if(ha_buferr(req->buf))
- return HA_CRITERROR;
-
- return ret;
-}
-
-static int simple_basic_response(simple_context_t* ctx, const char* header,
- const ha_request_t* req, ha_response_t* resp)
-{
- basic_header_t basic;
- int ret = HA_FALSE;
- int found = 0;
- int r;
-
- ASSERT(header && req && resp);
-
- if((r = basic_parse(header, req->buf, &basic)) < 0)
- return r;
-
- /* 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, "simple: validated basic user against cache: %s", basic.user);
- 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, req, basic.user, basic.password);
-
- if(ret == HA_OK)
- ha_messagex(LOG_NOTICE, "simple: validated basic user against file: %s", basic.user);
-
- else
- ha_messagex(LOG_WARNING, "simple: basic authentication failed for user: %s", basic.user);
-finally:
+ fclose(f);
- 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 simple_digest_challenge(simple_context_t* ctx, const ha_request_t* req,
- ha_response_t* resp, int stale)
-{
- const char* nonce_str;
- const char* header;
-
- ASSERT(ctx && req && resp);
-
- /* Generate an nonce */
-
-#ifdef _DEBUG
- if(req->context->digest_debugnonce)
- {
- nonce_str = req->context->digest_debugnonce;
- ha_messagex(LOG_WARNING, "simple: using debug nonce. security non-existant.");
- }
- else
-#endif
- {
- unsigned char nonce[DIGEST_NONCE_LEN];
- digest_makenonce(nonce, g_simple_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);
+ if(ha_buferr(req->buf))
+ return HA_CRITERROR;
- ha_messagex(LOG_DEBUG, "simple: created digest challenge with nonce: %s", nonce_str);
- return HA_OK;
+ return ret;
}
-static int simple_digest_response(simple_context_t* ctx, const char* header,
- const ha_request_t* req, ha_response_t* resp)
+static int validate_basic(const ha_request_t* req, const char* user, const char* password)
{
- 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, "simple: 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_simple_secret, &expiry);
- if(r != HA_OK)
+ simple_context_t* ctx = (simple_context_t*)req->context->ctx_data;
+ FILE* f;
+ char line[SIMPLE_MAXLINE];
+ unsigned char ha1[MD5_LEN];
+ char* t;
+ char* t2;
+ size_t len;
+ int ret = HA_FALSE;
+
+ ASSERT(ctx && req);
+ ASSERT(user && user[0] && password);
+ ha_messagex(LOG_DEBUG, "simple: validating user against password file: %s", user);
+
+ f = fopen(ctx->filename, "r");
+ if(!f)
{
- if(r == HA_FALSE)
- ha_messagex(LOG_WARNING, "simple: digest response contains invalid nonce");
-
- ret = r;
- goto finally;
+ ha_message(LOG_ERR, "simple: can't open file for basic auth: %s", ctx->filename);
+ return HA_FAILED;
}
- }
- 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, "simple: nonce expired, sending stale challenge: %s",
- dg.username);
-
- stale = 1;
- goto finally;
- }
-
- if(!rec)
- {
- ha_messagex(LOG_INFO, "simple: no record in cache, creating one: %s", dg.username);
+ digest_makeha1(ha1, user, req->context->realm, password);
/*
- * If we're valid but don't have a record in the
- * cache then complete the record properly.
+ * Note: There should be no returns or jumps between
+ * this point and the closing of the file below.
*/
- rec = digest_makerec(nonce, dg.username);
- if(!rec)
+ /* Now look through the whole file */
+ while(!feof(f))
{
- ret = HA_CRITERROR;
- goto finally;
- }
+ fgets(line, SIMPLE_MAXLINE, f);
- r = complete_digest_ha1(ctx, rec, req, dg.username);
- 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, "simple: nonce almost expired, creating new one: %s",
- dg.username);
-
- digest_makenonce(nonce, g_simple_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, "simple: validated digest user: %s", dg.username);
+ if(ferror(f))
+ {
+ ha_message(LOG_ERR, "simple: error reading basic password file");
+ ret = HA_FAILED;
+ break;
+ }
- /* Put the connection into the cache */
- if((r = save_cached_digest(ctx, req->context, rec)) < 0)
- ret = r;
+ /* Take white space off end of line */
+ trim_end(line);
- rec = NULL;
- }
+ t = strchr(line, ':');
+ if(t)
+ {
+ /* Split the line */
+ *t = 0;
+ t++;
-finally:
+ /* Check the user */
+ if(strcmp(line, user) == 0)
+ {
+ /* Not sure if crypt is thread safe so we lock */
+ ha_lock(NULL);
+
+ /* Check the password */
+ t2 = crypt(password, t);
+
+ ha_unlock(NULL);
+
+ if(strcmp(t2, t) == 0)
+ {
+ ha_messagex(LOG_DEBUG, "simple: found valid crypt password for user: %s", user);
+ ret = HA_OK;
+ break;
+ }
+
+ /* Otherwise it might be a digest type ha1. */
+ t2 = strchr(t, ':');
+ if(t2)
+ {
+ *t2 = 0;
+ t2++;
+
+ /* Check the realm */
+ if(strcmp(t, req->context->realm) == 0)
+ {
+ len = MD5_LEN;
+
+ /* Now try antd decode the ha1 */
+ t = ha_bufdechex(req->buf, t2, &len);
+ if(t && len == MD5_LEN && memcmp(ha1, t, MD5_LEN) == 0)
+ {
+ ha_messagex(LOG_DEBUG, "simple: found valid ha1 for user: %s", user);
+ ret = HA_OK;
+ break;
+ }
+ }
+ }
+
+ if(ha_buferr(req->buf))
+ break;
+ }
+ }
+ }
- /* If the record wasn't stored away then free it */
- if(rec)
- free(rec);
+ fclose(f);
- /* If nobody above responded then challenge the client again */
- if(resp->code == -1)
- return simple_digest_challenge(ctx, req, resp, stale);
+ if(ha_buferr(req->buf))
+ return HA_CRITERROR;
- return ret;
+ 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->ctx_data);
-
- ASSERT(name && name[0] && value && value[0]);
-
- if(strcmp(name, "passwordfile") == 0)
- {
- ctx->filename = value;
- return HA_OK;
- }
-
- return HA_FALSE;
-}
-
-int simple_init(ha_context_t* context)
-{
- /* Global initialization */
- if(!context)
- {
- ha_messagex(LOG_DEBUG, "simple: generating secret");
- return ha_genrandom(g_simple_secret, DIGEST_SECRET_LEN);
- }
-
- /* Context specific initialization */
- else
- {
simple_context_t* ctx = (simple_context_t*)(context->ctx_data);
- hsh_table_calls_t htc;
- int fd;
-
- 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, "simple: module configured, but does not implement any "
- "configured authentication type.");
- return HA_FAILED;
- }
-
- /* Check to make sure the file exists */
- if(!ctx->filename)
- {
- ha_messagex(LOG_ERR, "simple: configuration incomplete. "
- "Must have a PasswordFile configured.");
- return HA_FAILED;
- }
+ ASSERT(name && name[0] && value && value[0]);
- fd = open(ctx->filename, O_RDONLY);
- if(fd == -1)
+ if(strcmp(name, "passwordfile") == 0)
{
- ha_message(LOG_ERR, "simple: can't open file for authentication: %s", ctx->filename);
- return HA_FAILED;
+ ctx->filename = value;
+ return HA_OK;
}
- close(fd);
-
- ASSERT(!ctx->cache);
-
- /* 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, "simple: initialized handler");
- }
-
- return HA_OK;
-}
-
-void simple_destroy(ha_context_t* context)
-{
- /* Per context destroy */
- if(context)
- {
- /* Note: We don't need to be thread safe here anymore */
- simple_context_t* ctx = (simple_context_t*)(context->ctx_data);
-
- if(ctx->cache)
- hsh_free(ctx->cache);
-
- ha_messagex(LOG_INFO, "simple: uninitialized handler");
- }
+ return HA_FALSE;
}
-int simple_process(const ha_request_t* req, ha_response_t* resp)
+int simple_init(ha_context_t* context)
{
- simple_context_t* ctx = (simple_context_t*)(req->context->ctx_data);
- const char* header = NULL;
- int ret = HA_FALSE;
- int found = 0;
- basic_header_t basic;
- int r;
-
- ASSERT(req && resp);
- ASSERT(req->args[AUTH_ARG_METHOD]);
- ASSERT(req->args[AUTH_ARG_URI]);
-
- ha_lock(NULL);
-
- /* Purge the cache */
- r = hsh_purge(ctx->cache, time(NULL) - req->context->cache_timeout);
+ int r;
- ha_unlock(NULL);
+ if((r = bd_init(context)) != HA_OK)
+ return r;
- if(r > 0)
- ha_messagex(LOG_DEBUG, "simple: 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, "simple: processing digest auth header");
- ret = simple_digest_response(ctx, header, req, resp);
- if(ret < 0)
- return ret;
- }
- }
-
- /* Or a basic authentication */
- if(!header && req->context->allowed_types & HA_TYPE_BASIC)
- {
- ha_messagex(LOG_DEBUG, "simple: processing basic auth header");
- header = ha_getheader(req, "Authorization", HA_PREFIX_BASIC);
- if(header)
+ if(context)
{
- ret = simple_basic_response(ctx, header, req, resp);
- if(ret < 0)
- return ret;
- }
- }
-
+ simple_context_t* ctx = (simple_context_t*)(context->ctx_data);
+ int fd;
- /* Send a default response if that's what we need */
- if(resp->code == -1)
- {
- resp->code = HA_SERVER_DECLINE;
+ ASSERT(ctx);
- if(req->context->allowed_types & HA_TYPE_BASIC)
- {
- ha_bufmcat(req->buf, "BASIC realm=\"", req->context->realm , "\"", NULL);
+ /* Check to make sure the file exists */
+ if(!ctx->filename)
+ {
+ ha_messagex(LOG_ERR, "simple: configuration incomplete. "
+ "Must have a PasswordFile configured.");
+ return HA_FAILED;
+ }
- if(ha_buferr(req->buf))
- return HA_CRITERROR;
+ fd = open(ctx->filename, O_RDONLY);
+ if(fd == -1)
+ {
+ ha_message(LOG_ERR, "simple: can't open file for authentication: %s", ctx->filename);
+ return HA_FAILED;
+ }
- ha_addheader(resp, "WWW-Authenticate", ha_bufdata(req->buf));
- ha_messagex(LOG_DEBUG, "simple: sent basic auth request");
- }
+ close(fd);
- if(req->context->allowed_types & HA_TYPE_DIGEST)
- {
- ret = simple_digest_challenge(ctx, req, resp, 0);
- if(ret < 0)
- return ret;
+ ha_messagex(LOG_INFO, "simple: initialized handler");
}
- }
- return ret;
+ return HA_OK;
}
@@ -766,9 +299,9 @@ ha_handler_t simple_handler =
{
"SIMPLE", /* The type */
simple_init, /* Initialization function */
- simple_destroy, /* Uninitialization routine */
+ bd_destroy, /* Uninitialization routine */
simple_config, /* Config routine */
- simple_process, /* Processing routine */
+ bd_process, /* Processing routine */
NULL, /* A default context */
sizeof(simple_context_t) /* Size of the context */
};