You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ro...@hyperreal.org on 1999/08/09 00:34:25 UTC

cvs commit: apache-1.3/src/modules/experimental mod_auth_digest.c Makefile.tmpl

ronald      99/08/08 15:34:25

  Modified:    src/modules/experimental Makefile.tmpl
  Added:       src/modules/experimental mod_auth_digest.c
  Log:
  updated version of mod_digest; in experimental until further tested
  
  Revision  Changes    Path
  1.14      +9 -0      apache-1.3/src/modules/experimental/Makefile.tmpl
  
  Index: Makefile.tmpl
  ===================================================================
  RCS file: /home/cvs/apache-1.3/src/modules/experimental/Makefile.tmpl,v
  retrieving revision 1.13
  retrieving revision 1.14
  diff -u -r1.13 -r1.14
  --- Makefile.tmpl	1998/09/07 06:59:34	1.13
  +++ Makefile.tmpl	1999/08/08 22:34:24	1.14
  @@ -12,3 +12,12 @@
    $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \
    $(INCDIR)/http_log.h $(INCDIR)/http_protocol.h \
    $(INCDIR)/http_request.h $(INCDIR)/http_core.h
  +mod_digest.o: mod_digest.c $(INCDIR)/httpd.h \
  + $(INCDIR)/ap_config.h $(INCDIR)/ap_mmn.h \
  + $(INCDIR)/ap_config_auto.h $(OSDIR)/os.h \
  + $(INCDIR)/ap_ctype.h $(INCDIR)/hsregex.h \
  + $(INCDIR)/alloc.h $(INCDIR)/buff.h $(INCDIR)/ap.h \
  + $(INCDIR)/util_uri.h $(INCDIR)/http_config.h \
  + $(INCDIR)/http_core.h $(INCDIR)/http_log.h \
  + $(INCDIR)/http_protocol.h $(INCDIR)/util_md5.h \
  + $(INCDIR)/ap_md5.h
  
  
  
  1.1                  apache-1.3/src/modules/experimental/mod_auth_digest.c
  
  Index: mod_auth_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_auth_digest: MD5 digest authentication
   *
   * Originally by Alexei Kosut <ak...@nueva.pvt.k12.ca.us>
   * Updated to RFC-2617 by Ronald Tschal�r <ro...@innovation.ch>
   * based on mod_auth, by Rob McCool and Robert S. Thau
   *
   * This module an updated version of modules/standard/mod_digest.c
   * However, it has not been extensively tested yet, and is therefore
   * currently marked experimental. Send problem reports to me
   * (ronald@innovation.ch)
   *
   * Requires either /dev/random (or equivalent) or the truerand library,
   * available for instance from
   * ftp://research.att.com/dist/mab/librand.shar
   *
   * Open Issues:
   *   - qop=auth-int (when streams and trailer support 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
   *   - shared-mem not completely tested yet. Seems to work ok for me,
   *     but... (definitely won't work on Windoze)
   */
  
  /* 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_conf_globals.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"
  #ifdef HAVE_SHMEM_MM
  #include "mm.h"
  #endif	/* HAVE_SHMEM_MM */
  
  
  /* struct to hold the configuration info */
  
  typedef struct digest_config_struct {
      const char  *dir_name;
      const char  *pwfile;
      const char  *grpfile;
      const char  *realm;
      const char **qop_list;
      AP_SHA1_CTX  nonce_ctx;
      long         nonce_lifetime;
      const char  *nonce_format;
      int          check_nc;
      const char  *algorithm;
      char        *uri_list;
      const char  *ha1;
  } digest_config_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
  
  
  /* client list definitions */
  
  typedef struct hash_entry {
      unsigned long      key;			/* the key for this entry    */
      struct hash_entry *next;			/* next entry in the bucket  */
      unsigned long      nonce_count;		/* for nonce-count checking  */
      char               ha1[17];			/* for algorithm=MD5-sess    */
      char               last_nonce[NONCE_LEN+1];	/* for one-time nonce's      */
  } client_entry;
  
  static struct hash_table {
      client_entry  **table;
      unsigned long   tbl_len;
      unsigned long   num_entries;
      unsigned long   num_created;
      unsigned long   num_removed;
      unsigned long   num_renewed;
  } *client_list;
  
  
  /* 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;
      unsigned long         opaque_num;
      const char           *message_qop;
      const char           *nonce_count;
      /* the following fields are not (directly) from the header */
      time_t                nonce_time;
      enum hdr_sts          auth_hdr_sts;
      uri_components       *request_uri;
      int                   needed_auth;
      client_entry         *client;
  } digest_header_rec;
  
  
  /* (mostly) nonce stuff */
  
  typedef union time_union {
      time_t	  time;
      unsigned char arr[sizeof(time_t)];
  } time_rec;
  
  
  static unsigned char secret[SECRET_LEN];
  static int call_cnt = 0;
  
  
  #ifdef HAVE_SHMEM_MM
  /* opaque stuff */
  
  static MM            *opaque_mm;
  static unsigned long *opaque_cntr;
  
  static MM            *client_mm;
  
  static MM            *otn_count_mm;
  static time_t        *otn_counter;	/* one-time-nonce counter */
  
  #define	SHMEM_SIZE 	1000		/* ~ 12 entries */
  #define	NUM_BUCKETS	15UL
  
  #else	/* HAVE_SHMEM_MM */
  static void          *client_mm = NULL;
  #endif	/* HAVE_SHMEM_MM */
  
  module MODULE_VAR_EXPORT digest_module;
  
  /*
   * initialization code
   */
  
  #ifdef HAVE_SHMEM_MM
  static void cleanup_tables(void *not_used)
  {
      fprintf(stderr, "Digest: cleaning up shared memory\n");
      fflush(stderr);
  
      if (client_mm) {
  	mm_destroy(client_mm);
  	client_mm = NULL;
      }
  
      if (opaque_mm) {
  	mm_destroy(opaque_mm);
  	opaque_mm = NULL;
      }
  
      if (otn_count_mm) {
  	mm_destroy(otn_count_mm);
  	otn_count_mm = NULL;
      }
  }
  #endif	/* HAVE_SHMEM_MM */
  
  static void initialize_secret(server_rec *s)
  {
  #ifdef	DEV_RANDOM
      FILE *rnd;
      size_t got, tot;
  #else
      extern int randbyte(void);	/* from the truerand library */
      unsigned int idx;
  #endif
  
      ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s,
  		 "Digest: 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,
  		     "Digest: 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,
  		     "Digest: 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,
  			 "Digest: 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, "Digest: done");
  }
  
  #ifdef HAVE_SHMEM_MM
  static void initialize_tables(server_rec *s)
  {
      unsigned long idx;
  
      /* set up client list */
  
      client_mm = mm_create(SHMEM_SIZE, tmpnam(NULL));
      if (client_mm == NULL)
  	goto failed;
  #ifdef MPE
      if (geteuid() == 1) {
  #else
      if (geteuid() == 0) {
  #endif
  	if (mm_permission(client_mm, 0600, ap_user_id, ap_group_id))
  	    goto failed;
      }
      client_list = mm_malloc(client_mm, sizeof(*client_list) +
  				       sizeof(client_entry*)*NUM_BUCKETS);
      if (!client_list)  goto failed;
      client_list->table = (client_entry**) (client_list + 1);
      for (idx=0; idx<NUM_BUCKETS; idx++)
  	client_list->table[idx] = NULL;
      client_list->tbl_len     = NUM_BUCKETS;
      client_list->num_entries = 0;
  
  
      /* setup opaque */
  
      opaque_mm = mm_create(sizeof(*opaque_cntr), tmpnam(NULL));
      if (opaque_mm == NULL)
  	goto failed;
  #ifdef MPE
      if (geteuid() == 1) {
  #else
      if (geteuid() == 0) {
  #endif
  	if (mm_permission(opaque_mm, 0600, ap_user_id, ap_group_id))
  	    goto failed;
      }
      opaque_cntr = mm_malloc(opaque_mm, sizeof(*opaque_cntr));
      if (opaque_cntr == NULL)
  	goto failed;
      *opaque_cntr = 1UL;
  
  
      /* setup one-time-nonce counter */
  
      otn_count_mm = mm_create(sizeof(*otn_counter), tmpnam(NULL));
      if (otn_count_mm == NULL)
  	goto failed;
  #ifdef MPE
      if (geteuid() == 1) {
  #else
      if (geteuid() == 0) {
  #endif
  	if (mm_permission(otn_count_mm, 0600, ap_user_id, ap_group_id))
  	    goto failed;
      }
      otn_counter = mm_malloc(otn_count_mm, sizeof(*otn_counter));
      if (otn_counter == NULL)
  	goto failed;
      *otn_counter = 0;
  
  
      /* success */
      return;
  
  failed:
      if (!client_mm || (client_list &&  client_list->table && !opaque_mm)
  	|| (opaque_cntr && !otn_count_mm))
  	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s,
  		     "Digest: failed to create shared memory segments; reason "
  		     "was `%s' - all nonce-count checking, one-time nonces, "
  		     "and MD5-sess algorithm disabled", mm_error());
      else
  	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s,
  		     "Digest: failed to allocate shared mem; reason was `%s' "
  		     "- all nonce-count checking, one-time nonces, and "
  		     "MD5-sess algorithm disabled", mm_error());
  
      cleanup_tables(NULL);
  }
  #endif	/* HAVE_SHMEM_MM */
  
  static void initialize_module(server_rec *s, pool *p)
  {
      /* keep from doing the init more than once at startup, and delay
       * the init until the second round
       */
      if (++call_cnt < 2)
  	return;
  
      /* only initialize the secret on startup, not on restarts */
      if (call_cnt == 2)
  	initialize_secret(s);
  
  #ifdef HAVE_SHMEM_MM
      /* Note: this stuff is currently fixed for the lifetime of the server,
       * i.e. even across restarts. This means that A) any shmem-size
       * configuration changes are ignored, and B) certain optimizations,
       * such as only allocating the smallest necessary entry for each
       * client, can't be done. However, the alternative is a nightmare:
       * we can't call mm_destroy on a graceful restart because there will
       * be children using the tables, and we also don't know when the
       * last child dies. Therefore we can never clean up the old stuff,
       * creating a creeping memory leak.
       */
      initialize_tables(s);
      /* atexit(cleanup_tables); */
      ap_register_cleanup(p, NULL, cleanup_tables, ap_null_cleanup);
  #endif	/* HAVE_SHMEM_MM */
  }
  
  
  /*
   * 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->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_realm(cmd_parms *cmd, void *config, const char *realm)
  {
      digest_config_rec *conf = (digest_config_rec *) config;
  
      /* The core already handles the realm, but it's just too convenient to
       * grab it ourselves too and cache some setups. However, we need to
       * let the core get at it too, which is why we decline at the end -
       * this relies on the fact that http_core is last in the list.
       */
      conf->realm = realm;
  
      /* we precompute the part of the nonce hash that is constant (well,
       * the host:port would be too, but that varies for .htaccess files
       * and directives outside a virtual host section)
       */
      ap_SHA1Init(&conf->nonce_ctx);
      ap_SHA1Update_binary(&conf->nonce_ctx, (const unsigned char *) realm,
  			 strlen(realm));
      ap_SHA1Update_binary(&conf->nonce_ctx, secret, sizeof(secret));
  
      return DECLINE_CMD;
  }
  
  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,
  		     "Digest: 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  lifetime;
  
      lifetime = 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 = lifetime;
      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 NULL;
  }
  
  static const char *set_algorithm(cmd_parms *cmd, void *config, const char *alg)
  {
      if (!strcasecmp(alg, "MD5-sess"))
  #ifdef HAVE_SHMEM_MM
  	;
  #else	/* HAVE_SHMEM_MM */
  	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, cmd->server,
  		     "Digest: WARNING: algorithm `MD5-sess' is currently not "
  		     "correctly implemented");
  #endif	/* HAVE_SHMEM_MM */
      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[] =
  {
      {"AuthName", set_realm, NULL, OR_AUTHCFG, TAKE1,
       "The authentication realm (e.g. \"Members Only\")"},
      {"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}
  };
  
  
  /*
   * 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 > 3) {
  	*(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;
      }
  
      /* Note: (nprbytes == 1) would be an error, so just ingore that case */
      if (nprbytes > 1) {
  	*(bufout++) =
  	    (unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4);
      }
      if (nprbytes > 2) {
  	*(bufout++) =
  	    (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
      }
  
      nbytesdecoded -= (4 - nprbytes) & 3;
      (*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;
  }
  
  
  #ifdef HAVE_SHMEM_MM
  /*
   * client list code
   *
   * Each client is assigned a number, which is transfered in the opaque
   * field of the WWW-Authenticate and Authorization headers. The number
   * is just a simple counter which is incremented for each new client.
   * Clients can't forge this number because it is hashed up into the
   * server nonce, and that is checked.
   *
   * The clients are kept in a simple hash table, which consists of an
   * array of client_entry's, each with a linked list of entries hanging
   * off it. The client's number modulo the size of the array gives the
   * bucket number.
   *
   * The clients are garbage collected whenever a new client is allocated
   * but there is not enough space left in the shared memory segment. A
   * simple semi-LRU is used for this: whenever a client entry is accessed
   * it is moved to the beginning of the linked list in its bucket (this
   * also makes for faster lookups for current clients). The garbage
   * collecter then just removes the oldest entry (i.e. the one at the
   * end of the list) in each bucket.
   *
   * The main advantages of the above scheme are that it's easy to implement
   * and it keeps the hash table evenly balanced (i.e. same number of entries
   * in each bucket). The major disadvantage is that you may be throwing
   * entries out which are in active use. This is not tragic, as these
   * clients will just be sent a new client id (opaque field) and nonce
   * with a stale=true (i.e. it will just look like the nonce expired,
   * thereby forcing an extra round trip). If the shared memory segment
   * has enough headroom over the current client set size then this should
   * not occur too often.
   *
   * To help tune the size of the shared memory segment (and see if the
   * above algorithm is really sufficient) a set of counters is kept
   * indicating the number of clients held, the number of garbage collected
   * clients, and the number of erroneously purged clients. These are printed
   * out at each garbage collection run. Note that access to the counters is
   * not synchronized because they are just indicaters, and whether they are
   * off by a few doesn't matter; and for the same reason no attempt is made
   * to guarantee the num_renewed is correct in the face of clients spoofing
   * the opaque field.
   */
  
  /*
   * Get the client given its client number (the key). Returns the entry,
   * or NULL if its not found.
   *
   * Access to the list itself is synchronized via locks. However, access
   * to the entry returned by get_client() is NOT synchronized. This means
   * that there are potentially problems if a client uses multiple,
   * simultaneous connections to access url's within the same protection
   * space. However, these problems are not new: when using multiple
   * connections you have no guarantee of the order the requests are
   * processed anyway, so you have problems with the nonce-count and
   * one-time nonces anyway.
   */
  static client_entry *get_client(unsigned long key, const request_rec *r)
  {
      int bucket;
      client_entry *entry, *prev = NULL;
  
  
      if (!key || !client_mm)  return NULL;
  
      bucket = key % client_list->tbl_len;
      entry  = client_list->table[bucket];
  
      mm_lock(client_mm, MM_LOCK_RD);
  
      while(entry && key != entry->key) {
  	prev  = entry;
  	entry = entry->next;
      }
  
      if (entry && prev) {		/* move entry to front of list */
  	prev->next  = entry->next;
  	entry->next = client_list->table[bucket];
  	client_list->table[bucket] = entry;
      }
  
      mm_unlock(client_mm);
  
      if (entry)
  	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r,
  		      "get_client(): client %lu found", key);
      else
  	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r,
  		      "get_client(): client %lu not found", key);
  
      return entry;
  }
  
  
  /* A simple garbage-collecter to remove unused clients. It removes the
   * last entry in each bucket and updates the counters. Returns the
   * number of removed entries.
   */
  static long gc(void)
  {
      client_entry *entry, *prev;
      unsigned long num_removed = 0, idx;
  
      /* garbage collect all last entries */
  
      for (idx=0; idx<client_list->tbl_len; idx++) {
  	entry = client_list->table[idx];
  	prev  = NULL;
  	while (entry->next) {	/* find last entry */
  	    prev  = entry;
  	    entry = entry->next;
  	}
  	if (prev)  prev->next = NULL;	/* cut list */
  	else       client_list->table[idx] = NULL;
  	if (entry) {			/* remove entry */
  	    mm_free(client_mm, entry);
  	    num_removed++;
  	}
      }
  
      /* update counters and log */
  
      client_list->num_entries -= num_removed;
      client_list->num_removed += num_removed;
  
      return num_removed;
  }
  
  
  /*
   * Add a new client to the list. Returns the entry if successful, NULL
   * otherwise. This triggers the garbage collection is memory is low.
   */
  static client_entry *add_client(unsigned long key, client_entry *new,
  				server_rec *s)
  {
      int bucket;
      client_entry *entry;
  
  
      if (!key || !client_mm)  return NULL;
  
      bucket = key % client_list->tbl_len;
      entry  = client_list->table[bucket];
  
      mm_lock(client_mm, MM_LOCK_RW);
  
      /* try to allocate a new entry */
  
      entry = mm_malloc(client_mm, sizeof(client_entry));
      if (!entry) {
  	long num_removed = gc();
  	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, s,
  		     "Digest: gc'd %ld client entries. Total new clients: "
  		     "%ld; Total removed clients: %ld; Total renewed clients: "
  		     "%ld", num_removed,
  		     client_list->num_created - client_list->num_renewed,
  		     client_list->num_removed, client_list->num_renewed);
  	entry = mm_malloc(client_mm, sizeof(client_entry));
  	if (!entry)  return NULL;	/* give up */
      }
  
      /* now add the entry */
  
      memcpy(entry, new, sizeof(client_entry));
      entry->key  = key;
      entry->next = client_list->table[bucket];
      client_list->table[bucket] = entry;
      client_list->num_created++;
      client_list->num_entries++;
  
      mm_unlock(client_mm);
  
      ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, s,
  		 "allocated new client %lu", key);
  
      return entry;
  }
  #else	/* HAVE_SHMEM_MM */
  static client_entry *get_client(unsigned long key, const request_rec *r)
  {
      return NULL;
  }
  #endif	/* HAVE_SHMEM_MM */
  
  
  /*
   * 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;
      }
  
      if (resp->opaque)
  	resp->opaque_num = (unsigned long) strtol(resp->opaque, NULL, 16);
  
      resp->auth_hdr_sts = VALID;
      return OK;
  }
  
  
  /* 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);
      resp->client = get_client(resp->opaque_num, r);
      if (res == OK  &&  resp->client)
  	resp->client->nonce_count++;
  
      return DECLINED;
  }
  
  
  /*
   * 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 *opaque,
  			   const server_rec *server,
  			   const digest_config_rec *conf)
  {
      const char *hex = "0123456789abcdef";
      unsigned char sha1[20];
      AP_SHA1_CTX ctx;
      int idx;
  
      memcpy(&ctx, &conf->nonce_ctx, sizeof(ctx));
      ap_SHA1Update_binary(&ctx, (const unsigned char *) server->server_hostname,
  			 strlen(server->server_hostname));
      ap_SHA1Update_binary(&ctx, (const unsigned char *) &server->port,
  			 sizeof(server->port));
      ap_SHA1Update_binary(&ctx, (const unsigned char *) time, strlen(time));
      if (opaque)
  	ap_SHA1Update_binary(&ctx, (const unsigned char *) opaque,
  			     strlen(opaque));
      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 *opaque,
  			     const server_rec *server,
  			     const digest_config_rec *conf)
  {
      char *nonce = ap_palloc(p, NONCE_LEN+1);
      time_rec t;
  
      if (conf->nonce_lifetime != 0)
  	t.time = now;
      else
  #ifdef HAVE_SHMEM_MM
  	/* this counter is not synch'd, because it doesn't really matter
  	 * if it counts exactly.
  	 */
  	t.time = (*otn_counter)++;
  #else	/* HAVE_SHMEM_MM */
  	t.time = 42;
  #endif	/* HAVE_SHMEM_MM */
      memcpy(nonce, base64encode(p, t.arr, sizeof(t.arr)), NONCE_TIME_LEN+1);
      gen_nonce_hash(nonce+NONCE_TIME_LEN, nonce, opaque, server, conf);
  
      return nonce;
  }
  
  
  /*
   * Opaque and hash-table management
   */
  
  #ifdef HAVE_SHMEM_MM
  /*
   * Generate a new client entry, add it to the list, and return the
   * entry. Returns NULL if failed.
   */
  static client_entry *gen_client(const request_rec *r)
  {
      unsigned long op;
      client_entry new = { 0, NULL, 0, "", "" }, *entry;
  
      if (!opaque_mm)  return 0;
  
      mm_lock(opaque_mm, MM_LOCK_RW);
      op = (*opaque_cntr)++;
      mm_unlock(opaque_mm);
  
      if (!(entry = add_client(op, &new, r->server))) {
  	ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
  		      "Digest: failed to allocate client entry - ignoring "
  		      "client");
  	return NULL;
      }
  
      return entry;
  }
  #else	/* HAVE_SHMEM_MM */
  static client_entry *gen_client(const request_rec *r) { return NULL; }
  #endif	/* HAVE_SHMEM_MM */
  
  
  
  /*
   * MD5-sess code.
   *
   * If you want to use algorithm=MD5-sess you must write get_userpw_hash()
   * yourself (see below). The dummy provided here just returns the hash
   * from the auth-file, i.e. it is only useful for testing client
   * implementations of MD5-sess .
   */
  
  /*
   * 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 they install a new version
   * of apache.
   */
  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,
  			       digest_header_rec *resp,
  			       const digest_config_rec *conf)
  {
      const char *ha1 = NULL, *urp;
  
      /* get ha1 from client list */
      if (resp->opaque && resp->client)
  	ha1 = resp->client->ha1;
  
      /* generate new session if necessary */
      if (ha1 == NULL || ha1[0] == '\0') {
  	urp = get_userpw_hash(r, resp, conf);
  	ha1 = ap_md5(r->pool,
  		     (unsigned char *) ap_pstrcat(r->pool, ha1, ":", resp->nonce,
  						  ":", resp->cnonce, NULL));
  	if (!resp->client)
  	    resp->client = gen_client(r);
  	if (resp->client)
  	    memcpy(resp->client->ha1, ha1, 17);
      }
  
      return ha1;
  }
  
  
  static void clear_session(const digest_header_rec *resp)
  {
      if (resp->client)
  	resp->client->ha1[0] = '\0';
  }
  
  
  /*
   * 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 ((unsigned long) (f-filename) < d_len) {
  	char *tmp = ap_pstrdup(p, uri);
  	tmp[(u-uri)+(d_len-(f-filename))] = '\0';
  	return tmp;
      }
  
      return "";	/* give up */
  }
  
  
  static const char *ltox(pool *p, unsigned long num)
  {
      if (num != 0)
  	return ap_psprintf(p, "%lx", num);
      else
  	return "";
  }
  
  static void note_digest_auth_failure(request_rec *r,
  				     const digest_config_rec *conf,
  				     digest_header_rec *resp, int stale)
  {
      const char   *qop, *opaque, *opaque_param, *domain, *nonce;
      int           cnt;
  
  
      /* Setup qop */
  
      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);
      }
  
      /* MD5-sess stuff */
  
      if (!stale && !strcasecmp(conf->algorithm, "MD5-sess"))
  	clear_session(resp);
  
      /* Setup opaque */
  
      if (resp->opaque == NULL) {
  	/* new client */
  	if ((conf->check_nc || conf->nonce_lifetime == 0
  	     || !strcasecmp(conf->algorithm, "MD5-sess"))
  	    && (resp->client = gen_client(r)) != NULL)
  	    opaque = ltox(r->pool, resp->client->key);
  	else
  	    opaque = "";		/* opaque not needed */
      }
      else if (resp->client == NULL) {
  	/* client info was gc'd */
  	resp->client = gen_client(r);
  	if (resp->client != NULL) {
  	    opaque = ltox(r->pool, resp->client->key);
  	    stale = 1;
  	    client_list->num_renewed++;
  	}
  	else
  	    opaque = "";		/* ??? */
      }
      else {
  	opaque = resp->opaque;
  	/* we're generating a new nonce, so reset the nonce-count */
  	resp->client->nonce_count = 0;
      }
  
      if (opaque[0])
  	opaque_param = ap_pstrcat(r->pool, ", opaque=\"", opaque, "\"", NULL);
      else
  	opaque_param = NULL;
  
      /* Setup nonce */
  
      nonce = gen_nonce(r->pool, r->request_time, opaque, r->server, conf);
      if (resp->client && conf->nonce_lifetime == 0)
  	memcpy(resp->client->last_nonce, nonce, NONCE_LEN+1);
  
      /* 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), nonce, conf->algorithm,
  				opaque_param ? opaque_param : "",
  				domain ? domain : "",
  				stale ? ", stale=true" : "", qop));
  }
  
  
  /*
   * 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,
  		      "Digest: 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)
  {
      if (conf->check_nc && client_mm) {
  	unsigned long nc;
  
  	const char *snc = resp->nonce_count;
  	char *endptr;
  
  	nc = strtol(snc, &endptr, 16);
  	if (endptr < (snc+strlen(snc)) && !ap_isspace(*endptr)) {
  	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
  			  "Digest: invalid nc %s received - not a number", snc);
  	    return !OK;
  	}
  
  	if (!resp->client)
  	    return !OK;
  
  	if (nc != resp->client->nonce_count) {
  	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r,
  			  "nonce-count check failed: %lu != %lu", nc,
  			  resp->client->nonce_count);
  	    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,
  		      "Digest: 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, resp->opaque, r->server, conf);
      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,
  		      "Digest: 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 (conf->nonce_lifetime > 0 && dt < 0) {
  	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
  		      "Digest: 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,
  			  "Digest: 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 && resp->client) {
  	if (memcmp(resp->client->last_nonce, resp->nonce, NONCE_LEN)) {
  	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, r,
  			  "Digest: user %s: one-time-nonce mismatch - sending "
  			  "new nonce", r->connection->user);
  	    note_digest_auth_failure(r, conf, resp, 1);
  	    return AUTH_REQUIRED;
  	}
      }
      /* 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,
  			      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, (const unsigned char*) ""), NULL); /* TBD */
      else
  	a2 = ap_pstrcat(r->pool, r->method, ":", resp->uri, NULL);
      ha2 = ap_md5(r->pool, (const 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));
  }
  
  
  /* 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,
  		      "Digest: 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;
  
  
      /* get our conf */
  
      conf = (digest_config_rec *) ap_get_module_config(r->per_dir_config,
  						      &digest_module);
  
  
      /* 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,
  			  "Digest: 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,
  			  "Digest: 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 = (char *) "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,
  			  "Digest: uri mismatch - <%s> does not match "
  			  "request-uri <%s>", resp->uri,
  			  ap_unparse_uri_components(r->pool, r_uri, 0));
  	    return BAD_REQUEST;
  	}
      }
  
      if (resp->opaque && resp->opaque_num == 0) {
  	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
  		      "Digest: received invalid opaque - got `%s'",
  		      resp->opaque);
  	note_digest_auth_failure(r, conf, resp, 0);
  	return AUTH_REQUIRED;
      }
  
      if (strcmp(resp->realm, conf->realm)) {
  	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
  		      "Digest: 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,
  		      "Digest: 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,
  		      "Digest: 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,
  			  "Digest: 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,
  			  "Digest: 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,
  			  "Digest: 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,
  		      "Digest: 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,
  		"Digest: 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,
  	"Digest: 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);
      digest_header_rec *resp =
  		(digest_header_rec *) ap_get_module_config(r->request_config,
  							   &digest_module);
      const 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,
  					     resp->opaque, r->server, conf),
  				   "\"", NULL);
  	    resp->client->nonce_count = 0;
  	}
      }
      else if (conf->nonce_lifetime == 0 && resp->client) {
          const char *nonce = gen_nonce(r->pool, 0, resp->opaque, r->server,
  				      conf);
  	nextnonce = ap_pstrcat(r->pool, ", nextnonce=\"", nonce, "\"", NULL);
  	memcpy(resp->client->last_nonce, nonce, NONCE_LEN+1);
      }
      /* 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, (const unsigned char *) ""), NULL); /* TBD */
  	else
  	    a2 = ap_pstrcat(r->pool, ":", resp->uri, NULL);
  	ha2 = ap_md5(r->pool, (const 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_module,		/* 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 */
  };