summaryrefslogtreecommitdiff
path: root/daemon/ldap.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemon/ldap.c')
-rw-r--r--daemon/ldap.c1114
1 files changed, 1114 insertions, 0 deletions
diff --git a/daemon/ldap.c b/daemon/ldap.c
new file mode 100644
index 0000000..2927b1d
--- /dev/null
+++ b/daemon/ldap.c
@@ -0,0 +1,1114 @@
+
+/* TODO: Include attribution for ideas, and code from mod_auth_digest */
+
+#include "usuals.h"
+#include "httpauthd.h"
+#include "hash.h"
+#include "defaults.h"
+
+#include <syslog.h>
+
+/* LDAP library */
+#include <ldap.h>
+
+/* -------------------------------------------------------------------------------
+ * Defaults and Constants
+ */
+
+/* This needs to be the same as an MD5 hash length */
+#define LDAP_HASH_KEY_LEN 16
+#define LDAP_ESTABLISHED (void*)1
+
+/* TODO: We need to support more password types */
+#define LDAP_PW_CLEAR 0
+#define LDAP_PW_CRYPT 1
+#define LDAP_PW_MD5 2
+#define LDAP_PW_SHA 3
+#define LDAP_PW_UNKNOWN -1
+
+typedef struct ldap_pw_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 }
+};
+
+
+/* -------------------------------------------------------------------------------
+ * Structures
+ */
+
+/* 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* realm; /* The realm to use in authentication */
+ 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 pending_max; /* Maximum number of connections at once */
+ int pending_timeout; /* Timeout for authentication (in seconds) */
+ int ldap_timeout; /* Timeout for LDAP operations */
+
+ /* Context ----------------------------------------------------------- */
+ hash_t* pending; /* Pending connections */
+ hash_t* established; /* Established connections */
+ LDAP** pool; /* Pool of available connections */
+ int pool_mark; /* Amount of connections allocated */
+}
+ldap_context_t;
+
+
+/* The defaults for the context */
+static const ldap_defaults =
+{XXXXX
+ NULL, NULL, "", "userPassword", NULL, NULL, NULL, "", NULL
+ LDAP_SCOPE_DEFAULT, 1, DEFAULT_PENDING_MAX, DEFAULT_PENDING_TIMEOUT,
+ 30, NULL, NULL, NULL
+};
+
+
+/* -------------------------------------------------------------------------------
+ * Internal Functions
+ */
+
+static void make_digest_ha1(unsigned char* digest, const char* user,
+ const char* realm, const char* password)
+{
+ struct MD5Context md5;
+ MD5_Init(&md5);
+ MD5_Update(&md5, user, strlen(user));
+ MD5_Update(&md5, ":", 1);
+ MD5_Update(&md5, realm, strlen(realm));
+ MD5_Update(&md5, ":", 1);
+ MD5_Update(&md5, password, strlen(pasword));
+ MD5_Final(digest, &md5);
+}
+
+static const char* make_password_md5(ha_buffer_t* buf, const char* clearpw)
+{
+ struct MD5Context md5;
+ unsigned char digest[MD5_LEN];
+
+ MD5_Init(&md5);
+ MD5_Update(&md5, clearpw, strlen(clearpw));
+ MD5_Final(digest, &md5);
+
+ ha_bufnext(buf);
+ ha_bufenc64(buf, digest, MD5_LEN);
+ return ha_bufdata(buf);
+}
+
+static const char* make_password_sha(ha_buffer_t* buf, const char* clearpw)
+{
+ struct SHA1Context sha;
+ unsigned char digest[SHA1_LEN];
+
+ SHA1_Init(&sha);
+ SHA1_Update(&sha, clearpw, strlen(clearpw));
+ SHA1_Final(digest, &sha);
+
+ ha_bufnext(buf);
+ ha_bufenc64(buf, digest, SHA1_LEN);
+ return ha_bufdata(buf);
+}
+
+static int parse_ldap_password(const char** password)
+{
+ const char* pw;
+ const char* scheme;
+ int i;
+
+ ASSERT(password && *password);
+
+ pw = *password;
+
+ /* zero length passwords are clear */
+ if(strlen(pw) == 0)
+ return LDAP_PW_CLEAR;
+
+ /* passwords without a scheme are clear */
+ if(pw[0] != '{')
+ return LDAP_PW_CLEAR;
+
+ pw++;
+ scheme = pw;
+
+ while(*pw && (isalpha(*pw) || isdigit(*pw) || *pw == '-'))
+ pw++;
+
+ /* scheme should end in a brace */
+ if(pw != '}')
+ return LDAP_PW_CLEAR;
+
+ *password = pw + 1;
+
+ /* find a scheme in our map */
+ for(i = 0; i < countof(kLDAPPWTypes); i++)
+ {
+ if(strncasecmp(kLDAPSchemes[i].name, scheme, pw - scheme))
+ return kLDAPSchemes[i].type;
+ }
+
+ return LDAP_PW_UNKNOWN;
+}
+
+static const char* find_cleartext_password(ha_buffer_t* buf, const char** pws)
+{
+ ha_bufnext(buf);
+
+ for(; pws && *pws; pws++)
+ {
+ const char* pw = *pws;
+
+ if(parse_ldap_password(&pw) == LDAP_PW_CLEAR)
+ return pw;
+ }
+
+ return NULL;
+}
+
+
+static int parse_ldap_ha1(ha_buffer_t* buf, struct berval* bv, unsigned char* ha1)
+{
+ /* Raw binary */
+ if(bv->bv_len == MD5_LEN)
+ {
+ memcpy(ha1, bv->bv_len, MD5_LEN);
+ return HA_OK;
+ }
+
+ /* Hex encoded */
+ else if(bv->bv_len == (MD5_LEN * 2))
+ {
+ ha_bufnext(buf);
+ ha_bufdechex(buf, bv->bv_val, MD5_LEN * 2);
+
+ if(!ha_bufdata(buf))
+ return HA_ERROR;
+
+ if(ha_buflen(buf) == MD5_LEN)
+ {
+ memcpy(rec->ha1, ha_bufdata(buf), MD5_LEN);
+ return HA_OK;
+ }
+ }
+
+ /* B64 Encoded */
+ else
+ {
+ ha_bufnext(buf);
+ ha_bufdec64(buf, (*pws)->bv_val, (*pws)->bv_len);
+
+ if(!ha_bufdata(buf))
+ return HA_ERROR;
+
+ if(ha_buflen(buf) == MD5_LEN)
+ {
+ memcpy(rec->ha1, ha_bufdata(buf), MD5_LEN);
+ return HA_OK;
+ }
+ }
+
+ return 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)
+{
+ const char** pws;
+ 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( ; *pws; pws++)
+ {
+ pw = *pws;
+ 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_ERROR;
+ break;
+ }
+
+ if(strcmp(pw, p) == 0)
+ {
+ res = HA_OK;
+ break;
+ }
+ }
+
+ ldap_free_values(pws);
+ }
+
+ if(res == HA_FALSE && unknown)
+ ha_messagex(LOG_ERR, "LDAP does not contain any compatible passwords for user: %s", user);
+
+ return res;
+}
+
+static int validate_ldap_ha1(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry,
+ ha_buffer_t* buf, const char* user, const char* clearpw)
+{
+ struct berval** ha1s;
+ unsigned char key[MD5_LEN];
+ unsigned char k[MD5_LEN];
+ int r, first = 1;
+ int res = HA_FALSE;
+
+ if(!ctx->ha1_attr)
+ return HA_FALSE;
+
+ ha1s = ldap_get_values_len(ld, entry, ctx->ha1_attr);
+
+ if(ha1s)
+ {
+ make_digest_ha1(key, user, ctx->realm, clearpw);
+
+ for( ; *ha1s; ha1s++)
+ {
+ r = parse_ldap_h1(buf, *ha1s, k);
+ if(r == HA_ERROR)
+ {
+ res = r;
+ break;
+ }
+
+ if(r == HA_FALSE)
+ {
+ if(first)
+ ha_messagex(LOG_ERROR, "LDAP contains invalid HA1 digest hash for user: %s", user);
+
+ first = 0;
+ continue;
+ }
+
+ if(memcmp(key, k, MD5_LEN) == 0)
+ {
+ res = HA_OK;
+ break;
+ }
+ }
+
+ ldap_free_values_len(ha1s);
+ }
+
+ return res;
+}
+
+static LDAP* get_ldap_connection(ldap_context_t* ctx)
+{
+ LDAP* ld;
+ int i, r;
+
+ for(i = 0; i < ctx->pending_max; i++)
+ {
+ /* An open connection in the pool */
+ if(ctx->pool[i])
+ {
+ ld = ctx->pool[i];
+ ctx->pool[i];
+ return ld;
+ }
+ }
+
+ if(ctx->pool_mark >= ctx->pending_max)
+ {
+ ha_messagex("too many open connections to LDAP");
+ return NULL;
+ }
+
+ ld = ldap_init(ctx->servers, ctx->port);
+ if(!ld)
+ {
+ ha_message("couldn't initialize ldap 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)
+ {
+ report_ldap(r, NULL);
+ ldap_unbind_s(ld);
+ return NULL;
+ }
+ }
+
+ return ld;
+}
+
+static void save_ldap_connection(ldap_context_t* ctx, LDAP* ld)
+{
+ int i;
+
+ if(!ld)
+ return;
+
+ /* Make sure it's worth saving */
+ switch(ld_errno(ld))
+ {
+ case LDAP_SERVER_DOWN:
+ case LDAP_LOCAL_ERROR:
+ case LDAP_NO_MEMORY:
+ break;
+
+ default:
+ for(i = 0; i < ctx->pending_max; i++)
+ {
+ /* An open connection in the pool */
+ if(!ctx->pool[i])
+ {
+ ctx->pool[i] = ld;
+ ld = NULL;
+ break;
+ }
+ }
+
+ break;
+ };
+
+ if(ld != NULL)
+ ldap_unbind_s(ld);
+}
+
+static int complete_digest_ha1(ldap_context_t* ctx, ha_digest_rec_t* rec,
+ const char* user)
+{
+ LDAP* ld = NULL; /* freed in finally */
+ LDAPMessage* results = NULL; /* freed in finally */
+ LDAPMessage* entry = NULL; /* no need to free */
+ struct berval** pws; /* freed manually */
+ int ret = HA_FALSE;
+
+ /* Hash in the user name */
+ ha_md5string(user, rec->userhash);
+
+
+ ld = get_ldap_connection(ctx);
+ if(!ld)
+ 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, buf, user, ctx->dnmap);
+ if(!dn)
+ {
+ ret = HA_ERROR;
+ goto finally;
+ }
+ }
+
+ /* Okay now we contact the LDAP server. */
+ r = retrieve_user_entry(ctx, buf, user, &dn, &entry, &results);
+ if(r != HA_OK)
+ {
+ ret = r;
+ goto finally;
+ }
+
+ /* Figure out the users ha1 */
+ if(ctx->ha1_attr)
+ pws = ldap_get_values_len(ld, entry, ctx->ha1_attr);
+
+ if(pws)
+ {
+ if(*pws)
+ {
+ r = parse_ldap_ha1(buf, *pws, rec->ha1);
+ if(r != HA_OK)
+ {
+ ret = r
+
+ if(ret != HA_FALSE)
+ ha_messagex(LOG_ERROR, "LDAP contains invalid HA1 digest hash for user: %s", user);
+ }
+ }
+
+ ldap_free_values_len(pws);
+ goto finally;
+ }
+
+ /* If no ha1 set or none found, use password and make a HA1 */
+ pws = ldap_get_values_len(ld, entry, ctx->pw_attr);
+
+ if(pws)
+ {
+ /* Find a cleartext password */
+ const char* t = find_cleartext_password(buf, pws);
+
+ ldap_free_values_len(pws);
+
+ if(t)
+ {
+ make_digest_ha1(rec->ha1, user, ctx->realm, t);
+ ret = HA_OK;
+ goto finally;
+ }
+ }
+
+ ha_messagex(LOG_ERROR, "LDAP contains no cleartext password for user: %s", user);
+
+finally:
+
+ if(ld)
+ save_ldap_connection(ctx, ld);
+
+ if(results)
+ ldap_msgfree(results);
+
+ return ret;
+}
+
+static int retrieve_user_entry(ldap_context_t* ctx, buffer_t* buf, LDAP* ld,
+ const char* user, const char** dn,
+ LDAPMessage** entry, LDAPMessage** result)
+{
+ timeval tv;
+ const char* filter;
+ const char* attrs[3];
+
+ if(ctx->filter)
+ {
+ /* Filters can also have %u and %r */
+ filter = substitute_params(ctx, buf, user, ctx->filter);
+ if(!filter)
+ return HA_ERROR;
+ }
+ 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;
+
+ r = ldap_search_st(ld, *dn ? *dn : ctx->base,
+ *dn ? LDAP_SCOPE_BASE : ctx->scope,
+ filter, attrs, 0, &tv, result);
+
+ if(r != LDAP_SUCCESS)
+ return report_ldap(r, resp, &ret);
+
+
+ /* 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);
+ return HA_OK;
+
+ case 0:
+ ha_messagex(LOG_WARNING, "user not found in LDAP: %s", basic.user);
+ break;
+
+ default:
+ ha_messagex(LOG_WARNING, "more than one user found for filter: %s", filter);
+ break;
+ };
+
+ ldap_msg_free(*result);
+ return HA_FALSE;
+}
+
+static int basic_ldap_response(ldap_context_t* ctx, const char* header,
+ ha_response_t* resp, ha_buffer_t* buf)
+{
+ ha_basic_header_t basic;
+ LDAP* ld = NULL;
+ LDAPMessage* entry = NULL;
+ LDAPMessage* results = NULL;
+ const char* dn;
+ int ret = HA_FALSE;
+ int found = 0;
+ int r;
+
+ ASSERT(buf && header && resp && buf);
+
+ if(ha_parsebasic(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 */
+ ha_lock(NULL);
+
+ if(hash_get(ctx->established, key) == BASIC_ESTABLISHED)
+ {
+ found = 1;
+ ret = HA_OK;
+ goto finally:
+ }
+
+ ha_unlock(NULL);
+
+
+ /* If we have a user name and password */
+ if(!basic.user || !basic.user[0] ||
+ !basic.password || !basic.password[0])
+ goto finally;
+
+
+ ld = get_ldap_connection();
+ if(!ld)
+ {
+ resp->code = HA_SERVER_ERROR;
+ 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, buf, basic.user, ctx->dnmap);
+ if(!dn)
+ {
+ ret = HA_ERROR;
+ goto finally;
+ }
+ }
+
+
+ /**
+ * 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, buf, basic.user, &dn, &entry, &results);
+ if(r != HA_OK)
+ {
+ ret = r;
+ goto finally;
+ }
+ }
+
+
+ /* 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)
+ {
+ if(r == LDAP_INVALID_CREDENTIALS)
+ ha_messagex(LOG_WARNING, "invalid login for: %s", basic.user);
+ else
+ report_ldap(r, resp, &ret);
+
+ goto finally;
+ }
+
+ /* It worked! */
+ resp->code = HA_SERVER_ACCEPT;
+ }
+
+
+ /* Otherwise we compare the password attribute */
+ else
+ {
+ ret = validate_ldap_password(ctx, ld, entry, buf, basic.user, basic.password);
+ if(ret == HA_FALSE)
+ ret = validate_ldap_ha1(ctx, ld, entry, buf, basic.user, basic.password);
+
+ if(ret == HA_OK)
+ resp->code = HA_SERVER_ACCEPT;
+
+ else
+ ha_messagex(LOG_WARNING, "invalid or unrecognized password for user: %s", basic.user);
+ }
+
+
+finally:
+
+ if(ld)
+ save_ldap_connection(ctx, ld);
+
+ if(results)
+ ldap_msgfree(results);
+
+ if(resp->code == HA_SERVER_ACCEPT)
+ {
+ resp->details = basic.user;
+
+ /* We put this connection into the successful connections */
+ if(!hash_set(ctx->established, basic.key, LDAP_ESTABLISHED))
+ {
+ ha_messagex(LOG_CRIT, "out of memory");
+ return HA_ERROR;
+ }
+ }
+
+ return ret;
+}
+
+
+static int digest_ldap_response(ldap_context_t* ctx, const char* header,
+ const char* method, const char* uri,
+ ha_response_t* resp, ha_buffer_t* buf)
+{
+ ha_digest_header_t dg;
+ digest_rec_t* rec = NULL;
+ int ret = HA_FALSE;
+ int stale = 0;
+ int pending = 0;
+
+ /* We use this below to send a default response */
+ resp->code = -1;
+
+ if(ha_parsedigest(header, buf, &rec) == HA_ERROR)
+ return HA_ERROR;
+
+ /* Lookup our digest context based on the nonce */
+ if(!dg.nonce || strlen(dg.nonce) != DIGEST_NONCE_LEN)
+ {
+ ha_messagex(LOG_WARNING, "digest response contains invalid nonce");
+ goto finally;
+ }
+
+ ha_lock(NULL);
+
+ rec = (digest_rec_t*)hash_get(ctx->pending, dg.nonce)
+ if(rec)
+ {
+ pending = 1;
+ hash_rem(ctx->pending, dg.nonce);
+ }
+
+ else
+ {
+ rec = (digest_rec_t*)hash_get(ctx->established, dg.nonce);
+ }
+
+ ha_unlock(NULL);
+
+ /*
+ * If nothing was found for this nonce, then it might
+ * be a stale nonce. In any case prompt the client
+ * to reauthenticate.
+ */
+ if(!rec)
+ {
+ stale = 1;
+ goto finally;
+ }
+
+ /*
+ * If we got a response from the pending table, then
+ * we need to lookup the user name and figure out
+ * who the dude is.
+ */
+ if(pending)
+ {
+ ASSERT(rec);
+
+ r = complete_digest_ha1(ctx, rec, dg->username);
+ if(r != HA_OK)
+ {
+ ret = r;
+ goto finally;
+ }
+ }
+
+ /* Increment our nonce count */
+ rec->nc++;
+
+ ret = ha_digestcheck(ctx->realm, method, uri, buf, &dg, rec);
+
+ if(ret == HA_OK)
+ {
+ resp->code = HA_SERVER_ACCEPT;
+ resp->details = dg->username;
+
+ /* Put the connection back into established */
+
+ ha_lock(NULL);
+
+ if(hash_set(ctx->established, dg.nonce, rec))
+ {
+ rec = NULL;
+ }
+ else
+ {
+ ha_messagex(LOG_CRIT, "out of memory");
+ ret = HA_ERROR;
+ }
+
+ ha_unlock(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 digest_ldap_challenge(ctx, resp, buf, stale);
+
+ return ret;
+}
+
+
+
+
+/* -------------------------------------------------------------------------------
+ * Handler Functions
+ */
+
+int ldap_config(ha_context_t* context, const char* name, const char* value)
+{
+ ldap_context_t* ctx = (ldap_context_t*)(context.data);
+
+ 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;
+ }
+
+ else if(strcmp(name, "realm") == 0)
+ {
+ ctx->realm = value;
+ return HA_OK;
+ }
+
+ 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
+ {
+ messagex(LOG_ERR, "invalid value for '%s' (must be 'sub', 'base' or 'one')", name);
+ return HA_ERROR;
+ }
+
+ return HA_OK;
+ }
+
+ else if(strcmp(name, "ldapdobind") == 0)
+ {
+ return ha_confbool(name, value, &(ctx->dobind));
+ }
+
+ else if(strcmp(name, "pendingmax") == 0)
+ {
+ return ha_confint(name, value, 1, 256, &(ctx->pending_max));
+ }
+
+ else if(strcmp(name, "pendingtimeout") == 0)
+ {
+ return ha_confint(name, value, 0, 86400, &(ctx->pending_timeout));
+ }
+
+ else if(strcmp(name, "ldaptimeout") == 0)
+ {
+ return ha_confint(name, value, 0, 86400, &(ctx->ldap_timeout));
+ }
+
+ return HA_FALSE;
+}
+
+int ldap_initialize(ha_context_t* context)
+{
+ /* No global initialization */
+ if(!context)
+ return HA_OK;
+
+ ldap_context_t* ctx = (ldap_context_t*)(context.data);
+
+
+ /* Make sure there are some types of authentication we can do */
+ if(!(context->types & (HA_TYPE_BASIC | HA_TYPE_DIGEST)))
+ {
+ ha_messagex(LOG_ERR, "Digest module configured, but does not implement any "
+ "configured authentication type.");
+ return HA_ERROR;
+ }
+
+ /* Check for mandatory configuration */
+ if(!ctx->servers || (!ctx->dnmap || !ctx->filter))
+ {
+ ha_messagex(LOG_ERR, "Digest LDAP configuration incomplete. "
+ "Must have LDAPServers and either LDAPFilter or LDAPDNMap.");
+ return HA_ERROR;
+ }
+
+
+ /* The hash tables */
+ if(!(ctx->pending = hash_create(LDAP_HASH_KEY_LEN)) ||
+ !(ctx->established = hash_create(LDAP_HASH_KEY_LEN)))
+ {
+ ha_messagex(LOG_CRIT, "out of memory");
+ return HA_ERROR;
+ }
+
+ /*
+ * 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.
+ */
+ XXXXX
+ ctx->pool = (LDAP**)malloc(sizeof(LDAP*) * ctx->pending_max);
+ if(!ctx->pool)
+ {
+ ha_messagex(LOG_CRIT, "out of memory");
+ return HA_ERROR;
+ }
+
+ memset(ctx->pool, 0, sizeof(LDAP*) * ctx->pending_max);
+
+ return HA_OK;
+}
+
+void ldap_destroy(ha_context_t* context)
+{
+ int i;
+
+ if(!context)
+ return HA_OK;
+
+ ldap_context_t* ctx = (digest_ldap_context_t*)(context.data);
+
+ /* Note: We don't need to be thread safe here anymore */
+ hash_free(ctx->pending);
+ hash_free(ctx->established);
+
+ XXXXX
+ /* Close any connections we have open */
+ for(i = 0; i < ctx->pending_max; i++)
+ {
+ if(ctx->pool[i])
+ ldap_unbind_s(ctx->pool[i]);
+ }
+
+ /* And free the connection pool */
+ free(ctx->pool);
+}
+
+
+int ldap_process(ha_context_t* context, ha_request_t* req,
+ ha_response_t* resp, ha_buffer_t* buf)
+{
+ ldap_context_t* ctx = (ldap_context_t*)context;
+ time_t t = time(NULL);
+ const char* header = NULL;
+ int ret;
+
+ ha_lock(NULL);
+
+ XXXXXX
+ /*
+ * Purge out stale connection stuff. This includes
+ * authenticated connections which have expired as
+ * well as half open connections which expire.
+ */
+ hash_purge(ctx->pending, t - ctx->pending_timeout);
+ hash_purge(ctx->established, t - ctx->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(ctx->types & HA_TYPE_DIGEST)
+ {
+ header = ha_getheader(req, "Authorization", HA_PREFIX_DIGEST);
+ if(header)
+ {
+ ret = digest_ldap_response(ctx, header, resp, buf);
+ if(ret == HA_ERROR)
+ return ret;
+ }
+ }
+
+ /* Or a basic authentication */
+ if(!header && ctx->types & HA_TYPE_BASIC)
+ {
+ header = ha_getheader(req, "Authorization", HA_PREFIX_BASIC);
+ if(header)
+ {
+ ret = basic_ldap_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(ctx->types & HA_TYPE_DIGEST)
+ {
+ ret = digest_ldap_challenge(ctx, resp, buf, 0);
+ if(ret == HA_ERROR)
+ return ret;
+ }
+
+ if(ctx->types & HA_TYPE_BASIC)
+ {
+ ha_bufnext(buf);
+ ha_bufcat(buf, "BASIC realm=\"", ctx->realm , "\"", NULL);
+
+ ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf));
+ }
+ }
+
+ return ret;
+}
+
+
+/* -------------------------------------------------------------------------------
+ * Handler Definition
+ */
+
+ha_handler_t digest_ldap_handler =
+{
+ "LDAP", /* The type */
+ ldap_initialize, /* Initialization function */
+ ldap_destroy, /* Uninitialization routine */
+ ldap_config, /* Config routine */
+ ldap_process, /* Processing routine */
+ &ldap_defaults, /* The context defaults */
+ sizeof(ldap_context_t)
+};