You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by "Life is hard, and then you die." <ro...@innovation.ch> on 1998/11/20 10:42:11 UTC

Updated mod_digest

I've mentioned that I've been working on a new version of mod_digest, so
here it is. The changes from the current mod_digest include:

  - Update to latest digest draft (draft-ietf-http-authentication-03);
    includes handling of new attributes: qop, algorithm, cnonce, and
    nonce_count. Still handles RFC-2069 clients.

  - New config directives: AuthDigestQop, AuthDigestNonceLifetime,
  			   AuthDigestNonceFormat, AuthDigestNcCheck,
  			   AuthDigestAlgorithm, and AuthDigestDomain

  - A cryptographically secure random nonce is used (160 bit secret
    hashed up with time and realm using SHA-1).

  - Response nonce is now checked, and nonce expiry implemented.

  - "stale=true" is generated on expired nonces (if the response is correct
    otherwise).

  - In general, much more checking is done (the realm, algorithm, uri, and
    qop attributes are all checked for validity).

  - The (Proxy-)Authentication-Info header is generated (with nextnonce)

  - Improved header parser: correctly deals with whitespace, handles
    escaped chars in quoted strings, is "maximally loose" (should accept
    anything that is parseable), is faster, etc.

  - note_digest_auth_failure() is not part of public api anymore. This is
    because it needs much more info to generate the header, and nobody
    besides mod_digest seems to need it anyway.

  - mod_digest must be invoked after mod_proxy (i.e. it must be listed
    before in the Configuration file) because the WWW-Authenticate and
    Proxy-Authenticate headers are parsed in the post-read-request phase
    and need to know if this is a proxy request or not.

  - Requires the "truerand" library (-> slow startup)

There are some open issues, as mentioned in the header comment and as
marked with TBD (To Be Done). I plan on trying to rip off the shared
memory and locking stuff from http_main and then implementing
nonce-count checking, one-time nonces, and session management for
MD5-sess. This'll happen hopefully in the next couple weeks.

Attached are the (unified) diffs and the complete mod_digest.c and
mod_digest.html . If anybody is looking for a client to run against
the new module and have java installed on their machine, then you
can get the lastest snapshot of my client at
http://www.innovation.ch/java/HTTPClient/V0.4-dev/
Also attached is the patch for Configuration.tmpl to move mod_proxy
up earlier in the processing.

One thing: there is a binary version of base64-encoding and decoding 
(i.e. binary -> string (encoding), and string -> binary (decoding)
in this mod_digest - it might be useful to extract them and put them
in util as ap_base64encode_binary() and ap_base64decode_binary().
ap_uuencode() and ap_uudecode() could then be implemented in terms
of these binary versions. If you think this is reasonable I'll supply
the patches.

Another thing: mod_proxy currently totally ignores any headers set by
any module. This prevents the Proxy-Authentication-Info header from
being sent. I've therefore also attached a couple patches to
proxy_http.c and proxy_ftp.c which simply merge in the headers set by
other modules (with the proxy generated headers overriding the others).
I'm not completely sure this is correct way to do it, though. I think
the best solution would be for the proxy to generate the headers in an
earlier phase so that the fixup handler could just add the
Proxy-Authorization-Info header.


  Cheers,

  Ronald


----- mod_digest.c (diff) ---------------------------------------------
--- mod_digest.c.orig	Sat Oct  3 12:12:23 1998
+++ mod_digest.c	Thu Nov 19 20:19:48 1998
@@ -59,113 +59,702 @@
  * mod_digest: MD5 digest authentication
  * 
  * by Alexei Kosut <ak...@nueva.pvt.k12.ca.us>
+ * and Ronald Tschal�r <ro...@innovation.ch>
  * based on mod_auth, by Rob McCool and Robert S. Thau
  *
+ * Requires the truerand library, available for instance from
+ * http://members.tripod.com/dr0nf/truerand/truerand.tar-gz
+ *
+ * Open Issues:
+ *   - qop=auth-int (when streams and trailer support available)
+ *   - one-time nonces (when shared-mem and semaphores available)
+ *   - nonce-count checking (when shared-mem and semaphores available)
+ *   - session management for algorithm=MD5-sess (when shared-mem and
+ *     semaphores available)
+ *   - nonce-format configurability
+ *   - Proxy-Authorization-Info header is set by this module, but is
+ *     currently ignored by mod_proxy (needs patch to mod_proxy)
+ *   - generating the secret takes a while (~ 8 seconds)
  */
 
 #include "httpd.h"
 #include "http_config.h"
 #include "http_core.h"
+#include "http_request.h"
 #include "http_log.h"
 #include "http_protocol.h"
+#include "ap_config.h"
+#include "ap_ctype.h"
+#include "util_uri.h"
 #include "util_md5.h"
 
+
+/* from the truerand library */
+
+typedef struct {
+    long totalLength;
+    unsigned long h[5];
+    unsigned long w[80];
+} SHS_CTX;
+
+extern void shsInit(SHS_CTX *ctx);
+extern void shsUpdate(SHS_CTX *ctx, const unsigned char *s, unsigned int n);
+extern void shsFinal(SHS_CTX *ctx);
+extern int  randbyte(void);
+
+
+/* struct to hold the configuration info */
+
 typedef struct digest_config_struct {
-    char *pwfile;
+    ap_pool     *pool;
+    const char  *dir_name;
+    const char  *pwfile;
+    const char  *realm;
+    const char **qop_list;
+    long         nonce_lifetime;
+    const char  *nonce_format;
+    int          check_nc;
+    const char  *algorithm;
+    char        *uri_list;
+    const char  *ha1;
 } digest_config_rec;
 
+
+/* struct to hold a parsed Authorization header */
+
+enum hdr_sts { NO_HEADER, NOT_DIGEST, INVALID, VALID };
+
 typedef struct digest_header_struct {
-    char *username;
-    char *realm;
-    char *nonce;
-    char *requested_uri;
-    char *digest;
+    const char           *scheme;
+    const char           *realm;
+    const char           *username;
+          char           *nonce;
+    const char           *uri;
+    const char           *digest;
+    const char           *algorithm;
+    const char           *cnonce;
+    const char           *opaque;
+    const char           *message_qop;
+    const char           *nonce_count;
+    time_t                nonce_time;
+    enum hdr_sts          auth_hdr_sts;
+    uri_components       *request_uri;
 } digest_header_rec;
 
-static void *create_digest_dir_config(pool *p, char *d)
+
+/* (mostly) nonce stuff */
+
+typedef union time_union {
+    time_t	  time;
+    unsigned char arr[sizeof(time_t)];
+} time_rec;
+
+
+#define	DFLT_ALGORITHM	"MD5"
+
+#define	DFLT_NONCE_LIFE	300L
+#define NEXTNONCE_DELTA	30
+
+
+#define NONCE_TIME_LEN	(((sizeof(time_t)+2)/3)*4)
+#define NONCE_HASH_LEN	40
+#define NONCE_LEN	(NONCE_TIME_LEN + NONCE_HASH_LEN)
+
+#define	SECRET_LEN	20
+
+
+static unsigned char secret[SECRET_LEN];
+static int call_cnt = 0;
+
+static void initialize_secret(server_rec *s, pool *p)
+{
+    int idx;
+
+    /* because it takes so long we only want to do it once. */
+    if (++call_cnt != 2)
+	return;
+
+    /* this will increase the startup time of the server, unfortunately...
+     * (generating 20 bytes takes about 8 seconds)
+     */
+    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s,
+		 "generating secret for digest authentication ...");
+
+    for (idx=0; idx<sizeof(secret); idx++)
+	secret[idx] = (unsigned char) randbyte();
+
+    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s, "done");
+}
+
+
+/*
+ * configuration code
+ */
+
+static void *create_digest_dir_config(pool *p, char *dir)
+{
+    digest_config_rec *conf;
+
+    if (dir == NULL)  return NULL;
+
+    conf = (digest_config_rec *) ap_pcalloc(p, sizeof(digest_config_rec));
+    if (conf) {
+	conf->pool           = p;
+	conf->qop_list       = ap_palloc(p, sizeof(char*));
+	conf->qop_list[0]    = NULL;
+	conf->nonce_lifetime = DFLT_NONCE_LIFE;
+	conf->dir_name       = ap_pstrdup(p, dir);
+	conf->algorithm      = DFLT_ALGORITHM;
+    }
+
+    return conf;
+}
+
+static const char *set_digest_file(cmd_parms *cmd, void *config,
+				   const char *file)
+{
+    ((digest_config_rec *) config)->pwfile = file;
+    return NULL;
+}
+
+static const char *set_qop(cmd_parms *cmd, void *config, const char *op)
+{
+    digest_config_rec *conf = (digest_config_rec *) config;
+    const char **tmp;
+    int cnt;
+
+    if (!strcasecmp(op, "none")) {
+	if (conf->qop_list[0] == NULL) {
+	    conf->qop_list = ap_palloc(cmd->pool, 2 * sizeof(char*));
+	    conf->qop_list[1] = NULL;
+	}
+	conf->qop_list[0] = "none";
+	return NULL;
+    }
+
+    if (!strcasecmp(op, "auth-int"))
+	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, cmd->server,
+		     "WARNING: qop `auth-int' currently only works correctly " \
+		     "for responses with no entity");
+    else if (strcasecmp(op, "auth"))
+	return ap_pstrcat(cmd->pool, "Unrecognized qop: ", op, NULL);
+
+    for (cnt=0; conf->qop_list[cnt] != NULL; cnt++)
+	;
+    tmp = ap_palloc(cmd->pool, (cnt+2)*sizeof(char*));
+    memcpy(tmp, conf->qop_list, cnt*sizeof(char*));
+    tmp[cnt]   = ap_pstrdup(cmd->pool, op);
+    tmp[cnt+1] = NULL;
+    conf->qop_list = tmp;
+
+    return NULL;
+}
+
+static const char *set_nonce_lifetime(cmd_parms *cmd, void *config,
+				      const char *t)
+{
+    char *endptr;
+    long  time;
+
+    time = strtol(t, &endptr, 10);
+    if (endptr < (t+strlen(t))  &&  !ap_isspace(*endptr))
+	return ap_pstrcat(cmd->pool, "Invalid time in AuthDigestNonceLifetime: ", t, NULL);
+
+    ((digest_config_rec *) config)->nonce_lifetime = time;
+    return NULL;
+}
+
+static const char *set_nonce_format(cmd_parms *cmd, void *config,
+				    const char *fmt)
+{
+    ((digest_config_rec *) config)->nonce_format = fmt;
+    return "AuthDigestNonceFormat is not implemented (yet)";
+}
+
+static const char *set_nc_check(cmd_parms *cmd, void *config, int flag)
 {
-    return ap_pcalloc(p, sizeof(digest_config_rec));
+    ((digest_config_rec *) config)->check_nc = flag;
+    return "AuthDigestNcCheck is not implemented (yet)";
 }
 
-static const char *set_digest_slot(cmd_parms *cmd, void *offset, char *f, char *t)
+static const char *set_algorithm(cmd_parms *cmd, void *config, const char *alg)
 {
-    if (t && strcmp(t, "standard"))
-	return ap_pstrcat(cmd->pool, "Invalid auth file type: ", t, NULL);
+    if (!strcasecmp(alg, "MD5-sess"))
+	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, cmd->server,
+		     "WARNING: algorithm `MD5-sess' is currently not " \
+		     "correctly implemented");
+    else if (strcasecmp(alg, "MD5"))
+	return ap_pstrcat(cmd->pool, "Invalid algorithm in AuthDigestAlgorithm: ", alg, NULL);
+
+    ((digest_config_rec *) config)->algorithm = alg;
+    return NULL;
+}
 
-    return ap_set_string_slot(cmd, offset, f);
+static const char *set_uri_list(cmd_parms *cmd, void *config, const char *uri)
+{
+    digest_config_rec *c = (digest_config_rec *) config;
+    if (c->uri_list) {
+	c->uri_list[strlen(c->uri_list)-1] = '\0';
+	c->uri_list = ap_pstrcat(cmd->pool, c->uri_list, " ", uri, "\"", NULL);
+    }
+    else
+	c->uri_list = ap_pstrcat(cmd->pool, ", domain=\"", uri, "\"", NULL);
+    return NULL;
 }
 
 static const command_rec digest_cmds[] =
 {
-    {"AuthDigestFile", set_digest_slot,
-  (void *) XtOffsetOf(digest_config_rec, pwfile), OR_AUTHCFG, TAKE12, NULL},
+    {"AuthDigestFile", set_digest_file, NULL, OR_AUTHCFG, TAKE1,
+     "The name of the file containing the usernames and password hashes"},
+    {"AuthDigestQop", set_qop, NULL, OR_AUTHCFG, ITERATE,
+     "A list of quality-of-protection options"},
+    {"AuthDigestNonceLifetime", set_nonce_lifetime, NULL, OR_AUTHCFG, TAKE1,
+     "Maximum lifetime of the server nonce (seconds)"},
+    {"AuthDigestNonceFormat", set_nonce_format, NULL, OR_AUTHCFG, TAKE1,
+     "The format to use when generating the server nonce"},
+    {"AuthDigestNcCheck", set_nc_check, NULL, OR_AUTHCFG, FLAG,
+     "Whether or not to check the nonce-count sent by the client"},
+    {"AuthDigestAlgorithm", set_algorithm, NULL, OR_AUTHCFG, TAKE1,
+     "The algorithm used for the hash calculation"},
+    {"AuthDigestDomain", set_uri_list, NULL, OR_AUTHCFG, ITERATE,
+     "A list of URI's which belong to the same protection space as the current URI"},
     {NULL}
 };
 
 module MODULE_VAR_EXPORT digest_module;
 
-static char *get_hash(request_rec *r, char *user, char *auth_pwfile)
+
+/*
+ * base-64 encoding helpers
+ */
+
+/* this is copied from util.c, with toascii folded into the table for EBCDIC */
+static const unsigned char pr2six[256] =
 {
-    configfile_t *f;
-    char l[MAX_STRING_LEN];
-    const char *rpw;
-    char *w, *x;
+#ifndef CHARSET_EBCDIC
+    /* ASCII table */
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
+    64,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
+    64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
+#else /*CHARSET_EBCDIC*/
+    /* EBCDIC table */
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 64, 64, 64, 64, 64, 64,
+    64, 35, 36, 37, 38, 39, 40, 41, 42, 43, 64, 64, 64, 64, 64, 64,
+    64, 64, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 64,
+    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+    64,  0,  1,  2,  3,  4,  5,  6,  7,  8, 64, 64, 64, 64, 64, 64,
+    64,  9, 10, 11, 12, 13, 14, 15, 16, 17, 64, 64, 64, 64, 64, 64,
+    64, 64, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 64,
+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
+#endif /*CHARSET_EBCDIC*/
+};
 
