summaryrefslogtreecommitdiff
path: root/daemon
diff options
context:
space:
mode:
authorStef Walter <stef@memberwebs.com>2004-08-12 00:50:29 +0000
committerStef Walter <stef@memberwebs.com>2004-08-12 00:50:29 +0000
commit86e45cfbd0655193e363be6daadbfd5434566a03 (patch)
tree5391fdcbfd6b3e360d425f0bd4b260d073901a21 /daemon
parentc44a74e52a8b227857f28289a91d126b7edc959c (diff)
- Added postgresql database support
- Lots of changes to properly abstract bd handlers - Handle multiple passwords and ha1s properly.
Diffstat (limited to 'daemon')
-rw-r--r--daemon/Makefile.am8
-rw-r--r--daemon/bd.c238
-rw-r--r--daemon/bd.h23
-rw-r--r--daemon/digest.c202
-rw-r--r--daemon/digest.h42
-rw-r--r--daemon/httpauthd.c4
-rw-r--r--daemon/ldap.c176
-rw-r--r--daemon/misc.c6
-rw-r--r--daemon/pgsql.c749
-rw-r--r--daemon/simple.c200
10 files changed, 1285 insertions, 363 deletions
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 7eb7087..db83f8b 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -10,6 +10,8 @@ httpauthd_SOURCES = httpauthd.c httpauthd.h usuals.h bd.h bd.c misc.c basic.h ba
LDAP_SOURCES = ldap.c
+PGSQL_SOURCES = pgsql.c
+
NTLM_SOURCES = ntlm.c ntlmssp.h ntlmssp.c \
smblib/smblib.c smblib/smblib-util.c smblib/file.c smblib/smb-errors.c \
smblib/exper.c smblib/smblib-api.c smblib/smblib.h smblib/std-defines.h \
@@ -26,8 +28,12 @@ if WITH_NTLM
httpauthd_SOURCES += $(NTLM_SOURCES)
endif
+if WITH_PGSQL
+httpauthd_SOURCES += $(PGSQL_SOURCES)
+endif
+
httpauthd_CFLAGS = -D_THREAD_SAFE -pthread -DLinux \
-I${top_srcdir}/common/ -I${top_srcdir}
httpauthd_LDFLAGS = -pthread
-EXTRA_DIST = $(LDAP_SOURCES)
+EXTRA_DIST = $(LDAP_SOURCES) $(NTLM_SOURCES) $(PGSQL_SOURCES)
diff --git a/daemon/bd.c b/daemon/bd.c
index 6cd7b6b..5519cfa 100644
--- a/daemon/bd.c
+++ b/daemon/bd.c
@@ -12,12 +12,23 @@
static unsigned char g_digest_secret[DIGEST_SECRET_LEN];
+
+
/* -------------------------------------------------------------------------------
* Defaults and Constants
*/
#define BASIC_ESTABLISHED (void*)1
+/* Kept by the us 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;
/* -------------------------------------------------------------------------------
* Internal Functions
@@ -127,6 +138,25 @@ static int add_cached_basic(bd_context_t* ctx, ha_context_t* c,
return HA_OK;
}
+digest_record_t* make_digest_rec(unsigned char* nonce, const char* user)
+{
+ digest_record_t* rec = (digest_record_t*)malloc(sizeof(*rec));
+
+ ASSERT(nonce && user);
+
+ if(!rec)
+ {
+ ha_messagex(NULL, 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;
+}
+
static int do_basic_response(ha_request_t* rq, bd_context_t* ctx, const char* header)
{
basic_header_t basic;
@@ -217,7 +247,7 @@ static int do_digest_challenge(ha_request_t* rq, bd_context_t* ctx, int stale)
static int do_digest_response(ha_request_t* rq, bd_context_t* ctx, const char* header)
{
unsigned char nonce[DIGEST_NONCE_LEN];
- digest_header_t dg;
+ digest_context_t dg;
digest_record_t* rec = NULL;
const char* t;
time_t expiry;
@@ -230,15 +260,20 @@ static int do_digest_response(ha_request_t* rq, bd_context_t* ctx, const char* h
/* We use this below to send a default response */
rq->resp_code = -1;
- if((r = digest_parse(header, rq->buf, &dg, nonce)) < 0)
+ if((r = digest_parse(header, rq->buf, &(dg.client))) < 0)
return r;
+ if(!dg.client.username)
+ {
+ ha_messagex(rq, LOG_WARNING, "digest response contains no user name");
+ goto finally;
+ }
+
#ifdef _DEBUG
if(rq->context->digest_debugnonce)
{
- if(dg.nonce && strcmp(dg.nonce, rq->context->digest_debugnonce) != 0)
+ if(dg.client.nonce && strcmp(dg.client.nonce, rq->context->digest_debugnonce) != 0)
{
- ret = HA_FALSE;
ha_messagex(rq, LOG_WARNING, "digest response contains invalid nonce");
goto finally;
}
@@ -252,6 +287,18 @@ static int do_digest_response(ha_request_t* rq, bd_context_t* ctx, const char* h
else
#endif
{
+ /* Parse out the nonce from that header */
+ memset(nonce, 0, DIGEST_NONCE_LEN);
+
+ if(dg.client.nonce)
+ {
+ size_t len = DIGEST_NONCE_LEN;
+ void* d = ha_bufdechex(rq->buf, dg.client.nonce, &len);
+
+ if(d && len == DIGEST_NONCE_LEN)
+ memcpy(nonce, d, DIGEST_NONCE_LEN);
+ }
+
r = digest_checknonce(nonce, g_digest_secret, &expiry);
if(r != HA_OK)
{
@@ -260,95 +307,142 @@ static int do_digest_response(ha_request_t* rq, bd_context_t* ctx, const char* h
goto finally;
}
+
+ /* Check to see if we're stale */
+ if((expiry + rq->context->cache_timeout) <= time(NULL))
+ {
+ ha_messagex(rq, LOG_INFO, "nonce expired, sending stale challenge: %s",
+ dg.client.username);
+
+ stale = 1;
+ goto finally;
+ }
}
+ /* See if we have this one from before. */
rec = get_cached_digest(ctx, rq->context, nonce);
- /* Check to see if we're stale */
- if((expiry + rq->context->cache_timeout) <= time(NULL))
+ /*
+ * Fill in the required fields.
+ */
+ dg.server_nc = rec ? ++(rec->nc) : 0; /* Note bumping up nc */
+ dg.server_uri = rq->req_args[AUTH_ARG_URI];
+ dg.server_method = rq->req_args[AUTH_ARG_METHOD];
+
+ /* Check the majority of the fields */
+ ret = digest_pre_check(&dg, rq->context, rq->buf);
+
+ if(ret != HA_OK)
{
- ha_messagex(rq, LOG_INFO, "nonce expired, sending stale challenge: %s",
- dg.username);
+ if(ret == HA_BADREQ)
+ {
+ ret = HA_FALSE;
+ rq->resp_code = HA_SERVER_BADREQ;
+ }
- stale = 1;
goto finally;
}
+ /*
+ * If this is the first instance then we pass off to our derived
+ * handler for validation and completion of the ha1. This completes
+ * the authentication, and leaves us the ha1 caching.
+ */
if(!rec)
{
- ha_messagex(rq, LOG_INFO, "no record in cache, creating one: %s", dg.username);
+ ha_messagex(rq, LOG_INFO, "no record in cache, creating one: %s", dg.client.username);
/*
* If we're valid but don't have a record in the
* cache then complete the record properly.
*/
- rec = digest_makerec(nonce, dg.username);
+ rec = make_digest_rec(nonce, dg.client.username);
if(!rec)
{
ret = HA_CRITERROR;
goto finally;
}
- ASSERT(ctx->f_complete_digest);
- r = ctx->f_complete_digest(rq, dg.username, rec->ha1);
+
+ ASSERT(ctx->f_validate_digest);
+ r = ctx->f_validate_digest(rq, dg.client.username, &dg);
if(r != HA_OK)
{
ret = r;
goto finally;
}
+
+ /* Save away pertinent information when successful*/
+ memcpy(rec->ha1, dg.ha1, MD5_LEN);
}
/* We had a record so ... */
else
{
+ /* Bump up the ncount */
rec->nc++;
- }
- ret = digest_check(&dg, rec, rq->context, rq->buf,
- rq->req_args[AUTH_ARG_METHOD], rq->req_args[AUTH_ARG_URI]);
+ /* Check the user name */
+ if(md5_strcmp(rec->userhash, dg.client.username) != 0)
+ {
+ ha_messagex(NULL, LOG_ERR, "digest response contains invalid username");
+ ret = HA_FALSE;
+ goto finally;
+ }
- if(ret == HA_BADREQ)
- {
- ret = HA_FALSE;
- rq->resp_code = HA_SERVER_BADREQ;
- }
+ /* And do the validation ourselves */
+ memcpy(dg.ha1, rec->ha1, MD5_LEN);
- else if(ret == HA_OK)
- {
- rq->resp_code = HA_SERVER_OK;
- rq->resp_detail = dg.username;
+ ret = digest_complete_check(&dg, rq->buf);
- /* Figure out if we need a new nonce */
- if((expiry + (rq->context->cache_timeout -
- (rq->context->cache_timeout / 8))) < time(NULL))
+ if(ret != HA_OK)
{
- ha_messagex(rq, LOG_INFO, "nonce almost expired, creating new one: %s",
- dg.username);
+ if(ret == HA_BADREQ)
+ {
+ ret = HA_FALSE;
+ rq->resp_code = HA_SERVER_BADREQ;
+ }
- digest_makenonce(nonce, g_digest_secret, NULL);
- stale = 1;
- }
+ if(ret == HA_FALSE)
+ ha_messagex(NULL, LOG_WARNING, "digest re-authentication failed for user: %s",
+ dg.client.username);
- t = digest_respond(rq->buf, &dg, rec, stale ? nonce : NULL);
- if(!t)
- {
- ret = HA_CRITERROR;
goto finally;
}
+ }
- if(t[0])
- ha_addheader(rq, "Authentication-Info", t);
- ha_messagex(rq, LOG_NOTICE, "validated digest user: %s", dg.username);
+ rq->resp_code = HA_SERVER_OK;
+ rq->resp_detail = dg.client.username;
- /* Put the connection into the cache */
- if((r = save_cached_digest(ctx, rq->context, rec)) < 0)
- ret = r;
+ /* Figure out if we need a new nonce */
+ if((expiry + (rq->context->cache_timeout -
+ (rq->context->cache_timeout / 8))) < time(NULL))
+ {
+ ha_messagex(rq, LOG_INFO, "nonce almost expired, creating new one: %s",
+ dg.client.username);
- rec = NULL;
+ digest_makenonce(nonce, g_digest_secret, NULL);
+ stale = 1;
}
+ t = digest_respond(&dg, rq->buf, stale ? nonce : NULL);
+ if(!t)
+ {
+ ret = HA_CRITERROR;
+ goto finally;
+ }
+
+ if(t[0])
+ ha_addheader(rq, "Authentication-Info", t);
+
+ ha_messagex(rq, LOG_NOTICE, "validated digest user: %s", dg.client.username);
+
+ /* Put the connection into the cache */
+ if((ret = save_cached_digest(ctx, rq->context, rec)) == HA_OK)
+ rec = NULL;
+
finally:
/* If the record wasn't stored away then free it */
@@ -363,6 +457,62 @@ finally:
}
+
+const char* bd_substitute(const ha_request_t* rq, const char* user, const char* str)
+{
+ bd_context_t* ctx = (bd_context_t*)rq->context->ctx_data;
+ const char* t;
+
+ ASSERT(rq && user && str);
+ ASSERT(ctx->f_escape_value);
+
+ /* TODO: We need to be escaping the user and realm properly */
+ /* This starts a new block to join */
+ ha_bufcpy(rq->buf, "");
+
+ while(str[0])
+ {
+ t = strchr(str, '%');
+ if(!t)
+ {
+ ha_bufjoin(rq->buf);
+ ha_bufcpy(rq->buf, str);
+ break;
+ }
+
+ ha_bufjoin(rq->buf);
+ ha_bufncpy(rq->buf, str, t - str);
+
+ t++;
+
+ switch(t[0])
+ {
+ case 'u':
+ ha_bufjoin(rq->buf);
+ (ctx->f_escape_value)(rq, rq->buf, user);
+ t++;
+ break;
+
+ case 'r':
+ ha_bufjoin(rq->buf);
+ (ctx->f_escape_value)(rq, rq->buf, rq->context->realm);
+ t++;
+ break;
+
+ case '%':
+ ha_bufjoin(rq->buf);
+ ha_bufcpy(rq->buf, "%");
+ t++;
+ break;
+ };
+
+ str = t;
+ }
+
+ return ha_bufdata(rq->buf);
+}
+
+
/* -------------------------------------------------------------------------------
* Handler Functions
*/
diff --git a/daemon/bd.h b/daemon/bd.h
index 272e09a..a59b1e1 100644
--- a/daemon/bd.h
+++ b/daemon/bd.h
@@ -3,6 +3,8 @@
#define BD_H
#include "hash.h"
+#include "digest.h"
+#include "httpauthd.h"
/* ----------------------------------------------------------------------------------
* Callbacks
@@ -17,8 +19,8 @@
* HA_OK: completed successfully
* HA_FAILED: error retrieving hash (should have logged error)
*/
-typedef int (*bd_complete_digest)(ha_request_t* rq,
- const char* user, unsigned char* ha1);
+typedef int (*bd_validate_digest)(ha_request_t* rq,
+ const char* user, digest_context_t* dg);
/*
* A callback for validating a given user's password.
@@ -31,6 +33,12 @@ typedef int (*bd_complete_digest)(ha_request_t* rq,
typedef int (*bd_validate_basic)(ha_request_t* rq,
const char* user, const char* password);
+/*
+ * Escapes a value for sending to 'server'
+ */
+typedef void (*bd_escape_value)(const ha_request_t* rq, ha_buffer_t* buf,
+ const char* value);
+
/* ----------------------------------------------------------------------------------
* Base Context
*/
@@ -38,12 +46,13 @@ typedef struct bd_context
{
hsh_t* cache; /* Some cached records or basic */
- bd_complete_digest f_complete_digest;
+ bd_validate_digest f_validate_digest;
bd_validate_basic f_validate_basic;
+ bd_escape_value f_escape_value;
}
bd_context_t;
-#define BD_CALLBACKS(a, b) { NULL, (a), (b) }
+#define BD_CALLBACKS(a, b, c) { NULL, (a), (b), (c) }
#define BD_DEFAULTS { NULL, NULL, NULL }
/* ----------------------------------------------------------------------------------
@@ -68,4 +77,10 @@ void bd_destroy(ha_context_t* context);
*/
int bd_process(ha_request_t* rq);
+/* ----------------------------------------------------------------------------------
+ * Other helper functionts
+ */
+
+const char* bd_substitute(const ha_request_t* rq, const char* user, const char* str);
+
#endif /* BD_H */
diff --git a/daemon/digest.c b/daemon/digest.c
index 099bf31..bce588e 100644
--- a/daemon/digest.c
+++ b/daemon/digest.c
@@ -69,25 +69,6 @@ int digest_checknonce(unsigned char* nonce, unsigned char* secret, time_t* tm)
return HA_FALSE;
}
-digest_record_t* digest_makerec(unsigned char* nonce, const char* user)
-{
- digest_record_t* rec = (digest_record_t*)malloc(sizeof(*rec));
-
- ASSERT(nonce && user);
-
- if(!rec)
- {
- ha_messagex(NULL, 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, const char* nonce_str,
const char* realm, const char* domains, int stale)
{
@@ -127,16 +108,16 @@ const char* digest_challenge(ha_buffer_t* buf, const char* nonce_str,
* limitations under the License.
*/
-int digest_parse(char* header, ha_buffer_t* buf, digest_header_t* rec,
- unsigned char* nonce)
+int digest_parse(const char* head, ha_buffer_t* buf, digest_header_t* rec)
{
char next;
char* key;
char* value;
+ char* header;
- ASSERT(header && buf && rec);
+ ASSERT(head && buf && rec);
- header = ha_bufcpy(buf, header);
+ header = ha_bufcpy(buf, head);
if(!header)
return HA_CRITERROR;
@@ -230,93 +211,78 @@ int digest_parse(char* header, ha_buffer_t* buf, digest_header_t* rec,
}
}
- if(nonce)
- {
- memset(nonce, 0, DIGEST_NONCE_LEN);
+ return HA_OK;
+}
- if(rec->nonce)
- {
- size_t len = DIGEST_NONCE_LEN;
- void* d = ha_bufdechex(buf, rec->nonce, &len);
+int digest_check(digest_context_t* dg, const ha_context_t* opts, ha_buffer_t* buf)
+{
+ int r;
- if(d && len == DIGEST_NONCE_LEN)
- memcpy(nonce, d, DIGEST_NONCE_LEN);
- }
- }
+ r = digest_pre_check(dg, opts, buf);
+ if(r == HA_OK)
+ r = digest_complete_check(dg, buf);
- return HA_OK;
+ return r;
}
-int digest_check(digest_header_t* dg, digest_record_t* rec, const ha_context_t* ctx,
- ha_buffer_t* buf, const char* method, const char* uri)
+int digest_pre_check(digest_context_t* dg, const ha_context_t* opts, ha_buffer_t* buf)
{
- unsigned char hash[MD5_LEN];
- md5_ctx_t md5;
- const char* digest;
- const char* t;
-
- ASSERT(ctx && method && buf && dg && rec);
+ ASSERT(buf && buf && dg);
/* TODO: This stuff should probably go into bd.c */
/* Check for digest */
- if(!dg->digest || !dg->digest[0])
+ if(!dg->client.digest || !dg->client.digest[0])
{
ha_messagex(NULL, LOG_WARNING, "digest response missing digest");
return HA_BADREQ;
}
/* Username */
- if(!dg->username || !dg->username[0])
+ if(!dg->client.username || !dg->client.username[0])
{
ha_messagex(NULL, LOG_WARNING, "digest response missing username");
return HA_BADREQ;
}
- if(md5_strcmp(rec->userhash, dg->username) != 0)
- {
- ha_messagex(NULL, LOG_ERR, "digest response contains invalid username");
- return HA_FALSE;
- }
-
/* The realm */
- if(!dg->realm)
+ if(!dg->client.realm)
{
ha_messagex(NULL, LOG_WARNING, "digest response contains missing realm");
return HA_BADREQ;
}
- if(strcmp(dg->realm, ctx->realm) != 0)
+ if(strcmp(dg->client.realm, opts->realm) != 0)
{
- ha_messagex(NULL, LOG_ERR, "digest response contains invalid realm: %s", dg->realm);
+ ha_messagex(NULL, LOG_ERR, "digest response contains invalid realm: %s", dg->client.realm);
return HA_FALSE;
}
/* Components in the new RFC */
- if(dg->qop)
+ if(dg->client.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)
+ if(strcmp(dg->client.qop, "auth") != 0)
{
ha_messagex(NULL, LOG_WARNING, "digest response contains unknown or unsupported qop: '%s'",
- dg->qop ? dg->qop : "");
+ dg->client.qop ? dg->client.qop : "");
return HA_BADREQ;
}
/* The cnonce */
- if(!dg->cnonce || !dg->cnonce[0])
+ if(!dg->client.cnonce || !dg->client.cnonce[0])
{
ha_messagex(NULL, LOG_WARNING, "digest response is missing cnonce value");
return HA_BADREQ;
}
- if(!ctx->digest_ignorenc)
+ if(!opts->digest_ignorenc)
{
/* The nonce count */
- if(!dg->nc || !dg->nc[0])
+ if(!dg->client.nc || !dg->client.nc[0])
{
ha_messagex(NULL, LOG_WARNING, "digest response is missing nc value");
return HA_BADREQ;
@@ -326,22 +292,22 @@ int digest_check(digest_header_t* dg, digest_record_t* rec, const ha_context_t*
else
{
char* e;
- long nc = strtol(dg->nc, &e, 16);
+ long nc = strtol(dg->client.nc, &e, 16);
if(*e)
{
- ha_messagex(NULL, LOG_ERR, "digest response has invalid nc value: %s", dg->nc);
+ ha_messagex(NULL, LOG_ERR, "digest response has invalid nc value: %s", dg->client.nc);
return HA_BADREQ;
}
/* If we didn't a nc then save it away */
- if(!*e && rec->nc == 0)
- rec->nc = nc;
+ if(!*e && dg->server_nc == 0)
+ dg->server_nc = nc;
- if(*e || nc != rec->nc)
+ if(*e || nc != dg->server_nc)
{
ha_messagex(NULL, LOG_WARNING, "digest response has wrong nc value. "
- "possible replay attack: %s", dg->nc);
+ "possible replay attack: %s", dg->client.nc);
return HA_FALSE;
}
}
@@ -349,47 +315,49 @@ int digest_check(digest_header_t* dg, digest_record_t* rec, const ha_context_t*
}
/* The algorithm */
- if(dg->algorithm && strcasecmp(dg->algorithm, "MD5") != 0)
+ if(dg->client.algorithm && strcasecmp(dg->client.algorithm, "MD5") != 0)
{
ha_messagex(NULL, LOG_WARNING, "digest response contains unknown or unsupported algorithm: '%s'",
- dg->algorithm ? dg->algorithm : "");
+ dg->client.algorithm ? dg->client.algorithm : "");
return HA_BADREQ;
}
/* Request URI */
- if(!dg->uri)
+ if(!dg->client.uri)
{
ha_messagex(NULL, LOG_WARNING, "digest response is missing uri");
return HA_BADREQ;
}
- if(!ctx->digest_ignoreuri && strcmp(dg->uri, uri) != 0)
+ if(!opts->digest_ignoreuri && strcmp(dg->client.uri, dg->server_uri) != 0)
{
ha_uri_t d_uri;
ha_uri_t s_uri;
- if(ha_uriparse(buf, dg->uri, &d_uri) < 0)
+ if(ha_uriparse(buf, dg->client.uri, &d_uri) < 0)
{
if(ha_buferr(buf))
return HA_CRITERROR;
- ha_messagex(NULL, LOG_WARNING, "digest response constains invalid uri: %s", dg->uri);
+ ha_messagex(NULL, LOG_WARNING, "digest response constains invalid uri: %s", dg->client.uri);
return HA_BADREQ;
}
- if(ha_uriparse(buf, uri, &s_uri) < 0)
+ ASSERT(dg->server_uri);
+
+ if(ha_uriparse(buf, dg->server_uri, &s_uri) < 0)
{
if(ha_buferr(buf))
return HA_CRITERROR;
- ha_messagex(NULL, LOG_ERR, "server sent us an invalid uri");
+ ha_messagex(NULL, LOG_ERR, "server sent us an invalid uri: %s", dg->server_uri);
return HA_BADREQ;
}
if(ha_uricmp(&d_uri, &s_uri) != 0)
{
ha_messagex(NULL, LOG_ERR, "digest response contains wrong uri: %s "
- "(should be %s)", dg->uri, uri);
+ "(should be %s)", dg->client.uri, dg->server_uri);
return HA_FALSE;
}
}
@@ -402,21 +370,36 @@ int digest_check(digest_header_t* dg, digest_record_t* rec, const ha_context_t*
* if it's used there.
*/
+ return HA_OK;
+}
+
+
+int digest_complete_check(digest_context_t* dg, ha_buffer_t* buf)
+{
+ unsigned char hash[MD5_LEN];
+ md5_ctx_t md5;
+ const char* t;
+ const char* digest;
+
+ ASSERT(dg && buf);
+
/*
* Now we validate the digest response
*/
/* Encode ha1 */
- t = ha_bufenchex(buf, rec->ha1, MD5_LEN);
+ t = ha_bufenchex(buf, dg->ha1, MD5_LEN);
if(t == NULL)
return HA_CRITERROR;
+ ASSERT(dg->server_method);
+
/* Encode ha2 */
md5_init(&md5);
- md5_update(&md5, method, strlen(method));
+ md5_update(&md5, dg->server_method, strlen(dg->server_method));
md5_update(&md5, ":", 1);
- md5_update(&md5, dg->uri, strlen(dg->uri));
+ md5_update(&md5, dg->client.uri, strlen(dg->client.uri));
md5_final(hash, &md5);
ha_bufenchex(buf, hash, MD5_LEN);
@@ -424,14 +407,15 @@ int digest_check(digest_header_t* dg, digest_record_t* rec, const ha_context_t*
if(!ha_bufdata(buf))
return HA_CRITERROR;
+ ASSERT(dg->client.nonce);
/* Old style digest (RFC 2069) */
- if(!dg->qop)
+ if(!dg->client.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, dg->client.nonce, strlen(dg->client.nonce)); /* nonce */
md5_update(&md5, ":", 1);
md5_update(&md5, ha_bufdata(buf), MD5_LEN * 2); /* ha1 */
md5_final(hash, &md5);
@@ -440,16 +424,20 @@ int digest_check(digest_header_t* dg, digest_record_t* rec, const ha_context_t*
/* New style 'auth' digest (RFC 2617) */
else
{
+ ASSERT(dg->client.nc);
+ ASSERT(dg->client.cnonce);
+ ASSERT(dg->client.qop);
+
md5_init(&md5);
- md5_update(&md5, t, MD5_LEN * 2); /* ha1 */
+ md5_update(&md5, t, MD5_LEN * 2); /* ha1 */
md5_update(&md5, ":", 1);
- md5_update(&md5, dg->nonce, strlen(dg->nonce)); /* nonce */
+ md5_update(&md5, dg->client.nonce, strlen(dg->client.nonce)); /* nonce */
md5_update(&md5, ":", 1);
- md5_update(&md5, dg->nc, strlen(dg->nc)); /* nc */
+ md5_update(&md5, dg->client.nc, strlen(dg->client.nc)); /* nc */
md5_update(&md5, ":", 1);
- md5_update(&md5, dg->cnonce, strlen(dg->cnonce)); /* cnonce */
+ md5_update(&md5, dg->client.cnonce, strlen(dg->client.cnonce)); /* cnonce */
md5_update(&md5, ":", 1);
- md5_update(&md5, dg->qop, strlen(dg->qop)); /* qop */
+ md5_update(&md5, dg->client.qop, strlen(dg->client.qop)); /* qop */
md5_update(&md5, ":", 1);
md5_update(&md5, ha_bufdata(buf), MD5_LEN * 2); /* ha2 */
md5_final(hash, &md5);
@@ -461,24 +449,23 @@ int digest_check(digest_header_t* dg, digest_record_t* rec, const ha_context_t*
if(digest == NULL)
return HA_CRITERROR;
- if(strcasecmp(dg->digest, digest) != 0)
- {
- ha_messagex(NULL, LOG_WARNING, "digest authentication failed for user: %s", dg->username);
+ ASSERT(dg->client.digest);
+
+ if(strcasecmp(dg->client.digest, digest) != 0)
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)
+const char* digest_respond(digest_context_t* dg, ha_buffer_t* buf,
+ unsigned char* next)
{
unsigned char hash[MD5_LEN];
md5_ctx_t md5;
const char* nextnonce = NULL;
const char* t;
- ASSERT(buf && dg && rec);
+ ASSERT(buf && dg);
/* This makes a new buffer */
ha_bufcpy(buf, "");
@@ -492,7 +479,7 @@ const char* digest_respond(ha_buffer_t* buf, digest_header_t* dg,
}
/* For older clients RFC 2069 */
- if(!dg->qop)
+ if(!dg->client.qop)
{
if(nextnonce)
ha_bufmcat(buf, "nextnonce=\"", nextnonce, "\"", NULL);
@@ -503,15 +490,17 @@ const char* digest_respond(ha_buffer_t* buf, digest_header_t* dg,
/* Otherwise we do the whole song and dance */
/* Encode ha1 */
- t = ha_bufenchex(buf, rec->ha1, MD5_LEN);
+ t = ha_bufenchex(buf, dg->ha1, MD5_LEN);
if(t == NULL)
return NULL;
+ ASSERT(dg->client.uri);
+
/* Encode ha2 */
md5_init(&md5);
md5_update(&md5, ":", 1);
- md5_update(&md5, dg->uri, strlen(dg->uri));
+ md5_update(&md5, dg->client.uri, strlen(dg->client.uri));
md5_final(hash, &md5);
ha_bufenchex(buf, hash, MD5_LEN);
@@ -519,19 +508,24 @@ const char* digest_respond(ha_buffer_t* buf, digest_header_t* dg,
if(!ha_bufdata(buf))
return NULL;
+ ASSERT(dg->client.nonce);
+ ASSERT(dg->client.nc);
+ ASSERT(dg->client.cnonce);
+ ASSERT(dg->client.qop);
+
/* New style 'auth' digest (RFC 2617) */
md5_init(&md5);
- md5_update(&md5, t, MD5_LEN * 2); /* ha1 */
+ md5_update(&md5, t, MD5_LEN * 2); /* ha1 */
md5_update(&md5, ":", 1);
- md5_update(&md5, dg->nonce, strlen(dg->nonce)); /* nonce */
+ md5_update(&md5, dg->client.nonce, strlen(dg->client.nonce)); /* nonce */
md5_update(&md5, ":", 1);
- md5_update(&md5, dg->nc, strlen(dg->nc)); /* nc */
+ md5_update(&md5, dg->client.nc, strlen(dg->client.nc)); /* nc */
md5_update(&md5, ":", 1);
- md5_update(&md5, dg->cnonce, strlen(dg->cnonce)); /* cnonce */
+ md5_update(&md5, dg->client.cnonce, strlen(dg->client.cnonce)); /* cnonce */
md5_update(&md5, ":", 1);
- md5_update(&md5, dg->qop, strlen(dg->qop)); /* qop */
+ md5_update(&md5, dg->client.qop, strlen(dg->client.qop)); /* qop */
md5_update(&md5, ":", 1);
- md5_update(&md5, ha_bufdata(buf), MD5_LEN * 2); /* ha2 */
+ md5_update(&md5, ha_bufdata(buf), MD5_LEN * 2); /* ha2 */
md5_final(hash, &md5);
/* Encode the digest */
@@ -541,9 +535,9 @@ const char* digest_respond(ha_buffer_t* buf, digest_header_t* dg,
return NULL;
ha_bufmcat(buf, "rspauth=\"", t, "\"",
- ", qop=", dg->qop,
- ", nc=", dg->nc,
- ", cnonce=\"", dg->cnonce, "\"", NULL);
+ ", qop=", dg->client.qop,
+ ", nc=", dg->client.nc,
+ ", cnonce=\"", dg->client.cnonce, "\"", NULL);
if(nextnonce)
{
diff --git a/daemon/digest.h b/daemon/digest.h
index f763133..a889024 100644
--- a/daemon/digest.h
+++ b/daemon/digest.h
@@ -25,33 +25,37 @@ typedef struct digest_header
}
digest_header_t;
-/* Kept by the server for validating the client */
-typedef struct digest_record
+typedef struct digest_context
{
- 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);
+ digest_header_t client;
-int ha_digestparse(char* header, ha_buffer_t* buf, digest_header_t* rec,
- unsigned char* nonce);
+ long server_nc;
+ const char* server_uri;
+ const char* server_method;
-int ha_digestnonce(time_t* tm, unsigned char* nonce);
+ unsigned char ha1[MD5_LEN];
+}
+digest_context_t;
-int digest_check(digest_header_t* dg, digest_record_t* rec, const ha_context_t* opts,
- ha_buffer_t* buf, const char* method, const char* uri);
+void digest_makeha1(unsigned char* digest, const char* user,
+ const char* realm, const char* password);
-const char* digest_respond(ha_buffer_t* buf, digest_header_t* dg,
- digest_record_t* rec, unsigned char* next);
+int digest_parse(const char* header, ha_buffer_t* buf, digest_header_t* dg);
const char* digest_challenge(ha_buffer_t* buf, const char* nonce_str,
const char* realm, const char* domains, int stale);
-void digest_makeha1(unsigned char* digest, const char* user,
- const char* realm, const char* password);
+/*
+ * Validate digest headers once they've been parsed. Note that it's up
+ * to the caller to validate the 'username' and 'nonce' fields.
+ */
+int digest_check(digest_context_t* dg, const ha_context_t* opts, ha_buffer_t* buf);
+int digest_pre_check(digest_context_t* dg, const ha_context_t* opts, ha_buffer_t* buf);
+
+/* This assumes a digest_context that's been prechecked successfully */
+int digest_complete_check(digest_context_t* dg, ha_buffer_t* buf);
+
+/* This assumes a digest_context that's been checked and validated successfully */
+const char* digest_respond(digest_context_t* dg, ha_buffer_t* buf, unsigned char* next);
#endif /* __DIGEST_H__ */
diff --git a/daemon/httpauthd.c b/daemon/httpauthd.c
index f995d6d..bae758a 100644
--- a/daemon/httpauthd.c
+++ b/daemon/httpauthd.c
@@ -32,6 +32,7 @@
extern ha_handler_t simple_handler;
extern ha_handler_t ldap_handler;
extern ha_handler_t ntlm_handler;
+extern ha_handler_t pgsql_handler;
/* This is the list of all available handlers */
ha_handler_t* g_handlerlist[] =
@@ -42,6 +43,9 @@ ha_handler_t* g_handlerlist[] =
#if WITH_NTLM
&ntlm_handler,
#endif
+#if WITH_PGSQL
+ &pgsql_handler,
+#endif
&simple_handler,
};
diff --git a/daemon/ldap.c b/daemon/ldap.c
index 719ac0d..1df7651 100644
--- a/daemon/ldap.c
+++ b/daemon/ldap.c
@@ -72,13 +72,15 @@ typedef struct ldap_context
ldap_context_t;
/* Forward declarations for callbacks */
-static int complete_digest(ha_request_t* rq, const char* user, unsigned char* ha1);
+static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg);
static int validate_basic(ha_request_t* rq, const char* user, const char* password);
+static void escape_ldap(const ha_request_t* rq, ha_buffer_t* buf, const char* value);
/* The defaults for the context */
static const ldap_context_t ldap_defaults =
{
- BD_CALLBACKS(complete_digest, validate_basic),
+ BD_CALLBACKS(validate_digest,
+ validate_basic, escape_ldap),
NULL, /* servers */
NULL, /* filter */
NULL, /* base */
@@ -122,7 +124,7 @@ static int report_ldap(const ha_request_t* rq, const char* msg, int code)
#define LDAP_NO_ESCAPE "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_"
#define LDAP_HEX "0123456789abcdef"
-static const char* escape_ldap(ha_buffer_t* buf, const char* str)
+static void escape_ldap(const ha_request_t* rq, ha_buffer_t* buf, const char* str)
{
const char* t = str;
size_t pos;
@@ -155,62 +157,8 @@ static const char* escape_ldap(ha_buffer_t* buf, const char* str)
t++;
}
}
-
- return ha_bufdata(buf);
}
-static const char* substitute_params(const ha_request_t* rq, ldap_context_t* ctx,
- const char* user, const char* str)
-{
- const char* t;
-
- ASSERT(ctx && rq && user && str);
-
- /* TODO: We need to be escaping the user and realm properly */
- /* This starts a new block to join */
- ha_bufcpy(rq->buf, "");
-
- while(str[0])
- {
- t = strchr(str, '%');
- if(!t)
- {
- ha_bufjoin(rq->buf);
- ha_bufcpy(rq->buf, str);
- break;
- }
-
- ha_bufjoin(rq->buf);
- ha_bufncpy(rq->buf, str, t - str);
-
- t++;
-
- switch(t[0])
- {
- case 'u':
- ha_bufjoin(rq->buf);
- escape_ldap(rq->buf, user);
- t++;
- break;
-
- case 'r':
- ha_bufjoin(rq->buf);
- escape_ldap(rq->buf, rq->context->realm);
- t++;
- break;
-
- case '%':
- ha_bufjoin(rq->buf);
- ha_bufcpy(rq->buf, "%");
- t++;
- break;
- };
-
- str = t;
- }
-
- return ha_bufdata(rq->buf);
-}
static const char* make_password_md5(ha_buffer_t* buf, const char* clearpw)
{
@@ -280,16 +228,14 @@ static int parse_ldap_password(const char** password)
return LDAP_PW_UNKNOWN;
}
-static const char* find_cleartext_password(ha_buffer_t* buf, const char** pws)
+static const char** find_cleartext_password(ha_buffer_t* buf, const char** pws)
{
ASSERT(buf);
for(; pws && *pws; pws++)
{
- const char* pw = *pws;
-
- if(parse_ldap_password(&pw) == LDAP_PW_CLEAR)
- return pw;
+ if(parse_ldap_password(pws) == LDAP_PW_CLEAR)
+ return pws;
}
return NULL;
@@ -478,12 +424,14 @@ static LDAP* get_ldap_connection(const ha_request_t* rq, ldap_context_t* ctx)
ASSERT(ctx);
+ /* TODO: What about multiple threads here? */
+
for(i = 0; i < ctx->ldap_max; i++)
{
/* An open connection in the pool */
if(ctx->pool[i])
{
- ha_messagex(rq, LOG_DEBUG, "using cached connection");
+ ha_messagex(rq, LOG_DEBUG, "using cached LDAP connection");
ld = ctx->pool[i];
ctx->pool[i] = NULL;
return ld;
@@ -492,14 +440,14 @@ static LDAP* get_ldap_connection(const ha_request_t* rq, ldap_context_t* ctx)
if(ctx->pool_mark >= ctx->ldap_max)
{
- ha_messagex(rq, LOG_ERR, "too many open connections");
+ ha_messagex(rq, LOG_ERR, "too many open LDAP connections");
return NULL;
}
ld = ldap_init(ctx->servers, ctx->port);
if(!ld)
{
- ha_message(rq, LOG_ERR, "couldn't initialize connection");
+ ha_message(rq, LOG_ERR, "couldn't initialize LDAP connection");
return NULL;
}
@@ -544,7 +492,6 @@ static void save_ldap_connection(const ha_request_t* rq, ldap_context_t* ctx, LD
case LDAP_SERVER_DOWN:
case LDAP_LOCAL_ERROR:
case LDAP_NO_MEMORY:
- discard_ldap_connection(rq, ctx, ld);
break;
default:
@@ -553,7 +500,7 @@ static void save_ldap_connection(const ha_request_t* rq, ldap_context_t* ctx, LD
/* An open connection in the pool */
if(!ctx->pool[i])
{
- ha_messagex(rq, LOG_DEBUG, "caching connection for later use");
+ ha_messagex(rq, LOG_DEBUG, "caching ldap connection for later use");
ctx->pool[i] = ld;
ld = NULL;
break;
@@ -561,6 +508,9 @@ static void save_ldap_connection(const ha_request_t* rq, ldap_context_t* ctx, LD
}
break;
};
+
+ if(ld != NULL)
+ discard_ldap_connection(rq, ctx, ld);
}
static int retrieve_user_entry(const ha_request_t* rq, ldap_context_t* ctx, LDAP* ld,
@@ -579,7 +529,7 @@ static int retrieve_user_entry(const ha_request_t* rq, ldap_context_t* ctx, LDAP
if(ctx->filter)
{
/* Filters can also have %u and %r */
- filter = substitute_params(rq, ctx, user, ctx->filter);
+ filter = bd_substitute(rq, user, ctx->filter);
if(!filter)
return HA_CRITERROR;
}
@@ -639,19 +589,20 @@ static int retrieve_user_entry(const ha_request_t* rq, ldap_context_t* ctx, LDAP
return HA_FALSE;
}
-static int complete_digest(ha_request_t* rq, const char* user, unsigned char* ha1)
+static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg)
{
ldap_context_t* ctx = (ldap_context_t*)rq->context->ctx_data;
LDAP* ld = NULL; /* freed in finally */
LDAPMessage* results = NULL; /* freed in finally */
LDAPMessage* entry = NULL; /* no need to free */
struct berval** ha1s = NULL; /* freed manually */
- char** pws;
+ const char** pws;
int ret = HA_FALSE;
const char* dn = NULL;
int r;
+ int foundany = 0;
- ASSERT(rq && ha1 && user);
+ ASSERT(rq && dg && user);
ld = get_ldap_connection(rq, ctx);
if(!ld)
@@ -668,7 +619,7 @@ static int complete_digest(ha_request_t* rq, const char* user, unsigned char* ha
if(ctx->dnmap)
{
/* The map can have %u and %r to denote user and realm */
- dn = substitute_params(rq, ctx, user, ctx->dnmap);
+ dn = bd_substitute(rq, user, ctx->dnmap);
if(!dn)
{
ret = HA_FAILED;
@@ -690,56 +641,77 @@ static int complete_digest(ha_request_t* rq, const char* user, unsigned char* ha
if(ctx->ha1_attr)
ha1s = ldap_get_values_len(ld, entry, ctx->ha1_attr);
- if(ha1s)
+ if(ha1s && *ha1s)
{
- if(*ha1s)
+ int foundinvalid = 0;
+
+ while(*ha1s)
{
- ret = parse_ldap_ha1(rq, *ha1s, ha1);
- if(ret != HA_OK)
+ r = parse_ldap_ha1(rq, *ha1s, dg->ha1);
+
+ if(r == HA_FALSE)
+ {
+ foundinvalid = 1;
+ }
+
+ else if(r == HA_OK)
{
- if(ret == HA_FALSE)
- ha_messagex(rq, LOG_ERR, "server contains invalid HA1 for user: %s", user);
+ foundany = 1;
+
+ /* Run the actual check */
+ ret = digest_complete_check(dg, rq->buf);
+
+ if(ret != HA_FALSE)
+ goto finally;
}
+
+ else if(r < 0)
+ goto finally;
+
+ ha1s++;
}
- ha_messagex(rq, LOG_DEBUG, "using HA1 from ldap");
- ldap_value_free_len(ha1s);
- goto finally;
+ if(foundinvalid)
+ ha_messagex(rq, LOG_ERR, "server contains invalid HA1 for user: %s", user);
}
/* If no ha1 set or none found, use password and make a HA1 */
- pws = ldap_get_values(ld, entry, ctx->pw_attr);
+ pws = (const char**)ldap_get_values(ld, entry, ctx->pw_attr);
- if(pws)
+ if(pws && *pws)
{
/* Find a cleartext password */
- const char* t = find_cleartext_password(rq->buf, (const char**)pws);
-
- if(t)
+ while((pws = find_cleartext_password(rq->buf, pws)))
{
- digest_makeha1(ha1, user, rq->context->realm, t);
- ret = HA_OK;
- }
+ foundany = 1;
- ldap_value_free(pws);
+ digest_makeha1(dg->ha1, user, rq->context->realm, *pws);
- if(ret == HA_OK)
- {
- ha_messagex(rq, LOG_DEBUG, "using clear password from ldap");
- goto finally;
+ /* Run the actual check */
+ ret = digest_complete_check(dg, rq->buf);
+
+ if(ret != HA_FALSE)
+ goto finally;
}
}
- ha_messagex(rq, LOG_ERR, "server contains no clear password or HA1 for user: %s", user);
+ if(!foundany)
+ ha_messagex(rq, LOG_WARNING, "server contains no clear password or HA1 for user: %s", user);
finally:
- if(ld)
- save_ldap_connection(rq, ctx, ld);
+ if(ha1s)
+ ldap_value_free_len(ha1s);
+
+ if(pws)
+ ldap_value_free((char**)pws);
if(results)
ldap_msgfree(results);
+ if(ld)
+ save_ldap_connection(rq, ctx, ld);
+
return ret;
}
@@ -772,7 +744,7 @@ static int validate_basic(ha_request_t* rq, const char* user, const char* passwo
if(ctx->dnmap)
{
/* The map can have %u and %r to denote user and realm */
- dn = substitute_params(rq, ctx, user, ctx->dnmap);
+ dn = bd_substitute(rq, user, ctx->dnmap);
if(!dn)
{
ret = HA_CRITERROR;
@@ -867,12 +839,12 @@ static int validate_basic(ha_request_t* rq, const char* user, const char* passwo
finally:
- if(ld)
- save_ldap_connection(rq, ctx, ld);
-
if(results)
ldap_msgfree(results);
+ if(ld)
+ save_ldap_connection(rq, ctx, ld);
+
/* TODO: Check to make sure this fits within the return values */
return ret;
}
@@ -1015,7 +987,7 @@ int ldap_inithand(ha_context_t* context)
}
memset(ctx->pool, 0, sizeof(LDAP*) * ctx->ldap_max);
- ha_messagex(NULL, LOG_INFO, "initialized handler");
+ ha_messagex(NULL, LOG_INFO, "initialized ldap handler");
}
return HA_OK;
diff --git a/daemon/misc.c b/daemon/misc.c
index f48d801..7ab8147 100644
--- a/daemon/misc.c
+++ b/daemon/misc.c
@@ -265,7 +265,7 @@ int ha_confint(const char* name, const char* conf, int min, int max, int* value)
/* -----------------------------------------------------------------------
- * Client Authentication Functions
+ * URI Functions
*/
char* ha_uriformat(ha_buffer_t* buf, const ha_uri_t* uri)
@@ -586,6 +586,10 @@ int ha_uricmp(ha_uri_t* one, ha_uri_t* two)
return 0;
}
+/* ----------------------------------------------------------------------------------
+ * Other miscellaneous stuff
+ */
+
int ha_genrandom(unsigned char* data, size_t len)
{
int r, dd;
diff --git a/daemon/pgsql.c b/daemon/pgsql.c
new file mode 100644
index 0000000..225bc65
--- /dev/null
+++ b/daemon/pgsql.c
@@ -0,0 +1,749 @@
+
+#include "usuals.h"
+#include "httpauthd.h"
+#include "md5.h"
+#include "sha1.h"
+#include "bd.h"
+
+#include <sys/time.h>
+
+/* LDAP library */
+#include <libpq-fe.h>
+
+/* -------------------------------------------------------------------------------
+ * Structures
+ */
+
+#define DB_PW_CLEAR 0
+#define DB_PW_SHA1 1
+#define DB_PW_MD5 2
+#define DB_PW_CRYPT 3
+
+/* Our hanler context */
+typedef struct pgsql_context
+{
+ /* Base Handler ------------------------------------------------------ */
+ bd_context_t bd;
+
+ /* Settings ---------------------------------------------------------- */
+ const char* host; /* The connection host or path */
+ const char* port; /* The connection port */
+ const char* user; /* The pgsql user name */
+ const char* password; /* The pgsql password */
+ const char* database; /* The database name */
+ const char* query; /* The query */
+ const char* pw_column; /* The database query to retrieve a password */
+ int pw_type; /* The type of password encoded in database */
+ const char* ha1_column; /* The database query to retrieve a ha1 */
+
+ int pgsql_max; /* Number of open connections allowed */
+ int pgsql_timeout; /* Maximum amount of time to dedicate to an ldap query */
+
+ /* Context ----------------------------------------------------------- */
+ PGconn** pool; /* Pool of available connections */
+ int pool_mark; /* Amount of connections allocated */
+}
+pgsql_context_t;
+
+/* Forward declarations for callbacks */
+static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg);
+static int validate_basic(ha_request_t* rq, const char* user, const char* password);
+static void escape_pgsql(const ha_request_t* rq, ha_buffer_t* buf, const char* value);
+
+/* The defaults for the context */
+static const pgsql_context_t pgsql_defaults =
+{
+ BD_CALLBACKS(validate_digest,
+ validate_basic, escape_pgsql),
+ NULL, /* host */
+ 0, /* port */
+ NULL, /* user */
+ NULL, /* password */
+ NULL, /* database */
+ NULL, /* query */
+ NULL, /* pw_attr */
+ DB_PW_CLEAR, /* pw_type */
+ NULL, /* ha1_attr */
+ 10, /* pgsql_max */
+ 30, /* pgsql_timeout */
+ NULL, /* pool */
+ 0 /* pool_mark */
+};
+
+
+/* -------------------------------------------------------------------------------
+ * Internal Functions
+ */
+
+static void escape_pgsql(const ha_request_t* rq, ha_buffer_t* buf, const char* value)
+{
+ size_t len;
+ char* t;
+
+ ASSERT(value);
+
+ len = strlen(value);
+
+ /* Bit of a hack, we copy the string in twice to give enough room. */
+ if((t = (char*)ha_bufmalloc(buf, (len * 2) + 1)) != NULL)
+ {
+ PQescapeString(t, value, len);
+ ha_bufcpy(buf, t);
+ }
+}
+
+static int dec_pgsql_binary(const ha_request_t* rq, const char* enc,
+ unsigned char* bytes, size_t len)
+{
+ size_t enclen;
+ void* d;
+
+ ASSERT(rq && enc && bytes && len);
+ enclen = strlen(enc);
+
+ /* Hex encoded */
+ if(enclen == (len * 2))
+ {
+ d = ha_bufdechex(rq->buf, enc, &enclen);
+
+ if(d && enclen == len)
+ {
+ ha_messagex(rq, LOG_DEBUG, "found value in hex encoded format");
+ memcpy(bytes, d, len);
+ return HA_OK;
+ }
+ }
+
+ /* Raw binary postgres encoded */
+ d = PQunescapeBytea(enc, &enclen);
+ if(d != NULL)
+ {
+ if(enclen == len)
+ memcpy(bytes, d, len);
+
+ PQfreemem(d);
+
+ if(enclen == len)
+ {
+ ha_messagex(rq, LOG_DEBUG, "found value in raw binary format");
+ return HA_OK;
+ }
+ }
+
+ /* B64 Encoded */
+ enclen = strlen(enc);
+ d = ha_bufdec64(rq->buf, enc, &enclen);
+
+ if(d && len == enclen)
+ {
+ ha_messagex(rq, LOG_DEBUG, "found value in b64 encoded format");
+ memcpy(bytes, d, len);
+ return HA_OK;
+ }
+
+ return ha_buferr(rq->buf) ? HA_CRITERROR : HA_FALSE;
+}
+
+static int validate_ha1(ha_request_t* rq, pgsql_context_t* ctx, const char* user,
+ const char* clearpw, const char* dbpw)
+{
+ unsigned char dbha1[MD5_LEN];
+ unsigned char ha1[MD5_LEN];
+ const char* p;
+
+ int r = dec_pgsql_binary(rq, dbpw, dbha1, MD5_LEN);
+
+ if(r < 0)
+ return r;
+
+ if(r == HA_OK)
+ {
+ digest_makeha1(ha1, user, rq->context->realm, clearpw);
+ if(memcmp(ha1, dbha1, MD5_LEN) == 0)
+ return HA_OK;
+ }
+
+ return HA_FALSE;
+}
+
+static int validate_password(ha_request_t* rq, pgsql_context_t* ctx, const char* user,
+ const char* clearpw, const char* dbpw)
+{
+ unsigned char buf[SHA1_LEN];
+ const char* p;
+ int r;
+
+ ASSERT(SHA1_LEN > MD5_LEN);
+
+ switch(ctx->pw_type)
+ {
+
+ /* Clear text */
+ case DB_PW_CLEAR:
+
+ if(strcmp(clearpw, dbpw) == 0)
+ {
+ ha_messagex(rq, LOG_DEBUG, "found matching clear text password");
+ return HA_OK;
+ }
+
+ break;
+
+ /* Crypt pw */
+ case DB_PW_CRYPT:
+
+ ha_lock();
+ p = crypt(clearpw, dbpw);
+ ha_unlock();
+
+ if(p && strcmp(clearpw, p) == 0)
+ {
+ ha_messagex(rq, LOG_DEBUG, "found matching crypt password");
+ return HA_OK;
+ }
+
+ break;
+
+ /* MD5 */
+ case DB_PW_MD5:
+
+ r = dec_pgsql_binary(rq, dbpw, buf, MD5_LEN);
+ if(r < 0) return r;
+
+ if(r == HA_OK && md5_strcmp(buf, clearpw) == 0)
+ {
+ ha_messagex(rq, LOG_DEBUG, "found matching md5 password");
+ return HA_OK;
+ }
+
+ break;
+
+
+ /* SHA1 */
+ case DB_PW_SHA1:
+
+ r = dec_pgsql_binary(rq, dbpw, buf, SHA1_LEN);
+ if(r < 0) return r;
+
+ if(r == HA_OK && sha1_strcmp(buf, clearpw) == 0)
+ {
+ ha_messagex(rq, LOG_DEBUG, "found matching sha1 password");
+ return HA_OK;
+ }
+
+ break;
+
+ default:
+ ASSERT(0 && "invalid password type");
+ break;
+ };
+
+ return HA_FALSE;
+}
+
+static PGconn* get_pgsql_connection(const ha_request_t* rq, pgsql_context_t* ctx)
+{
+ PGconn* pg;
+ int i;
+
+ ASSERT(ctx);
+
+ for(i = 0; i < ctx->pgsql_max; i++)
+ {
+ /* An open connection in the pool */
+ if(ctx->pool[i])
+ {
+ ha_messagex(rq, LOG_DEBUG, "using cached pgsql connection");
+ pg = ctx->pool[i];
+ ctx->pool[i] = NULL;
+ return pg;
+ }
+ }
+
+ if(ctx->pool_mark >= ctx->pgsql_max)
+ {
+ ha_messagex(rq, LOG_ERR, "too many open pgsql connections");
+ return NULL;
+ }
+
+ pg = PQsetdbLogin(ctx->host, ctx->port, NULL, NULL, ctx->database,
+ ctx->user, ctx->password);
+ if(!pg)
+ {
+ ha_messagex(rq, LOG_CRIT, "internal error in postgres library");
+ return NULL;
+ }
+
+ if(PQstatus(pg) == CONNECTION_BAD)
+ {
+ ha_messagex(rq, LOG_ERR, "error opening pgsql connection: %s", PQerrorMessage(pg));
+ PQfinish(pg);
+ return NULL;
+ }
+
+ ctx->pool_mark++;
+ ha_messagex(rq, LOG_DEBUG, "opened new pgsql connection (total %d)", ctx->pool_mark);
+ return pg;
+}
+
+static void discard_pgsql_connection(const ha_request_t* rq, pgsql_context_t* ctx, PGconn* pg)
+{
+ PQfinish(pg);
+ ctx->pool_mark--;
+ ha_messagex(rq, LOG_DEBUG, "discarding pgsql connection (total %d)", ctx->pool_mark);
+}
+
+static void save_pgsql_connection(const ha_request_t* rq, pgsql_context_t* ctx, PGconn* pg)
+{
+ int i, e;
+
+ ASSERT(ctx);
+
+ if(!pg)
+ return;
+
+ /* Make sure it's worth saving */
+ if(PQstatus(pg) != CONNECTION_BAD)
+ {
+ for(i = 0; i < ctx->pgsql_max; i++)
+ {
+ /* An open connection in the pool */
+ if(!ctx->pool[i])
+ {
+ ha_messagex(rq, LOG_DEBUG, "caching pgsql connection for later use");
+ ctx->pool[i] = pg;
+ pg = NULL;
+ break;
+ }
+ }
+ }
+
+ if(pg != NULL)
+ discard_pgsql_connection(rq, ctx, pg);
+}
+
+static int check_pgsql_result(ha_request_t* rq, PGresult* res)
+{
+ switch(PQresultStatus(res))
+ {
+ case PGRES_COMMAND_OK:
+ case PGRES_EMPTY_QUERY:
+ ha_messagex(rq, LOG_WARNING, "query did not return data");
+ return HA_FALSE;
+ case PGRES_TUPLES_OK:
+ return HA_OK;
+ case PGRES_BAD_RESPONSE:
+ ha_messagex(rq, LOG_ERR, "error communicating with pgsql server");
+ return HA_FAILED;
+ case PGRES_NONFATAL_ERROR:
+ ha_messagex(rq, LOG_ERR, "error querying database: %s", PQresultErrorMessage(res));
+ return HA_FAILED;
+ case PGRES_FATAL_ERROR:
+ ha_messagex(rq, LOG_CRIT, "internal error in postgres library");
+ return HA_CRITERROR;
+ case PGRES_COPY_OUT:
+ case PGRES_COPY_IN:
+ default:
+ ASSERT(0 && "unexpected response");
+ return HA_FAILED;
+ };
+
+ return HA_OK;
+}
+
+static int resolve_column(PGresult* res, const char* column)
+{
+ int i;
+
+ if(column)
+ {
+ for(i = 0; i < PQnfields(res); i++)
+ {
+ if(strcasecmp(column, PQfname(res, i)) == 0)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static int retrieve_user_rows(ha_request_t* rq, pgsql_context_t* ctx,
+ const char* user, PGresult** results)
+{
+ PGconn* pg = NULL;
+ PGresult* res = NULL;
+ const char* query;
+ int ret = HA_OK;
+
+ ASSERT(rq && ctx && user && res);
+ *results = NULL;
+
+ pg = get_pgsql_connection(rq, ctx);
+ if(!pg)
+ {
+ ret = HA_FAILED;
+ goto finally;
+ }
+
+ ASSERT(ctx->query);
+
+ /* The map can have %u and %r to denote user and realm */
+ query = bd_substitute(rq, user, ctx->query);
+ if(!query)
+ {
+ ret = HA_CRITERROR;
+ goto finally;
+ }
+
+ ha_messagex(rq, LOG_DEBUG, "executing query: %s", query);
+ res = PQexec(pg, query);
+
+
+ ret = check_pgsql_result(rq, res);
+ if(ret != HA_OK)
+ goto finally;
+
+ if(PQntuples(res) == 0)
+ {
+ ha_messagex(rq, LOG_WARNING, "login failed. couldn't find user: %s", user);
+ ret = HA_FALSE;
+ goto finally;
+ }
+
+ if(PQnfields(res) <= 0)
+ {
+ ha_messagex(rq, LOG_ERR, "query returned 0 columns: %s", query);
+ ret = HA_FAILED;
+ goto finally;
+ }
+
+ *results = res;
+ ha_messagex(rq, LOG_DEBUG, "received %d result rows", PQntuples(res));
+
+finally:
+
+ /* According to libpg we can close/save the connection
+ * before the returned results are freed, no worries there */
+ if(pg != NULL)
+ save_pgsql_connection(rq, ctx, pg);
+
+ return ret;
+}
+
+static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg)
+{
+ pgsql_context_t* ctx = (pgsql_context_t*)rq->context->ctx_data;
+ PGresult* res = NULL;
+ int ret = HA_FALSE;
+ int pw_column = -1;
+ int ha1_column = -1;
+ int r, i, foundany = 0;
+
+ ASSERT(rq && user && dg);
+
+ ret = retrieve_user_rows(rq, ctx, user, &res);
+ if(ret != HA_OK)
+ goto finally;
+
+ ASSERT(res);
+
+ pw_column = resolve_column(res, ctx->pw_column);
+ ha1_column = resolve_column(res, ctx->ha1_column);
+
+ if(pw_column == -1 && ha1_column == -1)
+ {
+ if(PQnfields(res) > 1)
+ ha_messagex(rq, LOG_WARNING, "query returned more than 1 column, using first as password");
+
+ pw_column = 0;
+ }
+
+ for(i = 0; i < PQntuples(res); i++)
+ {
+ if(pw_column != -1)
+ {
+ if(ctx->pw_type == DB_PW_CLEAR && !PQgetisnull(res, i, pw_column))
+ {
+ foundany = 1;
+
+ digest_makeha1(dg->ha1, user, rq->context->realm, PQgetvalue(res, i, pw_column));
+
+ /* Run the actual check */
+ ret = digest_complete_check(dg, rq->buf);
+
+ if(ret != HA_FALSE)
+ goto finally;
+ }
+ }
+
+ if(ha1_column != -1)
+ {
+ if(!PQgetisnull(res, i, ha1_column))
+ {
+ ret = dec_pgsql_binary(rq, PQgetvalue(res, i, ha1_column), dg->ha1, MD5_LEN);
+ if(ret < 0)
+ goto finally;
+ else if(ret == HA_FALSE)
+ continue;
+
+ foundany = 1;
+
+ /* Run the actual check */
+ ret = digest_complete_check(dg, rq->buf);
+
+ if(ret != HA_FALSE)
+ goto finally;
+ }
+ }
+ }
+
+ if(!foundany)
+ ha_messagex(rq, LOG_WARNING, "no clear password or ha1 present for user: %s", user);
+
+finally:
+ if(res)
+ PQclear(res);
+
+ return ret;
+}
+
+static int validate_basic(ha_request_t* rq, const char* user, const char* password)
+{
+ pgsql_context_t* ctx = (pgsql_context_t*)rq->context->ctx_data;
+ PGresult* res = NULL;
+ int ret = HA_FALSE;
+ int pw_column = -1;
+ int ha1_column = -1;
+ int i, foundany = 0;
+
+ ASSERT(rq && user && password);
+
+ ret = retrieve_user_rows(rq, ctx, user, &res);
+ if(ret != HA_OK)
+ goto finally;
+
+ ASSERT(res);
+
+ pw_column = resolve_column(res, ctx->pw_column);
+ ha1_column = resolve_column(res, ctx->ha1_column);
+
+ if(pw_column == -1 && ha1_column == -1)
+ {
+ if(PQnfields(res) > 1)
+ ha_messagex(rq, LOG_WARNING, "query returned more than 1 column, using first as password");
+ pw_column = 0;
+ }
+
+
+ for(i = 0; i < PQntuples(res); i++)
+ {
+ if(pw_column != -1)
+ {
+ if(!PQgetisnull(res, i, pw_column))
+ {
+ foundany = 1;
+ ret = validate_password(rq, ctx, user, password, PQgetvalue(res, i, pw_column));
+ if(ret != HA_FALSE)
+ goto finally;
+ }
+ }
+
+ if(ha1_column != -1)
+ {
+ if(!PQgetisnull(res, i, ha1_column))
+ {
+ foundany = 1;
+ ret = validate_ha1(rq, ctx, user, password, PQgetvalue(res, i, ha1_column));
+ if(ret != HA_FALSE)
+ goto finally;
+ }
+ }
+ }
+
+ if(!foundany)
+ ha_messagex(rq, LOG_WARNING, "no password present for user: %s", user);
+
+finally:
+ if(res)
+ PQclear(res);
+
+ return ret;
+}
+
+
+/* -------------------------------------------------------------------------------
+ * Handler Functions
+ */
+
+int pgsql_config(ha_context_t* context, const char* name, const char* value)
+{
+ pgsql_context_t* ctx = (pgsql_context_t*)(context->ctx_data);
+
+ ASSERT(name && value && value[0]);
+
+ if(strcmp(name, "dbserver") == 0)
+ {
+ ctx->host = value;
+ return HA_OK;
+ }
+
+ if(strcmp(name, "dbport") == 0)
+ {
+ ctx->port = value;
+ return HA_OK;
+ }
+
+ if(strcmp(name, "dbuser") == 0)
+ {
+ ctx->user = value;
+ return HA_OK;
+ }
+
+ if(strcmp(name, "dbpassword") == 0)
+ {
+ ctx->password = value;
+ return HA_OK;
+ }
+
+ if(strcmp(name, "dbdatabase") == 0)
+ {
+ ctx->database = value;
+ return HA_OK;
+ }
+
+ if(strcmp(name, "dbquery") == 0)
+ {
+ ctx->query = value;
+ return HA_OK;
+ }
+
+ if(strcmp(name, "dbpwcolumn") == 0)
+ {
+ ctx->pw_column = value;
+ return HA_OK;
+ }
+
+ if(strcmp(name, "dbpwtype") == 0)
+ {
+ if(strcmp(value, "clear") == 0)
+ ctx->pw_type = DB_PW_CLEAR;
+ else if(strcmp(value, "crypt") == 0)
+ ctx->pw_type = DB_PW_CRYPT;
+ else if(strcmp(value, "md5") == 0)
+ ctx->pw_type = DB_PW_MD5;
+ else if(strcmp(value, "sha1") == 0)
+ ctx->pw_type = DB_PW_SHA1;
+ else
+ {
+ ha_messagex(NULL, LOG_ERR, "invalid value for '%s' (must be 'clear', 'crypt', 'md5' or 'sha1')", name);
+ return HA_FAILED;
+ }
+ }
+
+ if(strcmp(name, "dbha1column") == 0)
+ {
+ ctx->ha1_column = value;
+ return HA_OK;
+ }
+
+ else if(strcmp(name, "dbmax") == 0)
+ {
+ return ha_confint(name, value, 1, 256, &(ctx->pgsql_max));
+ }
+
+ else if(strcmp(name, "dbtimeout") == 0)
+ {
+ /* TODO: Implement database timeouts */
+ return ha_confint(name, value, 0, 86400, &(ctx->pgsql_timeout));
+ }
+
+ return HA_FALSE;
+}
+
+int pgsql_init(ha_context_t* context)
+{
+ int r;
+
+ if((r = bd_init(context)) != HA_OK)
+ return r;
+
+ /* Context specific initialization */
+ if(context)
+ {
+ pgsql_context_t* ctx = (pgsql_context_t*)(context->ctx_data);
+ ASSERT(ctx);
+
+ /* Check for mandatory configuration */
+ if(!ctx->database || !ctx->query)
+ {
+ ha_messagex(NULL, LOG_ERR, "configuration incomplete. "
+ "Must have DBDatabase and DBQuery.");
+ return HA_FAILED;
+ }
+
+ ASSERT(!ctx->pool);
+ ASSERT(ctx->pgsql_max > 0);
+
+ /*
+ * Our connection pool. It's the size of our maximum
+ * amount of pending connections as that's the max
+ * we'd be able to use at a time anyway.
+ */
+ ctx->pool = (PGconn**)malloc(sizeof(PGconn*) * ctx->pgsql_max);
+ if(!ctx->pool)
+ {
+ ha_messagex(NULL, LOG_CRIT, "out of memory");
+ return HA_CRITERROR;
+ }
+
+ memset(ctx->pool, 0, sizeof(PGconn*) * ctx->pgsql_max);
+ ha_messagex(NULL, LOG_INFO, "initialized pgsql handler");
+ }
+
+ return HA_OK;
+}
+
+void pgsql_destroy(ha_context_t* context)
+{
+ if(context)
+ {
+ /* Note: We don't need to be thread safe here anymore */
+ pgsql_context_t* ctx = (pgsql_context_t*)(context->ctx_data);
+ int i;
+
+ ASSERT(ctx);
+
+ if(ctx->pool)
+ {
+ /* Close any connections we have open */
+ for(i = 0; i < ctx->pgsql_max; i++)
+ {
+ if(ctx->pool[i])
+ PQfinish(ctx->pool[i]);
+ }
+
+ /* And free the connection pool */
+ free(ctx->pool);
+ }
+ }
+
+ bd_destroy(context);
+ ha_messagex(NULL, LOG_INFO, "uninitialized pgsql handler");
+}
+
+
+
+/* -------------------------------------------------------------------------------
+ * Handler Definition
+ */
+
+ha_handler_t pgsql_handler =
+{
+ "PGSQL", /* The type */
+ pgsql_init, /* Initialization function */
+ pgsql_destroy, /* Uninitialization routine */
+ pgsql_config, /* Config routine */
+ bd_process, /* Processing routine */
+ &pgsql_defaults, /* The context defaults */
+ sizeof(pgsql_context_t)
+};
diff --git a/daemon/simple.c b/daemon/simple.c
index bda60c3..8e4f8df 100644
--- a/daemon/simple.c
+++ b/daemon/simple.c
@@ -29,13 +29,13 @@ typedef struct simple_context
simple_context_t;
/* Forward declarations for callbacks */
-static int complete_digest(ha_request_t* rq, const char* user, unsigned char* ha1);
+static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg);
static int validate_basic(ha_request_t* rq, const char* user, const char* password);
/* The defaults for the context */
static const simple_context_t simple_defaults =
{
- BD_CALLBACKS(complete_digest, validate_basic),
+ BD_CALLBACKS(validate_digest, validate_basic, NULL),
NULL /* filename */
};
@@ -43,7 +43,7 @@ static const simple_context_t simple_defaults =
* Internal Functions
*/
-static int complete_digest(ha_request_t* rq, const char* user, unsigned char* ha1)
+static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg)
{
simple_context_t* ctx = (simple_context_t*)rq->context->ctx_data;
FILE* f;
@@ -52,6 +52,8 @@ static int complete_digest(ha_request_t* rq, const char* user, unsigned char* ha
size_t len;
char line[SIMPLE_MAXLINE];
int ret = HA_FALSE;
+ int foundinvalid = 0;
+ int foundgood = 0;
ASSERT(ctx && rq && user && user[0]);
ha_messagex(rq, LOG_DEBUG, "searching password file for user's ha1: %s", user);
@@ -59,7 +61,7 @@ static int complete_digest(ha_request_t* rq, const char* user, unsigned char* ha
f = fopen(ctx->filename, "r");
if(!f)
{
- ha_message(rq, LOG_ERR, "can't open file for basic auth: %s", ctx->filename);
+ ha_message(rq, LOG_ERR, "can't open file for digest auth: %s", ctx->filename);
return HA_FAILED;
}
@@ -81,45 +83,63 @@ static int complete_digest(ha_request_t* rq, const char* user, unsigned char* ha
}
t = strchr(line, ':');
- if(t)
+ if(!t)
+ continue;
+
+ /* Split the line */
+ *t = 0;
+ t++;
+
+ /* Check the user */
+ if(strcmp(line, user) != 0)
+ continue;
+
+ t2 = strchr(t, ':');
+ if(!t2)
{
- /* Split the line */
- *t = 0;
- t++;
-
- /* Check the user */
- if(strcmp(line, user) == 0)
- {
- /* Otherwise it might be a digest type ha1. */
- t2 = strchr(t, ':');
- if(t2)
- {
- *t2 = 0;
- t2++;
-
- /* Check the realm */
- if(strcmp(t, rq->context->realm) == 0)
- {
- len = MD5_LEN;
-
- /* Now try and decode the ha1 */
- t = ha_bufdechex(rq->buf, t2, &len);
- if(t && len == MD5_LEN)
- {
- ha_messagex(rq, LOG_DEBUG, "found ha1 for user: %s", user);
- memcpy(ha1, t, MD5_LEN);
- ret = HA_OK;
- break;
- }
- }
- }
-
- if(!t2 || ret != HA_OK)
- ha_messagex(rq, LOG_WARNING, "user '%s' found in file, but password not in digest format", user);
- }
+ /* An invalid line without a realm */
+ foundinvalid = 1;
+ continue;
}
+
+ /* Split the line */
+ *t2 = 0;
+ t2++;
+
+ /* Check the realm */
+ if(strcmp(t, rq->context->realm) != 0)
+ continue;
+
+ /* Now try and decode the ha1 */
+ len = MD5_LEN;
+ t = ha_bufdechex(rq->buf, t2, &len);
+
+ if(!t || len != MD5_LEN)
+ {
+ /* An invalid ha1 */
+ foundinvalid = 1;
+ continue;
+ }
+
+ ha_messagex(rq, LOG_DEBUG, "found ha1 for user: %s", user);
+ memcpy(dg->ha1, t, MD5_LEN);
+ foundgood = 1;
+
+ /* Try to do the validation */
+ ret = digest_complete_check(dg, rq->buf);
+
+ /* If invalid then continue search */
+ if(ret == HA_FALSE)
+ continue;
+
+ /* Success or an error ends here */
+ break;
}
+
+ if(foundinvalid && !foundgood)
+ ha_messagex(rq, LOG_WARNING, "user '%s' found in file, but password not in digest format", user);
+
fclose(f);
if(ha_buferr(rq->buf))
@@ -173,57 +193,61 @@ static int validate_basic(ha_request_t* rq, const char* user, const char* passwo
trim_end(line);
t = strchr(line, ':');
- if(t)
+ if(!t)
+ continue;
+
+ /* Split the line */
+ *t = 0;
+ t++;
+
+ /* Check the user */
+ if(strcmp(line, user) != 0)
+ continue;
+
+ /* Not sure if crypt is thread safe so we lock */
+ ha_lock(NULL);
+
+ /* Check the password */
+ t2 = crypt(password, t);
+
+ ha_unlock(NULL);
+
+ if(strcmp(t2, t) == 0)
{
- /* Split the line */
- *t = 0;
- t++;
-
- /* Check the user */
- if(strcmp(line, user) == 0)
- {
- /* Not sure if crypt is thread safe so we lock */
- ha_lock(NULL);
-
- /* Check the password */
- t2 = crypt(password, t);
-
- ha_unlock(NULL);
-
- if(strcmp(t2, t) == 0)
- {
- ha_messagex(rq, LOG_DEBUG, "found valid crypt password for user: %s", user);
- ret = HA_OK;
- break;
- }
-
- /* Otherwise it might be a digest type ha1. */
- t2 = strchr(t, ':');
- if(t2)
- {
- *t2 = 0;
- t2++;
-
- /* Check the realm */
- if(strcmp(t, rq->context->realm) == 0)
- {
- len = MD5_LEN;
-
- /* Now try antd decode the ha1 */
- t = ha_bufdechex(rq->buf, t2, &len);
- if(t && len == MD5_LEN && memcmp(ha1, t, MD5_LEN) == 0)
- {
- ha_messagex(rq, LOG_DEBUG, "found valid ha1 for user: %s", user);
- ret = HA_OK;
- break;
- }
- }
- }
-
- if(ha_buferr(rq->buf))
- break;
- }
+ ha_messagex(rq, LOG_DEBUG, "found valid crypt password for user: %s", user);
+ ret = HA_OK;
+ break;
}
+
+ /* Otherwise it might be a digest type ha1. */
+ t2 = strchr(t, ':');
+ if(!t2)
+ continue;
+
+ /* Split the line */
+ *t2 = 0;
+ t2++;
+
+ /* Check the realm */
+ if(strcmp(t, rq->context->realm) != 0)
+ continue;
+
+ /* Now try antd decode the ha1 */
+ len = MD5_LEN;
+
+ t = ha_bufdechex(rq->buf, t2, &len);
+ if(!t || len != MD5_LEN)
+ continue;
+
+ if(memcmp(ha1, t, MD5_LEN) == 0)
+ {
+ ha_messagex(rq, LOG_DEBUG, "found valid ha1 for user: %s", user);
+ ret = HA_OK;
+ break;
+ }
+
+ if(ha_buferr(rq->buf))
+ break;
}
fclose(f);