/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/
/* * mod_auth_digest: MD5 digest authentication * * Originally by Alexei Kosut <akosut@nueva.pvt.k12.ca.us> * Updated to RFC-2617 by Ronald Tschal�r <ronald@innovation.ch> * based on mod_auth, by Rob McCool and Robert S. Thau * * This module an updated version of modules/standard/mod_digest.c * It is still fairly new and problems may turn up - submit problem * reports to the Apache bug-database, or send them directly to me * at ronald@innovation.ch. * * 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 * 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) * - shared-mem not completely tested yet. Seems to work ok for me, * but... (definitely won't work on Windoze) * - Sharing a realm among multiple servers has following problems: * o Server name and port can't be included in nonce-hash * (we need two nonce formats, which must be configured explicitly) * o Nonce-count check can't be for equal, or then nonce-count checking * must be disabled. What we could do is the following: * (expected < received) ? set expected = received : issue error * The only problem is that it allows replay attacks when somebody * captures a packet sent to one server and sends it to another * one. Should we add "AuthDigestNcCheck Strict"? * - expired nonces give amaya fits. * - MD5-sess and auth-int are not yet implemented. An incomplete * implementation has been removed and can be retrieved from svn history.
*/
typedefstruct hash_entry { unsignedlong key; /* the key for this entry */ struct hash_entry *next; /* next entry in the bucket */ unsignedlong nonce_count; /* for nonce-count checking */ char last_nonce[NONCE_LEN+1]; /* for one-time nonce's */
} client_entry;
/* * Create a unique filename using our pid. This information is * stashed in the global variable so the children inherit it.
*/
client_shm_filename = ap_runtime_dir_relative(ctx, "authdigest_shm");
client_shm_filename = ap_append_pid(ctx, client_shm_filename, ".");
/* Use anonymous shm by default, fall back on name-based. */
sts = apr_shm_create(&client_shm, shmem_size, NULL, ctx); if (APR_STATUS_IS_ENOTIMPL(sts)) { /* For a name-based segment, remove it first in case of a
* previous unclean shutdown. */
apr_shm_remove(client_shm_filename, ctx);
/* Now create that segment */
sts = apr_shm_create(&client_shm, shmem_size,
client_shm_filename, ctx);
}
if (APR_SUCCESS != sts) {
ap_log_error(APLOG_MARK, APLOG_ERR, sts, s, APLOGNO(01762) "Failed to create shared memory segment on file %s",
client_shm_filename);
log_error_and_cleanup("failed to initialize shm", sts, s); return HTTP_INTERNAL_SERVER_ERROR;
}
sts = apr_rmm_init(&client_rmm,
NULL, /* no lock, we'll do the locking ourselves */
apr_shm_baseaddr_get(client_shm),
shmem_size, ctx); if (sts != APR_SUCCESS) {
log_error_and_cleanup("failed to initialize rmm", sts, s); 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"); return !OK;
}
}
secret = retained; return OK;
}
staticint initialize_module(apr_pool_t *p, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *s)
{ /* initialize_module() will be called twice, and if it's a DSO * then all static data from the first call will be lost. Only
* set up our static data on the second call. */ 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, * such as only allocating the smallest necessary entry for each * client, can't be done. However, the alternative is a nightmare: * we can't call apr_shm_destroy on a graceful restart because there * will be children using the tables, and we also don't know when the * 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;
}
/* Get access to rmm in child */
sts = apr_rmm_attach(&client_rmm,
NULL,
apr_shm_baseaddr_get(client_shm),
p); if (sts != APR_SUCCESS) {
log_error_and_cleanup("failed to attach to rmm", sts, s); return;
}
sts = apr_global_mutex_child_init(&client_lock,
apr_global_mutex_lockfile(client_lock),
p); if (sts != APR_SUCCESS) {
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;
}
}
/* 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, (constunsignedchar *) realm,
strlen(realm));
/* lookup and cache the actual provider now */
newp->provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
newp->provider_name,
AUTHN_PROVIDER_VERSION);
if (newp->provider == NULL) { /* by the time they use it, the provider should be loaded and
registered with us. */ return apr_psprintf(cmd->pool, "Unknown Authn provider: %s",
newp->provider_name);
}
if (!newp->provider->get_realm_hash) { /* if it doesn't provide the appropriate function, reject it */ return apr_psprintf(cmd->pool, "The '%s' Authn provider doesn't support " "Digest Authentication", newp->provider_name);
}
/* Add it to the list now. */ if (!conf->providers) {
conf->providers = newp;
} else {
authn_provider_list *last = conf->providers;
while (last->next) {
last = last->next;
}
last->next = newp;
}
staticconst 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, "The algorithm used for the hash calculation"),
AP_INIT_ITERATE("AuthDigestDomain", set_uri_list, NULL, OR_AUTHCFG, "A list of URI's which belong to the same protection space as the current URI"),
AP_INIT_TAKE1("AuthDigestShmemSize", set_shmem_size, NULL, RSRC_CONF, "The amount of shared memory to allocate for keeping track of clients"),
{NULL}
};
/* * client list code * * Each client is assigned a number, which is transferred in the opaque * field of the WWW-Authenticate and Authorization headers. The number * is just a simple counter which is incremented for each new client. * Clients can't forge this number because it is hashed up into the * server nonce, and that is checked. * * The clients are kept in a simple hash table, which consists of an * array of client_entry's, each with a linked list of entries hanging * off it. The client's number modulo the size of the array gives the * bucket number. * * The clients are garbage collected whenever a new client is allocated * but there is not enough space left in the shared memory segment. A * simple semi-LRU is used for this: whenever a client entry is accessed * it is moved to the beginning of the linked list in its bucket (this * also makes for faster lookups for current clients). The garbage * collecter then just removes the oldest entry (i.e. the one at the * end of the list) in each bucket. * * The main advantages of the above scheme are that it's easy to implement * and it keeps the hash table evenly balanced (i.e. same number of entries * in each bucket). The major disadvantage is that you may be throwing * entries out which are in active use. This is not tragic, as these * clients will just be sent a new client id (opaque field) and nonce * with a stale=true (i.e. it will just look like the nonce expired, * thereby forcing an extra round trip). If the shared memory segment * has enough headroom over the current client set size then this should * not occur too often. * * To help tune the size of the shared memory segment (and see if the * 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.
*/
/* * Get the client given its client number (the key). 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.
*/ static client_entry *get_client(unsignedlong key, const request_rec *r)
{ int bucket;
client_entry *entry, *prev = NULL;
if (entry && prev) { /* move entry to front of list */
prev->next = entry->next;
entry->next = client_list->table[bucket];
client_list->table[bucket] = entry;
}
/* A simple garbage-collecter to remove unused clients. It removes the * last entry in each bucket and updates the counters. Returns the * number of removed entries.
*/ staticlong gc(server_rec *s)
{
client_entry *entry, *prev; unsignedlong num_removed = 0, idx;
if (!entry) { /* This bucket is empty. */ continue;
}
while (entry->next) { /* find last entry */
prev = entry;
entry = entry->next;
} if (prev) {
prev->next = NULL; /* cut list */
} else {
client_list->table[idx] = NULL;
} if (entry) { /* remove entry */
apr_status_t err;
err = rmm_free(client_rmm, entry);
num_removed++;
if (err) { /* Nothing we can really do but log... */
ap_log_error(APLOG_MARK, APLOG_ERR, err, s, APLOGNO(10007) "Failed to free auth_digest client allocation");
}
}
}
/* * Add a new client to the list. Returns the entry if successful, NULL * otherwise. This triggers the garbage collection if memory is low.
*/ static client_entry *add_client(unsignedlong key, client_entry *info,
server_rec *s)
{ int bucket;
client_entry *entry;
if (!key || !client_shm) { return NULL;
}
bucket = key % client_list->tbl_len;
apr_global_mutex_lock(client_lock);
/* try to allocate a new entry */
entry = rmm_malloc(client_rmm, sizeof(client_entry)); if (!entry) { 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,
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 */
}
}
if (resp->opaque) {
resp->opaque_num = (unsignedlong) strtol(resp->opaque, NULL, 16);
}
resp->auth_hdr_sts = VALID; return OK;
}
/* 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. * * Note that this must be called after mod_proxy had its go so that * r->proxyreq is set correctly.
*/ staticint parse_hdr_and_update_nc(request_rec *r)
{
digest_header_rec *resp; int res;
res = get_digest_rec(r, resp);
resp->client = get_client(resp->opaque_num, r); if (res == OK && resp->client) {
resp->client->nonce_count++;
}
return DECLINED;
}
/* * Nonce generation code
*/
/* The hash part of the nonce is a SHA-1 hash of the time, realm, server host * and port, opaque, and our secret.
*/ staticvoid gen_nonce_hash(char *hash, constchar *timestr, constchar *opaque, const server_rec *server, const digest_config_rec *conf)
{ unsignedchar sha1[APR_SHA1_DIGESTSIZE];
apr_sha1_ctx_t ctx;
/* The nonce has the format b64(time)+hash .
*/ staticconstchar *gen_nonce(apr_pool_t *p, apr_time_t now, constchar *opaque, const server_rec *server, const digest_config_rec *conf)
{ char *nonce = apr_palloc(p, NONCE_LEN+1);
time_rec t;
if (conf->nonce_lifetime != 0) {
t.time = now;
} elseif (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;
}
apr_base64_encode_binary(nonce, t.arr, sizeof(t.arr));
gen_nonce_hash(nonce+NONCE_TIME_LEN, nonce, opaque, server, conf);
return nonce;
}
/* * Opaque and hash-table management
*/
/* * Generate a new client entry, add it to the list, and return the * entry. Returns NULL if failed.
*/ static client_entry *gen_client(const request_rec *r)
{ unsignedlong op;
client_entry new_entry = { 0, NULL, 0, "" }, *entry;
if (!opaque_cntr) { return NULL;
}
apr_global_mutex_lock(opaque_lock);
op = (*opaque_cntr)++;
apr_global_mutex_unlock(opaque_lock);
/* setup domain attribute. We want to send this attribute wherever * possible so that the client won't send the Authorization header * unnecessarily (it's usually > 200 bytes!).
*/
/* don't send domain * - for proxy requests * - if it's not specified
*/ if (r->proxyreq || !conf->uri_list) {
domain = NULL;
} else {
domain = conf->uri_list;
}
current_provider = conf->providers; do { const authn_provider *provider;
/* For now, if a provider isn't set, we'll be nice and use the file * provider.
*/ if (!current_provider) {
provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
AUTHN_DEFAULT_PROVIDER,
AUTHN_PROVIDER_VERSION);
/* Since the time part of the nonce is a base64 encoding of an * apr_time_t (8 bytes), it should end with a '=', fail early otherwise.
*/ if (strlen(resp->nonce) != NONCE_LEN
|| resp->nonce[NONCE_TIME_LEN - 1] != '=') {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01775) "invalid nonce '%s' received - length is not %d " "or time encoding is incorrect",
resp->nonce, NONCE_LEN);
note_digest_auth_failure(r, conf, resp, 1); return HTTP_UNAUTHORIZED;
}
/* These functions return 0 if client is OK, and proper error status * if not... either HTTP_UNAUTHORIZED, if we made a check, and it failed, or * HTTP_INTERNAL_SERVER_ERROR, if things are so totally confused that we * couldn't figure out how to tell if the client is authorized or not. * * If they return DECLINED, and all other modules also decline, that's * treated by the server core as a configuration error, logged and * reported as such.
*/
/* Determine user ID, and check if the attributes are correct, if it * really is that user, if the nonce is correct, etc.
*/
if (strcmp(resp->uri, resp->raw_request_uri)) { /* Hmm, the simple match didn't work (probably a proxy modified the * request-uri), so lets do a more sophisticated match
*/
apr_uri_t r_uri, d_uri;
copy_uri_components(&r_uri, resp->psd_request_uri, r); if (apr_uri_parse(r->pool, resp->uri, &d_uri) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01783) "invalid uri <%s> in Authorization header",
resp->uri); return HTTP_BAD_REQUEST;
}
if (d_uri.hostname) {
ap_unescape_url(d_uri.hostname);
} if (d_uri.path) {
ap_unescape_url(d_uri.path);
}
if (d_uri.query) {
ap_unescape_url(d_uri.query);
} elseif (r_uri.query) { /* MSIE compatibility hack. MSIE has some RFC issues - doesn't * include the query string in the uri Authorization component * or when computing the response component. the second part * works out ok, since we can hash the header and get the same * result. however, the uri from the request line won't match * the uri Authorization component since the header lacks the * query string, leaving us incompatible with a (broken) MSIE. * * the workaround is to fake a query string match if in the proper * environment - BrowserMatch MSIE, for example. the cool thing * is that if MSIE ever fixes itself the simple match ought to * work and this code won't be reached anyway, even if the * environment is set.
*/
if (apr_table_get(r->subprocess_env, "AuthDigestEnableQueryStringHack")) {
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;
} elseif (return_code == AUTH_USER_FOUND) { /* we have a password, so continue */
} elseif (return_code == AUTH_DENIED) { /* 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;
} else { /* AUTH_GENERAL_ERROR (or worse) * We'll assume that the module has already said what its error * was in the logs.
*/ return HTTP_INTERNAL_SERVER_ERROR;
}
if (resp->message_qop == NULL) { /* old (rfc-2069) style digest */ if (strcmp(resp->digest, old_digest(r, resp))) {
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;
}
} else { constchar *exp_digest; int match = 0, idx; constchar **tmp = (constchar **)(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;
}
/* Note: this check is done last so that a "stale=true" can be
generated if the nonce is old */ if ((res = check_nonce(r, resp, conf))) { return res;
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.54Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.