-    if (!(f = ap_pcfg_openfile(r->pool, auth_pwfile))) {
-	ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
-		    "Could not open password file: %s", auth_pwfile);
-	return NULL;
+/* this is the same as ap_uudecode in util.c, but returns the length instead
+ * of a pointer to the decoded data and takes a pointer to the decoded buffer
+ * as a third parameter. Also, for EBCDIC machines the toebcdic[] on the ouput
+ * is left out because we want a binary result.
+ */
+static int base64decode(pool *p, const char *bufcoded, unsigned char **bufplain)
+{
+    int nbytesdecoded;
+    register const unsigned char *bufin;
+    register unsigned char *bufout;
+    register int nprbytes;
+
+    /* Strip leading whitespace. */
+
+    while (*bufcoded == ' ' || *bufcoded == '\t')
+	bufcoded++;
+
+    /* Figure out how many characters are in the input buffer.
+     * Allocate this many from the per-transaction pool for the result.
+     */
+    bufin = (const unsigned char *) bufcoded;
+    while (pr2six[*(bufin++)] <= 63);
+    nprbytes = (bufin - (const unsigned char *) bufcoded) - 1;
+    nbytesdecoded = ((nprbytes + 3) / 4) * 3;
+
+    if (*bufplain == NULL)
+	*bufplain = ap_palloc(p, nbytesdecoded + 1);
+    bufout = *bufplain;
+
+    bufin = (const unsigned char *) bufcoded;
+
+    while (nprbytes > 0) {
+	*(bufout++) =
+	    (unsigned char) (pr2six[bufin[0]] << 2 | pr2six[bufin[1]] >> 4);
+	*(bufout++) =
+	    (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
+	*(bufout++) =
+	    (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
+	bufin += 4;
+	nprbytes -= 4;
     }
-    while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {
-	if ((l[0] == '#') || (!l[0]))
-	    continue;
-	rpw = l;
-	w = ap_getword(r->pool, &rpw, ':');
-	x = ap_getword(r->pool, &rpw, ':');
 
-	if (x && w && !strcmp(user, w) && !strcmp(ap_auth_name(r), x)) {
-	    ap_cfg_closefile(f);
-	    return ap_pstrdup(r->pool, rpw);
+    if (nprbytes & 03) {
+	if (pr2six[bufin[-2]] > 63)
+	    nbytesdecoded -= 2;
+	else
+	    nbytesdecoded -= 1;
+    }
+    (*bufplain)[nbytesdecoded] = '\0';
+
+    return nbytesdecoded;
+}
+
+static const char six2pr[64] =
+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/* This is similar to ap_uuencode except that it takes a length parameter
+ * (so we can encode binary data) and fixes a bug. Also note that no
+ * toascii[] is done on the input for EBCDIC (six2pr is automatically
+ * right).
+ */
+static char *base64encode(pool *p, const unsigned char *bufplain, int buflen)
+{
+    int nbytescoded;
+    char *bufcoded;
+    register const unsigned char *bufin;
+    register char *bufout;
+    register int nsixbytes;
+
+    /* Figure out how many characters are in the input buffer.
+     * Allocate this many from the per-transaction pool for the result.
+     */
+    nsixbytes = ((buflen + 2) / 3) * 4;
+
+    bufcoded = ap_palloc(p, nsixbytes + 1);
+    bufcoded[nsixbytes] = '\0';
+
+    bufin  = bufplain;
+    bufout = bufcoded;
+
+    nbytescoded = 0;
+
+    while (nbytescoded < (buflen - 2)) {
+	*(bufout++) = six2pr[(bufin[0] >> 2) & 0x3F];
+	*(bufout++) =
+		six2pr[((bufin[1] >> 4) & 0x0F) | ((bufin[0] << 4) & 0x3F)];
+	*(bufout++) =
+		six2pr[((bufin[2] >> 6) & 0x03) | ((bufin[1] << 2) & 0x3F)];
+	*(bufout++) = six2pr[bufin[2] & 0x3F];
+	bufin += 3;
+	nbytescoded += 3;
+    }
+
+    if (nbytescoded < buflen) {
+	*(bufout++) = six2pr[(bufin[0] >> 2) & 0x3F];
+	if (nbytescoded < (buflen-1)) {
+	    *(bufout++) =
+		    six2pr[((bufin[1] >> 4) & 0x0F) | ((bufin[0] << 4) & 0x3F)];
+	    *(bufout++) = six2pr[(bufin[1] << 2) & 0x3F];
+	}
+	else {
+	    *(bufout++) = six2pr[(bufin[0] << 4) & 0x3F];
 	}
     }
-    ap_cfg_closefile(f);
-    return NULL;
+
+    while (bufout < (bufcoded+nsixbytes))
+	*(bufout++) = (unsigned char) '=';
+
+    return bufcoded;
 }
 
-/* Parse the Authorization header, if it exists */
 
-static int get_digest_rec(request_rec *r, digest_header_rec * response)
+/*
+ * Nonce generation code
+ */
+
+/* The hash part of the nonce is a SHA-1 hash of the time, realm, opaque,
+ * and our secret.
+ */
+static void gen_nonce_hash(char *hash, const char *time, const char *realm,
+			   const char *opaque)
 {
-    const char *auth_line = ap_table_get(r->headers_in,
-                                    r->proxyreq ? "Proxy-Authorization"
-                                    : "Authorization");
-    int l;
-    int s, vk = 0, vv = 0;
-    const char *t;
-    char *key, *value;
-    const char *scheme;
+    const char *hex = "0123456789abcdef";
+    SHS_CTX ctx;
+    int idx;
+
+    shsInit(&ctx);
+    shsUpdate(&ctx, (unsigned char *) time, strlen(time));
+    shsUpdate(&ctx, (unsigned char *) realm, strlen(realm));
+    if (opaque)
+	shsUpdate(&ctx, (unsigned char *) opaque, strlen(opaque));
+    shsUpdate(&ctx, secret, sizeof(secret));
+    shsFinal(&ctx);
+
+    for (idx=0; idx<5; idx++) {
+	/* like sprintf(hash, "%08lx", ctx.h[idx]), but faster */
+	unsigned long x = ctx.h[idx];
+	*hash++ = hex[(x >> 28) & 0xF];
+	*hash++ = hex[(x >> 24) & 0xF];
+	*hash++ = hex[(x >> 20) & 0xF];
+	*hash++ = hex[(x >> 16) & 0xF];
+	*hash++ = hex[(x >> 12) & 0xF];
+	*hash++ = hex[(x >>  8) & 0xF];
+	*hash++ = hex[(x >>  4) & 0xF];
+	*hash++ = hex[x & 0xF];
+    }
 
-    if (!(t = ap_auth_type(r)) || strcasecmp(t, "Digest"))
-	return DECLINED;
+    *hash++ = '\0';
+}
 
-    if (!ap_auth_name(r)) {
-	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
-		    "need AuthName: %s", r->uri);
-	return SERVER_ERROR;
+
+/* The nonce has the format b64(time)+hash .
+ */
+static const char *gen_nonce(pool *p, time_t now, const char *realm,
+			     const char *opaque)
+{
+    char *nonce = ap_palloc(p, NONCE_LEN+1);
+    time_rec t;
+
+    t.time = now;
+    memcpy(nonce, base64encode(p, t.arr, sizeof(t.arr)), NONCE_TIME_LEN+1);
+    gen_nonce_hash(nonce+NONCE_TIME_LEN, nonce, realm, opaque);
+
+    return nonce;
+}
+
+
+/*
+ * MD5-sess code.
+ *
+ * If you want to use algorithm=MD5-sess you must write get_userpw_hash()
+ * yourself (see below).
+ */
+
+/*
+ * get_userpw_hash() will be called each time a new session needs to be
+ * generated and is expected to return the equivalent of
+ *
+ * ap_md5(r->pool,
+ *        ap_pstrcat(r->pool, username, ":", ap_auth_name(r), ":", passwd))
+ *
+ * You must implement this yourself, and will probably consist of code
+ * contacting the password server and retrieving the hash from it.
+ *
+ * TBD: This function should probably be in a seperate source file so that
+ * people need no modify mod_digest.c each time a new release comes out.
+ */
+static const char *get_userpw_hash(const request_rec *r,
+				   const digest_header_rec *resp,
+				   const digest_config_rec *conf)
+{
+    /* for now, just get it from pwfile */
+    return conf->ha1;
+}
+
+
+static const char *get_session(const request_rec *r,
+			       const digest_header_rec *resp,
+			       const digest_config_rec *conf)
+{
+    const char *ha1 = NULL, *urp;
+
+    /* get ha1 from session list - TBD */
+
+    /* generate new session if necessary */
+    if (ha1 == NULL) {
+	urp = get_userpw_hash(r, resp, conf);
+	ha1 = ap_md5(r->pool,
+		     (unsigned char *) ap_pstrcat(r->pool, ha1, ":", resp->nonce,
+						  ":", resp->cnonce, NULL));
+    }
+
+    return ha1;
+}
+
+
+static void clear_session(const request_rec *r, const digest_header_rec *resp,
+			  const digest_config_rec *conf)
+{
+    /* TBD */
+}
+
+
+/*
+ * Authorization challenge generation code (for WWW-Authenticate)
+ */
+
+static const char *guess_domain(pool *p, const char *uri, const char *filename,
+				const char *dir)
+{
+    size_t u_len = strlen(uri), f_len = strlen(filename), d_len = strlen(dir);
+    const char *u, *f;
+
+
+    /* Because of things like mod_alias and mod_rewrite and the fact that
+     * protection is often on a directory basis (not a location basis) it
+     * is hard to determine the uri to put in the domain attribute.
+     *
+     * What we do is the following: first we see if the directory is
+     * a prefix for the uri - if this is the case we assume that therefore
+     * a <Location> directive was protecting this uri and we can use it
+     * for the domain.
+     */
+    if (u_len >= d_len  &&  !memcmp(uri, dir, d_len))
+	return dir;
+
+    /* Now we check for <Files ...>, and if we find one we send back a
+     * dummy uri - this is the only way to specify that the protection
+     * space only covers a single uri.
+     */
+    if (dir[0] != '/')
+	return "http://0.0.0.0/";
+
+    /* Next we find the largest common common suffix of the request-uri
+     * and the final file name, ignoring any extensions; this gives us a
+     * hint as to where any rewriting could've occured (assuming that some
+     * prefix of the uri is rewritten, not a suffix).
+     */
+    u = uri + u_len - 1;	/* strip any extension */
+    while (u > uri  &&  *u != '/')  u--;
+    while (*u  &&  *u != '.')  u++;
+    if (*u == '.')  u--;
+    if (*u == '/')  u--;
+
+    f = filename + f_len - 1;	/* strip any extension */
+    while (f > filename  &&  *f != '/')  f--;
+    while (*f  &&  *f != '.')  f++;
+    if (*f == '.')  f--;
+    if (*f == '/')  f--;
+
+    while (*f == *u  &&  f > filename  &&  u > uri)  u--, f--;
+    f++; u++;
+
+    while (*f  &&  *f != '/')  f++, u++;	/* suffix must start with / */
+
+    /* Now, if the directory reaches into this common suffix then we can
+     * take the uri with the same reach.
+     */
+    if ((f-filename) < d_len) {
+	char *tmp = ap_pstrdup(p, uri);
+	tmp[(u-uri)+(d_len-(f-filename))] = '\0';
+	return tmp;
+    }
+
+    return "";	/* give up */
+}
+
+
+static void note_digest_auth_failure(request_rec *r,
+				     const digest_config_rec *conf,
+				     const digest_header_rec *resp, int stale)
+{
+    const char *qop, *opaque, *opaque_param, *domain;
+    int   cnt;
+
+    if (conf->qop_list[0] == NULL)
+	qop = ", qop=\"auth\"";
+    else if (!strcasecmp(conf->qop_list[0], "none"))
+	qop = "";
+    else {
+	qop = ap_pstrcat(r->pool, ", qop=\"", conf->qop_list[0], NULL);
+	for (cnt=1; conf->qop_list[cnt] != NULL; cnt++)
+	    qop = ap_pstrcat(r->pool, qop, ",", conf->qop_list[cnt], NULL);
+	qop = ap_pstrcat(r->pool, qop, "\"", NULL);
+    }
+
+    if (!stale  &&  !strcasecmp(conf->algorithm, "MD5-sess"))
+	clear_session(r, resp, conf);
+
+    if (resp->opaque == NULL) {
+	/* new client */
+	if (conf->check_nc  ||  !strcasecmp(conf->algorithm, "MD5-sess")) {
+	    /* so generate a unique opaque string */
+	    /* TBD when nonce-count checking is impl */
+	    opaque = "";
+	}
+	else {
+	    /* opaque not needed */
+	    opaque = "";
+	}
+    }
+    else {
+	opaque = resp->opaque;
+    }
+    if (opaque[0])
+	opaque_param = ap_pstrcat(r->pool, ", opaque=\"", opaque, "\"", NULL);
+    else
+	opaque_param = NULL;
+
+    /* setup domain attribute. We want to send this attribute wherever
+     * possible so that the client won't send the Authorization header
+     * unneccessarily (it's usually > 200 bytes!).
+     */
+
+    if (conf->uri_list)
+	domain = conf->uri_list;
+    else {
+	/* They didn't specify any domain, so let's guess at it */
+	domain = guess_domain(r->pool, resp->request_uri->path, r->filename,
+			      conf->dir_name);
+	if (domain[0] == '/'  &&  domain[1] == '\0')
+	    domain = "";	/* "/" is the default, so no need to send it */
+	else
+	    domain = ap_pstrcat(r->pool, ", domain=\"", domain, "\"", NULL);
     }
 
+    ap_table_mergen(r->err_headers_out,
+		    r->proxyreq ? "Proxy-Authenticate" : "WWW-Authenticate",
+		    ap_psprintf(r->pool, "Digest realm=\"%s\", nonce=\"%s\", "
+					 "algorithm=%s%s%s%s%s",
+				ap_auth_name(r),
+				gen_nonce(r->pool, r->request_time,
+					  conf->realm, opaque),
+				conf->algorithm,
+				opaque_param ? opaque_param : "",
+				domain ? domain : "",
+				stale ? ", stale=true" : "",
+				qop));
+}
+
+
+/*
+ * Authorization header parser code
+ */
+
+/* Parse the Authorization header, if it exists */
+static int get_digest_rec(request_rec *r, digest_header_rec *resp)
+{
+    const char *auth_line = ap_table_get(r->headers_in,
+					 r->proxyreq ? "Proxy-Authorization"
+						     : "Authorization");
+    size_t l;
+    int vk = 0, vv = 0;
+    char *key, *value;
+
+
     if (!auth_line) {
-	ap_note_digest_auth_failure(r);
-	return AUTH_REQUIRED;
+	resp->auth_hdr_sts = NO_HEADER;
+	return !OK;
     }
 
-    if (strcasecmp(scheme = ap_getword_white(r->pool, &auth_line), "Digest")) {
-	/* Client tried to authenticate using wrong auth scheme */
-	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r->server,
-		    "client used wrong authentication scheme: %s for %s", 
-		    scheme, r->uri);
-	ap_note_digest_auth_failure(r);
-	return AUTH_REQUIRED;
+    resp->scheme = ap_getword(r->pool, &auth_line, ' ');
+    if (strcasecmp(resp->scheme, "Digest")) {
+	resp->auth_hdr_sts = NOT_DIGEST;
+	return !OK;
     }
 
     l = strlen(auth_line);
@@ -174,100 +763,297 @@
      * there has to be at least one '=' character for either of these two
      * new strings to be terminated.  That takes care of the need for +1.
      */
-    key = ap_palloc(r->pool, l);
+    key   = ap_palloc(r->pool, l);
     value = ap_palloc(r->pool, l);
 
-    /* There's probably a better way to do this, but for the time being... */
+    while (auth_line[0] != '\0') {
 
-#define D_KEY 0
-#define D_VALUE 1
-#define D_STRING 2
-#define D_EXIT -1
-
-    s = D_KEY;
-    while (s != D_EXIT) {
-	switch (s) {
-	case D_STRING:
-	    if (auth_line[0] == '\"') {
-		s = D_VALUE;
-	    }
-	    else {
-		value[vv] = auth_line[0];
-		vv++;
-	    }
-	    auth_line++;
-	    break;
+	/* find key */
 
-	case D_VALUE:
-	    if (ap_isalnum(auth_line[0])) {
-		value[vv] = auth_line[0];
-		vv++;
-	    }
-	    else if (auth_line[0] == '\"') {
-		s = D_STRING;
-	    }
-	    else {
-		value[vv] = '\0';
+	while (ap_isspace(auth_line[0])) auth_line++;
+	vk = 0;
+	while (auth_line[0] != '='  &&  auth_line[0] != ','  &&
+	       auth_line[0] != '\0'  &&  !ap_isspace(auth_line[0]))
+	    key[vk++] = *auth_line++;
+	key[vk] = '\0';
+	while (ap_isspace(auth_line[0])) auth_line++;
 
-		if (!strcasecmp(key, "username"))
-		    response->username = ap_pstrdup(r->pool, value);
-		else if (!strcasecmp(key, "realm"))
-		    response->realm = ap_pstrdup(r->pool, value);
-		else if (!strcasecmp(key, "nonce"))
-		    response->nonce = ap_pstrdup(r->pool, value);
-		else if (!strcasecmp(key, "uri"))
-		    response->requested_uri = ap_pstrdup(r->pool, value);
-		else if (!strcasecmp(key, "response"))
-		    response->digest = ap_pstrdup(r->pool, value);
+	/* find value */
 
-		vv = 0;
-		s = D_KEY;
-	    }
+	if (auth_line[0] == '=') {
 	    auth_line++;
-	    break;
+	    while (ap_isspace(auth_line[0])) auth_line++;
 
-	case D_KEY:
-	    if (ap_isalnum(auth_line[0])) {
-		key[vk] = auth_line[0];
-		vk++;
+	    vv = 0;
+	    if (auth_line[0] == '\"') {		/* quoted string */
+		auth_line++;
+		while (auth_line[0] != '\"'  &&  auth_line[0] != '\0') {
+		    if (auth_line[0] == '\\'  &&  auth_line[1] != '\0')
+			auth_line++;		/* escaped char */
+		    value[vv++] = *auth_line++;
+		}
+		if (auth_line[0] != '\0') auth_line++;
 	    }
-	    else if (auth_line[0] == '=') {
-		key[vk] = '\0';
-		vk = 0;
-		s = D_VALUE;
+	    else {				 /* token */
+		while (auth_line[0] != ','  &&  auth_line[0] != '\0'  &&
+		       !ap_isspace(auth_line[0]))
+		    value[vv++] = *auth_line++;
 	    }
-	    auth_line++;
-	    break;
+	    value[vv] = '\0';
+	}
+
+	while (auth_line[0] != ','  &&  auth_line[0] != '\0')  auth_line++;
+	if (auth_line[0] != '\0') auth_line++;
+
+	if (!strcasecmp(key, "username"))
+	    resp->username = ap_pstrdup(r->pool, value);
+	else if (!strcasecmp(key, "realm"))
+	    resp->realm = ap_pstrdup(r->pool, value);
+	else if (!strcasecmp(key, "nonce"))
+	    resp->nonce = ap_pstrdup(r->pool, value);
+	else if (!strcasecmp(key, "uri"))
+	    resp->uri = ap_pstrdup(r->pool, value);
+	else if (!strcasecmp(key, "response"))
+	    resp->digest = ap_pstrdup(r->pool, value);
+	else if (!strcasecmp(key, "algorithm"))
+	    resp->algorithm = ap_pstrdup(r->pool, value);
+	else if (!strcasecmp(key, "cnonce"))
+	    resp->cnonce = ap_pstrdup(r->pool, value);
+	else if (!strcasecmp(key, "opaque"))
+	    resp->opaque = ap_pstrdup(r->pool, value);
+	else if (!strcasecmp(key, "qop"))
+	    resp->message_qop = ap_pstrdup(r->pool, value);
+	else if (!strcasecmp(key, "nc"))
+	    resp->nonce_count = ap_pstrdup(r->pool, value);
+    }
+
+    if (!resp->username || !resp->realm || !resp->nonce || !resp->uri ||
+	!resp->digest) {
+	resp->auth_hdr_sts = INVALID;
+	return !OK;
+    }
+
+    resp->auth_hdr_sts = VALID;
+    return OK;
+}
+
+
+/*
+ * Nonce-count handling code
+ */
+
+static int inc_nc(const server_rec *server, const char *realm,
+		  const char *opaque)
+{
+    /* TBD */
+    return 0;
+}
+
+
+/* Because the browser may preemptively send auth info, incrementing the
+ * nonce-count when it does, and because the client does not get notified
+ * if the URI didn't need authentication after all, we need to be sure to
+ * update the nonce-count each time we receive an Authorization header no
+ * matter what the final outcome of the request.
+ *
+ * Note that this must be called after mod_proxy had its go so that
+ * r->proxyreq is set correctly.
+ */
+static int update_nonce_count(request_rec *r)
+{
+    digest_header_rec *resp;
+    int res;
+
+    if (!ap_is_initial_req(r))
+	return DECLINED;
+
+    resp = ap_pcalloc(r->pool, sizeof(digest_header_rec));
+    res = get_digest_rec(r, resp);
+    ap_set_module_config(r->request_config, &digest_module, resp);
+
+    if (res == OK)
+	inc_nc(r->server, resp->realm, resp->opaque);
+
+    return DECLINED;
+}
+
+
+/*
+ * Authorization header verification code
+ */
+
+static const char *get_hash(request_rec *r, const char *user,
+			    const char *realm, const char *auth_pwfile)
+{
+    configfile_t *f;
+    char l[MAX_STRING_LEN];
+    const char *rpw;
+    char *w, *x;
+
+    if (!(f = ap_pcfg_openfile(r->pool, auth_pwfile))) {
+	ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
+		      "Could not open password file: %s", auth_pwfile);
+	return NULL;
+    }
+    while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {
+	if ((l[0] == '#') || (!l[0]))
+	    continue;
+	rpw = l;
+	w = ap_getword(r->pool, &rpw, ':');
+	x = ap_getword(r->pool, &rpw, ':');
+
+	if (x && w && !strcmp(user, w) && !strcmp(realm, x)) {
+	    ap_cfg_closefile(f);
+	    return ap_pstrdup(r->pool, rpw);
+	}
+    }
+    ap_cfg_closefile(f);
+    return NULL;
+}
+
+static int check_nc(const request_rec *r, const digest_header_rec *resp,
+		    const digest_config_rec *conf)
+{
+    int nc, my_nc = inc_nc(r->server, resp->realm, resp->opaque);
+
+    if (conf->check_nc) {
+	const char *snc = resp->nonce_count;
+	char *endptr;
+
+	nc = strtol(snc, &endptr, 10);
+	if (endptr < (snc+strlen(snc))  &&  !ap_isspace(*endptr)) {
+	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+			  "invalid nc %s received - not a number", snc);
+	    return !OK;
 	}
 
-	if (auth_line[-1] == '\0')
-	    s = D_EXIT;
+	if (nc != my_nc)
+	    return !OK;
     }
 
-    if (!response->username || !response->realm || !response->nonce ||
-	!response->requested_uri || !response->digest) {
-	ap_note_digest_auth_failure(r);
+    return OK;
+}
+
+static int check_nonce(request_rec *r, digest_header_rec *resp,
+		       const digest_config_rec *conf)
+{
+    double dt;
+    time_rec nonce_time;
+    unsigned char *t;
+    char tmp, hash[NONCE_HASH_LEN+1];
+
+    if (strlen(resp->nonce) != NONCE_LEN) {
+	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+		      "invalid nonce %s received - length is not %d",
+		      resp->nonce, NONCE_LEN);
+	note_digest_auth_failure(r, conf, resp, 0);
 	return AUTH_REQUIRED;
     }
 
-    r->connection->user = response->username;
-    r->connection->ap_auth_type = "Digest";
+    tmp = resp->nonce[NONCE_TIME_LEN];
+    resp->nonce[NONCE_TIME_LEN] = '\0';
+    t = nonce_time.arr;
+    base64decode(r->pool, resp->nonce, &t);
+    gen_nonce_hash(hash, resp->nonce, conf->realm, resp->opaque);
+    resp->nonce[NONCE_TIME_LEN] = tmp;
+    resp->nonce_time = nonce_time.time;
+
+    if (strcmp(hash, resp->nonce+NONCE_TIME_LEN)) {
+	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+		      "invalid nonce %s received - hash is not %s", resp->nonce,
+		      hash);
+	note_digest_auth_failure(r, conf, resp, 0);
+	return AUTH_REQUIRED;
+    }
+
+    dt = difftime(r->request_time, nonce_time.time);
+    if (dt < 0) {
+	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+		      "invalid nonce %s received - user attempted time travel",
+		      resp->nonce);
+	note_digest_auth_failure(r, conf, resp, 0);
+	return AUTH_REQUIRED;
+    }
+
+    if (conf->nonce_lifetime > 0) {
+	if (dt > conf->nonce_lifetime) {
+	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, r,
+			  "user %s: nonce expired - sending new nonce",
+			  r->connection->user);
+	    note_digest_auth_failure(r, conf, resp, 1);
+	    return AUTH_REQUIRED;
+	}
+    }
+    else if (conf->nonce_lifetime == 0) {
+	/* TBD */
+    }
+    /* else (lifetime < 0) => never expires */
 
     return OK;
 }
 
 /* The actual MD5 code... whee */
 
-static char *find_digest(request_rec *r, digest_header_rec * h, char *a1)
+static const char *old_digest(const request_rec *r,
+			      const digest_header_rec *resp, const char *ha1)
 {
+    const char *ha2;
+
+    /* rfc-2069 */
+    ha2 = ap_md5(r->pool, (unsigned char *)ap_pstrcat(r->pool, r->method, ":",
+						      resp->uri, NULL));
     return ap_md5(r->pool,
-		  (unsigned char *)ap_pstrcat(r->pool, a1, ":", h->nonce, ":",
-					   ap_md5(r->pool,
-		           (unsigned char *)ap_pstrcat(r->pool, r->method, ":",
-						    h->requested_uri, NULL)),
-					   NULL));
+		  (unsigned char *)ap_pstrcat(r->pool, ha1, ":", resp->nonce,
+					      ":", ha2, NULL));
 }
 
+static const char *new_digest(const request_rec *r,
+			      const digest_header_rec *resp,
+			      const digest_config_rec *conf)
+{
+    const char *ha1, *ha2, *a2;
+
+    /* draft-ietf-http-authentication-03 */
+    if (resp->algorithm  &&  !strcasecmp(resp->algorithm, "MD5-sess"))
+	ha1 = get_session(r, resp, conf);
+    else
+	ha1 = conf->ha1;
+
+    if (resp->message_qop  &&  !strcasecmp(resp->message_qop, "auth-int"))
+	a2 = ap_pstrcat(r->pool, r->method, ":", resp->uri, ":",
+			ap_md5(r->pool, (unsigned char*) ""), NULL); /* TBD */
+    else
+	a2 = ap_pstrcat(r->pool, r->method, ":", resp->uri, NULL);
+    ha2 = ap_md5(r->pool, (unsigned char *)a2);
+
+    return ap_md5(r->pool,
+		  (unsigned char *)ap_pstrcat(r->pool, ha1, ":", resp->nonce,
+					      ":", resp->nonce_count, ":",
+					      resp->cnonce, ":",
+					      resp->message_qop, ":", ha2,
+					      NULL));
+}
+
+
+static const char *unescape(pool *p, const char *str)
+{
+    char *bs = strchr(str, '\\'), *new, *pos;
+    if (!bs  ||  bs[1] == '\0')  return str;
+
+    new = ap_palloc(p, strlen(str));
+    memcpy(new, str, (bs-str));
+    pos = new + (bs-str);
+    bs++;
+    while (*bs) {
+	if (*bs == '\\') bs++;
+	*pos++ = *bs++;
+    }
+
+    *pos = '\0';
+    return new;
+}
+
+
 /* These functions return 0 if client is OK, and proper error status
  * if not... either AUTH_REQUIRED, if we made a check, and it failed, or
  * SERVER_ERROR, if things are so totally confused that we couldn't
@@ -278,46 +1064,179 @@
  * reported as such.
  */
 
-/* Determine user ID, and check if it really is that user, for HTTP
- * basic authentication...
+/* Determine user ID, and check if the attributes are correct, if it
+ * really is that user, if the nonce is correct, etc.
  */
 
 static int authenticate_digest_user(request_rec *r)
 {
-    digest_config_rec *sec =
-    (digest_config_rec *) ap_get_module_config(r->per_dir_config,
-					    &digest_module);
-    digest_header_rec *response = ap_pcalloc(r->pool, sizeof(digest_header_rec));
-    conn_rec *c = r->connection;
-    char *a1;
-    int res;
+    digest_config_rec *conf;
+    digest_header_rec *resp;
+    request_rec       *main;
+    conn_rec          *conn = r->connection;
+    const char        *t;
+    int                res;
 
-    if ((res = get_digest_rec(r, response)))
-	return res;
 
-    if (!sec->pwfile)
+    /* do we require Digest auth for this URI? */
+
+    if (!(t = ap_auth_type(r)) || strcasecmp(t, "Digest"))
 	return DECLINED;
 
-    if (!(a1 = get_hash(r, c->user, sec->pwfile))) {
+    if (!ap_auth_name(r)) {
+	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+		      "need AuthName: %s", r->uri);
+	return SERVER_ERROR;
+    }
+
+
+    /* get the client response and real uri */
+
+    main = r;
+    while (main->main != NULL)  main = main->main;
+    while (main->prev != NULL)  main = main->prev;
+    resp = (digest_header_rec *) ap_get_module_config(main->request_config,
+						      &digest_module);
+    resp->request_uri = &main->parsed_uri;
+
+
+    /* set up our conf */
+
+    conf = (digest_config_rec *) ap_get_module_config(r->per_dir_config,
+						      &digest_module);
+    if (!conf->realm)
+	conf->realm = unescape(conf->pool, ap_auth_name(r));
+
+
+    /* check for existence and syntax of Auth header */
+
+    if (resp->auth_hdr_sts != VALID) {
+	if (resp->auth_hdr_sts == NOT_DIGEST)
+	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+			  "client used wrong authentication scheme `%s': %s",
+			  resp->scheme, r->uri);
+	else if (resp->auth_hdr_sts == INVALID)
+	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+			  "missing user, realm, nonce, uri, or digest in "
+			  "authorization header: %s", r->uri);
+	/* else (resp->auth_hdr_sts == NO_HEADER) */
+	note_digest_auth_failure(r, conf, resp, 0);
+	return AUTH_REQUIRED;
+    }
+
+    r->connection->user = (char *) resp->username;
+    r->connection->ap_auth_type = "Digest";
+
+
+    /* check the auth attributes */
+
+    if (strcmp(resp->uri, resp->request_uri->path)) {
+	uri_components *r_uri = resp->request_uri, d_uri;
+	ap_parse_uri_components(r->pool, resp->uri, &d_uri);
+
+	if ((d_uri.hostname  &&  d_uri.hostname[0] != '\0'  &&
+	     strcasecmp(d_uri.hostname, r->server->server_hostname))  ||
+	    (d_uri.port_str  &&  d_uri.port != r->server->port)  ||
+	    (!d_uri.port_str  &&  r->server->port != 80)  ||
+	    strcmp(d_uri.path, r_uri->path)  ||
+	    (d_uri.query != r_uri->query &&
+	     (!d_uri.query || !r_uri->query ||
+	      strcmp(d_uri.query, r_uri->query)))
+	    ) {
+	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+			  "uri mismatch - <%s> does not match request-uri <%s>",
+			  resp->uri,
+			  ap_unparse_uri_components(r->pool, r_uri, 0));
+	    return BAD_REQUEST;
+	}
+    }
+
+    if (strcmp(resp->realm, conf->realm)) {
+	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+		      "realm mismatch - got `%s' but expected `%s'",
+		      resp->realm, conf->realm);
+	note_digest_auth_failure(r, conf, resp, 0);
+	return AUTH_REQUIRED;
+    }
+
+    if (resp->algorithm != NULL  &&
+	strcasecmp(resp->algorithm, "MD5")  &&
+	strcasecmp(resp->algorithm, "MD5-sess")) {
 	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
-		    "user %s not found: %s", c->user, r->uri);
-	ap_note_digest_auth_failure(r);
+		      "unknown algorithm `%s' received: %s",
+		      resp->algorithm, r->uri);
+	note_digest_auth_failure(r, conf, resp, 0);
 	return AUTH_REQUIRED;
     }
-    if (strcmp(response->digest, find_digest(r, response, a1))) {
+
+    if (!conf->pwfile)
+	return DECLINED;
+
+    if (!(conf->ha1 = get_hash(r, conn->user, conf->realm, conf->pwfile))) {
 	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
-		    "user %s: password mismatch: %s", c->user, r->uri);
-	ap_note_digest_auth_failure(r);
+		      "user `%s' in realm `%s' not found: %s", conn->user,
+		      conf->realm, r->uri);
+	note_digest_auth_failure(r, conf, resp, 0);
 	return AUTH_REQUIRED;
     }
+
+    if (resp->message_qop == NULL) {
+	/* old (rfc-2069) style digest */
+	if (strcmp(resp->digest, old_digest(r, resp, conf->ha1))) {
+	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+			  "user %s: password mismatch: %s", conn->user, r->uri);
+	    note_digest_auth_failure(r, conf, resp, 0);
+	    return AUTH_REQUIRED;
+	}
+    }
+    else {
+	int match = 0, idx;
+	for (idx=0; conf->qop_list[idx] != NULL; idx++) {
+	    if (!strcasecmp(conf->qop_list[idx], resp->message_qop)) {
+		match = 1;
+		break;
+	    }
+	}
+
+	if (!match  &&
+	    !(conf->qop_list[0] == NULL  &&
+	      !strcasecmp(resp->message_qop, "auth"))) {
+	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+			  "invalid qop `%s' received: %s",
+			  resp->message_qop, r->uri);
+	    note_digest_auth_failure(r, conf, resp, 0);
+	    return AUTH_REQUIRED;
+	}
+
+	if (strcmp(resp->digest, new_digest(r, resp, conf))) {
+	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+			  "user %s: password mismatch: %s", conn->user, r->uri);
+	    note_digest_auth_failure(r, conf, resp, 0);
+	    return AUTH_REQUIRED;
+	}
+    }
+
+    if (check_nc(r, resp, conf) != OK) {
+	note_digest_auth_failure(r, conf, resp, 0);
+	return AUTH_REQUIRED;
+    }
+
+    /* Note: this check is done last so that a "stale=true" can be
+       generated if the nonce is old */
+    if ((res = check_nonce(r, resp, conf)))
+	return res;
+
     return OK;
 }
 
