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