diff options
Diffstat (limited to 'daemon/ldap.c')
-rw-r--r-- | daemon/ldap.c | 768 |
1 files changed, 474 insertions, 294 deletions
diff --git a/daemon/ldap.c b/daemon/ldap.c index 2927b1d..59af797 100644 --- a/daemon/ldap.c +++ b/daemon/ldap.c @@ -1,23 +1,31 @@ /* 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 <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 */ -/* This needs to be the same as an MD5 hash length */ -#define LDAP_HASH_KEY_LEN 16 -#define LDAP_ESTABLISHED (void*)1 +#define BASIC_ESTABLISHED (void*)1 /* TODO: We need to support more password types */ #define LDAP_PW_CLEAR 0 @@ -57,18 +65,21 @@ typedef struct ldap_context 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 */ + + const char* realm; /* The realm to use in authentication */ + const char* domains; /* Domains for which digest auth is valid */ + 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 */ + int cache_max; /* Maximum number of connections at once */ + int ldap_max; /* Number of open connections allowed */ + int ldap_timeout; /* Maximum amount of time to dedicate to an ldap query */ /* Context ----------------------------------------------------------- */ - hash_t* pending; /* Pending connections */ - hash_t* established; /* Established connections */ + hash_t* cache; /* Some cached records or basic */ + LDAP** pool; /* Pool of available connections */ int pool_mark; /* Amount of connections allocated */ } @@ -76,11 +87,27 @@ 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 +static const ldap_context_t ldap_defaults = +{ + NULL, /* servers */ + NULL, /* filter */ + "", /* base */ + "userPassword", /* pw_attr */ + NULL, /* ha1_attr */ + NULL, /* user */ + NULL, /* password */ + NULL, /* dnmap */ + 389, /* port */ + LDAP_SCOPE_DEFAULT, /* scope */ + "", /* realm */ + NULL, /* domains */ + 1, /* dobind */ + 1000, /* cache_max */ + 10, /* ldap_max */ + 30, /* ldap_timeout */ + NULL, /* cache */ + NULL, /* pool */ + 0 /* pool_mark */ }; @@ -88,45 +115,182 @@ static const ldap_defaults = * Internal Functions */ -static void make_digest_ha1(unsigned char* digest, const char* user, - const char* realm, const char* password) +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, ha_response_t* resp) +{ + if(!msg) + msg = "ldap error"; + + ha_messagex(LOG_ERR, "%s: %s", msg, ldap_err2string(code)); + + switch(code) + { + case LDAP_NO_MEMORY: + return HA_ERROR; + + default: + if(resp) + resp->code = HA_SERVER_ERROR; + + return HA_FALSE; + }; +} + +static digest_record_t* get_cached_digest(ldap_context_t* ctx, unsigned char* nonce) +{ + digest_record_t* rec; + + if(ctx->cache_max == 0) + return NULL; + + ha_lock(NULL); + + rec = (digest_record_t*)hash_get(ctx->cache, nonce); + + /* Just in case it's a basic :) */ + if(rec && rec != BASIC_ESTABLISHED) + hash_rem(ctx->cache, nonce); + + ha_unlock(NULL); + + ASSERT(!rec || memcmp(nonce, rec->nonce, DIGEST_NONCE_LEN) == 0); + return rec; +} + +static int have_cached_basic(ldap_context_t* ctx, unsigned char* key) +{ + int ret = 0; + + ha_lock(NULL); + + ret = (hash_get(ctx->cache, key) == BASIC_ESTABLISHED); + + ha_unlock(NULL); + + return ret; +} + +static int save_cached_digest(ldap_context_t* ctx, digest_record_t* rec) +{ + int r; + + if(ctx->cache_max == 0) + return HA_FALSE; + + ha_lock(NULL); + + while(hash_count(ctx->cache) >= ctx->cache_max) + hash_bump(ctx->cache); + + r = hash_set(ctx->cache, rec->nonce, rec); + + ha_unlock(NULL); + + if(!r) + { + ha_messagex(LOG_CRIT, "out of memory"); + return HA_ERROR; + } + + return HA_OK; +} + +static int add_cached_basic(ldap_context_t* ctx, unsigned char* key) +{ + int r; + + if(ctx->cache_max == 0) + return HA_FALSE; + + ha_lock(NULL); + + while(hash_count(ctx->cache) >= ctx->cache_max) + hash_bump(ctx->cache); + + r = hash_set(ctx->cache, key, BASIC_ESTABLISHED); + + ha_unlock(NULL); + + if(!r) + { + ha_messagex(LOG_CRIT, "out of memory"); + return HA_ERROR; + } + + return HA_OK; +} + +static const char* substitute_params(ldap_context_t* ctx, ha_buffer_t* buf, + const char* user, const char* str) { - 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); + const char* t; + + /* This starts a new block to join */ + ha_bufcpy(buf, ""); + + while(str[0]) + { + t = strchr(str, '%'); + if(!t) + { + ha_bufjoin(buf); + ha_bufcpy(buf, str); + break; + } + + ha_bufjoin(buf); + ha_bufncpy(buf, str, t - str); + + t++; + + switch(t[0]) + { + case 'u': + ha_bufjoin(buf); + ha_bufcpy(buf, user); + t++; + break; + + case 'r': + ha_bufjoin(buf); + ha_bufcpy(buf, ctx->realm); + t++; + break; + }; + + str = t; + } + + return ha_bufdata(buf); } static const char* make_password_md5(ha_buffer_t* buf, const char* clearpw) { - struct MD5Context md5; + md5_ctx_t md5; unsigned char digest[MD5_LEN]; - 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); - ha_bufnext(buf); - ha_bufenc64(buf, digest, MD5_LEN); - return ha_bufdata(buf); + return ha_bufenc64(buf, digest, MD5_LEN); } static const char* make_password_sha(ha_buffer_t* buf, const char* clearpw) { - struct SHA1Context sha; + sha1_ctx_t sha; unsigned char digest[SHA1_LEN]; - 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); - ha_bufnext(buf); - ha_bufenc64(buf, digest, SHA1_LEN); - return ha_bufdata(buf); + return ha_bufenc64(buf, digest, SHA1_LEN); } static int parse_ldap_password(const char** password) @@ -154,7 +318,7 @@ static int parse_ldap_password(const char** password) pw++; /* scheme should end in a brace */ - if(pw != '}') + if(*pw != '}') return LDAP_PW_CLEAR; *password = pw + 1; @@ -162,8 +326,8 @@ static int parse_ldap_password(const char** password) /* 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; + if(strncasecmp(kLDAPPWTypes[i].name, scheme, pw - scheme)) + return kLDAPPWTypes[i].type; } return LDAP_PW_UNKNOWN; @@ -171,8 +335,6 @@ static int parse_ldap_password(const char** password) static const char* find_cleartext_password(ha_buffer_t* buf, const char** pws) { - ha_bufnext(buf); - for(; pws && *pws; pws++) { const char* pw = *pws; @@ -184,28 +346,23 @@ static const char* find_cleartext_password(ha_buffer_t* buf, const char** pws) 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); + memcpy(ha1, bv->bv_val, 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); + void* d = ha_bufdechex(buf, bv->bv_val, MD5_LEN); - if(!ha_bufdata(buf)) - return HA_ERROR; - - if(ha_buflen(buf) == MD5_LEN) + if(d) { - memcpy(rec->ha1, ha_bufdata(buf), MD5_LEN); + memcpy(ha1, d, MD5_LEN); return HA_OK; } } @@ -213,26 +370,22 @@ static int parse_ldap_ha1(ha_buffer_t* buf, struct berval* bv, unsigned char* ha /* B64 Encoded */ else { - ha_bufnext(buf); - ha_bufdec64(buf, (*pws)->bv_val, (*pws)->bv_len); + void* d = ha_bufdec64(buf, bv->bv_val, MD5_LEN); - if(!ha_bufdata(buf)) - return HA_ERROR; - - if(ha_buflen(buf) == MD5_LEN) + if(d) { - memcpy(rec->ha1, ha_bufdata(buf), MD5_LEN); + memcpy(ha1, ha_bufdata(buf), MD5_LEN); return HA_OK; } } - return HA_FALSE; + return ha_buferr(buf) ? HA_ERROR : 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; + char** pws; const char* pw; const char* p; int type; @@ -295,7 +448,7 @@ static int validate_ldap_password(ldap_context_t* ctx, LDAP* ld, LDAPMessage* en } } - ldap_free_values(pws); + ldap_value_free(pws); } if(res == HA_FALSE && unknown) @@ -320,11 +473,11 @@ static int validate_ldap_ha1(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry, if(ha1s) { - make_digest_ha1(key, user, ctx->realm, clearpw); + digest_makeha1(key, user, ctx->realm, clearpw); for( ; *ha1s; ha1s++) { - r = parse_ldap_h1(buf, *ha1s, k); + r = parse_ldap_ha1(buf, *ha1s, k); if(r == HA_ERROR) { res = r; @@ -334,7 +487,7 @@ static int validate_ldap_ha1(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry, if(r == HA_FALSE) { if(first) - ha_messagex(LOG_ERROR, "LDAP contains invalid HA1 digest hash for user: %s", user); + ha_messagex(LOG_ERR, "LDAP contains invalid HA1 digest hash for user: %s", user); first = 0; continue; @@ -347,7 +500,7 @@ static int validate_ldap_ha1(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry, } } - ldap_free_values_len(ha1s); + ldap_value_free_len(ha1s); } return res; @@ -358,7 +511,7 @@ static LDAP* get_ldap_connection(ldap_context_t* ctx) LDAP* ld; int i, r; - for(i = 0; i < ctx->pending_max; i++) + for(i = 0; i < ctx->ldap_max; i++) { /* An open connection in the pool */ if(ctx->pool[i]) @@ -369,16 +522,16 @@ static LDAP* get_ldap_connection(ldap_context_t* ctx) } } - if(ctx->pool_mark >= ctx->pending_max) + if(ctx->pool_mark >= ctx->ldap_max) { - ha_messagex("too many open connections to LDAP"); + ha_messagex(LOG_ERR, "too many open connections to LDAP"); return NULL; } ld = ldap_init(ctx->servers, ctx->port); if(!ld) { - ha_message("couldn't initialize ldap connection"); + ha_message(LOG_ERR, "couldn't initialize ldap connection"); return NULL; } @@ -388,10 +541,12 @@ static LDAP* get_ldap_connection(ldap_context_t* ctx) ctx->password ? ctx->password : ""); if(r != LDAP_SUCCESS) { - report_ldap(r, NULL); + report_ldap("couldn't bind to LDAP server", r, NULL); ldap_unbind_s(ld); return NULL; } + + ctx->pool_mark++; } return ld; @@ -399,13 +554,15 @@ static LDAP* get_ldap_connection(ldap_context_t* ctx) static void save_ldap_connection(ldap_context_t* ctx, LDAP* ld) { - int i; + int i, e; if(!ld) return; + ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &e); + /* Make sure it's worth saving */ - switch(ld_errno(ld)) + switch(e) { case LDAP_SERVER_DOWN: case LDAP_LOCAL_ERROR: @@ -413,7 +570,7 @@ static void save_ldap_connection(ldap_context_t* ctx, LDAP* ld) break; default: - for(i = 0; i < ctx->pending_max; i++) + for(i = 0; i < ctx->ldap_max; i++) { /* An open connection in the pool */ if(!ctx->pool[i]) @@ -428,25 +585,88 @@ static void save_ldap_connection(ldap_context_t* ctx, LDAP* ld) }; if(ld != NULL) + { ldap_unbind_s(ld); + ctx->pool_mark--; + } +} + +static int retrieve_user_entry(ldap_context_t* ctx, ha_buffer_t* buf, LDAP* ld, + const char* user, const char** dn, + LDAPMessage** entry, LDAPMessage** result) +{ + struct timeval tv; + const char* filter; + const char* attrs[3]; + int r; + + 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, (char**)attrs, 0, &tv, result); + + if(r != LDAP_SUCCESS) + return report_ldap("couldn't search LDAP server", r, NULL); + + + /* 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", user); + break; + + default: + ha_messagex(LOG_WARNING, "more than one user found for filter: %s", filter); + break; + }; + + ldap_msgfree(*result); + return HA_FALSE; } -static int complete_digest_ha1(ldap_context_t* ctx, ha_digest_rec_t* rec, - const char* user) +static int complete_digest_ha1(ldap_context_t* ctx, digest_record_t* rec, + ha_buffer_t* buf, const char* user, int* code) { LDAP* ld = NULL; /* freed in finally */ LDAPMessage* results = NULL; /* freed in finally */ LDAPMessage* entry = NULL; /* no need to free */ - struct berval** pws; /* freed manually */ + struct berval** ha1s; /* freed manually */ + char** pws; int ret = HA_FALSE; - - /* Hash in the user name */ - ha_md5string(user, rec->userhash); - + const char* dn; + int r; ld = get_ldap_connection(ctx); if(!ld) + { + *code = HA_SERVER_ERROR; goto finally; + } /* * Discover the DN of the user. If there's a DN map string @@ -465,7 +685,7 @@ static int complete_digest_ha1(ldap_context_t* ctx, ha_digest_rec_t* rec, } /* Okay now we contact the LDAP server. */ - r = retrieve_user_entry(ctx, buf, user, &dn, &entry, &results); + r = retrieve_user_entry(ctx, buf, ld, user, &dn, &entry, &results); if(r != HA_OK) { ret = r; @@ -474,45 +694,45 @@ static int complete_digest_ha1(ldap_context_t* ctx, ha_digest_rec_t* rec, /* Figure out the users ha1 */ if(ctx->ha1_attr) - pws = ldap_get_values_len(ld, entry, ctx->ha1_attr); + ha1s = ldap_get_values_len(ld, entry, ctx->ha1_attr); - if(pws) + if(ha1s) { - if(*pws) + if(*ha1s) { - r = parse_ldap_ha1(buf, *pws, rec->ha1); + r = parse_ldap_ha1(buf, *ha1s, rec->ha1); if(r != HA_OK) { - ret = r + ret = r; if(ret != HA_FALSE) - ha_messagex(LOG_ERROR, "LDAP contains invalid HA1 digest hash for user: %s", user); + ha_messagex(LOG_ERR, "LDAP contains invalid HA1 digest hash for user: %s", user); } } - ldap_free_values_len(pws); + ldap_value_free_len(ha1s); 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); + pws = ldap_get_values(ld, entry, ctx->pw_attr); if(pws) { /* Find a cleartext password */ - const char* t = find_cleartext_password(buf, pws); + const char* t = find_cleartext_password(buf, (const char**)pws); - ldap_free_values_len(pws); + ldap_value_free(pws); if(t) { - make_digest_ha1(rec->ha1, user, ctx->realm, t); + digest_makeha1(rec->ha1, user, ctx->realm, t); ret = HA_OK; goto finally; } } - ha_messagex(LOG_ERROR, "LDAP contains no cleartext password for user: %s", user); + ha_messagex(LOG_ERR, "LDAP contains no cleartext password for user: %s", user); finally: @@ -525,67 +745,10 @@ finally: 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; + basic_header_t basic; LDAP* ld = NULL; LDAPMessage* entry = NULL; LDAPMessage* results = NULL; @@ -596,23 +759,18 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, ASSERT(buf && header && resp && buf); - if(ha_parsebasic(header, buf, &basic) == HA_ERROR) + if(basic_parse(header, buf, &basic) == HA_ERROR) return HA_ERROR; /* Past this point we don't return directly */ /* Check and see if this connection is in the cache */ - ha_lock(NULL); - - if(hash_get(ctx->established, key) == BASIC_ESTABLISHED) - { - found = 1; - ret = HA_OK; - goto finally: - } - - ha_unlock(NULL); - + if(have_cached_basic(ctx, basic.key)) + { + found = 1; + ret = HA_OK; + goto finally; + } /* If we have a user name and password */ if(!basic.user || !basic.user[0] || @@ -620,7 +778,7 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, goto finally; - ld = get_ldap_connection(); + ld = get_ldap_connection(ctx); if(!ld) { resp->code = HA_SERVER_ERROR; @@ -662,7 +820,7 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, if(!ctx->dobind || !dn || ctx->filter) { - r = retrieve_user_entry(ctx, buf, basic.user, &dn, &entry, &results); + r = retrieve_user_entry(ctx, buf, ld, basic.user, &dn, &entry, &results); if(r != HA_OK) { ret = r; @@ -682,7 +840,7 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, if(r == LDAP_INVALID_CREDENTIALS) ha_messagex(LOG_WARNING, "invalid login for: %s", basic.user); else - report_ldap(r, resp, &ret); + report_ldap("couldn't bind to LDAP server", r, resp); goto finally; } @@ -717,80 +875,90 @@ finally: if(resp->code == HA_SERVER_ACCEPT) { - resp->details = basic.user; + resp->detail = 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; - } + ret = add_cached_basic(ctx, basic.key); } return ret; } +static int digest_ldap_challenge(ldap_context_t* ctx, ha_response_t* resp, + ha_buffer_t* buf, int stale) +{ + unsigned char nonce[DIGEST_NONCE_LEN]; + const char* header; + + /* Generate an nonce */ + digest_makenonce(nonce, g_ldap_secret, NULL); + + /* Now generate a message to send */ + header = digest_challenge(buf, nonce, ctx->realm, ctx->domains, stale); + + if(!header) + return HA_ERROR; + + /* And append it nicely */ + resp->code = HA_SERVER_DECLINE; + ha_addheader(resp, "WWW-Authenticate", header); + + return HA_OK; +} static int digest_ldap_response(ldap_context_t* ctx, const char* header, - const char* method, const char* uri, - ha_response_t* resp, ha_buffer_t* buf) + const char* method, const char* uri, int timeout, + ha_response_t* resp, ha_buffer_t* buf) { - ha_digest_header_t dg; - digest_rec_t* rec = NULL; + 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 pending = 0; + int r; /* We use this below to send a default response */ resp->code = -1; - if(ha_parsedigest(header, buf, &rec) == HA_ERROR) + if(digest_parse(header, buf, &dg, nonce) == HA_ERROR) return HA_ERROR; - /* Lookup our digest context based on the nonce */ - if(!dg.nonce || strlen(dg.nonce) != DIGEST_NONCE_LEN) + r = digest_checknonce(nonce, g_ldap_secret, &expiry); + if(r != HA_OK) { - ha_messagex(LOG_WARNING, "digest response contains invalid nonce"); + if(r == HA_FALSE) + ha_messagex(LOG_WARNING, "digest response contains invalid nonce"); + + ret = r; 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); - } + rec = get_cached_digest(ctx, 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) + /* Check to see if we're stale */ + if((expiry + timeout) <= time(NULL)) { 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) + if(!rec) { - ASSERT(rec); + /* + * If we're valid but don't have a record in the + * cache then complete the record properly. + */ + + rec = digest_makerec(nonce, dg.username); + if(!rec) + { + ret = HA_ERROR; + goto finally; + } - r = complete_digest_ha1(ctx, rec, dg->username); + r = complete_digest_ha1(ctx, rec, buf, dg.username, &(resp->code)); if(r != HA_OK) { ret = r; @@ -801,28 +969,35 @@ static int digest_ldap_response(ldap_context_t* ctx, const char* header, /* Increment our nonce count */ rec->nc++; - ret = ha_digestcheck(ctx->realm, method, uri, buf, &dg, rec); + ret = digest_check(ctx->realm, method, uri, buf, &dg, rec); if(ret == HA_OK) { resp->code = HA_SERVER_ACCEPT; - resp->details = dg->username; + resp->detail = dg.username; - /* Put the connection back into established */ + /* Figure out if we need a new nonce */ + if((expiry + (timeout - (timeout / 8))) < time(NULL)) + { + digest_makenonce(nonce, g_ldap_secret, NULL); + stale = 1; + } - ha_lock(NULL); + t = digest_respond(buf, &dg, rec, stale ? nonce : NULL); + if(!t) + { + ret = HA_ERROR; + goto finally; + } - if(hash_set(ctx->established, dg.nonce, rec)) - { - rec = NULL; - } - else - { - ha_messagex(LOG_CRIT, "out of memory"); - ret = HA_ERROR; - } + if(t[0]) + ha_addheader(resp, "Authentication-Info", t); - ha_unlock(NULL); + /* Put the connection into the cache */ + if(save_cached_digest(ctx, rec) == HA_ERROR) + ret = HA_ERROR; + else + rec = NULL; } finally: @@ -847,7 +1022,7 @@ finally: int ldap_config(ha_context_t* context, const char* name, const char* value) { - ldap_context_t* ctx = (ldap_context_t*)(context.data); + ldap_context_t* ctx = (ldap_context_t*)(context->data); if(strcmp(name, "ldapservers") == 0) { @@ -903,6 +1078,12 @@ int ldap_config(ha_context_t* context, const char* name, const char* value) return HA_OK; } + else if(strcmp(name, "digestdomains") == 0) + { + ctx->domains = value; + return HA_OK; + } + else if(strcmp(name, "ldapscope") == 0) { if(strcmp(value, "sub") == 0 || strcmp(value, "subtree") == 0) @@ -914,7 +1095,7 @@ int ldap_config(ha_context_t* context, const char* name, const char* value) else { - messagex(LOG_ERR, "invalid value for '%s' (must be 'sub', 'base' or 'one')", name); + ha_messagex(LOG_ERR, "invalid value for '%s' (must be 'sub', 'base' or 'one')", name); return HA_ERROR; } @@ -926,72 +1107,75 @@ int ldap_config(ha_context_t* context, const char* name, const char* value) return ha_confbool(name, value, &(ctx->dobind)); } - else if(strcmp(name, "pendingmax") == 0) + else if(strcmp(name, "ldapmax") == 0) { - return ha_confint(name, value, 1, 256, &(ctx->pending_max)); + return ha_confint(name, value, 1, 256, &(ctx->ldap_max)); } - else if(strcmp(name, "pendingtimeout") == 0) + else if(strcmp(name, "ldaptimeout") == 0) { - return ha_confint(name, value, 0, 86400, &(ctx->pending_timeout)); + return ha_confint(name, value, 0, 86400, &(ctx->ldap_timeout)); } - else if(strcmp(name, "ldaptimeout") == 0) + else if(strcmp(name, "cachemax") == 0) { - return ha_confint(name, value, 0, 86400, &(ctx->ldap_timeout)); + return ha_confint(name, value, 0, 0x7FFFFFFF, &(ctx->cache_max)); } return HA_FALSE; } -int ldap_initialize(ha_context_t* context) +int ldap_inithand(ha_context_t* context) { - /* No global initialization */ + /* 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; + return ha_genrandom(g_ldap_secret, DIGEST_SECRET_LEN); } - /* Check for mandatory configuration */ - if(!ctx->servers || (!ctx->dnmap || !ctx->filter)) + + /* Context specific initialization */ + else { - ha_messagex(LOG_ERR, "Digest LDAP configuration incomplete. " - "Must have LDAPServers and either LDAPFilter or LDAPDNMap."); - return HA_ERROR; - } + 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, "LDAP module configured, but does not implement any " + "configured authentication type."); + 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; - } + /* 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; + } - /* - * 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; - } + /* The cache for digest records and basic */ + if(!(ctx->cache = hash_create(MD5_LEN, free_hash_object, NULL))) + { + ha_messagex(LOG_CRIT, "out of memory"); + return HA_ERROR; + } - memset(ctx->pool, 0, sizeof(LDAP*) * ctx->pending_max); + /* + * 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_ERROR; + } + + memset(ctx->pool, 0, sizeof(LDAP*) * ctx->ldap_max); + } return HA_OK; } @@ -1001,17 +1185,15 @@ void ldap_destroy(ha_context_t* context) int i; if(!context) - return HA_OK; + return; - ldap_context_t* ctx = (digest_ldap_context_t*)(context.data); + ldap_context_t* ctx = (ldap_context_t*)(context->data); /* Note: We don't need to be thread safe here anymore */ - hash_free(ctx->pending); - hash_free(ctx->established); + hash_free(ctx->cache); - XXXXX /* Close any connections we have open */ - for(i = 0; i < ctx->pending_max; i++) + for(i = 0; i < ctx->ldap_max; i++) { if(ctx->pool[i]) ldap_unbind_s(ctx->pool[i]); @@ -1032,14 +1214,8 @@ int ldap_process(ha_context_t* context, ha_request_t* req, 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); + /* Purge out stale connection stuff. */ + hash_purge(ctx->cache, t - context->timeout); ha_unlock(NULL); @@ -1049,19 +1225,21 @@ int ldap_process(ha_context_t* context, ha_request_t* req, /* Check the headers and see if we got a response thingy */ - if(ctx->types & HA_TYPE_DIGEST) + if(context->types & HA_TYPE_DIGEST) { header = ha_getheader(req, "Authorization", HA_PREFIX_DIGEST); if(header) { - ret = digest_ldap_response(ctx, header, resp, buf); + ret = digest_ldap_response(ctx, header, req->args[AUTH_ARG_METHOD], + req->args[AUTH_ARG_URI], context->timeout, + resp, buf); if(ret == HA_ERROR) return ret; } } /* Or a basic authentication */ - if(!header && ctx->types & HA_TYPE_BASIC) + if(!header && context->types & HA_TYPE_BASIC) { header = ha_getheader(req, "Authorization", HA_PREFIX_BASIC); if(header) @@ -1078,17 +1256,19 @@ int ldap_process(ha_context_t* context, ha_request_t* req, { resp->code = HA_SERVER_DECLINE; - if(ctx->types & HA_TYPE_DIGEST) + if(context->types & HA_TYPE_DIGEST) { ret = digest_ldap_challenge(ctx, resp, buf, 0); if(ret == HA_ERROR) return ret; } - if(ctx->types & HA_TYPE_BASIC) + if(context->types & HA_TYPE_BASIC) { - ha_bufnext(buf); - ha_bufcat(buf, "BASIC realm=\"", ctx->realm , "\"", NULL); + ha_bufmcat(buf, "BASIC realm=\"", ctx->realm , "\"", NULL); + + if(ha_buferr(buf)) + return HA_ERROR; ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf)); } @@ -1102,10 +1282,10 @@ int ldap_process(ha_context_t* context, ha_request_t* req, * Handler Definition */ -ha_handler_t digest_ldap_handler = +ha_handler_t ldap_handler = { "LDAP", /* The type */ - ldap_initialize, /* Initialization function */ + ldap_inithand, /* Initialization function */ ldap_destroy, /* Uninitialization routine */ ldap_config, /* Config routine */ ldap_process, /* Processing routine */ |