diff --git a/CHANGES b/CHANGES index c30fcdff21..6b2272aea7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,14 @@ -*- coding: utf-8 -*- Changes with Apache 2.4.69 + *) mod_md: MDServerStatus is now disabled by default. [Joe Orton] + + *) mod_auth_digest: Fix compatibility with expression-based AuthName. + PR 59039. [Eric Covener] + *) mod_auth_digest.c: Drop RFC 2069 support; rewrite shared memory + handling and client nonce handling; "authdigest-opaque" mutex is + now longer needed. [Joe Orton] + *) mod_lbmethod_heartbeat: Use safe integer parsing with range validation, replacing atoi(). [Sayed Kaif ] diff --git a/STATUS b/STATUS index 9af9ce7443..78a9813626 100644 --- a/STATUS +++ b/STATUS @@ -163,30 +163,6 @@ RELEASE SHOWSTOPPERS: PATCHES ACCEPTED TO BACKPORT FROM TRUNK: [ start all new proposals below, under PATCHES PROPOSED. ] - *) mod_auth_digest: Rebase to trunk. - trunk patch: https://svn.apache.org/r1731662 - https://svn.apache.org/r1934434 - https://svn.apache.org/r1935944 - https://svn.apache.org/r1935945 - https://svn.apache.org/r1935946 - https://svn.apache.org/r1935947 - https://svn.apache.org/r1935948 - https://svn.apache.org/r1935949 - https://svn.apache.org/r1935950 - https://svn.apache.org/r1935951 - https://svn.apache.org/r1935952 - https://svn.apache.org/r1935953 - https://svn.apache.org/r1935954 - https://svn.apache.org/r1937104 - https://svn.apache.org/r1937105 - https://svn.apache.org/r1937106 - https://svn.apache.org/r1937107 - https://svn.apache.org/r1937108 - https://svn.apache.org/r1937109 - https://svn.apache.org/r1937110 - https://svn.apache.org/r1937111 - 2.4.x patch: https://github.com/apache/httpd/pull/706.patch - +1: jorton, covener, jim ------------------------------------------------------------------------ diff --git a/changes-entries/md-status.txt b/changes-entries/md-status.txt deleted file mode 100644 index cf6d627658..0000000000 --- a/changes-entries/md-status.txt +++ /dev/null @@ -1,3 +0,0 @@ - *) mod_md: MDServerStatus is now disabled by default. [Joe Orton] - - diff --git a/modules/aaa/config.m4 b/modules/aaa/config.m4 index b443761d2b..766354a145 100644 --- a/modules/aaa/config.m4 +++ b/modules/aaa/config.m4 @@ -70,9 +70,9 @@ APACHE_MODULE(auth_basic, basic authentication, , , yes) APACHE_MODULE(auth_form, form authentication, , , most) APACHE_MODULE(auth_digest, RFC2617 Digest authentication, , , most, [ APR_CHECK_APR_DEFINE(APR_HAS_RANDOM) - if test $ac_cv_define_APR_HAS_RANDOM = "no"; then - echo "You need APR random support to use mod_auth_digest." - echo "Look at APR configure options --with-egd and --with-devrandom." + APR_CHECK_APR_DEFINE(APR_HAS_SHARED_MEMORY) + if test "${ac_cv_define_APR_HAS_RANDOM}X${ac_cv_define_APR_HAS_SHARED_MEMORY}" != "yesXyes"; then + AC_MSG_NOTICE([mod_auth_digest requires APR with random and shared memory support]) enable_auth_digest="no" fi ]) diff --git a/modules/aaa/mod_auth_digest.c b/modules/aaa/mod_auth_digest.c index f10af0da81..0267a83be0 100644 --- a/modules/aaa/mod_auth_digest.c +++ b/modules/aaa/mod_auth_digest.c @@ -18,7 +18,7 @@ * mod_auth_digest: MD5 digest authentication * * Originally by Alexei Kosut - * Updated to RFC-2617 by Ronald Tschal�r + * Updated to RFC-2617 by Ronald Tschalär * based on mod_auth, by Rob McCool and Robert S. Thau * * This module an updated version of modules/standard/mod_digest.c @@ -29,7 +29,7 @@ * Open Issues: * - qop=auth-int (when streams and trailer support available) * - nonce-format configurability - * - Proxy-Authorization-Info header is set by this module, but is + * - Proxy-Authentication-Info header is set by this module, but is * currently ignored by mod_proxy (needs patch to mod_proxy) * - The source of the secret should be run-time directive (with server * scope: RSRC_CONF) @@ -56,6 +56,7 @@ #include "apr_errno.h" #include "apr_global_mutex.h" #include "apr_strings.h" +#include "apr_atomic.h" #define APR_WANT_STRFUNC #include "apr_want.h" @@ -80,17 +81,19 @@ #include #endif +/* configure declines to build this module without both of these, so this + * only catches builds which don't use it. */ +#if !APR_HAS_RANDOM || !APR_HAS_SHARED_MEMORY +#error mod_auth_digest requires APR with random and shared memory support +#endif + /* struct to hold the configuration info */ typedef struct digest_config_struct { - const char *dir_name; authn_provider_list *providers; - const char *realm; - apr_array_header_t *qop_list; - apr_sha1_ctx_t nonce_ctx; apr_time_t nonce_lifetime; int check_nc; - const char *algorithm; + const char *algorithm; /* currently a constant (MD5). */ char *uri_list; } digest_config_rec; @@ -118,11 +121,20 @@ typedef struct digest_config_struct { /* client list definitions */ +/* Identifies a client entry. This is the value sent to the client in the + * opaque field of the challenge, and echoed back in its Authorization + * header; zero is never a valid id, and means "no client". Ids are counted + * out by client_id_counter, so this must remain the type which the atomics + * used on it take, and the "%u"/"%x" formats below must match it. */ +typedef apr_uint32_t client_id_t; + typedef struct hash_entry { - unsigned long key; /* the key for this entry */ + client_id_t key; /* the key for this entry */ struct hash_entry *next; /* next entry in the bucket */ - unsigned long nonce_count; /* for nonce-count checking */ - char last_nonce[NONCE_LEN+1]; /* for one-time nonce's */ + unsigned long nonce_count; /* highest nonce-count seen + * for last_nonce_time */ + apr_time_t last_nonce_time; /* nonce of the last request + * accepted for this client */ } client_entry; static struct hash_table { @@ -139,6 +151,14 @@ static struct hash_table { enum hdr_sts { NO_HEADER, NOT_DIGEST, INVALID, VALID }; +/* Outcome of checking a request's nonce and nonce-count against the state + * tracked for its client. */ +enum nonce_state { + NONCE_ACCEPTED, /* recorded as the latest used by this client */ + NONCE_STALE, /* already used, or the client is unknown */ + NONCE_BAD_COUNT /* nonce-count did not increase: possible replay */ +}; + typedef struct digest_header_struct { const char *scheme; const char *realm; @@ -150,7 +170,7 @@ typedef struct digest_header_struct { const char *algorithm; const char *cnonce; const char *opaque; - unsigned long opaque_num; + client_id_t opaque_num; const char *message_qop; const char *nonce_count; /* the following fields are not (directly) from the header */ @@ -160,7 +180,6 @@ typedef struct digest_header_struct { enum hdr_sts auth_hdr_sts; int needed_auth; const char *ha1; - client_entry *client; } digest_header_rec; @@ -177,12 +196,10 @@ static unsigned char *secret; static apr_shm_t *client_shm = NULL; static apr_rmm_t *client_rmm = NULL; -static unsigned long *opaque_cntr; -static apr_time_t *otn_counter; /* one-time-nonce counter */ +static volatile client_id_t *client_id_counter; +static volatile apr_uint32_t *otn_counter; /* one-time-nonce counter */ static apr_global_mutex_t *client_lock = NULL; -static apr_global_mutex_t *opaque_lock = NULL; static const char *client_mutex_type = "authdigest-client"; -static const char *opaque_mutex_type = "authdigest-opaque"; static const char *client_shm_filename; #define DEF_SHMEM_SIZE 1000L /* ~ 12 entries */ @@ -219,11 +236,6 @@ static apr_status_t cleanup_tables(void *not_used) client_lock = NULL; } - if (opaque_lock) { - apr_global_mutex_destroy(opaque_lock); - opaque_lock = NULL; - } - client_list = NULL; return APR_SUCCESS; @@ -258,8 +270,6 @@ static apr_status_t rmm_free(apr_rmm_t *rmm, void *alloc) return apr_rmm_free(rmm, offset); } -#if APR_HAS_SHARED_MEMORY - static int initialize_tables(server_rec *s, apr_pool_t *ctx) { unsigned long idx; @@ -272,7 +282,6 @@ static int initialize_tables(server_rec *s, apr_pool_t *ctx) client_shm = NULL; client_rmm = NULL; client_lock = NULL; - opaque_lock = NULL; client_list = NULL; /* @@ -334,20 +343,12 @@ static int initialize_tables(server_rec *s, apr_pool_t *ctx) /* setup opaque */ - opaque_cntr = rmm_malloc(client_rmm, sizeof(*opaque_cntr)); - if (opaque_cntr == NULL) { + client_id_counter = rmm_malloc(client_rmm, sizeof *client_id_counter); + if (client_id_counter == NULL) { log_error_and_cleanup("failed to allocate shared memory", -1, s); return !OK; } - *opaque_cntr = 1UL; - - sts = ap_global_mutex_create(&opaque_lock, NULL, opaque_mutex_type, NULL, - s, ctx, 0); - if (sts != APR_SUCCESS) { - log_error_and_cleanup("failed to create lock (opaque_lock)", sts, s); - return !OK; - } - + *client_id_counter = 1; /* setup one-time-nonce counter */ @@ -364,17 +365,12 @@ static int initialize_tables(server_rec *s, apr_pool_t *ctx) return OK; } -#endif /* APR_HAS_SHARED_MEMORY */ - static int pre_init(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) { apr_status_t rv; void *retained; rv = ap_mutex_register(pconf, client_mutex_type, NULL, APR_LOCK_DEFAULT, 0); - if (rv != APR_SUCCESS) - return !OK; - rv = ap_mutex_register(pconf, opaque_mutex_type, NULL, APR_LOCK_DEFAULT, 0); if (rv != APR_SUCCESS) return !OK; @@ -383,11 +379,7 @@ static int pre_init(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) retained = ap_retained_data_create(RETAINED_DATA_ID, SECRET_LEN); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(01757) "generating secret for digest authentication"); -#if APR_HAS_RANDOM rv = apr_generate_random_bytes(retained, SECRET_LEN); -#else -#error APR random number support is missing -#endif if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(01758) "error generating secret"); @@ -407,7 +399,6 @@ static int initialize_module(apr_pool_t *p, apr_pool_t *plog, if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) return OK; -#if APR_HAS_SHARED_MEMORY /* Note: this stuff is currently fixed for the lifetime of the server, * i.e. even across restarts. This means that A) any shmem-size * configuration changes are ignored, and B) certain optimizations, @@ -418,21 +409,13 @@ static int initialize_module(apr_pool_t *p, apr_pool_t *plog, * last child dies. Therefore we can never clean up the old stuff, * creating a creeping memory leak. */ - if (initialize_tables(s, p) != OK) { - return !OK; - } -#endif /* APR_HAS_SHARED_MEMORY */ - return OK; + return initialize_tables(s, p); } static void initialize_child(apr_pool_t *p, server_rec *s) { apr_status_t sts; - if (!client_shm) { - return; - } - /* Get access to rmm in child */ sts = apr_rmm_attach(&client_rmm, NULL, @@ -450,13 +433,6 @@ static void initialize_child(apr_pool_t *p, server_rec *s) log_error_and_cleanup("failed to create lock (client_lock)", sts, s); return; } - sts = apr_global_mutex_child_init(&opaque_lock, - apr_global_mutex_lockfile(opaque_lock), - p); - if (sts != APR_SUCCESS) { - log_error_and_cleanup("failed to create lock (opaque_lock)", sts, s); - return; - } } /* @@ -465,56 +441,14 @@ static void initialize_child(apr_pool_t *p, server_rec *s) static void *create_digest_dir_config(apr_pool_t *p, char *dir) { - digest_config_rec *conf; + digest_config_rec *conf = apr_pcalloc(p, sizeof *conf); - if (dir == NULL) { - return NULL; - } - - conf = (digest_config_rec *) apr_pcalloc(p, sizeof(digest_config_rec)); - if (conf) { - conf->qop_list = apr_array_make(p, 2, sizeof(char *)); - conf->nonce_lifetime = DFLT_NONCE_LIFE; - conf->dir_name = apr_pstrdup(p, dir); - conf->algorithm = DFLT_ALGORITHM; - } + conf->nonce_lifetime = DFLT_NONCE_LIFE; + conf->algorithm = DFLT_ALGORITHM; return conf; } -static const char *set_realm(cmd_parms *cmd, void *config, const char *realm) -{ - digest_config_rec *conf = (digest_config_rec *) config; -#ifdef AP_DEBUG - int i; - - /* check that we got random numbers */ - for (i = 0; i < SECRET_LEN; i++) { - if (secret[i] != 0) - break; - } - ap_assert(i < SECRET_LEN); -#endif - - /* The core already handles the realm, but it's just too convenient to - * grab it ourselves too and cache some setups. However, we need to - * let the core get at it too, which is why we decline at the end - - * this relies on the fact that http_core is last in the list. - */ - conf->realm = realm; - - /* we precompute the part of the nonce hash that is constant (well, - * the host:port would be too, but that varies for .htaccess files - * and directives outside a virtual host section) - */ - apr_sha1_init(&conf->nonce_ctx); - apr_sha1_update_binary(&conf->nonce_ctx, secret, SECRET_LEN); - apr_sha1_update_binary(&conf->nonce_ctx, (const unsigned char *) realm, - strlen(realm)); - - return DECLINE_CMD; -} - static const char *add_authn_provider(cmd_parms *cmd, void *config, const char *arg) { @@ -562,23 +496,10 @@ static const char *add_authn_provider(cmd_parms *cmd, void *config, static const char *set_qop(cmd_parms *cmd, void *config, const char *op) { - digest_config_rec *conf = (digest_config_rec *) config; - - if (!ap_cstr_casecmp(op, "none")) { - apr_array_clear(conf->qop_list); - *(const char **)apr_array_push(conf->qop_list) = "none"; - return NULL; + if (ap_cstr_casecmp(op, "auth")) { + return "AuthDigestQop is deprecated: only 'auth' is supported"; } - if (!ap_cstr_casecmp(op, "auth-int")) { - return "AuthDigestQop auth-int is not implemented"; - } - else if (ap_cstr_casecmp(op, "auth")) { - return apr_pstrcat(cmd->pool, "Unrecognized qop: ", op, NULL); - } - - *(const char **)apr_array_push(conf->qop_list) = op; - return NULL; } @@ -599,37 +520,19 @@ static const char *set_nonce_lifetime(cmd_parms *cmd, void *config, return NULL; } -static const char *set_nonce_format(cmd_parms *cmd, void *config, - const char *fmt) -{ - return "AuthDigestNonceFormat is not implemented"; -} - static const char *set_nc_check(cmd_parms *cmd, void *config, int flag) { -#if !APR_HAS_SHARED_MEMORY - if (flag) { - return "AuthDigestNcCheck: ERROR: nonce-count checking " - "is not supported on platforms without shared-memory " - "support"; - } -#endif - ((digest_config_rec *) config)->check_nc = flag; return NULL; } static const char *set_algorithm(cmd_parms *cmd, void *config, const char *alg) { - if (!ap_cstr_casecmp(alg, "MD5-sess")) { - return "AuthDigestAlgorithm: ERROR: algorithm `MD5-sess' " - "is not implemented"; - } - else if (ap_cstr_casecmp(alg, "MD5")) { - return apr_pstrcat(cmd->pool, "Invalid algorithm in AuthDigestAlgorithm: ", alg, NULL); + if (ap_cstr_casecmp(alg, "MD5")) { + return apr_pstrcat(cmd->pool, "Unsupported algorithm in AuthDigestAlgorithm: ", alg, NULL); } - ((digest_config_rec *) config)->algorithm = alg; + /* conf->algorithm remains the constant, "MD5". */ return NULL; } @@ -689,16 +592,12 @@ static const char *set_shmem_size(cmd_parms *cmd, void *config, static const command_rec digest_cmds[] = { - AP_INIT_TAKE1("AuthName", set_realm, NULL, OR_AUTHCFG, - "The authentication realm (e.g. \"Members Only\")"), AP_INIT_ITERATE("AuthDigestProvider", add_authn_provider, NULL, OR_AUTHCFG, "specify the auth providers for a directory or location"), AP_INIT_ITERATE("AuthDigestQop", set_qop, NULL, OR_AUTHCFG, "A list of quality-of-protection options"), AP_INIT_TAKE1("AuthDigestNonceLifetime", set_nonce_lifetime, NULL, OR_AUTHCFG, "Maximum lifetime of the server nonce (seconds)"), - AP_INIT_TAKE1("AuthDigestNonceFormat", set_nonce_format, NULL, OR_AUTHCFG, - "The format to use when generating the server nonce"), AP_INIT_FLAG("AuthDigestNcCheck", set_nc_check, NULL, OR_AUTHCFG, "Whether or not to check the nonce-count sent by the client"), AP_INIT_TAKE1("AuthDigestAlgorithm", set_algorithm, NULL, OR_AUTHCFG, @@ -747,39 +646,41 @@ static const command_rec digest_cmds[] = * above algorithm is really sufficient) a set of counters is kept * indicating the number of clients held, the number of garbage collected * clients, and the number of erroneously purged clients. These are printed - * out at each garbage collection run. Note that access to the counters is - * not synchronized because they are just indicaters, and whether they are - * off by a few doesn't matter; and for the same reason no attempt is made - * to guarantee the num_renewed is correct in the face of clients spoofing - * the opaque field. + * out at each garbage collection run. Note that no attempt is made to + * guarantee that num_renewed is correct in the face of clients spoofing + * the opaque field; it is just an indicator, and whether it is off by a + * few doesn't matter. */ /* - * Get the client given its client number (the key). Returns the entry, - * or NULL if it's not found. + * Find the client given its client number (the key), moving it to the + * front of its bucket. Returns the entry, or NULL if it's not found. * - * Access to the list itself is synchronized via locks. However, access - * to the entry returned by get_client() is NOT synchronized. This means - * that there are potentially problems if a client uses multiple, - * simultaneous connections to access url's within the same protection - * space. However, these problems are not new: when using multiple - * connections you have no guarantee of the order the requests are - * processed anyway, so you have problems with the nonce-count and - * one-time nonces anyway. + * MUST be called with client_lock held, and the entry returned MUST NOT be + * used outside that critical section: it lives in the shared memory + * segment, where gc() can free it at any time on behalf of another + * process. The accessors below are the only supported way to reach a + * client entry; each looks it up afresh, so a client which has since been + * garbage collected is simply reported as unknown and the caller goes on + * to issue a new challenge for it. + * + * Note that this still gives no ordering guarantee for a client using + * multiple simultaneous connections within the same protection space: the + * requests can be processed in any order, so the nonce-count and one-time + * nonce checks may reject some of them. That is not new. */ -static client_entry *get_client(unsigned long key, const request_rec *r) +static client_entry *find_client(client_id_t key) { int bucket; client_entry *entry, *prev = NULL; - - if (!key || !client_shm) return NULL; + if (!key) { + return NULL; + } bucket = key % client_list->tbl_len; entry = client_list->table[bucket]; - apr_global_mutex_lock(client_lock); - while (entry && key != entry->key) { prev = entry; entry = entry->next; @@ -791,18 +692,120 @@ static client_entry *get_client(unsigned long key, const request_rec *r) client_list->table[bucket] = entry; } + return entry; +} + + +/* Determine whether the client identified by key is still known. */ +static int client_exists(client_id_t key, const request_rec *r) +{ + int found; + + apr_global_mutex_lock(client_lock); + found = find_client(key) != NULL; apr_global_mutex_unlock(client_lock); - if (entry) { + if (found) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01764) - "get_client(): client %lu found", key); + "client %u found", key); } else { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01765) - "get_client(): client %lu not found", key); + "client %u not found", key); } - return entry; + return found; +} + + +/* Note that a client entry was created to replace one which had been + * garbage collected. */ +static void client_note_renewed(void) +{ + apr_global_mutex_lock(client_lock); + client_list->num_renewed++; + apr_global_mutex_unlock(client_lock); +} + + +/* Check the nonce generated at nonce_time, and the nonce-count nc sent + * with it, against the state tracked for the client identified by key, and + * record them if acceptable. + * + * Both nonce_time and the count are compared against what the client last + * *used*, never against what was last issued to it: a nonce is issued + * whenever a challenge is generated, and anything quoting the client's + * opaque can provoke a challenge, so tracking what was issued lets an + * unauthenticated request invalidate the nonce which the legitimate client + * is holding. + * + * A one-time nonce (AuthDigestNonceLifetime 0) is therefore accepted iff + * it is newer than the last nonce this client used, which permits it + * exactly once. Otherwise, with AuthDigestNcCheck, a newer nonce starts a + * new count and the same nonce must raise it. + * + * Must only be called for a request which is fully verified - both the + * response digest and the nonce - so that a request which fails to + * authenticate cannot alter the state tracked for the client whose opaque + * it quotes. */ +static enum nonce_state client_update_nonce(const request_rec *r, + client_id_t key, + const digest_config_rec *conf, + apr_time_t nonce_time, + unsigned long nc, + const char *nonce) +{ + client_entry *client; + unsigned long tracked = 0; + enum nonce_state state; + int known; + + apr_global_mutex_lock(client_lock); + client = find_client(key); + known = (client != NULL); + if (!known) { + state = NONCE_STALE; + } + else { + tracked = client->nonce_count; + if (conf->nonce_lifetime == 0) { + /* one-time nonce: usable until it has been used */ + state = (nonce_time > client->last_nonce_time) + ? NONCE_ACCEPTED : NONCE_STALE; + } + else if (nonce_time > client->last_nonce_time + || (nonce_time == client->last_nonce_time && nc > tracked)) { + state = NONCE_ACCEPTED; + } + else { + state = NONCE_BAD_COUNT; + } + + if (state == NONCE_ACCEPTED) { + client->last_nonce_time = nonce_time; + client->nonce_count = nc; + } + } + apr_global_mutex_unlock(client_lock); + + if (!known) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(10618) + "client %u is no longer known - sending new nonce", + key); + } + else if (state == NONCE_STALE) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01779) + "user %s: one-time-nonce %s already used - sending " + "new nonce", r->user, nonce); + } + else if (state == NONCE_BAD_COUNT) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01774) + "Warning, possible replay attack: nonce-count check " + "failed: %lu is not above %lu for nonce %s", nc, + tracked, nonce); + } + + return state; } @@ -810,7 +813,7 @@ static client_entry *get_client(unsigned long key, const request_rec *r) * last entry in each bucket and updates the counters. Returns the * number of removed entries. */ -static long gc(server_rec *s) +static unsigned long gc(server_rec *s) { client_entry *entry, *prev; unsigned long num_removed = 0, idx; @@ -860,18 +863,17 @@ static long gc(server_rec *s) /* - * Add a new client to the list. Returns the entry if successful, NULL - * otherwise. This triggers the garbage collection if memory is low. + * Add a new client to the list. Returns non-zero if successful, zero + * otherwise. This triggers the garbage collection if memory is low. (The + * new entry is not returned: see find_client().) */ -static client_entry *add_client(unsigned long key, client_entry *info, - server_rec *s) +static int add_client(client_id_t key, client_entry *info, server_rec *s) { int bucket; client_entry *entry; - - if (!key || !client_shm) { - return NULL; + if (!key) { + return 0; } bucket = key % client_list->tbl_len; @@ -882,19 +884,17 @@ static client_entry *add_client(unsigned long key, client_entry *info, entry = rmm_malloc(client_rmm, sizeof(client_entry)); if (!entry) { - long num_removed = gc(s); + unsigned long num_removed = gc(s); ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01766) - "gc'd %ld client entries. Total new clients: " - "%ld; Total removed clients: %ld; Total renewed clients: " - "%ld", num_removed, + "gc'd %lu client entries. Total new clients: " + "%lu; Total removed clients: %lu; Total renewed clients: " + "%lu", num_removed, client_list->num_created - client_list->num_renewed, client_list->num_removed, client_list->num_renewed); entry = rmm_malloc(client_rmm, sizeof(client_entry)); if (!entry) { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01767) - "unable to allocate new auth_digest client"); apr_global_mutex_unlock(client_lock); - return NULL; /* give up */ + return 0; /* give up; the caller logs this */ } } @@ -910,9 +910,9 @@ static client_entry *add_client(unsigned long key, client_entry *info, apr_global_mutex_unlock(client_lock); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01768) - "allocated new client %lu", key); + "allocated new client %u", key); - return entry; + return 1; } @@ -920,8 +920,10 @@ static client_entry *add_client(unsigned long key, client_entry *info, * Authorization header parser code */ -/* Parse the Authorization header, if it exists */ -static int get_digest_rec(request_rec *r, digest_header_rec *resp) +/* Parse the Authorization header, if it exists, into resp; returns the + * status of the header. */ +static enum hdr_sts parse_digest_header(request_rec *r, + digest_header_rec *resp) { const char *auth_line; apr_size_t l; @@ -933,14 +935,12 @@ static int get_digest_rec(request_rec *r, digest_header_rec *resp) ? "Proxy-Authorization" : "Authorization"); if (!auth_line) { - resp->auth_hdr_sts = NO_HEADER; - return !OK; + return NO_HEADER; } resp->scheme = ap_getword_white(r->pool, &auth_line); if (ap_cstr_casecmp(resp->scheme, "Digest")) { - resp->auth_hdr_sts = NOT_DIGEST; - return !OK; + return NOT_DIGEST; } l = strlen(auth_line); @@ -1028,34 +1028,40 @@ static int get_digest_rec(request_rec *r, digest_header_rec *resp) || !VALID_NONCE(resp->nonce) || !resp->digest || strlen(resp->digest) != MD5_DIGEST_LEN || (resp->message_qop && (!resp->cnonce || !resp->nonce_count))) { - resp->auth_hdr_sts = INVALID; - return !OK; + return INVALID; } if (resp->opaque) { - resp->opaque_num = (unsigned long) strtol(resp->opaque, NULL, 16); + char *endptr; + long num; + + errno = 0; + num = strtol(resp->opaque, &endptr, 16); + if (errno == 0 && *endptr == '\0' && num > 0 + && num <= APR_UINT32_MAX) + resp->opaque_num = (client_id_t)num; } - resp->auth_hdr_sts = VALID; - return OK; + return VALID; } -/* Because the browser may preemptively send auth info, incrementing the - * nonce-count when it does, and because the client does not get notified - * if the URI didn't need authentication after all, we need to be sure to - * update the nonce-count each time we receive an Authorization header no - * matter what the final outcome of the request. Furthermore this is a - * convenient place to get the request-uri (before any subrequests etc - * are initiated) and to initialize the request_config. +/* Set up the per-request record: this is the place to get the request-uri + * (before any subrequests etc are initiated), to initialize the + * request_config, and to parse the Authorization header. + * + * Note that the nonce-count tracked for the client is deliberately NOT + * updated here: the state of an authenticated client must not be altered + * by a request which has not (yet) been authenticated, or a replayed or + * bogus request quoting the client's opaque would be able to rewind that + * state. See check_and_update_nc(). * * Note that this must be called after mod_proxy had its go so that * r->proxyreq is set correctly. */ -static int parse_hdr_and_update_nc(request_rec *r) +static int init_digest_request(request_rec *r) { digest_header_rec *resp; - int res; if (!ap_is_initial_req(r)) { return DECLINED; @@ -1068,11 +1074,7 @@ static int parse_hdr_and_update_nc(request_rec *r) resp->method = r->method; ap_set_module_config(r->request_config, &auth_digest_module, resp); - res = get_digest_rec(r, resp); - resp->client = get_client(resp->opaque_num, r); - if (res == OK && resp->client) { - resp->client->nonce_count++; - } + resp->auth_hdr_sts = parse_digest_header(r, resp); return DECLINED; } @@ -1082,18 +1084,16 @@ static int parse_hdr_and_update_nc(request_rec *r) * minimum size (NONCE_HASH_LEN+1). */ static void gen_nonce_hash(char hash[NONCE_HASH_LEN+1], const char *timestr, const char *opaque, const server_rec *server, - const digest_config_rec *conf) + const digest_config_rec *conf, + const char *realm) { unsigned char sha1[APR_SHA1_DIGESTSIZE]; apr_sha1_ctx_t ctx; - memcpy(&ctx, &conf->nonce_ctx, sizeof(ctx)); - /* - apr_sha1_update_binary(&ctx, (const unsigned char *) server->server_hostname, - strlen(server->server_hostname)); - apr_sha1_update_binary(&ctx, (const unsigned char *) &server->port, - sizeof(server->port)); - */ + apr_sha1_init(&ctx); + apr_sha1_update_binary(&ctx, secret, SECRET_LEN); + apr_sha1_update_binary(&ctx, (const unsigned char *) realm, strlen(realm)); + apr_sha1_update_binary(&ctx, (const unsigned char *) timestr, strlen(timestr)); if (opaque) { apr_sha1_update_binary(&ctx, (const unsigned char *) opaque, @@ -1109,7 +1109,8 @@ static void gen_nonce_hash(char hash[NONCE_HASH_LEN+1], const char *timestr, con */ static const char *gen_nonce(apr_pool_t *p, apr_time_t now, const char *opaque, const server_rec *server, - const digest_config_rec *conf) + const digest_config_rec *conf, + const char *realm) { char *nonce = apr_palloc(p, NONCE_LEN+1); time_rec t; @@ -1117,18 +1118,14 @@ static const char *gen_nonce(apr_pool_t *p, apr_time_t now, const char *opaque, if (conf->nonce_lifetime != 0) { t.time = now; } - else if (otn_counter) { - /* this counter is not synch'd, because it doesn't really matter - * if it counts exactly. - */ - t.time = (*otn_counter)++; - } else { - /* XXX: WHAT IS THIS CONSTANT? */ - t.time = 42; + /* Nonces are ordered by this counter rather than by time; the +1 + * is because apr_atomic_inc32() returns the previous value, and a + * nonce time of zero means "no nonce used yet" in a client entry. */ + t.time = apr_atomic_inc32(otn_counter) + 1; } apr_base64_encode_binary(nonce, t.arr, sizeof(t.arr)); - gen_nonce_hash(nonce+NONCE_TIME_LEN, nonce, opaque, server, conf); + gen_nonce_hash(nonce+NONCE_TIME_LEN, nonce, opaque, server, conf, realm); return nonce; } @@ -1139,29 +1136,29 @@ static const char *gen_nonce(apr_pool_t *p, apr_time_t now, const char *opaque, */ /* - * Generate a new client entry, add it to the list, and return the - * entry. Returns NULL if failed. + * Generate a new client entry and add it to the list. Returns the key of + * the new entry, or 0 if it failed. (The entry itself is deliberately not + * returned: see find_client().) */ -static client_entry *gen_client(const request_rec *r) +static client_id_t client_generate(const request_rec *r) { - unsigned long op; - client_entry new_entry = { 0, NULL, 0, "" }, *entry; + client_id_t op = apr_atomic_inc32(client_id_counter); + client_entry new_entry = { 0, NULL, 0, 0 }; - if (!opaque_cntr) { - return NULL; + /* The counter wraps after 2^32 clients: skip an id of zero, which means + * "no client" and which add_client() would refuse. */ + if (op == 0) { + op = apr_atomic_inc32(client_id_counter); } - apr_global_mutex_lock(opaque_lock); - op = (*opaque_cntr)++; - apr_global_mutex_unlock(opaque_lock); - - if (!(entry = add_client(op, &new_entry, r->server))) { + if (!add_client(op, &new_entry, r->server)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01769) - "failed to allocate client entry - ignoring client"); - return NULL; + "unable to allocate a client entry - failing the " + "request, since this configuration needs one"); + return 0; } - return entry; + return op; } @@ -1169,79 +1166,66 @@ static client_entry *gen_client(const request_rec *r) * Authorization challenge generation code (for WWW-Authenticate) */ -static const char *ltox(apr_pool_t *p, unsigned long num) +/* Format a client id as the opaque sent to the client. Never called with + * zero: the callers check client_generate() for failure first. */ +static const char *ltox(apr_pool_t *p, client_id_t num) { - if (num != 0) { - return apr_psprintf(p, "%lx", num); - } - else { - return ""; - } + return apr_psprintf(p, "%x", num); } -static void note_digest_auth_failure(request_rec *r, - const digest_config_rec *conf, - digest_header_rec *resp, int stale) +/* Generate a challenge for the client, and return the status which the + * caller should return for this request: HTTP_UNAUTHORIZED normally, or + * HTTP_SERVICE_UNAVAILABLE if the per-client state which this configuration + * requires could not be allocated. No challenge is sent in that case: it + * could only carry an opaque which identifies nothing, so the client would + * be unable to authenticate through it however often it retried. */ +static int note_digest_auth_failure(request_rec *r, + const digest_config_rec *conf, + digest_header_rec *resp, int stale) { - const char *qop, *opaque, *opaque_param, *domain, *nonce; + const char *qop, *opaque = NULL, *opaque_param = "", *domain, *nonce; + client_id_t client_key = 0; /* Setup qop */ - if (apr_is_empty_array(conf->qop_list)) { - qop = ", qop=\"auth\""; - } - else if (!ap_cstr_casecmp(*(const char **)(conf->qop_list->elts), "none")) { - qop = ""; - } - else { - qop = apr_pstrcat(r->pool, ", qop=\"", - apr_array_pstrcat(r->pool, conf->qop_list, ','), - "\"", - NULL); - } + qop = ", qop=\"auth\""; /* Setup opaque */ if (resp->opaque == NULL) { /* new client */ - if ((conf->check_nc || conf->nonce_lifetime == 0) - && (resp->client = gen_client(r)) != NULL) { - opaque = ltox(r->pool, resp->client->key); - } - else { - opaque = ""; /* opaque not needed */ + if (conf->check_nc || conf->nonce_lifetime == 0) { + if ((client_key = client_generate(r)) == 0) { + return HTTP_SERVICE_UNAVAILABLE; + } + opaque = ltox(r->pool, client_key); } + /* else no opaque is needed, and none is sent */ } - else if (resp->client == NULL) { + else if (!client_exists(resp->opaque_num, r)) { /* client info was gc'd */ - resp->client = gen_client(r); - if (resp->client != NULL) { - opaque = ltox(r->pool, resp->client->key); - stale = 1; - client_list->num_renewed++; - } - else { - opaque = ""; /* ??? */ + if ((client_key = client_generate(r)) == 0) { + return HTTP_SERVICE_UNAVAILABLE; } + opaque = ltox(r->pool, client_key); + stale = 1; + client_note_renewed(); } else { + /* Note that the nonce-count tracked for this client is left alone + * here: the client may not even see this challenge (it may have + * been triggered by somebody else quoting its opaque), and it is + * tied to the nonce it was counted for in any case. */ + client_key = resp->opaque_num; opaque = resp->opaque; - /* we're generating a new nonce, so reset the nonce-count */ - resp->client->nonce_count = 0; } - if (opaque[0]) { + if (opaque) { opaque_param = apr_pstrcat(r->pool, ", opaque=\"", opaque, "\"", NULL); } - else { - opaque_param = NULL; - } /* Setup nonce */ - nonce = gen_nonce(r->pool, r->request_time, opaque, r->server, conf); - if (resp->client && conf->nonce_lifetime == 0) { - memcpy(resp->client->last_nonce, nonce, NONCE_LEN+1); - } + nonce = gen_nonce(r->pool, r->request_time, opaque, r->server, conf, ap_auth_name(r)); /* setup domain attribute. We want to send this attribute wherever * possible so that the client won't send the Authorization header @@ -1266,10 +1250,11 @@ static void note_digest_auth_failure(request_rec *r, apr_psprintf(r->pool, "Digest realm=\"%s\", " "nonce=\"%s\", algorithm=%s%s%s%s%s", ap_auth_name(r), nonce, conf->algorithm, - opaque_param ? opaque_param : "", + opaque_param, domain ? domain : "", stale ? ", stale=true" : "", qop)); + return HTTP_UNAUTHORIZED; } static int hook_note_digest_auth_failure(request_rec *r, const char *auth_type) @@ -1344,7 +1329,7 @@ static authn_status get_hash(request_rec *r, const char *user, /* We expect the password to be md5 hash of user:realm:password */ - auth_result = provider->get_realm_hash(r, user, conf->realm, + auth_result = provider->get_realm_hash(r, user, ap_auth_name(r), &password); apr_table_unset(r->notes, AUTHN_PROVIDER_NAME_NOTE); @@ -1369,57 +1354,49 @@ static authn_status get_hash(request_rec *r, const char *user, return auth_result; } -static int check_nc(const request_rec *r, const digest_header_rec *resp, - const digest_config_rec *conf) +/* Check the nonce and nonce-count of a fully verified request against the + * state tracked for its client, record them, and generate a new challenge + * if they are not acceptable. + * + * The nonce-count is counted by the client per-nonce (RFC 7616 3.4.3), so + * the count tracked here is tied to the nonce it was counted for: a request + * using a newer nonce starts a new count. Within a single nonce the count + * must strictly increase, but it need not increase by exactly one: the + * client also counts the requests it sends to URIs in the protection space + * which turn out not to need authentication, and this server never sees + * those. + */ +static int check_and_record_nonce(request_rec *r, digest_header_rec *resp, + const digest_config_rec *conf) { unsigned long nc; const char *snc = resp->nonce_count; char *endptr; - if (conf->check_nc && !client_shm) { - /* Shouldn't happen, but just in case... */ - ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01771) - "cannot check nonce count without shared memory"); - return OK; - } - - if (!conf->check_nc || !client_shm) { - return OK; - } - - if (!apr_is_empty_array(conf->qop_list) && - !ap_cstr_casecmp(*(const char **)(conf->qop_list->elts), "none")) { - /* qop is none, client must not send a nonce count */ - if (snc != NULL) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01772) - "invalid nc %s received - no nonce count allowed when qop=none", - snc); - return !OK; - } - /* qop is none, cannot check nonce count */ - return OK; + if (!conf->check_nc && conf->nonce_lifetime != 0) { + return OK; /* nothing is tracked per-client */ } nc = strtol(snc, &endptr, 16); if (endptr < (snc+strlen(snc)) && !apr_isspace(*endptr)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01773) "invalid nc %s received - not a number", snc); - return !OK; + return note_digest_auth_failure(r, conf, resp, 0); } - if (!resp->client) { - return !OK; - } + switch (client_update_nonce(r, resp->opaque_num, conf, resp->nonce_time, + nc, resp->nonce)) { + case NONCE_ACCEPTED: + return OK; - if (nc != resp->client->nonce_count) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01774) - "Warning, possible replay attack: nonce-count " - "check failed: %lu != %lu", nc, - resp->client->nonce_count); - return !OK; - } + case NONCE_STALE: + /* the credentials were good, so the client can silently retry with + * the nonce from this challenge */ + return note_digest_auth_failure(r, conf, resp, 1); - return OK; + default: + return note_digest_auth_failure(r, conf, resp, 0); + } } static int check_nonce(request_rec *r, digest_header_rec *resp, @@ -1432,7 +1409,7 @@ static int check_nonce(request_rec *r, digest_header_rec *resp, tmp = resp->nonce[NONCE_TIME_LEN]; resp->nonce[NONCE_TIME_LEN] = '\0'; apr_base64_decode_binary(nonce_time.arr, resp->nonce); - gen_nonce_hash(hash, resp->nonce, resp->opaque, r->server, conf); + gen_nonce_hash(hash, resp->nonce, resp->opaque, r->server, conf, ap_auth_name(r)); resp->nonce[NONCE_TIME_LEN] = tmp; resp->nonce_time = nonce_time.time; @@ -1440,8 +1417,7 @@ static int check_nonce(request_rec *r, digest_header_rec *resp, ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01776) "invalid nonce %s received - hash is not %s", resp->nonce, hash); - note_digest_auth_failure(r, conf, resp, 1); - return HTTP_UNAUTHORIZED; + return note_digest_auth_failure(r, conf, resp, 1); } dt = r->request_time - nonce_time.time; @@ -1449,8 +1425,7 @@ static int check_nonce(request_rec *r, digest_header_rec *resp, ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01777) "invalid nonce %s received - user attempted " "time travel", resp->nonce); - note_digest_auth_failure(r, conf, resp, 1); - return HTTP_UNAUTHORIZED; + return note_digest_auth_failure(r, conf, resp, 1); } if (conf->nonce_lifetime > 0) { @@ -1460,39 +1435,17 @@ static int check_nonce(request_rec *r, digest_header_rec *resp, "- max lifetime %.2f) - sending new nonce", r->user, (double)apr_time_sec(dt), (double)apr_time_sec(conf->nonce_lifetime)); - note_digest_auth_failure(r, conf, resp, 1); - return HTTP_UNAUTHORIZED; + return note_digest_auth_failure(r, conf, resp, 1); } } - else if (conf->nonce_lifetime == 0 && resp->client) { - if (memcmp(resp->client->last_nonce, resp->nonce, NONCE_LEN)) { - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01779) - "user %s: one-time-nonce mismatch - sending " - "new nonce", r->user); - note_digest_auth_failure(r, conf, resp, 1); - return HTTP_UNAUTHORIZED; - } - } - /* else (lifetime < 0) => never expires */ + /* else (lifetime <= 0) => never expires by time; a one-time nonce is + * retired by use, in check_and_record_nonce() */ return OK; } /* The actual MD5 code... whee */ -/* RFC-2069 */ -static const char *old_digest(const request_rec *r, - const digest_header_rec *resp) -{ - const char *ha2; - - ha2 = ap_md5(r->pool, (unsigned char *)apr_pstrcat(r->pool, resp->method, ":", - resp->uri, NULL)); - return ap_md5(r->pool, - (unsigned char *)apr_pstrcat(r->pool, resp->ha1, ":", - resp->nonce, ":", ha2, NULL)); -} - /* RFC-2617 */ static const char *new_digest(const request_rec *r, digest_header_rec *resp) @@ -1577,6 +1530,7 @@ static int authenticate_digest_user(request_rec *r) const char *t; int res; authn_status return_code; + const char *realm; /* do we require Digest auth for this URI? */ @@ -1604,6 +1558,7 @@ static int authenticate_digest_user(request_rec *r) &auth_digest_module); resp->needed_auth = 1; + realm = ap_auth_name(r); /* get our conf */ @@ -1626,8 +1581,7 @@ static int authenticate_digest_user(request_rec *r) r->uri); } /* else (resp->auth_hdr_sts == NO_HEADER) */ - note_digest_auth_failure(r, conf, resp, 0); - return HTTP_UNAUTHORIZED; + return note_digest_auth_failure(r, conf, resp, 0); } r->user = (char *) resp->username; @@ -1727,24 +1681,23 @@ static int authenticate_digest_user(request_rec *r) ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01787) "received invalid opaque - got `%s'", resp->opaque); - note_digest_auth_failure(r, conf, resp, 0); - return HTTP_UNAUTHORIZED; + return note_digest_auth_failure(r, conf, resp, 0); } + + - if (!conf->realm) { + if (!realm) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02533) "realm mismatch - got `%s' but no realm specified", resp->realm); - note_digest_auth_failure(r, conf, resp, 0); - return HTTP_UNAUTHORIZED; + return note_digest_auth_failure(r, conf, resp, 0); } - if (!resp->realm || strcmp(resp->realm, conf->realm)) { + if (!resp->realm || strcmp(resp->realm, realm)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01788) "realm mismatch - got `%s' but expected `%s'", - resp->realm, conf->realm); - note_digest_auth_failure(r, conf, resp, 0); - return HTTP_UNAUTHORIZED; + resp->realm, realm); + return note_digest_auth_failure(r, conf, resp, 0); } if (resp->algorithm != NULL @@ -1752,8 +1705,7 @@ static int authenticate_digest_user(request_rec *r) ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01789) "unknown algorithm `%s' received: %s", resp->algorithm, r->uri); - note_digest_auth_failure(r, conf, resp, 0); - return HTTP_UNAUTHORIZED; + return note_digest_auth_failure(r, conf, resp, 0); } return_code = get_hash(r, r->user, conf, &resp->ha1); @@ -1761,9 +1713,8 @@ static int authenticate_digest_user(request_rec *r) if (return_code == AUTH_USER_NOT_FOUND) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01790) "user `%s' in realm `%s' not found: %s", - r->user, conf->realm, r->uri); - note_digest_auth_failure(r, conf, resp, 0); - return HTTP_UNAUTHORIZED; + r->user, realm, r->uri); + return note_digest_auth_failure(r, conf, resp, 0); } else if (return_code == AUTH_USER_FOUND) { /* we have a password, so continue */ @@ -1772,9 +1723,8 @@ static int authenticate_digest_user(request_rec *r) /* authentication denied in the provider before attempting a match */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01791) "user `%s' in realm `%s' denied by provider: %s", - r->user, conf->realm, r->uri); - note_digest_auth_failure(r, conf, resp, 0); - return HTTP_UNAUTHORIZED; + r->user, realm, r->uri); + return note_digest_auth_failure(r, conf, resp, 0); } else { /* AUTH_GENERAL_ERROR (or worse) @@ -1784,39 +1734,17 @@ static int authenticate_digest_user(request_rec *r) return HTTP_INTERNAL_SERVER_ERROR; } - if (resp->message_qop == NULL) { - /* old (rfc-2069) style digest */ - if (!ap_memeq_timingsafe(old_digest(r, resp), resp->digest, MD5_DIGEST_LEN)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01792) - "user %s: password mismatch: %s", r->user, - r->uri); - note_digest_auth_failure(r, conf, resp, 0); - return HTTP_UNAUTHORIZED; - } + if (resp->message_qop == NULL + || ap_cstr_casecmp(resp->message_qop, "auth")) { + /* RFC 2069-style Digest is no longer supported. */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10560) + "invalid or missing qop value '%s', RFC 2069 is " + "no longer supported: %s", resp->message_qop, r->uri); + return note_digest_auth_failure(r, conf, resp, 0); } else { - const char *exp_digest; - int match = 0, idx; - const char **tmp = (const char **)(conf->qop_list->elts); - for (idx = 0; idx < conf->qop_list->nelts; idx++) { - if (!ap_cstr_casecmp(*tmp, resp->message_qop)) { - match = 1; - break; - } - ++tmp; - } - - if (!match - && !(apr_is_empty_array(conf->qop_list) - && !ap_cstr_casecmp(resp->message_qop, "auth"))) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01793) - "invalid qop `%s' received: %s", - resp->message_qop, r->uri); - note_digest_auth_failure(r, conf, resp, 0); - return HTTP_UNAUTHORIZED; - } - - exp_digest = new_digest(r, resp); + /* RFC 2617 (or 7616)-style Digest hash calculation. */ + const char *exp_digest = new_digest(r, resp); if (!exp_digest) { /* we failed to allocate a client struct */ return HTTP_INTERNAL_SERVER_ERROR; @@ -1825,29 +1753,23 @@ static int authenticate_digest_user(request_rec *r) ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01794) "user %s: password mismatch: %s", r->user, r->uri); - note_digest_auth_failure(r, conf, resp, 0); - return HTTP_UNAUTHORIZED; + return note_digest_auth_failure(r, conf, resp, 0); } } - if (check_nc(r, resp, conf) != OK) { - note_digest_auth_failure(r, conf, resp, 0); - return HTTP_UNAUTHORIZED; - } - - /* Note: this check is done last so that a "stale=true" can be - generated if the nonce is old */ + /* Note: the nonce is checked before the nonce-count so that the + * nonce-count state is only ever updated for a request which is using + * a nonce this server issued, and so that a request using an expired + * nonce gets a "stale=true" challenge (and hence a silent retry with a + * fresh nonce-count) rather than being reported as a replay. */ if ((res = check_nonce(r, resp, conf))) { return res; } - return OK; + return check_and_record_nonce(r, resp, conf); } -/* - * Authorization-Info header code - */ - +/* Authentication-Info header code. */ static int add_auth_info(request_rec *r) { const digest_config_rec *conf = @@ -1862,40 +1784,32 @@ static int add_auth_info(request_rec *r) return OK; } - /* 2069-style entity-digest is not supported (it's too hard, and - * there are no clients which support 2069 but not 2617). */ + /* Don't add Authentication-Info for 401/407 responses. */ + if (apr_table_get(r->err_headers_out, + (r->proxyreq == PROXYREQ_PROXY) + ? "Proxy-Authenticate" : "WWW-Authenticate")) { + return OK; + } - /* setup nextnonce - */ + /* Set up nextnonce for one-time-nonces and expiring-nonce cases. */ if (conf->nonce_lifetime > 0) { /* send nextnonce if current nonce will expire in less than 30 secs */ if ((r->request_time - resp->nonce_time) > (conf->nonce_lifetime-NEXTNONCE_DELTA)) { nextnonce = apr_pstrcat(r->pool, ", nextnonce=\"", gen_nonce(r->pool, r->request_time, - resp->opaque, r->server, conf), + resp->opaque, r->server, conf, ap_auth_name(r)), "\"", NULL); - if (resp->client) - resp->client->nonce_count = 0; } } - else if (conf->nonce_lifetime == 0 && resp->client) { + else if (conf->nonce_lifetime == 0 && resp->opaque_num) { const char *nonce = gen_nonce(r->pool, 0, resp->opaque, r->server, - conf); + conf, ap_auth_name(r)); nextnonce = apr_pstrcat(r->pool, ", nextnonce=\"", nonce, "\"", NULL); - memcpy(resp->client->last_nonce, nonce, NONCE_LEN+1); } /* else nonce never expires, hence no nextnonce */ - /* do rfc-2069 digest - */ - if (!apr_is_empty_array(conf->qop_list) && - !ap_cstr_casecmp(*(const char **)(conf->qop_list->elts), "none") - && resp->message_qop == NULL) { - /* use only RFC-2069 format */ - ai = nextnonce; - } - else { + { const char *resp_dig, *ha1, *a2, *ha2; /* calculate rspauth attribute @@ -1950,7 +1864,7 @@ static void register_hooks(apr_pool_t *p) ap_hook_pre_config(pre_init, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_post_config(initialize_module, NULL, cfgPost, APR_HOOK_MIDDLE); ap_hook_child_init(initialize_child, NULL, NULL, APR_HOOK_MIDDLE); - ap_hook_post_read_request(parse_hdr_and_update_nc, parsePre, NULL, APR_HOOK_MIDDLE); + ap_hook_post_read_request(init_digest_request, parsePre, NULL, APR_HOOK_MIDDLE); ap_hook_check_authn(authenticate_digest_user, NULL, NULL, APR_HOOK_MIDDLE, AP_AUTH_INTERNAL_PER_CONF);