-/* Checking ID */
+
+/*
+ * Checking ID
+ */
 
 static int digest_check_auth(request_rec *r)
 {
-    char *user = r->connection->user;
+    const char *user = r->connection->user;
     int m = r->method_number;
     int method_restricted = 0;
     register int x;
@@ -345,10 +1264,10 @@
 	method_restricted = 1;
 
 	t = reqs[x].requirement;
-	w = ap_getword_white(r->pool, &t);
-	if (!strcmp(w, "valid-user"))
+	w = ap_getword(r->pool, &t, ' ');
+	if (!strcasecmp(w, "valid-user"))
 	    return OK;
-	else if (!strcmp(w, "user")) {
+	else if (!strcasecmp(w, "user")) {
 	    while (t[0]) {
 		w = ap_getword_conf(r->pool, &t);
 		if (!strcmp(user, w))
@@ -362,14 +1281,170 @@
     if (!method_restricted)
 	return OK;
 
-    ap_note_digest_auth_failure(r);
+    note_digest_auth_failure(r, 
+	(digest_config_rec *) ap_get_module_config(r->per_dir_config,
+						   &digest_module),
+	(digest_header_rec *) ap_get_module_config(r->request_config,
+						   &digest_module),
+	0);
     return AUTH_REQUIRED;
 }
 
+
+/*
+ * Authorization-Info header code
+ */
+
+#ifdef SEND_DIGEST
+static const char *hdr(const table *tbl, const char *name)
+{
+    const char *val = ap_table_get(tbl, name);
+    if (val)
+	return val;
+    else
+	return "";
+}
+#endif
+
+static int add_auth_info(request_rec *r)
+{
+    const digest_config_rec *conf =
+		(digest_config_rec *) ap_get_module_config(r->per_dir_config,
+							   &digest_module);
+    const digest_header_rec *resp =
+		(digest_header_rec *) ap_get_module_config(r->request_config,
+							   &digest_module);
+    char *ai = NULL, *digest = NULL, *nextnonce = "";
+
+    if (resp == NULL  ||  conf == NULL)
+	return OK;
+
+
+    /* rfc-2069 digest
+     */
+    if (resp->message_qop == NULL) {
+	/* old client, so calc rfc-2069 digest */
+
+#ifdef SEND_DIGEST
+	/* most of this totally bogus because the handlers don't set the
+	 * headers until the final handler phase (I wonder why this phase
+	 * is called fixup when there's almost nothing you can fix up...)
+	 *
+	 * Because it's basically impossible to get this right (e.g. the
+	 * Content-length is never set yet when we get here, and we can't
+	 * calc the entity hash) it's best to just leave this #def'd out.
+	 */
+	char *entity_info =
+	    ap_md5(r->pool,
+		   (unsigned char *) ap_pstrcat(r->pool,
+		       ap_unparse_uri_components(r->pool,
+						 resp->request_uri, 0), ":",
+		       r->content_type ? r->content_type : ap_default_type(r), ":",
+		       hdr(r->headers_out, "Content-Length"), ":",
+		       r->content_encoding ? r->content_encoding : "", ":",
+		       hdr(r->headers_out, "Last-Modified"), ":",
+		       r->no_cache && !ap_table_get(r->headers_out, "Expires") ?
+			    ap_gm_timestr_822(r->pool, r->request_time) :
+			    hdr(r->headers_out, "Expires"),
+		       NULL));
+	digest =
+	    ap_md5(r->pool,
+		   (unsigned char *)ap_pstrcat(r->pool, conf->ha1, ":",
+					       resp->nonce, ":",
+					       r->method, ":",
+					       ap_gm_timestr_822(r->pool, r->request_time), ":",
+					       entity_info, ":",
+					       ap_md5(r->pool, (unsigned char *) ""), /* H(entity) - TBD */
+					       NULL));
+#endif
+    }
+
+
+    /* setup nextnonce
+     */
+    if (conf->nonce_lifetime > 0) {
+	/* send nextnonce if current nonce will expire in less than 30 secs */
+	if (difftime(r->request_time, resp->nonce_time) > (conf->nonce_lifetime-NEXTNONCE_DELTA)) {
+	    nextnonce = ap_pstrcat(r->pool, ", nextnonce=\"",
+				   gen_nonce(r->pool, r->request_time,
+					     conf->realm, resp->opaque),
+				   "\"", NULL);
+	}
+    }
+    else if (conf->nonce_lifetime == 0) {
+	/* TBD */
+    }
+    /* else nonce never expires, hence no nextnonce */
+
+
+    /* do rfc-2069 digest
+     */
+    if (conf->qop_list[0]  &&  !strcasecmp(conf->qop_list[0], "none")  &&
+	resp->message_qop == NULL) {
+	/* use only RFC-2069 format */
+	if (digest)
+	    ai = ap_pstrcat(r->pool, "digest=\"", digest, "\"", nextnonce,NULL);
+	else
+	    ai = nextnonce;
+    }
+    else {
+	const char *resp_dig, *ha1, *a2, *ha2;
+
+	/* calculate rspauth attribute
+	 */
+	if (resp->algorithm  &&  !strcasecmp(resp->algorithm, "MD5-sess"))
+	    ha1 = get_session(r, resp, conf);
+	else
+	    ha1 = conf->ha1;
+
+	if (resp->message_qop  &&  !strcasecmp(resp->message_qop, "auth-int"))
+	    a2 = ap_pstrcat(r->pool, ":", resp->uri, ":",
+			    ap_md5(r->pool, (unsigned char *) ""), NULL); /* TBD */
+	else
+	    a2 = ap_pstrcat(r->pool, ":", resp->uri, NULL);
+	ha2 = ap_md5(r->pool, (unsigned char *)a2);
+
+	resp_dig = ap_md5(r->pool,
+		         (unsigned char *)ap_pstrcat(r->pool, ha1, ":",
+						     resp->nonce, ":",
+						     resp->nonce_count, ":",
+						     resp->cnonce, ":",
+						     resp->message_qop ?
+							 resp->message_qop : "",
+						     ":", ha2, NULL));
+
+	/* assemble Authentication-Info header
+	 */
+	ai = ap_pstrcat(r->pool,
+			"rspauth=\"", resp_dig, "\"",
+			nextnonce,
+		        resp->cnonce ? ", cnonce=\"" : "",
+		        resp->cnonce ? ap_escape_quotes(r->pool, resp->cnonce) :
+					"",
+		        resp->cnonce ? "\"" : "",
+		        resp->nonce_count ? ", nc=" : "",
+		        resp->nonce_count ? resp->nonce_count : "",
+		        resp->message_qop ? ", qop=" : "",
+		        resp->message_qop ? resp->message_qop : "",
+			digest ? "digest=\"" : "",
+			digest ? digest : "",
+			digest ? "\"" : "",
+			NULL);
+    }
+
+    if (ai  &&  ai[0])
+	ap_table_mergen(r->headers_out,
+			r->proxyreq ? "Proxy-Authentication-Info" :
+				      "Authentication-Info",
+			ai);
+    return OK;
+}
+
+
 module MODULE_VAR_EXPORT digest_module =
 {
     STANDARD_MODULE_STUFF,
-    NULL,			/* initializer */
+    initialize_secret,		/* initializer */
     create_digest_dir_config,	/* dir config creater */
     NULL,			/* dir merger --- default is to override */
     NULL,			/* server config */
@@ -381,10 +1456,11 @@
     digest_check_auth,		/* check auth */
     NULL,			/* check access */
     NULL,			/* type_checker */
-    NULL,			/* fixups */
+    add_auth_info,		/* fixups */
     NULL,			/* logger */
     NULL,			/* header parser */
     NULL,			/* child_init */
     NULL,			/* child_exit */
-    NULL			/* post read-request */
+    update_nonce_count		/* post read-request */
 };
+
----- mod_digest.html (diff) ------------------------------------------
--- mod_digest.html.orig	Wed May 20 07:12:55 1998
+++ mod_digest.html	Sat Nov 14 13:22:32 1998
@@ -23,6 +23,12 @@
 
 <MENU>
 <LI><A HREF="#authdigestfile">AuthDigestFile</A>
+<LI><A HREF="#authdigestqop">AuthDigestQop</A>
+<LI><A HREF="#authdigestnoncelifetime">AuthDigestNonceLifetime</A>
+<LI><A HREF="#authdigestnonceformat">AuthDigestNonceFormat</A>
+<LI><A HREF="#authdigestnccheck">AuthDigestNcCheck</A>
+<LI><A HREF="#authdigestalgorithm">AuthDigestAlgorithm</A>
+<LI><A HREF="#authdigestdomain">AuthDigestDomain</A>
 </MENU>
 <HR>
 
@@ -47,32 +53,286 @@
 <A
  HREF="directive-dict.html#Module"
  REL="Help"
-><STRONG>Module:</STRONG></A> mod_digest<P>
+><STRONG>Module:</STRONG></A> mod_digest<BR>
 
 <P>The AuthDigestFile directive sets the name of a textual file containing
-the list
-of users and encoded passwords for digest authentication.
-<EM>Filename</EM>
-is the absolute path to the user file.</P>
+the list of users and encoded passwords for digest authentication.
+<EM>Filename</EM> is the absolute path to the user file.
+
 <P>The digest file uses a special format. Files in this format can be
 created using the "htdigest" utility found in the support/ subdirectory of
-the Apache distribution.</P>
+the Apache distribution.
+
+<HR>
+
+<H2><A NAME="authdigestqop">AuthDigestQop</A></H2>
+<A
+ HREF="directive-dict.html#Syntax"
+ REL="Help"
+><STRONG>Syntax:</STRONG></A> AuthDigestQop <EM>none | 1*{ auth | auth-int }</EM><BR>
+<A
+ HREF="directive-dict.html#Default"
+ REL="Help"
+><STRONG>Default:</STRONG></A> <CODE>AuthDigestQop auth</CODE><BR>
+<A
+ HREF="directive-dict.html#Context"
+ REL="Help"
+><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
+<A
+ HREF="directive-dict.html#Override"
+ REL="Help"
+><STRONG>Override:</STRONG></A> AuthConfig<BR>
+<A
+ HREF="directive-dict.html#Status"
+ REL="Help"
+><STRONG>Status:</STRONG></A> Base<BR>
+<A
+ HREF="directive-dict.html#Module"
+ REL="Help"
+><STRONG>Module:</STRONG></A> mod_digest<BR>
+<A
+ HREF="directive-dict.html#Compatibility"
+ REL="Help"
+><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later
+
+<P>The AuthDigestQop directive determines the quality-of-protection to use.
+<EM>auth</EM> will only do authentication (username/password);
+<EM>auth-int</EM> is authentication plus integrity checking (an MD5 hash
+of the entity is also made and checked); <EM>none</EM> will cause the
+module to use the old RFC-2069 digest algorithm (which does not include
+integrity checking). Both <EM>auth</em> and <EM>auth-int</EM> may be
+specified, in which the case the browser will choose which of these to
+use. <EM>none</EM> should only be used if the browser for some reason
+does not like the challenge is receives otherwise.
+
+<P><STRONG><EM>auth-int</EM> is not implemented yet</STRONG>.
+
+<HR>
+
+<H2><A NAME="authdigestnoncelifetime">AuthDigestNonceLifetime</A></H2>
+<A
+ HREF="directive-dict.html#Syntax"
+ REL="Help"
+><STRONG>Syntax:</STRONG></A> AuthDigestNonceLifetime <EM>&lt;time&gt;</EM><BR>
+<A
+ HREF="directive-dict.html#Default"
+ REL="Help"
+><STRONG>Default:</STRONG></A> <CODE>AuthDigestNonceLifetime 300</CODE><BR>
+<A
+ HREF="directive-dict.html#Context"
+ REL="Help"
+><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
+<A
+ HREF="directive-dict.html#Override"
+ REL="Help"
+><STRONG>Override:</STRONG></A> AuthConfig<BR>
+<A
+ HREF="directive-dict.html#Status"
+ REL="Help"
+><STRONG>Status:</STRONG></A> Base<BR>
+<A
+ HREF="directive-dict.html#Module"
+ REL="Help"
+><STRONG>Module:</STRONG></A> mod_digest<BR>
+<A
+ HREF="directive-dict.html#Compatibility"
+ REL="Help"
+><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later
+
+<P>The AuthDigestNonceLifetime directive controls how long the server
+nonce is valid. When the client contacts the server using an expired
+nonce the server will send back a 407 with <code>stale=true</code>. If
+<EM>&lt;time&gt;</EM> is greater than 0 then it specifies the number of
+seconds the nonce is valid; this should probably never be set to less
+than 10 seconds. If <EM>&lt;time&gt;</EM> is less than 0 then the nonce
+never expires.
+
+<!-- Not implemented yet
+If <EM>&lt;time&gt;</EM> is 0 then the nonce may be used exactly once by
+the client.
+-->
+
+<HR>
+<H2><A NAME="authdigestnonceformat">AuthDigestNonceFormat</A></H2>
+<A
+ HREF="directive-dict.html#Syntax"
+ REL="Help"
+><STRONG>Syntax:</STRONG></A> AuthDigestNonceFormat <EM>???</EM><BR>
+<A
+ HREF="directive-dict.html#Default"
+ REL="Help"
+><STRONG>Default:</STRONG></A> <CODE>AuthDigestNonceFormat ???</CODE><BR>
+<A
+ HREF="directive-dict.html#Context"
+ REL="Help"
+><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
+<A
+ HREF="directive-dict.html#Override"
+ REL="Help"
+><STRONG>Override:</STRONG></A> AuthConfig<BR>
+<A
+ HREF="directive-dict.html#Status"
+ REL="Help"
+><STRONG>Status:</STRONG></A> Base<BR>
+<A
+ HREF="directive-dict.html#Module"
+ REL="Help"
+><STRONG>Module:</STRONG></A> mod_digest<BR>
+<A
+ HREF="directive-dict.html#Compatibility"
+ REL="Help"
+><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later
+
+<P><STRONG>Not implemented yet.</STRONG>
+<!--
+<P>The AuthDigestNonceFormat directive determines how the nonce is
+generated.
+-->
+
+<HR>
+<H2><A NAME="authdigestnccheck">AuthDigestNcCheck</A></H2>
+<A
+ HREF="directive-dict.html#Syntax"
+ REL="Help"
+><STRONG>Syntax:</STRONG></A> AuthDigestNcCheck <EM>On/Off</EM><BR>
+<A
+ HREF="directive-dict.html#Default"
+ REL="Help"
+><STRONG>Default:</STRONG></A> <CODE>AuthDigestNcCheck Off</CODE><BR>
+<A
+ HREF="directive-dict.html#Context"
+ REL="Help"
+><STRONG>Context:</STRONG></A> server config<BR>
+<A
+ HREF="directive-dict.html#Override"
+ REL="Help"
+><STRONG>Override:</STRONG></A> <EM>Not applicable</EM><BR>
+<A
+ HREF="directive-dict.html#Status"
+ REL="Help"
+><STRONG>Status:</STRONG></A> Base<BR>
+<A
+ HREF="directive-dict.html#Module"
+ REL="Help"
+><STRONG>Module:</STRONG></A> mod_digest<BR>
+<A
+ HREF="directive-dict.html#Compatibility"
+ REL="Help"
+><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later
+
+<P><STRONG>Not implemented yet.</STRONG>
+<!--
+<P>The AuthDigestNcCheck directive enables or disables the checking of the
+nonce-count sent by the server.
+
+<P>While recommended from a security standpoint, turning this directive
+On has one important performance implication. To check the nonce-count
+*all* requests (which have an Authorization header, irrespective of
+whether they require digest authentication) must be serialized through
+a critical section. If the server is handling a large number of
+requests which contain the Authorization header then this may noticeably
+impact performance.
+
+-->
+
+<HR>
+<H2><A NAME="authdigestalgorithm">AuthDigestAlgorithm</A></H2>
+<A
+ HREF="directive-dict.html#Syntax"
+ REL="Help"
+><STRONG>Syntax:</STRONG></A> AuthDigestAlgorithm <EM>MD5 | MD5-sess</EM><BR>
+<A
+ HREF="directive-dict.html#Default"
+ REL="Help"
+><STRONG>Default:</STRONG></A> <CODE>AuthDigestAlgorithm MD5</CODE><BR>
+<A
+ HREF="directive-dict.html#Context"
+ REL="Help"
+><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
+<A
+ HREF="directive-dict.html#Override"
+ REL="Help"
+><STRONG>Override:</STRONG></A> AuthConfig<BR>
+<A
+ HREF="directive-dict.html#Status"
+ REL="Help"
+><STRONG>Status:</STRONG></A> Base<BR>
+<A
+ HREF="directive-dict.html#Module"
+ REL="Help"
+><STRONG>Module:</STRONG></A> mod_digest<BR>
+<A
+ HREF="directive-dict.html#Compatibility"
+ REL="Help"
+><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later
+
+<P>The AuthDigestAlgorithm directive selects the algorithm used to calculate
+the challenge and response hashes.
+
+<P><STRONG><EM>MD5-sess</EM> is not correctly implemented yet</STRONG>.
+<!--
+<P>To use <EM>MD5-sess</EM> you must first code up the
+<VAR>get_userpw_hash()</VAR> function in <VAR>mod_digest.c</VAR> .
+-->
+
+<HR>
+<H2><A NAME="authdigestdomain">AuthDigestDomain</A></H2>
+<A
+ HREF="directive-dict.html#Syntax"
+ REL="Help"
+><STRONG>Syntax:</STRONG></A> AuthDigestDomain <EM>URI URI ...</EM><BR>
+<A
+ HREF="directive-dict.html#Context"
+ REL="Help"
+><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
+<A
+ HREF="directive-dict.html#Override"
+ REL="Help"
+><STRONG>Override:</STRONG></A> AuthConfig<BR>
+<A
+ HREF="directive-dict.html#Status"
+ REL="Help"
+><STRONG>Status:</STRONG></A> Base<BR>
+<A
+ HREF="directive-dict.html#Module"
+ REL="Help"
+><STRONG>Module:</STRONG></A> mod_digest<BR>
+<A
+ HREF="directive-dict.html#Compatibility"
+ REL="Help"
+><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later
+
+<P>The AuthDigestDomain directive allows to specify which URIs are in the
+same protection space (i.e. use the same realm and username/password
+info). The specified URIs are prefixes, i.e. the client will assume that
+all URIs "below" these are also protected by the same username/password.
+This directive <em>should</em> be specified and contain at least the set
+of root URIs for this space.  Omiting to do so will cause the client to
+send the Authorization header for <em>every request</em> sent to this
+server.  Apart from increasing the size of the request, it may also have
+a detrimental effect on performance if "AuthDigestNcCheck" is on.
+
+<P>The URIs specified can also point to different servers, in which case
+clients (which understand this) will then share username/password info
+across multiple servers without prompting the user each time.
+
 
 <HR>
 
 <H3>Using Digest Authentication</H3>
 
 <P>Using MD5 Digest authentication is very simple. Simply set up
-authentication normally. However, use "AuthType Digest" and
-"AuthDigestFile" instead of the normal "AuthType Basic" and
-"AuthUserFile". Everything else should remain the same.</P>
+authentication normally, using "AuthType Digest" and "AuthDigestFile"
+instead of the normal "AuthType Basic" and "AuthUserFile". Then add a
+"AuthDigestDomain" directive containing at least the root URIs for this
+protection space.
 
 <P>MD5 authentication provides a more secure password system, but only
-works with supporting browsers. As of this writing (July 1996), the
+works with supporting browsers. As of this writing (September 1998), the
 majority of browsers do not support digest authentication. Therefore, we
 do not recommend using this feature on a large Internet site. However, for
 personal and intra-net use, where browser users can be controlled, it is
-ideal.</P>
+ideal.
 
 <!--#include virtual="footer.html" -->
 </BODY>
----- mod_digest.c ----------------------------------------------------
/* ====================================================================
 * Copyright (c) 1995-1998 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */

/*
 * mod_digest: MD5 digest authentication
 * 
 * by Alexei Kosut <ak...@nueva.pvt.k12.ca.us>
 * and Ronald Tschal�r <ro...@innovation.ch>
 * based on mod_auth, by Rob McCool and Robert S. Thau
 *
 * Requires the truerand library, available for instance from
 * http://members.tripod.com/dr0nf/truerand/truerand.tar-gz
 *
 * Open Issues:
 *   - qop=auth-int (when streams and trailer support available)
 *   - one-time nonces (when shared-mem and semaphores available)
 *   - nonce-count checking (when shared-mem and semaphores available)
 *   - session management for algorithm=MD5-sess (when shared-mem and
 *     semaphores available)
 *   - nonce-format configurability
 *   - Proxy-Authorization-Info header is set by this module, but is
 *     currently ignored by mod_proxy (needs patch to mod_proxy)
 *   - generating the secret takes a while (~ 8 seconds)
 */

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_request.h"
#include "http_log.h"
#include "http_protocol.h"
#include "ap_config.h"
#include "ap_ctype.h"
#include "util_uri.h"
#include "util_md5.h"


/* from the truerand library */

typedef struct {
    long totalLength;
    unsigned long h[5];
    unsigned long w[80];
} SHS_CTX;

extern void shsInit(SHS_CTX *ctx);
extern void shsUpdate(SHS_CTX *ctx, const unsigned char *s, unsigned int n);
extern void shsFinal(SHS_CTX *ctx);
extern int  randbyte(void);


/* struct to hold the configuration info */

typedef struct digest_config_struct {
    ap_pool     *pool;
    const char  *dir_name;
    const char  *pwfile;
    const char  *realm;
    const char **qop_list;
    long         nonce_lifetime;
    const char  *nonce_format;
    int          check_nc;
    const char  *algorithm;
    char        *uri_list;
    const char  *ha1;
} digest_config_rec;


/* struct to hold a parsed Authorization header */

enum hdr_sts { NO_HEADER, NOT_DIGEST, INVALID, VALID };

typedef struct digest_header_struct {
    const char           *scheme;
    const char           *realm;
    const char           *username;
          char           *nonce;
    const char           *uri;
    const char           *digest;
    const char           *algorithm;
    const char           *cnonce;
    const char           *opaque;
    const char           *message_qop;
    const char           *nonce_count;
    time_t                nonce_time;
    enum hdr_sts          auth_hdr_sts;
    uri_components       *request_uri;
} digest_header_rec;


/* (mostly) nonce stuff */

typedef union time_union {
    time_t	  time;
    unsigned char arr[sizeof(time_t)];
} time_rec;


#define	DFLT_ALGORITHM	"MD5"

#define	DFLT_NONCE_LIFE	300L
#define NEXTNONCE_DELTA	30


#define NONCE_TIME_LEN	(((sizeof(time_t)+2)/3)*4)
#define NONCE_HASH_LEN	40
#define NONCE_LEN	(NONCE_TIME_LEN + NONCE_HASH_LEN)

#define	SECRET_LEN	20


static unsigned char secret[SECRET_LEN];
static int call_cnt = 0;

static void initialize_secret(server_rec *s, pool *p)
{
    int idx;

    /* because it takes so long we only want to do it once. */
    if (++call_cnt != 2)
	return;

    /* this will increase the startup time of the server, unfortunately...
     * (generating 20 bytes takes about 8 seconds)
     */
    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s,
		 "generating secret for digest authentication ...");

    for (idx=0; idx<sizeof(secret); idx++)
	secret[idx] = (unsigned char) randbyte();

    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s, "done");
}


/*
 * configuration code
 */

static void *create_digest_dir_config(pool *p, char *dir)
{
    digest_config_rec *conf;

    if (dir == NULL)  return NULL;

    conf = (digest_config_rec *) ap_pcalloc(p, sizeof(digest_config_rec));
    if (conf) {
	conf->pool           = p;
	conf->qop_list       = ap_palloc(p, sizeof(char*));
	conf->qop_list[0]    = NULL;
	conf->nonce_lifetime = DFLT_NONCE_LIFE;
	conf->dir_name       = ap_pstrdup(p, dir);
	conf->algorithm      = DFLT_ALGORITHM;
    }

    return conf;
}

static const char *set_digest_file(cmd_parms *cmd, void *config,
				   const char *file)
{
    ((digest_config_rec *) config)->pwfile = file;
    return NULL;
}

static const char *set_qop(cmd_parms *cmd, void *config, const char *op)
{
    digest_config_rec *conf = (digest_config_rec *) config;
    const char **tmp;
    int cnt;

    if (!strcasecmp(op, "none")) {
	if (conf->qop_list[0] == NULL) {
	    conf->qop_list = ap_palloc(cmd->pool, 2 * sizeof(char*));
	    conf->qop_list[1] = NULL;
	}
	conf->qop_list[0] = "none";
	return NULL;
    }

    if (!strcasecmp(op, "auth-int"))
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, cmd->server,
		     "WARNING: qop `auth-int' currently only works correctly " \
		     "for responses with no entity");
    else if (strcasecmp(op, "auth"))
	return ap_pstrcat(cmd->pool, "Unrecognized qop: ", op, NULL);

    for (cnt=0; conf->qop_list[cnt] != NULL; cnt++)
	;
    tmp = ap_palloc(cmd->pool, (cnt+2)*sizeof(char*));
    memcpy(tmp, conf->qop_list, cnt*sizeof(char*));
    tmp[cnt]   = ap_pstrdup(cmd->pool, op);
    tmp[cnt+1] = NULL;
    conf->qop_list = tmp;

    return NULL;
}

static const char *set_nonce_lifetime(cmd_parms *cmd, void *config,
				      const char *t)
{
    char *endptr;
    long  time;

    time = strtol(t, &endptr, 10);
    if (endptr < (t+strlen(t))  &&  !ap_isspace(*endptr))
	return ap_pstrcat(cmd->pool, "Invalid time in AuthDigestNonceLifetime: ", t, NULL);

    ((digest_config_rec *) config)->nonce_lifetime = time;
    return NULL;
}

