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