httpd-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Life is hard, and then you die." <ron...@innovation.ch>
Subject Re: Updated mod_digest (fwd)
Date Thu, 08 Jul 1999 07:02:24 GMT

Appended is the new mod_digest.c, mod_digest.html, and a patch to add
a new Rule to the config.

One issue I'm still wondering about is that of generating the inital
seed. Currently the decision whether to use /dev/random (or equivalent)
or the truerand library is made at compile time - anybody see a need
for it to be done at run time instead? The only reason I've hesitated
is because that means you the truerand library to build mod_digest,
even if you don't use it (but that argument is void if we include
the truerand library with Apache).

It would be easiest to distribute the truerand stuff with Apache. The
license part of it reads:

 * The authors of this software are Don Mitchell and Matt Blaze.
 *              Copyright (c) 1995, 1996 by AT&T.
 * Permission to use, copy, and modify this software without fee
 * is hereby granted, provided that this entire notice is included in
 * all copies of any software which is or includes a copy or
 * modification of this software and in all copies of the supporting
 * documentation for such software.

This sounds to me like it's compatible with the Apache license. I
don't know what to do about the notice "in all copies of the
supporting documentation" clause, though.

Btw., in case anybody is wondering about the note there:

 * This software may be subject to United States export controls.

This is not a problem - random number generators are not restricted.


  Cheers,

  Ronald


----- mod_digest.c -------------------------------------------------
/* ====================================================================
 * Copyright (c) 1995-1999 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
 *
 * Originally by Alexei Kosut <akosut@nueva.pvt.k12.ca.us>
 * Updated to draft-ietf-http-authentication-03 by
 *   Ronald Tschalär <ronald@innovation.ch>
 * based on mod_auth, by Rob McCool and Robert S. Thau
 *
 * Requires either /dev/random (or equivalent) or 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) if using the
 *     truerand library
 */

/* The section for the Configure script:
 * MODULE-DEFINITION-START
 * Name: digest_module
 * ConfigStart

    RULE_DEV_RANDOM=`./helpers/CutRule DEV_RANDOM $file`
    if [ "$RULE_DEV_RANDOM" = "default" ]; then
	if [ -r "/dev/random" ]; then
	    RULE_DEV_RANDOM="/dev/random"
	elif [ -r "/dev/urandom" ]; then
	    RULE_DEV_RANDOM="/dev/urandom"
	else
	    RULE_DEV_RANDOM="truerand"
	fi
    fi
    if [ "$RULE_DEV_RANDOM" = "truerand" ]; then
	echo "      (mod_digest) using truerand library for the random seed"
	LIBS="$LIBS -L/usr/local/lib -lrand"
    else
	echo "      (mod_digest) using $RULE_DEV_RANDOM for the random seed"
	CFLAGS="$CFLAGS -DDEV_RANDOM=$RULE_DEV_RANDOM"
    fi

 * ConfigEnd
 * MODULE-DEFINITION-END
 */

#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"
#include "ap_sha1.h"


/* struct to hold the configuration info */

typedef struct digest_config_struct {
    ap_pool     *pool;
    const char  *dir_name;
    const char  *pwfile;
    const char  *grpfile;
    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;
    int                   needed_auth;
} 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)
{
#ifdef	DEV_RANDOM
    FILE *rnd;
    size_t got, tot;
#else
    extern int randbyte(void);	/* from the truerand library */
    int idx;
#endif

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

    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s,
		 "generating secret for digest authentication ...");

#ifdef	DEV_RANDOM
#define	XSTR(x)	#x
#define	STR(x)	XSTR(x)
    if ((rnd = fopen(STR(DEV_RANDOM), "rb")) == NULL) {
	ap_log_error(APLOG_MARK, APLOG_CRIT, s,
		     "Couldn't open " STR(DEV_RANDOM));
	exit(EXIT_FAILURE);
    }
    if (setvbuf(rnd, NULL, _IONBF, 0) != 0) {
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_CRIT, s,
		     "Error trying to disable buffering for " STR(DEV_RANDOM));
	exit(EXIT_FAILURE);
    }
    for (tot=0; tot<sizeof(secret); tot += got) {
	if ((got = fread(secret+tot, 1, sizeof(secret)-tot, rnd)) < 1) {
	    ap_log_error(APLOG_MARK, APLOG_CRIT, s,
			 "Error reading " STR(DEV_RANDOM));
	    exit(EXIT_FAILURE);
	}
    }
    fclose(rnd);
