From 0cb3f6098d959479a96c26a92d91becc2110b30d Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Wed, 11 Jun 2008 21:48:27 +0000 Subject: Support getting groups from the server and limiting access based on LDAP groups. See #112 --- ChangeLog | 5 + apache2x/mod_httpauth.c | 323 ++++++++++++++++------ common/buffer.c | 4 +- common/compat.c | 1 + common/sock_any.c | 1 + common/stringx.c | 201 +++++++++++++- common/stringx.h | 9 + configure.in | 24 +- daemon/bd.c | 679 +++++++++++++++++++++++++--------------------- daemon/bd.h | 9 +- daemon/digest.c | 45 ++- daemon/digest.h | 10 +- daemon/dummy.c | 8 +- daemon/httpauthd.c | 22 +- daemon/httpauthd.h | 7 +- daemon/ldap.c | 98 ++++++- daemon/misc.c | 6 +- daemon/mysql.c | 42 +-- daemon/ntlmssp.c | 2 +- daemon/pgsql.c | 26 +- daemon/simple.c | 14 +- doc/httpauthd.conf.5 | 5 + tests/Makefile.am | 6 +- tests/unit-test-ntlmssp.c | 1 - tests/unit-test-stringx.c | 105 +++++++ tools/mkha1.c | 5 +- 26 files changed, 1160 insertions(+), 498 deletions(-) create mode 100644 tests/unit-test-stringx.c diff --git a/ChangeLog b/ChangeLog index d71c1d1..c35abaf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +0.9.3 ??? + - Support sending access groups to mod_httpauth apache2x module. + - Support retrieving LDAP access groups for users. + - Build warning fixes. + 0.9.2 [22-05-2008] - Authenticate sub requests properly in the apache module. diff --git a/apache2x/mod_httpauth.c b/apache2x/mod_httpauth.c index 3e11750..d6697ae 100644 --- a/apache2x/mod_httpauth.c +++ b/apache2x/mod_httpauth.c @@ -67,10 +67,17 @@ typedef struct httpauth_context int types; const char* handler; const char* domain; + char* needed_groups; + int alloced_groups; apr_pool_t* child_pool; } httpauth_context_t; +typedef struct httpauth_request { + const char *user; + const char *groups; +} httpauth_request_t; + /* TODO: Support proxy authentication properly */ #define AUTH_PREFIX_BASIC "Basic" @@ -98,6 +105,8 @@ static void* httpauth_dir_config(apr_pool_t* p, char* dir) ctx->socket = -1; ctx->types = 0xFFFFFFFF; ctx->child_pool = p; + ctx->needed_groups = NULL; + ctx->alloced_groups = 0; return ctx; } @@ -184,6 +193,8 @@ void disconnect_socket(httpauth_context_t* ctx, server_rec* s) cleanup_socket); close(ctx->socket); ctx->socket = -1; + if (ctx->needed_groups) + ctx->needed_groups[0] = 0; } } @@ -223,7 +234,7 @@ void read_junk(httpauth_context_t* ctx, request_rec* r) int read_line(httpauth_context_t* ctx, request_rec* r, char** line) { int l; - int al = 128; + int al = 256; char* t; const char* e; @@ -300,7 +311,8 @@ int read_line(httpauth_context_t* ctx, request_rec* r, char** line) } int read_response(httpauth_context_t* ctx, request_rec* r, - int* code, int* ccode, char** details) + int* code, int* ccode, char** details, + int return_errors) { int c; char* line; @@ -328,7 +340,7 @@ int read_response(httpauth_context_t* ctx, request_rec* r, if(code) *code = c; - if(c >= 400) + if(c >= 400 && !return_errors) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "httpauth: received error from httpauthd: %d %s", c, line); @@ -357,7 +369,9 @@ int read_response(httpauth_context_t* ctx, request_rec* r, return 0; } -int read_copy_headers(httpauth_context_t* ctx, int ccode, request_rec* r) +static int +read_process_headers(httpauth_context_t* ctx, int ccode, + request_rec* r, char **groups) { char* line; const char* name; @@ -445,6 +459,12 @@ int read_copy_headers(httpauth_context_t* ctx, int ccode, request_rec* r) name = "Proxy-Authentication-Info"; } + else if (strcasecmp(name, "X-HttpAuth-Groups") == 0) + { + if (groups && line) + *groups = line; + } + c++; apr_table_addn(headers, name, line); } @@ -545,7 +565,7 @@ int connect_httpauth(httpauth_context_t* ctx, request_rec* r) if(connect_socket(ctx, r) == -1) goto finally; - if(read_response(ctx, r, &code, NULL, &details) == -1) + if(read_response(ctx, r, &code, NULL, &details, 0) == -1) goto finally; if(code != 100) @@ -576,13 +596,13 @@ int connect_httpauth(httpauth_context_t* ctx, request_rec* r) ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "httpauth: sent handler to daemon: %s", t); - if(read_response(ctx, r, &code, NULL, NULL) == -1) + if(read_response(ctx, r, &code, NULL, NULL, 0) == -1) goto finally; if(code != 202) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "httpauth: protocol error (Expected 202, got %d)", code); + "httpauth: protocol error: couldn't send handler to daemon (Expected 202, got %d)", code); goto finally; } } @@ -598,13 +618,13 @@ int connect_httpauth(httpauth_context_t* ctx, request_rec* r) ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "httpauth: sent domains to daemon: %s", t); - if(read_response(ctx, r, &code, NULL, NULL) == -1) + if(read_response(ctx, r, &code, NULL, NULL, 0) == -1) goto finally; if(code != 202) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "httpauth: protocol error (Expected 202, got %d)", code); + "httpauth: protocol error: couldn't send domain to daemon (Expected 202, got %d)", code); goto finally; } } @@ -711,12 +731,88 @@ int write_request(httpauth_context_t* ctx, request_rec* r) return write_data(ctx, r->server, "\n"); } +static int +write_needed_groups(httpauth_context_t *ctx, request_rec *r) +{ + const apr_array_header_t *reqs_arr = ap_requires(r); + require_line *reqs; + const char *groups = NULL; + const char *text; + char *word; + register int x; + int m = r->method_number; + int code, len; + + if(reqs_arr) { + reqs = (require_line*)reqs_arr->elts; + for (x = 0; x < reqs_arr->nelts; x++) { + if (!(reqs[x].method_mask & (AP_METHOD_BIT << m))) + continue; + + text = reqs[x].requirement; + word = ap_getword_white (r->pool, &text); + + /* Append all groups to the string */ + if (strcmp (word, "group") == 0 && text && text[0]) { + if (!groups) + groups = text; + else + groups = apr_pstrcat (r->pool, text, " ", groups, NULL); + } + } + } + + /* No groups, no need to send */ + if (!groups && !ctx->needed_groups) + return 0; + + if (!groups) + groups = ""; + + /* Equal groups, no need to send */ + if (ctx->needed_groups && strcmp (groups, ctx->needed_groups) == 0) + return 0; + + /* Groups changed, send to daemon */ + text = apr_pstrcat (r->pool, "SET Groups ", groups, "\n", NULL); + + if (write_data (ctx, r->server, text) < 0) + return -1; + + ap_log_rerror (APLOG_MARK, APLOG_DEBUG, 0, r, + "httpauth: sent groups to daemon: %s", text); + + if (read_response (ctx, r, &code, NULL, NULL, 1) < 0) + return -1; + + if (code != 202) { + ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, r, + "httpauth: couldn't send groups to daemon (Expected 202, got %d)", code); + /* Older versions of the daemon did not support the 'SET Groups' command */ + if (code != 400) + return -1; + } + + /* Save away the groups for next time */ + len = strlen (groups); + if (len >= ctx->alloced_groups) { + if (len < 512) + len = 512; + ctx->needed_groups = (char*)apr_pcalloc (ctx->child_pool, len * 2); + ctx->alloced_groups = len * 2; + } + strcpy (ctx->needed_groups, groups); + return 0; +} + static int httpauth_authenticate(request_rec* r) { httpauth_context_t* ctx; + httpauth_request_t* hreq; const char* authtype; int code = 0; int ccode = 0; + char *groups = NULL; char* details = NULL; request_rec* mainreq; int retried = 0; @@ -739,14 +835,13 @@ static int httpauth_authenticate(request_rec* r) while(mainreq->prev != NULL) mainreq = mainreq->prev; - /* Check if we've already authenticated this request */ - details = ap_get_module_config(mainreq->request_config, &httpauth_module); - if(details) - { - r->user = apr_pstrdup(r->pool, details); - r->ap_auth_type = HTTPAUTH_AUTHTYPE; - return OK; - } + /* Check if we've already authenticated this request */ + hreq = ap_get_module_config (mainreq->request_config, &httpauth_module); + if (hreq) { + r->user = apr_pstrdup (r->pool, hreq->user); + r->ap_auth_type = HTTPAUTH_AUTHTYPE; + return OK; + } /* For jumping to when a connection has been closed */ retry: @@ -760,9 +855,10 @@ retry: /* Make sure we're starting on a clean slate */ read_junk(ctx, r); - /* Send off a request */ - if(write_request(ctx, r) == -1 || - read_response(ctx, r, &code, &ccode, &details) == -1) + /* Send off a request, along with groups, and read response */ + if(write_needed_groups(ctx, r) == -1 || + write_request(ctx, r) == -1 || + read_response(ctx, r, &code, &ccode, &details, 0) == -1) { /* * If our connection was closed by httpauthd then this @@ -788,89 +884,136 @@ retry: if(code != 200) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "httpauth: protocol error: unexpected code: %d", code); + "httpauth: protocol error: unexpected code while authenticating: %d", code); return HTTP_INTERNAL_SERVER_ERROR; } /* Copy over other headers */ - if(read_copy_headers(ctx, ccode, r) == -1) + if(read_process_headers(ctx, ccode, r, &groups) == -1) return HTTP_INTERNAL_SERVER_ERROR; - if(ccode == 200) - { - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, - "httpauth: successful authentication for user: %s", details); + if (ccode == 200) { + ap_log_rerror (APLOG_MARK, APLOG_INFO, 0, r, + "httpauth: successful authentication for user: %s", details); - r->user = apr_pstrdup(r->pool, details); - r->ap_auth_type = HTTPAUTH_AUTHTYPE; + r->user = apr_pstrdup (r->pool, details); + r->ap_auth_type = HTTPAUTH_AUTHTYPE; - /* Mark request as successfully authenticated */ - ap_set_module_config(r->request_config, &httpauth_module, details); - return OK; - } + hreq = (httpauth_request_t*)apr_pcalloc (r->pool, sizeof (*hreq)); + hreq->user = details; + hreq->groups = groups; - return ccode; + /* Mark request as successfully authenticated */ + ap_set_module_config (r->request_config, &httpauth_module, hreq); + return OK; + } + + return ccode; } -static int httpauth_access(request_rec *r) +static const char* +find_word_quoted (const char *all, const char *word) { - httpauth_context_t* ctx; - const char* authtype; - char *user = r->user; - int m = r->method_number; - int method_restricted = 0; - register int x; - const char *t, *w; - const apr_array_header_t *reqs_arr = ap_requires(r); - require_line *reqs; - - /* Make sure it's for us */ - if(!(authtype = ap_auth_type(r)) || strcasecmp(HTTPAUTH_AUTHTYPE, authtype) != 0) - return DECLINED; - - ctx = (httpauth_context_t*)ap_get_module_config(r->per_dir_config, - &httpauth_module); - - if (!reqs_arr) - return OK; - reqs = (require_line *)reqs_arr->elts; - - for (x = 0; x < reqs_arr->nelts; x++) - { - if (!(reqs[x].method_mask & (AP_METHOD_BIT << m))) - continue; - - method_restricted = 1; - - t = reqs[x].requirement; - w = ap_getword_white(r->pool, &t); - if(!strcmp(w, "valid-user")) - return OK; - else if(!strcmp(w, "user")) - { - while (t[0]) - { - w = ap_getword_conf(r->pool, &t); - if (!strcmp(user, w)) { - return OK; - } - } - } - else - { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "access to %s failed, reason: unknown require " - "directive:\"%s\"", r->uri, reqs[x].requirement); - } - } - - if (!method_restricted) - return OK; + const char *at; + char before, after; + size_t len = strlen (word); + + at = all; + for (;;) { + at = strstr (at, word); + if (!at) + return NULL; + + before = (at == all) ? 0 : *(at - 1); + after = *(at + len); + + /* Beginning and end of a space delimited word */ + if ((!before || isspace (before)) && + (!after || isspace (after))) { + return at; + } + + /* Beginning and end of a quoted word */ + if ((before == '"' || before == '\'') && after == before) + return at; + + at += len; + } +} - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "access to %s failed, reason: user %s not allowed access", - r->uri, user); - return HTTP_UNAUTHORIZED; +static int +httpauth_access(request_rec *r) +{ + httpauth_context_t *ctx; + httpauth_request_t *hreq; + const char* authtype; + char *user = r->user; + int m = r->method_number; + int method_restricted = 0; + register int x; + const char *text, *word, *at; + const apr_array_header_t *reqs_arr = ap_requires (r); + require_line *reqs; + + /* Make sure it's for us */ + if (!(authtype = ap_auth_type (r)) || strcasecmp (HTTPAUTH_AUTHTYPE, authtype) != 0) + return DECLINED; + + if (!reqs_arr) + return OK; + + /* Dig out our configuration */ + ctx = ap_get_module_config (r->per_dir_config, &httpauth_module); + hreq = ap_get_module_config (r->request_config, &httpauth_module); + reqs = (require_line *)reqs_arr->elts; + + for (x = 0; x < reqs_arr->nelts; x++) { + if (!(reqs[x].method_mask & (AP_METHOD_BIT << m))) + continue; + + method_restricted = 1; + + text = reqs[x].requirement; + word = ap_getword_white(r->pool, &text); + + /* Any valid user */ + if (strcmp (word, "valid-user") == 0) { + return OK; + + /* Specific listed users */ + } else if (strcmp (word, "user") == 0) { + while (text[0]) { + word = ap_getword_conf (r->pool, &text); + if (strcmp (user, word) == 0) { + return OK; + } + } + + /* Specific groups */ + } else if (strcmp (word, "group") == 0) { + if (hreq && hreq->groups) { + while (text[0]) { + word = ap_getword_conf (r->pool, &text); + if (find_word_quoted (hreq->groups, word)) + return OK; + } + } + + /* What is this? */ + } else { + ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, r, + "access to %s failed, reason: unknown require " + "directive:\"%s\"", r->uri, reqs[x].requirement); + } + } + + if (!method_restricted) + return OK; + + ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, r, + "access to %s failed, reason: user %s not allowed access", + r->uri, user); + return HTTP_UNAUTHORIZED; } static void register_hooks(apr_pool_t *p) diff --git a/common/buffer.c b/common/buffer.c index 25494c9..088c2df 100644 --- a/common/buffer.c +++ b/common/buffer.c @@ -40,6 +40,8 @@ #include #include #include +#include +#include /* ----------------------------------------------------------------------- * Memory Buffer @@ -368,7 +370,7 @@ char* ha_bufparseline(ha_buffer_t* buf, int trim) if(trim) { /* Knock out any previous whitespace */ - while(buf->_pp < buf->_rp && isblank(*(buf->_pp))) + while(buf->_pp < buf->_rp && strchr (" \t", *(buf->_pp))) buf->_pp++; } diff --git a/common/compat.c b/common/compat.c index 081a7ee..3ab7ae2 100644 --- a/common/compat.c +++ b/common/compat.c @@ -33,6 +33,7 @@ #include "compat.h" #include +#include #ifndef HAVE_REALLOCF diff --git a/common/sock_any.c b/common/sock_any.c index 714154a..5972e62 100644 --- a/common/sock_any.c +++ b/common/sock_any.c @@ -44,6 +44,7 @@ #include #include #include +#include #include "sock_any.h" diff --git a/common/stringx.c b/common/stringx.c index 2246cf8..24cf9d1 100644 --- a/common/stringx.c +++ b/common/stringx.c @@ -36,9 +36,16 @@ * */ -#include #include "stringx.h" +#include +#include +#include +#include +#include + +#define WHITESPACE " \t\r\n\v" + const char* trim_start(const char* data) { while(*data && isspace(*data)) @@ -64,3 +71,195 @@ char* trim_space(char* data) data = (char*)trim_start(data); return trim_end(data); } + +static int +is_escaped (const char *string, const char *at) +{ + int escaped = 0; + while (at > string) { + at = at - 1; + if (*at != '\\') + break; + escaped = !escaped; + } + return escaped; +} + +void +str_unescape (char *str) +{ + int len = strlen (str); + char *at; + + while (len > 0) { + at = strchr (str, '\\'); + if (!at) + break; + + len -= at - str; + str = at + 1; + --len; + memmove (at, str, len); + at[len] = 0; + } +} + +char** +str_array_parse_quoted (const char *data) +{ + char **array; + const char *at; + char quote; + int n; + + assert (data); + + array = str_array_create (NULL); + + for (n = 0; 1; ++n) { + quote = 0; + + /* Strip all leading blanks */ + while (data && data[0] && strchr (WHITESPACE, data[0])) + ++data; + + if (!data || !data[0]) + break; + + /* See if the next character is a quote */ + if (data[0] == '\'' || data[0] == '\"') { + quote = data[0]; + ++data; + } + + if (quote) { + at = data; + do { + ++data; + data = strchr (data, quote); + } while (data && is_escaped (at, data)); + + if (!data) { + array = str_array_append (array, at); + } else { + array = str_array_appendn (array, at, data - at); + ++data; + } + + str_unescape (array[n]); + } else { + at = data; + do { + ++data; + data = data + strcspn (data, WHITESPACE); + } while (*data && is_escaped (at, data)); + + array = str_array_appendn (array, at, data - at); + str_unescape (array[n]); + } + + if (!array) + break; + } + + return array; +} + +char** +str_array_create (const char *first, ...) +{ + char **array; + char *value; + va_list va; + + array = calloc (32, sizeof (char*)); + if (!array) + return NULL; + + if (first) { + array[0] = strdup (first); + if (!array[0]) { + free (array); + return NULL; + } + + va_start (va, first); + while ((value = va_arg (va, char*)) != NULL) { + array = str_array_append (array, value); + if (!array) + break; + } + va_end (va); + } + + return array; +} + +unsigned int +str_array_length (char **array) +{ + unsigned int length = 0; + + assert (array); + + while (*array) { + ++length; + ++array; + } + return length; +} + +char** +str_array_append (char **array, const char *next) +{ + assert (array); + assert (next); + + return str_array_appendn (array, next, strlen (next)); +} + +char** +str_array_appendn (char **array, const char *next, unsigned int len) +{ + char **narray; + int num; + + assert (array); + assert (next || !len); + + num = str_array_length (array); + + /* + * Actually because of intelligent libc this is not + * as inefficient as it looks. + */ + narray = realloc (array, (num + 2) * sizeof (char*)); + if (!narray) { + str_array_free (array); + return NULL; + } + + narray[num] = malloc (len + 1); + if (!narray[num]) { + str_array_free (narray); + return NULL; + } + memcpy (narray[num], next, len); + narray[num][len] = 0; + narray[num + 1] = NULL; + + return narray; +} + +void +str_array_free (char **array) +{ + char **a; + + if (!array); + return; + + for (a = array; *a; ++a) + free (*a); + free (array); +} diff --git a/common/stringx.h b/common/stringx.h index c9a2832..4216d4f 100644 --- a/common/stringx.h +++ b/common/stringx.h @@ -43,4 +43,13 @@ const char* trim_start(const char* data); char* trim_end(char* data); char* trim_space(char* data); +void str_unescape (char *str); + +unsigned int str_array_length (char **array); +char** str_array_parse_quoted (const char *data); +char** str_array_create (const char *first, ...); +char** str_array_append (char **array, const char *next); +char** str_array_appendn (char **array, const char *next, unsigned int len); +void str_array_free (char **array); + #endif /* __STRINGX_H__ */ diff --git a/configure.in b/configure.in index 36ffd9e..d57045e 100644 --- a/configure.in +++ b/configure.in @@ -51,17 +51,6 @@ 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" - AC_DEFINE_UNQUOTED(_DEBUG, 1, [In debug mode]) - echo "enabling debug compile mode" -fi - # Check for the various options AC_ARG_WITH(ldap, [ --with-ldap with LDAP support]) AC_ARG_WITH(pgsql, [ --with-pgsql with Postgres support]) @@ -130,7 +119,7 @@ if test -n "$with_pgsql"; then AC_CHECK_LIB([pq], [PQexec], , [ echo "ERROR: Postgres libpq library required."; exit 1] ) - AC_CHECK_HEADERS(libpq-fe.h) + AC_CHECK_HEADERS(libpq-fe.h, postgresql/libpq-fe.h) AC_DEFINE_UNQUOTED(WITH_PGSQL, 1, [With PGSQL Support] ) fi @@ -206,6 +195,17 @@ AC_SUBST(APACHECTL) AC_DEFINE_UNQUOTED(CONF_PREFIX, "`eval echo ${sysconfdir}`", [Installation Prefix] ) +# 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 -Wall -Werror" + AC_DEFINE_UNQUOTED(_DEBUG, 1, [In debug mode]) + echo "enabling debug compile mode" +fi + AC_CONFIG_FILES([Makefile daemon/Makefile doc/Makefile diff --git a/daemon/bd.c b/daemon/bd.c index e11a56b..eb0bec0 100644 --- a/daemon/bd.c +++ b/daemon/bd.c @@ -29,6 +29,7 @@ #include "basic.h" #include "md5.h" #include "bd.h" +#include "stringx.h" static unsigned char g_digest_secret[DIGEST_SECRET_LEN]; @@ -38,193 +39,277 @@ static unsigned char g_digest_secret[DIGEST_SECRET_LEN]; * Defaults and Constants */ -#define BASIC_ESTABLISHED (void*)1 +enum { + RECORD_TYPE_BASIC, + RECORD_TYPE_DIGEST +}; /* Kept by the us for validating the client */ -typedef struct digest_record -{ - unsigned char nonce[DIGEST_NONCE_LEN]; - unsigned char userhash[MD5_LEN]; - unsigned char ha1[MD5_LEN]; - unsigned int nc; -} -digest_record_t; +typedef struct bd_record { + int type; + + /* Used for Digest */ + unsigned char nonce[DIGEST_NONCE_LEN]; + unsigned char userhash[MD5_LEN]; + unsigned char ha1[MD5_LEN]; + unsigned int nc; + + /* Used for both */ + char **groups; +} bd_record_t; /* ------------------------------------------------------------------------------- * Internal Functions */ -static void free_hash_object(void* arg, void* val) +static void +free_hash_object (void *arg, void *val) { - if(val && val != BASIC_ESTABLISHED) - free(val); + bd_record_t *rec = val; + if (!rec) + return; + str_array_free (rec->groups); + free (rec); } -static digest_record_t* get_cached_digest(bd_context_t* ctx, ha_context_t* c, - unsigned char* nonce) +static int +have_cached_basic (bd_context_t* ctx, unsigned char* key) { - digest_record_t* rec; + bd_record_t *rec; - ASSERT(ctx && c && nonce); + ASSERT (ctx && key); - if(c->cache_max == 0) - return NULL; + ha_lock(NULL); - ha_lock(NULL); - - rec = (digest_record_t*)hsh_get(ctx->cache, nonce); + rec = (bd_record_t*)hsh_get (ctx->cache, key); + if (rec && rec->type != RECORD_TYPE_BASIC) + rec = NULL; + else + hsh_touch (ctx->cache, key); - /* Just in case it's a basic :) */ - if(rec && rec != BASIC_ESTABLISHED) - hsh_rem(ctx->cache, nonce); - - ha_unlock(NULL); + ha_unlock(NULL); - ASSERT(!rec || memcmp(nonce, rec->nonce, DIGEST_NONCE_LEN) == 0); - return rec; + return rec != NULL; } -static int have_cached_basic(bd_context_t* ctx, unsigned char* key) +static int +add_cached_basic (bd_context_t *ctx, unsigned char *key, char **groups) { - int ret = 0; + bd_record_t *rec; + int r; - ASSERT(ctx && key); + ASSERT (ctx && key); - ha_lock(NULL); + rec = (bd_record_t*)malloc (sizeof (*rec)); + if (!rec) { + str_array_free (groups); + ha_memerr (NULL); + return HA_CRITERROR; + } - ret = (hsh_get(ctx->cache, key) == BASIC_ESTABLISHED); + memset (rec, 0, sizeof (*rec)); + rec->type = RECORD_TYPE_BASIC; + rec->groups = groups; - ha_unlock(NULL); + ha_lock (NULL); - return ret; -} + while (hsh_count (ctx->cache) >= ctx->cache_max) + hsh_bump (ctx->cache); -static int save_cached_digest(bd_context_t* ctx, ha_context_t* c, - digest_record_t* rec) -{ - int r; + r = hsh_set (ctx->cache, key, rec); - ASSERT(ctx && rec); + ha_unlock (NULL); - if(c->cache_max == 0) - { - free_hash_object(NULL, rec); - return HA_FALSE; - } + if (!r) { + free_hash_object (NULL, rec); + ha_memerr (NULL); + return HA_CRITERROR; + } - ha_lock(NULL); + return HA_OK; +} - while(hsh_count(ctx->cache) >= c->cache_max) - hsh_bump(ctx->cache); +static int +prepare_digest_from_cached (bd_context_t *ctx, digest_context_t *dg, + ha_request_t *rq, unsigned char *nonce) +{ + bd_record_t *rec; + int ret; + + ASSERT (dg && rq && nonce); + + ha_lock (NULL); + + rec = (bd_record_t*)hsh_get (ctx->cache, nonce); + if (rec && rec->type == RECORD_TYPE_DIGEST) { + ASSERT (memcmp (nonce, rec->nonce, DIGEST_NONCE_LEN) == 0); + memcpy (dg->server_userhash, rec->userhash, sizeof (dg->server_userhash)); + memcpy (dg->server_ha1, rec->ha1, sizeof (dg->server_ha1)); + dg->server_nc = ++(rec->nc); + hsh_touch (ctx->cache, nonce); + ret = HA_OK; + } else { + memset (dg->server_userhash, 0, sizeof (dg->server_userhash)); + memset (dg->server_ha1, 0, sizeof (dg->server_ha1)); + dg->server_nc = 1; + ret = HA_FALSE; + } + + ha_unlock (NULL); + + dg->server_uri = rq->req_args[AUTH_ARG_URI]; + dg->server_method = rq->req_args[AUTH_ARG_METHOD]; + + return ret; +} - r = hsh_set(ctx->cache, rec->nonce, rec); +static int +add_digest_rec (bd_context_t *ctx, unsigned char *nonce, + const char *user, digest_context_t *dg, char **groups) +{ + bd_record_t* rec = (bd_record_t*)malloc (sizeof (*rec)); + int r; - ha_unlock(NULL); + ASSERT (nonce && user); - if(!r) - { + if(!rec) { + str_array_free (groups); ha_memerr(NULL); - return HA_CRITERROR; - } + return HA_CRITERROR; + } - return HA_OK; -} + memset (rec, 0, sizeof (*rec)); + rec->type = RECORD_TYPE_DIGEST; + memcpy (rec->nonce, nonce, DIGEST_NONCE_LEN); + memcpy (rec->ha1, dg->server_ha1, MD5_LEN); -static int add_cached_basic(bd_context_t* ctx, ha_context_t* c, - unsigned char* key) -{ - int r; + /* We take ownership of groups */ + rec->groups = groups; + rec->nc = dg->server_nc; - ASSERT(ctx && c && key); + md5_string (rec->userhash, user); - if(c->cache_max == 0) - return HA_FALSE; + ha_lock (NULL); - ha_lock(NULL); + while (hsh_count (ctx->cache) >= ctx->cache_max) + hsh_bump (ctx->cache); - while(hsh_count(ctx->cache) >= c->cache_max) - hsh_bump(ctx->cache); + r = hsh_set (ctx->cache, nonce, rec); - r = hsh_set(ctx->cache, key, BASIC_ESTABLISHED); + ha_unlock (NULL); - ha_unlock(NULL); - - if(!r) - { - ha_memerr(NULL); - return HA_CRITERROR; - } + if (!r) { + free_hash_object (NULL, rec); + ha_memerr (NULL); + return HA_CRITERROR; + } - return HA_OK; + return HA_OK; } -digest_record_t* make_digest_rec(unsigned char* nonce, const char* user) +static int +include_group_headers (bd_context_t *ctx, ha_request_t *rq, unsigned char *key) { - digest_record_t* rec = (digest_record_t*)malloc(sizeof(*rec)); - - ASSERT(nonce && user); - - if(!rec) - { - ha_memerr(NULL); - return NULL; - } - - memset(rec, 0, sizeof(*rec)); - memcpy(rec->nonce, nonce, DIGEST_NONCE_LEN); - - md5_string(rec->userhash, user); - return rec; + bd_record_t *rec; + int have, all = 0; + char *header; + char **ug; + char **rg; + + if (!rq->requested_groups || !rq->requested_groups[0]) + return HA_OK; + + ha_lock (NULL); + + /* This starts a new block to join */ + ha_bufcpy (rq->buf, ""); + + rec = hsh_get (ctx->cache, key); + if (rec) { + for (ug = rec->groups; ug && *ug; ++ug) { + have = all; + for (rg = rq->requested_groups; rg && *rg && !have; ++rg) { + if (strcasecmp (*rg, "*") == 0) { + have = all = 1; + break; + } else if (strcasecmp (*rg, *ug) == 0) { + have = 1; + break; + } + } + + if (have) { + ha_bufjoin (rq->buf); + ha_bufcpy (rq->buf, "\""); + ha_bufjoin (rq->buf); + digest_escape (rq->buf, *ug); + ha_bufjoin (rq->buf); + ha_bufcpy (rq->buf, "\" "); + } + } + } + + ha_unlock (NULL); + + header = ha_bufdata (rq->buf); + if (!header) + return HA_CRITERROR; + + ha_addheader (rq, "X-HttpAuth-Groups", header); + return HA_OK; } -static int do_basic_response(ha_request_t* rq, bd_context_t* ctx, const char* header) +static int +do_basic_response (ha_request_t* rq, bd_context_t* ctx, const char* header) { - basic_header_t basic; - int ret = HA_FALSE; + basic_header_t basic; + char **groups = NULL; + int ret = HA_FALSE; - ASSERT(header && rq); + ASSERT (header && rq); - if((ret = basic_parse(header, rq->buf, &basic)) < 0) - return ret; + if ((ret = basic_parse (header, rq->buf, &basic)) < 0) + return ret; - /* Past this point we don't return directly */ + /* 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)) - { - ha_messagex(rq, LOG_NOTICE, "validated basic user against cache: %s", - basic.user); - RETURN(HA_OK); - } + /* Check and see if this connection is in the cache */ + if (have_cached_basic (ctx, basic.key)) { + ha_messagex (rq, LOG_NOTICE, "validated basic user against cache: %s", + basic.user); + RETURN (HA_OK); + } - /* If we have a user name and password */ - if(!basic.user || !basic.user[0] || - !basic.password || !basic.password[0]) - { - ha_messagex(rq, LOG_NOTICE, "no valid basic auth info"); - RETURN(HA_FALSE); - } + /* If we have a user name and password */ + if(!basic.user || !basic.user[0] || + !basic.password || !basic.password[0]) { + ha_messagex(rq, LOG_NOTICE, "no valid basic auth info"); + RETURN(HA_FALSE); + } - ASSERT(ctx->f_validate_basic); - ret = ctx->f_validate_basic(rq, basic.user, basic.password); + ASSERT (ctx->f_validate_basic); + ret = ctx->f_validate_basic (rq, basic.user, basic.password, &groups); -finally: + /* + * We put this connection into the successful connections. + * This takes ownership of groups. + */ + if (ret == HA_OK) + ret = add_cached_basic (ctx, basic.key, groups); - if(ret == HA_OK) - { - rq->resp_code = HA_SERVER_OK; - rq->resp_detail = basic.user; +finally: - /* We put this connection into the successful connections */ - ret = add_cached_basic(ctx, rq->context, basic.key); - } + if (ret == HA_OK) { + rq->resp_code = HA_SERVER_OK; + rq->resp_detail = basic.user; + include_group_headers (ctx, rq, basic.key); + } - return ret; + return ret; } static int do_digest_challenge(ha_request_t* rq, bd_context_t* ctx, int stale) { - unsigned char nonce[DIGEST_NONCE_LEN]; const char* nonce_str; const char* header; @@ -264,208 +349,172 @@ static int do_digest_challenge(ha_request_t* rq, bd_context_t* ctx, int stale) static int do_digest_response(ha_request_t* rq, bd_context_t* ctx, const char* header) { - unsigned char nonce[DIGEST_NONCE_LEN]; - digest_context_t dg; - digest_record_t* rec = NULL; - const char* t; - time_t expiry; - int ret = HA_FALSE; - int stale = 0; - int r; + unsigned char nonce[DIGEST_NONCE_LEN]; + digest_context_t dg; + const char *t; + char **groups = NULL; + time_t expiry; + int ret = HA_FALSE; + int stale = 0; + int r, cached; - ASSERT(ctx && header && rq); + ASSERT (ctx && header && rq); - /* We use this below to send a default response */ - rq->resp_code = -1; + /* We use this below to send a default response */ + rq->resp_code = -1; - if((r = digest_parse(header, rq->buf, &(dg.client))) < 0) - return r; + if ((r = digest_parse (header, rq->buf, &(dg.client))) < 0) + return r; - if(!dg.client.username) - { - ha_messagex(rq, LOG_WARNING, "digest response contains no user name"); - RETURN(HA_FALSE); - } + if (!dg.client.username) { + ha_messagex(rq, LOG_WARNING, "digest response contains no user name"); + RETURN(HA_FALSE); + } #ifdef _DEBUG - if(rq->context->digest_debugnonce) - { - if(dg.client.nonce && strcmp(dg.client.nonce, rq->context->digest_debugnonce) != 0) - { - ha_messagex(rq, LOG_WARNING, "digest response contains invalid nonce"); - RETURN(HA_FALSE); - } - - /* Do a rough hash into the real nonce, for use as a key */ - md5_string(nonce, rq->context->digest_debugnonce); - - /* Debug nonce's never expire */ - expiry = time(NULL); - } - else + if (rq->context->digest_debugnonce) { + if (dg.client.nonce && strcmp (dg.client.nonce, rq->context->digest_debugnonce) != 0) { + ha_messagex(rq, LOG_WARNING, "digest response contains invalid nonce"); + RETURN(HA_FALSE); + } + + /* Do a rough hash into the real nonce, for use as a key */ + md5_string (nonce, rq->context->digest_debugnonce); + + /* Debug nonce's never expire */ + expiry = time (NULL); + } else #endif - { - /* Parse out the nonce from that header */ - memset(nonce, 0, DIGEST_NONCE_LEN); - - if(dg.client.nonce) - { - size_t len = DIGEST_NONCE_LEN; - void* d = ha_bufdechex(rq->buf, dg.client.nonce, &len); - - if(d && len == DIGEST_NONCE_LEN) - memcpy(nonce, d, DIGEST_NONCE_LEN); - } - - r = digest_checknonce(nonce, g_digest_secret, &expiry); - if(r != HA_OK) - { - if(r == HA_FALSE) - ha_messagex(rq, LOG_WARNING, "digest response contains invalid nonce"); - - RETURN(r); - } - - /* Check to see if we're stale */ - if((expiry + rq->context->cache_timeout) <= time(NULL)) - { - ha_messagex(rq, LOG_INFO, "nonce expired, sending stale challenge: %s", - dg.client.username); - - stale = 1; - RETURN(HA_FALSE); - } - } - - /* See if we have this one from before. */ - rec = get_cached_digest(ctx, rq->context, nonce); - - /* - * Fill in the required fields. - */ - dg.server_nc = rec ? ++(rec->nc) : 0; /* Note bumping up nc */ - dg.server_uri = rq->req_args[AUTH_ARG_URI]; - dg.server_method = rq->req_args[AUTH_ARG_METHOD]; - - /* Check the majority of the fields */ - ret = digest_pre_check(&dg, rq->context, rq->buf); - - if(ret != HA_OK) - { - if(ret == HA_BADREQ) - { - ret = HA_FALSE; - rq->resp_code = HA_SERVER_BADREQ; - } - - RETURN(ret); - } - - /* - * If this is the first instance then we pass off to our derived - * handler for validation and completion of the ha1. This completes - * the authentication, and leaves us the ha1 caching. - */ - if(!rec) - { - ha_messagex(rq, LOG_INFO, "no record in cache, creating one: %s", dg.client.username); - - /* - * If we're valid but don't have a record in the - * cache then complete the record properly. - */ - - rec = make_digest_rec(nonce, dg.client.username); - if(!rec) - RETURN(HA_CRITERROR); - - - ASSERT(ctx->f_validate_digest); - r = ctx->f_validate_digest(rq, dg.client.username, &dg); - if(r != HA_OK) - RETURN(r); - - /* Save away pertinent information when successful*/ - memcpy(rec->ha1, dg.ha1, MD5_LEN); - } - - /* We had a record so ... */ - else - { - /* Bump up the ncount */ - rec->nc++; - - /* Check the user name */ - if(md5_strcmp(rec->userhash, dg.client.username) != 0) - { - ha_messagex(NULL, LOG_ERR, "digest response contains invalid username"); - RETURN(HA_FALSE); - } - - /* And do the validation ourselves */ - memcpy(dg.ha1, rec->ha1, MD5_LEN); - - ret = digest_complete_check(&dg, rq->context, rq->buf); - - if(ret != HA_OK) - { - if(ret == HA_BADREQ) - { - ret = HA_FALSE; - rq->resp_code = HA_SERVER_BADREQ; - } - - if(ret == HA_FALSE) - ha_messagex(NULL, LOG_WARNING, "digest re-authentication failed for user: %s", - dg.client.username); - - RETURN(ret); - } - } - - - rq->resp_code = HA_SERVER_OK; - rq->resp_detail = dg.client.username; - - /* Figure out if we need a new nonce */ - if((expiry + (rq->context->cache_timeout - - (rq->context->cache_timeout / 8))) < time(NULL)) - { - ha_messagex(rq, LOG_INFO, "nonce almost expired, creating new one: %s", - dg.client.username); - - digest_makenonce(nonce, g_digest_secret, NULL); - stale = 1; - } - - t = digest_respond(&dg, rq->buf, stale ? nonce : NULL); - if(!t) - RETURN(HA_CRITERROR); - - if(t[0]) - ha_addheader(rq, "Authentication-Info", t); - - ha_messagex(rq, LOG_NOTICE, "validated digest user: %s", dg.client.username); - - /* Put the connection into the cache */ - if((ret = save_cached_digest(ctx, rq->context, rec)) == HA_OK) - rec = NULL; + { + /* Parse out the nonce from that header */ + memset (nonce, 0, DIGEST_NONCE_LEN); + + if (dg.client.nonce) { + size_t len = DIGEST_NONCE_LEN; + void* d = ha_bufdechex (rq->buf, dg.client.nonce, &len); + + if (d && len == DIGEST_NONCE_LEN) + memcpy (nonce, d, DIGEST_NONCE_LEN); + } + + r = digest_checknonce (nonce, g_digest_secret, &expiry); + if (r != HA_OK) { + if (r == HA_FALSE) + ha_messagex (rq, LOG_WARNING, "digest response contains invalid nonce"); + stale = 1; + RETURN (r); + } + + /* Check to see if we're stale */ + if ((expiry + rq->context->cache_timeout) <= time (NULL)) { + ha_messagex(rq, LOG_INFO, "nonce expired, sending stale challenge: %s", + dg.client.username); + + stale = 1; + RETURN (HA_FALSE); + } + } + + /* + * Fill in all the required fields from any cached response, + * and check the user name if cached. Otherwise initializes + * to default values. + */ + cached = (prepare_digest_from_cached (ctx, &dg, rq, nonce) == HA_OK); + + /* Check the majority of the fields */ + ret = digest_pre_check (&dg, rq->context, rq->buf); + if (ret != HA_OK) { + if (ret == HA_BADREQ) { + ret = HA_FALSE; + rq->resp_code = HA_SERVER_BADREQ; + } + + RETURN (ret); + } + + /* + * If this is the first instance then we pass off to our derived + * handler for validation and completion of the ha1. This completes + * the authentication, and leaves us the ha1 caching. + */ + if (!cached) { + ha_messagex (rq, LOG_INFO, "no record in cache, creating one: %s", + dg.client.username); + + /* + * If we're valid but don't have a record in the + * cache then complete the record properly. + */ + + ASSERT (ctx->f_validate_digest); + r = ctx->f_validate_digest (rq, dg.client.username, &dg, &groups); + if (r != HA_OK) + RETURN (r); + + /* Add the digest record to the cache */ + r = add_digest_rec (ctx, nonce, dg.client.username, &dg, groups); + if (r != HA_OK) + RETURN (r); + + /* We had a record so ... */ + } else { + + /* Check the user name */ + if (md5_strcmp (dg.server_userhash, dg.client.username) != 0) { + ha_messagex(NULL, LOG_ERR, "digest response contains invalid username"); + RETURN(HA_FALSE); + } + + /* And do the validation ourselves */ + ret = digest_complete_check (&dg, rq->context, rq->buf); + + if (ret != HA_OK) { + if (ret == HA_BADREQ) { + ret = HA_FALSE; + rq->resp_code = HA_SERVER_BADREQ; + } + + if (ret == HA_FALSE) + ha_messagex (NULL, LOG_WARNING, "digest re-authentication failed for user: %s", + dg.client.username); + + RETURN (ret); + } + } + + rq->resp_code = HA_SERVER_OK; + rq->resp_detail = dg.client.username; + include_group_headers (ctx, rq, nonce); + + /* Figure out if we need a new nonce */ + if ((expiry + (rq->context->cache_timeout - + (rq->context->cache_timeout / 8))) < time (NULL)) { + ha_messagex (rq, LOG_INFO, "nonce almost expired, creating new one: %s", + dg.client.username); + + digest_makenonce (nonce, g_digest_secret, NULL); + stale = 1; + } + + t = digest_respond (&dg, rq->buf, stale ? nonce : NULL); + if (!t) + RETURN (HA_CRITERROR); + + if (t[0]) + ha_addheader(rq, "Authentication-Info", t); + + ha_messagex(rq, LOG_NOTICE, "validated digest user: %s", dg.client.username); 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(ret == HA_FALSE && rq->resp_code == -1) - return do_digest_challenge(rq, ctx, stale); + /* If nobody above responded then challenge the client again */ + if (ret == HA_FALSE && rq->resp_code == -1) + return do_digest_challenge (rq, ctx, stale); - return ret; + return ret; } - - const char* bd_substitute(const ha_request_t* rq, const char* user, const char* str) { bd_context_t* ctx = (bd_context_t*)rq->context->ctx_data; @@ -559,6 +608,7 @@ int bd_init(ha_context_t* context) htc.f_freeval = free_hash_object; htc.arg = NULL; hsh_set_table_calls(ctx->cache, &htc); + ctx->cache_max = context->cache_max; } return HA_OK; @@ -567,7 +617,6 @@ int bd_init(ha_context_t* context) void bd_destroy(ha_context_t* context) { bd_context_t* ctx; - int i; if(!context) return; diff --git a/daemon/bd.h b/daemon/bd.h index a42af68..1697cfb 100644 --- a/daemon/bd.h +++ b/daemon/bd.h @@ -39,8 +39,8 @@ * HA_OK: completed successfully * HA_FAILED: error retrieving hash (should have logged error) */ -typedef int (*bd_validate_digest)(ha_request_t* rq, - const char* user, digest_context_t* dg); +typedef int (*bd_validate_digest)(ha_request_t* rq, const char* user, + digest_context_t* dg, char ***groups); /* * A callback for validating a given user's password. @@ -50,8 +50,8 @@ typedef int (*bd_validate_digest)(ha_request_t* rq, * HA_FALSE: invalid password * HA_FAILED: error validating (should have logged error) */ -typedef int (*bd_validate_basic)(ha_request_t* rq, - const char* user, const char* password); +typedef int (*bd_validate_basic) (ha_request_t *rq, const char *user, + const char *password, char ***groups); /* * Escapes a value for sending to 'server' @@ -71,6 +71,7 @@ typedef struct bd_context /* Require locking --------------------------------------*/ hsh_t* cache; /* Some cached records or basic */ + unsigned int cache_max; /* Maximum number of records in cache */ } bd_context_t; diff --git a/daemon/digest.c b/daemon/digest.c index ecde6b7..21cb453 100644 --- a/daemon/digest.c +++ b/daemon/digest.c @@ -27,6 +27,7 @@ #include "digest.h" #include "stringx.h" +#include #include /* A globally unique counter used to guarantee uniqueness of nonces */ @@ -333,8 +334,9 @@ int digest_pre_check(digest_context_t* dg, const ha_context_t* opts, ha_buffer_t if(*e || nc != dg->server_nc) { - ha_messagex(NULL, LOG_WARNING, "digest response has wrong nc value. " - "possible replay attack: %s", dg->client.nc); + ha_messagex(NULL, LOG_WARNING, "digest response has wrong nc value: %s " + "possible replay attack, should be: %d", + dg->client.nc, dg->server_nc); return HA_FALSE; } } @@ -415,7 +417,7 @@ static int internal_check(digest_context_t* dg, const char* http_method, ha_buff */ /* Encode ha1 */ - t = ha_bufenchex(buf, dg->ha1, MD5_LEN); + t = ha_bufenchex(buf, dg->server_ha1, MD5_LEN); if(t == NULL) return HA_CRITERROR; @@ -541,7 +543,7 @@ const char* digest_respond(digest_context_t* dg, ha_buffer_t* buf, /* Otherwise we do the whole song and dance */ /* Encode ha1 */ - t = ha_bufenchex(buf, dg->ha1, MD5_LEN); + t = ha_bufenchex(buf, dg->server_ha1, MD5_LEN); if(t == NULL) return NULL; @@ -614,3 +616,38 @@ void digest_makeha1(unsigned char* digest, const char* user, md5_update(&md5, password, strlen(password)); md5_final(digest, &md5); } + +#define MUST_ESCAPE "\"\' \t\n\r\v\\" + +void +digest_escape (ha_buffer_t *buf, const char *orig) +{ + const char* t; + size_t pos; + + assert (orig); + assert (buf); + + ha_bufcpy(buf, ""); + + t = orig; + while (*t) { + pos = strcspn (t, MUST_ESCAPE); + + if(pos > 0) { + ha_bufjoin (buf); + ha_bufncpy (buf, t, pos); + t += pos; + } + + while (*t && !strchr (MUST_ESCAPE, *t)) { + char esc[3]; + esc[0] = '\\'; + esc[1] = *t; + esc[2] = '\0'; + ha_bufjoin (buf); + ha_bufcpy (buf, esc); + t++; + } + } +} diff --git a/daemon/digest.h b/daemon/digest.h index af8a1d7..7a0f59c 100644 --- a/daemon/digest.h +++ b/daemon/digest.h @@ -22,8 +22,11 @@ #ifndef __DIGEST_H__ #define __DIGEST_H__ +#include "httpauthd.h" #include "md5.h" +#include + #define DIGEST_NONCE_LEN sizeof(time_t) + sizeof(unsigned int) + MD5_LEN #define DIGEST_SECRET_LEN 16 @@ -53,7 +56,8 @@ typedef struct digest_context const char* server_uri; const char* server_method; - unsigned char ha1[MD5_LEN]; + unsigned char server_userhash[MD5_LEN]; + unsigned char server_ha1[MD5_LEN]; } digest_context_t; @@ -78,4 +82,8 @@ int digest_complete_check(digest_context_t* dg, const ha_context_t* opts, ha_buf /* This assumes a digest_context that's been checked and validated successfully */ const char* digest_respond(digest_context_t* dg, ha_buffer_t* buf, unsigned char* next); +void digest_makenonce(unsigned char* nonce, unsigned char* secret, unsigned char* old); +int digest_checknonce(unsigned char* nonce, unsigned char* secret, time_t* tm); +void digest_escape (ha_buffer_t *buf, const char *orig); + #endif /* __DIGEST_H__ */ diff --git a/daemon/dummy.c b/daemon/dummy.c index ca83e54..245d32a 100755 --- a/daemon/dummy.c +++ b/daemon/dummy.c @@ -33,8 +33,8 @@ */ /* Forward declarations for callbacks */ -static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg); -static int validate_basic(ha_request_t* rq, const char* user, const char* password); +static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg, char ***groups); +static int validate_basic(ha_request_t* rq, const char* user, const char* password, char ***groups); /* The defaults for the context */ static const bd_context_t dummy_defaults = BD_CALLBACKS(validate_digest, validate_basic, NULL); @@ -43,12 +43,12 @@ static const bd_context_t dummy_defaults = BD_CALLBACKS(validate_digest, validat * Internal Functions */ -static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg) +static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg, char ***groups) { return HA_OK; } -static int validate_basic(ha_request_t* rq, const char* user, const char* password) +static int validate_basic(ha_request_t* rq, const char* user, const char* password, char ***groups) { return HA_OK; } diff --git a/daemon/httpauthd.c b/daemon/httpauthd.c index 1161517..09fb1d7 100644 --- a/daemon/httpauthd.c +++ b/daemon/httpauthd.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include "usuals.h" @@ -85,6 +86,8 @@ httpauth_loaded_t; /* The list of handlers in use */ httpauth_loaded_t* g_handlers = NULL; +extern int pthread_mutexattr_settype (pthread_mutexattr_t *attr, int kind); + /* ----------------------------------------------------------------------- * Structures and Constants */ @@ -162,7 +165,6 @@ static void writepid(const char* pid); static void* httpauth_thread(void* arg); static int httpauth_processor(int ifd, int ofd); static int httpauth_respond(ha_request_t* rq, int ofd, int scode, int ccode, const char* msg); -static int process_auth(ha_request_t* rq); static int config_parse(const char* file, ha_buffer_t* buf); static void on_quit(int signal); @@ -176,7 +178,6 @@ int main(int argc, char* argv[]) const char* pidfile = NULL; httpauth_thread_t* threads = NULL; httpauth_loaded_t* h; - char peername[MAXPATHLEN]; struct sockaddr_any sany; int daemonize = 1; ha_buffer_t cbuf; @@ -944,9 +945,6 @@ static int httpauth_error(ha_request_t* rq, int ofd, int r) static int httpauth_ready(ha_request_t* rq, int ofd) { - const char* t; - httpauth_loaded_t* h; - ASSERT(ofd != -1); ASSERT(rq); @@ -1027,6 +1025,14 @@ static int httpauth_set(ha_request_t* rq, ha_buffer_t* cbuf, int ofd) rq->digest_domain = ha_bufcpy(rq->conn_buf, value ? value : ""); } + else if (strcasecmp (name, "Groups") == 0) { + + /* we need to copy this string so it doesn't get destroyed on next req */ + if (rq->requested_groups) + str_array_free (rq->requested_groups); + rq->requested_groups = str_array_parse_quoted (value ? value : ""); + } + else if(strcasecmp(name, "Handler") == 0) { if(!value || !*value) @@ -1048,7 +1054,7 @@ static int httpauth_set(ha_request_t* rq, ha_buffer_t* cbuf, int ofd) if(value != NULL) { - ha_messagex(rq, LOG_ERR, "unknown authentication handler: %s", rq->req_args[0]); + ha_messagex(rq, LOG_ERR, "unknown authentication handler: %s", value); return httpauth_respond(rq, ofd, HA_SERVER_BADREQ, 0, "Unknown Auth Handler"); } } @@ -1107,6 +1113,7 @@ static int httpauth_processor(int ifd, int ofd) /* Set up some context stuff */ rq.digest_domain = ""; + rq.requested_groups = NULL; rq.buf = &buf; rq.conn_buf = &cbuf; @@ -1188,6 +1195,8 @@ finally: close(ofd); } + if (rq.requested_groups) + str_array_free (rq.requested_groups); ha_messagex(&rq, LOG_INFO, "closed connection"); ha_buffree(&cbuf); @@ -1320,7 +1329,6 @@ static int config_parse(const char* file, ha_buffer_t* buf) if(ha_bufchar(buf) == '[') { ha_handler_t* handler = NULL; - const char* x; ha_bufeat(buf); name = ha_bufparseline(buf, 1); diff --git a/daemon/httpauthd.h b/daemon/httpauthd.h index 1540138..e533dde 100644 --- a/daemon/httpauthd.h +++ b/daemon/httpauthd.h @@ -138,13 +138,13 @@ ha_context_t; #define HA_MAX_ARGS 4 /* - * The maximum number of pertinent headers to read - * from the client. If you need to add valid headers + * The maximum number of pertinent headers to read/send + * from/to the client. If you need to add valid headers * make sure to update this number *and* the list * of valid headers in httpauthd.c */ -#define HA_MAX_HEADERS 2 +#define HA_MAX_HEADERS 8 /* * The maximum number of handlers. If you add @@ -183,6 +183,7 @@ typedef struct ha_request /* Additional request info */ ha_context_t* context; const char* digest_domain; + char** requested_groups; /* The buffer in use for the request */ ha_buffer_t* buf; diff --git a/daemon/ldap.c b/daemon/ldap.c index a1aa1a1..f4324be 100644 --- a/daemon/ldap.c +++ b/daemon/ldap.c @@ -24,10 +24,16 @@ #include "md5.h" #include "sha1.h" #include "bd.h" +#include "stringx.h" #include +#include + +#define __USE_XOPEN +#include /* LDAP library */ +#define LDAP_DEPRECATED 1 #include /* ------------------------------------------------------------------------------- @@ -73,6 +79,7 @@ typedef struct ldap_context const char* base; /* Base for the filter */ const char* pw_attr; /* The clear password attribute */ const char* ha1_attr; /* Password for an encrypted Digest H(A1) */ + const char* group_attr; /* The group attribute */ const char* user; /* User to bind as */ const char* password; /* Password to bind with */ const char* dnmap; /* For mapping users to dns */ @@ -90,8 +97,8 @@ typedef struct ldap_context ldap_context_t; /* Forward declarations for callbacks */ -static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg); -static int validate_basic(ha_request_t* rq, const char* user, const char* password); +static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg, char ***groups); +static int validate_basic(ha_request_t* rq, const char* user, const char* password, char ***groups); static void escape_ldap(const ha_request_t* rq, ha_buffer_t* buf, const char* value); /* The defaults for the context */ @@ -104,6 +111,7 @@ static const ldap_context_t ldap_defaults = NULL, /* base */ "userPassword", /* pw_attr */ NULL, /* ha1_attr */ + NULL, /* group_attr */ NULL, /* user */ NULL, /* password */ NULL, /* dnmap */ @@ -563,10 +571,10 @@ static int retrieve_user_entry(const ha_request_t* rq, ldap_context_t* ctx, LDAP { struct timeval tv; const char* filter; - const char* attrs[3]; + const char* attrs[4]; const char* base; int scope; - int r; + int r, i; ASSERT(ctx && rq && ld && user && dn && entry && result); @@ -582,9 +590,14 @@ static int retrieve_user_entry(const ha_request_t* rq, ldap_context_t* ctx, LDAP filter = "(objectClass=*)"; } - attrs[0] = ctx->dobind ? NULL : ctx->pw_attr; - attrs[1] = ctx->dobind ? NULL : ctx->ha1_attr; - attrs[2] = NULL; + i = 0; + if (ctx->group_attr) + attrs[i++] = ctx->group_attr; + if (ctx->dobind && ctx->pw_attr) + attrs[i++] = ctx->pw_attr; + if (ctx->dobind && ctx->ha1_attr) + attrs[i++] = ctx->ha1_attr; + attrs[i] = NULL; tv.tv_sec = ctx->ldap_timeout; tv.tv_usec = 0; @@ -633,7 +646,43 @@ static int retrieve_user_entry(const ha_request_t* rq, ldap_context_t* ctx, LDAP return HA_FALSE; } -static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg) +static int +get_user_groups (ldap_context_t *ctx, LDAP *ld, LDAPMessage *entry, char*** result) +{ + struct berval **vals, **v; + char **groups = NULL; + int ret = HA_OK; + + if (!ctx->group_attr) { + *result = NULL; + return HA_OK; + } + + groups = str_array_create (NULL); + vals = ldap_get_values_len (ld, entry, ctx->group_attr); + for (v = vals; v && *v; ++v) { + if ((*v)->bv_len) { + groups = str_array_appendn (groups, (*v)->bv_val, (*v)->bv_len); + if (!groups) { + ha_memerr (NULL); + ret = HA_CRITERROR; + break; + } + } + } + + if (vals) + ldap_value_free_len (vals); + if (ret != HA_OK) + str_array_free (groups); + else + *result = groups; + return ret; +} + +static int +validate_digest (ha_request_t *rq, const char *user, + digest_context_t *dg, char ***groups) { ldap_context_t* ctx = (ldap_context_t*)rq->context->ctx_data; LDAP* ld = NULL; /* freed in finally */ @@ -684,7 +733,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* for(h = ha1s; *h; ++h) { - r = parse_ldap_ha1(rq, *h, dg->ha1); + r = parse_ldap_ha1(rq, *h, dg->server_ha1); if(r == HA_FALSE) { @@ -722,7 +771,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* { foundany = 1; - digest_makeha1(dg->ha1, user, rq->context->realm, password); + digest_makeha1(dg->server_ha1, user, rq->context->realm, password); /* Run the actual check */ ret = digest_complete_check(dg, rq->context, rq->buf); @@ -739,6 +788,13 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* finally: + /* Fill in the user's groups */ + if (ret == HA_OK) { + r = get_user_groups (ctx, ld, entry, groups); + if (r < 0) + ret = r; + } + if(ha1s) ldap_value_free_len(ha1s); @@ -754,7 +810,9 @@ finally: return ret; } -static int validate_basic(ha_request_t* rq, const char* user, const char* password) +static int +validate_basic (ha_request_t *rq, const char *user, + const char *password, char ***groups) { ldap_context_t* ctx = (ldap_context_t*)rq->context->ctx_data; LDAP* ld = NULL; @@ -845,7 +903,11 @@ static int validate_basic(ha_request_t* rq, const char* user, const char* passwo /* Discard the connection since it's useless to us */ discard_ldap_connection(rq, ctx, ld); ld = NULL; - } + ret = HA_FALSE; + + } else { + ret = HA_OK; + } } @@ -870,6 +932,12 @@ static int validate_basic(ha_request_t* rq, const char* user, const char* passwo } finally: + /* Fill in the user's groups */ + if (ret == HA_OK) { + r = get_user_groups (ctx, ld, entry, groups); + if (r < 0) + ret = r; + } if(results) ldap_msgfree(results); @@ -909,6 +977,12 @@ int ldap_config(ha_context_t* context, const char* name, const char* value) return HA_OK; } + else if(strcmp(name, "ldapgroupattr") == 0) + { + ctx->group_attr = value; + return HA_OK; + } + else if(strcmp(name, "ldappwattr") == 0) { ctx->pw_attr = value; diff --git a/daemon/misc.c b/daemon/misc.c index ca14ee3..49c585e 100644 --- a/daemon/misc.c +++ b/daemon/misc.c @@ -27,6 +27,7 @@ #include #include #include +#include extern int g_debuglevel; extern int g_daemonized; @@ -53,7 +54,6 @@ static void vmessage(const ha_request_t* rq, int level, int err, size_t len; char* m; int e = errno; - int x; if(g_daemonized) { @@ -390,7 +390,7 @@ int ha_uriparse(ha_buffer_t* buf, const char* suri, ha_uri_t* uri) if(str[0] == '/') { deal_with_path: - *str == 0; + *str = 0; ++str; /* @@ -611,7 +611,7 @@ int ha_uricmp(ha_uri_t* one, ha_uri_t* two) /* 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_s(one->host, two->host, NULL, 1)) != 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 || diff --git a/daemon/mysql.c b/daemon/mysql.c index e926bb2..69a28ff 100644 --- a/daemon/mysql.c +++ b/daemon/mysql.c @@ -27,6 +27,9 @@ #include +#define __USE_XOPEN +#include + /* Mysql library */ #include #include @@ -48,14 +51,14 @@ typedef struct mysql_context /* Readonly Settings ------------------------------------------------- */ const char* host; /* The connection host or path */ - unsigned int port; /* The connection port */ + int port; /* The connection port */ const char* user; /* The pgsql user name */ const char* password; /* The pgsql password */ const char* database; /* The database name */ - const char* query; /* The query */ - const char* pw_column; /* The database query to retrieve a password */ + const char* user_query; /* The database query to get the user info */ + const char* pw_column; /* The database column with a password */ int pw_type; /* The type of password encoded in database */ - const char* ha1_column; /* The database query to retrieve a ha1 */ + const char* ha1_column; /* The database column with a ha1 */ int mysql_max; /* Number of open connections allowed */ int mysql_timeout; /* Maximum amount of time to dedicate to a query */ @@ -68,8 +71,8 @@ typedef struct mysql_context mysql_context_t; /* Forward declarations for callbacks */ -static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg); -static int validate_basic(ha_request_t* rq, const char* user, const char* password); +static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg, char ***groups); +static int validate_basic(ha_request_t* rq, const char* user, const char* password, char ***groups); static void escape_mysql(const ha_request_t* rq, ha_buffer_t* buf, const char* value); /* The defaults for the context */ @@ -82,7 +85,7 @@ static const mysql_context_t mysql_defaults = NULL, /* user */ NULL, /* password */ NULL, /* database */ - NULL, /* query */ + NULL, /* user_query */ NULL, /* pw_attr */ DB_PW_CLEAR, /* pw_type */ NULL, /* ha1_attr */ @@ -97,7 +100,7 @@ static const mysql_context_t mysql_defaults = static pthread_mutex_t g_mysql_mutex; static pthread_mutexattr_t g_mysql_mutexattr; - +extern int pthread_mutexattr_settype (pthread_mutexattr_t *attr, int kind); /* ------------------------------------------------------------------------------- * Internal Functions @@ -164,7 +167,6 @@ static int validate_ha1(ha_request_t* rq, mysql_context_t* ctx, const char* user { unsigned char dbha1[MD5_LEN]; unsigned char ha1[MD5_LEN]; - const char* p; int r = dec_mysql_binary(rq, dbpw, dbha1, MD5_LEN); @@ -344,7 +346,7 @@ static void discard_mysql_connection(const ha_request_t* rq, mysql_context_t* ct static void save_mysql_connection(const ha_request_t* rq, mysql_context_t* ctx, MYSQL* my) { - int i, e; + int i; ASSERT(ctx); @@ -424,10 +426,10 @@ static int retrieve_user_rows(ha_request_t* rq, mysql_context_t* ctx, if(!my) RETURN(HA_FAILED); - ASSERT(ctx->query); + ASSERT(ctx->user_query); /* The map can have %u and %r to denote user and realm */ - query = bd_substitute(rq, user, ctx->query); + query = bd_substitute(rq, user, ctx->user_query); if(!query) RETURN(HA_CRITERROR); @@ -472,7 +474,7 @@ finally: return ret; } -static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg) +static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg, char ***groups) { mysql_context_t* ctx = (mysql_context_t*)rq->context->ctx_data; MYSQL_RES* res = NULL; @@ -480,7 +482,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* int ret = HA_FALSE; int pw_column = -1; int ha1_column = -1; - int r, i, foundany = 0; + int foundany = 0; const char* v; ASSERT(rq && user && dg); @@ -511,7 +513,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* { foundany = 1; - digest_makeha1(dg->ha1, user, rq->context->realm, v); + digest_makeha1(dg->server_ha1, user, rq->context->realm, v); ha_messagex(rq, LOG_DEBUG, "testing clear text password for digest auth"); /* Run the actual check */ @@ -527,7 +529,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* v = *(row + ha1_column); if(v != NULL) { - ret = dec_mysql_binary(rq, v, dg->ha1, MD5_LEN); + ret = dec_mysql_binary(rq, v, dg->server_ha1, MD5_LEN); if(ret < 0) RETURN(ret) else if(ret == HA_FALSE) @@ -554,7 +556,7 @@ finally: return ret; } -static int validate_basic(ha_request_t* rq, const char* user, const char* password) +static int validate_basic(ha_request_t* rq, const char* user, const char* password, char ***groups) { mysql_context_t* ctx = (mysql_context_t*)rq->context->ctx_data; MYSQL_RES* res = NULL; @@ -562,7 +564,7 @@ static int validate_basic(ha_request_t* rq, const char* user, const char* passwo int ret = HA_FALSE; int pw_column = -1; int ha1_column = -1; - int i, foundany = 0; + int foundany = 0; const char* v; ASSERT(rq && user && password); @@ -665,7 +667,7 @@ int mysql_config(ha_context_t* context, const char* name, const char* value) if(strcmp(name, "dbquery") == 0) { - ctx->query = value; + ctx->user_query = value; return HA_OK; } @@ -727,7 +729,7 @@ int mysql_initialize(ha_context_t* context) ASSERT(ctx); /* Check for mandatory configuration */ - if(!ctx->database || !ctx->query) + if(!ctx->database || !ctx->user_query) { ha_messagex(NULL, LOG_ERR, "mysql configuration incomplete. " "Must have DBDatabase and DBQuery."); diff --git a/daemon/ntlmssp.c b/daemon/ntlmssp.c index ca8c5a8..1b51264 100644 --- a/daemon/ntlmssp.c +++ b/daemon/ntlmssp.c @@ -239,7 +239,7 @@ ntlm_msg3_getusername(unsigned char *raw_msg, unsigned msglen, return 16; else { /* Win9x client leave username in uppercase...fix it: */ - while (*username!=(unsigned char)NULL) { + while (*username!=(unsigned char)0) { c=tolower((int)*username); *username=(unsigned char)c; username++; diff --git a/daemon/pgsql.c b/daemon/pgsql.c index 0306cbf..f19624a 100644 --- a/daemon/pgsql.c +++ b/daemon/pgsql.c @@ -25,10 +25,17 @@ #include "sha1.h" #include "bd.h" +#define __USE_XOPEN +#include + #include /* Postgresql library */ +#ifdef HAVE_LIBPQ_FE_H #include +#else +#include +#endif /* ------------------------------------------------------------------------------- * Structures @@ -66,8 +73,8 @@ typedef struct pgsql_context pgsql_context_t; /* Forward declarations for callbacks */ -static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg); -static int validate_basic(ha_request_t* rq, const char* user, const char* password); +static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg, char ***groups); +static int validate_basic(ha_request_t* rq, const char* user, const char* password, char ***groups); static void escape_pgsql(const ha_request_t* rq, ha_buffer_t* buf, const char* value); /* The defaults for the context */ @@ -136,7 +143,7 @@ static int dec_pgsql_binary(const ha_request_t* rq, const char* enc, } /* Raw binary postgres encoded */ - d = PQunescapeBytea(enc, &enclen); + d = PQunescapeBytea((const unsigned char*)enc, &enclen); if(d != NULL) { if(enclen == len) @@ -170,7 +177,6 @@ static int validate_ha1(ha_request_t* rq, pgsql_context_t* ctx, const char* user { unsigned char dbha1[MD5_LEN]; unsigned char ha1[MD5_LEN]; - const char* p; int r = dec_pgsql_binary(rq, dbpw, dbha1, MD5_LEN); @@ -383,7 +389,7 @@ static void discard_pgsql_connection(const ha_request_t* rq, pgsql_context_t* ct static void save_pgsql_connection(const ha_request_t* rq, pgsql_context_t* ctx, PGconn* pg) { - int i, e; + int i; ASSERT(ctx); @@ -518,14 +524,14 @@ finally: return ret; } -static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg) +static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg, char ***groups) { pgsql_context_t* ctx = (pgsql_context_t*)rq->context->ctx_data; PGresult* res = NULL; int ret = HA_FALSE; int pw_column = -1; int ha1_column = -1; - int r, i, foundany = 0; + int i, foundany = 0; ASSERT(rq && user && dg); @@ -554,7 +560,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* { foundany = 1; - digest_makeha1(dg->ha1, user, rq->context->realm, PQgetvalue(res, i, pw_column)); + digest_makeha1(dg->server_ha1, user, rq->context->realm, PQgetvalue(res, i, pw_column)); ha_messagex(rq, LOG_DEBUG, "testing clear text password for digest auth"); /* Run the actual check */ @@ -569,7 +575,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* { if(!PQgetisnull(res, i, ha1_column)) { - ret = dec_pgsql_binary(rq, PQgetvalue(res, i, ha1_column), dg->ha1, MD5_LEN); + ret = dec_pgsql_binary(rq, PQgetvalue(res, i, ha1_column), dg->server_ha1, MD5_LEN); if(ret < 0) RETURN(ret) else if(ret == HA_FALSE) @@ -596,7 +602,7 @@ finally: return ret; } -static int validate_basic(ha_request_t* rq, const char* user, const char* password) +static int validate_basic(ha_request_t* rq, const char* user, const char* password, char ***groups) { pgsql_context_t* ctx = (pgsql_context_t*)rq->context->ctx_data; PGresult* res = NULL; diff --git a/daemon/simple.c b/daemon/simple.c index 1c37c23..1e3cc64 100644 --- a/daemon/simple.c +++ b/daemon/simple.c @@ -28,9 +28,11 @@ #include "hash.h" #include "bd.h" #include "md5.h" +#include "stringx.h" #include #include +#include #define SIMPLE_MAXLINE 256 @@ -49,8 +51,8 @@ typedef struct simple_context simple_context_t; /* Forward declarations for callbacks */ -static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg); -static int validate_basic(ha_request_t* rq, const char* user, const char* password); +static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg, char ***groups); +static int validate_basic(ha_request_t* rq, const char* user, const char* password, char ***groups); /* The defaults for the context */ static const simple_context_t simple_defaults = @@ -63,7 +65,8 @@ static const simple_context_t simple_defaults = * Internal Functions */ -static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg) +static int validate_digest(ha_request_t* rq, const char* user, + digest_context_t* dg, char ***groups) { simple_context_t* ctx = (simple_context_t*)rq->context->ctx_data; FILE* f; @@ -142,7 +145,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* } ha_messagex(rq, LOG_DEBUG, "found ha1 for user: %s", user); - memcpy(dg->ha1, t, MD5_LEN); + memcpy(dg->server_ha1, t, MD5_LEN); foundgood = 1; /* Try to do the validation */ @@ -168,7 +171,8 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* return ret; } -static int validate_basic(ha_request_t* rq, const char* user, const char* password) +static int validate_basic(ha_request_t* rq, const char* user, + const char* password, char ***groups) { simple_context_t* ctx = (simple_context_t*)rq->context->ctx_data; FILE* f; diff --git a/doc/httpauthd.conf.5 b/doc/httpauthd.conf.5 index 7bca8b6..567b0b6 100644 --- a/doc/httpauthd.conf.5 +++ b/doc/httpauthd.conf.5 @@ -243,6 +243,11 @@ should be taken that the filter only returns one record. [ Required when .Em LDAPDNMap is missing ] +.It Cd LDAPGroupAttr +The name of an attribute on the LDAP server that contains the groups that +the user is in. +.Pp +[ Optional ] .It Cd LDAPHA1Attr A HA1 is a special kind of digest containing the user name, realm and password. This can be used in place of cleartext passwords when doing diff --git a/tests/Makefile.am b/tests/Makefile.am index 75719b1..acfe56f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,10 +1,12 @@ UNIT_TESTS = \ - unit-test-ntlmssp.c + unit-test-ntlmssp.c \ + unit-test-stringx.c INCLUDES= \ -I$(top_srcdir) \ - -I$(top_builddir) + -I$(top_builddir) \ + -I$(top_srcdir)/common noinst_PROGRAMS= \ run-tests diff --git a/tests/unit-test-ntlmssp.c b/tests/unit-test-ntlmssp.c index b12102f..8d8a647 100644 --- a/tests/unit-test-ntlmssp.c +++ b/tests/unit-test-ntlmssp.c @@ -93,5 +93,4 @@ void unit_test_ntlmssp_decode (CuTest *cu) */ #include "common/buffer.c" -#include "common/stringx.c" #include "daemon/ntlmssp.c" diff --git a/tests/unit-test-stringx.c b/tests/unit-test-stringx.c new file mode 100644 index 0000000..e1bb2a2 --- /dev/null +++ b/tests/unit-test-stringx.c @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2008, Stefan Walter + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Stef Walter + * + */ + +#include "config.h" + +#include "run-tests.h" + +#include "common/stringx.h" + +static const char* PARSES[] = { + "one 'twenty \"two\"' 'three", + " one 'twenty \"two\"' 'three' \n", + "one twenty\\ \\\"two\\\" \nthree ", + " one\n \"twenty \\\"two\\\"\" three", + NULL +}; + +/* + * Each test looks like (on one line): + * void unit_test_xxxxx (CuTest* cu) + * + * Each setup looks like (on one line): + * void unit_setup_xxxxx (void) + * + * Each teardown looks like (on one line): + * void unit_teardown_xxxxx (void) + * + * Tests be run in the order specified here. + */ + +void unit_test_str_array_general (CuTest *cu) +{ + char** array; + + array = str_array_create ("one", "two", "three", NULL); + CuAssertPtrNotNull (cu, array); + CuAssertStrEquals (cu, "one", array[0]); + CuAssertStrEquals (cu, "two", array[1]); + CuAssertStrEquals (cu, "three", array[2]); + + array = str_array_append (array, "four"); + CuAssertStrEquals (cu, "four", array[3]); + + array = str_array_appendn (array, "five and fifty", 4); + CuAssertStrEquals (cu, "five", array[4]); + + str_array_free (array); +} + +void unit_test_str_array_parse (CuTest *cu) +{ + char** array; + int i; + + for (i = 0; PARSES[i]; ++i) { + array = str_array_parse_quoted (PARSES[i]); + // fprintf (stderr, "%d --- %s", i, PARSES[i]); + CuAssertPtrNotNull (cu, array); + CuAssertIntEquals (cu, 3, str_array_length (array)); + CuAssertStrEquals (cu, "one", array[0]); + CuAssertStrEquals (cu, "twenty \"two\"", array[1]); + CuAssertStrEquals (cu, "three", array[2]); + } +} + +/* ----------------------------------------------------------------------------- + * Code being tested + */ + +#include "common/stringx.c" diff --git a/tools/mkha1.c b/tools/mkha1.c index 97c7dcd..c61c725 100644 --- a/tools/mkha1.c +++ b/tools/mkha1.c @@ -41,6 +41,7 @@ #include #include #include +#include #include "compat.h" #include "buffer.h" @@ -147,9 +148,9 @@ static char* check_value(ha_buffer_t* buf, char* value, const char* name) } if(value[0] == 0) - errx("%s is empty"); + errx(1, "%s is empty", name); if(strchr(value, ':') != NULL) - errx("%s must be a not contain colons"); + errx(1, "%s must be a not contain colons", name); return value; } -- cgit v1.2.3