]> granicus.if.org Git - apache/blob - modules/aaa/mod_auth_digest.c
Update the copyright year in all .c, .h and .xml files
[apache] / modules / aaa / mod_auth_digest.c
1 /* Copyright 1999-2006 The Apache Software Foundation or its licensors, as
2  * applicable.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * mod_auth_digest: MD5 digest authentication
19  *
20  * Originally by Alexei Kosut <akosut@nueva.pvt.k12.ca.us>
21  * Updated to RFC-2617 by Ronald Tschalär <ronald@innovation.ch>
22  * based on mod_auth, by Rob McCool and Robert S. Thau
23  *
24  * This module an updated version of modules/standard/mod_digest.c
25  * It is still fairly new and problems may turn up - submit problem
26  * reports to the Apache bug-database, or send them directly to me
27  * at ronald@innovation.ch.
28  *
29  * Requires either /dev/random (or equivalent) or the truerand library,
30  * available for instance from
31  * ftp://research.att.com/dist/mab/librand.shar
32  *
33  * Open Issues:
34  *   - qop=auth-int (when streams and trailer support available)
35  *   - nonce-format configurability
36  *   - Proxy-Authorization-Info header is set by this module, but is
37  *     currently ignored by mod_proxy (needs patch to mod_proxy)
38  *   - generating the secret takes a while (~ 8 seconds) if using the
39  *     truerand library
40  *   - The source of the secret should be run-time directive (with server
41  *     scope: RSRC_CONF). However, that could be tricky when trying to
42  *     choose truerand vs. file...
43  *   - shared-mem not completely tested yet. Seems to work ok for me,
44  *     but... (definitely won't work on Windoze)
45  *   - Sharing a realm among multiple servers has following problems:
46  *     o Server name and port can't be included in nonce-hash
47  *       (we need two nonce formats, which must be configured explicitly)
48  *     o Nonce-count check can't be for equal, or then nonce-count checking
49  *       must be disabled. What we could do is the following:
50  *       (expected < received) ? set expected = received : issue error
51  *       The only problem is that it allows replay attacks when somebody
52  *       captures a packet sent to one server and sends it to another
53  *       one. Should we add "AuthDigestNcCheck Strict"?
54  *   - expired nonces give amaya fits.
55  */
56
57 #include "apr_sha1.h"
58 #include "apr_base64.h"
59 #include "apr_lib.h"
60 #include "apr_time.h"
61 #include "apr_errno.h"
62 #include "apr_global_mutex.h"
63 #include "apr_strings.h"
64
65 #define APR_WANT_STRFUNC
66 #include "apr_want.h"
67
68 #include "ap_config.h"
69 #include "httpd.h"
70 #include "http_config.h"
71 #include "http_core.h"
72 #include "http_request.h"
73 #include "http_log.h"
74 #include "http_protocol.h"
75 #include "apr_uri.h"
76 #include "util_md5.h"
77 #include "apr_shm.h"
78 #include "apr_rmm.h"
79 #include "ap_provider.h"
80
81 #include "mod_auth.h"
82
83 /* Disable shmem until pools/init gets sorted out
84  * remove following two lines when fixed
85  */
86 #undef APR_HAS_SHARED_MEMORY
87 #define APR_HAS_SHARED_MEMORY 0
88
89 /* struct to hold the configuration info */
90
91 typedef struct digest_config_struct {
92     const char  *dir_name;
93     authn_provider_list *providers;
94     const char  *realm;
95     char **qop_list;
96     apr_sha1_ctx_t  nonce_ctx;
97     apr_time_t    nonce_lifetime;
98     const char  *nonce_format;
99     int          check_nc;
100     const char  *algorithm;
101     char        *uri_list;
102     const char  *ha1;
103 } digest_config_rec;
104
105
106 #define DFLT_ALGORITHM  "MD5"
107
108 #define DFLT_NONCE_LIFE apr_time_from_sec(300)
109 #define NEXTNONCE_DELTA apr_time_from_sec(30)
110
111
112 #define NONCE_TIME_LEN  (((sizeof(apr_time_t)+2)/3)*4)
113 #define NONCE_HASH_LEN  (2*APR_SHA1_DIGESTSIZE)
114 #define NONCE_LEN       (int )(NONCE_TIME_LEN + NONCE_HASH_LEN)
115
116 #define SECRET_LEN      20
117
118
119 /* client list definitions */
120
121 typedef struct hash_entry {
122     unsigned long      key;                     /* the key for this entry    */
123     struct hash_entry *next;                    /* next entry in the bucket  */
124     unsigned long      nonce_count;             /* for nonce-count checking  */
125     char               ha1[2*APR_MD5_DIGESTSIZE+1]; /* for algorithm=MD5-sess    */
126     char               last_nonce[NONCE_LEN+1]; /* for one-time nonce's      */
127 } client_entry;
128
129 static struct hash_table {
130     client_entry  **table;
131     unsigned long   tbl_len;
132     unsigned long   num_entries;
133     unsigned long   num_created;
134     unsigned long   num_removed;
135     unsigned long   num_renewed;
136 } *client_list;
137
138
139 /* struct to hold a parsed Authorization header */
140
141 enum hdr_sts { NO_HEADER, NOT_DIGEST, INVALID, VALID };
142
143 typedef struct digest_header_struct {
144     const char           *scheme;
145     const char           *realm;
146     const char           *username;
147           char           *nonce;
148     const char           *uri;
149     const char           *method;
150     const char           *digest;
151     const char           *algorithm;
152     const char           *cnonce;
153     const char           *opaque;
154     unsigned long         opaque_num;
155     const char           *message_qop;
156     const char           *nonce_count;
157     /* the following fields are not (directly) from the header */
158     apr_time_t            nonce_time;
159     enum hdr_sts          auth_hdr_sts;
160     const char           *raw_request_uri;
161     apr_uri_t            *psd_request_uri;
162     int                   needed_auth;
163     client_entry         *client;
164 } digest_header_rec;
165
166
167 /* (mostly) nonce stuff */
168
169 typedef union time_union {
170     apr_time_t    time;
171     unsigned char arr[sizeof(apr_time_t)];
172 } time_rec;
173
174 static unsigned char secret[SECRET_LEN];
175
176 /* client-list, opaque, and one-time-nonce stuff */
177
178 static apr_shm_t      *client_shm =  NULL;
179 static apr_rmm_t      *client_rmm = NULL;
180 static unsigned long  *opaque_cntr;
181 static apr_time_t     *otn_counter;     /* one-time-nonce counter */
182 static apr_global_mutex_t *client_lock = NULL;
183 static apr_global_mutex_t *opaque_lock = NULL;
184 static char           client_lock_name[L_tmpnam];
185 static char           opaque_lock_name[L_tmpnam];
186
187 #define DEF_SHMEM_SIZE  1000L           /* ~ 12 entries */
188 #define DEF_NUM_BUCKETS 15L
189 #define HASH_DEPTH      5
190
191 static long shmem_size  = DEF_SHMEM_SIZE;
192 static long num_buckets = DEF_NUM_BUCKETS;
193
194
195 module AP_MODULE_DECLARE_DATA auth_digest_module;
196
197 /*
198  * initialization code
199  */
200
201 static apr_status_t cleanup_tables(void *not_used)
202 {
203     ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
204                   "Digest: cleaning up shared memory");
205     fflush(stderr);
206
207     if (client_shm) {
208         apr_shm_destroy(client_shm);
209         client_shm = NULL;
210     }
211
212     if (client_lock) {
213         apr_global_mutex_destroy(client_lock);
214         client_lock = NULL;
215     }
216
217     if (opaque_lock) {
218         apr_global_mutex_destroy(opaque_lock);
219         opaque_lock = NULL;
220     }
221
222     return APR_SUCCESS;
223 }
224
225 static apr_status_t initialize_secret(server_rec *s)
226 {
227     apr_status_t status;
228
229     ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s,
230                  "Digest: generating secret for digest authentication ...");
231
232 #if APR_HAS_RANDOM
233     status = apr_generate_random_bytes(secret, sizeof(secret));
234 #else
235 #error APR random number support is missing; you probably need to install the truerand library.
236 #endif
237
238     if (status != APR_SUCCESS) {
239         char buf[120];
240         ap_log_error(APLOG_MARK, APLOG_CRIT, status, s,
241                      "Digest: error generating secret: %s",
242                      apr_strerror(status, buf, sizeof(buf)));
243         return status;
244     }
245
246     ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, "Digest: done");
247
248     return APR_SUCCESS;
249 }
250
251 static void log_error_and_cleanup(char *msg, apr_status_t sts, server_rec *s)
252 {
253     ap_log_error(APLOG_MARK, APLOG_ERR, sts, s,
254                  "Digest: %s - all nonce-count checking, one-time nonces, and "
255                  "MD5-sess algorithm disabled", msg);
256
257     cleanup_tables(NULL);
258 }
259
260 #if APR_HAS_SHARED_MEMORY
261
262 static void initialize_tables(server_rec *s, apr_pool_t *ctx)
263 {
264     unsigned long idx;
265     apr_status_t   sts;
266
267     /* set up client list */
268
269     sts = apr_shm_create(&client_shm, shmem_size, tmpnam(NULL), ctx);
270     if (sts != APR_SUCCESS) {
271         log_error_and_cleanup("failed to create shared memory segments", sts, s);
272         return;
273     }
274
275     client_list = apr_rmm_malloc(client_rmm, sizeof(*client_list) +
276                                             sizeof(client_entry*)*num_buckets);
277     if (!client_list) {
278         log_error_and_cleanup("failed to allocate shared memory", -1, s);
279         return;
280     }
281     client_list->table = (client_entry**) (client_list + 1);
282     for (idx = 0; idx < num_buckets; idx++) {
283         client_list->table[idx] = NULL;
284     }
285     client_list->tbl_len     = num_buckets;
286     client_list->num_entries = 0;
287
288     tmpnam(client_lock_name);
289     /* FIXME: get the client_lock_name from a directive so we're portable
290      * to non-process-inheriting operating systems, like Win32. */
291     sts = apr_global_mutex_create(&client_lock, client_lock_name,
292                                   APR_LOCK_DEFAULT, ctx);
293     if (sts != APR_SUCCESS) {
294         log_error_and_cleanup("failed to create lock (client_lock)", sts, s);
295         return;
296     }
297
298
299     /* setup opaque */
300
301     opaque_cntr = apr_rmm_malloc(client_rmm, sizeof(*opaque_cntr));
302     if (opaque_cntr == NULL) {
303         log_error_and_cleanup("failed to allocate shared memory", -1, s);
304         return;
305     }
306     *opaque_cntr = 1UL;
307
308     tmpnam(opaque_lock_name);
309     /* FIXME: get the opaque_lock_name from a directive so we're portable
310      * to non-process-inheriting operating systems, like Win32. */
311     sts = apr_global_mutex_create(&opaque_lock, opaque_lock_name,
312                                   APR_LOCK_DEFAULT, ctx);
313     if (sts != APR_SUCCESS) {
314         log_error_and_cleanup("failed to create lock (opaque_lock)", sts, s);
315         return;
316     }
317
318
319     /* setup one-time-nonce counter */
320
321     otn_counter = apr_rmm_malloc(client_rmm, sizeof(*otn_counter));
322     if (otn_counter == NULL) {
323         log_error_and_cleanup("failed to allocate shared memory", -1, s);
324         return;
325     }
326     *otn_counter = 0;
327     /* no lock here */
328
329
330     /* success */
331     return;
332 }
333
334 #endif /* APR_HAS_SHARED_MEMORY */
335
336
337 static int initialize_module(apr_pool_t *p, apr_pool_t *plog,
338                              apr_pool_t *ptemp, server_rec *s)
339 {
340     void *data;
341     const char *userdata_key = "auth_digest_init";
342
343     /* initialize_module() will be called twice, and if it's a DSO
344      * then all static data from the first call will be lost. Only
345      * set up our static data on the second call. */
346     apr_pool_userdata_get(&data, userdata_key, s->process->pool);
347     if (!data) {
348         apr_pool_userdata_set((const void *)1, userdata_key,
349                                apr_pool_cleanup_null, s->process->pool);
350         return OK;
351     }
352     if (initialize_secret(s) != APR_SUCCESS) {
353         return !OK;
354     }
355
356 #if APR_HAS_SHARED_MEMORY
357     /* Note: this stuff is currently fixed for the lifetime of the server,
358      * i.e. even across restarts. This means that A) any shmem-size
359      * configuration changes are ignored, and B) certain optimizations,
360      * such as only allocating the smallest necessary entry for each
361      * client, can't be done. However, the alternative is a nightmare:
362      * we can't call apr_shm_destroy on a graceful restart because there
363      * will be children using the tables, and we also don't know when the
364      * last child dies. Therefore we can never clean up the old stuff,
365      * creating a creeping memory leak.
366      */
367     initialize_tables(s, p);
368     apr_pool_cleanup_register(p, NULL, cleanup_tables, apr_pool_cleanup_null);
369 #endif  /* APR_HAS_SHARED_MEMORY */
370     return OK;
371 }
372
373 static void initialize_child(apr_pool_t *p, server_rec *s)
374 {
375     apr_status_t sts;
376
377     if (!client_shm) {
378         return;
379     }
380
381     /* FIXME: get the client_lock_name from a directive so we're portable
382      * to non-process-inheriting operating systems, like Win32. */
383     sts = apr_global_mutex_child_init(&client_lock, client_lock_name, p);
384     if (sts != APR_SUCCESS) {
385         log_error_and_cleanup("failed to create lock (client_lock)", sts, s);
386         return;
387     }
388     /* FIXME: get the opaque_lock_name from a directive so we're portable
389      * to non-process-inheriting operating systems, like Win32. */
390     sts = apr_global_mutex_child_init(&opaque_lock, opaque_lock_name, p);
391     if (sts != APR_SUCCESS) {
392         log_error_and_cleanup("failed to create lock (opaque_lock)", sts, s);
393         return;
394     }
395 }
396
397 /*
398  * configuration code
399  */
400
401 static void *create_digest_dir_config(apr_pool_t *p, char *dir)
402 {
403     digest_config_rec *conf;
404
405     if (dir == NULL) {
406         return NULL;
407     }
408
409     conf = (digest_config_rec *) apr_pcalloc(p, sizeof(digest_config_rec));
410     if (conf) {
411         conf->qop_list       = apr_palloc(p, sizeof(char*));
412         conf->qop_list[0]    = NULL;
413         conf->nonce_lifetime = DFLT_NONCE_LIFE;
414         conf->dir_name       = apr_pstrdup(p, dir);
415         conf->algorithm      = DFLT_ALGORITHM;
416     }
417
418     return conf;
419 }
420
421 static const char *set_realm(cmd_parms *cmd, void *config, const char *realm)
422 {
423     digest_config_rec *conf = (digest_config_rec *) config;
424
425     /* The core already handles the realm, but it's just too convenient to
426      * grab it ourselves too and cache some setups. However, we need to
427      * let the core get at it too, which is why we decline at the end -
428      * this relies on the fact that http_core is last in the list.
429      */
430     conf->realm = realm;
431
432     /* we precompute the part of the nonce hash that is constant (well,
433      * the host:port would be too, but that varies for .htaccess files
434      * and directives outside a virtual host section)
435      */
436     apr_sha1_init(&conf->nonce_ctx);
437     apr_sha1_update_binary(&conf->nonce_ctx, secret, sizeof(secret));
438     apr_sha1_update_binary(&conf->nonce_ctx, (const unsigned char *) realm,
439                            strlen(realm));
440
441     return DECLINE_CMD;
442 }
443
444 static const char *add_authn_provider(cmd_parms *cmd, void *config,
445                                       const char *arg)
446 {
447     digest_config_rec *conf = (digest_config_rec*)config;
448     authn_provider_list *newp;
449
450     newp = apr_pcalloc(cmd->pool, sizeof(authn_provider_list));
451     newp->provider_name = apr_pstrdup(cmd->pool, arg);
452
453     /* lookup and cache the actual provider now */
454     newp->provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
455                                         newp->provider_name, "0");
456
457     if (newp->provider == NULL) {
458        /* by the time they use it, the provider should be loaded and
459            registered with us. */
460         return apr_psprintf(cmd->pool,
461                             "Unknown Authn provider: %s",
462                             newp->provider_name);
463     }
464
465     if (!newp->provider->get_realm_hash) {
466         /* if it doesn't provide the appropriate function, reject it */
467         return apr_psprintf(cmd->pool,
468                             "The '%s' Authn provider doesn't support "
469                             "Digest Authentication", newp->provider_name);
470     }
471
472     /* Add it to the list now. */
473     if (!conf->providers) {
474         conf->providers = newp;
475     }
476     else {
477         authn_provider_list *last = conf->providers;
478
479         while (last->next) {
480             last = last->next;
481         }
482         last->next = newp;
483     }
484
485     return NULL;
486 }
487
488 static const char *set_qop(cmd_parms *cmd, void *config, const char *op)
489 {
490     digest_config_rec *conf = (digest_config_rec *) config;
491     char **tmp;
492     int cnt;
493
494     if (!strcasecmp(op, "none")) {
495         if (conf->qop_list[0] == NULL) {
496             conf->qop_list = apr_palloc(cmd->pool, 2 * sizeof(char*));
497             conf->qop_list[1] = NULL;
498         }
499         conf->qop_list[0] = "none";
500         return NULL;
501     }
502
503     if (!strcasecmp(op, "auth-int")) {
504         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
505                      "Digest: WARNING: qop `auth-int' currently only works "
506                      "correctly for responses with no entity");
507     }
508     else if (strcasecmp(op, "auth")) {
509         return apr_pstrcat(cmd->pool, "Unrecognized qop: ", op, NULL);
510     }
511
512     for (cnt = 0; conf->qop_list[cnt] != NULL; cnt++)
513         ;
514
515     tmp = apr_palloc(cmd->pool, (cnt + 2) * sizeof(char*));
516     memcpy(tmp, conf->qop_list, cnt*sizeof(char*));
517     tmp[cnt]   = apr_pstrdup(cmd->pool, op);
518     tmp[cnt+1] = NULL;
519     conf->qop_list = tmp;
520
521     return NULL;
522 }
523
524 static const char *set_nonce_lifetime(cmd_parms *cmd, void *config,
525                                       const char *t)
526 {
527     char *endptr;
528     long  lifetime;
529
530     lifetime = strtol(t, &endptr, 10);
531     if (endptr < (t+strlen(t)) && !apr_isspace(*endptr)) {
532         return apr_pstrcat(cmd->pool,
533                            "Invalid time in AuthDigestNonceLifetime: ",
534                            t, NULL);
535     }
536
537     ((digest_config_rec *) config)->nonce_lifetime = apr_time_from_sec(lifetime);
538     return NULL;
539 }
540
541 static const char *set_nonce_format(cmd_parms *cmd, void *config,
542                                     const char *fmt)
543 {
544     ((digest_config_rec *) config)->nonce_format = fmt;
545     return "AuthDigestNonceFormat is not implemented (yet)";
546 }
547
548 static const char *set_nc_check(cmd_parms *cmd, void *config, int flag)
549 {
550     if (flag && !client_shm)
551         ap_log_error(APLOG_MARK, APLOG_WARNING, 0,
552                      cmd->server, "Digest: WARNING: nonce-count checking "
553                      "is not supported on platforms without shared-memory "
554                      "support - disabling check");
555
556     ((digest_config_rec *) config)->check_nc = flag;
557     return NULL;
558 }
559
560 static const char *set_algorithm(cmd_parms *cmd, void *config, const char *alg)
561 {
562     if (!strcasecmp(alg, "MD5-sess")) {
563         if (!client_shm) {
564             ap_log_error(APLOG_MARK, APLOG_WARNING, 0,
565                          cmd->server, "Digest: WARNING: algorithm `MD5-sess' "
566                          "is not supported on platforms without shared-memory "
567                          "support - reverting to MD5");
568             alg = "MD5";
569         }
570     }
571     else if (strcasecmp(alg, "MD5")) {
572         return apr_pstrcat(cmd->pool, "Invalid algorithm in AuthDigestAlgorithm: ", alg, NULL);
573     }
574
575     ((digest_config_rec *) config)->algorithm = alg;
576     return NULL;
577 }
578
579 static const char *set_uri_list(cmd_parms *cmd, void *config, const char *uri)
580 {
581     digest_config_rec *c = (digest_config_rec *) config;
582     if (c->uri_list) {
583         c->uri_list[strlen(c->uri_list)-1] = '\0';
584         c->uri_list = apr_pstrcat(cmd->pool, c->uri_list, " ", uri, "\"", NULL);
585     }
586     else {
587         c->uri_list = apr_pstrcat(cmd->pool, ", domain=\"", uri, "\"", NULL);
588     }
589     return NULL;
590 }
591
592 static const char *set_shmem_size(cmd_parms *cmd, void *config,
593                                   const char *size_str)
594 {
595     char *endptr;
596     long  size, min;
597
598     size = strtol(size_str, &endptr, 10);
599     while (apr_isspace(*endptr)) endptr++;
600     if (*endptr == '\0' || *endptr == 'b' || *endptr == 'B') {
601         ;
602     }
603     else if (*endptr == 'k' || *endptr == 'K') {
604         size *= 1024;
605     }
606     else if (*endptr == 'm' || *endptr == 'M') {
607         size *= 1048576;
608     }
609     else {
610         return apr_pstrcat(cmd->pool, "Invalid size in AuthDigestShmemSize: ",
611                           size_str, NULL);
612     }
613
614     min = sizeof(*client_list) + sizeof(client_entry*) + sizeof(client_entry);
615     if (size < min) {
616         return apr_psprintf(cmd->pool, "size in AuthDigestShmemSize too small: "
617                            "%ld < %ld", size, min);
618     }
619
620     shmem_size  = size;
621     num_buckets = (size - sizeof(*client_list)) /
622                   (sizeof(client_entry*) + HASH_DEPTH * sizeof(client_entry));
623     if (num_buckets == 0) {
624         num_buckets = 1;
625     }
626     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
627                  "Digest: Set shmem-size: %ld, num-buckets: %ld", shmem_size,
628                  num_buckets);
629
630     return NULL;
631 }
632
633 static const command_rec digest_cmds[] =
634 {
635     AP_INIT_TAKE1("AuthName", set_realm, NULL, OR_AUTHCFG,
636      "The authentication realm (e.g. \"Members Only\")"),
637     AP_INIT_ITERATE("AuthDigestProvider", add_authn_provider, NULL, OR_AUTHCFG,
638                      "specify the auth providers for a directory or location"),
639     AP_INIT_ITERATE("AuthDigestQop", set_qop, NULL, OR_AUTHCFG,
640      "A list of quality-of-protection options"),
641     AP_INIT_TAKE1("AuthDigestNonceLifetime", set_nonce_lifetime, NULL, OR_AUTHCFG,
642      "Maximum lifetime of the server nonce (seconds)"),
643     AP_INIT_TAKE1("AuthDigestNonceFormat", set_nonce_format, NULL, OR_AUTHCFG,
644      "The format to use when generating the server nonce"),
645     AP_INIT_FLAG("AuthDigestNcCheck", set_nc_check, NULL, OR_AUTHCFG,
646      "Whether or not to check the nonce-count sent by the client"),
647     AP_INIT_TAKE1("AuthDigestAlgorithm", set_algorithm, NULL, OR_AUTHCFG,
648      "The algorithm used for the hash calculation"),
649     AP_INIT_ITERATE("AuthDigestDomain", set_uri_list, NULL, OR_AUTHCFG,
650      "A list of URI's which belong to the same protection space as the current URI"),
651     AP_INIT_TAKE1("AuthDigestShmemSize", set_shmem_size, NULL, RSRC_CONF,
652      "The amount of shared memory to allocate for keeping track of clients"),
653     {NULL}
654 };
655
656
657 /*
658  * client list code
659  *
660  * Each client is assigned a number, which is transferred in the opaque
661  * field of the WWW-Authenticate and Authorization headers. The number
662  * is just a simple counter which is incremented for each new client.
663  * Clients can't forge this number because it is hashed up into the
664  * server nonce, and that is checked.
665  *
666  * The clients are kept in a simple hash table, which consists of an
667  * array of client_entry's, each with a linked list of entries hanging
668  * off it. The client's number modulo the size of the array gives the
669  * bucket number.
670  *
671  * The clients are garbage collected whenever a new client is allocated
672  * but there is not enough space left in the shared memory segment. A
673  * simple semi-LRU is used for this: whenever a client entry is accessed
674  * it is moved to the beginning of the linked list in its bucket (this
675  * also makes for faster lookups for current clients). The garbage
676  * collecter then just removes the oldest entry (i.e. the one at the
677  * end of the list) in each bucket.
678  *
679  * The main advantages of the above scheme are that it's easy to implement
680  * and it keeps the hash table evenly balanced (i.e. same number of entries
681  * in each bucket). The major disadvantage is that you may be throwing
682  * entries out which are in active use. This is not tragic, as these
683  * clients will just be sent a new client id (opaque field) and nonce
684  * with a stale=true (i.e. it will just look like the nonce expired,
685  * thereby forcing an extra round trip). If the shared memory segment
686  * has enough headroom over the current client set size then this should
687  * not occur too often.
688  *
689  * To help tune the size of the shared memory segment (and see if the
690  * above algorithm is really sufficient) a set of counters is kept
691  * indicating the number of clients held, the number of garbage collected
692  * clients, and the number of erroneously purged clients. These are printed
693  * out at each garbage collection run. Note that access to the counters is
694  * not synchronized because they are just indicaters, and whether they are
695  * off by a few doesn't matter; and for the same reason no attempt is made
696  * to guarantee the num_renewed is correct in the face of clients spoofing
697  * the opaque field.
698  */
699
700 /*
701  * Get the client given its client number (the key). Returns the entry,
702  * or NULL if it's not found.
703  *
704  * Access to the list itself is synchronized via locks. However, access
705  * to the entry returned by get_client() is NOT synchronized. This means
706  * that there are potentially problems if a client uses multiple,
707  * simultaneous connections to access url's within the same protection
708  * space. However, these problems are not new: when using multiple
709  * connections you have no guarantee of the order the requests are
710  * processed anyway, so you have problems with the nonce-count and
711  * one-time nonces anyway.
712  */
713 static client_entry *get_client(unsigned long key, const request_rec *r)
714 {
715     int bucket;
716     client_entry *entry, *prev = NULL;
717
718
719     if (!key || !client_shm)  return NULL;
720
721     bucket = key % client_list->tbl_len;
722     entry  = client_list->table[bucket];
723
724     apr_global_mutex_lock(client_lock);
725
726     while (entry && key != entry->key) {
727         prev  = entry;
728         entry = entry->next;
729     }
730
731     if (entry && prev) {                /* move entry to front of list */
732         prev->next  = entry->next;
733         entry->next = client_list->table[bucket];
734         client_list->table[bucket] = entry;
735     }
736
737     apr_global_mutex_unlock(client_lock);
738
739     if (entry) {
740         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
741                       "get_client(): client %lu found", key);
742     }
743     else {
744         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
745                       "get_client(): client %lu not found", key);
746     }
747
748     return entry;
749 }
750
751
752 /* A simple garbage-collecter to remove unused clients. It removes the
753  * last entry in each bucket and updates the counters. Returns the
754  * number of removed entries.
755  */
756 static long gc(void)
757 {
758     client_entry *entry, *prev;
759     unsigned long num_removed = 0, idx;
760
761     /* garbage collect all last entries */
762
763     for (idx = 0; idx < client_list->tbl_len; idx++) {
764         entry = client_list->table[idx];
765         prev  = NULL;
766         while (entry->next) {   /* find last entry */
767             prev  = entry;
768             entry = entry->next;
769         }
770         if (prev) {
771             prev->next = NULL;   /* cut list */
772         }
773         else {
774             client_list->table[idx] = NULL;
775         }
776         if (entry) {                    /* remove entry */
777             apr_rmm_free(client_rmm, (apr_rmm_off_t)entry);
778             num_removed++;
779         }
780     }
781
782     /* update counters and log */
783
784     client_list->num_entries -= num_removed;
785     client_list->num_removed += num_removed;
786
787     return num_removed;
788 }
789
790
791 /*
792  * Add a new client to the list. Returns the entry if successful, NULL
793  * otherwise. This triggers the garbage collection if memory is low.
794  */
795 static client_entry *add_client(unsigned long key, client_entry *info,
796                                 server_rec *s)
797 {
798     int bucket;
799     client_entry *entry;
800
801
802     if (!key || !client_shm) {
803         return NULL;
804     }
805
806     bucket = key % client_list->tbl_len;
807     entry  = client_list->table[bucket];
808
809     apr_global_mutex_lock(client_lock);
810
811     /* try to allocate a new entry */
812
813     entry = (client_entry *)apr_rmm_malloc(client_rmm, sizeof(client_entry));
814     if (!entry) {
815         long num_removed = gc();
816         ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
817                      "Digest: gc'd %ld client entries. Total new clients: "
818                      "%ld; Total removed clients: %ld; Total renewed clients: "
819                      "%ld", num_removed,
820                      client_list->num_created - client_list->num_renewed,
821                      client_list->num_removed, client_list->num_renewed);
822         entry = (client_entry *)apr_rmm_malloc(client_rmm, sizeof(client_entry));
823         if (!entry) {
824             return NULL;       /* give up */
825         }
826     }
827
828     /* now add the entry */
829
830     memcpy(entry, info, sizeof(client_entry));
831     entry->key  = key;
832     entry->next = client_list->table[bucket];
833     client_list->table[bucket] = entry;
834     client_list->num_created++;
835     client_list->num_entries++;
836
837     apr_global_mutex_unlock(client_lock);
838
839     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
840                  "allocated new client %lu", key);
841
842     return entry;
843 }
844
845
846 /*
847  * Authorization header parser code
848  */
849
850 /* Parse the Authorization header, if it exists */
851 static int get_digest_rec(request_rec *r, digest_header_rec *resp)
852 {
853     const char *auth_line;
854     apr_size_t l;
855     int vk = 0, vv = 0;
856     char *key, *value;
857
858     auth_line = apr_table_get(r->headers_in,
859                              (PROXYREQ_PROXY == r->proxyreq)
860                                  ? "Proxy-Authorization"
861                                  : "Authorization");
862     if (!auth_line) {
863         resp->auth_hdr_sts = NO_HEADER;
864         return !OK;
865     }
866
867     resp->scheme = ap_getword_white(r->pool, &auth_line);
868     if (strcasecmp(resp->scheme, "Digest")) {
869         resp->auth_hdr_sts = NOT_DIGEST;
870         return !OK;
871     }
872
873     l = strlen(auth_line);
874
875     key   = apr_palloc(r->pool, l+1);
876     value = apr_palloc(r->pool, l+1);
877
878     while (auth_line[0] != '\0') {
879
880         /* find key */
881
882         while (apr_isspace(auth_line[0])) {
883             auth_line++;
884         }
885         vk = 0;
886         while (auth_line[0] != '=' && auth_line[0] != ','
887                && auth_line[0] != '\0' && !apr_isspace(auth_line[0])) {
888             key[vk++] = *auth_line++;
889         }
890         key[vk] = '\0';
891         while (apr_isspace(auth_line[0])) {
892             auth_line++;
893         }
894
895         /* find value */
896
897         if (auth_line[0] == '=') {
898             auth_line++;
899             while (apr_isspace(auth_line[0])) {
900                 auth_line++;
901             }
902
903             vv = 0;
904             if (auth_line[0] == '\"') {         /* quoted string */
905                 auth_line++;
906                 while (auth_line[0] != '\"' && auth_line[0] != '\0') {
907                     if (auth_line[0] == '\\' && auth_line[1] != '\0') {
908                         auth_line++;            /* escaped char */
909                     }
910                     value[vv++] = *auth_line++;
911                 }
912                 if (auth_line[0] != '\0') {
913                     auth_line++;
914                 }
915             }
916             else {                               /* token */
917                 while (auth_line[0] != ',' && auth_line[0] != '\0'
918                        && !apr_isspace(auth_line[0])) {
919                     value[vv++] = *auth_line++;
920                 }
921             }
922             value[vv] = '\0';
923         }
924
925         while (auth_line[0] != ',' && auth_line[0] != '\0') {
926             auth_line++;
927         }
928         if (auth_line[0] != '\0') {
929             auth_line++;
930         }
931
932         if (!strcasecmp(key, "username"))
933             resp->username = apr_pstrdup(r->pool, value);
934         else if (!strcasecmp(key, "realm"))
935             resp->realm = apr_pstrdup(r->pool, value);
936         else if (!strcasecmp(key, "nonce"))
937             resp->nonce = apr_pstrdup(r->pool, value);
938         else if (!strcasecmp(key, "uri"))
939             resp->uri = apr_pstrdup(r->pool, value);
940         else if (!strcasecmp(key, "response"))
941             resp->digest = apr_pstrdup(r->pool, value);
942         else if (!strcasecmp(key, "algorithm"))
943             resp->algorithm = apr_pstrdup(r->pool, value);
944         else if (!strcasecmp(key, "cnonce"))
945             resp->cnonce = apr_pstrdup(r->pool, value);
946         else if (!strcasecmp(key, "opaque"))
947             resp->opaque = apr_pstrdup(r->pool, value);
948         else if (!strcasecmp(key, "qop"))
949             resp->message_qop = apr_pstrdup(r->pool, value);
950         else if (!strcasecmp(key, "nc"))
951             resp->nonce_count = apr_pstrdup(r->pool, value);
952     }
953
954     if (!resp->username || !resp->realm || !resp->nonce || !resp->uri
955         || !resp->digest
956         || (resp->message_qop && (!resp->cnonce || !resp->nonce_count))) {
957         resp->auth_hdr_sts = INVALID;
958         return !OK;
959     }
960
961     if (resp->opaque) {
962         resp->opaque_num = (unsigned long) strtol(resp->opaque, NULL, 16);
963     }
964
965     resp->auth_hdr_sts = VALID;
966     return OK;
967 }
968
969
970 /* Because the browser may preemptively send auth info, incrementing the
971  * nonce-count when it does, and because the client does not get notified
972  * if the URI didn't need authentication after all, we need to be sure to
973  * update the nonce-count each time we receive an Authorization header no
974  * matter what the final outcome of the request. Furthermore this is a
975  * convenient place to get the request-uri (before any subrequests etc
976  * are initiated) and to initialize the request_config.
977  *
978  * Note that this must be called after mod_proxy had its go so that
979  * r->proxyreq is set correctly.
980  */
981 static int parse_hdr_and_update_nc(request_rec *r)
982 {
983     digest_header_rec *resp;
984     int res;
985
986     if (!ap_is_initial_req(r)) {
987         return DECLINED;
988     }
989
990     resp = apr_pcalloc(r->pool, sizeof(digest_header_rec));
991     resp->raw_request_uri = r->unparsed_uri;
992     resp->psd_request_uri = &r->parsed_uri;
993     resp->needed_auth = 0;
994     resp->method = r->method;
995     ap_set_module_config(r->request_config, &auth_digest_module, resp);
996
997     res = get_digest_rec(r, resp);
998     resp->client = get_client(resp->opaque_num, r);
999     if (res == OK && resp->client) {
1000         resp->client->nonce_count++;
1001     }
1002
1003     return DECLINED;
1004 }
1005
1006
1007 /*
1008  * Nonce generation code
1009  */
1010
1011 /* The hash part of the nonce is a SHA-1 hash of the time, realm, server host
1012  * and port, opaque, and our secret.
1013  */
1014 static void gen_nonce_hash(char *hash, const char *timestr, const char *opaque,
1015                            const server_rec *server,
1016                            const digest_config_rec *conf)
1017 {
1018     const char *hex = "0123456789abcdef";
1019     unsigned char sha1[APR_SHA1_DIGESTSIZE];
1020     apr_sha1_ctx_t ctx;
1021     int idx;
1022
1023     memcpy(&ctx, &conf->nonce_ctx, sizeof(ctx));
1024     /*
1025     apr_sha1_update_binary(&ctx, (const unsigned char *) server->server_hostname,
1026                          strlen(server->server_hostname));
1027     apr_sha1_update_binary(&ctx, (const unsigned char *) &server->port,
1028                          sizeof(server->port));
1029      */
1030     apr_sha1_update_binary(&ctx, (const unsigned char *) timestr, strlen(timestr));
1031     if (opaque) {
1032         apr_sha1_update_binary(&ctx, (const unsigned char *) opaque,
1033                              strlen(opaque));
1034     }
1035     apr_sha1_final(sha1, &ctx);
1036
1037     for (idx=0; idx<APR_SHA1_DIGESTSIZE; idx++) {
1038         *hash++ = hex[sha1[idx] >> 4];
1039         *hash++ = hex[sha1[idx] & 0xF];
1040     }
1041
1042     *hash++ = '\0';
1043 }
1044
1045
1046 /* The nonce has the format b64(time)+hash .
1047  */
1048 static const char *gen_nonce(apr_pool_t *p, apr_time_t now, const char *opaque,
1049                              const server_rec *server,
1050                              const digest_config_rec *conf)
1051 {
1052     char *nonce = apr_palloc(p, NONCE_LEN+1);
1053     int len;
1054     time_rec t;
1055
1056     if (conf->nonce_lifetime != 0) {
1057         t.time = now;
1058     }
1059     else if (otn_counter) {
1060         /* this counter is not synch'd, because it doesn't really matter
1061          * if it counts exactly.
1062          */
1063         t.time = (*otn_counter)++;
1064     }
1065     else {
1066         /* XXX: WHAT IS THIS CONSTANT? */
1067         t.time = 42;
1068     }
1069     len = apr_base64_encode_binary(nonce, t.arr, sizeof(t.arr));
1070     gen_nonce_hash(nonce+NONCE_TIME_LEN, nonce, opaque, server, conf);
1071
1072     return nonce;
1073 }
1074
1075
1076 /*
1077  * Opaque and hash-table management
1078  */
1079
1080 /*
1081  * Generate a new client entry, add it to the list, and return the
1082  * entry. Returns NULL if failed.
1083  */
1084 static client_entry *gen_client(const request_rec *r)
1085 {
1086     unsigned long op;
1087     client_entry new_entry = { 0, NULL, 0, "", "" }, *entry;
1088
1089     if (!opaque_cntr) {
1090         return NULL;
1091     }
1092
1093     apr_global_mutex_lock(opaque_lock);
1094     op = (*opaque_cntr)++;
1095     apr_global_mutex_lock(opaque_lock);
1096
1097     if (!(entry = add_client(op, &new_entry, r->server))) {
1098         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1099                       "Digest: failed to allocate client entry - ignoring "
1100                       "client");
1101         return NULL;
1102     }
1103
1104     return entry;
1105 }
1106
1107
1108 /*
1109  * MD5-sess code.
1110  *
1111  * If you want to use algorithm=MD5-sess you must write get_userpw_hash()
1112  * yourself (see below). The dummy provided here just uses the hash from
1113  * the auth-file, i.e. it is only useful for testing client implementations
1114  * of MD5-sess .
1115  */
1116
1117 /*
1118  * get_userpw_hash() will be called each time a new session needs to be
1119  * generated and is expected to return the equivalent of
1120  *
1121  * h_urp = ap_md5(r->pool,
1122  *         apr_pstrcat(r->pool, username, ":", ap_auth_name(r), ":", passwd))
1123  * ap_md5(r->pool,
1124  *         (unsigned char *) apr_pstrcat(r->pool, h_urp, ":", resp->nonce, ":",
1125  *                                      resp->cnonce, NULL));
1126  *
1127  * or put differently, it must return
1128  *
1129  *   MD5(MD5(username ":" realm ":" password) ":" nonce ":" cnonce)
1130  *
1131  * If something goes wrong, the failure must be logged and NULL returned.
1132  *
1133  * You must implement this yourself, which will probably consist of code
1134  * contacting the password server with the necessary information (typically
1135  * the username, realm, nonce, and cnonce) and receiving the hash from it.
1136  *
1137  * TBD: This function should probably be in a seperate source file so that
1138  * people need not modify mod_auth_digest.c each time they install a new
1139  * version of apache.
1140  */
1141 static const char *get_userpw_hash(const request_rec *r,
1142                                    const digest_header_rec *resp,
1143                                    const digest_config_rec *conf)
1144 {
1145     return ap_md5(r->pool,
1146              (unsigned char *) apr_pstrcat(r->pool, conf->ha1, ":", resp->nonce,
1147                                            ":", resp->cnonce, NULL));
1148 }
1149
1150
1151 /* Retrieve current session H(A1). If there is none and "generate" is
1152  * true then a new session for MD5-sess is generated and stored in the
1153  * client struct; if generate is false, or a new session could not be
1154  * generated then NULL is returned (in case of failure to generate the
1155  * failure reason will have been logged already).
1156  */
1157 static const char *get_session_HA1(const request_rec *r,
1158                                    digest_header_rec *resp,
1159                                    const digest_config_rec *conf,
1160                                    int generate)
1161 {
1162     const char *ha1 = NULL;
1163
1164     /* return the current sessions if there is one */
1165     if (resp->opaque && resp->client && resp->client->ha1[0]) {
1166         return resp->client->ha1;
1167     }
1168     else if (!generate) {
1169         return NULL;
1170     }
1171
1172     /* generate a new session */
1173     if (!resp->client) {
1174         resp->client = gen_client(r);
1175     }
1176     if (resp->client) {
1177         ha1 = get_userpw_hash(r, resp, conf);
1178         if (ha1) {
1179             memcpy(resp->client->ha1, ha1, sizeof(resp->client->ha1));
1180         }
1181     }
1182
1183     return ha1;
1184 }
1185
1186
1187 static void clear_session(const digest_header_rec *resp)
1188 {
1189     if (resp->client) {
1190         resp->client->ha1[0] = '\0';
1191     }
1192 }
1193
1194 /*
1195  * Authorization challenge generation code (for WWW-Authenticate)
1196  */
1197
1198 static const char *ltox(apr_pool_t *p, unsigned long num)
1199 {
1200     if (num != 0) {
1201         return apr_psprintf(p, "%lx", num);
1202     }
1203     else {
1204         return "";
1205     }
1206 }
1207
1208 static void note_digest_auth_failure(request_rec *r,
1209                                      const digest_config_rec *conf,
1210                                      digest_header_rec *resp, int stale)
1211 {
1212     const char   *qop, *opaque, *opaque_param, *domain, *nonce;
1213     int           cnt;
1214
1215     /* Setup qop */
1216
1217     if (conf->qop_list[0] == NULL) {
1218         qop = ", qop=\"auth\"";
1219     }
1220     else if (!strcasecmp(conf->qop_list[0], "none")) {
1221         qop = "";
1222     }
1223     else {
1224         qop = apr_pstrcat(r->pool, ", qop=\"", conf->qop_list[0], NULL);
1225         for (cnt = 1; conf->qop_list[cnt] != NULL; cnt++) {
1226             qop = apr_pstrcat(r->pool, qop, ",", conf->qop_list[cnt], NULL);
1227         }
1228         qop = apr_pstrcat(r->pool, qop, "\"", NULL);
1229     }
1230
1231     /* Setup opaque */
1232
1233     if (resp->opaque == NULL) {
1234         /* new client */
1235         if ((conf->check_nc || conf->nonce_lifetime == 0
1236              || !strcasecmp(conf->algorithm, "MD5-sess"))
1237             && (resp->client = gen_client(r)) != NULL) {
1238             opaque = ltox(r->pool, resp->client->key);
1239         }
1240         else {
1241             opaque = "";                /* opaque not needed */
1242         }
1243     }
1244     else if (resp->client == NULL) {
1245         /* client info was gc'd */
1246         resp->client = gen_client(r);
1247         if (resp->client != NULL) {
1248             opaque = ltox(r->pool, resp->client->key);
1249             stale = 1;
1250             client_list->num_renewed++;
1251         }
1252         else {
1253             opaque = "";                /* ??? */
1254         }
1255     }
1256     else {
1257         opaque = resp->opaque;
1258         /* we're generating a new nonce, so reset the nonce-count */
1259         resp->client->nonce_count = 0;
1260     }
1261
1262     if (opaque[0]) {
1263         opaque_param = apr_pstrcat(r->pool, ", opaque=\"", opaque, "\"", NULL);
1264     }
1265     else {
1266         opaque_param = NULL;
1267     }
1268
1269     /* Setup nonce */
1270
1271     nonce = gen_nonce(r->pool, r->request_time, opaque, r->server, conf);
1272     if (resp->client && conf->nonce_lifetime == 0) {
1273         memcpy(resp->client->last_nonce, nonce, NONCE_LEN+1);
1274     }
1275
1276     /* Setup MD5-sess stuff. Note that we just clear out the session
1277      * info here, since we can't generate a new session until the request
1278      * from the client comes in with the cnonce.
1279      */
1280
1281     if (!strcasecmp(conf->algorithm, "MD5-sess")) {
1282         clear_session(resp);
1283     }
1284
1285     /* setup domain attribute. We want to send this attribute wherever
1286      * possible so that the client won't send the Authorization header
1287      * unneccessarily (it's usually > 200 bytes!).
1288      */
1289
1290
1291     /* don't send domain
1292      * - for proxy requests
1293      * - if it's no specified
1294      */
1295     if (r->proxyreq || !conf->uri_list) {
1296         domain = NULL;
1297     }
1298     else {
1299         domain = conf->uri_list;
1300     }
1301
1302     apr_table_mergen(r->err_headers_out,
1303                      (PROXYREQ_PROXY == r->proxyreq)
1304                          ? "Proxy-Authenticate" : "WWW-Authenticate",
1305                      apr_psprintf(r->pool, "Digest realm=\"%s\", "
1306                                   "nonce=\"%s\", algorithm=%s%s%s%s%s",
1307                                   ap_auth_name(r), nonce, conf->algorithm,
1308                                   opaque_param ? opaque_param : "",
1309                                   domain ? domain : "",
1310                                   stale ? ", stale=true" : "", qop));
1311
1312 }
1313
1314
1315 /*
1316  * Authorization header verification code
1317  */
1318
1319 static authn_status get_hash(request_rec *r, const char *user,
1320                              digest_config_rec *conf)
1321 {
1322     authn_status auth_result;
1323     char *password;
1324     authn_provider_list *current_provider;
1325
1326     current_provider = conf->providers;
1327     do {
1328         const authn_provider *provider;
1329
1330         /* For now, if a provider isn't set, we'll be nice and use the file
1331          * provider.
1332          */
1333         if (!current_provider) {
1334             provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP,
1335                                           AUTHN_DEFAULT_PROVIDER, "0");
1336
1337             if (!provider || !provider->get_realm_hash) {
1338                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1339                               "No Authn provider configured");
1340                 auth_result = AUTH_GENERAL_ERROR;
1341                 break;
1342             }
1343             apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, AUTHN_DEFAULT_PROVIDER);
1344         }
1345         else {
1346             provider = current_provider->provider;
1347             apr_table_setn(r->notes, AUTHN_PROVIDER_NAME_NOTE, current_provider->provider_name);
1348         }
1349
1350
1351         /* We expect the password to be md5 hash of user:realm:password */
1352         auth_result = provider->get_realm_hash(r, user, conf->realm,
1353                                                &password);
1354
1355         apr_table_unset(r->notes, AUTHN_PROVIDER_NAME_NOTE);
1356
1357         /* Something occured.  Stop checking. */
1358         if (auth_result != AUTH_USER_NOT_FOUND) {
1359             break;
1360         }
1361
1362         /* If we're not really configured for providers, stop now. */
1363         if (!conf->providers) {
1364            break;
1365         }
1366
1367         current_provider = current_provider->next;
1368     } while (current_provider);
1369
1370     if (auth_result == AUTH_USER_FOUND) {
1371         conf->ha1 = password;
1372     }
1373
1374     return auth_result;
1375 }
1376
1377 static int check_nc(const request_rec *r, const digest_header_rec *resp,
1378                     const digest_config_rec *conf)
1379 {
1380     unsigned long nc;
1381     const char *snc = resp->nonce_count;
1382     char *endptr;
1383
1384     if (!conf->check_nc || !client_shm) {
1385         return OK;
1386     }
1387
1388     nc = strtol(snc, &endptr, 16);
1389     if (endptr < (snc+strlen(snc)) && !apr_isspace(*endptr)) {
1390         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1391                       "Digest: invalid nc %s received - not a number", snc);
1392         return !OK;
1393     }
1394
1395     if (!resp->client) {
1396         return !OK;
1397     }
1398
1399     if (nc != resp->client->nonce_count) {
1400         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1401                       "Digest: Warning, possible replay attack: nonce-count "
1402                       "check failed: %lu != %lu", nc,
1403                       resp->client->nonce_count);
1404         return !OK;
1405     }
1406
1407     return OK;
1408 }
1409
1410 static int check_nonce(request_rec *r, digest_header_rec *resp,
1411                        const digest_config_rec *conf)
1412 {
1413     apr_time_t dt;
1414     int len;
1415     time_rec nonce_time;
1416     char tmp, hash[NONCE_HASH_LEN+1];
1417
1418     if (strlen(resp->nonce) != NONCE_LEN) {
1419         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1420                       "Digest: invalid nonce %s received - length is not %d",
1421                       resp->nonce, NONCE_LEN);
1422         note_digest_auth_failure(r, conf, resp, 1);
1423         return HTTP_UNAUTHORIZED;
1424     }
1425
1426     tmp = resp->nonce[NONCE_TIME_LEN];
1427     resp->nonce[NONCE_TIME_LEN] = '\0';
1428     len = apr_base64_decode_binary(nonce_time.arr, resp->nonce);
1429     gen_nonce_hash(hash, resp->nonce, resp->opaque, r->server, conf);
1430     resp->nonce[NONCE_TIME_LEN] = tmp;
1431     resp->nonce_time = nonce_time.time;
1432
1433     if (strcmp(hash, resp->nonce+NONCE_TIME_LEN)) {
1434         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1435                       "Digest: invalid nonce %s received - hash is not %s",
1436                       resp->nonce, hash);
1437         note_digest_auth_failure(r, conf, resp, 1);
1438         return HTTP_UNAUTHORIZED;
1439     }
1440
1441     dt = r->request_time - nonce_time.time;
1442     if (conf->nonce_lifetime > 0 && dt < 0) {
1443         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1444                       "Digest: invalid nonce %s received - user attempted "
1445                       "time travel", resp->nonce);
1446         note_digest_auth_failure(r, conf, resp, 1);
1447         return HTTP_UNAUTHORIZED;
1448     }
1449
1450     if (conf->nonce_lifetime > 0) {
1451         if (dt > conf->nonce_lifetime) {
1452             ap_log_rerror(APLOG_MARK, APLOG_INFO, 0,r,
1453                           "Digest: user %s: nonce expired (%.2f seconds old "
1454                           "- max lifetime %.2f) - sending new nonce",
1455                           r->user, (double)apr_time_sec(dt),
1456                           (double)apr_time_sec(conf->nonce_lifetime));
1457             note_digest_auth_failure(r, conf, resp, 1);
1458             return HTTP_UNAUTHORIZED;
1459         }
1460     }
1461     else if (conf->nonce_lifetime == 0 && resp->client) {
1462         if (memcmp(resp->client->last_nonce, resp->nonce, NONCE_LEN)) {
1463             ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
1464                           "Digest: user %s: one-time-nonce mismatch - sending "
1465                           "new nonce", r->user);
1466             note_digest_auth_failure(r, conf, resp, 1);
1467             return HTTP_UNAUTHORIZED;
1468         }
1469     }
1470     /* else (lifetime < 0) => never expires */
1471
1472     return OK;
1473 }
1474
1475 /* The actual MD5 code... whee */
1476
1477 /* RFC-2069 */
1478 static const char *old_digest(const request_rec *r,
1479                               const digest_header_rec *resp, const char *ha1)
1480 {
1481     const char *ha2;
1482
1483     ha2 = ap_md5(r->pool, (unsigned char *)apr_pstrcat(r->pool, resp->method, ":",
1484                                                        resp->uri, NULL));
1485     return ap_md5(r->pool,
1486                   (unsigned char *)apr_pstrcat(r->pool, ha1, ":", resp->nonce,
1487                                               ":", ha2, NULL));
1488 }
1489
1490 /* RFC-2617 */
1491 static const char *new_digest(const request_rec *r,
1492                               digest_header_rec *resp,
1493                               const digest_config_rec *conf)
1494 {
1495     const char *ha1, *ha2, *a2;
1496
1497     if (resp->algorithm && !strcasecmp(resp->algorithm, "MD5-sess")) {
1498         ha1 = get_session_HA1(r, resp, conf, 1);
1499         if (!ha1) {
1500             return NULL;
1501         }
1502     }
1503     else {
1504         ha1 = conf->ha1;
1505     }
1506
1507     if (resp->message_qop && !strcasecmp(resp->message_qop, "auth-int")) {
1508         a2 = apr_pstrcat(r->pool, resp->method, ":", resp->uri, ":",
1509                          ap_md5(r->pool, (const unsigned char*) ""), NULL);
1510                          /* TBD */
1511     }
1512     else {
1513         a2 = apr_pstrcat(r->pool, resp->method, ":", resp->uri, NULL);
1514     }
1515     ha2 = ap_md5(r->pool, (const unsigned char *)a2);
1516
1517     return ap_md5(r->pool,
1518                   (unsigned char *)apr_pstrcat(r->pool, ha1, ":", resp->nonce,
1519                                                ":", resp->nonce_count, ":",
1520                                                resp->cnonce, ":",
1521                                                resp->message_qop, ":", ha2,
1522                                                NULL));
1523 }
1524
1525
1526 static void copy_uri_components(apr_uri_t *dst,
1527                                 apr_uri_t *src, request_rec *r) {
1528     if (src->scheme && src->scheme[0] != '\0') {
1529         dst->scheme = src->scheme;
1530     }
1531     else {
1532         dst->scheme = (char *) "http";
1533     }
1534
1535     if (src->hostname && src->hostname[0] != '\0') {
1536         dst->hostname = apr_pstrdup(r->pool, src->hostname);
1537         ap_unescape_url(dst->hostname);
1538     }
1539     else {
1540         dst->hostname = (char *) ap_get_server_name(r);
1541     }
1542
1543     if (src->port_str && src->port_str[0] != '\0') {
1544         dst->port = src->port;
1545     }
1546     else {
1547         dst->port = ap_get_server_port(r);
1548     }
1549
1550     if (src->path && src->path[0] != '\0') {
1551         dst->path = apr_pstrdup(r->pool, src->path);
1552         ap_unescape_url(dst->path);
1553     }
1554     else {
1555         dst->path = src->path;
1556     }
1557
1558     if (src->query && src->query[0] != '\0') {
1559         dst->query = apr_pstrdup(r->pool, src->query);
1560         ap_unescape_url(dst->query);
1561     }
1562     else {
1563         dst->query = src->query;
1564     }
1565
1566     dst->hostinfo = src->hostinfo;
1567 }
1568
1569 /* These functions return 0 if client is OK, and proper error status
1570  * if not... either HTTP_UNAUTHORIZED, if we made a check, and it failed, or
1571  * HTTP_INTERNAL_SERVER_ERROR, if things are so totally confused that we
1572  * couldn't figure out how to tell if the client is authorized or not.
1573  *
1574  * If they return DECLINED, and all other modules also decline, that's
1575  * treated by the server core as a configuration error, logged and
1576  * reported as such.
1577  */
1578
1579 /* Determine user ID, and check if the attributes are correct, if it
1580  * really is that user, if the nonce is correct, etc.
1581  */
1582
1583 static int authenticate_digest_user(request_rec *r)
1584 {
1585     digest_config_rec *conf;
1586     digest_header_rec *resp;
1587     request_rec       *mainreq;
1588     const char        *t;
1589     int                res;
1590     authn_status       return_code;
1591
1592     /* do we require Digest auth for this URI? */
1593
1594     if (!(t = ap_auth_type(r)) || strcasecmp(t, "Digest")) {
1595         return DECLINED;
1596     }
1597
1598     if (!ap_auth_name(r)) {
1599         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1600                       "Digest: need AuthName: %s", r->uri);
1601         return HTTP_INTERNAL_SERVER_ERROR;
1602     }
1603
1604
1605     /* get the client response and mark */
1606
1607     mainreq = r;
1608     while (mainreq->main != NULL) {
1609         mainreq = mainreq->main;
1610     }
1611     while (mainreq->prev != NULL) {
1612         mainreq = mainreq->prev;
1613     }
1614     resp = (digest_header_rec *) ap_get_module_config(mainreq->request_config,
1615                                                       &auth_digest_module);
1616     resp->needed_auth = 1;
1617
1618
1619     /* get our conf */
1620
1621     conf = (digest_config_rec *) ap_get_module_config(r->per_dir_config,
1622                                                       &auth_digest_module);
1623
1624
1625     /* check for existence and syntax of Auth header */
1626
1627     if (resp->auth_hdr_sts != VALID) {
1628         if (resp->auth_hdr_sts == NOT_DIGEST) {
1629             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1630                           "Digest: client used wrong authentication scheme "
1631                           "`%s': %s", resp->scheme, r->uri);
1632         }
1633         else if (resp->auth_hdr_sts == INVALID) {
1634             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1635                           "Digest: missing user, realm, nonce, uri, digest, "
1636                           "cnonce, or nonce_count in authorization header: %s",
1637                           r->uri);
1638         }
1639         /* else (resp->auth_hdr_sts == NO_HEADER) */
1640         note_digest_auth_failure(r, conf, resp, 0);
1641         return HTTP_UNAUTHORIZED;
1642     }
1643
1644     r->user         = (char *) resp->username;
1645     r->ap_auth_type = (char *) "Digest";
1646
1647     /* check the auth attributes */
1648
1649     if (strcmp(resp->uri, resp->raw_request_uri)) {
1650         /* Hmm, the simple match didn't work (probably a proxy modified the
1651          * request-uri), so lets do a more sophisticated match
1652          */
1653         apr_uri_t r_uri, d_uri;
1654
1655         copy_uri_components(&r_uri, resp->psd_request_uri, r);
1656         if (apr_uri_parse(r->pool, resp->uri, &d_uri) != APR_SUCCESS) {
1657             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1658                           "Digest: invalid uri <%s> in Authorization header",
1659                           resp->uri);
1660             return HTTP_BAD_REQUEST;
1661         }
1662
1663         if (d_uri.hostname) {
1664             ap_unescape_url(d_uri.hostname);
1665         }
1666         if (d_uri.path) {
1667             ap_unescape_url(d_uri.path);
1668         }
1669
1670         if (d_uri.query) {
1671             ap_unescape_url(d_uri.query);
1672         }
1673         else if (r_uri.query) {
1674             /* MSIE compatibility hack.  MSIE has some RFC issues - doesn't
1675              * include the query string in the uri Authorization component
1676              * or when computing the response component.  the second part
1677              * works out ok, since we can hash the header and get the same
1678              * result.  however, the uri from the request line won't match
1679              * the uri Authorization component since the header lacks the
1680              * query string, leaving us incompatable with a (broken) MSIE.
1681              *
1682              * the workaround is to fake a query string match if in the proper
1683              * environment - BrowserMatch MSIE, for example.  the cool thing
1684              * is that if MSIE ever fixes itself the simple match ought to
1685              * work and this code won't be reached anyway, even if the
1686              * environment is set.
1687              */
1688
1689             if (apr_table_get(r->subprocess_env,
1690                               "AuthDigestEnableQueryStringHack")) {
1691
1692                 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Digest: "
1693                               "applying AuthDigestEnableQueryStringHack "
1694                               "to uri <%s>", resp->raw_request_uri);
1695
1696                d_uri.query = r_uri.query;
1697             }
1698         }
1699
1700         if (r->method_number == M_CONNECT) {
1701             if (!r_uri.hostinfo || strcmp(resp->uri, r_uri.hostinfo)) {
1702                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1703                               "Digest: uri mismatch - <%s> does not match "
1704                               "request-uri <%s>", resp->uri, r_uri.hostinfo);
1705                 return HTTP_BAD_REQUEST;
1706             }
1707         }
1708         else if (
1709             /* check hostname matches, if present */
1710             (d_uri.hostname && d_uri.hostname[0] != '\0'
1711               && strcasecmp(d_uri.hostname, r_uri.hostname))
1712             /* check port matches, if present */
1713             || (d_uri.port_str && d_uri.port != r_uri.port)
1714             /* check that server-port is default port if no port present */
1715             || (d_uri.hostname && d_uri.hostname[0] != '\0'
1716                 && !d_uri.port_str && r_uri.port != ap_default_port(r))
1717             /* check that path matches */
1718             || (d_uri.path != r_uri.path
1719                 /* either exact match */
1720                 && (!d_uri.path || !r_uri.path
1721                     || strcmp(d_uri.path, r_uri.path))
1722                 /* or '*' matches empty path in scheme://host */
1723                 && !(d_uri.path && !r_uri.path && resp->psd_request_uri->hostname
1724                     && d_uri.path[0] == '*' && d_uri.path[1] == '\0'))
1725             /* check that query matches */
1726             || (d_uri.query != r_uri.query
1727                 && (!d_uri.query || !r_uri.query
1728                     || strcmp(d_uri.query, r_uri.query)))
1729             ) {
1730             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1731                           "Digest: uri mismatch - <%s> does not match "
1732                           "request-uri <%s>", resp->uri, resp->raw_request_uri);
1733             return HTTP_BAD_REQUEST;
1734         }
1735     }
1736
1737     if (resp->opaque && resp->opaque_num == 0) {
1738         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1739                       "Digest: received invalid opaque - got `%s'",
1740                       resp->opaque);
1741         note_digest_auth_failure(r, conf, resp, 0);
1742         return HTTP_UNAUTHORIZED;
1743     }
1744
1745     if (strcmp(resp->realm, conf->realm)) {
1746         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1747                       "Digest: realm mismatch - got `%s' but expected `%s'",
1748                       resp->realm, conf->realm);
1749         note_digest_auth_failure(r, conf, resp, 0);
1750         return HTTP_UNAUTHORIZED;
1751     }
1752
1753     if (resp->algorithm != NULL
1754         && strcasecmp(resp->algorithm, "MD5")
1755         && strcasecmp(resp->algorithm, "MD5-sess")) {
1756         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1757                       "Digest: unknown algorithm `%s' received: %s",
1758                       resp->algorithm, r->uri);
1759         note_digest_auth_failure(r, conf, resp, 0);
1760         return HTTP_UNAUTHORIZED;
1761     }
1762
1763     return_code = get_hash(r, r->user, conf);
1764
1765     if (return_code == AUTH_USER_NOT_FOUND) {
1766         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1767                       "Digest: user `%s' in realm `%s' not found: %s",
1768                       r->user, conf->realm, r->uri);
1769         note_digest_auth_failure(r, conf, resp, 0);
1770         return HTTP_UNAUTHORIZED;
1771     }
1772     else if (return_code == AUTH_USER_FOUND) {
1773         /* we have a password, so continue */
1774     }
1775     else if (return_code == AUTH_DENIED) {
1776         /* authentication denied in the provider before attempting a match */
1777         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1778                       "Digest: user `%s' in realm `%s' denied by provider: %s",
1779                       r->user, conf->realm, r->uri);
1780         note_digest_auth_failure(r, conf, resp, 0);
1781         return HTTP_UNAUTHORIZED;
1782     }
1783     else {
1784         /* AUTH_GENERAL_ERROR (or worse)
1785          * We'll assume that the module has already said what its error
1786          * was in the logs.
1787          */
1788         return HTTP_INTERNAL_SERVER_ERROR;
1789     }
1790
1791     if (resp->message_qop == NULL) {
1792         /* old (rfc-2069) style digest */
1793         if (strcmp(resp->digest, old_digest(r, resp, conf->ha1))) {
1794             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1795                           "Digest: user %s: password mismatch: %s", r->user,
1796                           r->uri);
1797             note_digest_auth_failure(r, conf, resp, 0);
1798             return HTTP_UNAUTHORIZED;
1799         }
1800     }
1801     else {
1802         const char *exp_digest;
1803         int match = 0, idx;
1804         for (idx = 0; conf->qop_list[idx] != NULL; idx++) {
1805             if (!strcasecmp(conf->qop_list[idx], resp->message_qop)) {
1806                 match = 1;
1807                 break;
1808             }
1809         }
1810
1811         if (!match
1812             && !(conf->qop_list[0] == NULL
1813                  && !strcasecmp(resp->message_qop, "auth"))) {
1814             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1815                           "Digest: invalid qop `%s' received: %s",
1816                           resp->message_qop, r->uri);
1817             note_digest_auth_failure(r, conf, resp, 0);
1818             return HTTP_UNAUTHORIZED;
1819         }
1820
1821         exp_digest = new_digest(r, resp, conf);
1822         if (!exp_digest) {
1823             /* we failed to allocate a client struct */
1824             return HTTP_INTERNAL_SERVER_ERROR;
1825         }
1826         if (strcmp(resp->digest, exp_digest)) {
1827             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1828                           "Digest: user %s: password mismatch: %s", r->user,
1829                           r->uri);
1830             note_digest_auth_failure(r, conf, resp, 0);
1831             return HTTP_UNAUTHORIZED;
1832         }
1833     }
1834
1835     if (check_nc(r, resp, conf) != OK) {
1836         note_digest_auth_failure(r, conf, resp, 0);
1837         return HTTP_UNAUTHORIZED;
1838     }
1839
1840     /* Note: this check is done last so that a "stale=true" can be
1841        generated if the nonce is old */
1842     if ((res = check_nonce(r, resp, conf))) {
1843         return res;
1844     }
1845
1846     return OK;
1847 }
1848
1849 /*
1850  * Authorization-Info header code
1851  */
1852
1853 static int add_auth_info(request_rec *r)
1854 {
1855     const digest_config_rec *conf =
1856                 (digest_config_rec *) ap_get_module_config(r->per_dir_config,
1857                                                            &auth_digest_module);
1858     digest_header_rec *resp =
1859                 (digest_header_rec *) ap_get_module_config(r->request_config,
1860                                                            &auth_digest_module);
1861     const char *ai = NULL, *nextnonce = "";
1862
1863     if (resp == NULL || !resp->needed_auth || conf == NULL) {
1864         return OK;
1865     }
1866
1867     /* 2069-style entity-digest is not supported (it's too hard, and
1868      * there are no clients which support 2069 but not 2617). */
1869
1870     /* setup nextnonce
1871      */
1872     if (conf->nonce_lifetime > 0) {
1873         /* send nextnonce if current nonce will expire in less than 30 secs */
1874         if ((r->request_time - resp->nonce_time) > (conf->nonce_lifetime-NEXTNONCE_DELTA)) {
1875             nextnonce = apr_pstrcat(r->pool, ", nextnonce=\"",
1876                                    gen_nonce(r->pool, r->request_time,
1877                                              resp->opaque, r->server, conf),
1878                                    "\"", NULL);
1879             if (resp->client)
1880                 resp->client->nonce_count = 0;
1881         }
1882     }
1883     else if (conf->nonce_lifetime == 0 && resp->client) {
1884         const char *nonce = gen_nonce(r->pool, 0, resp->opaque, r->server,
1885                                       conf);
1886         nextnonce = apr_pstrcat(r->pool, ", nextnonce=\"", nonce, "\"", NULL);
1887         memcpy(resp->client->last_nonce, nonce, NONCE_LEN+1);
1888     }
1889     /* else nonce never expires, hence no nextnonce */
1890
1891
1892     /* do rfc-2069 digest
1893      */
1894     if (conf->qop_list[0] && !strcasecmp(conf->qop_list[0], "none")
1895         && resp->message_qop == NULL) {
1896         /* use only RFC-2069 format */
1897         ai = nextnonce;
1898     }
1899     else {
1900         const char *resp_dig, *ha1, *a2, *ha2;
1901
1902         /* calculate rspauth attribute
1903          */
1904         if (resp->algorithm && !strcasecmp(resp->algorithm, "MD5-sess")) {
1905             ha1 = get_session_HA1(r, resp, conf, 0);
1906             if (!ha1) {
1907                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1908                               "Digest: internal error: couldn't find session "
1909                               "info for user %s", resp->username);
1910                 return !OK;
1911             }
1912         }
1913         else {
1914             ha1 = conf->ha1;
1915         }
1916
1917         if (resp->message_qop && !strcasecmp(resp->message_qop, "auth-int")) {
1918             a2 = apr_pstrcat(r->pool, ":", resp->uri, ":",
1919                              ap_md5(r->pool,(const unsigned char *) ""), NULL);
1920                              /* TBD */
1921         }
1922         else {
1923             a2 = apr_pstrcat(r->pool, ":", resp->uri, NULL);
1924         }
1925         ha2 = ap_md5(r->pool, (const unsigned char *)a2);
1926
1927         resp_dig = ap_md5(r->pool,
1928                           (unsigned char *)apr_pstrcat(r->pool, ha1, ":",
1929                                                        resp->nonce, ":",
1930                                                        resp->nonce_count, ":",
1931                                                        resp->cnonce, ":",
1932                                                        resp->message_qop ?
1933                                                          resp->message_qop : "",
1934                                                        ":", ha2, NULL));
1935
1936         /* assemble Authentication-Info header
1937          */
1938         ai = apr_pstrcat(r->pool,
1939                          "rspauth=\"", resp_dig, "\"",
1940                          nextnonce,
1941                          resp->cnonce ? ", cnonce=\"" : "",
1942                          resp->cnonce
1943                            ? ap_escape_quotes(r->pool, resp->cnonce)
1944                            : "",
1945                          resp->cnonce ? "\"" : "",
1946                          resp->nonce_count ? ", nc=" : "",
1947                          resp->nonce_count ? resp->nonce_count : "",
1948                          resp->message_qop ? ", qop=" : "",
1949                          resp->message_qop ? resp->message_qop : "",
1950                          NULL);
1951     }
1952
1953     if (ai && ai[0]) {
1954         apr_table_mergen(r->headers_out,
1955                          (PROXYREQ_PROXY == r->proxyreq)
1956                              ? "Proxy-Authentication-Info"
1957                              : "Authentication-Info",
1958                          ai);
1959     }
1960
1961     return OK;
1962 }
1963
1964
1965 static void register_hooks(apr_pool_t *p)
1966 {
1967     static const char * const cfgPost[]={ "http_core.c", NULL };
1968     static const char * const parsePre[]={ "mod_proxy.c", NULL };
1969
1970     ap_hook_post_config(initialize_module, NULL, cfgPost, APR_HOOK_MIDDLE);
1971     ap_hook_child_init(initialize_child, NULL, NULL, APR_HOOK_MIDDLE);
1972     ap_hook_post_read_request(parse_hdr_and_update_nc, parsePre, NULL, APR_HOOK_MIDDLE);
1973     ap_hook_check_user_id(authenticate_digest_user, NULL, NULL, APR_HOOK_MIDDLE);
1974
1975     ap_hook_fixups(add_auth_info, NULL, NULL, APR_HOOK_MIDDLE);
1976 }
1977
1978 module AP_MODULE_DECLARE_DATA auth_digest_module =
1979 {
1980     STANDARD20_MODULE_STUFF,
1981     create_digest_dir_config,   /* dir config creater */
1982     NULL,                       /* dir merger --- default is to override */
1983     NULL,                       /* server config */
1984     NULL,                       /* merge server config */
1985     digest_cmds,                /* command table */
1986     register_hooks              /* register hooks */
1987 };
1988