#undef	STR
#undef	XSTR
#else	/* use truerand */
    /* this will increase the startup time of the server, unfortunately...
     * (generating 20 bytes takes about 8 seconds)
     */
    for (idx=0; idx<sizeof(secret); idx++)
	secret[idx] = (unsigned char) randbyte();
#endif	/* DEV_RANDOM */

    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_group_file(cmd_parms *cmd, void *config,
				  const char *file)
{
    ((digest_config_rec *) config)->grpfile = 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"},
    {"AuthDigestGroupFile", set_group_file, NULL, OR_AUTHCFG, TAKE1,
     "The name of the file containing the group names and members"},
    {"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";
    unsigned char sha1[20];
    AP_SHA1_CTX ctx;
    int idx;

    ap_SHA1Init(&ctx);
    ap_SHA1Update_binary(&ctx, (unsigned char *) time, strlen(time));
    ap_SHA1Update_binary(&ctx, (unsigned char *) realm, strlen(realm));
    if (opaque)
	ap_SHA1Update_binary(&ctx, (unsigned char *) opaque, strlen(opaque));
    ap_SHA1Update_binary(&ctx, secret, sizeof(secret));
    ap_SHA1Final(sha1, &ctx);

    for (idx=0; idx<20; idx++) {
	*hash++ = hex[sha1[idx] >> 4];
	*hash++ = hex[sha1[idx] & 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 not 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_white(r->pool, &auth_line);
    if (strcasecmp(resp->scheme, "Digest")) {
	resp->auth_hdr_sts = NOT_DIGEST;
	return !OK;
    }

    l = strlen(auth_line);

    key   = ap_palloc(r->pool, l+1);
    value = ap_palloc(r->pool, l+1);

    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. Furthermore this is a
 * convenient place to get the request-uri (before any subrequests etc
 * are initiated) and to initialize the request_config.
 *
 * 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));
    resp->request_uri = &r->parsed_uri;
    resp->needed_auth = 0;
    ap_set_module_config(r->request_config, &digest_module, resp);

    res = get_digest_rec(r, 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 mark */

    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->needed_auth = 1;


    /* 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 table *groups_for_user(request_rec *r, const char *user,
			      const char *grpfile)
{
    configfile_t *f;
    table *grps = ap_make_table(r->pool, 15);
    pool *sp;
    char l[MAX_STRING_LEN];
    const char *group_name, *ll, *w;

    if (!(f = ap_pcfg_openfile(r->pool, grpfile))) {
	ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
		      "Could not open group file: %s", grpfile);
	return NULL;
    }

    sp = ap_make_sub_pool(r->pool);

    while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {
	if ((l[0] == '#') || (!l[0]))
	    continue;
	ll = l;
	ap_clear_pool(sp);

	group_name = ap_getword(sp, &ll, ':');

	while (ll[0]) {
	    w = ap_getword_conf(sp, &ll);
	    if (!strcmp(w, user)) {
		ap_table_setn(grps, ap_pstrdup(r->pool, group_name), "in");
		break;
	    }
	}
    }

    ap_cfg_closefile(f);
    ap_destroy_pool(sp);
    return grps;
}


static int digest_check_auth(request_rec *r)
{
    const digest_config_rec *conf =
		(digest_config_rec *) ap_get_module_config(r->per_dir_config,
							   &digest_module);
    const char *user = r->connection->user;
    int m = r->method_number;
    int method_restricted = 0;
    register int x;
    const char *t, *w;
    table *grpstatus;
    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;

    if (conf->grpfile)
	grpstatus = groups_for_user(r, user, conf->grpfile);
    else
	grpstatus = NULL;

    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_white(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 if (!strcasecmp(w, "group")) {
	    if (!grpstatus)
		return DECLINED;

	    while (t[0]) {
		w = ap_getword_conf(r->pool, &t);
		if (ap_table_get(grpstatus, w))
		    return OK;
	    }
	}
	else {
	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
		"access to %s failed, reason: unknown require directive:"
		"\"%s\"", r->uri, reqs[x].requirement);
	    return DECLINED;
	}
    }

    if (!method_restricted)
	return OK;

    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
	"access to %s failed, reason: user %s not allowed access",
	r->uri, user);

    note_digest_auth_failure(r, conf,
	(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 || !resp->needed_auth || 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="#authdigestgroupfile">AuthDigestGroupFile</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="authdigestgroupfile">AuthDigestGroupFile</A></H2>
<A
 HREF="directive-dict.html#Syntax"
 REL="Help"
><STRONG>Syntax:</STRONG></A> AuthDigestGroupFile <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>
<A
 HREF="directive-dict.html#Compatibility"
 REL="Help"
><STRONG>Compatibility:</STRONG></A> Available in Apache 1.3.7 and later

<P>The AuthDigestGroupFile directive sets the name of a textual file
containing the list of groups and their members (user names).
<EM>Filename</EM> is the absolute path to the group file.

<P>Each line of the group file contains a groupname followed by a colon,
followed by the member usernames separated by spaces. Example:
<BLOCKQUOTE><CODE>mygroup: bob joe anne</CODE></BLOCKQUOTE>
Note that searching large text files is <EM>very</EM> inefficient.

<P>Security: make sure that the AuthGroupFile is stored outside the
document tree of the web-server; do <EM>not</EM> put it in the directory
that it protects. Otherwise, clients will be able to download the
AuthGroupFile.

<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.7 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 it 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.7 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.7 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.7 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.7 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.7 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"; also,
replace any "AuthGroupFile" with "AuthDigestGroupFile". 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 1999), the only
major browsers which support digest authentication are Internet Exploder
5.0 and Amaya. 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>

----- diffs --------------------------------------------------------
RCS file: /home/cvs/apache-1.3/src/Configuration.tmpl,v
retrieving revision 1.117
diff -u -r1.117 Configuration.tmpl
--- Configuration.tmpl  1999/06/22 15:33:10     1.117
+++ Configuration.tmpl  1999/07/08 06:37:54
@@ -124,12 +124,12 @@
 # functions. The format is: Rule RULE=value
 #
 # At present, only the following RULES are known: WANTHSREGEX, SOCKS4,
-# SOCKS5, IRIXNIS, IRIXN32 and PARANOID.
+# SOCKS5, IRIXNIS, IRIXN32, PARANOID, and DEV_RANDOM.
 #
-# For all Rules, if set to "yes", then Configure knows we want that
-# capability and does what is required to add it in. If set to "default"
-# then Configure makes a "best guess"; if set to anything else, or not
-# present, then nothing is done.
+# For all Rules except DEV_RANDOM, if set to "yes", then Configure knows
+# we want that capability and does what is required to add it in. If set
+# to "default" then Configure makes a "best guess"; if set to anything
+# else, or not present, then nothing is done.
 #
 # SOCKS4:
 #  If SOCKS4 is set to 'yes', be sure that you add the socks library
@@ -174,6 +174,19 @@
 Rule PARANOID=no
 Rule EXPAT=default
 
+# DEV_RANDOM:
+#  Note: this rule is only used when compiling mod_digest.
+#  mod_digest requires a cryptographically strong random seed for its
+#  random number generator. It knows two ways of getting this: 1) from
+#  a file or device (such as "/dev/random"), or 2) from the truerand
+#  library. If this rule is set to 'default' then Configure will choose
+#  to use /dev/random if it exists, else /dev/urandom if it exists,
+#  else the truerand library. To override this behaviour set DEV_RANDOM
+#  either to 'truerand' (to use the library) or to a device or file
+#  (e.g. '/dev/urandom'). If the truerand library is selected, Configure
+#  will assume "-L/usr/local/lib -lrand".
+Rule DEV_RANDOM=default
+
 # The following rules should be set automatically by Configure. However, if
 # they are not set by Configure (because we don't know the correct value for
 # your platform), or are set incorrectly, you may override them here.
--------------------------------------------------------------------


Mime
View raw message