From cbbe71752d7f9c6204ab0f16600fe7f10490f203 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Sat, 24 Apr 2004 22:38:50 +0000 Subject: Completed implementation of ldap/ntlm/simple handlers --- daemon/Makefile.am | 4 +- daemon/basic.c | 242 ++--------------- daemon/basic.h | 17 ++ daemon/defaults.h | 2 +- daemon/digest.c | 533 +++++++++++++++++++++++++++++++++++++ daemon/digest.h | 57 ++++ daemon/httpauthd.c | 65 +++-- daemon/httpauthd.h | 182 +++++++------ daemon/ldap.c | 768 +++++++++++++++++++++++++++++++++-------------------- daemon/misc.c | 599 +++++++++++++++++++---------------------- daemon/ntlm.c | 115 ++++---- daemon/simple.c | 663 +++++++++++++++++++++++++++++++++++++++++++++ daemon/usuals.h | 7 + 13 files changed, 2242 insertions(+), 1012 deletions(-) create mode 100644 daemon/basic.h create mode 100644 daemon/digest.c create mode 100644 daemon/digest.h create mode 100644 daemon/simple.c (limited to 'daemon') diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 4525edf..c93e89f 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -2,8 +2,8 @@ bin_PROGRAMS = httpauthd httpauthd_SOURCES = httpauthd.c httpauthd.h usuals.h compat.h compat.c \ - buffer.c misc.c basic.c ntlm.c hash.c hash.h ntlmssp.h ntlmssp.c \ - md5.c md5.h \ + buffer.c misc.c basic.h basic.c ntlm.c hash.c hash.h ntlmssp.h ntlmssp.c \ + md5.c md5.h sha1.c sha1.h digest.h digest.c ldap.c defaults.h simple.c \ smblib/smblib.c smblib/smblib-util.c smblib/file.c smblib/smb-errors.c smblib/exper.c smblib/smblib-api.c \ rfcnb/rfcnb-io.c rfcnb/rfcnb-util.c rfcnb/session.c diff --git a/daemon/basic.c b/daemon/basic.c index a8325af..da73682 100644 --- a/daemon/basic.c +++ b/daemon/basic.c @@ -1,243 +1,45 @@ -/* On some linux this is required to get crypt to show itself */ -#define _XOPEN_SOURCE - #include "usuals.h" #include "httpauthd.h" -#include "defaults.h" - -#include -#include -#include - -#define BASIC_MAXLINE 128 - -/* This needs to be the same as an MD5 hash length */ -#define BASIC_HASH_KEY_LEN 16 -#define BASIC_ESTABLISHED (void*)1 - -/* ------------------------------------------------------------------------------- - * Structures - */ - -typedef struct basic_context -{ - const char* filename; /* The file name with the user names */ - const char* realm; /* The realm for basic authentication */ - - /* Context ----------------------------------------------------------- */ - hash_t* established; /* Established connections */ -} -basic_context_t; - -/* ------------------------------------------------------------------------------- - * Handler Functions - */ - -int basic_config(ha_context_t* context, const char* name, const char* value) -{ - basic_context_t* ctx = (basic_context_t*)(context.data); - - if(strcmp(name, "basicfile") == 0) - { - ctx->filename = value; - return HA_OK; - } - - else if(strcmp(name, "realm") == 0) - { - ctx->realm = value; - return HA_OK; - } - - return HA_FALSE; -} - -int basic_init(ha_context_t* context) -{ - /* Don't do global init */ - if(!context) - return HA_OK; - - basic_context_t* ctx = (basic_context_t*)(context.data); - int fd; - - /* Make sure there are some types of authentication we can do */ - if(!(context->types & HA_TYPE_BASIC)) - { - ha_messagex(LOG_ERR, "Basic module configured, but does not implement any " - "configured authentication type."); - return HA_ERROR; - } - - /* Check to make sure the file exists */ - if(!ctx->filename) - { - ha_messagex(LOG_ERR, "Basic configuration incomplete. " - "Must have a BasicFile configured."); - return HA_ERROR; - } - - fd = open(ctx->filename, O_RDONLY); - if(fd == -1) - { - ha_message(LOG_ERR, "can't open file for basic authentication: %s", ctx->filename); - return HA_ERROR; - } - - close(fd); +#include "basic.h" - /* Initialize our cache table */ - if(!(ctx->established = hash_create(BASIC_HASH_KEY_LEN))) - { - ha_messagex(LOG_CRIT, "out of memory"); - return HA_ERROR; - } - - return HA_OK; -} - -int basic_process(ha_context_t* context, ha_request_t* req, - ha_response_t* resp, ha_buffer_t* buf) +int basic_parse(const char* header, ha_buffer_t* buf, basic_header_t* rec) { - basic_context_t* ctx = (basic_context_t*)(context.data); - const char* header; char* t; - int found = 0; - ha_basic_header_t basic; - - memset(&basic, 0, sizeof(basic)); - - ha_lock(NULL); - /* Purge the cache */ - hash_purge(ctx->established, time(NULL) - context->timeout); - - ha_unlock(NULL); + memset(rec, 0, sizeof(*rec)); + /* Trim the white space */ + while(*header && isspace(*header)) + header++; /* - * Init checked and made sure basic auth is enabled, so we - * can take that for granted here. + * Authorization header is in this format: + * + * "Basic " B64(user ":" password) */ + ha_bufdec64(buf, header, 0); + header = ha_bufdata(buf); - header = ha_getheader(req, "Authorization", BASIC_PREFIX); - if(header) - { - if(ha_parsebasic(header, buf, &basic) == HA_ERROR) - return HA_ERROR; - - /* Check and see if this connection is in the cache */ - ha_lock(NULL); - - if(hash_get(ctx->established, basic.key) == BASIC_ESTABLISHED) - { - hash_touch(ctx->established, basic.key); - found = 1; - } - - ha_unlock(NULL); - } - - - /* If we have a user name and password that wasn't in the cache */ - if(!found && basic.user && basic.user[0] && - basic.password && basic.password[0]) - { - FILE* f; - char line[BASIC_MAXLINE]; - - f = fopen(ctx->filename, "r"); - if(!f) - { - ha_message(LOG_ERR, "can't open file for basic auth: %s", ctx->filename); - resp->code = HA_SERVER_ERROR; - return HA_FALSE; - } - - /* - * Note: There should be no returns or jumps between - * this point and the closing of the file below. - */ - - /* Now look through the whole file */ - while(!feof(f)) - { - fgets(line, BASIC_MAXLINE, f); - - if(ferror(f)) - ha_message(LOG_ERR, "error reading basic password file"); - - t = strchr(line, ':'); - if(t) - { - /* Split the line */ - *t = 0; - t++; - - /* Check the user */ - if(strcmp(line, basic.user) == 0) - { - /* Not sure if crypt is thread safe so we lock */ - ha_lock(); - - /* Check the password */ - if(strcmp(crypt(basic.password, t), t) == 0) - found = 1; - - ha_unlock(); - - if(found) - break; - } - } - } + if(!header) + return HA_ERROR; - fclose(f); - } - /* If we found a user then tell them what it was */ - if(found) - { - resp->code = HA_SERVER_ACCEPT; - resp->detail = basic.user; + /* We have a cache key at this point so hash it */ + md5_string(rec->key, header); - /* We put this connection into the successful connections */ - if(!hash_set(ctx->established, basic.key, BASIC_ESTABLISHED)) - { - ha_messagex(LOG_CRIT, "out of memory"); - return HA_ERROR; - } - } - /* Otherwise make the browser authenticate */ - else + /* Parse the user. We need it in any case */ + t = strchr(header, ':'); + if(t != NULL) { - resp->code = HA_SERVER_DECLINE; - - ha_bufnext(buf); - ha_bufcat(buf, "BASIC realm=\"", ctx->realm ? ctx->realm : "", - "\"", NULL); + /* Break the string in half */ + *t = 0; - ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf)); + rec->user = header; + rec->password = t + 1; } return HA_OK; } - -/* ------------------------------------------------------------------------------- - * Handler Definition - */ - -ha_handler_t basic_handler = -{ - "Basic", /* The type */ - basic_init, /* Initialization function */ - basic_destroy, /* Uninitialization routine */ - basic_config, /* Config routine */ - basic_process, /* Processing routine */ - NULL, /* A default context */ - sizeof(basic_context_t) /* Size of the context */ -}; - diff --git a/daemon/basic.h b/daemon/basic.h new file mode 100644 index 0000000..c28dc1e --- /dev/null +++ b/daemon/basic.h @@ -0,0 +1,17 @@ + +#ifndef __BASIC_H__ +#define __BASIC_H__ + +#include "md5.h" + +typedef struct basic_header +{ + const char* user; + const char* password; + unsigned char key[MD5_LEN]; +} +basic_header_t; + +int basic_parse(const char* header, ha_buffer_t* buf, basic_header_t* rec); + +#endif /* __BASIC_H__ */ diff --git a/daemon/defaults.h b/daemon/defaults.h index bfcd9f4..755d12c 100644 --- a/daemon/defaults.h +++ b/daemon/defaults.h @@ -6,4 +6,4 @@ #define DEFAULT_PENDING_TIMEOUT 60 #define DEFAULT_TIMEOUT 900 -#endif /* __DEFAULTS_H__ */ \ No newline at end of file +#endif /* __DEFAULTS_H__ */ diff --git a/daemon/digest.c b/daemon/digest.c new file mode 100644 index 0000000..51120a0 --- /dev/null +++ b/daemon/digest.c @@ -0,0 +1,533 @@ + +#include "usuals.h" +#include "md5.h" +#include "httpauthd.h" +#include "digest.h" + +#include + +/* A globally unique counter used to guarantee uniqueness of nonces */ +static unsigned int g_digest_unique = 0; + +typedef struct internal_nonce +{ + unsigned char hash[MD5_LEN]; + time_t tm; + unsigned int unique; +} +internal_nonce_t; + +void digest_makenonce(unsigned char* nonce, unsigned char* secret, unsigned char* old) +{ + internal_nonce_t in; + md5_ctx_t md5; + + ASSERT(sizeof(internal_nonce_t) == DIGEST_NONCE_LEN); + + if(old) + { + internal_nonce_t* nold = (internal_nonce_t*)old; + in.tm = nold->tm; + in.unique = nold->unique; + } + else + { + in.tm = time(NULL); + ha_lock(NULL); + in.unique = g_digest_unique++; + ha_unlock(NULL); + } + + md5_init(&md5); + md5_update(&md5, secret, DIGEST_SECRET_LEN); + md5_update(&md5, &(in.tm), sizeof(in.tm)); + md5_update(&md5, &(in.unique), sizeof(in.unique)); + md5_final(in.hash, &md5); + + memcpy(nonce, &in, DIGEST_NONCE_LEN); +} + +int digest_checknonce(unsigned char* nonce, unsigned char* secret, time_t* tm) +{ + internal_nonce_t in; + + ASSERT(sizeof(internal_nonce_t) == DIGEST_NONCE_LEN); + + digest_makenonce((unsigned char*)&in, secret, nonce); + + if(memcmp((unsigned char*)&in, nonce, DIGEST_SECRET_LEN) == 0) + { + if(tm) + *tm = in.tm; + + return HA_OK; + } + + return HA_FALSE; +} + +digest_record_t* digest_makerec(unsigned char* nonce, const char* user) +{ + digest_record_t* rec = (digest_record_t*)malloc(sizeof(*rec)); + if(!rec) + { + ha_messagex(LOG_CRIT, "out of memory"); + return NULL; + } + + memset(rec, 0, sizeof(*rec)); + memcpy(rec->nonce, nonce, DIGEST_NONCE_LEN); + + md5_string(rec->userhash, user); + return rec; +} + +const char* digest_challenge(ha_buffer_t* buf, unsigned char* nonce, + const char* realm, const char* domains, int stale) +{ + ASSERT(realm); + ASSERT(nonce); + + ha_bufmcat(buf, HA_PREFIX_DIGEST, " realm=\"", realm, "\", nonce=\"", NULL); + ha_bufjoin(buf); + ha_bufenc64(buf, nonce, DIGEST_NONCE_LEN); + ha_bufjoin(buf); + ha_bufmcat(buf, "\", qop=\"auth\", algorithm=\"MD5\"", NULL); + + if(domains) + { + ha_bufjoin(buf); + ha_bufmcat(buf, ", domain=\"", domains, "\"", NULL); + } + + if(stale) + { + ha_bufjoin(buf); + ha_bufcat(buf, ", stale=true"); + } + + return ha_bufdata(buf); +} + +/* + * Copyright 1999-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +int digest_parse(char* header, ha_buffer_t* buf, digest_header_t* rec, + unsigned char* nonce) +{ + /* + * This function destroys the contents of header by + * terminating strings in it all over the place. + */ + + char next; + char* key; + char* value; + + header = ha_bufcpy(buf, header); + + if(!header) + return HA_ERROR; + + memset(rec, 0, sizeof(rec)); + + while(header[0]) + { + /* find key */ + + while(header[0] && isspace(header[0])) + header++; + + key = header; + + while(header[0] && header[0] != '=' && header[0] != ',' && + !isspace(header[0])) + header++; + + /* null terminate and move on */ + next = header[0]; + header[0] = '\0'; + header++; + + if(!next) + break; + + if(isspace(header[0])) + { + while(header[0] && isspace(header[0])) + header++; + + next = header[0]; + } + + /* find value */ + + if(next == '=') + { + header++; + + while(header[0] && isspace(header[0])) + header++; + + if(header[0] == '\"') /* quoted string */ + { + header++; + value = header; + + while(header[0] && header[0] != '\"') + header++; + + header[0] = 0; + header++; + } + + else /* token */ + { + value = header; + + while(header[0] && header[0] != ',' && !isspace(header[0])) + header++; + + header[0] = 0; + header++; + } + + while(header[0] && header[0] != ',') + header++; + + if(header[0]) + header++; + + if(!strcasecmp(key, "username")) + rec->username = value; + else if(!strcasecmp(key, "realm")) + rec->realm = value; + else if (!strcasecmp(key, "nonce")) + rec->nonce = value; + else if (!strcasecmp(key, "uri")) + rec->uri = value; + else if (!strcasecmp(key, "response")) + rec->digest = value; + else if (!strcasecmp(key, "algorithm")) + rec->algorithm = value; + else if (!strcasecmp(key, "cnonce")) + rec->cnonce = value; + else if (!strcasecmp(key, "opaque")) + rec->opaque = value; + else if (!strcasecmp(key, "qop")) + rec->qop = value; + else if (!strcasecmp(key, "nc")) + rec->nc = value; + } + } + + if(nonce) + { + memset(nonce, 0, DIGEST_NONCE_LEN); + + if(rec->nonce) + { + void* d = ha_bufdec64(buf, rec->nonce, DIGEST_NONCE_LEN); + + if(d != NULL) + memcpy(nonce, d, DIGEST_NONCE_LEN); + } + } + + return HA_OK; +} + +int digest_check(const char* realm, const char* method, const char* uri, + ha_buffer_t* buf, digest_header_t* dg, digest_record_t* rec) +{ + unsigned char hash[MD5_LEN]; + md5_ctx_t md5; + const char* digest; + const char* t; + + /* Check for digest */ + if(!dg->digest || !dg->digest[0]) + { + ha_messagex(LOG_WARNING, "digest response missing digest"); + return HA_FALSE; + } + + /* Username */ + if(!dg->username || !dg->username[0] || + md5_strcmp(rec->userhash, dg->username) != 0) + { + ha_messagex(LOG_WARNING, "digest response missing username"); + return HA_FALSE; + } + + /* The realm */ + if(!dg->realm || strcmp(dg->realm, realm) != 0) + { + ha_messagex(LOG_WARNING, "digest response contains invalid realm: '%s'", + dg->realm ? dg->realm : ""); + return HA_FALSE; + } + + /* Components in the new RFC */ + if(dg->qop) + { + /* + * We only support 'auth' qop. We don't have access to the data + * and wouldn't be able to support anything else. + */ + if(strcmp(dg->qop, "auth") != 0) + { + ha_messagex(LOG_WARNING, "digest response contains unknown or unsupported qop: '%s'", + dg->qop ? dg->qop : ""); + return HA_FALSE; + } + + /* The cnonce */ + if(!dg->cnonce || !dg->cnonce[0]) + { + ha_messagex(LOG_WARNING, "digest response is missing cnonce value"); + return HA_FALSE; + } + + /* The nonce count */ + if(!dg->nc || !dg->nc[0]) + { + ha_messagex(LOG_WARNING, "digest response is missing nc value"); + return HA_FALSE; + } + + /* Validate the nc */ + else + { + char* e; + long nc = strtol(dg->nc, &e, 10); + + if(e != (dg->nc + strlen(e)) || nc != rec->nc) + { + ha_messagex(LOG_WARNING, "digest response has invalid nc value: %s", + dg->nc); + return HA_FALSE; + } + } + } + + /* The algorithm */ + if(dg->algorithm && strcasecmp(dg->algorithm, "MD5") != 0) + { + ha_messagex(LOG_WARNING, "digest response contains unknown or unsupported algorithm: '%s'", + dg->algorithm ? dg->algorithm : ""); + return HA_FALSE; + } + + /* Request URI */ + if(!dg->uri) + { + ha_messagex(LOG_WARNING, "digest response is missing uri"); + return HA_FALSE; + } + + if(strcmp(dg->uri, uri) != 0) + { + ha_uri_t d_uri; + ha_uri_t s_uri; + + if(ha_uriparse(buf, dg->uri, &d_uri) == HA_ERROR) + { + ha_messagex(LOG_WARNING, "digest response constains invalid uri: %s", dg->uri); + return HA_FALSE; + } + + if(ha_uriparse(buf, uri, &s_uri) == HA_ERROR) + { + ha_messagex(LOG_ERR, "server sent us an invalid uri"); + return HA_ERROR; + } + + if(ha_uricmp(&d_uri, &s_uri) != 0) + { + ha_messagex(LOG_WARNING, "digest response contains wrong uri: %s " + "(should be %s)", dg->uri, uri); + return HA_ERROR; + } + } + + /* + * nonce: should have already been validated by the caller who + * found this nice rec structure to pass us. + * + * opaque: We also don't use opaque. The caller should have validated it + * if it's used there. + */ + + /* + * Now we validate the digest response + */ + + /* Encode ha1 */ + t = ha_bufenchex(buf, rec->ha1, MD5_LEN); + + if(t == NULL) + return HA_ERROR; + + /* Encode ha2 */ + md5_init(&md5); + md5_update(&md5, method, strlen(method)); + md5_update(&md5, ":", 1); + md5_update(&md5, dg->uri, strlen(dg->uri)); + md5_final(hash, &md5); + + ha_bufenchex(buf, hash, MD5_LEN); + + if(!ha_bufdata(buf)) + return HA_ERROR; + + + /* Old style digest (RFC 2069) */ + if(!dg->qop) + { + md5_init(&md5); + md5_update(&md5, t, MD5_LEN * 2); /* ha1 */ + md5_update(&md5, ":", 1); + md5_update(&md5, dg->nonce, strlen(dg->nonce)); /* nonce */ + md5_update(&md5, ":", 1); + md5_update(&md5, ha_bufdata(buf), MD5_LEN * 2); /* ha1 */ + md5_final(hash, &md5); + } + + /* New style 'auth' digest (RFC 2617) */ + else + { + md5_init(&md5); + md5_update(&md5, t, MD5_LEN * 2); /* ha1 */ + md5_update(&md5, ":", 1); + md5_update(&md5, dg->nonce, strlen(dg->nonce)); /* nonce */ + md5_update(&md5, ":", 1); + md5_update(&md5, dg->nc, strlen(dg->nc)); /* nc */ + md5_update(&md5, ":", 1); + md5_update(&md5, dg->cnonce, strlen(dg->cnonce)); /* cnonce */ + md5_update(&md5, ":", 1); + md5_update(&md5, dg->qop, strlen(dg->qop)); /* qop */ + md5_update(&md5, ":", 1); + md5_update(&md5, ha_bufdata(buf), MD5_LEN * 2); /* ha2 */ + md5_final(hash, &md5); + } + + /* Encode the digest */ + digest = ha_bufenchex(buf, hash, MD5_LEN); + + if(digest == NULL) + return HA_ERROR; + + if(strcasecmp(dg->digest, digest) != 0) + { + ha_messagex(LOG_WARNING, "digest authentication failed for user: %s", dg->username); + return HA_FALSE; + } + + return HA_OK; +} + +const char* digest_respond(ha_buffer_t* buf, digest_header_t* dg, + digest_record_t* rec, unsigned char* next) +{ + unsigned char hash[MD5_LEN]; + md5_ctx_t md5; + const char* nextnonce = NULL; + const char* t; + + if(next) + { + nextnonce = ha_bufenc64(buf, next, DIGEST_NONCE_LEN); + + if(nextnonce == NULL) + return NULL; + } + + /* For older clients RFC 2069 */ + if(dg->qop) + { + if(nextnonce) + ha_bufmcat(buf, "nextnonce=\"", nextnonce, "\"", NULL); + + return ha_bufdata(buf); + } + + /* Otherwise we do the whole song and dance */ + + /* Encode ha1 */ + t = ha_bufenchex(buf, rec->ha1, MD5_LEN); + + if(t == NULL) + return NULL; + + /* Encode ha2 */ + md5_init(&md5); + md5_update(&md5, ":", 1); + md5_update(&md5, dg->uri, strlen(dg->uri)); + md5_final(hash, &md5); + + ha_bufenchex(buf, hash, MD5_LEN); + + if(!ha_bufdata(buf)) + return NULL; + + /* New style 'auth' digest (RFC 2617) */ + md5_init(&md5); + md5_update(&md5, t, MD5_LEN * 2); /* ha1 */ + md5_update(&md5, ":", 1); + md5_update(&md5, dg->nonce, strlen(dg->nonce)); /* nonce */ + md5_update(&md5, ":", 1); + md5_update(&md5, dg->nc, strlen(dg->nc)); /* nc */ + md5_update(&md5, ":", 1); + md5_update(&md5, dg->cnonce, strlen(dg->cnonce)); /* cnonce */ + md5_update(&md5, ":", 1); + md5_update(&md5, dg->qop, strlen(dg->qop)); /* qop */ + md5_update(&md5, ":", 1); + md5_update(&md5, ha_bufdata(buf), MD5_LEN * 2); /* ha2 */ + md5_final(hash, &md5); + + /* Encode the digest */ + t = ha_bufenchex(buf, hash, MD5_LEN); + + if(t == NULL) + return NULL; + + ha_bufmcat(buf, "rspauth=\"", t, "\"", + " qop=", dg->qop, + " nc=", dg->nc, + " cnonce=\"", dg->cnonce, "\"", NULL); + + if(nextnonce) + { + ha_bufjoin(buf); + ha_bufmcat(buf, " nextnonce=\"", nextnonce, "\"", NULL); + } + + return ha_bufdata(buf); +} + +void digest_makeha1(unsigned char* digest, const char* user, + const char* realm, const char* password) +{ + md5_ctx_t 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(password)); + md5_final(digest, &md5); +} \ No newline at end of file diff --git a/daemon/digest.h b/daemon/digest.h new file mode 100644 index 0000000..6bd7acc --- /dev/null +++ b/daemon/digest.h @@ -0,0 +1,57 @@ + +#ifndef __DIGEST_H__ +#define __DIGEST_H__ + +#include "md5.h" + +#define DIGEST_NONCE_LEN sizeof(time_t) + sizeof(unsigned int) + MD5_LEN +#define DIGEST_SECRET_LEN 16 + +/* Parsed Digest response from the client */ +typedef struct digest_header +{ + const char* scheme; + const char* realm; + const char* username; + const char* nonce; + const char* uri; + const char* method; + const char* digest; + const char* algorithm; + const char* cnonce; + const char* opaque; + const char* qop; + const char* nc; +} +digest_header_t; + +/* Kept by the server for validating the client */ +typedef struct digest_record +{ + unsigned char nonce[DIGEST_NONCE_LEN]; + unsigned char userhash[MD5_LEN]; + unsigned char ha1[MD5_LEN]; + unsigned int nc; +} +digest_record_t; + +digest_record_t* digest_makerec(unsigned char* nonce, const char* user); + +int ha_digestparse(char* header, ha_buffer_t* buf, digest_header_t* rec, + unsigned char* nonce); + +int ha_digestnonce(time_t* tm, unsigned char* nonce); + +int ha_digestcheck(const char* realm, const char* method, const char* uri, + ha_buffer_t* buf, digest_header_t* header, digest_record_t* rec); + +const char* digest_respond(ha_buffer_t* buf, digest_header_t* dg, + digest_record_t* rec, unsigned char* next); + +const char* digest_challenge(ha_buffer_t* buf, unsigned char* nonce, + const char* realm, const char* domains, int stale); + +void digest_makeha1(unsigned char* digest, const char* user, + const char* realm, const char* password); + +#endif /* __DIGEST_H__ */ diff --git a/daemon/httpauthd.c b/daemon/httpauthd.c index 8af5d7f..2b74586 100644 --- a/daemon/httpauthd.c +++ b/daemon/httpauthd.c @@ -15,17 +15,22 @@ #include "usuals.h" #include "httpauthd.h" +#include "defaults.h" /* ----------------------------------------------------------------------- * Handlers Registered Here */ -extern ha_handler_t basic_handler; +extern ha_handler_t simple_handler; +extern ha_handler_t ldap_handler; +extern ha_handler_t ntlm_handler; /* This is the list of all available handlers */ -const ha_handler_t* g_handlerlist[] = +ha_handler_t* g_handlerlist[] = { - &basic_handler + &simple_handler, + &ldap_handler, + &ntlm_handler }; typedef struct httpauth_loaded @@ -63,7 +68,7 @@ const char* kAuthHeaders[] = /* The command definitions */ const httpauth_command_t kCommands[] = { - { "auth", REQTYPE_AUTH, 2, kAuthHeaders }, + { "auth", REQTYPE_AUTH, 3, kAuthHeaders }, { "quit", REQTYPE_QUIT, 0, 0 }, { NULL, -1, -1 } }; @@ -123,7 +128,7 @@ int main(int argc, char* argv[]) { const char* conf = DEFAULT_CONFIG; httpauth_thread_t* threads = NULL; - const httpauth_loaded_t* h; + httpauth_loaded_t* h; int daemonize = 1; ha_buffer_t cbuf; int r, i, sock; @@ -392,7 +397,7 @@ int httpauth_read(int ifd, ha_request_t* req, /* This guarantees a bit of memory allocated, and resets buffer */ ha_bufreset(buf); - r = ha_readline(ifd, buf); + r = ha_bufreadline(ifd, buf); if(r == -1) return -1; @@ -408,7 +413,7 @@ int httpauth_read(int ifd, ha_request_t* req, } /* Find the first space in the line */ - t = ha_parseword(buf, " \t"); + t = ha_bufparseword(buf, " \t"); if(t) { @@ -429,7 +434,7 @@ int httpauth_read(int ifd, ha_request_t* req, /* Now parse the arguments if any */ for(i = 0; i < cmd->args; i++) - req->args[i] = ha_parseword(buf, " \t"); + req->args[i] = ha_bufparseword(buf, " \t"); /* Now skip anything else we have in the buffer */ @@ -449,7 +454,7 @@ int httpauth_read(int ifd, ha_request_t* req, if(!more) break; - r = ha_readline(ifd, buf); + r = ha_bufreadline(ifd, buf); if(r == -1) return -1; @@ -477,7 +482,7 @@ int httpauth_read(int ifd, ha_request_t* req, with a space continues the previous header */ if(valid && i > 0) { - t = ha_parseline(buf, 0); + t = ha_bufparseline(buf, 0); if(t) { char* t2 = (char*)req->headers[i - 1].data + strlen(req->headers[i - 1].data); @@ -492,7 +497,7 @@ int httpauth_read(int ifd, ha_request_t* req, { if(i < MAX_HEADERS) { - t = ha_parseword(buf, ":"); + t = ha_bufparseword(buf, ":"); if(t) { @@ -512,7 +517,7 @@ int httpauth_read(int ifd, ha_request_t* req, if(t) { req->headers[i].name = t; - req->headers[i].data = ha_parseline(buf, 1); + req->headers[i].data = ha_bufparseline(buf, 1); i++; } @@ -631,11 +636,15 @@ int httpauth_ready(int ofd, ha_buffer_t* buf) httpauth_loaded_t* h; /* We send a ready banner to our client */ - ha_bufnext(buf); for(h = g_handlers; h; h = h->next) - ha_bufcat(buf, (h == g_handlers) ? "" : " ", + { + if(h != g_handlers) + ha_bufjoin(buf); + + ha_bufmcat(buf, (h != g_handlers) ? " " : "", h->ctx.name, NULL); + } if(ha_buferr(buf)) return httpauth_respond(ofd, HA_SERVER_ERROR, NULL); @@ -731,7 +740,7 @@ finally: int process_auth(ha_request_t* req, ha_response_t* resp, ha_buffer_t* outb) { - const httpauth_loaded_t* h; + httpauth_loaded_t* h; /* Clear out our response */ memset(resp, 0, sizeof(*resp)); @@ -769,7 +778,7 @@ int process_auth(ha_request_t* req, ha_response_t* resp, */ ha_context_t* config_addhandler(ha_buffer_t* buf, const char* alias, - const ha_handler_t* handler, const ha_context_t* defaults) + ha_handler_t* handler, const ha_context_t* defaults) { httpauth_loaded_t* loaded; int len = sizeof(httpauth_loaded_t) + handler->context_size; @@ -834,7 +843,7 @@ int config_parse(const char* file, ha_buffer_t* buf) int fd; const char** t; char* name; - const char* value; + char* value; int more = 1; int recog; int r, i; @@ -856,7 +865,7 @@ int config_parse(const char* file, ha_buffer_t* buf) { ha_bufskip(buf); - if((more = ha_readline(fd, buf)) == -1) + if((more = ha_bufreadline(fd, buf)) == -1) return -1; line++; @@ -876,11 +885,11 @@ int config_parse(const char* file, ha_buffer_t* buf) /* Check for a handler */ if(ha_bufchar(buf) == '[') { - const ha_handler_t* handler = NULL; + ha_handler_t* handler = NULL; const char* x; ha_bufeat(buf); - name = ha_parseline(buf, 1); + name = ha_bufparseline(buf, 1); if(!name || name[strlen(name) - 1] != ']') errx(1, "configuration section invalid (line %d)", line); @@ -905,7 +914,7 @@ int config_parse(const char* file, ha_buffer_t* buf) errx(1, "unknown handler type '%s' (line %d)", name, line); /* If we had a last handler then add it to handlers */ - loaded = config_addhandler(buf, name, handler, defaults); + ctx = config_addhandler(buf, name, handler, &defaults); /* Rest doesn't apply to handler headers */ continue; @@ -913,14 +922,14 @@ int config_parse(const char* file, ha_buffer_t* buf) /* Parse out the name */ - name = ha_parseword(buf, ":"); + name = ha_bufparseword(buf, ":"); if(!name || !name[0]) errx(1, "configuration file invalid (line %d)", line); strlwr(name); /* And get the rest of the line */ - value = ha_parseline(buf, 1); + value = ha_bufparseline(buf, 1); if(value == NULL) errx(1, "configuration missing value at (line %d)", line); @@ -939,8 +948,8 @@ int config_parse(const char* file, ha_buffer_t* buf) else if(strcmp("maxthreads", name) == 0) { - if(ha_confint(value, 1, 256, &g_maxthreads) == -1) - errx(1, "invalid value for '%s'. must be a number between 1 and 256", name); + if(ha_confint(name, value, 1, 256, &g_maxthreads) == -1) + exit(1); recog = 1; } @@ -951,13 +960,13 @@ int config_parse(const char* file, ha_buffer_t* buf) { if(strcmp("alias", name) == 0) { - loaded->alias = value; + ctx->name = value; recog = 1; } - if(loaded->ctx.handler->f_config) + if(ctx->handler->f_config) { - r = (loaded->ctx.handler->f_config)(&(loaded->ctx), name, value); + r = (ctx->handler->f_config)(ctx, name, value); if(r == -1) return -1; diff --git a/daemon/httpauthd.h b/daemon/httpauthd.h index 536dfdc..b710444 100644 --- a/daemon/httpauthd.h +++ b/daemon/httpauthd.h @@ -16,31 +16,14 @@ typedef struct ha_buffer } ha_buffer_t; +/* Initializes a buffer */ void ha_bufinit(ha_buffer_t* buf); -void ha_buffree(ha_buffer_t* buf); -void ha_bufreset(ha_buffer_t* buf); - -/* Buffer input functions */ -int ha_readline(int fd, ha_buffer_t* buf); -char* ha_parseline(ha_buffer_t* buf, int trim); -char* ha_parseword(ha_buffer_t* buf, const char* delims); - -/* Buffer output functions */ -void ha_bufnext(ha_buffer_t* buf); -void ha_bufcat(ha_buffer_t* buf, ...); -/* Buffer encoding functions */ -void ha_bufenc64(ha_buffer_t* buf, const const char* src, size_t len); -void ha_bufdec64(ha_buffer_t* buf, const char* src, size_t len); - -void ha_bufenchex(ha_buffer_t* buf, const unsigned char* src, size_t len); -void ha_bufdechex(ha_buffer_t* buf, const char* src, size_t len); - -/* Memory allocation functions */ -void* ha_bufmalloc(ha_buffer_t* buf, size_t sz); +/* Frees all memory associated with a buffer */ +void ha_buffree(ha_buffer_t* buf); -#define ha_bufskip(buf) \ - ((buf)->_pp = (buf)->_rp) +/* Resets a buffer for later reuse */ +void ha_bufreset(ha_buffer_t* buf); #define ha_buflen(buf) \ ((buf)->_rp - (buf)->_pp) @@ -51,32 +34,87 @@ void* ha_bufmalloc(ha_buffer_t* buf, size_t sz); #define ha_bufdata(buf) \ ((buf)->_pp) +#define ha_buferr(buf) \ + ((buf)->_dt == NULL) + +/* Buffer input functions ------------------------------------------------ */ + +/* Read a line from an input handle */ +int ha_bufreadline(int fd, ha_buffer_t* buf); + +/* Parse the current line */ +char* ha_bufparseline(ha_buffer_t* buf, int trim); + +/* Parse a word from the current block */ +char* ha_bufparseword(ha_buffer_t* buf, const char* delims); + +#define ha_bufskip(buf) \ + ((buf)->_pp = (buf)->_rp) + #define ha_bufeat(buf) \ ((!ha_buferr(buf) && ha_buflen(buf) > 0) ? ++((buf)->_pp) : (buf)->_pp) -#define ha_buferr(buf) \ - ((buf)->_dt == NULL) +/* Buffer output functions ----------------------------------------------- */ + +/* Adds multiple strings together */ +char* ha_bufmcat(ha_buffer_t* buf, ...); + +/* Copies a string to the buffer */ +char* ha_bufcpy(ha_buffer_t* buf, const char* src); + +/* Copies a portion of a string to the buffer */ +char* ha_bufncpy(ha_buffer_t* buf, const char* src, size_t len); + +/* Opens up the end of the current block so it can be joined by more data */ +#define ha_bufjoin(buf) \ + ((buf)->_rp && ((buf)->_rp != (buf)->_pp) ? (buf)->_rp-- : (buf)->_rp) + +#define ha_bufcat ha_bufcpy + +/* Buffer allocation functions ------------------------------------------- */ + +/* Memory allocation */ +void* ha_bufmalloc(ha_buffer_t* buf, size_t bytes); + +void* ha_bufmemdup(ha_buffer_t* buf, const void* src, size_t bytes); + +/* Buffer Encoding Functions --------------------------------------------- */ + +/* Encode an array of bytes in base 64 */ +char* ha_bufenc64(ha_buffer_t* buf, const void* src, size_t bytes); + +/* Decode an array of bytes from base 64 */ +void* ha_bufdec64(ha_buffer_t* buf, const char* src, size_t bytes); + +/* Encode an array of bytes in hex */ +char* ha_bufenchex(ha_buffer_t* buf, const void* src, size_t bytes); + +/* Decode an array of bytes in hex */ +void* ha_bufdechex(ha_buffer_t* buf, const char* src, size_t bytes); + /* ----------------------------------------------------------------------- * HTTP Auth Handlers */ -typedef struct ha_context_t; +struct ha_context; +struct ha_request; +struct ha_response; /* * This function initializes the handler. It gets called * after the configuration gets loaded so if a config func * is registered it'll get called before this. */ -typedef int (*auth_init_t)(ha_context_t* ctx); +typedef int (*auth_init_t)(struct ha_context* ctx); /* * This function is called when the app exits. All threads * should have completed at this point, so it's not necessary * to be thread safe in here */ -typedef void (*auth_destroy_t)(ha_context_t* ctx); +typedef void (*auth_destroy_t)(struct ha_context* ctx); /* * Called once for each configuration parameter. This is @@ -84,15 +122,15 @@ typedef void (*auth_destroy_t)(ha_context_t* ctx); * always be lower case. White space will always be trimmed * from the value. */ -typedef int (*auth_config_t)(ha_context_t* ctx, const char* name, const char* value); +typedef int (*auth_config_t)(struct ha_context* ctx, const char* name, const char* value); /* * Called for each authentication request that is designated * for this handler. Note that all data access in this * function must be thread-safe. */ -typedef int (*auth_process_t)(ha_context_t* ctx, ha_request_t* req, - ha_response_t* resp, ha_buffer_t* mem); +typedef int (*auth_process_t)(struct ha_context* ctx, struct ha_request* req, + struct ha_response* resp, ha_buffer_t* mem); /* An authentication handler */ typedef struct ha_handler @@ -131,7 +169,7 @@ ha_handler_t; struct ha_options; /* Context passed to the handler functions below */ -typdef struct ha_context +typedef struct ha_context { const char* name; /* A name assigned by the configuration file */ ha_handler_t* handler; /* The original handler structure */ @@ -152,7 +190,7 @@ ha_context_t; * should be no need to change it unless we're * adding or removing commands */ -#define MAX_ARGS 2 +#define MAX_ARGS 6 /* * The maximum number of pertinent headers to read @@ -183,6 +221,10 @@ ha_header_t; #define REQTYPE_QUIT 1 #define REQTYPE_AUTH 2 +#define AUTH_ARG_CONN 0 +#define AUTH_ARG_METHOD 1 +#define AUTH_ARG_URI 2 + /* A single request from client */ typedef struct ha_request { @@ -220,13 +262,6 @@ void ha_addheader(ha_response_t* resp, const char* name, const char* data); int ha_confbool(const char* name, const char* conf, int* value); int ha_confint(const char* name, const char* conf, int min, int max, int* value); -/* A little hashing */ -#ifndef MD5_LEN - #define MD5_LEN 16 -#endif - -void ha_md5string(const char* data, unsigned char* hash); - /* ----------------------------------------------------------------------- * Error Handling */ @@ -244,54 +279,8 @@ void ha_messagex(int level, const char* msg, ...); #define HA_TYPE_BASIC 1 << 1 #define HA_PREFIX_BASIC "Basic " -typedef struct ha_basic_header -{ - const char* user; - const char* password; - unsigned char key[MD5_LEN]; -} -ha_basic_header_t; - -int ha_parsebasic(char* header, ha_buffer_t* buf, ha_basic_header_t* rec); - - #define HA_TYPE_DIGEST 1 << 2 #define HA_PREFIX_DIGEST "Digest " -#define HA_DIGEST_NONCE_LEN MD5_LEN * 2 - -/* Parsed Digest response from the client */ -typedef struct ha_digest_header -{ - const char* scheme; - const char* realm; - const char* username; - const char* nonce; - const char* uri; - const char* method; - const char* digest; - const char* algorithm; - const char* cnonce; - const char* opaque; - const char* message_qop; - const char* nc; - unsigned char key[MD5_LEN]; -} -ha_digest_header_t; - -/* Kept by the server for validating the client */ -typedef struct ha_digest_record -{ - unsigned char nonce[HA_DIGEST_NONCE_LEN]; - unsigned char userhash[MD5_LEN]; - unsigned char ha1[MD5_LEN]; - unsigned int nc; -} -ha_digest_record_t; - -int ha_digestparse(char* header, ha_buffer_t* buf, ha_digest_header_t* rec); -int ha_digestcheck(const char* realm, const char* method, const char* uri, - ha_buffer_t* buf, ha_digest_header_t* header, ha_digest_record_t* rec); - #define HA_TYPE_NTLM 1 << 3 #define HA_PREFIX_NTLM "NTLM " @@ -301,21 +290,22 @@ int ha_digestcheck(const char* realm, const char* method, const char* uri, * URI Parse Support */ -struct ha_uri_t +typedef struct ha_uri { - /* Note: We only support HTTP uris */ + const char* scheme; const char* user; const char* pw; const char* host; unsigned short port; const char* path; const char* query; - const char* bookmark; -}; - + const char* fragment; +} +ha_uri_t; -char* ha_uriformat(const ha_uri_t* uri, ha_buffer_t* buf); -int ha_uriparse(const char* str, ha_uri_t* uri); +char* ha_uriformat(ha_buffer_t* buf, const ha_uri_t* uri); +int ha_uriparse(ha_buffer_t* buf, const char* suri, ha_uri_t* uri); +int ha_uricmp(ha_uri_t* one, ha_uri_t* two); /* ----------------------------------------------------------------------- @@ -325,4 +315,12 @@ int ha_uriparse(const char* str, ha_uri_t* uri); void ha_lock(); void ha_unlock(); + +/* ----------------------------------------------------------------------- + * Miscellaneous + */ + +int ha_genrandom(unsigned char* data, size_t len); + + #endif /* __HTTPAUTHD_H__ */ 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 +#include #include /* LDAP library */ #include +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 */ diff --git a/daemon/misc.c b/daemon/misc.c index 7091bfa..b585d8b 100644 --- a/daemon/misc.c +++ b/daemon/misc.c @@ -5,6 +5,8 @@ #include #include +#include +#include extern int g_debugging; extern int g_daemonized; @@ -98,9 +100,9 @@ const char* ha_getheader(ha_request_t* req, const char* name, const char* prefix if(!prefix) return req->headers[i].data; - l = strlen(prefix) + l = strlen(prefix); - if(strncasecmp(prefix, req->headers[i].value, l) == 0) + if(strncasecmp(prefix, req->headers[i].data, l) == 0) return req->headers[i].data + l; } } @@ -185,7 +187,7 @@ int ha_confbool(const char* name, const char* conf, int* value) int ha_confint(const char* name, const char* conf, int min, int max, int* value) { - const char* p; + char* p; errno = 0; *value = strtol(conf, &p, 10); @@ -200,422 +202,361 @@ int ha_confint(const char* name, const char* conf, int min, int max, int* value) } -/* ----------------------------------------------------------------------- - * Hash Stuff - */ - -void ha_md5string(const char* data, unsigned char* hash) -{ - struct MD5Context md5; - MD5Init(&md5); - MD5Update(&md5, data, strlen(data)); - MD5Final(hash, &md5); -} - - /* ----------------------------------------------------------------------- * Client Authentication Functions */ -int ha_parsebasic(char* header, ha_buffer_t* buf, ha_basic_header_t* rec) +char* ha_uriformat(ha_buffer_t* buf, const ha_uri_t* uri) { - char* t; - - memset(rec, 0, sizeof(*rec)); + /* This creates a new block */ + ha_bufcpy(buf, ""); - /* Trim the white space */ - while(*header && isspace(*header)) - header++; - - /* - * Authorization header is in this format: - * - * "Basic " B64(user ":" password) - */ - ha_bufnext(buf); - ha_bufdec64(buf, header); + if(uri->host) + { + const char* l = ""; + const char* r = ""; - header = ha_bufdata(buf); + ha_bufmcat(buf, uri->scheme ? uri->scheme : "http", + "://", NULL); - if(!header) - return HA_ERROR; + if(uri->user) + { + ha_bufjoin(buf); + ha_bufmcat(buf, uri->user, + uri->pw ? ":" : "", + uri->pw ? uri->pw : "", + "@", NULL); + } + if(strchr(uri->host, ':')) /* v6 ip */ + { + l = "["; + r = "]"; + } - /* We have a cache key at this point so hash it */ - ha_md5string(header, rec->key); + ha_bufjoin(buf); + ha_bufmcat(buf, l, uri->host, r, NULL); + } + if(uri->path) + { + ha_bufjoin(buf); + ha_bufmcat(buf, "/", uri->path); + } - /* Parse the user. We need it in any case */ - t = strchr(header, ':'); - if(t != NULL) + if(uri->query) { - /* Break the string in half */ - *t = 0; + ha_bufjoin(buf); + ha_bufmcat(buf, "?", uri->query); + } - rec->user = header; - rec->password = t + 1; + if(uri->fragment) + { + ha_bufjoin(buf); + ha_bufmcat(buf, "#", uri->fragment); } - return HA_OK; + return ha_bufdata(buf); } +int ha_uriparse(ha_buffer_t* buf, const char* suri, ha_uri_t* uri) +{ + char* s; + char* s1; + char* hostinfo; + char* endstr; + char* str; + int port; + int v6_offset1 = 0; + + /* Copy the memory */ + str = ha_bufcpy(buf, suri); + + if(!str) + return HA_ERROR; -/* - * Copyright 1999-2004 The Apache Software Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + /* Initialize the structure */ + memset(uri, 0, sizeof(*uri)); -int ha_parsedigest(char* header, ha_buffer_t* buf, ha_digest_header_t* rec) -{ /* - * This function destroys the contents of header by - * terminating strings in it all over the place. + * We assume the processor has a branch predictor like most -- + * it assumes forward branches are untaken and backwards are taken. That's + * the reason for the gotos. -djg */ - char next; - char* key; - char* value; - - ha_bufnext(buf); - ha_bufcat(buf, header, NULL); - - header = ha_bufdata(buf); - - if(!header) - return HA_ERROR; - - memset(rec, 0, sizeof(rec)); - - while(header[0]) + if(str[0] == '/') { - /* find key */ - - while(header[0] && isspace(header[0])) - header++; - - key = header; - - while(header[0] && header[0] != '=' && header[0] != ',' && - !isspace(header[0])) - header++; +deal_with_path: + *str == 0; + ++str; - /* null terminate and move on */ - next = header[0]; - header[0] = '\0'; - header++; - - if(!next) - break; - - if(isspace(t)) - { - while(header[0] && isspace(header[0])) - header++; + /* + * we expect uri to point to first character of path ... remember + * that the path could be empty -- http://foobar?query for example + */ + s = str; + while(*s && *s != '?' && *s != '#') + ++s; - next = header[0]; - } + if(s != str) + uri->path = str; - /* find value */ + if(*s == 0) + return HA_OK; - if(next == '=') + if(*s == '?') { - header++; - - while(header[0] && isspace(header[0])) - header++; + *s = 0; + ++s; + uri->query = s; - vv = 0; - if(header[0] == '\"') /* quoted string */ + s1 = strchr(s, '#'); + if(s1) { - header++; - value = header; - - while(header[0] && header[0] != '\"') - header++; - - header[0] = 0; - header++; + *s1 = 0; + ++s1; + uri->fragment = s1; } - else /* token */ - { - value = header; - - while(header[0] && header[0] != ',' && !isspace(header[0])) - header++; + return HA_OK; + } - header[0] = 0; - header++; - } + *s = 0; + ++s; - while(header[0] && header[0] != ',') - header++; - - if(header[0]) - header++; - - if(!strcasecmp(key, "username")) - rec->username = value; - else if(!strcasecmp(key, "realm")) - rec->realm = value; - else if (!strcasecmp(key, "nonce")) - rec->nonce = value; - else if (!strcasecmp(key, "uri")) - rec->uri = value; - else if (!strcasecmp(key, "response")) - rec->digest = value; - else if (!strcasecmp(key, "algorithm")) - rec->algorithm = value; - else if (!strcasecmp(key, "cnonce")) - rec->cnonce = value; - else if (!strcasecmp(key, "opaque")) - rec->opaque = value; - else if (!strcasecmp(key, "qop")) - rec->qop = value; - else if (!strcasecmp(key, "nc")) - rec->nc = value; - } + /* otherwise it's a fragment */ + uri->fragment = s; + return HA_OK; } - if(rec->nonce) - ha_md5string(rec->nonce, rec->key); + /* find the scheme: */ + s = str; - return HA_OK; -} + while(*s && *s != ':' && *s != '/' && *s != '?' && *s != '#') + s++; -int ha_digestcheck(const char* realm, const char* method, const char* uri, - ha_buffer_t* buf, ha_digest_header_t* dg, ha_digest_record_t* rec) -{ - unsigned char hash[MD5_LEN]; - struct MD5Context md5; - const char* digest; - const char* t; + /* scheme must be non-empty and followed by :// */ + if(s == str || s[0] != ':' || s[1] != '/' || s[2] != '/') + goto deal_with_path; /* backwards predicted taken! */ - /* Check for digest */ - if(!dg->digest || !dg->digest[0]) - { - ha_messagex(LOG_WARNING, "digest response missing digest"); - return HA_FALSE; - } + uri->scheme = str; + *s = 0; + s += 3; - /* Username */ - if(!dg->username || !dg->username[0] || - ha_md5strcmp(dg->username, rec->userhash) != 0) - { - ha_messagex(LOG_WARNING, "digest response missing username"); - return HA_FALSE; - } + hostinfo = s; + + while(*s && *s != '/' && *s != '?' && *s != '#') + ++s; - /* The realm */ - if(!dg->realm || strcmp(dg->realm, realm) != 0) + str = s; /* whatever follows hostinfo is start of uri */ + + /* + * If there's a username:password@host:port, the @ we want is the last @... + * too bad there's no memrchr()... For the C purists, note that hostinfo + * is definately not the first character of the original uri so therefore + * &hostinfo[-1] < &hostinfo[0] ... and this loop is valid C. + */ + do { - ha_messagex(LOG_WARNING, "digest response contains invalid realm: '%s'", - dg->realm ? dg->realm : ""); - return HA_FALSE; + --s; } + while(s >= hostinfo && *s != '@'); - /* Components in the new RFC */ - if(dg->qop) + if(s < hostinfo) { + /* again we want the common case to be fall through */ +deal_with_host: /* - * We only support 'auth' qop. We don't have access to the data - * and wouldn't be able to support anything else. + * We expect hostinfo to point to the first character of + * the hostname. If there's a port it is the first colon, + * except with IPv6. */ - if(strcmp(dg->qop, "auth") != 0) - { - ha_messagex(LOG_WARNING, "digest response contains unknown or unsupported qop: '%s'", - dg->qop ? dg->qop : ""); - return HA_FALSE; - } - - /* The nonce */ - if(!dg->cnonce || !dg->cnonce[0]) + if(*hostinfo == '[') { - ha_messagex(LOG_WARNING, "digest response is missing cnonce value"); - return HA_FALSE; - } + v6_offset1 = 1; - /* The nonce count */ - if(!dg->nc || !dg->nc[0]) - { - ha_messagex(LOG_WARNING, "digest response is missing nc value"); - return HA_FALSE; + s = str; + for(;;) + { + --s; + + if(s < hostinfo) + { + s = NULL; /* no port */ + break; + } + + else if(*s == ']') + { + *s = 0; + s = NULL; /* no port */; + break; + } + + else if(*s == ':') + { + *s = 0; + ++s; /* found a port */ + break; + } + } } - /* Validate the nc */ else { - char* e; - long nc = strtol(dg->nc, &e, 10); - - if(e != (dg->nc + strlen(e)) || nc != rec->nc) + s = memchr(hostinfo, ':', str - hostinfo); + if(s) { - ha_messagex(LOG_WARNING, "digest response has invalid nc value: %s", - dg->nc); - return HA_FALSE; + *s = 0; + ++s; } } - } - - /* The algorithm */ - if(dg->algorithm && strcasecmp(dg->algorithm, "MD5") != 0) - { - ha_messagex(LOG_WARNING, "digest response contains unknown or unsupported algorithm: '%s'", - dg->algorithm ? dg->algorithm : ""); - return HA_FALSE; - } - /* Request URI */ - if(!dg->uri) - { - ha_messagex(LOG_WARNING, "digest response is missing uri"); - return HA_FALSE; - } - - if(strcmp(dg->uri, uri) != 0) - { - ha_uri_t d_uri; - ha_uri_t s_uri; + uri->host = hostinfo + v6_offset1; - if(ha_uriparse(dg->uri, &d_uri) == HA_ERROR) + if(s == NULL) { - ha_messagex(LOG_WARNING, "digest response constains invalid uri: %s", dg->uri); - return HA_FALSE; + /* we expect the common case to have no port */ + goto deal_with_path; } - if(ha_uriparse(uri, &s_uri) == HA_ERROR) + if(str != s) { - ha_messagex(LOG_ERR, "server sent us an invalid uri"); - return HA_ERROR; + port = strtol(s, &endstr, 10); + uri->port = port; + if(*endstr != '\0') + return HA_FALSE; } - if(ha_uricmp(&d_uri, &s_uri) != 0) - { - ha_messagex(LOG_WARNING, "digest response contains wrong uri: %s " - "(should be %s)", dg->uri, uri); - return HA_ERROR; - } + goto deal_with_path; } - /* - * nonce: should have already been validated by the caller who - * found this nice rec structure to pass us. - * - * opaque: We also don't use opaque. The caller should have validated it - * if it's used there. - */ + /* Remove the @ symbol */ + *s = 0; - /* - * Now we validate the digest response - */ + /* first colon delimits username:password */ + s1 = memchr(hostinfo, ':', s - hostinfo); + if(s1) + { + *s1 = 0; + ++s1; - /* Encode ha1 */ - ha_bufnext(buf); - ha_bufenchex(buf, rec->ha1, MD5_LEN); + uri->user = hostinfo; + uri->pw = s1; + } + else + { + str = hostinfo; + } - if((t = ha_bufdata(buf)) == NULL) - return HA_ERROR; + hostinfo = s + 1; + goto deal_with_host; +} - /* Encode ha2 */ - MD5Init(&md5); - MD5Update(&md5, method, strlen(method)); - MD5Update(&md5, ":", 1); - MD5Update(&md5, dg->uri, strlen(dg->uri)); - MD5Final(hash, &md5); +int uri_cmp_part_s(const char* s1, const char* s2, const char* def, int cs) +{ + if(def) + { + if(s1 && strcmp(def, s1) == 0) + s1 = NULL; + if(s2 && strcmp(def, s2) == 0) + s2 = NULL; + } - ha_bufnext(buf); - ha_bufenchex(buf, hash, MD5_LEN); + if(!s1 && !s2) + return 0; - if(!ha_bufdata(buf)) - return HA_ERROR; + if(!s1) + return -1; + if(!s2) + return 1; - /* Old style digest (RFC 2069) */ - if(!dg->qop) - { - MD5Init(&md5); - MD5Update(&md5, t, MD5_LEN * 2); /* ha1 */ - MD5Update(&md5, ":", 1); - MD5Update(&md5, dg->nonce, strlen(dg->nonce)); /* nonce */ - MD5Update(&md5, ":", 1); - MD5Update(&md5, ha_bufdata(buf), MD5_LEN * 2); /* ha1 */ - MD5Final(hash, &md5); - } + return cs ? strcasecmp(s1, s2) : strcmp(s1, s2); +} - /* New style 'auth' digest (RFC 2617) */ - else +int uri_cmp_part_n(int n1, int n2, int def) +{ + if(def) { - MD5Init(&md5); - MD5Update(&md5, t, MD5_LEN * 2); /* ha1 */ - MD5Update(&md5, ":", 1); - MD5Update(&md5, dg->nonce, strlen(dg->nonce)); /* nonce */ - MD5Update(&md5, ":", 1); - MD5Update(&md5, dg->nc, strlen(dg->nc)); /* nc */ - MD5Update(&md5, ":", 1); - MD5Update(&md5, dg->cnonce, strlen(dg->cnonce)); /* cnonce */ - MD5Update(&md5, ":", 1); - MD5Update(&md5, dg->qop, strlen(dg->qop)); /* qop */ - MD5Update(&md5, ":", 1); - MD5Update(&md5, ha_bufdata(buf), MD5_LEN * 2); /* ha2 */ - MD5Final(hash, &md5); + if(n1 == def) + n1 = 0; + if(n2 == def) + n2 = 0; } - /* Encode the digest */ - ha_bufnext(buf); - ha_bufenchex(buf, hash, MD5_LEN); + if(n1 == n2) + return 0; - if((digest = ha_bufdata(buf)) == NULL) - return HA_ERROR; + else if(n1 < n2) + return -1; - if(strcasecmp(dg->digest, digest) != 0) - { - ha_messagex(LOG_WARNING, "digest authentication failed for user: %s", dg->username); - return HA_FALSE; - } + else if(n2 > n1) + return 1; - return HA_OK; + /* Not reached */ + ASSERT(0); } -const char* ha_digestrespond(ha_buffer_t* buf, ha_digest_header_t* dg, - ha_digest_rec_t* rec) +int ha_uricmp(ha_uri_t* one, ha_uri_t* two) { - unsigned char hash[MD5_LEN]; - struct MD5Context md5; - const char* t; - const char* digest; + int r; - /* Encode ha1 */ - ha_bufnext(buf); - ha_bufenchex(buf, rec->ha1, MD5_LEN); + /* We don't compare user or password */ - if((t = ha_bufdata(buf)) == NULL) - return HA_ERROR; + /* The scheme */ + if((r = uri_cmp_part_s(one->scheme, two->scheme, "http", 0)) != 0 || + (r = uri_cmp_part_s(one->host, two->host, NULL, 1)) != 0 != 0 || + (r = uri_cmp_part_n(one->port, two->port, 80)) != 0 || + (r = uri_cmp_part_s(one->path, two->path, NULL, 0)) != 0 || + (r = uri_cmp_part_s(one->query, two->query, NULL, 0)) != 0 || + (r = uri_cmp_part_s(one->fragment, two->fragment, NULL, 0))) + return r; - /* Encode ha2 */ - MD5Init(&md5); - MD5Update(&md5, method, strlen(method)); - MD5Update(&md5, ":", 1); - MD5Update(&md5, dg->uri, strlen(dg->uri)); - MD5Final(hash, &md5); + return 0; +} - ha_bufnext(buf); - ha_bufenchex(buf, hash, MD5_LEN); +int ha_genrandom(unsigned char* data, size_t len) +{ + int r, dd; - if(!ha_bufdata(buf)) + dd = open("/dev/urandom", O_RDONLY); + if(dd == -1) + { + ha_message(LOG_ERR, "couldn't open /dev/urandom"); return HA_ERROR; -} + } + + for(;;) + { + r = read(dd, data, len); + + if(r == -1) + { + switch(errno) + { + case EINTR: + case EAGAIN: + continue; + + default: + ha_message(LOG_ERR, "couldn't read random bytes from /dev/urandom"); + break; + } + break; + } + + else if(r >= 0) + { + if(r >= len) + break; -char* ha_uriformat(const ha_uri_t* uri, ha_buffer_t* buf); -int ha_uriparse(const char* str, ha_uri_t* uri); + len -= r; + data += r; + } + } + + close(dd); + return r == -1 ? HA_ERROR : HA_OK; +} diff --git a/daemon/ntlm.c b/daemon/ntlm.c index 880ae9e..bdf2116 100644 --- a/daemon/ntlm.c +++ b/daemon/ntlm.c @@ -5,6 +5,8 @@ #include "httpauthd.h" #include "hash.h" #include "defaults.h" +#include "md5.h" +#include "basic.h" #include @@ -15,8 +17,7 @@ * Defaults and Constants */ -/* This needs to be the same as an MD5 hash length */ -#define NTLM_HASH_KEY_LEN 16 +#define NTLM_HASH_KEY_LEN MD5_LEN #define NTLM_ESTABLISHED (void*)1 @@ -75,8 +76,7 @@ static ntlm_connection_t* getpending(ntlm_context_t* ctx, const void* key) ha_lock(NULL); - if(ret = (ntlm_connection_t*)hash_get(ctx->pending, key)) - hash_rem(ctx->pending, key); + ret = (ntlm_connection_t*)hash_rem(ctx->pending, key); ha_unlock(NULL); @@ -87,18 +87,18 @@ static int putpending(ntlm_context_t* ctx, const void* key, ntlm_connection_t* c { int r = 0; - ha_lock(NULL); + if(!hash_get(ctx->pending, key)) + { + ha_lock(NULL); - if(hash_get(ctx->pending, key)) - { if(!hash_set(ctx->pending, key, (void*)conn)) { ha_messagex(LOG_ERR, "out of memory"); r = -1; } - } - ha_unlock(NULL); + ha_unlock(NULL); + } return r; } @@ -132,7 +132,7 @@ static ntlm_connection_t* makeconnection(ntlm_context_t* ctx) } } -static void freeconnection(ntlm_context_t* ctx, ntlm_connection_t* conn) +static void freeconnection(ntlm_connection_t* conn) { if(conn->handle) { @@ -143,13 +143,20 @@ static void freeconnection(ntlm_context_t* ctx, ntlm_connection_t* conn) free(conn); } +static void free_hash_object(void* arg, void* val) +{ + if(val) + freeconnection((ntlm_connection_t*)val); +} + int ntlm_auth_basic(ntlm_context_t* ctx, char* key, const char* header, ha_response_t* resp, ha_buffer_t* buf) { ntlm_connection_t* conn; char* t; - ha_basic_header_t basic; + basic_header_t basic; const char* domain = NULL; + int found = 0; /* * We're doing basic authentication on the connection @@ -158,15 +165,15 @@ int ntlm_auth_basic(ntlm_context_t* ctx, char* key, const char* header, */ conn = getpending(ctx, key); if(conn) - freeconnection(ctx, conn); + freeconnection(conn); - if(ha_parsebasic(header, buf, &basic) == HA_ERROR) + if(basic_parse(header, buf, &basic) == HA_ERROR) return HA_ERROR; /* Check and see if this connection is in the cache */ ha_lock(NULL); - if(hash_get(ctx->established, basic.key) == BASIC_ESTABLISHED) + if(hash_get(ctx->established, basic.key) == NTLM_ESTABLISHED) found = 1; ha_unlock(NULL); @@ -204,18 +211,26 @@ int ntlm_auth_basic(ntlm_context_t* ctx, char* key, const char* header, ctx->backup, domain) == NTV_NO_ERROR) { /* If valid then we return */ - resp->code = HA_SERVER_ACCEPT; + found = 1; } ha_unlock(&g_smblib_mutex); } - if(resp->code = HA_SERVER_ACCEPT) + if(found) { + int r; + resp->code = HA_SERVER_ACCEPT; resp->detail = basic.user; - /* We put this connection into the successful connections */ - if(!hash_set(ctx->established, basic.key, BASIC_ESTABLISHED)) + ha_lock(NULL); + + /* We put this connection into the successful connections */ + r = hash_set(ctx->established, basic.key, NTLM_ESTABLISHED); + + ha_unlock(NULL); + + if(!r) { ha_messagex(LOG_CRIT, "out of memory"); return HA_ERROR; @@ -255,8 +270,7 @@ int ntlm_auth_ntlm(ntlm_context_t* ctx, void* key, const char* header, * is sending us. */ - ha_bufnext(buf); - ha_bufdec64(buf, header); + ha_bufdec64(buf, header, 0); header = ha_bufdata(buf); if(ha_buferr(buf)) @@ -312,14 +326,11 @@ int ntlm_auth_ntlm(ntlm_context_t* ctx, void* key, const char* header, if(ctx->pending_max != -1) { ha_lock(NULL); - r = (hash_count(ctx->pending) >= ctx->pending_max); - ha_unlock(NULL); - if(r) - { - resp->code = HA_SERVER_BUSY; - goto finally; - } + if(hash_count(ctx->pending) >= ctx->pending_max) + hash_bump(ctx->pending); + + ha_unlock(NULL); } @@ -340,19 +351,20 @@ int ntlm_auth_ntlm(ntlm_context_t* ctx, void* key, const char* header, conn->flags = flags; /* Start building the header */ - ha_bufnext(buf); - ha_bufcat(buf, NTLM_PREFIX); + ha_bufcpy(buf, HA_PREFIX_NTLM); if(win9x) { struct ntlm_msg2_win9x msg_win9x; ntlmssp_encode_msg2_win9x(conn->nonce, &msg_win9x, (char*)ctx->domain, flags); + ha_bufjoin(buf); ha_bufenc64(buf, (unsigned char*)&msg_win9x, sizeof(msg_win9x)); } else { struct ntlm_msg2 msg; ntlmssp_encode_msg2(conn->nonce, &msg); + ha_bufjoin(buf); ha_bufenc64(buf, (unsigned char*)&msg, sizeof(msg)); } @@ -431,14 +443,25 @@ int ntlm_auth_ntlm(ntlm_context_t* ctx, void* key, const char* header, /* A successful login ends here */ else { - resp->code = HA_SERVER_ACCEPT; + int r; + resp->detail = ntlmssp.user; + + ha_lock(NULL); - /* We put this connection into the successful connections */ - if(!hash_set(ctx->established, key, NTLM_ESTABLISHED)) + /* We put this connection into the successful connections */ + r = hash_set(ctx->established, key, NTLM_ESTABLISHED); + + ha_unlock(NULL); + + if(!r) { ha_messagex(LOG_CRIT, "out of memory"); ret = HA_ERROR; } + else + { + ret = HA_OK; + } } goto finally; @@ -456,7 +479,7 @@ finally: ret = HA_ERROR; if(conn) - freeconnection(ctx, conn); + freeconnection(conn); return ret; } @@ -531,8 +554,8 @@ int ntlm_init(ha_context_t* context) } /* Initialize our tables */ - if(!(ctx->pending = hash_create(NTLM_HASH_KEY_LEN)) || - !(ctx->established = hash_create(NTLM_HASH_KEY_LEN))) + if(!(ctx->pending = hash_create(NTLM_HASH_KEY_LEN, free_hash_object, NULL)) || + !(ctx->established = hash_create(NTLM_HASH_KEY_LEN, NULL, NULL))) { ha_messagex(LOG_CRIT, "out of memory"); return HA_ERROR; @@ -545,7 +568,7 @@ int ntlm_init(ha_context_t* context) /* Create the smblib mutex */ if(pthread_mutexattr_init(&g_smblib_mutexattr) != 0 || pthread_mutexattr_settype(&g_smblib_mutexattr, HA_MUTEX_TYPE) || - pthread_mutex_init(&g_mutex, &g_smblib_mutexattr) != 0) + pthread_mutex_init(&g_smblib_mutex, &g_smblib_mutexattr) != 0) { ha_messagex(LOG_CRIT, "threading problem. can't create mutex"); return HA_ERROR; @@ -589,13 +612,11 @@ int ntlm_process(ha_context_t* context, ha_request_t* req, resp->code = -1; /* Hash the unique key */ - ha_md5string(req->args[1], key); + md5_string(key, req->args[AUTH_ARG_CONN]); ha_lock(NULL); - XXX: These connections aren't being destroyed properly - /* * Purge out stale connection stuff. This includes * authenticated connections which have expired as @@ -608,9 +629,9 @@ int ntlm_process(ha_context_t* context, ha_request_t* req, /* Look for a NTLM header */ - if(context->types & HA_TYPES_NTLM) + if(context->types & HA_TYPE_NTLM) { - header = ha_getheader(req, "Authorization", NTLM_PREFIX); + header = ha_getheader(req, "Authorization", HA_PREFIX_NTLM); if(header) { /* Trim off for decoding */ @@ -627,7 +648,7 @@ int ntlm_process(ha_context_t* context, ha_request_t* req, if(!header && context->types & HA_TYPE_BASIC) { /* Look for a Basic header */ - header = ha_getheader(req, "Authorization", BASIC_PREFIX); + header = ha_getheader(req, "Authorization", HA_PREFIX_BASIC); if(header) { /* Trim off for decoding */ @@ -669,14 +690,16 @@ int ntlm_process(ha_context_t* context, ha_request_t* req, resp->code = HA_SERVER_DECLINE; if(context->types & HA_TYPE_NTLM) - ha_addheader(resp, "WWW-Authenticate", NTLM_PREFIX); + ha_addheader(resp, "WWW-Authenticate", HA_PREFIX_NTLM); if(context->types & HA_TYPE_BASIC) { - ha_bufnext(buf); - ha_bufcat(buf, BASIC_PREFIX, "realm=\"", + ha_bufmcat(buf, HA_PREFIX_BASIC, "realm=\"", ctx->basic_realm ? ctx->basic_realm : "", "\"", NULL); + if(ha_buferr(buf)) + return HA_ERROR; + ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf)); } } @@ -697,7 +720,7 @@ ha_handler_t ntlm_handler = ntlm_destroy, /* Uninitialization routine */ ntlm_config, /* Config routine */ ntlm_process, /* Processing routine */ - ntlm_defaults, /* Default settings */ + &ntlm_defaults, /* Default settings */ sizeof(ntlm_context_t) }; diff --git a/daemon/simple.c b/daemon/simple.c new file mode 100644 index 0000000..cd1b812 --- /dev/null +++ b/daemon/simple.c @@ -0,0 +1,663 @@ + +/* On some linux this is required to get crypt to show itself */ +#define _XOPEN_SOURCE + +#include "usuals.h" +#include "httpauthd.h" +#include "defaults.h" +#include "basic.h" +#include "digest.h" +#include "hash.h" +#include "md5.h" + +#include +#include +#include + +unsigned char g_simple_secret[DIGEST_SECRET_LEN]; + +#define SIMPLE_MAXLINE 256 +#define BASIC_ESTABLISHED (void*)1 + +/* ------------------------------------------------------------------------------- + * Structures + */ + +typedef struct simple_context +{ + const char* filename; /* The file name with the user names */ + const char* realm; /* The realm for basic authentication */ + const char* domains; /* Domains for which digest auth is valid */ + int cache_max; /* Maximum number of connections at once */ + + /* Context ----------------------------------------------------------- */ + hash_t* cache; /* Some cached records or basic */ +} +simple_context_t; + +/* The defaults for the context */ +static const simple_context_t simple_defaults = +{ + NULL, /* filename */ + NULL, /* realm */ + NULL, /* domains */ + 1000, /* cache_max */ + NULL /* cache */ +}; + + +/* ------------------------------------------------------------------------------- + * Internal Functions + */ + +static void free_hash_object(void* arg, void* val) +{ + if(val && val != BASIC_ESTABLISHED) + free(val); +} + +static digest_record_t* get_cached_digest(simple_context_t* ctx, unsigned char* nonce) +{ + digest_record_t* rec; + + if(ctx->cache_max == 0) + return NULL; + + ha_lock(NULL); + + rec = (digest_record_t*)hash_get(ctx->cache, nonce); + + /* Just in case it's a basic :) */ + if(rec && rec != BASIC_ESTABLISHED) + hash_rem(ctx->cache, nonce); + + ha_unlock(NULL); + + ASSERT(!rec || memcmp(nonce, rec->nonce, DIGEST_NONCE_LEN) == 0); + return rec; +} + +static int have_cached_basic(simple_context_t* ctx, unsigned char* key) +{ + int ret = 0; + + ha_lock(NULL); + + ret = (hash_get(ctx->cache, key) == BASIC_ESTABLISHED); + + ha_unlock(NULL); + + return ret; +} + +static int save_cached_digest(simple_context_t* ctx, digest_record_t* rec) +{ + int r; + + if(ctx->cache_max == 0) + return HA_FALSE; + + ha_lock(NULL); + + while(hash_count(ctx->cache) >= ctx->cache_max) + hash_bump(ctx->cache); + + r = hash_set(ctx->cache, rec->nonce, rec); + + ha_unlock(NULL); + + if(!r) + { + ha_messagex(LOG_CRIT, "out of memory"); + return HA_ERROR; + } + + return HA_OK; +} + +static int add_cached_basic(simple_context_t* ctx, unsigned char* key) +{ + int r; + + if(ctx->cache_max == 0) + return HA_FALSE; + + ha_lock(NULL); + + while(hash_count(ctx->cache) >= ctx->cache_max) + hash_bump(ctx->cache); + + r = hash_set(ctx->cache, key, BASIC_ESTABLISHED); + + ha_unlock(NULL); + + if(!r) + { + ha_messagex(LOG_CRIT, "out of memory"); + return HA_ERROR; + } + + return HA_OK; +} + +static int complete_digest_ha1(simple_context_t* ctx, digest_record_t* rec, + ha_buffer_t* buf, const char* user, int* code) +{ + FILE* f; + int found = 0; + char* t; + char line[SIMPLE_MAXLINE]; + + f = fopen(ctx->filename, "r"); + if(!f) + { + ha_message(LOG_ERR, "can't open file for basic auth: %s", ctx->filename); + *code = HA_SERVER_ERROR; + return HA_FALSE; + } + + /* + * Note: There should be no returns or jumps between + * this point and the closing of the file below. + */ + + /* Now look through the whole file */ + while(!feof(f)) + { + fgets(line, SIMPLE_MAXLINE, f); + + if(ferror(f)) + { + ha_message(LOG_ERR, "error reading basic password file"); + *code = HA_SERVER_ERROR; + break; + } + + t = strchr(line, ':'); + if(t) + { + /* Split the line */ + *t = 0; + t++; + + /* Check the user */ + if(strcmp(line, user) == 0) + { + /* Now decode the rest of the line and see if it matches up */ + t = ha_bufdechex(buf, t, MD5_LEN); + + if(t != NULL) + { + memcpy(rec->ha1, t, MD5_LEN); + found = 1; + break; + } + + else + { + if(ha_buferr(buf)) + break; + + ha_messagex(LOG_WARNING, "user '%s' found in file, but password not in digest format", user); + } + } + } + + fclose(f); + } + + if(ha_buferr(buf)) + return HA_ERROR; + + return found ? HA_FALSE : HA_OK; +} + +static int validate_user_password(simple_context_t* ctx, ha_buffer_t* buf, + const char* user, const char* clearpw, int* code) +{ + FILE* f; + int found = 0; + char line[SIMPLE_MAXLINE]; + unsigned char ha1[MD5_LEN]; + char* t; + char* t2; + + f = fopen(ctx->filename, "r"); + if(!f) + { + ha_message(LOG_ERR, "can't open file for basic auth: %s", ctx->filename); + *code = HA_SERVER_ERROR; + return HA_FALSE; + } + + digest_makeha1(ha1, user, ctx->realm, clearpw); + + /* + * Note: There should be no returns or jumps between + * this point and the closing of the file below. + */ + + /* Now look through the whole file */ + while(!feof(f)) + { + fgets(line, SIMPLE_MAXLINE, f); + + if(ferror(f)) + { + ha_message(LOG_ERR, "error reading basic password file"); + *code = HA_SERVER_ERROR; + break; + } + + t = strchr(line, ':'); + if(t) + { + /* Split the line */ + *t = 0; + t++; + + /* Check the user */ + if(strcmp(line, user) == 0) + { + /* We can validate against an ha1, so check if it decodes as one */ + t2 = ha_bufdechex(buf, t, MD5_LEN); + + if(t2 && memcmp(ha1, t2, MD5_LEN) == 0) + { + memcpy(ha1, t2, MD5_LEN); + found = 1; + break; + } + + /* Otherwise we try a nice crypt style password */ + else + { + /* Not sure if crypt is thread safe so we lock */ + ha_lock(); + + /* Check the password */ + if(strcmp(crypt(clearpw, t), t) == 0) + found = 1; + + ha_unlock(); + + if(found) + break; + } + + if(ha_buferr(buf)) + break; + } + } + + fclose(f); + } + + if(ha_buferr(buf)) + return HA_ERROR; + + return found ? HA_FALSE : HA_OK; +} + +static int simple_basic_response(simple_context_t* ctx, const char* header, + ha_response_t* resp, ha_buffer_t* buf) +{ + basic_header_t basic; + int ret = HA_FALSE; + int found = 0; + int r; + + ASSERT(buf && header && resp && buf); + + if(basic_parse(header, buf, &basic) == HA_ERROR) + return HA_ERROR; + + /* Past this point we don't return directly */ + + /* Check and see if this connection is in the cache */ + if(have_cached_basic(ctx, basic.key)) + { + found = 1; + ret = HA_OK; + goto finally; + } + + /* If we have a user name and password */ + if(!basic.user || !basic.user[0] || + !basic.password || !basic.password[0]) + goto finally; + + + ret = validate_user_password(ctx, buf, basic.user, basic.password, &(resp->code)); + +finally: + + if(resp->code == HA_SERVER_ACCEPT) + { + resp->detail = basic.user; + + /* We put this connection into the successful connections */ + ret = add_cached_basic(ctx, basic.key); + } + + return ret; +} + +static int simple_digest_challenge(simple_context_t* ctx, ha_response_t* resp, + ha_buffer_t* buf, int stale) +{ + unsigned char nonce[DIGEST_NONCE_LEN]; + const char* header; + + /* Generate an nonce */ + digest_makenonce(nonce, g_simple_secret, NULL); + + /* Now generate a message to send */ + header = digest_challenge(buf, nonce, ctx->realm, ctx->domains, stale); + + if(!header) + return HA_ERROR; + + /* And append it nicely */ + resp->code = HA_SERVER_DECLINE; + ha_addheader(resp, "WWW-Authenticate", header); + + return HA_OK; +} + +static int simple_digest_response(simple_context_t* ctx, const char* header, + const char* method, const char* uri, int timeout, + ha_response_t* resp, ha_buffer_t* buf) +{ + unsigned char nonce[DIGEST_NONCE_LEN]; + digest_header_t dg; + digest_record_t* rec = NULL; + const char* t; + time_t expiry; + int ret = HA_FALSE; + int stale = 0; + int r; + + /* We use this below to send a default response */ + resp->code = -1; + + if(digest_parse(header, buf, &dg, nonce) == HA_ERROR) + return HA_ERROR; + + r = digest_checknonce(nonce, g_simple_secret, &expiry); + if(r != HA_OK) + { + if(r == HA_FALSE) + ha_messagex(LOG_WARNING, "digest response contains invalid nonce"); + + ret = r; + goto finally; + } + + rec = get_cached_digest(ctx, nonce); + + /* Check to see if we're stale */ + if((expiry + timeout) <= time(NULL)) + { + stale = 1; + goto finally; + } + + if(!rec) + { + /* + * If we're valid but don't have a record in the + * cache then complete the record properly. + */ + + rec = digest_makerec(nonce, dg.username); + if(!rec) + { + ret = HA_ERROR; + goto finally; + } + + r = complete_digest_ha1(ctx, rec, buf, dg.username, &(resp->code)); + if(r != HA_OK) + { + ret = r; + goto finally; + } + } + + /* Increment our nonce count */ + rec->nc++; + + ret = digest_check(ctx->realm, method, uri, buf, &dg, rec); + + if(ret == HA_OK) + { + resp->code = HA_SERVER_ACCEPT; + resp->detail = dg.username; + + /* Figure out if we need a new nonce */ + if((expiry + (timeout - (timeout / 8))) < time(NULL)) + { + digest_makenonce(nonce, g_simple_secret, NULL); + stale = 1; + } + + t = digest_respond(buf, &dg, rec, stale ? nonce : NULL); + if(!t) + { + ret = HA_ERROR; + goto finally; + } + + if(t[0]) + ha_addheader(resp, "Authentication-Info", t); + + /* Put the connection into the cache */ + if(save_cached_digest(ctx, rec) == HA_ERROR) + ret = HA_ERROR; + else + rec = NULL; + } + +finally: + + /* If the record wasn't stored away then free it */ + if(rec) + free(rec); + + /* If nobody above responded then challenge the client again */ + if(resp->code == -1) + return simple_digest_challenge(ctx, resp, buf, stale); + + return ret; +} + + +/* ------------------------------------------------------------------------------- + * Handler Functions + */ + +int simple_config(ha_context_t* context, const char* name, const char* value) +{ + simple_context_t* ctx = (simple_context_t*)(context->data); + + if(strcmp(name, "passwordfile") == 0) + { + ctx->filename = value; + return HA_OK; + } + + else if(strcmp(name, "realm") == 0) + { + ctx->realm = value; + return HA_OK; + } + + else if(strcmp(name, "digestdomains") == 0) + { + ctx->domains = value; + return HA_OK; + } + + else if(strcmp(name, "cachemax") == 0) + { + return ha_confint(name, value, 0, 0x7FFFFFFF, &(ctx->cache_max)); + } + + return HA_FALSE; +} + +int simple_init(ha_context_t* context) +{ + /* Global initialization */ + if(!context) + { + return ha_genrandom(g_simple_secret, DIGEST_SECRET_LEN); + } + + /* Context specific initialization */ + else + { + simple_context_t* ctx = (simple_context_t*)(context->data); + int fd; + + /* Make sure there are some types of authentication we can do */ + if(!(context->types & (HA_TYPE_BASIC | HA_TYPE_DIGEST))) + { + ha_messagex(LOG_ERR, "Simple module configured, but does not implement any " + "configured authentication type."); + return HA_ERROR; + } + + + /* Check to make sure the file exists */ + if(!ctx->filename) + { + ha_messagex(LOG_ERR, "Basic configuration incomplete. " + "Must have a PasswordFile configured."); + return HA_ERROR; + } + + fd = open(ctx->filename, O_RDONLY); + if(fd == -1) + { + ha_message(LOG_ERR, "can't open file for simple authentication: %s", ctx->filename); + return HA_ERROR; + } + + close(fd); + + /* The cache for digest records and basic */ + if(!(ctx->cache = hash_create(MD5_LEN, free_hash_object, NULL))) + { + ha_messagex(LOG_CRIT, "out of memory"); + return HA_ERROR; + } + } + + return HA_OK; +} + +void simple_destroy(ha_context_t* context) +{ + /* Per context destroy */ + if(context) + { + simple_context_t* ctx = (simple_context_t*)(context->data); + + /* Note: We don't need to be thread safe here anymore */ + hash_free(ctx->cache); + } +} + +int simple_process(ha_context_t* context, ha_request_t* req, + ha_response_t* resp, ha_buffer_t* buf) +{ + simple_context_t* ctx = (simple_context_t*)(context->data); + const char* header; + int ret = HA_FALSE; + int found = 0; + basic_header_t basic; + + + ha_lock(NULL); + + /* Purge the cache */ + hash_purge(ctx->cache, time(NULL) - context->timeout); + + ha_unlock(NULL); + + + /* We use this below to detect whether to send a default response */ + resp->code = -1; + + + /* Check the headers and see if we got a response thingy */ + if(context->types & HA_TYPE_DIGEST) + { + header = ha_getheader(req, "Authorization", HA_PREFIX_DIGEST); + if(header) + { + ret = simple_digest_response(ctx, header, req->args[AUTH_ARG_METHOD], + req->args[AUTH_ARG_URI], context->timeout, + resp, buf); + if(ret == HA_ERROR) + return ret; + } + } + + /* Or a basic authentication */ + if(!header && context->types & HA_TYPE_BASIC) + { + header = ha_getheader(req, "Authorization", HA_PREFIX_BASIC); + if(header) + { + ret = simple_basic_response(ctx, header, resp, buf); + if(ret == HA_ERROR) + return ret; + } + } + + + /* Send a default response if that's what we need */ + if(resp->code == -1) + { + resp->code = HA_SERVER_DECLINE; + + if(context->types & HA_TYPE_DIGEST) + { + ret = simple_digest_challenge(ctx, resp, buf, 0); + if(ret == HA_ERROR) + return ret; + } + + if(context->types & HA_TYPE_BASIC) + { + ha_bufmcat(buf, "BASIC realm=\"", ctx->realm , "\"", NULL); + + if(ha_buferr(buf)) + return HA_ERROR; + + ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf)); + } + } + + return ret; +} + + +/* ------------------------------------------------------------------------------- + * Handler Definition + */ + +ha_handler_t simple_handler = +{ + "SIMPLE", /* The type */ + simple_init, /* Initialization function */ + simple_destroy, /* Uninitialization routine */ + simple_config, /* Config routine */ + simple_process, /* Processing routine */ + &simple_defaults, /* A default context */ + sizeof(simple_context_t) /* Size of the context */ +}; + diff --git a/daemon/usuals.h b/daemon/usuals.h index 22dcb3a..e14ecf5 100644 --- a/daemon/usuals.h +++ b/daemon/usuals.h @@ -28,4 +28,11 @@ #define countof(x) (sizeof(x) / sizeof(x[0])) +#ifdef _DEBUG + #include "assert.h" + #define ASSERT assert +#else + #define ASSERT +#endif + #endif /* __USUALS_H__ */ -- cgit v1.2.3