~ubuntu-branches/ubuntu/feisty/apache2/feisty

« back to all changes in this revision

Viewing changes to modules/aaa/mod_auth_digest.c

  • Committer: Bazaar Package Importer
  • Author(s): Andreas Barth
  • Date: 2006-12-09 21:05:45 UTC
  • mfrom: (0.6.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20061209210545-h70s0xaqc2v8vqr2
Tags: 2.2.3-3.2
* Non-maintainer upload.
* 043_ajp_connection_reuse: Patch from upstream Bugzilla, fixing a critical
  issue with regard to connection reuse in mod_proxy_ajp.
  Closes: #396265

Show diffs side-by-side

added added

removed removed

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