Main Page | Modules | Namespace List | Class List | Directories | File List | Class Members | File Members | Related Pages | Examples

mod_auth_digest.c

Go to the documentation of this file.
00001 /* Licensed to the Apache Software Foundation (ASF) under one or more
00002  * contributor license agreements.  See the NOTICE file distributed with
00003  * this work for additional information regarding copyright ownership.
00004  * The ASF licenses this file to You under the Apache License, Version 2.0
00005  * (the "License"); you may not use this file except in compliance with
00006  * the License.  You may obtain a copy of the License at
00007  *
00008  *     http://www.apache.org/licenses/LICENSE-2.0
00009  *
00010  * Unless required by applicable law or agreed to in writing, software
00011  * distributed under the License is distributed on an "AS IS" BASIS,
00012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013  * See the License for the specific language governing permissions and
00014  * limitations under the License.
00015  */
00016 
00017 /*
00018  * mod_auth_digest: MD5 digest authentication
00019  *
00020  * Originally by Alexei Kosut <akosut@nueva.pvt.k12.ca.us>
00021  * Updated to RFC-2617 by Ronald Tschalär <ronald@innovation.ch>
00022  * based on mod_auth, by Rob McCool and Robert S. Thau
00023  *
00024  * This module an updated version of modules/standard/mod_digest.c
00025  * It is still fairly new and problems may turn up - submit problem
00026  * reports to the Apache bug-database, or send them directly to me
00027  * at ronald@innovation.ch.
00028  *
00029  * Requires either /dev/random (or equivalent) or the truerand library,
00030  * available for instance from
00031  * ftp://research.att.com/dist/mab/librand.shar
00032  *
00033  * Open Issues:
00034  *   - qop=auth-int (when streams and trailer support available)
00035  *   - nonce-format configurability
00036  *   - Proxy-Authorization-Info header is set by this module, but is
00037  *     currently ignored by mod_proxy (needs patch to mod_proxy)
00038  *   - generating the secret takes a while (~ 8 seconds) if using the
00039  *     truerand library
00040  *   - The source of the secret should be run-time directive (with server
00041  *     scope: RSRC_CONF). However, that could be tricky when trying to
00042  *     choose truerand vs. file...
00043  *   - shared-mem not completely tested yet. Seems to work ok for me,
00044  *     but... (definitely won't work on Windoze)
00045  *   - Sharing a realm among multiple servers has following problems:
00046  *     o Server name and port can't be included in nonce-hash
00047  *       (we need two nonce formats, which must be configured explicitly)
00048  *     o Nonce-count check can't be for equal, or then nonce-count checking
00049  *       must be disabled. What we could do is the following:
00050  *       (expected < received) ? set expected = received : issue error
00051  *       The only problem is that it allows replay attacks when somebody
00052  *       captures a packet sent to one server and sends it to another
00053  *       one. Should we add "AuthDigestNcCheck Strict"?
00054  *   - expired nonces give amaya fits.
00055  */
00056 
00057 #include "apr_sha1.h"
00058 #include "apr_base64.h"
00059 #include "apr_lib.h"
00060 #include "apr_time.h"
00061 #include "apr_errno.h"
00062 #include "apr_global_mutex.h"
00063 #include "apr_strings.h"
00064 
00065 #define APR_WANT_STRFUNC
00066 #include "apr_want.h"
00067 
00068 #include "ap_config.h"
00069 #include "httpd.h"
00070 #include "http_config.h"
00071 #include "http_core.h"
00072 #include "http_request.h"
00073 #include "http_log.h"
00074 #include "http_protocol.h"
00075 #include "apr_uri.h"
00076 #include "util_md5.h"
00077 #include "apr_shm.h"
00078 #include "apr_rmm.h"
00079 #include "ap_provider.h"
00080 
00081 #include "mod_auth.h"
00082 
00083 /* Disable shmem until pools/init gets sorted out
00084  * remove following two lines when fixed
00085  */
00086 #undef APR_HAS_SHARED_MEMORY
00087 #define APR_HAS_SHARED_MEMORY 0
00088 
00089 /* struct to hold the configuration info */
00090 
00091 typedef struct digest_config_struct {
00092     const char  *dir_name;
00093     authn_provider_list *providers;
00094     const char  *realm;
00095     char **qop_list;
00096     apr_sha1_ctx_t  nonce_ctx;
00097     apr_time_t    nonce_lifetime;
00098     const char  *nonce_format;
00099     int          check_nc;
00100     const char  *algorithm;
00101     char        *uri_list;
00102     const char  *ha1;
00103 } digest_config_rec;
00104 
00105 
00106 #define DFLT_ALGORITHM  "MD5"
00107 
00108 #define DFLT_NONCE_LIFE apr_time_from_sec(300)
00109 #define NEXTNONCE_DELTA apr_time_from_sec(30)
00110 
00111 
00112 #define NONCE_TIME_LEN  (((sizeof(apr_time_t)+2)/3)*4)
00113 #define NONCE_HASH_LEN  (2*APR_SHA1_DIGESTSIZE)
00114 #define NONCE_LEN       (int )(NONCE_TIME_LEN + NONCE_HASH_LEN)
00115 
00116 #define SECRET_LEN      20
00117 
00118 
00119 /* client list definitions */
00120 
00121 typedef struct hash_entry {
00122     unsigned long      key;                     /* the key for this entry    */
00123     struct hash_entry *next;                    /* next entry in the bucket  */
00124     unsigned long      nonce_count;             /* for nonce-count checking  */
00125     char               ha1[2*APR_MD5_DIGESTSIZE+1]; /* for algorithm=MD5-sess    */
00126     char               last_nonce[NONCE_LEN+1]; /* for one-time nonce's      */
00127 } client_entry;
00128 
00129 static struct hash_table {
00130     client_entry  **table;
00131     unsigned long   tbl_len;
00132     unsigned long   num_entries;
00133     unsigned long   num_created;
00134     unsigned long   num_removed;
00135     unsigned long   num_renewed;
00136 } *client_list;
00137 
00138 
00139 /* struct to hold a parsed Authorization header */
00140 
00141 enum hdr_sts { NO_HEADER, NOT_DIGEST, INVALID, VALID };
00142 
00143 typedef struct digest_header_struct {
00144     const char           *scheme;
00145     const char           *realm;
00146     const char           *username;
00147           char           *nonce;
00148     const char           *uri;
00149     const char           *method;
00150     const char           *digest;
00151     const char           *algorithm;
00152     const char           *cnonce;
00153     const char           *opaque;
00154     unsigned long         opaque_num;
00155     const char           *message_qop;
00156     const char           *nonce_count;
00157     /* the following fields are not (directly) from the header */
00158     apr_time_t            nonce_time;
00159     enum hdr_sts          auth_hdr_sts;
00160     const char           *raw_request_uri;
00161     apr_uri_t            *psd_request_uri;
00162     int                   needed_auth;
00163     client_entry         *client;
00164 } digest_header_rec;
00165 
00166 
00167 /* (mostly) nonce stuff */
00168 
00169 typedef union time_union {
00170     apr_time_t    time;
00171     unsigned char arr[sizeof(apr_time_t)];
00172 } time_rec;
00173 
00174 static unsigned char secret[SECRET_LEN];
00175 
00176 /* client-list, opaque, and one-time-nonce stuff */
00177 
00178 static apr_shm_t      *client_shm =  NULL;
00179 static apr_rmm_t      *client_rmm = NULL;
00180 static unsigned long  *opaque_cntr;
00181 static apr_time_t     *otn_counter;     /* one-time-nonce counter */
00182 static apr_global_mutex_t *client_lock = NULL;
00183 static apr_global_mutex_t *opaque_lock = NULL;
00184 static char           client_lock_name[L_tmpnam];
00185 static char           opaque_lock_name[L_tmpnam];
00186 
00187 #define DEF_SHMEM_SIZE  1000L           /* ~ 12 entries */
00188 #define DEF_NUM_BUCKETS 15L
00189 #define HASH_DEPTH      5
00190 
00191 static long shmem_size  = DEF_SHMEM_SIZE;
00192 static long num_buckets = DEF_NUM_BUCKETS;
00193 
00194 
00195 module AP_MODULE_DECLARE_DATA auth_digest_module;
00196 
00197 /*
00198  * initialization code
00199  */
00200 
00201 static apr_status_t cleanup_tables(void *not_used)
00202 {
00203     ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
00204                   "Digest: cleaning up shared memory");
00205     fflush(stderr);
00206 
00207     if (client_shm) {
00208         apr_shm_destroy(client_shm);
00209         client_shm = NULL;
00210     }
00211 
00212     if (client_lock) {
00213         apr_global_mutex_destroy(client_lock);
00214         client_lock = NULL;
00215     }
00216 
00217     if (opaque_lock) {
00218         apr_global_mutex_destroy(opaque_lock);
00219         opaque_lock = NULL;
00220     }
00221 
00222     return APR_SUCCESS;
00223 }
00224 
00225 static apr_status_t initialize_secret(server_rec *s)
00226 {
00227     apr_status_t status;
00228 
00229     ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s,
00230                  "Digest: generating secret for digest authentication ...");
00231 
00232 #if APR_HAS_RANDOM
00233     status = apr_generate_random_bytes(secret, sizeof(secret));
00234 #else
00235 #error APR random number support is missing; you probably need to install the truerand library.
00236 #endif
00237 
00238     if (status != APR_SUCCESS) {
00239         char buf[120];
00240         ap_log_error(APLOG_MARK, APLOG_CRIT, status, s,
00241                      "Digest: error generating secret: %s",
00242                      apr_strerror(status, buf, sizeof(buf)));
00243         return status;
00244     }
00245 
00246     ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, "Digest: done");
00247 
00248     return APR_SUCCESS;
00249 }
00250 
00251 static void log_error_and_cleanup(char *msg, apr_status_t sts, server_rec *s)
00252 {
00253     ap_log_error(APLOG_MARK, APLOG_ERR, sts, s,
00254                  "Digest: %s - all nonce-count checking, one-time nonces, and "
00255                  "MD5-sess algorithm disabled", msg);
00256 
00257     cleanup_tables(NULL);
00258 }
00259 
00260 #if APR_HAS_SHARED_MEMORY
00261 
00262 static void initialize_tables(server_rec *s, apr_pool_t *ctx)
00263 {
00264     unsigned long idx;
00265     apr_status_t   sts;
00266 
00267     /* set up client list */
00268 
00269     sts = apr_shm_create(&client_shm, shmem_size, tmpnam(NULL), ctx);
00270     if (sts != APR_SUCCESS) {
00271         log_error_and_cleanup("failed to create shared memory segments", sts, s);
00272         return;
00273     }
00274 
00275     client_list = apr_rmm_malloc(client_rmm, sizeof(*client_list) +
00276                                             sizeof(client_entry*)*num_buckets);
00277     if (!client_list) {
00278         log_error_and_cleanup("failed to allocate shared memory", -1, s);
00279         return;
00280     }
00281     client_list->table = (client_entry**) (client_list + 1);
00282     for (idx = 0; idx < num_buckets; idx++) {
00283         client_list->table[idx] = NULL;
00284     }
00285     client_list->tbl_len     = num_buckets;
00286     client_list->num_entries = 0;
00287 
00288     tmpnam(client_lock_name);
00289     /* FIXME: get the client_lock_name from a directive so we're portable
00290      * to non-process-inheriting operating systems, like Win32. */
00291     sts = apr_global_mutex_create(&client_lock, client_lock_name,
00292                                   APR_LOCK_DEFAULT, ctx);
00293     if (sts != APR_SUCCESS) {
00294         log_error_and_cleanup("failed to create lock (client_lock)", sts, s);
00295         return;
00296     }
00297 
00298 
00299     /* setup opaque */
00300 
00301     opaque_cntr = apr_rmm_malloc(client_rmm, sizeof(*opaque_cntr));
00302     if (opaque_cntr == NULL) {
00303         log_error_and_cleanup("failed to allocate shared memory", -1, s);
00304         return;
00305     }
00306     *opaque_cntr = 1UL;
00307 
00308     tmpnam(opaque_lock_name);
00309     /* FIXME: get the opaque_lock_name from a directive so we're portable
00310      * to non-process-inheriting operating systems, like Win32. */
00311     sts = apr_global_mutex_create(&opaque_lock, opaque_lock_name,
00312                                   APR_LOCK_DEFAULT, ctx);
00313     if (sts != APR_SUCCESS) {
00314         log_error_and_cleanup("failed to create lock (opaque_lock)", sts, s);
00315         return;
00316     }
00317 
00318 
00319     /* setup one-time-nonce counter */
00320 
00321     otn_counter = apr_rmm_malloc(client_rmm, sizeof(*otn_counter));
00322     if (otn_counter == NULL) {
00323         log_error_and_cleanup("failed to allocate shared memory", -1, s);
00324         return;
00325     }
00326     *otn_counter = 0;
00327     /* no lock here */
00328 
00329 
00330     /* success */
00331     return;
00332 }
00333 
00334 #endif /* APR_HAS_SHARED_MEMORY */
00335 
00336 
00337 static int initialize_module(apr_pool_t *p, apr_pool_t *plog,
00338                              apr_pool_t *ptemp, server_rec *s)
00339 {
00340     void *data;
00341     const char *userdata_key = "auth_digest_init";
00342 
00343     /* initialize_module() will be called twice, and if it's a DSO
00344      * then all static data from the first call will be lost. Only
00345      * set up our static data on the second call. */
00346     apr_pool_userdata_get(&data, userdata_key, s->process->pool);
00347     if (!data) {
00348         apr_pool_userdata_set((const void *)1, userdata_key,
00349                                apr_pool_cleanup_null, s->process->pool);
00350         return OK;
00351     }
00352     if (initialize_secret(s) != APR_SUCCESS) {
00353         return !OK;
00354     }
00355 
00356 #if APR_HAS_SHARED_MEMORY
00357     /* Note: this stuff is currently fixed for the lifetime of the server,
00358      * i.e. even across restarts. This means that A) any shmem-size
00359      * configuration changes are ignored, and B) certain optimizations,
00360      * such as only allocating the smallest necessary entry for each
00361      * client, can't be done. However, the alternative is a nightmare:
00362      * we can't call apr_shm_destroy on a graceful restart because there
00363      * will be children using the tables, and we also don't know when the
00364      * last child dies. Therefore we can never clean up the old stuff,
00365      * creating a creeping memory leak.
00366      */
00367     initialize_tables(s, p);
00368     apr_pool_cleanup_register(p, NULL, cleanup_tables, apr_pool_cleanup_null);
00369 #endif  /* APR_HAS_SHARED_MEMORY */
00370     return OK;
00371 }
00372 
00373 static void initialize_child(apr_pool_t *p, server_rec *s)
00374 {
00375     apr_status_t sts;
00376 
00377     if (!client_shm) {
00378         return;
00379     }
00380 
00381     /* FIXME: get the client_lock_name from a directive so we're portable
00382      * to non-process-inheriting operating systems, like Win32. */
00383     sts = apr_global_mutex_child_init(&client_lock, client_lock_name, p);
00384     if (sts != APR_SUCCESS) {
00385         log_error_and_cleanup("failed to create lock (client_lock)", sts, s);
00386         return;
00387     }
00388     /* FIXME: get the opaque_lock_name from a directive so we're portable
00389      * to non-process-inheriting operating systems, like Win32. */
00390     sts = apr_global_mutex_child_init(&opaque_lock, opaque_lock_name, p);
00391     if (sts != APR_SUCCESS) {
00392         log_error_and_cleanup("failed to create lock (opaque_lock)", sts, s);
00393         return;
00394     }
00395 }
00396 
00397 /*
00398  * configuration code
00399  */
00400 
00401 static void *create_digest_dir_config(apr_pool_t *p, char *dir)
00402 {
00403     digest_config_rec *conf;
00404 
00405     if (dir == NULL) {
00406         return NULL;
00407     }
00408 
00409     conf = (digest_config_rec *) apr_pcalloc(p, sizeof(digest_config_rec));
00410     if (conf) {
00411         conf->qop_list       = apr_palloc(p, sizeof(char*));
00412         conf->qop_list[0]    = NULL;
00413         conf->nonce_lifetime = DFLT_NONCE_LIFE;
00414         conf->dir_name       = apr_pstrdup(p, dir);
00415         conf->algorithm      = DFLT_ALGORITHM;
00416     }
00417 
00418     return conf;
00419 }
00420 
00421 static const char *set_realm(cmd_parms *cmd, void *config, const char *realm)
00422 {
00423     digest_config_rec *conf = (digest_config_rec *) config;
00424 
00425     /* The core already handles the realm, but it's just too convenient to
00426      * grab it ourselves too and cache some setups. However, we need to
00427      * let the core get at it too, which is why we decline at the end -
00428      * this relies on the fact that http_core is last in the list.
00429      */
00430     conf->realm = realm;
00431 
00432     /* we precompute the part of the nonce hash that is constant (well,
00433      * the host:port would be too, but that varies for .htaccess files
00434      * and directives outside a virtual host section)
00435      */
00436     apr_sha1_init(&conf->nonce_ctx);
00437     apr_sha1_update_binary(&conf->nonce_ctx, secret, sizeof(secret));
00438     apr_sha1_update_binary(&conf->nonce_ctx, (const unsigned char *) realm,
00439                            strlen(realm));
00440 
00441     return DECLINE_CMD;
00442 }
00443 
00444 static const char *add_authn_provider(cmd_parms *cmd, void *config,
00445                                       const char *arg)
00446 {
00447     digest_config_rec *conf = (digest_config_rec*)config;
00448     authn_provider_list *newp;
00449 
00450     newp = apr_pcalloc(cmd->pool, sizeof(authn_provider_list));
00451     newp->provider_name = apr_pstrdup(cmd->pool, arg);
00452 
00453     /* lookup and cache the actual provider now */
00454     newp->provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
00455                                         newp->provider_name, "0");
00456 
00457     if (newp->provider == NULL) {
00458        /* by the time they use it, the provider should be loaded and
00459            registered with us. */
00460         return apr_psprintf(cmd->pool,
00461                             "Unknown Authn provider: %s",
00462                             newp->provider_name);
00463     }
00464 
00465     if (!newp->provider->get_realm_hash) {
00466         /* if it doesn't provide the appropriate function, reject it */
00467         return apr_psprintf(cmd->pool,
00468                             "The '%s' Authn provider doesn't support "
00469                             "Digest Authentication", newp->provider_name);
00470     }
00471 
00472     /* Add it to the list now. */
00473     if (!conf->providers) {
00474         conf->providers = newp;
00475     }
00476     else {
00477         authn_provider_list *last = conf->providers;
00478 
00479         while (last->next) {
00480             last = last->next;
00481         }
00482         last->next = newp;
00483     }
00484 
00485     return NULL;
00486 }
00487 
00488 static const char *set_qop(cmd_parms *cmd, void *config, const char *op)
00489 {
00490     digest_config_rec *conf = (digest_config_rec *) config;
00491     char **tmp;
00492     int cnt;
00493 
00494     if (!strcasecmp(op, "none")) {
00495         if (conf->qop_list[0] == NULL) {
00496             conf->qop_list = apr_palloc(cmd->pool, 2 * sizeof(char*));
00497             conf->qop_list[1] = NULL;
00498         }
00499         conf->qop_list[0] = "none";
00500         return NULL;
00501     }
00502 
00503     if (!strcasecmp(op, "auth-int")) {
00504         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
00505                      "Digest: WARNING: qop `auth-int' currently only works "
00506                      "correctly for responses with no entity");
00507     }
00508     else if (strcasecmp(op, "auth")) {
00509         return apr_pstrcat(cmd->pool, "Unrecognized qop: ", op, NULL);
00510     }
00511 
00512     for (cnt = 0; conf->qop_list[cnt] != NULL; cnt++)
00513         ;
00514 
00515     tmp = apr_palloc(cmd->pool, (cnt + 2) * sizeof(char*));
00516     memcpy(tmp, conf->qop_list, cnt*sizeof(char*));
00517     tmp[cnt]   = apr_pstrdup(cmd->pool, op);
00518     tmp[cnt+1] = NULL;
00519     conf->qop_list = tmp;
00520 
00521     return NULL;
00522 }
00523 
00524 static const char *set_nonce_lifetime(cmd_parms *cmd, void *config,
00525                                       const char *t)
00526 {
00527     char *endptr;
00528     long  lifetime;
00529 
00530     lifetime = strtol(t, &endptr, 10);
00531     if (endptr < (t+strlen(t)) && !apr_isspace(*endptr)) {
00532         return apr_pstrcat(cmd->pool,
00533                            "Invalid time in AuthDigestNonceLifetime: ",
00534                            t, NULL);
00535     }
00536 
00537     ((digest_config_rec *) config)->nonce_lifetime = apr_time_from_sec(lifetime);
00538     return NULL;
00539 }
00540 
00541 static const char *set_nonce_format(cmd_parms *cmd, void *config,
00542                                     const char *fmt)
00543 {
00544     ((digest_config_rec *) config)->nonce_format = fmt;
00545     return "AuthDigestNonceFormat is not implemented (yet)";
00546 }
00547 
00548 static const char *set_nc_check(cmd_parms *cmd, void *config, int flag)
00549 {
00550     if (flag && !client_shm)
00551         ap_log_error(APLOG_MARK, APLOG_WARNING, 0,
00552                      cmd->server, "Digest: WARNING: nonce-count checking "
00553                      "is not supported on platforms without shared-memory "
00554                      "support - disabling check");
00555 
00556     ((digest_config_rec *) config)->check_nc = flag;
00557     return NULL;
00558 }
00559 
00560 static const char *set_algorithm(cmd_parms *cmd, void *config, const char *alg)
00561 {
00562     if (!strcasecmp(alg, "MD5-sess")) {
00563         if (!client_shm) {
00564             ap_log_error(APLOG_MARK, APLOG_WARNING, 0,
00565                          cmd->server, "Digest: WARNING: algorithm `MD5-sess' "
00566                          "is not supported on platforms without shared-memory "
00567                          "support - reverting to MD5");
00568             alg = "MD5";
00569         }
00570     }
00571     else if (strcasecmp(alg, "MD5")) {
00572         return apr_pstrcat(cmd->pool, "Invalid algorithm in AuthDigestAlgorithm: ", alg, NULL);
00573     }
00574 
00575     ((digest_config_rec *) config)->algorithm = alg;
00576     return NULL;
00577 }
00578 
00579 static const char *set_uri_list(cmd_parms *cmd, void *config, const char *uri)
00580 {
00581     digest_config_rec *c = (digest_config_rec *) config;
00582     if (c->uri_list) {
00583         c->uri_list[strlen(c->uri_list)-1] = '\0';
00584         c->uri_list = apr_pstrcat(cmd->pool, c->uri_list, " ", uri, "\"", NULL);
00585     }
00586     else {
00587         c->uri_list = apr_pstrcat(cmd->pool, ", domain=\"", uri, "\"", NULL);
00588     }
00589     return NULL;
00590 }
00591 
00592 static const char *set_shmem_size(cmd_parms *cmd, void *config,
00593                                   const char *size_str)
00594 {
00595     char *endptr;
00596     long  size, min;
00597 
00598     size = strtol(size_str, &endptr, 10);
00599     while (apr_isspace(*endptr)) endptr++;
00600     if (*endptr == '\0' || *endptr == 'b' || *endptr == 'B') {
00601         ;
00602     }
00603     else if (*endptr == 'k' || *endptr == 'K') {
00604         size *= 1024;
00605     }
00606     else if (*endptr == 'm' || *endptr == 'M') {
00607         size *= 1048576;
00608     }
00609     else {
00610         return apr_pstrcat(cmd->pool, "Invalid size in AuthDigestShmemSize: ",
00611                           size_str, NULL);
00612     }
00613 
00614     min = sizeof(*client_list) + sizeof(client_entry*) + sizeof(client_entry);
00615     if (size < min) {
00616         return apr_psprintf(cmd->pool, "size in AuthDigestShmemSize too small: "
00617                            "%ld < %ld", size, min);
00618     }
00619 
00620     shmem_size  = size;
00621     num_buckets = (size - sizeof(*client_list)) /
00622                   (sizeof(client_entry*) + HASH_DEPTH * sizeof(client_entry));
00623     if (num_buckets == 0) {
00624         num_buckets = 1;
00625     }
00626     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
00627                  "Digest: Set shmem-size: %ld, num-buckets: %ld", shmem_size,
00628                  num_buckets);
00629 
00630     return NULL;
00631 }
00632 
00633 static const command_rec digest_cmds[] =
00634 {
00635     AP_INIT_TAKE1("AuthName", set_realm, NULL, OR_AUTHCFG,
00636      "The authentication realm (e.g. \"Members Only\")"),
00637     AP_INIT_ITERATE("AuthDigestProvider", add_authn_provider, NULL, OR_AUTHCFG,
00638                      "specify the auth providers for a directory or location"),
00639     AP_INIT_ITERATE("AuthDigestQop", set_qop, NULL, OR_AUTHCFG,
00640      "A list of quality-of-protection options"),
00641     AP_INIT_TAKE1("AuthDigestNonceLifetime", set_nonce_lifetime, NULL, OR_AUTHCFG,
00642      "Maximum lifetime of the server nonce (seconds)"),
00643     AP_INIT_TAKE1("AuthDigestNonceFormat", set_nonce_format, NULL, OR_AUTHCFG,
00644      "The format to use when generating the server nonce"),
00645     AP_INIT_FLAG("AuthDigestNcCheck", set_nc_check, NULL, OR_AUTHCFG,
00646      "Whether or not to check the nonce-count sent by the client"),
00647     AP_INIT_TAKE1("AuthDigestAlgorithm", set_algorithm, NULL, OR_AUTHCFG,
00648      "The algorithm used for the hash calculation"),
00649     AP_INIT_ITERATE("AuthDigestDomain", set_uri_list, NULL, OR_AUTHCFG,
00650      "A list of URI's which belong to the same protection space as the current URI"),
00651     AP_INIT_TAKE1("AuthDigestShmemSize", set_shmem_size, NULL, RSRC_CONF,
00652      "The amount of shared memory to allocate for keeping track of clients"),
00653     {NULL}
00654 };
00655 
00656 
00657 /*
00658  * client list code
00659  *
00660  * Each client is assigned a number, which is transferred in the opaque
00661  * field of the WWW-Authenticate and Authorization headers. The number
00662  * is just a simple counter which is incremented for each new client.
00663  * Clients can't forge this number because it is hashed up into the
00664  * server nonce, and that is checked.
00665  *
00666  * The clients are kept in a simple hash table, which consists of an
00667  * array of client_entry's, each with a linked list of entries hanging
00668  * off it. The client's number modulo the size of the array gives the
00669  * bucket number.
00670  *
00671  * The clients are garbage collected whenever a new client is allocated
00672  * but there is not enough space left in the shared memory segment. A
00673  * simple semi-LRU is used for this: whenever a client entry is accessed
00674  * it is moved to the beginning of the linked list in its bucket (this
00675  * also makes for faster lookups for current clients). The garbage
00676  * collecter then just removes the oldest entry (i.e. the one at the
00677  * end of the list) in each bucket.
00678  *
00679  * The main advantages of the above scheme are that it's easy to implement
00680  * and it keeps the hash table evenly balanced (i.e. same number of entries
00681  * in each bucket). The major disadvantage is that you may be throwing
00682  * entries out which are in active use. This is not tragic, as these
00683  * clients will just be sent a new client id (opaque field) and nonce
00684  * with a stale=true (i.e. it will just look like the nonce expired,
00685  * thereby forcing an extra round trip). If the shared memory segment
00686  * has enough headroom over the current client set size then this should
00687  * not occur too often.
00688  *
00689  * To help tune the size of the shared memory segment (and see if the
00690  * above algorithm is really sufficient) a set of counters is kept
00691  * indicating the number of clients held, the number of garbage collected
00692  * clients, and the number of erroneously purged clients. These are printed
00693  * out at each garbage collection run. Note that access to the counters is
00694  * not synchronized because they are just indicaters, and whether they are
00695  * off by a few doesn't matter; and for the same reason no attempt is made
00696  * to guarantee the num_renewed is correct in the face of clients spoofing
00697  * the opaque field.
00698  */
00699 
00700 /*
00701  * Get the client given its client number (the key). Returns the entry,
00702  * or NULL if it's not found.
00703  *
00704  * Access to the list itself is synchronized via locks. However, access
00705  * to the entry returned by get_client() is NOT synchronized. This means
00706  * that there are potentially problems if a client uses multiple,
00707  * simultaneous connections to access url's within the same protection
00708  * space. However, these problems are not new: when using multiple
00709  * connections you have no guarantee of the order the requests are
00710  * processed anyway, so you have problems with the nonce-count and
00711  * one-time nonces anyway.
00712  */
00713 static client_entry *get_client(unsigned long key, const request_rec *r)
00714 {
00715     int bucket;
00716     client_entry *entry, *prev = NULL;
00717 
00718 
00719     if (!key || !client_shm)  return NULL;
00720 
00721     bucket = key % client_list->tbl_len;
00722     entry  = client_list->table[bucket];
00723 
00724     apr_global_mutex_lock(client_lock);
00725 
00726     while (entry && key != entry->key) {
00727         prev  = entry;
00728         entry = entry->next;
00729     }
00730 
00731     if (entry && prev) {                /* move entry to front of list */
00732         prev->next  = entry->next;
00733         entry->next = client_list->table[bucket];
00734         client_list->table[bucket] = entry;
00735     }
00736 
00737     apr_global_mutex_unlock(client_lock);
00738 
00739     if (entry) {
00740         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
00741                       "get_client(): client %lu found", key);
00742     }
00743     else {
00744         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
00745                       "get_client(): client %lu not found", key);
00746     }
00747 
00748     return entry;
00749 }
00750 
00751 
00752 /* A simple garbage-collecter to remove unused clients. It removes the
00753  * last entry in each bucket and updates the counters. Returns the
00754  * number of removed entries.
00755  */
00756 static long gc(void)
00757 {
00758     client_entry *entry, *prev;
00759     unsigned long num_removed = 0, idx;
00760 
00761     /* garbage collect all last entries */
00762 
00763     for (idx = 0; idx < client_list->tbl_len; idx++) {
00764         entry = client_list->table[idx];
00765         prev  = NULL;
00766         while (entry->next) {   /* find last entry */
00767             prev  = entry;
00768             entry = entry->next;
00769         }
00770         if (prev) {
00771             prev->next = NULL;   /* cut list */
00772         }
00773         else {
00774             client_list->table[idx] = NULL;
00775         }
00776         if (entry) {                    /* remove entry */
00777             apr_rmm_free(client_rmm, (apr_rmm_off_t)entry);
00778             num_removed++;
00779         }
00780     }
00781 
00782     /* update counters and log */
00783 
00784     client_list->num_entries -= num_removed;
00785     client_list->num_removed += num_removed;
00786 
00787     return num_removed;
00788 }
00789 
00790 
00791 /*
00792  * Add a new client to the list. Returns the entry if successful, NULL
00793  * otherwise. This triggers the garbage collection if memory is low.
00794  */
00795 static client_entry *add_client(unsigned long key, client_entry *info,
00796                                 server_rec *s)
00797 {
00798     int bucket;
00799     client_entry *entry;
00800 
00801 
00802     if (!key || !client_shm) {
00803         return NULL;
00804     }
00805 
00806     bucket = key % client_list->tbl_len;
00807     entry  = client_list->table[bucket];
00808 
00809     apr_global_mutex_lock(client_lock);
00810 
00811     /* try to allocate a new entry */
00812 
00813     entry = (client_entry *)apr_rmm_malloc(client_rmm, sizeof(client_entry));
00814     if (!entry) {
00815         long num_removed = gc();
00816         ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
00817                      "Digest: gc'd %ld client entries. Total new clients: "
00818                      "%ld; Total removed clients: %ld; Total renewed clients: "
00819                      "%ld", num_removed,
00820                      client_list->num_created - client_list->num_renewed,
00821                      client_list->num_removed, client_list->num_renewed);
00822         entry = (client_entry *)apr_rmm_malloc(client_rmm, sizeof(client_entry));
00823         if (!entry) {
00824             return NULL;       /* give up */
00825         }
00826     }
00827 
00828     /* now add the entry */
00829 
00830     memcpy(entry, info, sizeof(client_entry));
00831     entry->key  = key;
00832     entry->next = client_list->table[bucket];
00833     client_list->table[bucket] = entry;
00834     client_list->num_created++;
00835     client_list->num_entries++;
00836 
00837     apr_global_mutex_unlock(client_lock);
00838 
00839     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
00840                  "allocated new client %lu", key);
00841 
00842     return entry;
00843 }
00844 
00845 
00846 /*
00847  * Authorization header parser code
00848  */
00849 
00850 /* Parse the Authorization header, if it exists */
00851 static int get_digest_rec(request_rec *r, digest_header_rec *resp)
00852 {
00853     const char *auth_line;
00854     apr_size_t l;
00855     int vk = 0, vv = 0;
00856     char *key, *value;
00857 
00858     auth_line = apr_table_get(r->headers_in,
00859                              (PROXYREQ_PROXY == r->proxyreq)
00860                                  ? "Proxy-Authorization"
00861                                  : "Authorization");
00862     if (!auth_line) {
00863         resp->auth_hdr_sts = NO_HEADER;
00864         return !OK;
00865     }
00866 
00867     resp->scheme = ap_getword_white(r->pool, &auth_line);
00868     if (strcasecmp(resp->scheme, "Digest")) {
00869         resp->auth_hdr_sts = NOT_DIGEST;
00870         return !OK;
00871     }
00872 
00873     l = strlen(auth_line);
00874 
00875     key   = apr_palloc(r->pool, l+1);
00876     value = apr_palloc(r->pool, l+1);
00877 
00878     while (auth_line[0] != '\0') {
00879 
00880         /* find key */
00881 
00882         while (apr_isspace(auth_line[0])) {
00883             auth_line++;
00884         }
00885         vk = 0;
00886         while (auth_line[0] != '=' && auth_line[0] != ','
00887                && auth_line[0] != '\0' && !apr_isspace(auth_line[0])) {
00888             key[vk++] = *auth_line++;
00889         }
00890         key[vk] = '\0';
00891         while (apr_isspace(auth_line[0])) {
00892             auth_line++;
00893         }
00894 
00895         /* find value */
00896 
00897         if (auth_line[0] == '=') {
00898             auth_line++;
00899             while (apr_isspace(auth_line[0])) {
00900                 auth_line++;
00901             }
00902 
00903             vv = 0;
00904             if (auth_line[0] == '\"') {         /* quoted string */
00905                 auth_line++;
00906                 while (auth_line[0] != '\"' && auth_line[0] != '\0') {
00907                     if (auth_line[0] == '\\' && auth_line[1] != '\0') {
00908                         auth_line++;            /* escaped char */
00909                     }
00910                     value[vv++] = *auth_line++;
00911                 }
00912                 if (auth_line[0] != '\0') {
00913                     auth_line++;
00914                 }
00915             }
00916             else {                               /* token */
00917                 while (auth_line[0] != ',' && auth_line[0] != '\0'
00918                        && !apr_isspace(auth_line[0])) {
00919                     value[vv++] = *auth_line++;
00920                 }
00921             }
00922             value[vv] = '\0';
00923         }
00924 
00925         while (auth_line[0] != ',' && auth_line[0] != '\0') {
00926             auth_line++;
00927         }
00928         if (auth_line[0] != '\0') {
00929             auth_line++;
00930         }
00931 
00932         if (!strcasecmp(key, "username"))
00933             resp->username = apr_pstrdup(r->pool, value);
00934         else if (!strcasecmp(key, "realm"))
00935             resp->realm = apr_pstrdup(r->pool, value);
00936         else if (!strcasecmp(key, "nonce"))
00937             resp->nonce = apr_pstrdup(r->pool, value);
00938         else if (!strcasecmp(key, "uri"))
00939             resp->uri = apr_pstrdup(r->pool, value);
00940         else if (!strcasecmp(key, "response"))
00941             resp->digest = apr_pstrdup(r->pool, value);
00942         else if (!strcasecmp(key, "algorithm"))
00943             resp->algorithm = apr_pstrdup(r->pool, value);
00944         else if (!strcasecmp(key, "cnonce"))
00945             resp->cnonce = apr_pstrdup(r->pool, value);
00946         else if (!strcasecmp(key, "opaque"))
00947             resp->opaque = apr_pstrdup(r->pool, value);
00948         else if (!strcasecmp(key, "qop"))
00949             resp->message_qop = apr_pstrdup(r->pool, value);
00950         else if (!strcasecmp(key, "nc"))
00951             resp->nonce_count = apr_pstrdup(r->pool, value);
00952     }
00953 
00954     if (!resp->username || !resp->realm || !resp->nonce || !resp->uri
00955         || !resp->digest
00956         || (resp->message_qop && (!resp->cnonce || !resp->nonce_count))) {
00957         resp->auth_hdr_sts = INVALID;
00958         return !OK;
00959     }
00960 
00961     if (resp->opaque) {
00962         resp->opaque_num = (unsigned long) strtol(resp->opaque, NULL, 16);
00963     }
00964 
00965     resp->auth_hdr_sts = VALID;
00966     return OK;
00967 }
00968 
00969 
00970 /* Because the browser may preemptively send auth info, incrementing the
00971  * nonce-count when it does, and because the client does not get notified
00972  * if the URI didn't need authentication after all, we need to be sure to
00973  * update the nonce-count each time we receive an Authorization header no
00974  * matter what the final outcome of the request. Furthermore this is a
00975  * convenient place to get the request-uri (before any subrequests etc
00976  * are initiated) and to initialize the request_config.
00977  *
00978  * Note that this must be called after mod_proxy had its go so that
00979  * r->proxyreq is set correctly.
00980  */
00981 static int parse_hdr_and_update_nc(request_rec *r)
00982 {
00983     digest_header_rec *resp;
00984     int res;
00985 
00986     if (!ap_is_initial_req(r)) {
00987         return DECLINED;
00988     }
00989 
00990     resp = apr_pcalloc(r->pool, sizeof(digest_header_rec));
00991     resp->raw_request_uri = r->unparsed_uri;
00992     resp->psd_request_uri = &r->parsed_uri;
00993     resp->needed_auth = 0;
00994     resp->method = r->method;
00995     ap_set_module_config(r->request_config, &auth_digest_module, resp);
00996 
00997     res = get_digest_rec(r, resp);
00998     resp->client = get_client(resp->opaque_num, r);
00999     if (res == OK && resp->client) {
01000         resp->client->nonce_count++;
01001     }
01002 
01003     return DECLINED;
01004 }
01005 
01006 
01007 /*
01008  * Nonce generation code
01009  */
01010 
01011 /* The hash part of the nonce is a SHA-1 hash of the time, realm, server host
01012  * and port, opaque, and our secret.
01013  */
01014 static void gen_nonce_hash(char *hash, const char *timestr, const char *opaque,
01015                            const server_rec *server,
01016                            const digest_config_rec *conf)
01017 {
01018     const char *hex = "0123456789abcdef";
01019     unsigned char sha1[APR_SHA1_DIGESTSIZE];
01020     apr_sha1_ctx_t ctx;
01021     int idx;
01022 
01023     memcpy(&ctx, &conf->nonce_ctx, sizeof(ctx));
01024     /*
01025     apr_sha1_update_binary(&ctx, (const unsigned char *) server->server_hostname,
01026                          strlen(server->server_hostname));
01027     apr_sha1_update_binary(&ctx, (const unsigned char *) &server->port,
01028                          sizeof(server->port));
01029      */
01030     apr_sha1_update_binary(&ctx, (const unsigned char *) timestr, strlen(timestr));
01031     if (opaque) {
01032         apr_sha1_update_binary(&ctx, (const unsigned char *) opaque,
01033                              strlen(opaque));
01034     }
01035     apr_sha1_final(sha1, &ctx);
01036 
01037     for (idx=0; idx<APR_SHA1_DIGESTSIZE; idx++) {
01038         *hash++ = hex[sha1[idx] >> 4];
01039         *hash++ = hex[sha1[idx] & 0xF];
01040     }
01041 
01042     *hash++ = '\0';
01043 }
01044 
01045 
01046 /* The nonce has the format b64(time)+hash .
01047  */
01048 static const char *gen_nonce(apr_pool_t *p, apr_time_t