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