static const char *set_nonce_format(cmd_parms *cmd, void *config,
				    const char *fmt)
{
    ((digest_config_rec *) config)->nonce_format = fmt;
    return "AuthDigestNonceFormat is not implemented (yet)";
}

static const char *set_nc_check(cmd_parms *cmd, void *config, int flag)
{
    ((digest_config_rec *) config)->check_nc = flag;
    return "AuthDigestNcCheck is not implemented (yet)";
}

static const char *set_algorithm(cmd_parms *cmd, void *config, const char *alg)
{
    if (!strcasecmp(alg, "MD5-sess"))
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, cmd->server,
		     "WARNING: algorithm `MD5-sess' is currently not " \
		     "correctly implemented");
    else if (strcasecmp(alg, "MD5"))
	return ap_pstrcat(cmd->pool, "Invalid algorithm in AuthDigestAlgorithm: ", alg, NULL);

    ((digest_config_rec *) config)->algorithm = alg;
    return NULL;
}

static const char *set_uri_list(cmd_parms *cmd, void *config, const char *uri)
{
    digest_config_rec *c = (digest_config_rec *) config;
    if (c->uri_list) {
	c->uri_list[strlen(c->uri_list)-1] = '\0';
	c->uri_list = ap_pstrcat(cmd->pool, c->uri_list, " ", uri, "\"", NULL);
    }
    else
	c->uri_list = ap_pstrcat(cmd->pool, ", domain=\"", uri, "\"", NULL);
    return NULL;
}

static const command_rec digest_cmds[] =
{
    {"AuthDigestFile", set_digest_file, NULL, OR_AUTHCFG, TAKE1,
     "The name of the file containing the usernames and password hashes"},
    {"AuthDigestQop", set_qop, NULL, OR_AUTHCFG, ITERATE,
     "A list of quality-of-protection options"},
    {"AuthDigestNonceLifetime", set_nonce_lifetime, NULL, OR_AUTHCFG, TAKE1,
     "Maximum lifetime of the server nonce (seconds)"},
    {"AuthDigestNonceFormat", set_nonce_format, NULL, OR_AUTHCFG, TAKE1,
     "The format to use when generating the server nonce"},
    {"AuthDigestNcCheck", set_nc_check, NULL, OR_AUTHCFG, FLAG,
     "Whether or not to check the nonce-count sent by the client"},
    {"AuthDigestAlgorithm", set_algorithm, NULL, OR_AUTHCFG, TAKE1,
     "The algorithm used for the hash calculation"},
    {"AuthDigestDomain", set_uri_list, NULL, OR_AUTHCFG, ITERATE,
     "A list of URI's which belong to the same protection space as the current URI"},
    {NULL}
};

module MODULE_VAR_EXPORT digest_module;


/*
 * base-64 encoding helpers
 */

/* this is copied from util.c, with toascii folded into the table for EBCDIC */
static const unsigned char pr2six[256] =
{
#ifndef CHARSET_EBCDIC
    /* ASCII table */
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
    64,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
    64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
#else /*CHARSET_EBCDIC*/
    /* EBCDIC table */
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 64, 64, 64, 64, 64, 64,
    64, 35, 36, 37, 38, 39, 40, 41, 42, 43, 64, 64, 64, 64, 64, 64,
    64, 64, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64,  0,  1,  2,  3,  4,  5,  6,  7,  8, 64, 64, 64, 64, 64, 64,
    64,  9, 10, 11, 12, 13, 14, 15, 16, 17, 64, 64, 64, 64, 64, 64,
    64, 64, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 64,
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
#endif /*CHARSET_EBCDIC*/
};

/* this is the same as ap_uudecode in util.c, but returns the length instead
 * of a pointer to the decoded data and takes a pointer to the decoded buffer
 * as a third parameter. Also, for EBCDIC machines the toebcdic[] on the ouput
 * is left out because we want a binary result.
 */
static int base64decode(pool *p, const char *bufcoded, unsigned char **bufplain)
{
    int nbytesdecoded;
    register const unsigned char *bufin;
    register unsigned char *bufout;
    register int nprbytes;

    /* Strip leading whitespace. */

    while (*bufcoded == ' ' || *bufcoded == '\t')
	bufcoded++;

    /* Figure out how many characters are in the input buffer.
     * Allocate this many from the per-transaction pool for the result.
     */
    bufin = (const unsigned char *) bufcoded;
    while (pr2six[*(bufin++)] <= 63);
    nprbytes = (bufin - (const unsigned char *) bufcoded) - 1;
    nbytesdecoded = ((nprbytes + 3) / 4) * 3;

    if (*bufplain == NULL)
	*bufplain = ap_palloc(p, nbytesdecoded + 1);
    bufout = *bufplain;

    bufin = (const unsigned char *) bufcoded;

    while (nprbytes > 0) {
	*(bufout++) =
	    (unsigned char) (pr2six[bufin[0]] << 2 | pr2six[bufin[1]] >> 4);
	*(bufout++) =
	    (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
	*(bufout++) =
	    (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
	bufin += 4;
	nprbytes -= 4;
    }

    if (nprbytes & 03) {
	if (pr2six[bufin[-2]] > 63)
	    nbytesdecoded -= 2;
	else
	    nbytesdecoded -= 1;
    }
    (*bufplain)[nbytesdecoded] = '\0';

    return nbytesdecoded;
}

static const char six2pr[64] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/* This is similar to ap_uuencode except that it takes a length parameter
 * (so we can encode binary data) and fixes a bug. Also note that no
 * toascii[] is done on the input for EBCDIC (six2pr is automatically
 * right).
 */
static char *base64encode(pool *p, const unsigned char *bufplain, int buflen)
{
    int nbytescoded;
    char *bufcoded;
    register const unsigned char *bufin;
    register char *bufout;
    register int nsixbytes;

    /* Figure out how many characters are in the input buffer.
     * Allocate this many from the per-transaction pool for the result.
     */
    nsixbytes = ((buflen + 2) / 3) * 4;

    bufcoded = ap_palloc(p, nsixbytes + 1);
    bufcoded[nsixbytes] = '\0';

    bufin  = bufplain;
    bufout = bufcoded;

    nbytescoded = 0;

    while (nbytescoded < (buflen - 2)) {
	*(bufout++) = six2pr[(bufin[0] >> 2) & 0x3F];
	*(bufout++) =
		six2pr[((bufin[1] >> 4) & 0x0F) | ((bufin[0] << 4) & 0x3F)];
	*(bufout++) =
		six2pr[((bufin[2] >> 6) & 0x03) | ((bufin[1] << 2) & 0x3F)];
	*(bufout++) = six2pr[bufin[2] & 0x3F];
	bufin += 3;
	nbytescoded += 3;
    }

    if (nbytescoded < buflen) {
	*(bufout++) = six2pr[(bufin[0] >> 2) & 0x3F];
	if (nbytescoded < (buflen-1)) {
	    *(bufout++) =
		    six2pr[((bufin[1] >> 4) & 0x0F) | ((bufin[0] << 4) & 0x3F)];
	    *(bufout++) = six2pr[(bufin[1] << 2) & 0x3F];
	}
	else {
	    *(bufout++) = six2pr[(bufin[0] << 4) & 0x3F];
	}
    }

    while (bufout < (bufcoded+nsixbytes))
	*(bufout++) = (unsigned char) '=';

    return bufcoded;
}


/*
 * Nonce generation code
 */

/* The hash part of the nonce is a SHA-1 hash of the time, realm, opaque,
 * and our secret.
 */
static void gen_nonce_hash(char *hash, const char *time, const char *realm,
			   const char *opaque)
{
    const char *hex = "0123456789abcdef";
    SHS_CTX ctx;
    int idx;

    shsInit(&ctx);
    shsUpdate(&ctx, (unsigned char *) time, strlen(time));
    shsUpdate(&ctx, (unsigned char *) realm, strlen(realm));
    if (opaque)
	shsUpdate(&ctx, (unsigned char *) opaque, strlen(opaque));
    shsUpdate(&ctx, secret, sizeof(secret));
    shsFinal(&ctx);

    for (idx=0; idx<5; idx++) {
	/* like sprintf(hash, "%08lx", ctx.h[idx]), but faster */
	unsigned long x = ctx.h[idx];
	*hash++ = hex[(x >> 28) & 0xF];
	*hash++ = hex[(x >> 24) & 0xF];
	*hash++ = hex[(x >> 20) & 0xF];
	*hash++ = hex[(x >> 16) & 0xF];
	*hash++ = hex[(x >> 12) & 0xF];
	*hash++ = hex[(x >>  8) & 0xF];
	*hash++ = hex[(x >>  4) & 0xF];
	*hash++ = hex[x & 0xF];
    }

    *hash++ = '\0';
}


/* The nonce has the format b64(time)+hash .
 */
static const char *gen_nonce(pool *p, time_t now, const char *realm,
			     const char *opaque)
{
    char *nonce = ap_palloc(p, NONCE_LEN+1);
    time_rec t;

    t.time = now;
    memcpy(nonce, base64encode(p, t.arr, sizeof(t.arr)), NONCE_TIME_LEN+1);
    gen_nonce_hash(nonce+NONCE_TIME_LEN, nonce, realm, opaque);

    return nonce;
}


/*
 * MD5-sess code.
 *
 * If you want to use algorithm=MD5-sess you must write get_userpw_hash()
 * yourself (see below).
 */

/*
 * get_userpw_hash() will be called each time a new session needs to be
 * generated and is expected to return the equivalent of
 *
 * ap_md5(r->pool,
 *        ap_pstrcat(r->pool, username, ":", ap_auth_name(r), ":", passwd))
 *
 * You must implement this yourself, and will probably consist of code
 * contacting the password server and retrieving the hash from it.
 *
 * TBD: This function should probably be in a seperate source file so that
 * people need no modify mod_digest.c each time a new release comes out.
 */
static const char *get_userpw_hash(const request_rec *r,
				   const digest_header_rec *resp,
				   const digest_config_rec *conf)
{
    /* for now, just get it from pwfile */
    return conf->ha1;
}


static const char *get_session(const request_rec *r,
			       const digest_header_rec *resp,
			       const digest_config_rec *conf)
{
    const char *ha1 = NULL, *urp;

    /* get ha1 from session list - TBD */

    /* generate new session if necessary */
    if (ha1 == NULL) {
	urp = get_userpw_hash(r, resp, conf);
	ha1 = ap_md5(r->pool,
		     (unsigned char *) ap_pstrcat(r->pool, ha1, ":", resp->nonce,
						  ":", resp->cnonce, NULL));
    }

    return ha1;
}


static void clear_session(const request_rec *r, const digest_header_rec *resp,
			  const digest_config_rec *conf)
{
    /* TBD */
}


/*
 * Authorization challenge generation code (for WWW-Authenticate)
 */

static const char *guess_domain(pool *p, const char *uri, const char *filename,
				const char *dir)
{
    size_t u_len = strlen(uri), f_len = strlen(filename), d_len = strlen(dir);
    const char *u, *f;


    /* Because of things like mod_alias and mod_rewrite and the fact that
     * protection is often on a directory basis (not a location basis) it
     * is hard to determine the uri to put in the domain attribute.
     *
     * What we do is the following: first we see if the directory is
     * a prefix for the uri - if this is the case we assume that therefore
     * a <Location> directive was protecting this uri and we can use it
     * for the domain.
     */
    if (u_len >= d_len  &&  !memcmp(uri, dir, d_len))
	return dir;

    /* Now we check for <Files ...>, and if we find one we send back a
     * dummy uri - this is the only way to specify that the protection
     * space only covers a single uri.
     */
    if (dir[0] != '/')
	return "http://0.0.0.0/";

    /* Next we find the largest common common suffix of the request-uri
     * and the final file name, ignoring any extensions; this gives us a
     * hint as to where any rewriting could've occured (assuming that some
     * prefix of the uri is rewritten, not a suffix).
     */
    u = uri + u_len - 1;	/* strip any extension */
    while (u > uri  &&  *u != '/')  u--;
    while (*u  &&  *u != '.')  u++;
    if (*u == '.')  u--;
    if (*u == '/')  u--;

    f = filename + f_len - 1;	/* strip any extension */
    while (f > filename  &&  *f != '/')  f--;
    while (*f  &&  *f != '.')  f++;
    if (*f == '.')  f--;
    if (*f == '/')  f--;

    while (*f == *u  &&  f > filename  &&  u > uri)  u--, f--;
    f++; u++;

    while (*f  &&  *f != '/')  f++, u++;	/* suffix must start with / */

    /* Now, if the directory reaches into this common suffix then we can
     * take the uri with the same reach.
     */
    if ((f-filename) < d_len) {
	char *tmp = ap_pstrdup(p, uri);
	tmp[(u-uri)+(d_len-(f-filename))] = '\0';
	return tmp;
    }

    return "";	/* give up */
}


static void note_digest_auth_failure(request_rec *r,
				     const digest_config_rec *conf,
				     const digest_header_rec *resp, int stale)
{
    const char *qop, *opaque, *opaque_param, *domain;
    int   cnt;

    if (conf->qop_list[0] == NULL)
	qop = ", qop=\"auth\"";
    else if (!strcasecmp(conf->qop_list[0], "none"))
	qop = "";
    else {
	qop = ap_pstrcat(r->pool, ", qop=\"", conf->qop_list[0], NULL);
	for (cnt=1; conf->qop_list[cnt] != NULL; cnt++)
	    qop = ap_pstrcat(r->pool, qop, ",", conf->qop_list[cnt], NULL);
	qop = ap_pstrcat(r->pool, qop, "\"", NULL);
    }

    if (!stale  &&  !strcasecmp(conf->algorithm, "MD5-sess"))
	clear_session(r, resp, conf);

    if (resp->opaque == NULL) {
	/* new client */
	if (conf->check_nc  ||  !strcasecmp(conf->algorithm, "MD5-sess")) {
	    /* so generate a unique opaque string */
	    /* TBD when nonce-count checking is impl */
	    opaque = "";
	}
	else {
	    /* opaque not needed */
	    opaque = "";
	}
    }
    else {
	opaque = resp->opaque;
    }
    if (opaque[0])
	opaque_param = ap_pstrcat(r->pool, ", opaque=\"", opaque, "\"", NULL);
    else
	opaque_param = NULL;

    /* setup domain attribute. We want to send this attribute wherever
     * possible so that the client won't send the Authorization header
     * unneccessarily (it's usually > 200 bytes!).
     */

    if (conf->uri_list)
	domain = conf->uri_list;
    else {
	/* They didn't specify any domain, so let's guess at it */
	domain = guess_domain(r->pool, resp->request_uri->path, r->filename,
			      conf->dir_name);
	if (domain[0] == '/'  &&  domain[1] == '\0')
	    domain = "";	/* "/" is the default, so no need to send it */
	else
	    domain = ap_pstrcat(r->pool, ", domain=\"", domain, "\"", NULL);
    }

    ap_table_mergen(r->err_headers_out,
		    r->proxyreq ? "Proxy-Authenticate" : "WWW-Authenticate",
		    ap_psprintf(r->pool, "Digest realm=\"%s\", nonce=\"%s\", "
					 "algorithm=%s%s%s%s%s",
				ap_auth_name(r),
				gen_nonce(r->pool, r->request_time,
					  conf->realm, opaque),
				conf->algorithm,
				opaque_param ? opaque_param : "",
				domain ? domain : "",
				stale ? ", stale=true" : "",
				qop));
}


/*
 * Authorization header parser code
 */

/* Parse the Authorization header, if it exists */
static int get_digest_rec(request_rec *r, digest_header_rec *resp)
{
    const char *auth_line = ap_table_get(r->headers_in,
					 r->proxyreq ? "Proxy-Authorization"
						     : "Authorization");
    size_t l;
    int vk = 0, vv = 0;
    char *key, *value;


    if (!auth_line) {
	resp->auth_hdr_sts = NO_HEADER;
	return !OK;
    }

    resp->scheme = ap_getword(r->pool, &auth_line, ' ');
    if (strcasecmp(resp->scheme, "Digest")) {
	resp->auth_hdr_sts = NOT_DIGEST;
	return !OK;
    }

    l = strlen(auth_line);

    /* Note we don't allocate l + 1 bytes for these deliberately, because
     * there has to be at least one '=' character for either of these two
     * new strings to be terminated.  That takes care of the need for +1.
     */
    key   = ap_palloc(r->pool, l);
    value = ap_palloc(r->pool, l);

    while (auth_line[0] != '\0') {

	/* find key */

	while (ap_isspace(auth_line[0])) auth_line++;
	vk = 0;
	while (auth_line[0] != '='  &&  auth_line[0] != ','  &&
	       auth_line[0] != '\0'  &&  !ap_isspace(auth_line[0]))
	    key[vk++] = *auth_line++;
	key[vk] = '\0';
	while (ap_isspace(auth_line[0])) auth_line++;

	/* find value */

	if (auth_line[0] == '=') {
	    auth_line++;
	    while (ap_isspace(auth_line[0])) auth_line++;

	    vv = 0;
	    if (auth_line[0] == '\"') {		/* quoted string */
		auth_line++;
		while (auth_line[0] != '\"'  &&  auth_line[0] != '\0') {
		    if (auth_line[0] == '\\'  &&  auth_line[1] != '\0')
			auth_line++;		/* escaped char */
		    value[vv++] = *auth_line++;
		}
		if (auth_line[0] != '\0') auth_line++;
	    }
	    else {				 /* token */
		while (auth_line[0] != ','  &&  auth_line[0] != '\0'  &&
		       !ap_isspace(auth_line[0]))
		    value[vv++] = *auth_line++;
	    }
	    value[vv] = '\0';
	}

	while (auth_line[0] != ','  &&  auth_line[0] != '\0')  auth_line++;
	if (auth_line[0] != '\0') auth_line++;

	if (!strcasecmp(key, "username"))
	    resp->username = ap_pstrdup(r->pool, value);
	else if (!strcasecmp(key, "realm"))
	    resp->realm = ap_pstrdup(r->pool, value);
	else if (!strcasecmp(key, "nonce"))
	    resp->nonce = ap_pstrdup(r->pool, value);
	else if (!strcasecmp(key, "uri"))
	    resp->uri = ap_pstrdup(r->pool, value);
	else if (!strcasecmp(key, "response"))
	    resp->digest = ap_pstrdup(r->pool, value);
	else if (!strcasecmp(key, "algorithm"))
	    resp->algorithm = ap_pstrdup(r->pool, value);
	else if (!strcasecmp(key, "cnonce"))
	    resp->cnonce = ap_pstrdup(r->pool, value);
	else if (!strcasecmp(key, "opaque"))
	    resp->opaque = ap_pstrdup(r->pool, value);
	else if (!strcasecmp(key, "qop"))
	    resp->message_qop = ap_pstrdup(r->pool, value);
	else if (!strcasecmp(key, "nc"))
	    resp->nonce_count = ap_pstrdup(r->pool, value);
    }

    if (!resp->username || !resp->realm || !resp->nonce || !resp->uri ||
	!resp->digest) {
	resp->auth_hdr_sts = INVALID;
	return !OK;
    }

    resp->auth_hdr_sts = VALID;
    return OK;
}


/*
 * Nonce-count handling code
 */

static int inc_nc(const server_rec *server, const char *realm,
		  const char *opaque)
{
    /* TBD */
    return 0;
}


/* Because the browser may preemptively send auth info, incrementing the
 * nonce-count when it does, and because the client does not get notified
 * if the URI didn't need authentication after all, we need to be sure to
 * update the nonce-count each time we receive an Authorization header no
 * matter what the final outcome of the request.
 *
 * Note that this must be called after mod_proxy had its go so that
 * r->proxyreq is set correctly.
 */
static int update_nonce_count(request_rec *r)
{
    digest_header_rec *resp;
    int res;

    if (!ap_is_initial_req(r))
	return DECLINED;

    resp = ap_pcalloc(r->pool, sizeof(digest_header_rec));
    res = get_digest_rec(r, resp);
    ap_set_module_config(r->request_config, &digest_module, resp);

    if (res == OK)
	inc_nc(r->server, resp->realm, resp->opaque);

    return DECLINED;
}


/*
 * Authorization header verification code
 */

static const char *get_hash(request_rec *r, const char *user,
			    const char *realm, const char *auth_pwfile)
{
    configfile_t *f;
    char l[MAX_STRING_LEN];
    const char *rpw;
    char *w, *x;

    if (!(f = ap_pcfg_openfile(r->pool, auth_pwfile))) {
	ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
		      "Could not open password file: %s", auth_pwfile);
	return NULL;
    }
    while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {
	if ((l[0] == '#') || (!l[0]))
	    continue;
	rpw = l;
	w = ap_getword(r->pool, &rpw, ':');
	x = ap_getword(r->pool, &rpw, ':');

	if (x && w && !strcmp(user, w) && !strcmp(realm, x)) {
	    ap_cfg_closefile(f);
	    return ap_pstrdup(r->pool, rpw);
	}
    }
    ap_cfg_closefile(f);
    return NULL;
}

static int check_nc(const request_rec *r, const digest_header_rec *resp,
		    const digest_config_rec *conf)
{
    int nc, my_nc = inc_nc(r->server, resp->realm, resp->opaque);

    if (conf->check_nc) {
	const char *snc = resp->nonce_count;
	char *endptr;

	nc = strtol(snc, &endptr, 10);
	if (endptr < (snc+strlen(snc))  &&  !ap_isspace(*endptr)) {
	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
			  "invalid nc %s received - not a number", snc);
	    return !OK;
	}

	if (nc != my_nc)
	    return !OK;
    }

    return OK;
}

static int check_nonce(request_rec *r, digest_header_rec *resp,
		       const digest_config_rec *conf)
{
    double dt;
    time_rec nonce_time;
    unsigned char *t;
    char tmp, hash[NONCE_HASH_LEN+1];

    if (strlen(resp->nonce) != NONCE_LEN) {
	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
		      "invalid nonce %s received - length is not %d",
		      resp->nonce, NONCE_LEN);
	note_digest_auth_failure(r, conf, resp, 0);
	return AUTH_REQUIRED;
    }

    tmp = resp->nonce[NONCE_TIME_LEN];
    resp->nonce[NONCE_TIME_LEN] = '\0';
    t = nonce_time.arr;
    base64decode(r->pool, resp->nonce, &t);
    gen_nonce_hash(hash, resp->nonce, conf->realm, resp->opaque);
    resp->nonce[NONCE_TIME_LEN] = tmp;
    resp->nonce_time = nonce_time.time;

    if (strcmp(hash, resp->nonce+NONCE_TIME_LEN)) {
	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
		      "invalid nonce %s received - hash is not %s", resp->nonce,
		      hash);
	note_digest_auth_failure(r, conf, resp, 0);
	return AUTH_REQUIRED;
    }

    dt = difftime(r->request_time, nonce_time.time);
    if (dt < 0) {
	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
		      "invalid nonce %s received - user attempted time travel",
		      resp->nonce);
	note_digest_auth_failure(r, conf, resp, 0);
	return AUTH_REQUIRED;
    }

    if (conf->nonce_lifetime > 0) {
	if (dt > conf->nonce_lifetime) {
	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, r,
			  "user %s: nonce expired - sending new nonce",
			  r->connection->user);
	    note_digest_auth_failure(r, conf, resp, 1);
	    return AUTH_REQUIRED;
	}
    }
    else if (conf->nonce_lifetime == 0) {
	/* TBD */
    }
    /* else (lifetime < 0) => never expires */

    return OK;
}

/* The actual MD5 code... whee */

static const char *old_digest(const request_rec *r,
			      const digest_header_rec *resp, const char *ha1)
{
    const char *ha2;

    /* rfc-2069 */
    ha2 = ap_md5(r->pool, (unsigned char *)ap_pstrcat(r->pool, r->method, ":",
						      resp->uri, NULL));
    return ap_md5(r->pool,
		  (unsigned char *)ap_pstrcat(r->pool, ha1, ":", resp->nonce,
					      ":", ha2, NULL));
}

static const char *new_digest(const request_rec *r,
			      const digest_header_rec *resp,
			      const digest_config_rec *conf)
{
    const char *ha1, *ha2, *a2;

    /* draft-ietf-http-authentication-03 */
    if (resp->algorithm  &&  !strcasecmp(resp->algorithm, "MD5-sess"))
	ha1 = get_session(r, resp, conf);
    else
	ha1 = conf->ha1;

    if (resp->message_qop  &&  !strcasecmp(resp->message_qop, "auth-int"))
	a2 = ap_pstrcat(r->pool, r->method, ":", resp->uri, ":",
			ap_md5(r->pool, (unsigned char*) ""), NULL); /* TBD */
    else
	a2 = ap_pstrcat(r->pool, r->method, ":", resp->uri, NULL);
    ha2 = ap_md5(r->pool, (unsigned char *)a2);

    return ap_md5(r->pool,
		  (unsigned char *)ap_pstrcat(r->pool, ha1, ":", resp->nonce,
					      ":", resp->nonce_count, ":",
					      resp->cnonce, ":",
					      resp->message_qop, ":", ha2,
					      NULL));
}


