diff options
author | Stef Walter <stef@memberwebs.com> | 2004-04-24 22:38:50 +0000 |
---|---|---|
committer | Stef Walter <stef@memberwebs.com> | 2004-04-24 22:38:50 +0000 |
commit | cbbe71752d7f9c6204ab0f16600fe7f10490f203 (patch) | |
tree | 365e6e472d239d117b5f849c45f3c08fc6617c0a | |
parent | ff76efc3e5e1b0e4ca3b10b7402406f619509bba (diff) |
Completed implementation of ldap/ntlm/simple handlers
-rw-r--r-- | common/buffer.c | 216 | ||||
-rw-r--r-- | common/hash.c | 130 | ||||
-rw-r--r-- | common/hash.h | 52 | ||||
-rw-r--r-- | common/md5.c | 36 | ||||
-rw-r--r-- | common/md5.h | 26 | ||||
-rw-r--r-- | common/sha1.c | 164 | ||||
-rw-r--r-- | common/sha1.h | 30 | ||||
-rw-r--r-- | configure.in | 20 | ||||
-rw-r--r-- | daemon/Makefile.am | 4 | ||||
-rw-r--r-- | daemon/basic.c | 242 | ||||
-rw-r--r-- | daemon/basic.h | 17 | ||||
-rw-r--r-- | daemon/defaults.h | 2 | ||||
-rw-r--r-- | daemon/digest.c | 533 | ||||
-rw-r--r-- | daemon/digest.h | 57 | ||||
-rw-r--r-- | daemon/httpauthd.c | 65 | ||||
-rw-r--r-- | daemon/httpauthd.h | 182 | ||||
-rw-r--r-- | daemon/ldap.c | 768 | ||||
-rw-r--r-- | daemon/misc.c | 599 | ||||
-rw-r--r-- | daemon/ntlm.c | 115 | ||||
-rw-r--r-- | daemon/simple.c | 663 | ||||
-rw-r--r-- | daemon/usuals.h | 7 |
21 files changed, 2814 insertions, 1114 deletions
diff --git a/common/buffer.c b/common/buffer.c index 28cab24..a5b18a7 100644 --- a/common/buffer.c +++ b/common/buffer.c @@ -47,6 +47,12 @@ void buffer_bump(ha_buffer_t* buf, int count) } } +void ha_bufinit(ha_buffer_t* buf) +{ + memset(buf, 0, sizeof(*buf)); + ha_bufreset(buf); +} + void ha_bufreset(ha_buffer_t* buf) { if(!buf->_dt || buf->_al == 0) @@ -67,12 +73,6 @@ void ha_bufreset(ha_buffer_t* buf) buf->_pp = buf->_dt; } -void ha_bufinit(ha_buffer_t* buf) -{ - memset(buf, 0, sizeof(*buf)); - ha_bufreset(buf); -} - void ha_buffree(ha_buffer_t* buf) { if(buf->_dt) @@ -82,7 +82,8 @@ void ha_buffree(ha_buffer_t* buf) buf->_rp = buf->_pp = NULL; } -int ha_readline(int fd, ha_buffer_t* buf) + +int ha_bufreadline(int fd, ha_buffer_t* buf) { int l; @@ -137,7 +138,7 @@ int ha_readline(int fd, ha_buffer_t* buf) return 1; } -char* ha_parseword(ha_buffer_t* buf, const char* delims) +char* ha_bufparseword(ha_buffer_t* buf, const char* delims) { char* word = NULL; @@ -185,7 +186,7 @@ char* ha_parseword(ha_buffer_t* buf, const char* delims) return word; } -char* ha_parseline(ha_buffer_t* buf, int trim) +char* ha_bufparseline(ha_buffer_t* buf, int trim) { char* t; char* line = NULL; @@ -231,55 +232,93 @@ char* ha_parseline(ha_buffer_t* buf, int trim) return line; } -void ha_bufnext(ha_buffer_t* buf) -{ - buffer_bump(buf, 1); - - if(!ha_buferr(buf)) - { - buf->_rp++; - buf->_pp = buf->_rp; - *(buf->_rp) = 0; - } -} - -void ha_bufcat(ha_buffer_t* buf, ...) +char* ha_bufmcat(ha_buffer_t* buf, ...) { const char* str; va_list ap; va_start(ap, buf); + if(ha_buferr(buf)) + return NULL; + + /* Move up the block pointer if we're not joining strings */ + if(ha_buflen(buf) > 0 && *(buf->_rp - 1) != 0) + buf->_pp = buf->_rp; + while((str = va_arg(ap, char*)) != NULL) { int len = strlen(str); - buffer_bump(buf, len); + /* Always add one for the terminating char */ + buffer_bump(buf, len + 1); if(ha_buferr(buf)) - return; + return NULL; - /* _rpoint points to teh null */ + /* _rp always points to the next write point */ strcpy(buf->_rp, str); buf->_rp += len; } + + buf->_rp++; + return buf->_pp; +} + +char* ha_bufcpy(ha_buffer_t* buf, const char* src) +{ + size_t len = strlen(src); + return ha_bufncpy(buf, src, len); +} + +char* ha_bufncpy(ha_buffer_t* buf, const char* src, size_t len) +{ + if(ha_buferr(buf)) + return NULL; + + /* Move up the block pointer if we're not joining strings */ + if(ha_buflen(buf) > 0 && *(buf->_rp - 1) != 0) + buf->_pp = buf->_rp; + + /* Always add one for the terminating char */ + buffer_bump(buf, len + 1); + + if(ha_buferr(buf)) + return NULL; + + memcpy(buf->_rp, src, len * sizeof(char)); + buf->_rp += (len + 1); + *(buf->_rp - 1) = 0; + return buf->_pp; } void* ha_bufmalloc(ha_buffer_t* buf, size_t sz) { void* ret; - ha_bufnext(buf); + if(ha_buferr(buf)) + return NULL; + + /* TODO: Align memory on an appropriate boundary here */ + + /* We're not working with strings so always bump the pointer up */ + buf->_pp = buf->_rp; + buffer_bump(buf, sz); - buf->_rp += sz; if(ha_buferr(buf)) return NULL; - ret = (void*)ha_bufdata(buf); - ha_bufnext(buf); + buf->_rp += sz; + return (void*)buf->_pp; +} - return ret; +void* ha_bufmemdup(ha_buffer_t* buf, const void* src, size_t bytes) +{ + void* mem = ha_bufmalloc(buf, bytes); + if(mem) + memcpy(mem, src, bytes); + return mem; } /* @@ -311,14 +350,15 @@ static const char BASE64C[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const char PAD64C = '='; -void ha_bufenc64(ha_buffer_t* buf, const char* src, int len) +char* ha_bufenc64(ha_buffer_t* buf, const void* source, size_t len) { unsigned char input[3]; unsigned char output[4]; + unsigned char* src = (unsigned char*)source; size_t i; - if(len == -1) - len = strlen(src); + if(ha_buferr(buf)) + return NULL; while(2 < len) { @@ -332,10 +372,11 @@ void ha_bufenc64(ha_buffer_t* buf, const char* src, int len) output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); output[3] = input[2] & 0x3f; + /* This also accounts for the null terminator */ buffer_bump(buf, 5); if(ha_buferr(buf)) - return; + return NULL; *(buf->_rp++) = BASE64C[output[0]]; *(buf->_rp++) = BASE64C[output[1]]; @@ -355,10 +396,11 @@ void ha_bufenc64(ha_buffer_t* buf, const char* src, int len) output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + /* This also accounts for the null terminator */ buffer_bump(buf, 5); if(ha_buferr(buf)) - return; + return NULL; *(buf->_rp++) = BASE64C[output[0]]; *(buf->_rp++) = BASE64C[output[1]]; @@ -370,17 +412,23 @@ void ha_bufenc64(ha_buffer_t* buf, const char* src, int len) } *(buf->_rp++) = '\0'; + return buf->_pp; } -void ha_bufdec64(ha_buffer_t* buf, const char* src) +void* ha_bufdec64(ha_buffer_t* buf, const char* src, size_t bytes) { - int state; + int state = 0; int ch; char* pos; + size_t done = 0; - state = 0; + if(ha_buferr(buf)) + return NULL; - while((ch = *src++) != '\0') + if(bytes == 0) + bytes = ~0; + + while((ch = *src++) != '\0' && done < bytes) { if(isspace(ch)) /* Skip whitespace anywhere. */ continue; @@ -401,23 +449,27 @@ void ha_bufdec64(ha_buffer_t* buf, const char* src) { case 0: *(buf->_rp++) = (pos - BASE64C) << 2; + done++; state = 1; break; case 1: *(buf->_rp++) |= (pos - BASE64C) >> 4; + done++; *(buf->_rp) = ((pos - BASE64C) & 0x0f) << 4; state = 2; break; case 2: *(buf->_rp++) |= (pos - BASE64C) >> 2; + done++; *(buf->_rp) = ((pos - BASE64C) & 0x03) << 6; state = 3; break; case 3: *(buf->_rp++) |= (pos - BASE64C); + done++; state = 0; break; }; @@ -425,5 +477,89 @@ void ha_bufdec64(ha_buffer_t* buf, const char* src) /* TODO: Validate ending and return error if invalid somehow */ } - *(buf->_rp++) = '\0'; + /* If we were asked for a specific amount of bytes, then return null */ + if(bytes != ~0 && bytes != done) + return NULL; + + return buf->_pp; +} + +static const char HEXC[] = "0123456789ABCDEF"; + +char* ha_bufenchex(ha_buffer_t* buf, const void* source, size_t len) +{ + unsigned char* src = (unsigned char*)source; + unsigned char j; + + buffer_bump(buf, (len * 2) + 1); + + if(ha_buferr(buf)) + return NULL; + + while(len > 0) + { + j = *(src) >> 4 & 0xf; + if(j <= 9) + *(buf->_rp++) = (j + '0'); + else + *(buf->_rp++) = (j + 'a' - 10); + + j = *(src++) & 0xf; + len--; + + if(j <= 9) + *(buf->_rp++) = (j + '0'); + else + *(buf->_rp++) = (j + 'a' - 10); + } + + *(buf->_rp++) = 0; + return buf->_pp; +} + +void* ha_bufdechex(ha_buffer_t* buf, const char* src, size_t bytes) +{ + unsigned short a; + unsigned short b; + size_t done = 0; + char* pos; + + if(bytes != 0) + { + buffer_bump(buf, bytes + 1); + } + else + { + bytes = ~0; + buffer_bump(buf, (strlen(src) / 2) + 1); + } + + if(ha_buferr(buf)) + return NULL; + + while(src[0] && src[1] && done < bytes) + { + /* Find the position */ + pos = strchr(HEXC, src[0]); + if(pos == 0) + break; + + a = HEXC - pos; + + pos = strchr(HEXC, src[1]); + if(pos == 0); + break; + + b = HEXC - pos; + + *(buf->_rp++) = ((a & 0xf) << 4) | (b & 0xf); + src += 2; + done++; + } + + /* If we were asked for a specific amount of bytes, then return null */ + if(bytes != ~0 && bytes != done) + return NULL; + + return buf->_pp; } diff --git a/common/hash.c b/common/hash.c index 7d67e53..6471803 100644 --- a/common/hash.c +++ b/common/hash.c @@ -88,10 +88,14 @@ struct hash_t #ifdef HASH_COPYKEYS unsigned int klen; #endif +#ifdef HASH_CALLBACKS + hash_free_val_t f_free; + void* arg; +#endif }; -#define INITIAL_MAX 15 /* tunable == 2^n - 1 */ +#define INITIAL_MAX 15 /* tunable == 2^n - 1 */ /* * Hash creation functions. @@ -102,10 +106,18 @@ static hash_entry_t** alloc_array(hash_t* ht, unsigned int max) return malloc(sizeof(*(ht->array)) * (max + 1)); } -#ifdef HASH_COPYKEYS -hash_t* hash_create(size_t klen) +#ifdef HASH_CALLBACKS + #ifdef HASH_COPYKEYS +hash_t* hash_create(size_t klen, hash_free_val_t f_free, void* arg) + #else +hash_t* hash_create(hash_free_val_t f_free, void* arg) + #endif #else + #ifdef HASH_COPYKEYS +hash_t* hash_create(size_t klen) + #else hash_t* hash_create() + #endif #endif { hash_t* ht = malloc(sizeof(hash_t)); @@ -117,7 +129,10 @@ hash_t* hash_create() #ifdef HASH_COPYKEYS ht->klen = klen; #endif - +#ifdef HASH_CALLBACKS + ht->f_free = f_free; + ht->arg = arg; +#endif if(!ht->array) { free(ht); @@ -132,7 +147,13 @@ void hash_free(hash_t* ht) hash_index_t* hi; for(hi = hash_first(ht); hi; hi = hash_next(hi)) + { +#ifdef HASH_CALLBACKS + if(hi->ths->val && ht->f_free) + ht->f_free(ht->arg, (void*)hi->ths->val); +#endif free(hi->ths); + } if(ht->array) free(ht->array); @@ -362,58 +383,64 @@ void* hash_get(hash_t* ht, const void *key, size_t klen) } #ifdef HASH_COPYKEYS -int hash_set(hash_t* ht, const void* key, const void* val) +int hash_set(hash_t* ht, const void* key, void* val) { hash_entry_t** hep = find_entry(ht, key, val); #else -int hash_set(hash_t* ht, const void* key, size_t klen, const void* val) +int hash_set(hash_t* ht, const void* key, size_t klen, void* val) { hash_entry_t** hep = find_entry(ht, key, klen, val); #endif if(hep && *hep) { - if(val) - { - /* replace entry */ - (*hep)->val = val; +#ifdef HASH_CALLBACKS + if((*hep)->val && (*hep)->val != val && ht->f_free) + ht->f_free(ht->arg, (void*)((*hep)->val)); +#endif + + /* replace entry */ + (*hep)->val = val; #ifdef HASH_TIMESTAMP - /* Update or set the timestamp */ - (*hep)->stamp = time(NULL); + /* Update or set the timestamp */ + (*hep)->stamp = time(NULL); #endif - /* check that the collision rate isn't too high */ - if(ht->count > ht->max) - { - if(!expand_array(ht)) - return 0; - } - - return 1; + /* check that the collision rate isn't too high */ + if(ht->count > ht->max) + { + if(!expand_array(ht)) + return 0; } + + return 1; } return 0; } #ifdef HASH_COPYKEYS -void hash_rem(hash_t* ht, const void* key) +void* hash_rem(hash_t* ht, const void* key) { hash_entry_t** hep = find_entry(ht, key, NULL); #else -void hash_rem(hash_t* ht, const void* key, size_t klen) +void* hash_rem(hash_t* ht, const void* key, size_t klen) { hash_entry_t** hep = find_entry(ht, key, klen, NULL); #endif + void* val = NULL; if(hep && *hep) { hash_entry_t* old = *hep; *hep = (*hep)->next; --ht->count; + val = (void*)old->val; free(old); } + + return val; } unsigned int hash_count(hash_t* ht) @@ -426,6 +453,7 @@ int hash_purge(hash_t* ht, time_t stamp) { hash_index_t* hi; int r = 0; + void* val; for(hi = hash_first(ht); hi; hi = hash_next(hi)) { @@ -433,9 +461,14 @@ int hash_purge(hash_t* ht, time_t stamp) { /* No need to check for errors as we're deleting */ #ifdef HASH_COPYKEYS - hash_rem(ht, KEY_DATA(hi->ths)); + val = hash_rem(ht, KEY_DATA(hi->ths)); #else - hash_rem(ht, hi->ths->key, hi->ths->klen); + val = hash_rem(ht, hi->ths->key, hi->ths->klen); +#endif + +#ifdef HASH_CALLBACKS + if(val && ht->f_free) + (ht->f_free)(ht->arg, val); #endif r++; @@ -446,17 +479,58 @@ int hash_purge(hash_t* ht, time_t stamp) } #ifdef HASH_COPYKEYS -void* hash_touch(hash_index_t* hi, const void** key); +void hash_touch(hash_t* ht, const void* key) { hash_entry_t** hep = find_entry(ht, key, NULL); #else -void* hash_touch(hash_index_t* hi, const void** key, size_t* klen); +void hash_touch(hash_t* ht, const void* key, size_t* klen) { hash_entry_t** hep = find_entry(ht, key, klen, NULL); #endif - if(he && *he) - ((*he)->stamp) = time(NULL); + if(hep && *hep) + ((*hep)->stamp) = time(NULL); +} + +int hash_bump(hash_t* ht) +{ + hash_index_t* hi; + void* key = NULL; + void* val = NULL; + time_t least = 0; +#ifndef HASH_COPYKEYS + size_t klen = 0; +#endif + + for(hi = hash_first(ht); hi; hi = hash_next(hi)) + { + if(least == 0 || hi->ths->stamp < least) + { + least = hi->ths->stamp; + key = KEY_DATA(hi->ths); +#ifndef HASH_COPYKEYS + klen = hi->this->klen; +#endif + } + } + + if(key) + { +#ifdef HASH_COPYKEYS + val = hash_rem(ht, key); +#else + val = hash_rem(ht, key, klen); +#endif + +#ifdef HASH_CALLBACKS + if(val && ht->f_free) + (ht->f_free)(ht->arg, (void*)val); +#endif + + return 1; + } + + return 0; } #endif /* HASH_TIMESTAMP */ diff --git a/common/hash.h b/common/hash.h index 9e8174c..2b22b64 100644 --- a/common/hash.h +++ b/common/hash.h @@ -22,7 +22,7 @@ #define __HASH_H__ /* - * Options to define. You need to build both this file and + * Features to define. You need to build both this file and * the corresponding hash.c file with whatever options you set here */ @@ -32,6 +32,9 @@ /* Keep key values internally */ #define HASH_COPYKEYS 1 +/* Hash callback functionality */ +#define HASH_CALLBACKS 1 + #ifdef __cplusplus extern "C" { @@ -49,16 +52,31 @@ typedef struct hash_t hash_t; /* Abstract type for scanning hash tables. */ typedef struct hash_index_t hash_index_t; +/* A callback during remove operations */ +#ifdef HASH_CALLBACKS + typedef void (*hash_free_val_t)(void* arg, void* val); +#endif + /* Create a hash table */ -#ifdef HASH_COPYKEYS - hash_t* hash_create(size_t klen); +#ifdef HASH_CALLBACKS + #ifdef HASH_COPYKEYS + hash_t* hash_create(size_t klen, hash_free_val_t f_free, void* arg); + #else + hash_t* hash_create(hash_free_val_t f_free, void* arg); + #endif #else - hash_t* hash_create(); -#endif + #ifdef HASH_COPYKEYS + hash_t* hash_create(size_t klen); + #else + hash_t* hash_create(); + #endif +#endif /* HASH_CALLBACKS */ + /* To release all resources for a hash table */ void hash_free(hash_t* ht); + /** * Associate a value with a key in a hash table. * @@ -70,11 +88,12 @@ void hash_free(hash_t* ht); * val must not be null */ #ifdef HASH_COPYKEYS - int hash_set(hash_t* ht, const void* key, const void* val); + int hash_set(hash_t* ht, const void* key, void* val); #else - int hash_set(hash_t* ht, const void* key, size_t klen, const void* val); + int hash_set(hash_t* ht, const void* key, size_t klen, void* val); #endif + /** * Remove a value and key form the hash table * @@ -83,11 +102,12 @@ void hash_free(hash_t* ht); * klen Length of the key. Can be HASH_KEY_STRING to use the string length */ #ifdef HASH_COPYKEYS - void hash_rem(hash_t* ht, const void* key); + void* hash_rem(hash_t* ht, const void* key); #else - void hash_rem(hash_t* ht, const void* key, size_t klen); + void* hash_rem(hash_t* ht, const void* key, size_t klen); #endif + /** * Look up the value associated with a key in a hash table. * @@ -103,6 +123,7 @@ void hash_free(hash_t* ht); void* hash_get(hash_t* ht, const void* key, size_t klen); #endif + /** * Start iterating over the entries in a hash table. * @@ -114,6 +135,7 @@ void hash_free(hash_t* ht); */ hash_index_t* hash_first(hash_t* ht); + /** * Continue iterating over the entries in a hash table. * @@ -124,6 +146,7 @@ hash_index_t* hash_first(hash_t* ht); */ hash_index_t* hash_next(hash_index_t* hi); + /** * Get the current entry's details from the iteration state. * @@ -141,19 +164,26 @@ hash_index_t* hash_next(hash_index_t* hi); void* hash_this(hash_index_t* hi, const void** key, size_t* klen); #endif + /** * Purge entries before a certain timestamp */ #ifdef HASH_TIMESTAMP + int hash_purge(hash_t* ht, time_t stamp); #ifdef HASH_COPYKEYS - void hash_touch(hash_index_t* hi, const void** key); + void hash_touch(hash_t* ht, const void* key); #else - void hash_touch(hash_index_t* hi, const void** key, size_t* klen); + void hash_touch(hash_t* ht, const void* key, size_t* klen); #endif + +/* Bumps the oldest out */ +int hash_bump(hash_t* ht); + #endif + /** * Get the number of key/value pairs in the hash table. * diff --git a/common/md5.c b/common/md5.c index c909bb3..2f92bec 100644 --- a/common/md5.c +++ b/common/md5.c @@ -18,6 +18,8 @@ #include <string.h> #include "md5.h" +void md5_transform(unsigned int buf[4], unsigned int const in[16]); + #ifdef BIG_ENDIAN void byteSwap(unsigned int *buf, unsigned words) @@ -39,7 +41,7 @@ byteSwap(unsigned int *buf, unsigned words) * initialization constants. */ void -MD5Init(struct MD5Context *ctx) +md5_init(md5_ctx_t* ctx) { ctx->buf[0] = 0x67452301; ctx->buf[1] = 0xefcdab89; @@ -55,9 +57,10 @@ MD5Init(struct MD5Context *ctx) * of bytes. */ void -MD5Update(struct MD5Context *ctx, const unsigned char* buf, unsigned len) +md5_update(md5_ctx_t* ctx, const void* b, unsigned len) { unsigned int t; + const unsigned char* buf = (const unsigned char*)b; /* Update byte count */ @@ -73,7 +76,7 @@ MD5Update(struct MD5Context *ctx, const unsigned char* buf, unsigned len) /* First chunk is an odd size */ memcpy((unsigned char *)ctx->in + 64 - t, buf, t); byteSwap(ctx->in, 16); - MD5Transform(ctx->buf, ctx->in); + md5_transform(ctx->buf, ctx->in); buf += t; len -= t; @@ -81,7 +84,7 @@ MD5Update(struct MD5Context *ctx, const unsigned char* buf, unsigned len) while (len >= 64) { memcpy(ctx->in, buf, 64); byteSwap(ctx->in, 16); - MD5Transform(ctx->buf, ctx->in); + md5_transform(ctx->buf, ctx->in); buf += 64; len -= 64; } @@ -95,7 +98,7 @@ MD5Update(struct MD5Context *ctx, const unsigned char* buf, unsigned len) * 1 0* (64-bit count of bits processed, MSB-first) */ void -MD5Final(unsigned char digest[16], struct MD5Context *ctx) +md5_final(unsigned char digest[16], md5_ctx_t* ctx) { int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */ unsigned char *p = (unsigned char *)ctx->in + count; @@ -109,7 +112,7 @@ MD5Final(unsigned char digest[16], struct MD5Context *ctx) if (count < 0) { /* Padding forces an extra block */ memset(p, 0, (unsigned int)count + 8); byteSwap(ctx->in, 16); - MD5Transform(ctx->buf, ctx->in); + md5_transform(ctx->buf, ctx->in); p = (unsigned char *)ctx->in; count = 56; } @@ -119,13 +122,30 @@ MD5Final(unsigned char digest[16], struct MD5Context *ctx) /* Append length in bits and transform */ ctx->in[14] = ctx->bytes[0] << 3; ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29; - MD5Transform(ctx->buf, ctx->in); + md5_transform(ctx->buf, ctx->in); byteSwap(ctx->buf, 4); memcpy(digest, ctx->buf, 16); memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ } +void +md5_string(unsigned char digest[MD5_LEN], const char* str) +{ + md5_ctx_t md5; + md5_init(&md5); + md5_update(&md5, str, strlen(str)); + md5_final(digest, &md5); +} + +int +md5_strcmp(unsigned char digest[MD5_LEN], const char* str) +{ + unsigned char other[MD5_LEN]; + md5_string(other, str); + return memcmp(digest, other, MD5_LEN); +} + /* The four core functions - F1 is optimized somewhat */ /* #define F1(x, y, z) (x & y | ~x & z) */ @@ -144,7 +164,7 @@ MD5Final(unsigned char digest[16], struct MD5Context *ctx) * the data and converts bytes into longwords for this routine. */ void -MD5Transform(unsigned int buf[4], unsigned int const in[16]) +md5_transform(unsigned int buf[4], unsigned int const in[16]) { register unsigned int a, b, c, d; diff --git a/common/md5.h b/common/md5.h index 62cbf2b..de431da 100644 --- a/common/md5.h +++ b/common/md5.h @@ -15,7 +15,7 @@ * will fill a supplied 16-byte array with the digest. * * Changed so as no longer to depend on Colin Plumb's `usual.h' - * header definitions; now uses stuff from dpkg's config.h + * header definitions * - Ian Jackson <ijackson@nyx.cs.du.edu>. * Still in the public domain. */ @@ -27,20 +27,22 @@ extern "C" { #ifndef MD5_H #define MD5_H +#define MD5_LEN 16 #define md5byte unsigned char -struct MD5Context { - unsigned int buf[4]; - unsigned int bytes[2]; - unsigned int in[16]; -}; - -typedef struct MD5Context MD5_CTX; +typedef struct md5_ctx +{ + unsigned int buf[4]; + unsigned int bytes[2]; + unsigned int in[16]; +} +md5_ctx_t; -void MD5Init(struct MD5Context *context); -void MD5Update(struct MD5Context *context, const md5byte* buf, unsigned len); -void MD5Final(unsigned char digest[16], struct MD5Context *context); -void MD5Transform(unsigned int buf[4], unsigned int const in[16]); +void md5_init(md5_ctx_t* context); +void md5_update(md5_ctx_t* context, const void* buf, unsigned len); +void md5_final(unsigned char digest[MD5_LEN], md5_ctx_t* context); +void md5_string(unsigned char digest[MD5_LEN], const char* str); +int md5_strcmp(unsigned char digest[MD5_LEN], const char* str); #ifdef __cplusplus } diff --git a/common/sha1.c b/common/sha1.c new file mode 100644 index 0000000..a40732e --- /dev/null +++ b/common/sha1.c @@ -0,0 +1,164 @@ +/* + * Fetched from ftp://ftp.funet.fi/pub/crypt/hash/sha/sha1.c on May 4, 1999 + * Modified by EPG May 6, 1999 + */ + +/* +SHA-1 in C +By Steve Reid <steve@edmweb.com> +100% Public Domain +*/ + +#include <string.h> +#include "sha1.h" + +typedef unsigned long ulong; +typedef unsigned char uchar; + +void SHA1Transform(ulong state[5], const uchar buffer[64]); + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#ifdef LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#else +#define blk0(i) block->l[i] +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void SHA1Transform(ulong state[5], const uchar buffer[64]) +{ + ulong a, b, c, d, e; + typedef union { + uchar c[64]; + ulong l[16]; + } CHAR64LONG16; + CHAR64LONG16* block; +#ifdef SHA1HANDSOFF + static uchar workspace[64]; + block = (CHAR64LONG16*)workspace; + memcpy(block, buffer, 64); +#else + block = (CHAR64LONG16*)buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +} + + +/* SHA1Init - Initialize new context */ + +void sha1_init(sha1_ctx_t* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void sha1_update(sha1_ctx_t* context, const void* d, unsigned int len) +{ + unsigned int i, j; + const uchar* data = (uchar*)d; + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; + context->count[1] += (len >> 29); + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void sha1_final(uchar digest[20], sha1_ctx_t* context) +{ + ulong i; + uchar finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (uchar)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + sha1_update(context, (uchar *)"\200", 1); + while ((context->count[0] & 504) != 448) { + sha1_update(context, (uchar *)"\0", 1); + } + sha1_update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (uchar) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + i = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +#ifdef SHA1HANDSOFF /* make SHA1Transform overwrite it's own static vars */ + SHA1Transform(context->state, context->buffer); +#endif +} + + diff --git a/common/sha1.h b/common/sha1.h new file mode 100644 index 0000000..37f734f --- /dev/null +++ b/common/sha1.h @@ -0,0 +1,30 @@ +#ifndef __SHA1_H__ +#define __SHA1_H__ + +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define SHA1_LEN 20 + +typedef struct sha1_ctx +{ + unsigned long state[5]; + unsigned long count[2]; + unsigned char buffer[64]; +} +sha1_ctx_t; + +void sha1_init(sha1_ctx_t* context); +void sha1_update(sha1_ctx_t* context, const void* data, unsigned int len); +void sha1_final(unsigned char digest[SHA1_LEN], sha1_ctx_t* context); + +#ifdef __cplusplus +} +#endif + +#endif // __SHA1_H__ diff --git a/configure.in b/configure.in index fbce603..2abbf10 100644 --- a/configure.in +++ b/configure.in @@ -51,14 +51,29 @@ AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET +# Debug mode +AC_ARG_ENABLE(debug, + AC_HELP_STRING([--enable-debug], + [Compile binaries in debug mode])) + +if test "$enable_debug" = "yes"; then + CFLAGS="$CFLAGS -g -O0 -D_DEBUG=1" + echo "enabling debug compile mode" +fi + +# TODO: Make SMB support an option +# TODO: Make LDAP support an option + # Check for libraries AC_CHECK_LIB([pthread], [pthread_create], , [ echo "ERROR: Pthread libraries required."; exit 1] ) -# Check for libraries AC_CHECK_LIB([crypt], [crypt], , [ echo "ERROR: Crypt library required."; exit 1] ) - + +AC_CHECK_LIB([ldap], [ldap_init], , + [ echo "ERROR: Open LDAP 2.x library required."; exit 1] ) + # Checks for header files. AC_HEADER_STDC AC_CHECK_HEADERS([unistd.h stdio.h stddef.h fcntl.h stdlib.h assert.h errno.h stdarg.h err.h string.h], , @@ -73,7 +88,6 @@ AC_CHECK_DECL(PTHREAD_MUTEX_ERRORCHECK_NP, [AC_DEFINE(HAVE_ERR_MUTEX, 1, "Error [echo "ERROR: Missing error checking mutex functionality in pthread.h"], [ #include <pthread.h> ])], [ #include <pthread.h> ]) - # Required Functions AC_CHECK_FUNCS([memset strerror malloc realloc getopt strchr tolower], , [echo "ERROR: Required function missing"; exit 1]) 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 <syslog.h> -#include <fcntl.h> -#include <unistd.h> - -#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 <syslog.h> + +/* 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 <sys/time.h> +#include <unistd.h> #include <syslog.h> /* LDAP library */ #include <ldap.h> +unsigned char g_ldap_secret[DIGEST_SECRET_LEN]; + /* ------------------------------------------------------------------------------- * Defaults and Constants */ -/* This needs to be the same as an MD5 hash length */ -#define LDAP_HASH_KEY_LEN 16 -#define LDAP_ESTABLISHED (void*)1 +#define BASIC_ESTABLISHED (void*)1 /* TODO: We need to support more password types */ #define LDAP_PW_CLEAR 0 @@ -57,18 +65,21 @@ typedef struct ldap_context const char* ha1_attr; /* Password for an encrypted Digest H(A1) */ const char* user; /* User to bind as */ const char* password; /* Password to bind with */ - const char* realm; /* The realm to use in authentication */ const char* dnmap; /* For mapping users to dns */ int port; /* Port to connect to LDAP server on */ int scope; /* Scope for filter */ + + const char* realm; /* The realm to use in authentication */ + const char* domains; /* Domains for which digest auth is valid */ + int dobind; /* Bind to do simple authentication */ - int pending_max; /* Maximum number of connections at once */ - int pending_timeout; /* Timeout for authentication (in seconds) */ - int ldap_timeout; /* Timeout for LDAP operations */ + int cache_max; /* Maximum number of connections at once */ + int ldap_max; /* Number of open connections allowed */ + int ldap_timeout; /* Maximum amount of time to dedicate to an ldap query */ /* Context ----------------------------------------------------------- */ - hash_t* pending; /* Pending connections */ - hash_t* established; /* Established connections */ + hash_t* cache; /* Some cached records or basic */ + LDAP** pool; /* Pool of available connections */ int pool_mark; /* Amount of connections allocated */ } @@ -76,11 +87,27 @@ ldap_context_t; /* The defaults for the context */ -static const ldap_defaults = -{XXXXX - NULL, NULL, "", "userPassword", NULL, NULL, NULL, "", NULL - LDAP_SCOPE_DEFAULT, 1, DEFAULT_PENDING_MAX, DEFAULT_PENDING_TIMEOUT, - 30, NULL, NULL, NULL +static const ldap_context_t ldap_defaults = +{ + NULL, /* servers */ + NULL, /* filter */ + "", /* base */ + "userPassword", /* pw_attr */ + NULL, /* ha1_attr */ + NULL, /* user */ + NULL, /* password */ + NULL, /* dnmap */ + 389, /* port */ + LDAP_SCOPE_DEFAULT, /* scope */ + "", /* realm */ + NULL, /* domains */ + 1, /* dobind */ + 1000, /* cache_max */ + 10, /* ldap_max */ + 30, /* ldap_timeout */ + NULL, /* cache */ + NULL, /* pool */ + 0 /* pool_mark */ }; @@ -88,45 +115,182 @@ static const ldap_defaults = * Internal Functions */ -static void make_digest_ha1(unsigned char* digest, const char* user, - const char* realm, const char* password) +static void free_hash_object(void* arg, void* val) +{ + if(val && val != BASIC_ESTABLISHED) + free(val); +} + +static int report_ldap(const char* msg, int code, ha_response_t* resp) +{ + if(!msg) + msg = "ldap error"; + + ha_messagex(LOG_ERR, "%s: %s", msg, ldap_err2string(code)); + + switch(code) + { + case LDAP_NO_MEMORY: + return HA_ERROR; + + default: + if(resp) + resp->code = HA_SERVER_ERROR; + + return HA_FALSE; + }; +} + +static digest_record_t* get_cached_digest(ldap_context_t* ctx, unsigned char* nonce) +{ + digest_record_t* rec; + + if(ctx->cache_max == 0) + return NULL; + + ha_lock(NULL); + + rec = (digest_record_t*)hash_get(ctx->cache, nonce); + + /* Just in case it's a basic :) */ + if(rec && rec != BASIC_ESTABLISHED) + hash_rem(ctx->cache, nonce); + + ha_unlock(NULL); + + ASSERT(!rec || memcmp(nonce, rec->nonce, DIGEST_NONCE_LEN) == 0); + return rec; +} + +static int have_cached_basic(ldap_context_t* ctx, unsigned char* key) +{ + int ret = 0; + + ha_lock(NULL); + + ret = (hash_get(ctx->cache, key) == BASIC_ESTABLISHED); + + ha_unlock(NULL); + + return ret; +} + +static int save_cached_digest(ldap_context_t* ctx, digest_record_t* rec) +{ + int r; + + if(ctx->cache_max == 0) + return HA_FALSE; + + ha_lock(NULL); + + while(hash_count(ctx->cache) >= ctx->cache_max) + hash_bump(ctx->cache); + + r = hash_set(ctx->cache, rec->nonce, rec); + + ha_unlock(NULL); + + if(!r) + { + ha_messagex(LOG_CRIT, "out of memory"); + return HA_ERROR; + } + + return HA_OK; +} + +static int add_cached_basic(ldap_context_t* ctx, unsigned char* key) +{ + int r; + + if(ctx->cache_max == 0) + return HA_FALSE; + + ha_lock(NULL); + + while(hash_count(ctx->cache) >= ctx->cache_max) + hash_bump(ctx->cache); + + r = hash_set(ctx->cache, key, BASIC_ESTABLISHED); + + ha_unlock(NULL); + + if(!r) + { + ha_messagex(LOG_CRIT, "out of memory"); + return HA_ERROR; + } + + return HA_OK; +} + +static const char* substitute_params(ldap_context_t* ctx, ha_buffer_t* buf, + const char* user, const char* str) { - struct MD5Context md5; - MD5_Init(&md5); - MD5_Update(&md5, user, strlen(user)); - MD5_Update(&md5, ":", 1); - MD5_Update(&md5, realm, strlen(realm)); - MD5_Update(&md5, ":", 1); - MD5_Update(&md5, password, strlen(pasword)); - MD5_Final(digest, &md5); + const char* t; + + /* This starts a new block to join */ + ha_bufcpy(buf, ""); + + while(str[0]) + { + t = strchr(str, '%'); + if(!t) + { + ha_bufjoin(buf); + ha_bufcpy(buf, str); + break; + } + + ha_bufjoin(buf); + ha_bufncpy(buf, str, t - str); + + t++; + + switch(t[0]) + { + case 'u': + ha_bufjoin(buf); + ha_bufcpy(buf, user); + t++; + break; + + case 'r': + ha_bufjoin(buf); + ha_bufcpy(buf, ctx->realm); + t++; + break; + }; + + str = t; + } + + return ha_bufdata(buf); } static const char* make_password_md5(ha_buffer_t* buf, const char* clearpw) { - struct MD5Context md5; + md5_ctx_t md5; unsigned char digest[MD5_LEN]; - MD5_Init(&md5); - MD5_Update(&md5, clearpw, strlen(clearpw)); - MD5_Final(digest, &md5); + md5_init(&md5); + md5_update(&md5, clearpw, strlen(clearpw)); + md5_final(digest, &md5); - ha_bufnext(buf); - ha_bufenc64(buf, digest, MD5_LEN); - return ha_bufdata(buf); + return ha_bufenc64(buf, digest, MD5_LEN); } static const char* make_password_sha(ha_buffer_t* buf, const char* clearpw) { - struct SHA1Context sha; + sha1_ctx_t sha; unsigned char digest[SHA1_LEN]; - SHA1_Init(&sha); - SHA1_Update(&sha, clearpw, strlen(clearpw)); - SHA1_Final(digest, &sha); + sha1_init(&sha); + sha1_update(&sha, clearpw, strlen(clearpw)); + sha1_final(digest, &sha); - ha_bufnext(buf); - ha_bufenc64(buf, digest, SHA1_LEN); - return ha_bufdata(buf); + return ha_bufenc64(buf, digest, SHA1_LEN); } static int parse_ldap_password(const char** password) @@ -154,7 +318,7 @@ static int parse_ldap_password(const char** password) pw++; /* scheme should end in a brace */ - if(pw != '}') + if(*pw != '}') return LDAP_PW_CLEAR; *password = pw + 1; @@ -162,8 +326,8 @@ static int parse_ldap_password(const char** password) /* find a scheme in our map */ for(i = 0; i < countof(kLDAPPWTypes); i++) { - if(strncasecmp(kLDAPSchemes[i].name, scheme, pw - scheme)) - return kLDAPSchemes[i].type; + if(strncasecmp(kLDAPPWTypes[i].name, scheme, pw - scheme)) + return kLDAPPWTypes[i].type; } return LDAP_PW_UNKNOWN; @@ -171,8 +335,6 @@ static int parse_ldap_password(const char** password) static const char* find_cleartext_password(ha_buffer_t* buf, const char** pws) { - ha_bufnext(buf); - for(; pws && *pws; pws++) { const char* pw = *pws; @@ -184,28 +346,23 @@ static const char* find_cleartext_password(ha_buffer_t* buf, const char** pws) return NULL; } - static int parse_ldap_ha1(ha_buffer_t* buf, struct berval* bv, unsigned char* ha1) { /* Raw binary */ if(bv->bv_len == MD5_LEN) { - memcpy(ha1, bv->bv_len, MD5_LEN); + memcpy(ha1, bv->bv_val, MD5_LEN); return HA_OK; } /* Hex encoded */ else if(bv->bv_len == (MD5_LEN * 2)) { - ha_bufnext(buf); - ha_bufdechex(buf, bv->bv_val, MD5_LEN * 2); + void* d = ha_bufdechex(buf, bv->bv_val, MD5_LEN); - if(!ha_bufdata(buf)) - return HA_ERROR; - - if(ha_buflen(buf) == MD5_LEN) + if(d) { - memcpy(rec->ha1, ha_bufdata(buf), MD5_LEN); + memcpy(ha1, d, MD5_LEN); return HA_OK; } } @@ -213,26 +370,22 @@ static int parse_ldap_ha1(ha_buffer_t* buf, struct berval* bv, unsigned char* ha /* B64 Encoded */ else { - ha_bufnext(buf); - ha_bufdec64(buf, (*pws)->bv_val, (*pws)->bv_len); + void* d = ha_bufdec64(buf, bv->bv_val, MD5_LEN); - if(!ha_bufdata(buf)) - return HA_ERROR; - - if(ha_buflen(buf) == MD5_LEN) + if(d) { - memcpy(rec->ha1, ha_bufdata(buf), MD5_LEN); + memcpy(ha1, ha_bufdata(buf), MD5_LEN); return HA_OK; } } - return HA_FALSE; + return ha_buferr(buf) ? HA_ERROR : HA_FALSE; } static int validate_ldap_password(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry, ha_buffer_t* buf, const char* user, const char* clearpw) { - const char** pws; + char** pws; const char* pw; const char* p; int type; @@ -295,7 +448,7 @@ static int validate_ldap_password(ldap_context_t* ctx, LDAP* ld, LDAPMessage* en } } - ldap_free_values(pws); + ldap_value_free(pws); } if(res == HA_FALSE && unknown) @@ -320,11 +473,11 @@ static int validate_ldap_ha1(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry, if(ha1s) { - make_digest_ha1(key, user, ctx->realm, clearpw); + digest_makeha1(key, user, ctx->realm, clearpw); for( ; *ha1s; ha1s++) { - r = parse_ldap_h1(buf, *ha1s, k); + r = parse_ldap_ha1(buf, *ha1s, k); if(r == HA_ERROR) { res = r; @@ -334,7 +487,7 @@ static int validate_ldap_ha1(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry, if(r == HA_FALSE) { if(first) - ha_messagex(LOG_ERROR, "LDAP contains invalid HA1 digest hash for user: %s", user); + ha_messagex(LOG_ERR, "LDAP contains invalid HA1 digest hash for user: %s", user); first = 0; continue; @@ -347,7 +500,7 @@ static int validate_ldap_ha1(ldap_context_t* ctx, LDAP* ld, LDAPMessage* entry, } } - ldap_free_values_len(ha1s); + ldap_value_free_len(ha1s); } return res; @@ -358,7 +511,7 @@ static LDAP* get_ldap_connection(ldap_context_t* ctx) LDAP* ld; int i, r; - for(i = 0; i < ctx->pending_max; i++) + for(i = 0; i < ctx->ldap_max; i++) { /* An open connection in the pool */ if(ctx->pool[i]) @@ -369,16 +522,16 @@ static LDAP* get_ldap_connection(ldap_context_t* ctx) } } - if(ctx->pool_mark >= ctx->pending_max) + if(ctx->pool_mark >= ctx->ldap_max) { - ha_messagex("too many open connections to LDAP"); + ha_messagex(LOG_ERR, "too many open connections to LDAP"); return NULL; } ld = ldap_init(ctx->servers, ctx->port); if(!ld) { - ha_message("couldn't initialize ldap connection"); + ha_message(LOG_ERR, "couldn't initialize ldap connection"); return NULL; } @@ -388,10 +541,12 @@ static LDAP* get_ldap_connection(ldap_context_t* ctx) ctx->password ? ctx->password : ""); if(r != LDAP_SUCCESS) { - report_ldap(r, NULL); + report_ldap("couldn't bind to LDAP server", r, NULL); ldap_unbind_s(ld); return NULL; } + + ctx->pool_mark++; } return ld; @@ -399,13 +554,15 @@ static LDAP* get_ldap_connection(ldap_context_t* ctx) static void save_ldap_connection(ldap_context_t* ctx, LDAP* ld) { - int i; + int i, e; if(!ld) return; + ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &e); + /* Make sure it's worth saving */ - switch(ld_errno(ld)) + switch(e) { case LDAP_SERVER_DOWN: case LDAP_LOCAL_ERROR: @@ -413,7 +570,7 @@ static void save_ldap_connection(ldap_context_t* ctx, LDAP* ld) break; default: - for(i = 0; i < ctx->pending_max; i++) + for(i = 0; i < ctx->ldap_max; i++) { /* An open connection in the pool */ if(!ctx->pool[i]) @@ -428,25 +585,88 @@ static void save_ldap_connection(ldap_context_t* ctx, LDAP* ld) }; if(ld != NULL) + { ldap_unbind_s(ld); + ctx->pool_mark--; + } +} + +static int retrieve_user_entry(ldap_context_t* ctx, ha_buffer_t* buf, LDAP* ld, + const char* user, const char** dn, + LDAPMessage** entry, LDAPMessage** result) +{ + struct timeval tv; + const char* filter; + const char* attrs[3]; + int r; + + if(ctx->filter) + { + /* Filters can also have %u and %r */ + filter = substitute_params(ctx, buf, user, ctx->filter); + if(!filter) + return HA_ERROR; + } + else + { + filter = "(objectClass=*)"; + } + + attrs[0] = ctx->dobind ? NULL : ctx->pw_attr; + attrs[1] = ctx->dobind ? NULL : ctx->ha1_attr; + attrs[2] = NULL; + + tv.tv_sec = ctx->ldap_timeout; + tv.tv_usec = 0; + + r = ldap_search_st(ld, *dn ? *dn : ctx->base, + *dn ? LDAP_SCOPE_BASE : ctx->scope, + filter, (char**)attrs, 0, &tv, result); + + if(r != LDAP_SUCCESS) + return report_ldap("couldn't search LDAP server", r, NULL); + + + /* Only one result should exist */ + switch(r = ldap_count_entries(ld, *result)) + { + case 1: + *entry = ldap_first_entry(ld, *result); + if(!(*dn)) + *dn = ldap_get_dn(ld, *entry); + return HA_OK; + + case 0: + ha_messagex(LOG_WARNING, "user not found in LDAP: %s", user); + break; + + default: + ha_messagex(LOG_WARNING, "more than one user found for filter: %s", filter); + break; + }; + + ldap_msgfree(*result); + return HA_FALSE; } -static int complete_digest_ha1(ldap_context_t* ctx, ha_digest_rec_t* rec, - const char* user) +static int complete_digest_ha1(ldap_context_t* ctx, digest_record_t* rec, + ha_buffer_t* buf, const char* user, int* code) { LDAP* ld = NULL; /* freed in finally */ LDAPMessage* results = NULL; /* freed in finally */ LDAPMessage* entry = NULL; /* no need to free */ - struct berval** pws; /* freed manually */ + struct berval** ha1s; /* freed manually */ + char** pws; int ret = HA_FALSE; - - /* Hash in the user name */ - ha_md5string(user, rec->userhash); - + const char* dn; + int r; ld = get_ldap_connection(ctx); if(!ld) + { + *code = HA_SERVER_ERROR; goto finally; + } /* * Discover the DN of the user. If there's a DN map string @@ -465,7 +685,7 @@ static int complete_digest_ha1(ldap_context_t* ctx, ha_digest_rec_t* rec, } /* Okay now we contact the LDAP server. */ - r = retrieve_user_entry(ctx, buf, user, &dn, &entry, &results); + r = retrieve_user_entry(ctx, buf, ld, user, &dn, &entry, &results); if(r != HA_OK) { ret = r; @@ -474,45 +694,45 @@ static int complete_digest_ha1(ldap_context_t* ctx, ha_digest_rec_t* rec, /* Figure out the users ha1 */ if(ctx->ha1_attr) - pws = ldap_get_values_len(ld, entry, ctx->ha1_attr); + ha1s = ldap_get_values_len(ld, entry, ctx->ha1_attr); - if(pws) + if(ha1s) { - if(*pws) + if(*ha1s) { - r = parse_ldap_ha1(buf, *pws, rec->ha1); + r = parse_ldap_ha1(buf, *ha1s, rec->ha1); if(r != HA_OK) { - ret = r + ret = r; if(ret != HA_FALSE) - ha_messagex(LOG_ERROR, "LDAP contains invalid HA1 digest hash for user: %s", user); + ha_messagex(LOG_ERR, "LDAP contains invalid HA1 digest hash for user: %s", user); } } - ldap_free_values_len(pws); + ldap_value_free_len(ha1s); goto finally; } /* If no ha1 set or none found, use password and make a HA1 */ - pws = ldap_get_values_len(ld, entry, ctx->pw_attr); + pws = ldap_get_values(ld, entry, ctx->pw_attr); if(pws) { /* Find a cleartext password */ - const char* t = find_cleartext_password(buf, pws); + const char* t = find_cleartext_password(buf, (const char**)pws); - ldap_free_values_len(pws); + ldap_value_free(pws); if(t) { - make_digest_ha1(rec->ha1, user, ctx->realm, t); + digest_makeha1(rec->ha1, user, ctx->realm, t); ret = HA_OK; goto finally; } } - ha_messagex(LOG_ERROR, "LDAP contains no cleartext password for user: %s", user); + ha_messagex(LOG_ERR, "LDAP contains no cleartext password for user: %s", user); finally: @@ -525,67 +745,10 @@ finally: return ret; } -static int retrieve_user_entry(ldap_context_t* ctx, buffer_t* buf, LDAP* ld, - const char* user, const char** dn, - LDAPMessage** entry, LDAPMessage** result) -{ - timeval tv; - const char* filter; - const char* attrs[3]; - - if(ctx->filter) - { - /* Filters can also have %u and %r */ - filter = substitute_params(ctx, buf, user, ctx->filter); - if(!filter) - return HA_ERROR; - } - else - { - filter = "(objectClass=*)"; - } - - attrs[0] = ctx->dobind ? NULL : ctx->pw_attr; - attrs[1] = ctx->dobind ? NULL : ctx->ha1_attr; - attrs[2] = NULL; - - tv.tv_sec = ctx->ldap_timeout; - tv.tv_usec = 0; - - r = ldap_search_st(ld, *dn ? *dn : ctx->base, - *dn ? LDAP_SCOPE_BASE : ctx->scope, - filter, attrs, 0, &tv, result); - - if(r != LDAP_SUCCESS) - return report_ldap(r, resp, &ret); - - - /* Only one result should exist */ - switch(r = ldap_count_entries(ld, *result)) - { - case 1: - *entry = ldap_first_entry(ld, *result); - if(!(*dn)) - *dn = ldap_get_dn(ld, entry); - return HA_OK; - - case 0: - ha_messagex(LOG_WARNING, "user not found in LDAP: %s", basic.user); - break; - - default: - ha_messagex(LOG_WARNING, "more than one user found for filter: %s", filter); - break; - }; - - ldap_msg_free(*result); - return HA_FALSE; -} - static int basic_ldap_response(ldap_context_t* ctx, const char* header, ha_response_t* resp, ha_buffer_t* buf) { - ha_basic_header_t basic; + basic_header_t basic; LDAP* ld = NULL; LDAPMessage* entry = NULL; LDAPMessage* results = NULL; @@ -596,23 +759,18 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, ASSERT(buf && header && resp && buf); - if(ha_parsebasic(header, buf, &basic) == HA_ERROR) + if(basic_parse(header, buf, &basic) == HA_ERROR) return HA_ERROR; /* Past this point we don't return directly */ /* Check and see if this connection is in the cache */ - ha_lock(NULL); - - if(hash_get(ctx->established, key) == BASIC_ESTABLISHED) - { - found = 1; - ret = HA_OK; - goto finally: - } - - ha_unlock(NULL); - + if(have_cached_basic(ctx, basic.key)) + { + found = 1; + ret = HA_OK; + goto finally; + } /* If we have a user name and password */ if(!basic.user || !basic.user[0] || @@ -620,7 +778,7 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, goto finally; - ld = get_ldap_connection(); + ld = get_ldap_connection(ctx); if(!ld) { resp->code = HA_SERVER_ERROR; @@ -662,7 +820,7 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, if(!ctx->dobind || !dn || ctx->filter) { - r = retrieve_user_entry(ctx, buf, basic.user, &dn, &entry, &results); + r = retrieve_user_entry(ctx, buf, ld, basic.user, &dn, &entry, &results); if(r != HA_OK) { ret = r; @@ -682,7 +840,7 @@ static int basic_ldap_response(ldap_context_t* ctx, const char* header, if(r == LDAP_INVALID_CREDENTIALS) ha_messagex(LOG_WARNING, "invalid login for: %s", basic.user); else - report_ldap(r, resp, &ret); + report_ldap("couldn't bind to LDAP server", r, resp); goto finally; } @@ -717,80 +875,90 @@ finally: if(resp->code == HA_SERVER_ACCEPT) { - resp->details = basic.user; + resp->detail = basic.user; /* We put this connection into the successful connections */ - if(!hash_set(ctx->established, basic.key, LDAP_ESTABLISHED)) - { - ha_messagex(LOG_CRIT, "out of memory"); - return HA_ERROR; - } + ret = add_cached_basic(ctx, basic.key); } return ret; } +static int digest_ldap_challenge(ldap_context_t* ctx, ha_response_t* resp, + ha_buffer_t* buf, int stale) +{ + unsigned char nonce[DIGEST_NONCE_LEN]; + const char* header; + + /* Generate an nonce */ + digest_makenonce(nonce, g_ldap_secret, NULL); + + /* Now generate a message to send */ + header = digest_challenge(buf, nonce, ctx->realm, ctx->domains, stale); + + if(!header) + return HA_ERROR; + + /* And append it nicely */ + resp->code = HA_SERVER_DECLINE; + ha_addheader(resp, "WWW-Authenticate", header); + + return HA_OK; +} static int digest_ldap_response(ldap_context_t* ctx, const char* header, - const char* method, const char* uri, - ha_response_t* resp, ha_buffer_t* buf) + const char* method, const char* uri, int timeout, + ha_response_t* resp, ha_buffer_t* buf) { - ha_digest_header_t dg; - digest_rec_t* rec = NULL; + unsigned char nonce[DIGEST_NONCE_LEN]; + digest_header_t dg; + digest_record_t* rec = NULL; + const char* t; + time_t expiry; int ret = HA_FALSE; int stale = 0; - int pending = 0; + int r; /* We use this below to send a default response */ resp->code = -1; - if(ha_parsedigest(header, buf, &rec) == HA_ERROR) + if(digest_parse(header, buf, &dg, nonce) == HA_ERROR) return HA_ERROR; - /* Lookup our digest context based on the nonce */ - if(!dg.nonce || strlen(dg.nonce) != DIGEST_NONCE_LEN) + r = digest_checknonce(nonce, g_ldap_secret, &expiry); + if(r != HA_OK) { - ha_messagex(LOG_WARNING, "digest response contains invalid nonce"); + if(r == HA_FALSE) + ha_messagex(LOG_WARNING, "digest response contains invalid nonce"); + + ret = r; goto finally; } - ha_lock(NULL); - - rec = (digest_rec_t*)hash_get(ctx->pending, dg.nonce) - if(rec) - { - pending = 1; - hash_rem(ctx->pending, dg.nonce); - } - - else - { - rec = (digest_rec_t*)hash_get(ctx->established, dg.nonce); - } + rec = get_cached_digest(ctx, nonce); - ha_unlock(NULL); - - /* - * If nothing was found for this nonce, then it might - * be a stale nonce. In any case prompt the client - * to reauthenticate. - */ - if(!rec) + /* Check to see if we're stale */ + if((expiry + timeout) <= time(NULL)) { stale = 1; goto finally; } - /* - * If we got a response from the pending table, then - * we need to lookup the user name and figure out - * who the dude is. - */ - if(pending) + if(!rec) { - ASSERT(rec); + /* + * If we're valid but don't have a record in the + * cache then complete the record properly. + */ + + rec = digest_makerec(nonce, dg.username); + if(!rec) + { + ret = HA_ERROR; + goto finally; + } - r = complete_digest_ha1(ctx, rec, dg->username); + r = complete_digest_ha1(ctx, rec, buf, dg.username, &(resp->code)); if(r != HA_OK) { ret = r; @@ -801,28 +969,35 @@ static int digest_ldap_response(ldap_context_t* ctx, const char* header, /* Increment our nonce count */ rec->nc++; - ret = ha_digestcheck(ctx->realm, method, uri, buf, &dg, rec); + ret = digest_check(ctx->realm, method, uri, buf, &dg, rec); if(ret == HA_OK) { resp->code = HA_SERVER_ACCEPT; - resp->details = dg->username; + resp->detail = dg.username; - /* Put the connection back into established */ + /* Figure out if we need a new nonce */ + if((expiry + (timeout - (timeout / 8))) < time(NULL)) + { + digest_makenonce(nonce, g_ldap_secret, NULL); + stale = 1; + } - ha_lock(NULL); + t = digest_respond(buf, &dg, rec, stale ? nonce : NULL); + if(!t) + { + ret = HA_ERROR; + goto finally; + } - if(hash_set(ctx->established, dg.nonce, rec)) - { - rec = NULL; - } - else - { - ha_messagex(LOG_CRIT, "out of memory"); - ret = HA_ERROR; - } + if(t[0]) + ha_addheader(resp, "Authentication-Info", t); - ha_unlock(NULL); + /* Put the connection into the cache */ + if(save_cached_digest(ctx, rec) == HA_ERROR) + ret = HA_ERROR; + else + rec = NULL; } finally: @@ -847,7 +1022,7 @@ finally: int ldap_config(ha_context_t* context, const char* name, const char* value) { - ldap_context_t* ctx = (ldap_context_t*)(context.data); + ldap_context_t* ctx = (ldap_context_t*)(context->data); if(strcmp(name, "ldapservers") == 0) { @@ -903,6 +1078,12 @@ int ldap_config(ha_context_t* context, const char* name, const char* value) return HA_OK; } + else if(strcmp(name, "digestdomains") == 0) + { + ctx->domains = value; + return HA_OK; + } + else if(strcmp(name, "ldapscope") == 0) { if(strcmp(value, "sub") == 0 || strcmp(value, "subtree") == 0) @@ -914,7 +1095,7 @@ int ldap_config(ha_context_t* context, const char* name, const char* value) else { - messagex(LOG_ERR, "invalid value for '%s' (must be 'sub', 'base' or 'one')", name); + ha_messagex(LOG_ERR, "invalid value for '%s' (must be 'sub', 'base' or 'one')", name); return HA_ERROR; } @@ -926,72 +1107,75 @@ int ldap_config(ha_context_t* context, const char* name, const char* value) return ha_confbool(name, value, &(ctx->dobind)); } - else if(strcmp(name, "pendingmax") == 0) + else if(strcmp(name, "ldapmax") == 0) { - return ha_confint(name, value, 1, 256, &(ctx->pending_max)); + return ha_confint(name, value, 1, 256, &(ctx->ldap_max)); } - else if(strcmp(name, "pendingtimeout") == 0) + else if(strcmp(name, "ldaptimeout") == 0) { - return ha_confint(name, value, 0, 86400, &(ctx->pending_timeout)); + return ha_confint(name, value, 0, 86400, &(ctx->ldap_timeout)); } - else if(strcmp(name, "ldaptimeout") == 0) + else if(strcmp(name, "cachemax") == 0) { - return ha_confint(name, value, 0, 86400, &(ctx->ldap_timeout)); + return ha_confint(name, value, 0, 0x7FFFFFFF, &(ctx->cache_max)); } return HA_FALSE; } -int ldap_initialize(ha_context_t* context) +int ldap_inithand(ha_context_t* context) { - /* No global initialization */ + /* Global initialization */ if(!context) - return HA_OK; - - ldap_context_t* ctx = (ldap_context_t*)(context.data); - - - /* Make sure there are some types of authentication we can do */ - if(!(context->types & (HA_TYPE_BASIC | HA_TYPE_DIGEST))) { - ha_messagex(LOG_ERR, "Digest module configured, but does not implement any " - "configured authentication type."); - return HA_ERROR; + return ha_genrandom(g_ldap_secret, DIGEST_SECRET_LEN); } - /* Check for mandatory configuration */ - if(!ctx->servers || (!ctx->dnmap || !ctx->filter)) + + /* Context specific initialization */ + else { - ha_messagex(LOG_ERR, "Digest LDAP configuration incomplete. " - "Must have LDAPServers and either LDAPFilter or LDAPDNMap."); - return HA_ERROR; - } + ldap_context_t* ctx = (ldap_context_t*)(context->data); + /* Make sure there are some types of authentication we can do */ + if(!(context->types & (HA_TYPE_BASIC | HA_TYPE_DIGEST))) + { + ha_messagex(LOG_ERR, "LDAP module configured, but does not implement any " + "configured authentication type."); + return HA_ERROR; + } - /* The hash tables */ - if(!(ctx->pending = hash_create(LDAP_HASH_KEY_LEN)) || - !(ctx->established = hash_create(LDAP_HASH_KEY_LEN))) - { - ha_messagex(LOG_CRIT, "out of memory"); - return HA_ERROR; - } + /* Check for mandatory configuration */ + if(!ctx->servers || (!ctx->dnmap || !ctx->filter)) + { + ha_messagex(LOG_ERR, "Digest LDAP configuration incomplete. " + "Must have LDAPServers and either LDAPFilter or LDAPDNMap."); + return HA_ERROR; + } - /* - * Our connection pool. It's the size of our maximum - * amount of pending connections as that's the max - * we'd be able to use at a time anyway. - */ - XXXXX - ctx->pool = (LDAP**)malloc(sizeof(LDAP*) * ctx->pending_max); - if(!ctx->pool) - { - ha_messagex(LOG_CRIT, "out of memory"); - return HA_ERROR; - } + /* The cache for digest records and basic */ + if(!(ctx->cache = hash_create(MD5_LEN, free_hash_object, NULL))) + { + ha_messagex(LOG_CRIT, "out of memory"); + return HA_ERROR; + } - memset(ctx->pool, 0, sizeof(LDAP*) * ctx->pending_max); + /* + * Our connection pool. It's the size of our maximum + * amount of pending connections as that's the max + * we'd be able to use at a time anyway. + */ + ctx->pool = (LDAP**)malloc(sizeof(LDAP*) * ctx->ldap_max); + if(!ctx->pool) + { + ha_messagex(LOG_CRIT, "out of memory"); + return HA_ERROR; + } + + memset(ctx->pool, 0, sizeof(LDAP*) * ctx->ldap_max); + } return HA_OK; } @@ -1001,17 +1185,15 @@ void ldap_destroy(ha_context_t* context) int i; if(!context) - return HA_OK; + return; - ldap_context_t* ctx = (digest_ldap_context_t*)(context.data); + ldap_context_t* ctx = (ldap_context_t*)(context->data); /* Note: We don't need to be thread safe here anymore */ - hash_free(ctx->pending); - hash_free(ctx->established); + hash_free(ctx->cache); - XXXXX /* Close any connections we have open */ - for(i = 0; i < ctx->pending_max; i++) + for(i = 0; i < ctx->ldap_max; i++) { if(ctx->pool[i]) ldap_unbind_s(ctx->pool[i]); @@ -1032,14 +1214,8 @@ int ldap_process(ha_context_t* context, ha_request_t* req, ha_lock(NULL); - XXXXXX - /* - * Purge out stale connection stuff. This includes - * authenticated connections which have expired as - * well as half open connections which expire. - */ - hash_purge(ctx->pending, t - ctx->pending_timeout); - hash_purge(ctx->established, t - ctx->timeout); + /* Purge out stale connection stuff. */ + hash_purge(ctx->cache, t - context->timeout); ha_unlock(NULL); @@ -1049,19 +1225,21 @@ int ldap_process(ha_context_t* context, ha_request_t* req, /* Check the headers and see if we got a response thingy */ - if(ctx->types & HA_TYPE_DIGEST) + if(context->types & HA_TYPE_DIGEST) { header = ha_getheader(req, "Authorization", HA_PREFIX_DIGEST); if(header) { - ret = digest_ldap_response(ctx, header, resp, buf); + ret = digest_ldap_response(ctx, header, req->args[AUTH_ARG_METHOD], + req->args[AUTH_ARG_URI], context->timeout, + resp, buf); if(ret == HA_ERROR) return ret; } } /* Or a basic authentication */ - if(!header && ctx->types & HA_TYPE_BASIC) + if(!header && context->types & HA_TYPE_BASIC) { header = ha_getheader(req, "Authorization", HA_PREFIX_BASIC); if(header) @@ -1078,17 +1256,19 @@ int ldap_process(ha_context_t* context, ha_request_t* req, { resp->code = HA_SERVER_DECLINE; - if(ctx->types & HA_TYPE_DIGEST) + if(context->types & HA_TYPE_DIGEST) { ret = digest_ldap_challenge(ctx, resp, buf, 0); if(ret == HA_ERROR) return ret; } - if(ctx->types & HA_TYPE_BASIC) + if(context->types & HA_TYPE_BASIC) { - ha_bufnext(buf); - ha_bufcat(buf, "BASIC realm=\"", ctx->realm , "\"", NULL); + ha_bufmcat(buf, "BASIC realm=\"", ctx->realm , "\"", NULL); + + if(ha_buferr(buf)) + return HA_ERROR; ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf)); } @@ -1102,10 +1282,10 @@ int ldap_process(ha_context_t* context, ha_request_t* req, * Handler Definition */ -ha_handler_t digest_ldap_handler = +ha_handler_t ldap_handler = { "LDAP", /* The type */ - ldap_initialize, /* Initialization function */ + ldap_inithand, /* Initialization function */ ldap_destroy, /* Uninitialization routine */ ldap_config, /* Config routine */ ldap_process, /* Processing routine */ 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 <syslog.h> #include <err.h> +#include <stdio.h> +#include <fcntl.h> 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); @@ -201,421 +203,360 @@ 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 <syslog.h> @@ -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 <syslog.h> +#include <fcntl.h> +#include <unistd.h> + +unsigned char g_simple_secret[DIGEST_SECRET_LEN]; + +#define SIMPLE_MAXLINE 256 +#define BASIC_ESTABLISHED (void*)1 + +/* ------------------------------------------------------------------------------- + * Structures + */ + +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__ */ |