You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ji...@apache.org on 2026/09/01 10:45:54 UTC
svn commit: r1937721 - in httpd/httpd/branches/2.4.x: . changes-entries modules/aaa test test/pyhttpd test/pytest_suite
Author: jim
Date: Tue Sep 1 10:45:53 2026
New Revision: 1937721
Log:
Merge r1731662, r1934434, r1935944, r1935945, r1935946, r1935947, r1935948, r1935949, r1935950, r1935951, r1935952, r1935953, r1935954, r1937104, r1937105, r1937106, r1937107, r1937108, r1937109, r1937110, r1937111 from trunk:
PR59039 Digest not working with ap_expr based AuthName
Stop caching the configured realm during config processing and always
call ap_auth_name(r) to determine (=evaluate potential expression)
the configured realm
* modules/aaa/mod_auth_digest.c: Remove undocumented and unimplemented
AuthDigestNonceFormat directive.
PR: 70056
mod_auth_digest: Drop RFC 2069 and configurable qop support.
(RFC 2617, which replaced 2069, is now 26 years old)
* modules/aaa/mod_auth_digest.c (digest_config_rec): Remove qop_list
field.
(create_digest_dir_config): Remove qop_list initialization.
(set_qop): Deprecate AuthDigestQop, only "auth" is supported.
(note_digest_auth_failure): Always send qop="auth".
(check_nc): Remove handling for qop=none.
(old_digest): Remove function.
(authenticate_digest_user): Reject requests with missing or
non-"auth" qop value rather than falling back to RFC 2069.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
GitHub: PR #661
* modules/aaa/mod_auth_digest.c: Remove "weird" override of AuthName
directive, which adds complexity for little benefit (avoids putting
20 bytes through SHA1 for each auth attempt).
(set_realm): Remove function.
(gen_nonce_hash): Create the nonce hash here from scratch.
(create_digest_dir_config): Always allocate a config struct.
Remove unused dir_name field from digest_config_rec.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
GitHub: PR #661
* modules/aaa/mod_auth_digest.c (set_algorithm): Note that
conf->algorithm is a constant ("MD5") so there is no point
in overriding it at runtime. Simplify error case.
GitHub: PR #661
* modules/aaa/config.m4,
modules/aaa/mod_auth_digest.c (pre_init, initialize_module):
Prevent using mod_auth_digest without APR_HAS_SHARED_MEMORY.
GitHub: PR #661
* modules/aaa/mod_auth_digest.c: Use apr_uint32_t for the
one-time-nonce counter.
(gen_nonce): Drop the fallback case for non-shmem builds
where otn_counter could be NULL previously. Use atomics
to increment the counter.
GitHub: PR #661
* modules/aaa/mod_auth_digest.c (get_digest_rec):
Parse opaque more strictly.
GitHub: PR #661
* modules/aaa/mod_auth_digest.c (cleanup_tables, initialize_tables,
initialize_child, gen_client): Replace mutex-protected opaque_cntr
with opaque_counter, incremented via atomics.
GitHub: PR #661
* modules/aaa/mod_auth_digest.c (pre_init): Don't register
now-unused "authdigest-opaque" mutex type.
GitHub: PR #661
* modules/aaa/mod_auth_digest.c: Remove checks for NULL client_shm
throughout.
GitHub: PR #661
* modules/aaa/mod_auth_digest.c: Fix comments.
GitHub: PR #661
* modules/aaa/mod_auth_digest.c (add_auth_info): Don't add
(Proxy-)Authentication-Info headers to 401/407 responses.
GitHub: PR #661
* modules/aaa/mod_auth_digest.c: Fix AuthDigestNcCheck: track the
nonce-count per-nonce, require it to increase (RFC 7616 3.4.3), and
record it only for a fully verified request.
* test/modules/aaa/test_007_replay.py: Add replay tests.
* test/modules/aaa/test_003_nccheck.py, test/modules/aaa/conftest.py:
Update for the new semantics.
Assisted-by: Claude Opus 5 (1M context) <no...@anthropic.com>
GitHub: PR #705
* modules/aaa/mod_auth_digest.c: Consolidate one-time nonce handling
in check_and_record_nonce, accepting a nonce iff it is newer than
the last used, and re-challenging as stale when reused.
* test/modules/aaa/test_008_onetime_nccheck.py: Add tests for one-time
nonces, alone and with AuthDigestNcCheck.
* test/modules/aaa/conftest.py: Add an AuthDigestNonceLifetime 0 plus
AuthDigestNcCheck location.
Assisted-by: Claude Opus 5 (1M context) <no...@anthropic.com>
GitHub: PR #705
* modules/aaa/mod_auth_digest.c (get_digest_rec): Rename to
parse_digest_header, returning the header status the caller stores
in resp->auth_hdr_sts. (parse_digest_header): Rename to
init_digest_request.
Assisted-by: Claude Opus 5 (1M context) <no...@anthropic.com>
GitHub: PR #705
* modules/aaa/mod_auth_digest.c: Don't let a client_entry pointer
escape the client_lock critical section; look up and release per
access rather than caching the pointer across the request.
Assisted-by: Claude Opus 5 (1M context) <no...@anthropic.com>
GitHub: PR #705
* modules/aaa/mod_auth_digest.c (note_digest_auth_failure): Return 503
when the client entry cannot be allocated, rather than sending a
challenge with no opaque. Default the opaque to NULL to match what
check_nonce() hashes when the client sends none.
Assisted-by: Claude Opus 5 (1M context) <no...@anthropic.com>
GitHub: PR #705
* modules/aaa/mod_auth_digest.c (client_generate): Skip a client id
of zero, which the counter hands out once it wraps: zero means "no
client", so add_client() refuses it.
Assisted-by: Claude Opus 5 (1M context) <no...@anthropic.com>
GitHub: PR #705
* modules/aaa/mod_auth_digest.c (gc): Return unsigned long, the type
the count is accumulated in and the type of the client_list counters.
(add_client): Log those counters with %lu, all being unsigned.
Assisted-by: Claude Opus 5 (1M context) <no...@anthropic.com>
GitHub: PR #705
* modules/aaa/mod_auth_digest.c: Fail the build without APR_HAS_RANDOM
or APR_HAS_SHARED_MEMORY, rather than testing at runtime. configure
already declines to build the module without them.
Assisted-by: Claude Opus 5 (1M context) <no...@anthropic.com>
GitHub: PR #705
Submitted by: covener, jorton, jorton, jorton, jorton, jorton, jorton, jorton, jorton, jorton, jorton, jorton, jorton, jorton, jorton, jorton, jorton, jorton, jorton, jorton, jorton
Reviewed by:
Github: closes #706
Deleted:
httpd/httpd/branches/2.4.x/changes-entries/md-status.txt
Modified:
httpd/httpd/branches/2.4.x/ (props changed)
httpd/httpd/branches/2.4.x/CHANGES
httpd/httpd/branches/2.4.x/STATUS
httpd/httpd/branches/2.4.x/modules/aaa/config.m4
httpd/httpd/branches/2.4.x/modules/aaa/mod_auth_digest.c
httpd/httpd/branches/2.4.x/test/ (props changed)
httpd/httpd/branches/2.4.x/test/pyhttpd/conf.py (props changed)
httpd/httpd/branches/2.4.x/test/pyhttpd/env.py (props changed)
httpd/httpd/branches/2.4.x/test/pyhttpd/log.py (props changed)
httpd/httpd/branches/2.4.x/test/pyhttpd/pyproject.toml (props changed)
httpd/httpd/branches/2.4.x/test/pyhttpd/uv.lock (props changed)
httpd/httpd/branches/2.4.x/test/pytest_suite/ (props changed)
Modified: httpd/httpd/branches/2.4.x/CHANGES
==============================================================================
--- httpd/httpd/branches/2.4.x/CHANGES Tue Sep 1 10:37:43 2026 (r1937720)
+++ httpd/httpd/branches/2.4.x/CHANGES Tue Sep 1 10:45:53 2026 (r1937721)
@@ -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 <metsw24 gmail.com>]
Modified: httpd/httpd/branches/2.4.x/STATUS
==============================================================================
--- httpd/httpd/branches/2.4.x/STATUS Tue Sep 1 10:37:43 2026 (r1937720)
+++ httpd/httpd/branches/2.4.x/STATUS Tue Sep 1 10:45:53 2026 (r1937721)
@@ -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
------------------------------------------------------------------------
Modified: httpd/httpd/branches/2.4.x/modules/aaa/config.m4
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/aaa/config.m4 Tue Sep 1 10:37:43 2026 (r1937720)
+++ httpd/httpd/branches/2.4.x/modules/aaa/config.m4 Tue Sep 1 10:45:53 2026 (r1937721)
@@ -70,9 +70,9 @@ APACHE_MODULE(auth_basic, basic authenti
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
])
Modified: httpd/httpd/branches/2.4.x/modules/aaa/mod_auth_digest.c
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/aaa/mod_auth_digest.c Tue Sep 1 10:37:43 2026 (r1937720)
+++ httpd/httpd/branches/2.4.x/modules/aaa/mod_auth_digest.c Tue Sep 1 10:45:53 2026 (r1937721)
@@ -18,7 +18,7 @@
* mod_auth_digest: MD5 digest authentication
*
* Originally by Alexei Kosut <ak...@nueva.pvt.k12.ca.us>
- * Updated to RFC-2617 by Ronald Tschal�r <ro...@innovation.ch>
+ * Updated to RFC-2617 by Ronald Tschalär <ro...@innovation.ch>
* 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 <unistd.h>
#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
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 *
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
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
/* 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,8 +365,6 @@ static int initialize_tables(server_rec
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;
@@ -374,20 +373,13 @@ static int pre_init(apr_pool_t *pconf, a
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;
retained = ap_retained_data_get(RETAINED_DATA_ID);
if (retained == NULL) {
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
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
* 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
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
static void *create_digest_dir_config(apr_pool_t *p, char *dir)
{
- digest_config_rec *conf;
-
- if (dir == NULL) {
- return NULL;
- }
+ digest_config_rec *conf = apr_pcalloc(p, sizeof *conf);
- 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(cm
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(cm
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_pa
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.
+ *
+ * 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.
*
- * 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.
+ * 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
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
* 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
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
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
* 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
? "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
|| !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(reque
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(reque
* 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[NON
*/
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
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
*/
/*
- * 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 re
* Authorization challenge generation code (for WWW-Authenticate)
*/
-static const char *ltox(apr_pool_t *p, unsigned long num)
-{
- if (num != 0) {
- return apr_psprintf(p, "%lx", num);
- }
- else {
- return "";
- }
-}
-
-static void note_digest_auth_failure(request_rec *r,
- const digest_config_rec *conf,
- digest_header_rec *resp, int stale)
+/* 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)
+{
+ return apr_psprintf(p, "%x", num);
+}
+
+/* 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(req
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
/* 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
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, d
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, d
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, d
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, d
"- 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(requ
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(requ
&auth_digest_module);
resp->needed_auth = 1;
+ realm = ap_auth_name(r);
/* get our conf */
@@ -1626,8 +1581,7 @@ static int authenticate_digest_user(requ
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(requ
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(requ
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(requ
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(requ
/* 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(requ
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(requ
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);