static const char *unescape(pool *p, const char *str)
{
    char *bs = strchr(str, '\\'), *new, *pos;
    if (!bs  ||  bs[1] == '\0')  return str;

    new = ap_palloc(p, strlen(str));
    memcpy(new, str, (bs-str));
    pos = new + (bs-str);
    bs++;
    while (*bs) {
	if (*bs == '\\') bs++;
	*pos++ = *bs++;
    }

    *pos = '\0';
    return new;
}


/* These functions return 0 if client is OK, and proper error status
 * if not... either AUTH_REQUIRED, if we made a check, and it failed, or
 * SERVER_ERROR, if things are so totally confused that we couldn't
 * figure out how to tell if the client is authorized or not.
 *
 * If they return DECLINED, and all other modules also decline, that's
 * treated by the server core as a configuration error, logged and
 * reported as such.
 */

/* Determine user ID, and check if the attributes are correct, if it
 * really is that user, if the nonce is correct, etc.
 */

static int authenticate_digest_user(request_rec *r)
{
    digest_config_rec *conf;
    digest_header_rec *resp;
    request_rec       *main;
    conn_rec          *conn = r->connection;
    const char        *t;
    int                res;


    /* do we require Digest auth for this URI? */

    if (!(t = ap_auth_type(r)) || strcasecmp(t, "Digest"))
	return DECLINED;

    if (!ap_auth_name(r)) {
	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
		      "need AuthName: %s", r->uri);
	return SERVER_ERROR;
    }


    /* get the client response and real uri */

    main = r;
    while (main->main != NULL)  main = main->main;
    while (main->prev != NULL)  main = main->prev;
    resp = (digest_header_rec *) ap_get_module_config(main->request_config,
						      &digest_module);
    resp->request_uri = &main->parsed_uri;


    /* set up our conf */

    conf = (digest_config_rec *) ap_get_module_config(r->per_dir_config,
						      &digest_module);
    if (!conf->realm)
	conf->realm = unescape(conf->pool, ap_auth_name(r));


    /* check for existence and syntax of Auth header */

    if (resp->auth_hdr_sts != VALID) {
	if (resp->auth_hdr_sts == NOT_DIGEST)
	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
			  "client used wrong authentication scheme `%s': %s",
			  resp->scheme, r->uri);
	else if (resp->auth_hdr_sts == INVALID)
	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
			  "missing user, realm, nonce, uri, or digest in "
			  "authorization header: %s", r->uri);
	/* else (resp->auth_hdr_sts == NO_HEADER) */
	note_digest_auth_failure(r, conf, resp, 0);
	return AUTH_REQUIRED;
    }

    r->connection->user = (char *) resp->username;
    r->connection->ap_auth_type = "Digest";


    /* check the auth attributes */

    if (strcmp(resp->uri, resp->request_uri->path)) {
	uri_components *r_uri = resp->request_uri, d_uri;
	ap_parse_uri_components(r->pool, resp->uri, &d_uri);

	if ((d_uri.hostname  &&  d_uri.hostname[0] != '\0'  &&
	     strcasecmp(d_uri.hostname, r->server->server_hostname))  ||
	    (d_uri.port_str  &&  d_uri.port != r->server->port)  ||
	    (!d_uri.port_str  &&  r->server->port != 80)  ||
	    strcmp(d_uri.path, r_uri->path)  ||
	    (d_uri.query != r_uri->query &&
	     (!d_uri.query || !r_uri->query ||
	      strcmp(d_uri.query, r_uri->query)))
	    ) {
	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
			  "uri mismatch - <%s> does not match request-uri <%s>",
			  resp->uri,
			  ap_unparse_uri_components(r->pool, r_uri, 0));
	    return BAD_REQUEST;
	}
    }

    if (strcmp(resp->realm, conf->realm)) {
	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
		      "realm mismatch - got `%s' but expected `%s'",
		      resp->realm, conf->realm);
	note_digest_auth_failure(r, conf, resp, 0);
	return AUTH_REQUIRED;
    }

    if (resp->algorithm != NULL  &&
	strcasecmp(resp->algorithm, "MD5")  &&
	strcasecmp(resp->algorithm, "MD5-sess")) {
	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
		      "unknown algorithm `%s' received: %s",
		      resp->algorithm, r->uri);
	note_digest_auth_failure(r, conf, resp, 0);
	return AUTH_REQUIRED;
    }

    if (!conf->pwfile)
	return DECLINED;

    if (!(conf->ha1 = get_hash(r, conn->user, conf->realm, conf->pwfile))) {
	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
		      "user `%s' in realm `%s' not found: %s", conn->user,
		      conf->realm, r->uri);
	note_digest_auth_failure(r, conf, resp, 0);
	return AUTH_REQUIRED;
    }

    if (resp->message_qop == NULL) {
	/* old (rfc-2069) style digest */
	if (strcmp(resp->digest, old_digest(r, resp, conf->ha1))) {
	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
			  "user %s: password mismatch: %s", conn->user, r->uri);
	    note_digest_auth_failure(r, conf, resp, 0);
	    return AUTH_REQUIRED;
	}
    }
    else {
	int match = 0, idx;
	for (idx=0; conf->qop_list[idx] != NULL; idx++) {
	    if (!strcasecmp(conf->qop_list[idx], resp->message_qop)) {
		match = 1;
		break;
	    }
	}

	if (!match  &&
	    !(conf->qop_list[0] == NULL  &&
	      !strcasecmp(resp->message_qop, "auth"))) {
	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
			  "invalid qop `%s' received: %s",
			  resp->message_qop, r->uri);
	    note_digest_auth_failure(r, conf, resp, 0);
	    return AUTH_REQUIRED;
	}

	if (strcmp(resp->digest, new_digest(r, resp, conf))) {
	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
			  "user %s: password mismatch: %s", conn->user, r->uri);
	    note_digest_auth_failure(r, conf, resp, 0);
	    return AUTH_REQUIRED;
	}
    }

    if (check_nc(r, resp, conf) != OK) {
	note_digest_auth_failure(r, conf, resp, 0);
	return AUTH_REQUIRED;
    }

    /* Note: this check is done last so that a "stale=true" can be
       generated if the nonce is old */
    if ((res = check_nonce(r, resp, conf)))
	return res;

    return OK;
}


/*
 * Checking ID
 */

static int digest_check_auth(request_rec *r)
{
    const char *user = r->connection->user;
    int m = r->method_number;
    int method_restricted = 0;
    register int x;
    const char *t;
    char *w;
    const array_header *reqs_arr;
    require_line *reqs;

    if (!(t = ap_auth_type(r)) || strcasecmp(t, "Digest"))
	return DECLINED;

    reqs_arr = ap_requires(r);
    /* If there is no "requires" directive, 
     * then any user will do.
     */
    if (!reqs_arr)
	return OK;
    reqs = (require_line *) reqs_arr->elts;

    for (x = 0; x < reqs_arr->nelts; x++) {

	if (!(reqs[x].method_mask & (1 << m)))
	    continue;

	method_restricted = 1;

	t = reqs[x].requirement;
	w = ap_getword(r->pool, &t, ' ');
	if (!strcasecmp(w, "valid-user"))
	    return OK;
	else if (!strcasecmp(w, "user")) {
	    while (t[0]) {
		w = ap_getword_conf(r->pool, &t);
		if (!strcmp(user, w))
		    return OK;
	    }
	}
	else
	    return DECLINED;
    }

    if (!method_restricted)
	return OK;

    note_digest_auth_failure(r, 
	(digest_config_rec *) ap_get_module_config(r->per_dir_config,
						   &digest_module),
	(digest_header_rec *) ap_get_module_config(r->request_config,
						   &digest_module),
	0);
    return AUTH_REQUIRED;
}


/*
 * Authorization-Info header code
 */

#ifdef SEND_DIGEST
static const char *hdr(const table *tbl, const char *name)
{
    const char *val = ap_table_get(tbl, name);
    if (val)
	return val;
    else
	return "";
}
#endif

static int add_auth_info(request_rec *r)
{
    const digest_config_rec *conf =
		(digest_config_rec *) ap_get_module_config(r->per_dir_config,
							   &digest_module);
    const digest_header_rec *resp =
		(digest_header_rec *) ap_get_module_config(r->request_config,
							   &digest_module);
    char *ai = NULL, *digest = NULL, *nextnonce = "";

    if (resp == NULL  ||  conf == NULL)
	return OK;


    /* rfc-2069 digest
     */
    if (resp->message_qop == NULL) {
	/* old client, so calc rfc-2069 digest */

#ifdef SEND_DIGEST
	/* most of this totally bogus because the handlers don't set the
	 * headers until the final handler phase (I wonder why this phase
	 * is called fixup when there's almost nothing you can fix up...)
	 *
	 * Because it's basically impossible to get this right (e.g. the
	 * Content-length is never set yet when we get here, and we can't
	 * calc the entity hash) it's best to just leave this #def'd out.
	 */
	char *entity_info =
	    ap_md5(r->pool,
		   (unsigned char *) ap_pstrcat(r->pool,
		       ap_unparse_uri_components(r->pool,
						 resp->request_uri, 0), ":",
		       r->content_type ? r->content_type : ap_default_type(r), ":",
		       hdr(r->headers_out, "Content-Length"), ":",
		       r->content_encoding ? r->content_encoding : "", ":",
		       hdr(r->headers_out, "Last-Modified"), ":",
		       r->no_cache && !ap_table_get(r->headers_out, "Expires") ?
			    ap_gm_timestr_822(r->pool, r->request_time) :
			    hdr(r->headers_out, "Expires"),
		       NULL));
	digest =
	    ap_md5(r->pool,
		   (unsigned char *)ap_pstrcat(r->pool, conf->ha1, ":",
					       resp->nonce, ":",
					       r->method, ":",
					       ap_gm_timestr_822(r->pool, r->request_time), ":",
					       entity_info, ":",
					       ap_md5(r->pool, (unsigned char *) ""), /* H(entity) - TBD */
					       NULL));
#endif
    }


    /* setup nextnonce
     */
    if (conf->nonce_lifetime > 0) {
	/* send nextnonce if current nonce will expire in less than 30 secs */
	if (difftime(r->request_time, resp->nonce_time) > (conf->nonce_lifetime-NEXTNONCE_DELTA)) {
	    nextnonce = ap_pstrcat(r->pool, ", nextnonce=\"",
				   gen_nonce(r->pool, r->request_time,
					     conf->realm, resp->opaque),
				   "\"", NULL);
	}
    }
    else if (conf->nonce_lifetime == 0) {
	/* TBD */
    }
    /* else nonce never expires, hence no nextnonce */


    /* do rfc-2069 digest
     */
    if (conf->qop_list[0]  &&  !strcasecmp(conf->qop_list[0], "none")  &&
	resp->message_qop == NULL) {
	/* use only RFC-2069 format */
	if (digest)
	    ai = ap_pstrcat(r->pool, "digest=\"", digest, "\"", nextnonce,NULL);
	else
	    ai = nextnonce;
    }
    else {
	const char *resp_dig, *ha1, *a2, *ha2;

	/* calculate rspauth attribute
	 */
	if (resp->algorithm  &&  !strcasecmp(resp->algorithm, "MD5-sess"))
	    ha1 = get_session(r, resp, conf);
	else
	    ha1 = conf->ha1;

	if (resp->message_qop  &&  !strcasecmp(resp->message_qop, "auth-int"))
	    a2 = ap_pstrcat(r->pool, ":", resp->uri, ":",
			    ap_md5(r->pool, (unsigned char *) ""), NULL); /* TBD */
	else
	    a2 = ap_pstrcat(r->pool, ":", resp->uri, NULL);
	ha2 = ap_md5(r->pool, (unsigned char *)a2);

	resp_dig = ap_md5(r->pool,
		         (unsigned char *)ap_pstrcat(r->pool, ha1, ":",
						     resp->nonce, ":",
						     resp->nonce_count, ":",
						     resp->cnonce, ":",
						     resp->message_qop ?
							 resp->message_qop : "",
						     ":", ha2, NULL));

	/* assemble Authentication-Info header
	 */
	ai = ap_pstrcat(r->pool,
			"rspauth=\"", resp_dig, "\"",
			nextnonce,
		        resp->cnonce ? ", cnonce=\"" : "",
		        resp->cnonce ? ap_escape_quotes(r->pool, resp->cnonce) :
					"",
		        resp->cnonce ? "\"" : "",
		        resp->nonce_count ? ", nc=" : "",
		        resp->nonce_count ? resp->nonce_count : "",
		        resp->message_qop ? ", qop=" : "",
		        resp->message_qop ? resp->message_qop : "",
			digest ? "digest=\"" : "",
			digest ? digest : "",
			digest ? "\"" : "",
			NULL);
    }

    if (ai  &&  ai[0])
	ap_table_mergen(r->headers_out,
			r->proxyreq ? "Proxy-Authentication-Info" :
				      "Authentication-Info",
			ai);
    return OK;
}


module MODULE_VAR_EXPORT digest_module =
{
    STANDARD_MODULE_STUFF,
    initialize_secret,		/* initializer */
    create_digest_dir_config,	/* dir config creater */
    NULL,			/* dir merger --- default is to override */
    NULL,			/* server config */
    NULL,			/* merge server config */
    digest_cmds,		/* command table */
    NULL,			/* handlers */
    NULL,			/* filename translation */
    authenticate_digest_user,	/* check_user_id */
    digest_check_auth,		/* check auth */
    NULL,			/* check access */
    NULL,			/* type_checker */
    add_auth_info,		/* fixups */
    NULL,			/* logger */
    NULL,			/* header parser */
    NULL,			/* child_init */
    NULL,			/* child_exit */
    update_nonce_count		/* post read-request */
};

----- mod_digest.html -------------------------------------------------
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
<HEAD>
<TITLE>Apache module mod_digest</TITLE>
</HEAD>

<!-- Background white, links blue (unvisited), navy (visited), red (active) -->
<BODY
 BGCOLOR="#FFFFFF"
 TEXT="#000000"
 LINK="#0000FF"
 VLINK="#000080"
 ALINK="#FF0000"
>
<!--#include virtual="header.html" -->
<H1 ALIGN="CENTER">Module mod_digest</H1>

This module is contained in the <CODE>mod_digest.c</CODE> file, and is
not compiled in by default. It is only available in Apache 1.1 and
later. It provides for user authentication using MD5 Digest
Authentication.


<MENU>
<LI><A HREF="#authdigestfile">AuthDigestFile</A>
<LI><A HREF="#authdigestqop">AuthDigestQop</A>
<LI><A HREF="#authdigestnoncelifetime">AuthDigestNonceLifetime</A>
<LI><A HREF="#authdigestnonceformat">AuthDigestNonceFormat</A>
<LI><A HREF="#authdigestnccheck">AuthDigestNcCheck</A>
<LI><A HREF="#authdigestalgorithm">AuthDigestAlgorithm</A>
<LI><A HREF="#authdigestdomain">AuthDigestDomain</A>
</MENU>
<HR>


<H2><A NAME="authdigestfile">AuthDigestFile</A></H2>
<A
 HREF="directive-dict.html#Syntax"
 REL="Help"
><STRONG>Syntax:</STRONG></A> AuthDigestFile <EM>filename</EM><BR>
<A
 HREF="directive-dict.html#Context"
 REL="Help"
><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
<A
 HREF="directive-dict.html#Override"
 REL="Help"
><STRONG>Override:</STRONG></A> AuthConfig<BR>
<A
 HREF="directive-dict.html#Status"
 REL="Help"
><STRONG>Status:</STRONG></A> Base<BR>
<A
 HREF="directive-dict.html#Module"
 REL="Help"
><STRONG>Module:</STRONG></A> mod_digest<BR>

<P>The AuthDigestFile directive sets the name of a textual file containing
the list of users and encoded passwords for digest authentication.
<EM>Filename</EM> is the absolute path to the user file.

<P>The digest file uses a special format. Files in this format can be
created using the "htdigest" utility found in the support/ subdirectory of
the Apache distribution.

<HR>

<H2><A NAME="authdigestqop">AuthDigestQop</A></H2>
<A
 HREF="directive-dict.html#Syntax"
 REL="Help"
><STRONG>Syntax:</STRONG></A> AuthDigestQop <EM>none | 1*{ auth | auth-int }</EM><BR>
<A
 HREF="directive-dict.html#Default"
 REL="Help"
><STRONG>Default:</STRONG></A> <CODE>AuthDigestQop auth</CODE><BR>
<A
 HREF="directive-dict.html#Context"
 REL="Help"
><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
<A
 HREF="directive-dict.html#Override"
 REL="Help"
><STRONG>Override:</STRONG></A> AuthConfig<BR>
<A
 HREF="directive-dict.html#Status"
 REL="Help"
><STRONG>Status:</STRONG></A> Base<BR>
<A
 HREF="directive-dict.html#Module"
 REL="Help"
><STRONG>Module:</STRONG></A> mod_digest<BR>
<A
 HREF="directive-dict.html#Compatibility"
 REL="Help"
><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later

<P>The AuthDigestQop directive determines the quality-of-protection to use.
<EM>auth</EM> will only do authentication (username/password);
<EM>auth-int</EM> is authentication plus integrity checking (an MD5 hash
of the entity is also made and checked); <EM>none</EM> will cause the
module to use the old RFC-2069 digest algorithm (which does not include
integrity checking). Both <EM>auth</em> and <EM>auth-int</EM> may be
specified, in which the case the browser will choose which of these to
use. <EM>none</EM> should only be used if the browser for some reason
does not like the challenge is receives otherwise.

<P><STRONG><EM>auth-int</EM> is not implemented yet</STRONG>.

<HR>

<H2><A NAME="authdigestnoncelifetime">AuthDigestNonceLifetime</A></H2>
<A
 HREF="directive-dict.html#Syntax"
 REL="Help"
><STRONG>Syntax:</STRONG></A> AuthDigestNonceLifetime <EM>&lt;time&gt;</EM><BR>
<A
 HREF="directive-dict.html#Default"
 REL="Help"
><STRONG>Default:</STRONG></A> <CODE>AuthDigestNonceLifetime 300</CODE><BR>
<A
 HREF="directive-dict.html#Context"
 REL="Help"
><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
<A
 HREF="directive-dict.html#Override"
 REL="Help"
><STRONG>Override:</STRONG></A> AuthConfig<BR>
<A
 HREF="directive-dict.html#Status"
 REL="Help"
><STRONG>Status:</STRONG></A> Base<BR>
<A
 HREF="directive-dict.html#Module"
 REL="Help"
><STRONG>Module:</STRONG></A> mod_digest<BR>
<A
 HREF="directive-dict.html#Compatibility"
 REL="Help"
><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later

<P>The AuthDigestNonceLifetime directive controls how long the server
nonce is valid. When the client contacts the server using an expired
nonce the server will send back a 407 with <code>stale=true</code>. If
<EM>&lt;time&gt;</EM> is greater than 0 then it specifies the number of
seconds the nonce is valid; this should probably never be set to less
than 10 seconds. If <EM>&lt;time&gt;</EM> is less than 0 then the nonce
never expires.

<!-- Not implemented yet
If <EM>&lt;time&gt;</EM> is 0 then the nonce may be used exactly once by
the client.
-->

<HR>
<H2><A NAME="authdigestnonceformat">AuthDigestNonceFormat</A></H2>
<A
 HREF="directive-dict.html#Syntax"
 REL="Help"
><STRONG>Syntax:</STRONG></A> AuthDigestNonceFormat <EM>???</EM><BR>
<A
 HREF="directive-dict.html#Default"
 REL="Help"
><STRONG>Default:</STRONG></A> <CODE>AuthDigestNonceFormat ???</CODE><BR>
<A
 HREF="directive-dict.html#Context"
 REL="Help"
><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
<A
 HREF="directive-dict.html#Override"
 REL="Help"
><STRONG>Override:</STRONG></A> AuthConfig<BR>
<A
 HREF="directive-dict.html#Status"
 REL="Help"
><STRONG>Status:</STRONG></A> Base<BR>
<A
 HREF="directive-dict.html#Module"
 REL="Help"
><STRONG>Module:</STRONG></A> mod_digest<BR>
<A
 HREF="directive-dict.html#Compatibility"
 REL="Help"
><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later

<P><STRONG>Not implemented yet.</STRONG>
<!--
<P>The AuthDigestNonceFormat directive determines how the nonce is
generated.
-->

<HR>
<H2><A NAME="authdigestnccheck">AuthDigestNcCheck</A></H2>
<A
 HREF="directive-dict.html#Syntax"
 REL="Help"
><STRONG>Syntax:</STRONG></A> AuthDigestNcCheck <EM>On/Off</EM><BR>
<A
 HREF="directive-dict.html#Default"
 REL="Help"
><STRONG>Default:</STRONG></A> <CODE>AuthDigestNcCheck Off</CODE><BR>
<A
 HREF="directive-dict.html#Context"
 REL="Help"
><STRONG>Context:</STRONG></A> server config<BR>
<A
 HREF="directive-dict.html#Override"
 REL="Help"
><STRONG>Override:</STRONG></A> <EM>Not applicable</EM><BR>
<A
 HREF="directive-dict.html#Status"
 REL="Help"
><STRONG>Status:</STRONG></A> Base<BR>
<A
 HREF="directive-dict.html#Module"
 REL="Help"
><STRONG>Module:</STRONG></A> mod_digest<BR>
<A
 HREF="directive-dict.html#Compatibility"
 REL="Help"
><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later

<P><STRONG>Not implemented yet.</STRONG>
<!--
<P>The AuthDigestNcCheck directive enables or disables the checking of the
nonce-count sent by the server.

<P>While recommended from a security standpoint, turning this directive
On has one important performance implication. To check the nonce-count
*all* requests (which have an Authorization header, irrespective of
whether they require digest authentication) must be serialized through
a critical section. If the server is handling a large number of
requests which contain the Authorization header then this may noticeably
impact performance.

-->

<HR>
<H2><A NAME="authdigestalgorithm">AuthDigestAlgorithm</A></H2>
<A
 HREF="directive-dict.html#Syntax"
 REL="Help"
><STRONG>Syntax:</STRONG></A> AuthDigestAlgorithm <EM>MD5 | MD5-sess</EM><BR>
<A
 HREF="directive-dict.html#Default"
 REL="Help"
><STRONG>Default:</STRONG></A> <CODE>AuthDigestAlgorithm MD5</CODE><BR>
<A
 HREF="directive-dict.html#Context"
 REL="Help"
><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
<A
 HREF="directive-dict.html#Override"
 REL="Help"
><STRONG>Override:</STRONG></A> AuthConfig<BR>
<A
 HREF="directive-dict.html#Status"
 REL="Help"
><STRONG>Status:</STRONG></A> Base<BR>
<A
 HREF="directive-dict.html#Module"
 REL="Help"
><STRONG>Module:</STRONG></A> mod_digest<BR>
<A
 HREF="directive-dict.html#Compatibility"
 REL="Help"
><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later

<P>The AuthDigestAlgorithm directive selects the algorithm used to calculate
the challenge and response hashes.

<P><STRONG><EM>MD5-sess</EM> is not correctly implemented yet</STRONG>.
<!--
<P>To use <EM>MD5-sess</EM> you must first code up the
<VAR>get_userpw_hash()</VAR> function in <VAR>mod_digest.c</VAR> .
-->

<HR>
<H2><A NAME="authdigestdomain">AuthDigestDomain</A></H2>
<A
 HREF="directive-dict.html#Syntax"
 REL="Help"
><STRONG>Syntax:</STRONG></A> AuthDigestDomain <EM>URI URI ...</EM><BR>
<A
 HREF="directive-dict.html#Context"
 REL="Help"
><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
<A
 HREF="directive-dict.html#Override"
 REL="Help"
><STRONG>Override:</STRONG></A> AuthConfig<BR>
<A
 HREF="directive-dict.html#Status"
 REL="Help"
><STRONG>Status:</STRONG></A> Base<BR>
<A
 HREF="directive-dict.html#Module"
 REL="Help"
><STRONG>Module:</STRONG></A> mod_digest<BR>
<A
 HREF="directive-dict.html#Compatibility"
 REL="Help"
><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later

<P>The AuthDigestDomain directive allows to specify which URIs are in the
same protection space (i.e. use the same realm and username/password
info). The specified URIs are prefixes, i.e. the client will assume that
all URIs "below" these are also protected by the same username/password.
This directive <em>should</em> be specified and contain at least the set
of root URIs for this space.  Omiting to do so will cause the client to
send the Authorization header for <em>every request</em> sent to this
server.  Apart from increasing the size of the request, it may also have
a detrimental effect on performance if "AuthDigestNcCheck" is on.

<P>The URIs specified can also point to different servers, in which case
clients (which understand this) will then share username/password info
across multiple servers without prompting the user each time.


<HR>

<H3>Using Digest Authentication</H3>

<P>Using MD5 Digest authentication is very simple. Simply set up
authentication normally, using "AuthType Digest" and "AuthDigestFile"
instead of the normal "AuthType Basic" and "AuthUserFile". Then add a
"AuthDigestDomain" directive containing at least the root URIs for this
protection space.

<P>MD5 authentication provides a more secure password system, but only
works with supporting browsers. As of this writing (September 1998), the
majority of browsers do not support digest authentication. Therefore, we
do not recommend using this feature on a large Internet site. However, for
personal and intra-net use, where browser users can be controlled, it is
ideal.

<!--#include virtual="footer.html" -->
</BODY>
</HTML>

----- Confiration.tmpl (diff) -----------------------------------------
--- Configuration.tmpl.orig	Wed Sep 16 18:12:24 1998
+++ Configuration.tmpl	Thu Nov 19 23:03:31 1998
@@ -290,13 +290,6 @@
 
 AddModule modules/standard/mod_userdir.o
 
-## The proxy module enables the server to act as a proxy for outside
-## http and ftp services. It's not as complete as it could be yet.
-## NOTE: You do not want this module UNLESS you are running a proxy;
-##       it is not needed for normal (origin server) operation.
-
-# AddModule modules/proxy/libproxy.a
-
 ## The Alias module provides simple URL translation and redirection.
 
 AddModule modules/standard/mod_alias.o
@@ -330,6 +323,13 @@
 ## secure Basic Auth used by the other modules.
 
 # AddModule modules/standard/mod_digest.o
+
+## The proxy module enables the server to act as a proxy for outside
+## http and ftp services. It's not as complete as it could be yet.
+## NOTE: You do not want this module UNLESS you are running a proxy;
+##       it is not needed for normal (origin server) operation.
+
+# AddModule modules/proxy/libproxy.a
 
 ## Optional response header manipulation modules. 
 ##
----- proxy_http.c (diff) ---------------------------------------------
--- proxy_http.c.orig	Mon Aug 31 18:12:25 1998
+++ proxy_http.c	Fri Nov 13 22:05:43 1998
@@ -428,6 +428,14 @@
 	}
 
 	clear_connection(p, resp_hdrs);	/* Strip Connection hdrs */
+	if (r->status_line[0] <= '2') {
+	    ap_overlap_tables(r->headers_out, resp_hdrs, 0);
+	    resp_hdrs = r->headers_out;
+	}
+	else {
+	    ap_overlap_tables(r->err_headers_out, resp_hdrs, 0);
+	    resp_hdrs = r->err_headers_out;
+	}
     }
     else {
 /* an http/0.9 response */
----- proxy_ftp.c (diff) ----------------------------------------------
--- proxy_ftp.c.orig	Fri Aug 28 06:12:20 1998
+++ proxy_ftp.c	Tue Nov 17 18:40:18 1998
@@ -1107,7 +1107,7 @@
     r->status = HTTP_OK;
     r->status_line = "200 OK";
 
-    resp_hdrs = ap_make_table(p, 2);
+    resp_hdrs = ap_copy_table(p, r->headers_out);
     c->hdrs = resp_hdrs;
 
     if (parms[0] == 'd')
-----------------------------------------------------------------------


RE: Updated mod_digest

Posted by Lars Eilebrecht <La...@unix-ag.org>.
According to Ronald Tschal�r:

>  I've mentioned that I've been working on a new version of mod_digest, so
>  here it is.

I just applied your patch and tried to test it, but unfortunately
httpd doesn't start up correctly...

It outputs the message "generating secret for digest authentication"
to the error log and starts to hog the cpu after a call to setitimer().

strace reveals the following:

 [...]
 write(16, "httpd: [Wed Dec 23 23:24:53 1998"..., 91) = 91
 sigaction(SIGALRM, {0x80a14c0, [], SA_NOCLDSTOP|0x36}, {SIG_DFL}) = 0
 setitimer(ITIMER_REAL, {it_interval={0, 0}, it_value={0, 16665}},
  {it_interval={0, 0}, it_value={0, 0}}) = 0
 --- SIGALRM (Alarm clock) ---
 sigaction(SIGALRM, {0x80a14c0, [], SA_STACK|0x628e0}, {0x80a14c0, [],
  SA_STACK|0x4a4e0}) = 0
 setitimer(ITIMER_REAL, {it_interval={0, 0}, it_value={0, 16665}},
  {it_interval={0, 0}, it_value={0, 0}}) = 0


ciao...
-- 
Lars Eilebrecht                       - Programmers do it all night long.
sfx@unix-ag.org
http://www.home.unix-ag.org/sfx/

>  +all URIs "below" these are also protected by the same username/password.
>  +This directive <em>should</em> be specified and contain at least the set
>  +of root URIs for this space.  Omiting to do so will cause the client to
>  +send the Authorization header for <em>every request</em> sent to this
>  +server.  Apart from increasing the size of the request, it may also have
>  +a detrimental effect on performance if "AuthDigestNcCheck" is on.
>  +
>  +<P>The URIs specified can also point to different servers, in which case
>  +clients (which understand this) will then share username/password info
>  +across multiple servers without prompting the user each time.
>  +
>   
>   <HR>
>   
>   <H3>Using Digest Authentication</H3>
>   
>   <P>Using MD5 Digest authentication is very simple. Simply set up
>  -authentication normally. However, use "AuthType Digest" and
>  -"AuthDigestFile" instead of the normal "AuthType Basic" and
>  -"AuthUserFile". Everything else should remain the same.</P>
>  +authentication normally, using "AuthType Digest" and "AuthDigestFile"
>  +instead of the normal "AuthType Basic" and "AuthUserFile". Then add a
>  +"AuthDigestDomain" directive containing at least the root URIs for this
>  +protection space.
>   
>   <P>MD5 authentication provides a more secure password system, but only
>  -works with supporting browsers. As of this writing (July 1996), the
>  +works with supporting browsers. As of this writing (September 1998), the
>   majority of browsers do not support digest authentication. Therefore, we
>   do not recommend using this feature on a large Internet site. However, for
>   personal and intra-net use, where browser users can be controlled, it is
>  -ideal.</P>
>  +ideal.
>   
>   <!--#include virtual="footer.html" -->
>   </BODY>
>  ----- mod_digest.c ----------------------------------------------------
>  /* ====================================================================
>   * Copyright (c) 1995-1998 The Apache Group.  All rights reserved.
>   *
>   * Redistribution and use in source and binary forms, with or without
>   * modification, are permitted provided that the following conditions
>   * are met:
>   *
>   * 1. Redistributions of source code must retain the above copyright
>   *    notice, this list of conditions and the following disclaimer. 
>   *
>   * 2. Redistributions in binary form must reproduce the above copyright
>   *    notice, this list of conditions and the following disclaimer in
>   *    the documentation and/or other materials provided with the
>   *    distribution.
>   *
>   * 3. All advertising materials mentioning features or use of this
>   *    software must display the following acknowledgment:
>   *    "This product includes software developed by the Apache Group
>   *    for use in the Apache HTTP server project (http://www.apache.org/)."
>   *
>   * 4. The names "Apache Server" and "Apache Group" must not be used to
>   *    endorse or promote products derived from this software without
>   *    prior written permission. For written permission, please contact
>   *    apache@apache.org.
>   *
>   * 5. Products derived from this software may not be called "Apache"
>   *    nor may "Apache" appear in their names without prior written
>   *    permission of the Apache Group.
>   *
>   * 6. Redistributions of any form whatsoever must retain the following
>   *    acknowledgment:
>   *    "This product includes software developed by the Apache Group
>   *    for use in the Apache HTTP server project (http://www.apache.org/)."
>   *
>   * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
>   * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
>   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
>   * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
>   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
>   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
>   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
>   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
>   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
>   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
>   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
>   * OF THE POSSIBILITY OF SUCH DAMAGE.
>   * ====================================================================
>   *
>   * This software consists of voluntary contributions made by many
>   * individuals on behalf of the Apache Group and was originally based
>   * on public domain software written at the National Center for
>   * Supercomputing Applications, University of Illinois, Urbana-Champaign.
>   * For more information on the Apache Group and the Apache HTTP server
>   * project, please see <http://www.apache.org/>.
>   *
>   */
>  
>  /*
>   * mod_digest: MD5 digest authentication
>   * 
>   * by Alexei Kosut <ak...@nueva.pvt.k12.ca.us>
>   * and Ronald Tschal�r <ro...@innovation.ch>
>   * based on mod_auth, by Rob McCool and Robert S. Thau
>   *
>   * Requires the truerand library, available for instance from
>   * http://members.tripod.com/dr0nf/truerand/truerand.tar-gz
>   *
>   * Open Issues:
>   *   - qop=auth-int (when streams and trailer support available)
>   *   - one-time nonces (when shared-mem and semaphores available)
>   *   - nonce-count checking (when shared-mem and semaphores available)
>   *   - session management for algorithm=MD5-sess (when shared-mem and
>   *     semaphores available)
>   *   - nonce-format configurability
>   *   - Proxy-Authorization-Info header is set by this module, but is
>   *     currently ignored by mod_proxy (needs patch to mod_proxy)
>   *   - generating the secret takes a while (~ 8 seconds)
>   */
>  
> #include "httpd.h"
> #include "http_config.h"
> #include "http_core.h"
> #include "http_request.h"
> #include "http_log.h"
> #include "http_protocol.h"
> #include "ap_config.h"
> #include "ap_ctype.h"
> #include "util_uri.h"
> #include "util_md5.h"
>  
>  
>  /* from the truerand library */
>  
>  typedef struct {
>      long totalLength;
>      unsigned long h[5];
>      unsigned long w[80];
>  } SHS_CTX;
>  
>  extern void shsInit(SHS_CTX *ctx);
>  extern void shsUpdate(SHS_CTX *ctx, const unsigned char *s, unsigned int
>  n);
>  extern void shsFinal(SHS_CTX *ctx);
>  extern int  randbyte(void);
>  
>  
>  /* struct to hold the configuration info */
>  
>  typedef struct digest_config_struct {
>      ap_pool     *pool;
>      const char  *dir_name;
>      const char  *pwfile;
>      const char  *realm;
>      const char **qop_list;
>      long         nonce_lifetime;
>      const char  *nonce_format;
>      int          check_nc;
>      const char  *algorithm;
>      char        *uri_list;
>      const char  *ha1;
>  } digest_config_rec;
>  
>  
>  /* struct to hold a parsed Authorization header */
>  
>  enum hdr_sts { NO_HEADER, NOT_DIGEST, INVALID, VALID };
>  
>  typedef struct digest_header_struct {
>      const char           *scheme;
>      const char           *realm;
>      const char           *username;
>            char           *nonce;
>      const char           *uri;
>      const char           *digest;
>      const char           *algorithm;
>      const char           *cnonce;
>      const char           *opaque;
>      const char           *message_qop;
>      const char           *nonce_count;
>      time_t                nonce_time;
>      enum hdr_sts          auth_hdr_sts;
>      uri_components       *request_uri;
>  } digest_header_rec;
>  
>  
>  /* (mostly) nonce stuff */
>  
>  typedef union time_union {
>      time_t	  time;
>      unsigned char arr[sizeof(time_t)];
>  } time_rec;
>  
>  
> #define	DFLT_ALGORITHM	"MD5"
>  
> #define	DFLT_NONCE_LIFE	300L
> #define NEXTNONCE_DELTA	30
>  
>  
> #define NONCE_TIME_LEN	(((sizeof(time_t)+2)/3)*4)
> #define NONCE_HASH_LEN	40
> #define NONCE_LEN	(NONCE_TIME_LEN + NONCE_HASH_LEN)
>  
> #define	SECRET_LEN	20
>  
>  
>  static unsigned char secret[SECRET_LEN];
>  static int call_cnt = 0;
>  
>  static void initialize_secret(server_rec *s, pool *p)
>  {
>      int idx;
>  
>      /* because it takes so long we only want to do it once. */
>      if (++call_cnt != 2)
>  	return;
>  
>      /* this will increase the startup time of the server, unfortunately...
>       * (generating 20 bytes takes about 8 seconds)
>       */
>      ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s,
>  		 "generating secret for digest authentication ...");
>  
>      for (idx=0; idx<sizeof(secret); idx++)
>  	secret[idx] = (unsigned char) randbyte();
>  
>      ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s, "done");
>  }
>  
>  
>  /*
>   * configuration code
>   */
>  
>  static void *create_digest_dir_config(pool *p, char *dir)
>  {
>      digest_config_rec *conf;
>  
>      if (dir == NULL)  return NULL;
>  
>      conf = (digest_config_rec *) ap_pcalloc(p, sizeof(digest_config_rec));
>      if (conf) {
>  	conf->pool           = p;
>  	conf->qop_list       = ap_palloc(p, sizeof(char*));
>  	conf->qop_list[0]    = NULL;
>  	conf->nonce_lifetime = DFLT_NONCE_LIFE;
>  	conf->dir_name       = ap_pstrdup(p, dir);
>  	conf->algorithm      = DFLT_ALGORITHM;
>      }
>  
>      return conf;
>  }
>  
>  static const char *set_digest_file(cmd_parms *cmd, void *config,
>  				   const char *file)
>  {
>      ((digest_config_rec *) config)->pwfile = file;
>      return NULL;
>  }
>  
>  static const char *set_qop(cmd_parms *cmd, void *config, const char *op)
>  {
>      digest_config_rec *conf = (digest_config_rec *) config;
>      const char **tmp;
>      int cnt;
>  
>      if (!strcasecmp(op, "none")) {
>  	if (conf->qop_list[0] == NULL) {
>  	    conf->qop_list = ap_palloc(cmd->pool, 2 * sizeof(char*));
>  	    conf->qop_list[1] = NULL;
>  	}
>  	conf->qop_list[0] = "none";
>  	return NULL;
>      }
>  
>      if (!strcasecmp(op, "auth-int"))
>  	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, cmd->server,
>  		     "WARNING: qop `auth-int' currently only works correctly " \
>  		     "for responses with no entity");
>      else if (strcasecmp(op, "auth"))
>  	return ap_pstrcat(cmd->pool, "Unrecognized qop: ", op, NULL);
>  
>      for (cnt=0; conf->qop_list[cnt] != NULL; cnt++)
>  	;
>      tmp = ap_palloc(cmd->pool, (cnt+2)*sizeof(char*));
>      memcpy(tmp, conf->qop_list, cnt*sizeof(char*));
>      tmp[cnt]   = ap_pstrdup(cmd->pool, op);
>      tmp[cnt+1] = NULL;
>      conf->qop_list = tmp;
>  
>      return NULL;
>  }
>  
>  static const char *set_nonce_lifetime(cmd_parms *cmd, void *config,
>  				      const char *t)
>  {
>      char *endptr;
>      long  time;
>  
>      time = strtol(t, &endptr, 10);
>      if (endptr < (t+strlen(t))  &&  !ap_isspace(*endptr))
>  	return ap_pstrcat(cmd->pool, "Invalid time in AuthDigestNonceLifetime: ",
>  t, NULL);
>  
>      ((digest_config_rec *) config)->nonce_lifetime = time;
>      return NULL;
>  }
>  
>  static const char *set_nonce_format(cmd_parms *cmd, void *config,
>  				    const char *fmt)
>  {
>      ((digest_config_rec *) config)->nonce_format = fmt;
>      return "AuthDigestNonceFormat is not implemented (yet)";
>  }
>  
>  static const char *set_nc_check(cmd_parms *cmd, void *config, int flag)
>  {
>      ((digest_config_rec *) config)->check_nc = flag;
>      return "AuthDigestNcCheck is not implemented (yet)";
>  }
>  
>  static const char *set_algorithm(cmd_parms *cmd, void *config, const char
>  *alg)
>  {
>      if (!strcasecmp(alg, "MD5-sess"))
>  	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, cmd->server,
>  		     "WARNING: algorithm `MD5-sess' is currently not " \
>  		     "correctly implemented");
>      else if (strcasecmp(alg, "MD5"))
>  	return ap_pstrcat(cmd->pool, "Invalid algorithm in AuthDigestAlgorithm: ",
>  alg, NULL);
>  
>      ((digest_config_rec *) config)->algorithm = alg;
>      return NULL;
>  }
>  
>  static const char *set_uri_list(cmd_parms *cmd, void *config, const char
>  *uri)
>  {
>      digest_config_rec *c = (digest_config_rec *) config;
>      if (c->uri_list) {
>  	c->uri_list[strlen(c->uri_list)-1] = '\0';
>  	c->uri_list = ap_pstrcat(cmd->pool, c->uri_list, " ", uri, "\"", NULL);
>      }
>      else
>  	c->uri_list = ap_pstrcat(cmd->pool, ", domain=\"", uri, "\"", NULL);
>      return NULL;
>  }
>  
>  static const command_rec digest_cmds[] =
>  {
>      {"AuthDigestFile", set_digest_file, NULL, OR_AUTHCFG, TAKE1,
>       "The name of the file containing the usernames and password hashes"},
>      {"AuthDigestQop", set_qop, NULL, OR_AUTHCFG, ITERATE,
>       "A list of quality-of-protection options"},
>      {"AuthDigestNonceLifetime", set_nonce_lifetime, NULL, OR_AUTHCFG,
>  TAKE1,
>       "Maximum lifetime of the server nonce (seconds)"},
>      {"AuthDigestNonceFormat", set_nonce_format, NULL, OR_AUTHCFG, TAKE1,
>       "The format to use when generating the server nonce"},
>      {"AuthDigestNcCheck", set_nc_check, NULL, OR_AUTHCFG, FLAG,
>       "Whether or not to check the nonce-count sent by the client"},
>      {"AuthDigestAlgorithm", set_algorithm, NULL, OR_AUTHCFG, TAKE1,
>       "The algorithm used for the hash calculation"},
>      {"AuthDigestDomain", set_uri_list, NULL, OR_AUTHCFG, ITERATE,
>       "A list of URI's which belong to the same protection space as the
>  current URI"},
>      {NULL}
>  };
>  
>  module MODULE_VAR_EXPORT digest_module;
>  
>  
>  /*
>   * base-64 encoding helpers
>   */
>  
>  /* this is copied from util.c, with toascii folded into the table for
>  EBCDIC */
>  static const unsigned char pr2six[256] =
>  {
> #ifndef CHARSET_EBCDIC
>      /* ASCII table */
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
>      52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
>      64,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
>      15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
>      64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
>      41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
> #else /*CHARSET_EBCDIC*/
>      /* EBCDIC table */
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 64, 64, 64, 64, 64, 64,
>      64, 35, 36, 37, 38, 39, 40, 41, 42, 43, 64, 64, 64, 64, 64, 64,
>      64, 64, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 64,
>      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
>      64,  0,  1,  2,  3,  4,  5,  6,  7,  8, 64, 64, 64, 64, 64, 64,
>      64,  9, 10, 11, 12, 13, 14, 15, 16, 17, 64, 64, 64, 64, 64, 64,
>      64, 64, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 64,
>      52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
> #endif /*CHARSET_EBCDIC*/
>  };
>  
>  /* this is the same as ap_uudecode in util.c, but returns the length
>  instead
>   * of a pointer to the decoded data and takes a pointer to the decoded
>  buffer
>   * as a third parameter. Also, for EBCDIC machines the toebcdic[] on the
>  ouput
>   * is left out because we want a binary result.
>   */
>  static int base64decode(pool *p, const char *bufcoded, unsigned char
>  **bufplain)
>  {
>      int nbytesdecoded;
>      register const unsigned char *bufin;
>      register unsigned char *bufout;
>      register int nprbytes;
>  
>      /* Strip leading whitespace. */
>  
>      while (*bufcoded == ' ' || *bufcoded == '\t')
>  	bufcoded++;
>  
>      /* Figure out how many characters are in the input buffer.
>       * Allocate this many from the per-transaction pool for the result.
>       */
>      bufin = (const unsigned char *) bufcoded;
>      while (pr2six[*(bufin++)] <= 63);
>      nprbytes = (bufin - (const unsigned char *) bufcoded) - 1;
>      nbytesdecoded = ((nprbytes + 3) / 4) * 3;
>  
>      if (*bufplain == NULL)
>  	*bufplain = ap_palloc(p, nbytesdecoded + 1);
>      bufout = *bufplain;
>  
>      bufin = (const unsigned char *) bufcoded;
>  
>      while (nprbytes > 0) {
>  	*(bufout++) =
>  	    (unsigned char) (pr2six[bufin[0]] << 2 | pr2six[bufin[1]] >> 4);
>  	*(bufout++) =
>  	    (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
>  	*(bufout++) =
>  	    (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
>  	bufin += 4;
>  	nprbytes -= 4;
>      }
>  
>      if (nprbytes & 03) {
>  	if (pr2six[bufin[-2]] > 63)
>  	    nbytesdecoded -= 2;
>  	else
>  	    nbytesdecoded -= 1;
>      }
>      (*bufplain)[nbytesdecoded] = '\0';
>  
>      return nbytesdecoded;
>  }
>  
>  static const char six2pr[64] =
>  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
>  
>  /* This is similar to ap_uuencode except that it takes a length parameter
>   * (so we can encode binary data) and fixes a bug. Also note that no
>   * toascii[] is done on the input for EBCDIC (six2pr is automatically
>   * right).
>   */
>  static char *base64encode(pool *p, const unsigned char *bufplain, int
>  buflen)
>  {
>      int nbytescoded;
>      char *bufcoded;
>      register const unsigned char *bufin;
>      register char *bufout;
>      register int nsixbytes;
>  
>      /* Figure out how many characters are in the input buffer.
>       * Allocate this many from the per-transaction pool for the result.
>       */
>      nsixbytes = ((buflen + 2) / 3) * 4;
>  
>      bufcoded = ap_palloc(p, nsixbytes + 1);
>      bufcoded[nsixbytes] = '\0';
>  
>      bufin  = bufplain;
>      bufout = bufcoded;
>  
>      nbytescoded = 0;
>  
>      while (nbytescoded < (buflen - 2)) {
>  	*(bufout++) = six2pr[(bufin[0] >> 2) & 0x3F];
>  	*(bufout++) =
>  		six2pr[((bufin[1] >> 4) & 0x0F) | ((bufin[0] << 4) & 0x3F)];
>  	*(bufout++) =
>  		six2pr[((bufin[2] >> 6) & 0x03) | ((bufin[1] << 2) & 0x3F)];
>  	*(bufout++) = six2pr[bufin[2] & 0x3F];
>  	bufin += 3;
>  	nbytescoded += 3;
>      }
>  
>      if (nbytescoded < buflen) {
>  	*(bufout++) = six2pr[(bufin[0] >> 2) & 0x3F];
>  	if (nbytescoded < (buflen-1)) {
>  	    *(bufout++) =
>  		    six2pr[((bufin[1] >> 4) & 0x0F) | ((bufin[0] << 4) & 0x3F)];
>  	    *(bufout++) = six2pr[(bufin[1] << 2) & 0x3F];
>  	}
>  	else {
>  	    *(bufout++) = six2pr[(bufin[0] << 4) & 0x3F];
>  	}
>      }
>  
>      while (bufout < (bufcoded+nsixbytes))
>  	*(bufout++) = (unsigned char) '=';
>  
>      return bufcoded;
>  }
>  
>  
>  /*
>   * Nonce generation code
>   */
>  
>  /* The hash part of the nonce is a SHA-1 hash of the time, realm, opaque,
>   * and our secret.
>   */
>  static void gen_nonce_hash(char *hash, const char *time, const char *realm,
>  			   const char *opaque)
>  {
>      const char *hex = "0123456789abcdef";
>      SHS_CTX ctx;
>      int idx;
>  
>      shsInit(&ctx);
>      shsUpdate(&ctx, (unsigned char *) time, strlen(time));
>      shsUpdate(&ctx, (unsigned char *) realm, strlen(realm));
>      if (opaque)
>  	shsUpdate(&ctx, (unsigned char *) opaque, strlen(opaque));
>      shsUpdate(&ctx, secret, sizeof(secret));
>      shsFinal(&ctx);
>  
>      for (idx=0; idx<5; idx++) {
>  	/* like sprintf(hash, "%08lx", ctx.h[idx]), but faster */
>  	unsigned long x = ctx.h[idx];
>  	*hash++ = hex[(x >> 28) & 0xF];
>  	*hash++ = hex[(x >> 24) & 0xF];
>  	*hash++ = hex[(x >> 20) & 0xF];
>  	*hash++ = hex[(x >> 16) & 0xF];
>  	*hash++ = hex[(x >> 12) & 0xF];
>  	*hash++ = hex[(x >>  8) & 0xF];
>  	*hash++ = hex[(x >>  4) & 0xF];
>  	*hash++ = hex[x & 0xF];
>      }
>  
>      *hash++ = '\0';
>  }
>  
>  
>  /* The nonce has the format b64(time)+hash .
>   */
>  static const char *gen_nonce(pool *p, time_t now, const char *realm,
>  			     const char *opaque)
>  {
>      char *nonce = ap_palloc(p, NONCE_LEN+1);
>      time_rec t;
>  
>      t.time = now;
>      memcpy(nonce, base64encode(p, t.arr, sizeof(t.arr)), NONCE_TIME_LEN+1);
>      gen_nonce_hash(nonce+NONCE_TIME_LEN, nonce, realm, opaque);
>  
>      return nonce;
>  }
>  
>  
>  /*
>   * MD5-sess code.
>   *
>   * If you want to use algorithm=MD5-sess you must write get_userpw_hash()
>   * yourself (see below).
>   */
>  
>  /*
>   * get_userpw_hash() will be called each time a new session needs to be
>   * generated and is expected to return the equivalent of
>   *
>   * ap_md5(r->pool,
>   *        ap_pstrcat(r->pool, username, ":", ap_auth_name(r), ":", passwd))
>   *
>   * You must implement this yourself, and will probably consist of code
>   * contacting the password server and retrieving the hash from it.
>   *
>   * TBD: This function should probably be in a seperate source file so that
>   * people need no modify mod_digest.c each time a new release comes out.
>   */
>  static const char *get_userpw_hash(const request_rec *r,
>  				   const digest_header_rec *resp,
>  				   const digest_config_rec *conf)
>  {
>      /* for now, just get it from pwfile */
>      return conf->ha1;
>  }
>  
>  
>  static const char *get_session(const request_rec *r,
>  			       const digest_header_rec *resp,
>  			       const digest_config_rec *conf)
>  {
>      const char *ha1 = NULL, *urp;
>  
>      /* get ha1 from session list - TBD */
>  
>      /* generate new session if necessary */
>      if (ha1 == NULL) {
>  	urp = get_userpw_hash(r, resp, conf);
>  	ha1 = ap_md5(r->pool,
>  		     (unsigned char *) ap_pstrcat(r->pool, ha1, ":", resp->nonce,
>  						  ":", resp->cnonce, NULL));
>      }
>  
>      return ha1;
>  }
>  
>  
>  static void clear_session(const request_rec *r, const digest_header_rec
>  *resp,
>  			  const digest_config_rec *conf)
>  {
>      /* TBD */
>  }
>  
>  
>  /*
>   * Authorization challenge generation code (for WWW-Authenticate)
>   */
>  
>  static const char *guess_domain(pool *p, const char *uri, const char
>  *filename,
>  				const char *dir)
>  {
>      size_t u_len = strlen(uri), f_len = strlen(filename), d_len =
>  strlen(dir);
>      const char *u, *f;
>  
>  
>      /* Because of things like mod_alias and mod_rewrite and the fact that
>       * protection is often on a directory basis (not a location basis) it
>       * is hard to determine the uri to put in the domain attribute.
>       *
>       * What we do is the following: first we see if the directory is
>       * a prefix for the uri - if this is the case we assume that therefore
>       * a <Location> directive was protecting this uri and we can use it
>       * for the domain.
>       */
>      if (u_len >= d_len  &&  !memcmp(uri, dir, d_len))
>  	return dir;
>  
>      /* Now we check for <Files ...>, and if we find one we send back a
>       * dummy uri - this is the only way to specify that the protection
>       * space only covers a single uri.
>       */
>      if (dir[0] != '/')
>  	return "http://0.0.0.0/";
>  
>      /* Next we find the largest common common suffix of the request-uri
>       * and the final file name, ignoring any extensions; this gives us a
>       * hint as to where any rewriting could've occured (assuming that some
>       * prefix of the uri is rewritten, not a suffix).
>       */
>      u = uri + u_len - 1;	/* strip any extension */
>      while (u > uri  &&  *u != '/')  u--;
>      while (*u  &&  *u != '.')  u++;
>      if (*u == '.')  u--;
>      if (*u == '/')  u--;
>  
>      f = filename + f_len - 1;	/* strip any extension */
>      while (f > filename  &&  *f != '/')  f--;
>      while (*f  &&  *f != '.')  f++;
>      if (*f == '.')  f--;
>      if (*f == '/')  f--;
>  
>      while (*f == *u  &&  f > filename  &&  u > uri)  u--, f--;
>      f++; u++;
>  
>      while (*f  &&  *f != '/')  f++, u++;	/* suffix must start with / */
>  
>      /* Now, if the directory reaches into this common suffix then we can
>       * take the uri with the same reach.
>       */
>      if ((f-filename) < d_len) {
>  	char *tmp = ap_pstrdup(p, uri);
>  	tmp[(u-uri)+(d_len-(f-filename))] = '\0';
>  	return tmp;
>      }
>  
>      return "";	/* give up */
>  }
>  
>  
>  static void note_digest_auth_failure(request_rec *r,
>  				     const digest_config_rec *conf,
>  				     const digest_header_rec *resp, int stale)
>  {
>      const char *qop, *opaque, *opaque_param, *domain;
>      int   cnt;
>  
>      if (conf->qop_list[0] == NULL)
>  	qop = ", qop=\"auth\"";
>      else if (!strcasecmp(conf->qop_list[0], "none"))
>  	qop = "";
>      else {
>  	qop = ap_pstrcat(r->pool, ", qop=\"", conf->qop_list[0], NULL);
>  	for (cnt=1; conf->qop_list[cnt] != NULL; cnt++)
>  	    qop = ap_pstrcat(r->pool, qop, ",", conf->qop_list[cnt], NULL);
>  	qop = ap_pstrcat(r->pool, qop, "\"", NULL);
>      }
>  
>      if (!stale  &&  !strcasecmp(conf->algorithm, "MD5-sess"))
>  	clear_session(r, resp, conf);
>  
>      if (resp->opaque == NULL) {
>  	/* new client */
>  	if (conf->check_nc  ||  !strcasecmp(conf->algorithm, "MD5-sess")) {
>  	    /* so generate a unique opaque string */
>  	    /* TBD when nonce-count checking is impl */
>  	    opaque = "";
>  	}
>  	else {
>  	    /* opaque not needed */
>  	    opaque = "";
>  	}
>      }
>      else {
>  	opaque = resp->opaque;
>      }
>      if (opaque[0])
>  	opaque_param = ap_pstrcat(r->pool, ", opaque=\"", opaque, "\"", NULL);
>      else
>  	opaque_param = NULL;
>  
>      /* setup domain attribute. We want to send this attribute wherever
>       * possible so that the client won't send the Authorization header
>       * unneccessarily (it's usually > 200 bytes!).
>       */
>  
>      if (conf->uri_list)
>  	domain = conf->uri_list;
>      else {
>  	/* They didn't specify any domain, so let's guess at it */
>  	domain = guess_domain(r->pool, resp->request_uri->path, r->filename,
>  			      conf->dir_name);
>  	if (domain[0] == '/'  &&  domain[1] == '\0')
>  	    domain = "";	/* "/" is the default, so no need to send it */
>  	else
>  	    domain = ap_pstrcat(r->pool, ", domain=\"", domain, "\"", NULL);
>      }
>  
>      ap_table_mergen(r->err_headers_out,
>  		    r->proxyreq ? "Proxy-Authenticate" : "WWW-Authenticate",
>  		    ap_psprintf(r->pool, "Digest realm=\"%s\", nonce=\"%s\", "
>  					 "algorithm=%s%s%s%s%s",
>  				ap_auth_name(r),
>  				gen_nonce(r->pool, r->request_time,
>  					  conf->realm, opaque),
>  				conf->algorithm,
>  				opaque_param ? opaque_param : "",
>  				domain ? domain : "",
>  				stale ? ", stale=true" : "",
>  				qop));
>  }
>  
>  
>  /*
>   * Authorization header parser code
>   */
>  
>  /* Parse the Authorization header, if it exists */
>  static int get_digest_rec(request_rec *r, digest_header_rec *resp)
>  {
>      const char *auth_line = ap_table_get(r->headers_in,
>  					 r->proxyreq ? "Proxy-Authorization"
>  						     : "Authorization");
>      size_t l;
>      int vk = 0, vv = 0;
>      char *key, *value;
>  
>  
>      if (!auth_line) {
>  	resp->auth_hdr_sts = NO_HEADER;
>  	return !OK;
>      }
>  
>      resp->scheme = ap_getword(r->pool, &auth_line, ' ');
>      if (strcasecmp(resp->scheme, "Digest")) {
>  	resp->auth_hdr_sts = NOT_DIGEST;
>  	return !OK;
>      }
>  
>      l = strlen(auth_line);
>  
>      /* Note we don't allocate l + 1 bytes for these deliberately, because
>       * there has to be at least one '=' character for either of these two
>       * new strings to be terminated.  That takes care of the need for +1.
>       */
>      key   = ap_palloc(r->pool, l);
>      value = ap_palloc(r->pool, l);
>  
>      while (auth_line[0] != '\0') {
>  
>  	/* find key */
>  
>  	while (ap_isspace(auth_line[0])) auth_line++;
>  	vk = 0;
>  	while (auth_line[0] != '='  &&  auth_line[0] != ','  &&
>  	       auth_line[0] != '\0'  &&  !ap_isspace(auth_line[0]))
>  	    key[vk++] = *auth_line++;
>  	key[vk] = '\0';
>  	while (ap_isspace(auth_line[0])) auth_line++;
>  
>  	/* find value */
>  
>  	if (auth_line[0] == '=') {
>  	    auth_line++;
>  	    while (ap_isspace(auth_line[0])) auth_line++;
>  
>  	    vv = 0;
>  	    if (auth_line[0] == '\"') {		/* quoted string */
>  		auth_line++;
>  		while (auth_line[0] != '\"'  &&  auth_line[0] != '\0') {
>  		    if (auth_line[0] == '\\'  &&  auth_line[1] != '\0')
>  			auth_line++;		/* escaped char */
>  		    value[vv++] = *auth_line++;
>  		}
>  		if (auth_line[0] != '\0') auth_line++;
>  	    }
>  	    else {				 /* token */
>  		while (auth_line[0] != ','  &&  auth_line[0] != '\0'  &&
>  		       !ap_isspace(auth_line[0]))
>  		    value[vv++] = *auth_line++;
>  	    }
>  	    value[vv] = '\0';
>  	}
>  
>  	while (auth_line[0] != ','  &&  auth_line[0] != '\0')  auth_line++;
>  	if (auth_line[0] != '\0') auth_line++;
>  
>  	if (!strcasecmp(key, "username"))
>  	    resp->username = ap_pstrdup(r->pool, value);
>  	else if (!strcasecmp(key, "realm"))
>  	    resp->realm = ap_pstrdup(r->pool, value);
>  	else if (!strcasecmp(key, "nonce"))
>  	    resp->nonce = ap_pstrdup(r->pool, value);
>  	else if (!strcasecmp(key, "uri"))
>  	    resp->uri = ap_pstrdup(r->pool, value);
>  	else if (!strcasecmp(key, "response"))
>  	    resp->digest = ap_pstrdup(r->pool, value);
>  	else if (!strcasecmp(key, "algorithm"))
>  	    resp->algorithm = ap_pstrdup(r->pool, value);
>  	else if (!strcasecmp(key, "cnonce"))
>  	    resp->cnonce = ap_pstrdup(r->pool, value);
>  	else if (!strcasecmp(key, "opaque"))
>  	    resp->opaque = ap_pstrdup(r->pool, value);
>  	else if (!strcasecmp(key, "qop"))
>  	    resp->message_qop = ap_pstrdup(r->pool, value);
>  	else if (!strcasecmp(key, "nc"))
>  	    resp->nonce_count = ap_pstrdup(r->pool, value);
>      }
>  
>      if (!resp->username || !resp->realm || !resp->nonce || !resp->uri ||
>  	!resp->digest) {
>  	resp->auth_hdr_sts = INVALID;
>  	return !OK;
>      }
>  
>      resp->auth_hdr_sts = VALID;
>      return OK;
>  }
>  
>  
>  /*
>   * Nonce-count handling code
>   */
>  
>  static int inc_nc(const server_rec *server, const char *realm,
>  		  const char *opaque)
>  {
>      /* TBD */
>      return 0;
>  }
>  
>  
>  /* Because the browser may preemptively send auth info, incrementing the
>   * nonce-count when it does, and because the client does not get notified
>   * if the URI didn't need authentication after all, we need to be sure to
>   * update the nonce-count each time we receive an Authorization header no
>   * matter what the final outcome of the request.
>   *
>   * Note that this must be called after mod_proxy had its go so that
>   * r->proxyreq is set correctly.
>   */
>  static int update_nonce_count(request_rec *r)
>  {
>      digest_header_rec *resp;
>      int res;
>  
>      if (!ap_is_initial_req(r))
>  	return DECLINED;
>  
>      resp = ap_pcalloc(r->pool, sizeof(digest_header_rec));
>      res = get_digest_rec(r, resp);
>      ap_set_module_config(r->request_config, &digest_module, resp);
>  
>      if (res == OK)
>  	inc_nc(r->server, resp->realm, resp->opaque);
>  
>      return DECLINED;
>  }
>  
>  
>  /*
>   * Authorization header verification code
>   */
>  
>  static const char *get_hash(request_rec *r, const char *user,
>  			    const char *realm, const char *auth_pwfile)
>  {
>      configfile_t *f;
>      char l[MAX_STRING_LEN];
>      const char *rpw;
>      char *w, *x;
>  
>      if (!(f = ap_pcfg_openfile(r->pool, auth_pwfile))) {
>  	ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
>  		      "Could not open password file: %s", auth_pwfile);
>  	return NULL;
>      }
>      while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {
>  	if ((l[0] == '#') || (!l[0]))
>  	    continue;
>  	rpw = l;
>  	w = ap_getword(r->pool, &rpw, ':');
>  	x = ap_getword(r->pool, &rpw, ':');
>  
>  	if (x && w && !strcmp(user, w) && !strcmp(realm, x)) {
>  	    ap_cfg_closefile(f);
>  	    return ap_pstrdup(r->pool, rpw);
>  	}
>      }
>      ap_cfg_closefile(f);
>      return NULL;
>  }
>  
>  static int check_nc(const request_rec *r, const digest_header_rec *resp,
>  		    const digest_config_rec *conf)
>  {
>      int nc, my_nc = inc_nc(r->server, resp->realm, resp->opaque);
>  
>      if (conf->check_nc) {
>  	const char *snc = resp->nonce_count;
>  	char *endptr;
>  
>  	nc = strtol(snc, &endptr, 10);
>  	if (endptr < (snc+strlen(snc))  &&  !ap_isspace(*endptr)) {
>  	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
>  			  "invalid nc %s received - not a number", snc);
>  	    return !OK;
>  	}
>  
>  	if (nc != my_nc)
>  	    return !OK;
>      }
>  
>      return OK;
>  }
>  
>  static int check_nonce(request_rec *r, digest_header_rec *resp,
>  		       const digest_config_rec *conf)
>  {
>      double dt;
>      time_rec nonce_time;
>      unsigned char *t;
>      char tmp, hash[NONCE_HASH_LEN+1];
>  
>      if (strlen(resp->nonce) != NONCE_LEN) {
>  	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
>  		      "invalid nonce %s received - length is not %d",
>  		      resp->nonce, NONCE_LEN);
>  	note_digest_auth_failure(r, conf, resp, 0);
>  	return AUTH_REQUIRED;
>      }
>  
>      tmp = resp->nonce[NONCE_TIME_LEN];
>      resp->nonce[NONCE_TIME_LEN] = '\0';
>      t = nonce_time.arr;
>      base64decode(r->pool, resp->nonce, &t);
>      gen_nonce_hash(hash, resp->nonce, conf->realm, resp->opaque);
>      resp->nonce[NONCE_TIME_LEN] = tmp;
>      resp->nonce_time = nonce_time.time;
>  
>      if (strcmp(hash, resp->nonce+NONCE_TIME_LEN)) {
>  	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
>  		      "invalid nonce %s received - hash is not %s", resp->nonce,
>  		      hash);
>  	note_digest_auth_failure(r, conf, resp, 0);
>  	return AUTH_REQUIRED;
>      }
>  
>      dt = difftime(r->request_time, nonce_time.time);
>      if (dt < 0) {
>  	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
>  		      "invalid nonce %s received - user attempted time travel",
>  		      resp->nonce);
>  	note_digest_auth_failure(r, conf, resp, 0);
>  	return AUTH_REQUIRED;
>      }
>  
>      if (conf->nonce_lifetime > 0) {
>  	if (dt > conf->nonce_lifetime) {
>  	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, r,
>  			  "user %s: nonce expired - sending new nonce",
>  			  r->connection->user);
>  	    note_digest_auth_failure(r, conf, resp, 1);
>  	    return AUTH_REQUIRED;
>  	}
>      }
>      else if (conf->nonce_lifetime == 0) {
>  	/* TBD */
>      }
>      /* else (lifetime < 0) => never expires */
>  
>      return OK;
>  }
>  
>  /* The actual MD5 code... whee */
>  
>  static const char *old_digest(const request_rec *r,
>  			      const digest_header_rec *resp, const char *ha1)
>  {
>      const char *ha2;
>  
>      /* rfc-2069 */
>      ha2 = ap_md5(r->pool, (unsigned char *)ap_pstrcat(r->pool, r->method,
>  ":",
>  						      resp->uri, NULL));
>      return ap_md5(r->pool,
>  		  (unsigned char *)ap_pstrcat(r->pool, ha1, ":", resp->nonce,
>  					      ":", ha2, NULL));
>  }
>  
>  static const char *new_digest(const request_rec *r,
>  			      const digest_header_rec *resp,
>  			      const digest_config_rec *conf)
>  {
>      const char *ha1, *ha2, *a2;
>  
>      /* draft-ietf-http-authentication-03 */
>      if (resp->algorithm  &&  !strcasecmp(resp->algorithm, "MD5-sess"))
>  	ha1 = get_session(r, resp, conf);
>      else
>  	ha1 = conf->ha1;
>  
>      if (resp->message_qop  &&  !strcasecmp(resp->message_qop, "auth-int"))
>  	a2 = ap_pstrcat(r->pool, r->method, ":", resp->uri, ":",
>  			ap_md5(r->pool, (unsigned char*) ""), NULL); /* TBD */
>      else
>  	a2 = ap_pstrcat(r->pool, r->method, ":", resp->uri, NULL);
>      ha2 = ap_md5(r->pool, (unsigned char *)a2);
>  
>      return ap_md5(r->pool,
>  		  (unsigned char *)ap_pstrcat(r->pool, ha1, ":", resp->nonce,
>  					      ":", resp->nonce_count, ":",
>  					      resp->cnonce, ":",
>  					      resp->message_qop, ":", ha2,
>  					      NULL));
>  }
>  
>  
>  static const char *unescape(pool *p, const char *str)
>  {
>      char *bs = strchr(str, '\\'), *new, *pos;
>      if (!bs  ||  bs[1] == '\0')  return str;
>  
>      new = ap_palloc(p, strlen(str));
>      memcpy(new, str, (bs-str));
>      pos = new + (bs-str);
>      bs++;
>      while (*bs) {
>  	if (*bs == '\\') bs++;
>  	*pos++ = *bs++;
>      }
>  
>      *pos = '\0';
>      return new;
>  }
>  
>  
>  /* These functions return 0 if client is OK, and proper error status
>   * if not... either AUTH_REQUIRED, if we made a check, and it failed, or
>   * SERVER_ERROR, if things are so totally confused that we couldn't
>   * figure out how to tell if the client is authorized or not.
>   *
>   * If they return DECLINED, and all other modules also decline, that's
>   * treated by the server core as a configuration error, logged and
>   * reported as such.
>   */
>  
>  /* Determine user ID, and check if the attributes are correct, if it
>   * really is that user, if the nonce is correct, etc.
>   */
>  
>  static int authenticate_digest_user(request_rec *r)
>  {
>      digest_config_rec *conf;
>      digest_header_rec *resp;
>      request_rec       *main;
>      conn_rec          *conn = r->connection;
>      const char        *t;
>      int                res;
>  
>  
>      /* do we require Digest auth for this URI? */
>  
>      if (!(t = ap_auth_type(r)) || strcasecmp(t, "Digest"))
>  	return DECLINED;
>  
>      if (!ap_auth_name(r)) {
>  	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
>  		      "need AuthName: %s", r->uri);
>  	return SERVER_ERROR;
>      }
>  
>  
>      /* get the client response and real uri */
>  
>      main = r;
>      while (main->main != NULL)  main = main->main;
>      while (main->prev != NULL)  main = main->prev;
>      resp = (digest_header_rec *) ap_get_module_config(main->request_config,
>  						      &digest_module);
>      resp->request_uri = &main->parsed_uri;
>  
>  
>      /* set up our conf */
>  
>      conf = (digest_config_rec *) ap_get_module_config(r->per_dir_config,
>  						      &digest_module);
>      if (!conf->realm)
>  	conf->realm = unescape(conf->pool, ap_auth_name(r));
>  
>  
>      /* check for existence and syntax of Auth header */
>  
>      if (resp->auth_hdr_sts != VALID) {
>  	if (resp->auth_hdr_sts == NOT_DIGEST)
>  	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
>  			  "client used wrong authentication scheme `%s': %s",
>  			  resp->scheme, r->uri);
>  	else if (resp->auth_hdr_sts == INVALID)
>  	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
>  			  "missing user, realm, nonce, uri, or digest in "
>  			  "authorization header: %s", r->uri);
>  	/* else (resp->auth_hdr_sts == NO_HEADER) */
>  	note_digest_auth_failure(r, conf, resp, 0);
>  	return AUTH_REQUIRED;
>      }
>  
>      r->connection->user = (char *) resp->username;
>      r->connection->ap_auth_type = "Digest";
>  
>  
>      /* check the auth attributes */
>  
>      if (strcmp(resp->uri, resp->request_uri->path)) {
>  	uri_components *r_uri = resp->request_uri, d_uri;
>  	ap_parse_uri_components(r->pool, resp->uri, &d_uri);
>  
>  	if ((d_uri.hostname  &&  d_uri.hostname[0] != '\0'  &&
>  	     strcasecmp(d_uri.hostname, r->server->server_hostname))  ||
>  	    (d_uri.port_str  &&  d_uri.port != r->server->port)  ||
>  	    (!d_uri.port_str  &&  r->server->port != 80)  ||
>  	    strcmp(d_uri.path, r_uri->path)  ||
>  	    (d_uri.query != r_uri->query &&
>  	     (!d_uri.query || !r_uri->query ||
>  	      strcmp(d_uri.query, r_uri->query)))
>  	    ) {
>  	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
>  			  "uri mismatch - <%s> does not match request-uri <%s>",
>  			  resp->uri,
>  			  ap_unparse_uri_components(r->pool, r_uri, 0));
>  	    return BAD_REQUEST;
>  	}
>      }
>  
>      if (strcmp(resp->realm, conf->realm)) {
>  	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
>  		      "realm mismatch - got `%s' but expected `%s'",
>  		      resp->realm, conf->realm);
>  	note_digest_auth_failure(r, conf, resp, 0);
>  	return AUTH_REQUIRED;
>      }
>  
>      if (resp->algorithm != NULL  &&
>  	strcasecmp(resp->algorithm, "MD5")  &&
>  	strcasecmp(resp->algorithm, "MD5-sess")) {
>  	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
>  		      "unknown algorithm `%s' received: %s",
>  		      resp->algorithm, r->uri);
>  	note_digest_auth_failure(r, conf, resp, 0);
>  	return AUTH_REQUIRED;
>      }
>  
>      if (!conf->pwfile)
>  	return DECLINED;
>  
>      if (!(conf->ha1 = get_hash(r, conn->user, conf->realm, conf->pwfile)))
>  {
>  	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
>  		      "user `%s' in realm `%s' not found: %s", conn->user,
>  		      conf->realm, r->uri);
>  	note_digest_auth_failure(r, conf, resp, 0);
>  	return AUTH_REQUIRED;
>      }
>  
>      if (resp->message_qop == NULL) {
>  	/* old (rfc-2069) style digest */
>  	if (strcmp(resp->digest, old_digest(r, resp, conf->ha1))) {
>  	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
>  			  "user %s: password mismatch: %s", conn->user, r->uri);
>  	    note_digest_auth_failure(r, conf, resp, 0);
>  	    return AUTH_REQUIRED;
>  	}
>      }
>      else {
>  	int match = 0, idx;
>  	for (idx=0; conf->qop_list[idx] != NULL; idx++) {
>  	    if (!strcasecmp(conf->qop_list[idx], resp->message_qop)) {
>  		match = 1;
>  		break;
>  	    }
>  	}
>  
>  	if (!match  &&
>  	    !(conf->qop_list[0] == NULL  &&
>  	      !strcasecmp(resp->message_qop, "auth"))) {
>  	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
>  			  "invalid qop `%s' received: %s",
>  			  resp->message_qop, r->uri);
>  	    note_digest_auth_failure(r, conf, resp, 0);
>  	    return AUTH_REQUIRED;
>  	}
>  
>  	if (strcmp(resp->digest, new_digest(r, resp, conf))) {
>  	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
>  			  "user %s: password mismatch: %s", conn->user, r->uri);
>  	    note_digest_auth_failure(r, conf, resp, 0);
>  	    return AUTH_REQUIRED;
>  	}
>      }
>  
>      if (check_nc(r, resp, conf) != OK) {
>  	note_digest_auth_failure(r, conf, resp, 0);
>  	return AUTH_REQUIRED;
>      }
>  
>      /* Note: this check is done last so that a "stale=true" can be
>         generated if the nonce is old */
>      if ((res = check_nonce(r, resp, conf)))
>  	return res;
>  
>      return OK;
>  }
>  
>  
>  /*
>   * Checking ID
>   */
>  
>  static int digest_check_auth(request_rec *r)
>  {
>      const char *user = r->connection->user;
>      int m = r->method_number;
>      int method_restricted = 0;
>      register int x;
>      const char *t;
>      char *w;
>      const array_header *reqs_arr;
>      require_line *reqs;
>  
>      if (!(t = ap_auth_type(r)) || strcasecmp(t, "Digest"))
>  	return DECLINED;
>  
>      reqs_arr = ap_requires(r);
>      /* If there is no "requires" directive, 
>       * then any user will do.
>       */
>      if (!reqs_arr)
>  	return OK;
>      reqs = (require_line *) reqs_arr->elts;
>  
>      for (x = 0; x < reqs_arr->nelts; x++) {
>  
>  	if (!(reqs[x].method_mask & (1 << m)))
>  	    continue;
>  
>  	method_restricted = 1;
>  
>  	t = reqs[x].requirement;
>  	w = ap_getword(r->pool, &t, ' ');
>  	if (!strcasecmp(w, "valid-user"))
>  	    return OK;
>  	else if (!strcasecmp(w, "user")) {
>  	    while (t[0]) {
>  		w = ap_getword_conf(r->pool, &t);
>  		if (!strcmp(user, w))
>  		    return OK;
>  	    }
>  	}
>  	else
>  	    return DECLINED;
>      }
>  
>      if (!method_restricted)
>  	return OK;
>  
>      note_digest_auth_failure(r, 
>  	(digest_config_rec *) ap_get_module_config(r->per_dir_config,
>  						   &digest_module),
>  	(digest_header_rec *) ap_get_module_config(r->request_config,
>  						   &digest_module),
>  	0);
>      return AUTH_REQUIRED;
>  }
>  
>  
>  /*
>   * Authorization-Info header code
>   */
>  
> #ifdef SEND_DIGEST
>  static const char *hdr(const table *tbl, const char *name)
>  {
>      const char *val = ap_table_get(tbl, name);
>      if (val)
>  	return val;
>      else
>  	return "";
>  }
> #endif
>  
>  static int add_auth_info(request_rec *r)
>  {
>      const digest_config_rec *conf =
>  		(digest_config_rec *) ap_get_module_config(r->per_dir_config,
>  							   &digest_module);
>      const digest_header_rec *resp =
>  		(digest_header_rec *) ap_get_module_config(r->request_config,
>  							   &digest_module);
>      char *ai = NULL, *digest = NULL, *nextnonce = "";
>  
>      if (resp == NULL  ||  conf == NULL)
>  	return OK;
>  
>  
>      /* rfc-2069 digest
>       */
>      if (resp->message_qop == NULL) {
>  	/* old client, so calc rfc-2069 digest */
>  
> #ifdef SEND_DIGEST
>  	/* most of this totally bogus because the handlers don't set the
>  	 * headers until the final handler phase (I wonder why this phase
>  	 * is called fixup when there's almost nothing you can fix up...)
>  	 *
>  	 * Because it's basically impossible to get this right (e.g. the
>  	 * Content-length is never set yet when we get here, and we can't
>  	 * calc the entity hash) it's best to just leave this #def'd out.
>  	 */
>  	char *entity_info =
>  	    ap_md5(r->pool,
>  		   (unsigned char *) ap_pstrcat(r->pool,
>  		       ap_unparse_uri_components(r->pool,
>  						 resp->request_uri, 0), ":",
>  		       r->content_type ? r->content_type : ap_default_type(r), ":",
>  		       hdr(r->headers_out, "Content-Length"), ":",
>  		       r->content_encoding ? r->content_encoding : "", ":",
>  		       hdr(r->headers_out, "Last-Modified"), ":",
>  		       r->no_cache && !ap_table_get(r->headers_out, "Expires") ?
>  			    ap_gm_timestr_822(r->pool, r->request_time) :
>  			    hdr(r->headers_out, "Expires"),
>  		       NULL));
>  	digest =
>  	    ap_md5(r->pool,
>  		   (unsigned char *)ap_pstrcat(r->pool, conf->ha1, ":",
>  					       resp->nonce, ":",
>  					       r->method, ":",
>  					       ap_gm_timestr_822(r->pool, r->request_time), ":",
>  					       entity_info, ":",
>  					       ap_md5(r->pool, (unsigned char *) ""), /* H(entity) - TBD */
>  					       NULL));
> #endif
>      }
>  
>  
>      /* setup nextnonce
>       */
>      if (conf->nonce_lifetime > 0) {
>  	/* send nextnonce if current nonce will expire in less than 30 secs */
>  	if (difftime(r->request_time, resp->nonce_time) >
>  (conf->nonce_lifetime-NEXTNONCE_DELTA)) {
>  	    nextnonce = ap_pstrcat(r->pool, ", nextnonce=\"",
>  				   gen_nonce(r->pool, r->request_time,
>  					     conf->realm, resp->opaque),
>  				   "\"", NULL);
>  	}
>      }
>      else if (conf->nonce_lifetime == 0) {
>  	/* TBD */
>      }
>      /* else nonce never expires, hence no nextnonce */
>  
>  
>      /* do rfc-2069 digest
>       */
>      if (conf->qop_list[0]  &&  !strcasecmp(conf->qop_list[0], "none")  &&
>  	resp->message_qop == NULL) {
>  	/* use only RFC-2069 format */
>  	if (digest)
>  	    ai = ap_pstrcat(r->pool, "digest=\"", digest, "\"", nextnonce,NULL);
>  	else
>  	    ai = nextnonce;
>      }
>      else {
>  	const char *resp_dig, *ha1, *a2, *ha2;
>  
>  	/* calculate rspauth attribute
>  	 */
>  	if (resp->algorithm  &&  !strcasecmp(resp->algorithm, "MD5-sess"))
>  	    ha1 = get_session(r, resp, conf);
>  	else
>  	    ha1 = conf->ha1;
>  
>  	if (resp->message_qop  &&  !strcasecmp(resp->message_qop, "auth-int"))
>  	    a2 = ap_pstrcat(r->pool, ":", resp->uri, ":",
>  			    ap_md5(r->pool, (unsigned char *) ""), NULL); /* TBD */
>  	else
>  	    a2 = ap_pstrcat(r->pool, ":", resp->uri, NULL);
>  	ha2 = ap_md5(r->pool, (unsigned char *)a2);
>  
>  	resp_dig = ap_md5(r->pool,
>  		         (unsigned char *)ap_pstrcat(r->pool, ha1, ":",
>  						     resp->nonce, ":",
>  						     resp->nonce_count, ":",
>  						     resp->cnonce, ":",
>  						     resp->message_qop ?
>  							 resp->message_qop : "",
>  						     ":", ha2, NULL));
>  
>  	/* assemble Authentication-Info header
>  	 */
>  	ai = ap_pstrcat(r->pool,
>  			"rspauth=\"", resp_dig, "\"",
>  			nextnonce,
>  		        resp->cnonce ? ", cnonce=\"" : "",
>  		        resp->cnonce ? ap_escape_quotes(r->pool, resp->cnonce) :
>  					"",
>  		        resp->cnonce ? "\"" : "",
>  		        resp->nonce_count ? ", nc=" : "",
>  		        resp->nonce_count ? resp->nonce_count : "",
>  		        resp->message_qop ? ", qop=" : "",
>  		        resp->message_qop ? resp->message_qop : "",
>  			digest ? "digest=\"" : "",
>  			digest ? digest : "",
>  			digest ? "\"" : "",
>  			NULL);
>      }
>  
>      if (ai  &&  ai[0])
>  	ap_table_mergen(r->headers_out,
>  			r->proxyreq ? "Proxy-Authentication-Info" :
>  				      "Authentication-Info",
>  			ai);
>      return OK;
>  }
>  
>  
>  module MODULE_VAR_EXPORT digest_module =
>  {
>      STANDARD_MODULE_STUFF,
>      initialize_secret,		/* initializer */
>      create_digest_dir_config,	/* dir config creater */
>      NULL,			/* dir merger --- default is to override */
>      NULL,			/* server config */
>      NULL,			/* merge server config */
>      digest_cmds,		/* command table */
>      NULL,			/* handlers */
>      NULL,			/* filename translation */
>      authenticate_digest_user,	/* check_user_id */
>      digest_check_auth,		/* check auth */
>      NULL,			/* check access */
>      NULL,			/* type_checker */
>      add_auth_info,		/* fixups */
>      NULL,			/* logger */
>      NULL,			/* header parser */
>      NULL,			/* child_init */
>      NULL,			/* child_exit */
>      update_nonce_count		/* post read-request */
>  };
>  
>  ----- mod_digest.html -------------------------------------------------
>  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
>  <HTML>
>  <HEAD>
>  <TITLE>Apache module mod_digest</TITLE>
>  </HEAD>
>  
>  <!-- Background white, links blue (unvisited), navy (visited), red (active)
>  -->
>  <BODY
>   BGCOLOR="#FFFFFF"
>   TEXT="#000000"
>   LINK="#0000FF"
>   VLINK="#000080"
>   ALINK="#FF0000"
> >
>  <!--#include virtual="header.html" -->
>  <H1 ALIGN="CENTER">Module mod_digest</H1>
>  
>  This module is contained in the <CODE>mod_digest.c</CODE> file, and is
>  not compiled in by default. It is only available in Apache 1.1 and
>  later. It provides for user authentication using MD5 Digest
>  Authentication.
>  
>  
>  <MENU>
>  <LI><A HREF="#authdigestfile">AuthDigestFile</A>
>  <LI><A HREF="#authdigestqop">AuthDigestQop</A>
>  <LI><A HREF="#authdigestnoncelifetime">AuthDigestNonceLifetime</A>
>  <LI><A HREF="#authdigestnonceformat">AuthDigestNonceFormat</A>
>  <LI><A HREF="#authdigestnccheck">AuthDigestNcCheck</A>
>  <LI><A HREF="#authdigestalgorithm">AuthDigestAlgorithm</A>
>  <LI><A HREF="#authdigestdomain">AuthDigestDomain</A>
>  </MENU>
>  <HR>
>  
>  
>  <H2><A NAME="authdigestfile">AuthDigestFile</A></H2>
>  <A
>   HREF="directive-dict.html#Syntax"
>   REL="Help"
> ><STRONG>Syntax:</STRONG></A> AuthDigestFile <EM>filename</EM><BR>
>  <A
>   HREF="directive-dict.html#Context"
>   REL="Help"
> ><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
>  <A
>   HREF="directive-dict.html#Override"
>   REL="Help"
> ><STRONG>Override:</STRONG></A> AuthConfig<BR>
>  <A
>   HREF="directive-dict.html#Status"
>   REL="Help"
> ><STRONG>Status:</STRONG></A> Base<BR>
>  <A
>   HREF="directive-dict.html#Module"
>   REL="Help"
> ><STRONG>Module:</STRONG></A> mod_digest<BR>
>  
>  <P>The AuthDigestFile directive sets the name of a textual file containing
>  the list of users and encoded passwords for digest authentication.
>  <EM>Filename</EM> is the absolute path to the user file.
>  
>  <P>The digest file uses a special format. Files in this format can be
>  created using the "htdigest" utility found in the support/ subdirectory of
>  the Apache distribution.
>  
>  <HR>
>  
>  <H2><A NAME="authdigestqop">AuthDigestQop</A></H2>
>  <A
>   HREF="directive-dict.html#Syntax"
>   REL="Help"
> ><STRONG>Syntax:</STRONG></A> AuthDigestQop <EM>none | 1*{ auth | auth-int
> >}</EM><BR>
>  <A
>   HREF="directive-dict.html#Default"
>   REL="Help"
> ><STRONG>Default:</STRONG></A> <CODE>AuthDigestQop auth</CODE><BR>
>  <A
>   HREF="directive-dict.html#Context"
>   REL="Help"
> ><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
>  <A
>   HREF="directive-dict.html#Override"
>   REL="Help"
> ><STRONG>Override:</STRONG></A> AuthConfig<BR>
>  <A
>   HREF="directive-dict.html#Status"
>   REL="Help"
> ><STRONG>Status:</STRONG></A> Base<BR>
>  <A
>   HREF="directive-dict.html#Module"
>   REL="Help"
> ><STRONG>Module:</STRONG></A> mod_digest<BR>
>  <A
>   HREF="directive-dict.html#Compatibility"
>   REL="Help"
> ><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later
>  
>  <P>The AuthDigestQop directive determines the quality-of-protection to use.
>  <EM>auth</EM> will only do authentication (username/password);
>  <EM>auth-int</EM> is authentication plus integrity checking (an MD5 hash
>  of the entity is also made and checked); <EM>none</EM> will cause the
>  module to use the old RFC-2069 digest algorithm (which does not include
>  integrity checking). Both <EM>auth</em> and <EM>auth-int</EM> may be
>  specified, in which the case the browser will choose which of these to
>  use. <EM>none</EM> should only be used if the browser for some reason
>  does not like the challenge is receives otherwise.
>  
>  <P><STRONG><EM>auth-int</EM> is not implemented yet</STRONG>.
>  
>  <HR>
>  
>  <H2><A NAME="authdigestnoncelifetime">AuthDigestNonceLifetime</A></H2>
>  <A
>   HREF="directive-dict.html#Syntax"
>   REL="Help"
> ><STRONG>Syntax:</STRONG></A> AuthDigestNonceLifetime
> ><EM>&lt;time&gt;</EM><BR>
>  <A
>   HREF="directive-dict.html#Default"
>   REL="Help"
> ><STRONG>Default:</STRONG></A> <CODE>AuthDigestNonceLifetime 300</CODE><BR>
>  <A
>   HREF="directive-dict.html#Context"
>   REL="Help"
> ><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
>  <A
>   HREF="directive-dict.html#Override"
>   REL="Help"
> ><STRONG>Override:</STRONG></A> AuthConfig<BR>
>  <A
>   HREF="directive-dict.html#Status"
>   REL="Help"
> ><STRONG>Status:</STRONG></A> Base<BR>
>  <A
>   HREF="directive-dict.html#Module"
>   REL="Help"
> ><STRONG>Module:</STRONG></A> mod_digest<BR>
>  <A
>   HREF="directive-dict.html#Compatibility"
>   REL="Help"
> ><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later
>  
>  <P>The AuthDigestNonceLifetime directive controls how long the server
>  nonce is valid. When the client contacts the server using an expired
>  nonce the server will send back a 407 with <code>stale=true</code>. If
>  <EM>&lt;time&gt;</EM> is greater than 0 then it specifies the number of
>  seconds the nonce is valid; this should probably never be set to less
>  than 10 seconds. If <EM>&lt;time&gt;</EM> is less than 0 then the nonce
>  never expires.
>  
>  <!-- Not implemented yet
>  If <EM>&lt;time&gt;</EM> is 0 then the nonce may be used exactly once by
>  the client.
>  -->
>  
>  <HR>
>  <H2><A NAME="authdigestnonceformat">AuthDigestNonceFormat</A></H2>
>  <A
>   HREF="directive-dict.html#Syntax"
>   REL="Help"
> ><STRONG>Syntax:</STRONG></A> AuthDigestNonceFormat <EM>???</EM><BR>
>  <A
>   HREF="directive-dict.html#Default"
>   REL="Help"
> ><STRONG>Default:</STRONG></A> <CODE>AuthDigestNonceFormat ???</CODE><BR>
>  <A
>   HREF="directive-dict.html#Context"
>   REL="Help"
> ><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
>  <A
>   HREF="directive-dict.html#Override"
>   REL="Help"
> ><STRONG>Override:</STRONG></A> AuthConfig<BR>
>  <A
>   HREF="directive-dict.html#Status"
>   REL="Help"
> ><STRONG>Status:</STRONG></A> Base<BR>
>  <A
>   HREF="directive-dict.html#Module"
>   REL="Help"
> ><STRONG>Module:</STRONG></A> mod_digest<BR>
>  <A
>   HREF="directive-dict.html#Compatibility"
>   REL="Help"
> ><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later
>  
>  <P><STRONG>Not implemented yet.</STRONG>
>  <!--
>  <P>The AuthDigestNonceFormat directive determines how the nonce is
>  generated.
>  -->
>  
>  <HR>
>  <H2><A NAME="authdigestnccheck">AuthDigestNcCheck</A></H2>
>  <A
>   HREF="directive-dict.html#Syntax"
>   REL="Help"
> ><STRONG>Syntax:</STRONG></A> AuthDigestNcCheck <EM>On/Off</EM><BR>
>  <A
>   HREF="directive-dict.html#Default"
>   REL="Help"
> ><STRONG>Default:</STRONG></A> <CODE>AuthDigestNcCheck Off</CODE><BR>
>  <A
>   HREF="directive-dict.html#Context"
>   REL="Help"
> ><STRONG>Context:</STRONG></A> server config<BR>
>  <A
>   HREF="directive-dict.html#Override"
>   REL="Help"
> ><STRONG>Override:</STRONG></A> <EM>Not applicable</EM><BR>
>  <A
>   HREF="directive-dict.html#Status"
>   REL="Help"
> ><STRONG>Status:</STRONG></A> Base<BR>
>  <A
>   HREF="directive-dict.html#Module"
>   REL="Help"
> ><STRONG>Module:</STRONG></A> mod_digest<BR>
>  <A
>   HREF="directive-dict.html#Compatibility"
>   REL="Help"
> ><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later
>  
>  <P><STRONG>Not implemented yet.</STRONG>
>  <!--
>  <P>The AuthDigestNcCheck directive enables or disables the checking of the
>  nonce-count sent by the server.
>  
>  <P>While recommended from a security standpoint, turning this directive
>  On has one important performance implication. To check the nonce-count
>  *all* requests (which have an Authorization header, irrespective of
>  whether they require digest authentication) must be serialized through
>  a critical section. If the server is handling a large number of
>  requests which contain the Authorization header then this may noticeably
>  impact performance.
>  
>  -->
>  
>  <HR>
>  <H2><A NAME="authdigestalgorithm">AuthDigestAlgorithm</A></H2>
>  <A
>   HREF="directive-dict.html#Syntax"
>   REL="Help"
> ><STRONG>Syntax:</STRONG></A> AuthDigestAlgorithm <EM>MD5 |
> >MD5-sess</EM><BR>
>  <A
>   HREF="directive-dict.html#Default"
>   REL="Help"
> ><STRONG>Default:</STRONG></A> <CODE>AuthDigestAlgorithm MD5</CODE><BR>
>  <A
>   HREF="directive-dict.html#Context"
>   REL="Help"
> ><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
>  <A
>   HREF="directive-dict.html#Override"
>   REL="Help"
> ><STRONG>Override:</STRONG></A> AuthConfig<BR>
>  <A
>   HREF="directive-dict.html#Status"
>   REL="Help"
> ><STRONG>Status:</STRONG></A> Base<BR>
>  <A
>   HREF="directive-dict.html#Module"
>   REL="Help"
> ><STRONG>Module:</STRONG></A> mod_digest<BR>
>  <A
>   HREF="directive-dict.html#Compatibility"
>   REL="Help"
> ><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later
>  
>  <P>The AuthDigestAlgorithm directive selects the algorithm used to
>  calculate
>  the challenge and response hashes.
>  
>  <P><STRONG><EM>MD5-sess</EM> is not correctly implemented yet</STRONG>.
>  <!--
>  <P>To use <EM>MD5-sess</EM> you must first code up the
>  <VAR>get_userpw_hash()</VAR> function in <VAR>mod_digest.c</VAR> .
>  -->
>  
>  <HR>
>  <H2><A NAME="authdigestdomain">AuthDigestDomain</A></H2>
>  <A
>   HREF="directive-dict.html#Syntax"
>   REL="Help"
> ><STRONG>Syntax:</STRONG></A> AuthDigestDomain <EM>URI URI ...</EM><BR>
>  <A
>   HREF="directive-dict.html#Context"
>   REL="Help"
> ><STRONG>Context:</STRONG></A> directory, .htaccess<BR>
>  <A
>   HREF="directive-dict.html#Override"
>   REL="Help"
> ><STRONG>Override:</STRONG></A> AuthConfig<BR>
>  <A
>   HREF="directive-dict.html#Status"
>   REL="Help"
> ><STRONG>Status:</STRONG></A> Base<BR>
>  <A
>   HREF="directive-dict.html#Module"
>   REL="Help"
> ><STRONG>Module:</STRONG></A> mod_digest<BR>
>  <A
>   HREF="directive-dict.html#Compatibility"
>   REL="Help"
> ><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.4 and later
>  
>  <P>The AuthDigestDomain directive allows to specify which URIs are in the
>  same protection space (i.e. use the same realm and username/password
>  info). The specified URIs are prefixes, i.e. the client will assume that
>  all URIs "below" these are also protected by the same username/password.
>  This directive <em>should</em> be specified and contain at least the set
>  of root URIs for this space.  Omiting to do so will cause the client to
>  send the Authorization header for <em>every request</em> sent to this
>  server.  Apart from increasing the size of the request, it may also have
>  a detrimental effect on performance if "AuthDigestNcCheck" is on.
>  
>  <P>The URIs specified can also point to different servers, in which case
>  clients (which understand this) will then share username/password info
>  across multiple servers without prompting the user each time.
>  
>  
>  <HR>
>  
>  <H3>Using Digest Authentication</H3>
>  
>  <P>Using MD5 Digest authentication is very simple. Simply set up
>  authentication normally, using "AuthType Digest" and "AuthDigestFile"
>  instead of the normal "AuthType Basic" and "AuthUserFile". Then add a
>  "AuthDigestDomain" directive containing at least the root URIs for this
>  protection space.
>  
>  <P>MD5 authentication provides a more secure password system, but only
>  works with supporting browsers. As of this writing (September 1998), the
>  majority of browsers do not support digest authentication. Therefore, we
>  do not recommend using this feature on a large Internet site. However, for
>  personal and intra-net use, where browser users can be controlled, it is
>  ideal.
>  
>  <!--#include virtual="footer.html" -->
>  </BODY>
>  </HTML>
>  
>  ----- Confiration.tmpl (diff) -----------------------------------------
>  --- Configuration.tmpl.orig	Wed Sep 16 18:12:24 1998
>  +++ Configuration.tmpl	Thu Nov 19 23:03:31 1998
>  @@ -290,13 +290,6 @@
>   
>   AddModule modules/standard/mod_userdir.o
>   
>  -## The proxy module enables the server to act as a proxy for outside
>  -## http and ftp services. It's not as complete as it could be yet.
>  -## NOTE: You do not want this module UNLESS you are running a proxy;
>  -##       it is not needed for normal (origin server) operation.
>  -
>  -# AddModule modules/proxy/libproxy.a
>  -
>   ## The Alias module provides simple URL translation and redirection.
>   
>   AddModule modules/standard/mod_alias.o
>  @@ -330,6 +323,13 @@
>   ## secure Basic Auth used by the other modules.
>   
>   # AddModule modules/standard/mod_digest.o
>  +
>  +## The proxy module enables the server to act as a proxy for outside
>  +## http and ftp services. It's not as complete as it could be yet.
>  +## NOTE: You do not want this module UNLESS you are running a proxy;
>  +##       it is not needed for normal (origin server) operation.
>  +
>  +# AddModule modules/proxy/libproxy.a
>   
>   ## Optional response header manipulation modules. 
>   ##
>  ----- proxy_http.c (diff) ---------------------------------------------
>  --- proxy_http.c.orig	Mon Aug 31 18:12:25 1998
>  +++ proxy_http.c	Fri Nov 13 22:05:43 1998
>  @@ -428,6 +428,14 @@
>   	}
>   
>   	clear_connection(p, resp_hdrs);	/* Strip Connection hdrs */
>  +	if (r->status_line[0] <= '2') {
>  +	    ap_overlap_tables(r->headers_out, resp_hdrs, 0);
>  +	    resp_hdrs = r->headers_out;
>  +	}
>  +	else {
>  +	    ap_overlap_tables(r->err_headers_out, resp_hdrs, 0);
>  +	    resp_hdrs = r->err_headers_out;
>  +	}
>       }
>       else {
>   /* an http/0.9 response */
>  ----- proxy_ftp.c (diff) ----------------------------------------------
>  --- proxy_ftp.c.orig	Fri Aug 28 06:12:20 1998
>  +++ proxy_ftp.c	Tue Nov 17 18:40:18 1998
>  @@ -1107,7 +1107,7 @@
>       r->status = HTTP_OK;
>       r->status_line = "200 OK";
>   
>  -    resp_hdrs = ap_make_table(p, 2);
>  +    resp_hdrs = ap_copy_table(p, r->headers_out);
>       c->hdrs = resp_hdrs;
>   
>       if (parms[0] == 'd')
>  -----------------------------------------------------------------------

ciao...
-- 
Lars Eilebrecht                     - This is the tomorrow you worried about
sfx@unix-ag.org                        - yesterday. And now you know why.
http://www.home.unix-ag.org/sfx/