/*
 * Copyright (c) 2003-2015
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*****************************************************************************
 * COPYRIGHT AND PERMISSION NOTICE
 * 
 * Copyright (c) 2001-2003 The Queen in Right of Canada
 * 
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, and/or sell
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, provided that the above copyright notice(s) and this
 * permission notice appear in all copies of the Software and that both the
 * above copyright notice(s) and this permission notice appear in supporting
 * documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE 
 * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
 * SOFTWARE.
 * 
 * Except as contained in this notice, the name of a copyright holder shall not
 * be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization of the
 * copyright holder.
 ***************************************************************************/

/*
 * mod_auth_dacs
 *
 * This Apache module is used to integrate DACS functionality.
 * It currently adds DACS authentication and access control to Apache by
 * invoking DACS as an external program (the "external" method).
 * The capability exists to integrate DACS into this module (the "internal"
 * method).
 *
 * In the external method, the check_user_id handler invokes DACS to do
 * valid credential checking and also auth access checking.
 * There is no point in incurring the expense of invoking DACS more than once.
 * And so for this method the check_auth handler has very little to do except
 * prevent the basic auth handler from being called.
 * An internal implementation might separate the two functions, however.
 * 
 * This module relies on two interactions with the mod_cgi module: one
 * to introduce new/replacment environment variables to an invoked CGI program
 * and one to reconstruct the POST method input stream to an invoked CGI
 * program.
 *
 * No arguments to the DACS authentication program are passed as environment
 * variables because some systems permit any user to read the environment of
 * any process ("ps -e").
 * Note that if any arguments are passed to DACS (see AddDACSAuth), they
 * may be visible to other processes, so passwords etc. should not be
 * passed as a command line argument.
 *
 * Because information passed to DACS might be sensitive, it is passed via the
 * standard input to a DACS authentication program, as a set of keyword=value
 * strings, each terminated by a newline character.
 *
 * In all cases, the DACS authentication program should return 0 if access is
 * granted and a non-zero error code if not.
 * Also, the standard output of the authentication program is read.  It is
 * assumed to consist of a set of keyword=value strings, newline terminated.
 * In the event that a CGI program is being invoked, these will be exported
 * into its environment.
 *
 *
 * Configuration information:
 *
 * The following are added to the server configuration file (normally
 * httpd.conf):
 *
 * AddDACSAuth       <keyword> <path-to-dacs-acs> [<command-line-args>]
 * SetDACSAuthMethod <keyword> <method>
 * SetDACSAuthConf   <keyword> <path-to-dacs-conf-file>
 *
 * The AddDACSAuth directive specifies the full pathname of an external
 * program to be used to provide DACS access control for the given DACS
 * authentication keyword.  The keyword can be any string and is simply used
 * to tag related information for later reference.
 * Optionally, command line arguments to be passed to the external program
 * can be specified (appearing as a single configuration file argument).
 * If the string doesn't contain any whitespace, it need not be quoted;
 * otherwise it must be quoted and it will be parsed into whitespace
 * separated arguments.  For example, the configuration file line
 *    AddDACSAuth  dacs /usr/local/dacs/bin/dacs_acs "-a -b -c=\"Hello world\""
 * would invoke DACS as:
 *     /usr/local/dacs/bin/dacs_acs -a -b -c="Hello world"
 *
 * The SetDACSAuthMethod directive specifies which form of DACS access control
 * is to be used. There is currently one possible <method>: 'external'.
 * A method named 'internal' is recognized but not implemented; it would be
 * used to indicate that instead of invoking an external program, an internal
 * function would be used, and <path-to-dacs-acs> would be treated as a control
 * argument to that function.
 *
 * Unless compiled in to all DACS binaries, the full pathname of the DACS
 * configuration file ("dacs.conf") must be provided, e.g.,
 *    AddDACSAuth dacs "/usr/local/dacs/dacs.conf"
 * This will cause the environment variable DACS_CONF, which is the pathname
 * of the DACS configuration file, to be passed to all DACS-controlled
 * programs.
 *
 * SetDACSAuthEnvBuffer is used to set the size of the buffer used to read
 * environment strings back from DACS.  It's only necessary if the default
 * (DACS_DEFAULT_ENV_BUF_SIZE) isn't satisfactory.
 * Similarly, SetDACSAuthPostBuffer sets the size of the buffer used to
 * sample the POST stream that is sent to DACS.  It may be set to 0
 * to disable this sampling, otherwise it must fall between certain limits.
 *
 * In a <Location> or <Directory> directive or .htaccess file, the following
 * is used to select DACS authentication and access control:
 *
 * AuthDACS <keyword>
 *
 * The keyword given must match one defined in the server configuration file.
 * DACS authentication is authoritative; if it fails, no other access control
 * scheme will be tried.
 *
 * To follow Apache's recent conventions, this module should probably
 * be called mod_authnz_dacs since it can act as both an authentication and
 * authorization provider.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2015\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: mod_auth_dacs.c 2835 2015-08-24 22:22:01Z brachman $";
#endif

#include "apr_strings.h"
#include "apr_lib.h"
#include "apr_base64.h"

#include "ap_config.h"
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"
#include "util_script.h"

#include "acs_module.h"

#ifdef DACS_VERSION
#include "dacs_version.h"
#endif

/*
 * XXX These make some assumptions about compatibility between versions
 * that may need to be corrected in future releases of Apache.
 */
#if (AP_SERVER_MAJORVERSION_NUMBER == 2 && AP_SERVER_MINORVERSION_NUMBER == 4)
#define APACHE_2_4_BUILD
static int is_apache_2_4_build = 1;
static int is_apache_2_2_build = 0;
#endif

#if (AP_SERVER_MAJORVERSION_NUMBER == 2 && AP_SERVER_MINORVERSION_NUMBER == 2)
#define APACHE_2_2_BUILD
static int is_apache_2_4_build = 0;
static int is_apache_2_2_build = 1;
#endif

#ifdef APACHE_2_4_BUILD
#include "ap_provider.h"
#include "mod_auth.h"
#endif

/*
 * Define DACS_DEBUG depending on how you want debugging output enabled.
 * Set it to 0 to omit debugging log messages, or 1 to produce them.
 * The default can be overridden in httpd.conf by setting SetDACSAuthDebug
 * to "on" or "off".
 */
#ifndef DACS_DEBUG
#define DACS_DEBUG		0
#endif

/* For getpid() */
#include <unistd.h>

/* In modules/ssl/ssl_engine_kernel.c */
extern int ssl_hook_Fixup(request_rec *);

#if defined(__DATE__) && defined(__TIME__)
static const char module_built[] = __DATE__ " " __TIME__;
#else
static const char module_built[] = "unknown";
#endif

module AP_MODULE_DECLARE_DATA auth_dacs_module;

#define DACS_DEFAULT_METHOD		"external"

#ifdef REQUIRE_PATCHED_SSL
/*
 * Versions of Apache prior to 2.0.51 require a patch to be applied to
 * modules/ssl/ssl_engine_io.c.
 */
extern int ssl_is_ssl_request(request_rec *r);
#else
/* For AP_SERVER_PATCHLEVEL > 50 */
APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
static APR_OPTIONAL_FN_TYPE(ssl_is_https) *rewrite_is_https = NULL;
#define ssl_is_ssl_request(R) \
	(rewrite_is_https && rewrite_is_https(R->connection))
#endif

enum {
  /* Emit the ARGS_TRUNCATED_ARG before the SERVICE_ARGS variable */
  DACS_SERVICE_ARGS_TRUNCATED_ARG      =  0,
  DACS_SERVICE_ARGS 			       =  1,
  DACS_SERVICE_AUTHORIZATION_ARG       =  2,
  DACS_SERVICE_CONTENT_ENCODING_ARG    =  3,
  DACS_SERVICE_CONTENT_LENGTH_ARG      =  4,
  DACS_SERVICE_CONTENT_TYPE_ARG	       =  5,
  DACS_SERVICE_COOKIE_ARG 		       =  6,
  DACS_SERVICE_FILENAME_ARG		       =  7,
  DACS_SERVICE_HOSTNAME_ARG            =  8,
  DACS_SERVICE_HTTPS_ARG               =  9,
  DACS_SERVICE_METHOD_ARG		       = 10,
  DACS_SERVICE_PATH_INFO_ARG           = 11,
  DACS_SERVICE_POSTDATA_ARG            = 12,
  DACS_SERVICE_PROXYREQ_ARG            = 13,
  DACS_SERVICE_QUERY_ARG		       = 14,
  DACS_SERVICE_REMOTE_ADDR_ARG	       = 15,
  DACS_SERVICE_REMOTE_HOST_ARG	       = 16,
  DACS_SERVICE_SERVER_PORT_ARG         = 17,
  DACS_SERVICE_URI_ARG 			       = 18,
  DACS_SERVICE_USER_AGENT_ARG          = 19,
  DACS_SERVICE_PROXY_AUTHORIZATION_ARG = 20,
  DACS_PROXY_AUTH_ARG                  = 21,
  DACS_MOD_AUTH_DACS_VERSION_ARG       = 22,
  DACS_SERVICE_NARGS			       = (DACS_MOD_AUTH_DACS_VERSION_ARG + 1)
};

enum {
  DACS_DEFAULT_ENV_BUF_SIZE		= 16384,
  DACS_MINIMUM_ENV_BUF_SIZE		= 1024,
  DACS_MAXIMUM_ENV_BUF_SIZE		= 1048576,
  DACS_DEFAULT_POST_BUF_SIZE	= 16384,
  DACS_MINIMUM_POST_BUF_SIZE	= 1024,
  DACS_MAXIMUM_LOG_POST_SIZE	= 511, /* < DACS_MINIMUM_POST_BUF_SIZE */
  DACS_MAXIMUM_POST_BUF_SIZE	= 1048576,
  DACS_INITIAL_CHILD_ARGS		= 10,
  DACS_INCR_CHILD_ARGS			= 10
};

#define DACS_HTTP_AUTH_NONE		0
#define DACS_HTTP_AUTH_BASIC	1
#define DACS_HTTP_AUTH_DIGEST	2

/* Per DACS-wrapped request configuration and state. */
typedef struct dacs_request_rec {
  int did_dacs_check;	/* Set if DACS has been invoked for this request. */
  int http_auth;		/* If HTTP authentication is needed, which style? */
  int granted;			/* Eventually set to one iff access is granted. */
  int check_only;		/* Set if this is only an access control check. */
  char *env_buf;
  int env_buf_size;
  int env_buf_len;

  char *post_buf;
  int post_buf_size;
  int post_buf_len;
  int post_buf_eof;
} dacs_request_rec;

typedef struct {
  char *dacs_extname;
} dacs_dir_config;

/*
 * These are set by any global directives and overridden by any per-server
 * directives.
 */
typedef struct {
  apr_table_t *dacs_extpath;
  apr_table_t *dacs_extargs;
  apr_table_t *dacs_method;
  apr_table_t *dacs_conf;
  apr_table_t *dacs_site_conf;
  int env_buf_size;
  int post_buf_size;
  int debug;
} dacs_server_config;

enum {
  DACS_DEBUG_UNDEF = 0,
  DACS_DEBUG_ON    = 1,
  DACS_DEBUG_OFF   = 2
};

typedef enum {
  DACS_COOKIE_NO_ZAP    = 0,	/* Config asks not to suppress DACS cookies. */
  DACS_COOKIE_DO_ZAP    = 1,	/* Default: suppress DACS cookies. */
  DACS_COOKIE_FORCE_ZAP = 2		/* Override config, suppress DACS cookies. */
} DACS_cookie_zap_mode;

static inline int
debug_enabled(dacs_server_config *sc_rec)
{

  return(sc_rec->debug == DACS_DEBUG_ON);
}

static inline char *
config_src(cmd_parms *c)
{
  server_rec *s = c->server;

  if (s->defn_name == NULL)
	return("");

  return(apr_psprintf(c->temp_pool,
					  " at %s:%d", s->defn_name, s->defn_line_number));
}

static int cgi_pre_handler(request_rec *r);
static const char *add_auth_dacs(cmd_parms *cmd, void *mconfig,
								 const char *keyword, const char *path,
								 const char *args);
static const char *set_dacs_method(cmd_parms *cmd, void *mconfig,
								   const char *keyword, const char *method);
static const char *set_buf_size(cmd_parms *cmd, void *mconfig,
								const char *value);
static const char *set_debug_mode(cmd_parms *cmd, void *mconfig,
								  const char *value);
static const char *set_dacs_conf(cmd_parms *cmd, void *mconfig,
								 const char *keyword, const char *path);
static const char *set_dacs_site_conf(cmd_parms *cmd, void *mconfig,
									  const char *keyword, const char *path);

/*
 * Configuration commands that this module understands
 *
 * RSRC_CONF means "outside <Directory> or <Location>" (include/http_config.h)
 * OR_AUTHCFG means "inside <Directory> or <Location> and .htaccess when
 * AllowOverride AuthConfig"
 */
static const command_rec dacs_cmds[] = {
  AP_INIT_TAKE1("AuthDACS", ap_set_string_slot,
				(void*)APR_OFFSETOF(dacs_dir_config, dacs_extname), OR_AUTHCFG,
				"a configuration keyword indicating which DACS configuration to use"),

  AP_INIT_TAKE23("AddDACSAuth", add_auth_dacs, NULL, RSRC_CONF,
				 "a keyword followed by an argument (for the external method, a path to the authenticator program; for the internal method, a control argument), optionally followed by a string encapsulating the arguments to the external method"),

  AP_INIT_TAKE2("SetDACSAuthMethod", set_dacs_method, NULL, RSRC_CONF,
				"a configuration keyword followed by the method, 'external' (default) or 'internal'"),

  AP_INIT_TAKE2("SetDACSAuthConf", set_dacs_conf, NULL, RSRC_CONF,
				"a configuration keyword followed by the full path to the DACS configuration file (dacs.conf)"),

  AP_INIT_TAKE2("SetDACSAuthSiteConf", set_dacs_site_conf, NULL, RSRC_CONF,
				"a configuration keyword followed by the full path to the DACS site configuration file (site.conf)"),

  AP_INIT_TAKE1("SetDACSAuthEnvBuffer", set_buf_size, NULL, RSRC_CONF,
				"an integer that is the maximum number of bytes, representing environment variables, to pass to a CGI program"),

  AP_INIT_TAKE1("SetDACSAuthPostBuffer", set_buf_size, NULL, RSRC_CONF,
				"an integer that is the maximum number of bytes from the POST stream to pass to DACS"),

  AP_INIT_TAKE1("SetDACSAuthDebug", set_debug_mode, NULL, RSRC_CONF,
				"the string \"on\" enables more verbose log messages; the default value is \"off\""),

  { NULL }
};

/*
 * Per-request configuration initialization.
 */
static dacs_request_rec *
init_dacs_request_rec(request_rec *r)
{
  dacs_request_rec *dacs_rec;
  dacs_server_config *sc_rec
	= (dacs_server_config *) ap_get_module_config(r->server->module_config,
												  &auth_dacs_module);

  dacs_rec = (dacs_request_rec *)
	ap_get_module_config(r->request_config, &auth_dacs_module);

  if (dacs_rec == NULL) {
	dacs_rec = apr_palloc(r->pool, sizeof(dacs_request_rec));
	ap_set_module_config(r->request_config, &auth_dacs_module, dacs_rec);
	dacs_rec->env_buf = apr_palloc(r->pool, sc_rec->env_buf_size + 1);
	dacs_rec->env_buf[0] = '\0';
	dacs_rec->env_buf_len = 0;
	dacs_rec->env_buf_size = sc_rec->env_buf_size;
	dacs_rec->post_buf = apr_palloc(r->pool, sc_rec->post_buf_size + 1);
	dacs_rec->post_buf[0] = '\0';
	dacs_rec->post_buf_len = 0;
	dacs_rec->post_buf_eof = 1;
	dacs_rec->post_buf_size = sc_rec->post_buf_size;
	dacs_rec->granted = 0;
	dacs_rec->did_dacs_check = 0;
	dacs_rec->http_auth = DACS_HTTP_AUTH_NONE;
	dacs_rec->check_only = 0;
  }

  if (debug_enabled(sc_rec))
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "init_dacs_request_rec: env_buf_size=%d, post_buf_size=%d",
				 dacs_rec->env_buf_size, dacs_rec->post_buf_size);

  return(dacs_rec);
}

static dacs_request_rec *
get_dacs_request_rec(request_rec *r)
{
  dacs_request_rec *dacs_rec;

  dacs_rec = (dacs_request_rec *) ap_get_module_config(r->request_config,
													   &auth_dacs_module);

  if (dacs_rec == NULL)
	dacs_rec = init_dacs_request_rec(r);

  return(dacs_rec);
}

/* Per-directory configuration. */
static void *
dacs_create_dir_config(apr_pool_t *p, char *d)
{
  dacs_dir_config *dc_rec
	= (dacs_dir_config *) apr_pcalloc(p, sizeof(dacs_dir_config));

  dc_rec->dacs_extname = NULL;

  return(dc_rec);
}

/* Per-server configuration. */
static void *
dacs_create_server_config(apr_pool_t *p, server_rec *s)
{
  dacs_server_config *sc_rec;

  sc_rec = (dacs_server_config *) apr_palloc(p, sizeof(dacs_server_config));

  sc_rec->dacs_extpath = apr_table_make(p, 4);
  sc_rec->dacs_extargs = apr_table_make(p, 4);
  sc_rec->dacs_method = apr_table_make(p, 4);
  sc_rec->dacs_conf = apr_table_make(p, 4);
  sc_rec->dacs_site_conf = apr_table_make(p, 4);

  sc_rec->env_buf_size = -1;
  sc_rec->post_buf_size = -1;

#if DACS_DEBUG == 1
  sc_rec->debug = DACS_DEBUG_ON;
#else
  sc_rec->debug = DACS_DEBUG_UNDEF;
#endif

  return((void *) sc_rec);
}

/*
 * Each virtual host inherits certain directives (defaults) from the main
 * server configuration.
 */
static void *
dacs_merge_server_config(apr_pool_t *p, void *base_conf, void *vh_conf)
{
  dacs_server_config *sc_rec = (dacs_server_config *) base_conf,
	*vh_sc_rec = (dacs_server_config *) vh_conf;

  if (vh_sc_rec->debug == DACS_DEBUG_UNDEF)
	vh_sc_rec->debug = sc_rec->debug;

  if (sc_rec->env_buf_size == -1)
	sc_rec->env_buf_size = DACS_DEFAULT_ENV_BUF_SIZE;
  if (vh_sc_rec->env_buf_size == -1)
	vh_sc_rec->env_buf_size = sc_rec->env_buf_size;

  if (sc_rec->post_buf_size == -1)
	sc_rec->post_buf_size = DACS_DEFAULT_POST_BUF_SIZE;
  if (vh_sc_rec->post_buf_size == -1)
	vh_sc_rec->post_buf_size = sc_rec->post_buf_size;

  return(vh_sc_rec);
}

/*
 * Handler for a AddDACSAuth server config line - add the type
 * to the server configuration.
 */
static const char *
add_auth_dacs(cmd_parms *cmd, void *mconfig, const char *keyword,
			  const char *path, const char *args)
{
  dacs_server_config *sc_rec;

  sc_rec = ap_get_module_config(cmd->server->module_config,
								&auth_dacs_module);

  if (path == NULL
	  || (path[0] != '/' && !isalpha(path[0]) && path[1] != ':'
		  && path[2] != '/'
		  && (path[3] == '/' || path[3] == '\0')))
	return("mod_auth_dacs: absolute path required");
  apr_table_set(sc_rec->dacs_extpath, keyword, path);

  if (args != NULL)
	apr_table_set(sc_rec->dacs_extargs, keyword, args);

  apr_table_set(sc_rec->dacs_method, keyword, DACS_DEFAULT_METHOD);

  return(NULL);
}

static const char *
set_debug_mode(cmd_parms *cmd, void *mconfig, const char *value)
{
  dacs_server_config *sc_rec;

  sc_rec = ap_get_module_config(cmd->server->module_config,
								&auth_dacs_module);

  if (value == NULL)
	return("string value expected for debug mode");

  if (!strcasecmp(value, "on"))
	sc_rec->debug = DACS_DEBUG_ON;
  else if (!strcasecmp(value, "off"))
	sc_rec->debug = DACS_DEBUG_OFF;
  else
	return("unrecognized SetDACSAuthDebug value");

#if DACS_DEBUG == 1
  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
			   "set_debug_mode: debug=%d at %s:%d for server %s:%u",
			   sc_rec->debug,
			   cmd->server->defn_name, cmd->server->defn_line_number,
			   cmd->server->server_hostname, (unsigned int) cmd->server->port);
#endif

  return(NULL);
}

/*
 * Handler for SetDACSAuthEnvBuffer/SetDACSAuthPostBuffer server config line
 */
static const char *
set_buf_size(cmd_parms *cmd, void *mconfig, const char *value)
{
  int size;
  char *ptr;
  dacs_server_config *sc_rec;

  sc_rec = ap_get_module_config(cmd->server->module_config, &auth_dacs_module);

  if (value == NULL)
	return("integer value expected for buffer size");
  size = strtol(value, &ptr, 10);
  if (*ptr != '\0' || size < 0)
	return("illegal integer value for buffer size");

  if (strcasecmp(cmd->cmd->name, "SetDACSAuthEnvBuffer") == 0) {
	if (size < DACS_MINIMUM_ENV_BUF_SIZE || size > DACS_MAXIMUM_ENV_BUF_SIZE)
	  return("Value out-of-range for SetDACSAuthEnvBuffer size");
	sc_rec->env_buf_size = size;
	if (debug_enabled(sc_rec))
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
				   "set_buf_size: env_buf_size=%d%s for server %s:%u", size,
				   config_src(cmd),
				   cmd->server->server_hostname,
				   (unsigned int) cmd->server->port);
  }
  else if (strcasecmp(cmd->cmd->name, "SetDACSAuthPostBuffer") == 0) {
	if (size != 0
		&& (size < DACS_MINIMUM_POST_BUF_SIZE
			|| size > DACS_MAXIMUM_POST_BUF_SIZE))
	  return("Value out-of-range for SetDACSAuthPostBuffer size");
	sc_rec->post_buf_size = size;
	if (debug_enabled(sc_rec))
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
				   "set_buf_size: post_buf_size=%d%s for server %s:%u", size,
				   config_src(cmd), cmd->server->server_hostname,
				   (unsigned int) cmd->server->port);
  }
  else
	return("command name botch - internal error!");

  return(NULL);
}

/*
 * Handler for a SetDACSAuthMethod server config line - change a DACS
 * authentication method in the server configuration.
 */
static const char *
set_dacs_method(cmd_parms *cmd, void *mconfig, const char *keyword,
				const char *method)
{
  dacs_server_config *sc_rec;

  if (strcmp(method, "external") != 0 && strcmp(method, "internal") != 0)
	return(apr_psprintf(cmd->pool,
						"mod_auth_dacs: SetDACSAuthMethod: unrecognized method: %s",
						method));

  sc_rec = ap_get_module_config(cmd->server->module_config, &auth_dacs_module);

  apr_table_set(sc_rec->dacs_method, keyword, method);

  return(NULL);
}

/*
 * Handler for a SetDACSAuthConf server config line - set the location of
 * the DACS configuration file method in the server configuration.
 */
static const char *
set_dacs_conf(cmd_parms *cmd, void *mconfig, const char *keyword,
			  const char *path)
{
  dacs_server_config *sc_rec;

  if (path == NULL
	  || (path[0] != '/' && !isalpha(path[0]) && path[1] != ':'
		  && path[2] != '/'
		  && (path[3] == '/' || path[3] == '\0')))
	return(apr_psprintf(cmd->pool,
						"mod_auth_dacs: SetDACSAuthConf: invalid path for \"%s\"",
						keyword));

  sc_rec = ap_get_module_config(cmd->server->module_config, &auth_dacs_module);
  apr_table_set(sc_rec->dacs_conf, keyword, path);
  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
			   "set_dacs_conf: DACS_CONF=\"%s\"%s for server %s:%u", path,
			   config_src(cmd),
			   cmd->server->server_hostname, (unsigned int) cmd->server->port);

  return(NULL);
}

/*
 * Handler for a SetDACSAuthSiteConf server config line - set the location of
 * the DACS site configuration file method in the server configuration.
 */
static const char *
set_dacs_site_conf(cmd_parms *cmd, void *mconfig, const char *keyword,
				   const char *path)
{
  dacs_server_config *sc_rec;

  if (path == NULL
	  || (path[0] != '/' && !isalpha(path[0]) && path[1] != ':'
		  && path[2] != '/'
		  && (path[3] == '/' || path[3] == '\0')))
	return(apr_psprintf(cmd->pool,
						"mod_auth_dacs: SetDACSAuthSiteConf: invalid path for '%s'",
						keyword));

  sc_rec = ap_get_module_config(cmd->server->module_config, &auth_dacs_module);

  apr_table_set(sc_rec->dacs_site_conf, keyword, path);
  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server,
			   "set_dacs_site_conf: DACS_SITE_CONF=\"%s\"%s for server %s:%u", path,
			   config_src(cmd),
			   cmd->server->server_hostname, (unsigned int) cmd->server->port);

  return(NULL);
}

static int
is_dacs_cookie(char *cookie_str)
{

  return(strncmp(cookie_str, "DACS:", 5) == 0);
}

/*
 * COOKIE_STR points to the start of a cookie entry (<name>=<value>) in
 * a semicolon-separated list of cookies.  One or more spaces may follow a
 * semicolon.
 * Return a pointer to the start of the next cookie, or NULL if COOKIE_STR
 * points at the last (or only) cookie.
 */
static char *
skip_cookie(char *cookie_str)
{
  char *p;

  p = cookie_str;
  while (*p != '\0' && *p != ';')
	p++;
  if (*p == '\0')
    return(NULL);
  p++;
  while (*p == ' ')
    p++;
  if (*p == '\0')
    return(NULL);

  return(p);
}

/*
 * Destroy a DACS cookie by munging COOKIE_STR, an HTTP Cookie header value.
 * Leave any other (non-DACS) cookies alone.
 * Return the number of deleted cookies.
 */
static int
dacs_cookie_zap(request_rec *r, char *cookie_str)
{
  int del_count;
  char *p, *q;
  dacs_server_config *sc_rec
	= (dacs_server_config *)
	ap_get_module_config(r->server->module_config, &auth_dacs_module);

  del_count = 0;
  q = p = cookie_str;
  while (1) {
	if (!is_dacs_cookie(p)) {
	  if (q != cookie_str) {
		/* Insert a separator. */
		*q++ = ';';
		*q++ = ' ';
	  }

	  /* Copy to the next cookie or the end. */
	  while (*p != '\0' && *p != ';')
		*q++ = *p++;

	  /* The last cookie will not be semi-colon-terminated. */
	  if (*p == '\0')
		break;

	  /* Advance past the semi-colon and any spaces that follow. */
	  p++;
	  while (*p == ' ')
		p++;
	  /* The last cookie will not be semi-colon-terminated. */
	  if (*p == '\0')
		break;
	}
	else {
	  /* Skip to the next cookie or the end. */
	  if (debug_enabled(sc_rec))
		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					 "dacs_cookie_zap: deleting DACS cookie");
	  del_count++;
	  if ((p = skip_cookie(p)) == NULL)
		break;
	}
  }
  *q = '\0';

  return(del_count);
}

static char *
have_proxy_auth(char *bufp)
{

  if (strncmp(bufp, "DACS-Proxy-Authorization=", 25) == 0)
	return(bufp + 25);

  return(NULL);
}

/*
 * DACS access control may have put newline separated strings of the form
 * keyword=value into the null-terminated env_buf.
 * Arrange for those strings to be put into the environment of the CGI
 * program.  Also, depending on ZAP_MODE, remove any DACS cookies from the
 * environment.
 */
static void
dacs_add_cgi_env(request_rec *r, char *bufp, DACS_cookie_zap_mode zap_mode,
				 char **saved_cookies)
{
  char *extname, *p, *name_start, *value_start;
  const char *c;
  apr_table_t *e = r->subprocess_env;
  dacs_request_rec *dacs_rec = get_dacs_request_rec(r);
  dacs_server_config *sc_rec
	= (dacs_server_config *)
	ap_get_module_config(r->server->module_config, &auth_dacs_module);
  dacs_dir_config *dc_rec
	= (dacs_dir_config *)
	ap_get_module_config(r->per_dir_config, &auth_dacs_module);

  /*
   * DACS credentials that are within HTTP_COOKIE should not be exported
   * lest someone poke around and find them in the environment of the CGI
   * program and then reuse them.
   * The individual cookies are separated by a ';' and a space.
   *
   * 9-Dec-03 Shouldn't delete if this request is an internal redirect,
   * otherwise cookies will get lost.
   * Don't know how to test for this specific case so ACS will tell us
   * what to do.
   *
   * Some number of directives, each beginning with a '=', may appear.
   * Following this block of directives is some number of environment
   * variables that we will export to the executable service.
   */
  if (zap_mode == DACS_COOKIE_FORCE_ZAP
	  || (zap_mode != DACS_COOKIE_NO_ZAP
		  && dacs_rec->granted && r->proxyreq != PROXYREQ_REVERSE)) {
	int i;
	const apr_array_header_t *hdrs_arr = apr_table_elts(r->headers_in);
	const apr_table_entry_t *hdrs = (const apr_table_entry_t *) hdrs_arr->elts;

  if (saved_cookies && *saved_cookies)
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "dacs_add_cgi_env: saved_cookies=\"%s\"", *saved_cookies);

	*saved_cookies = NULL;
	for (i = 0; i < hdrs_arr->nelts; i++) {
	  if (hdrs[i].key == NULL)
		continue;

	  /* In case the Cookie header appears more than once, continue. */
	  if (!strcasecmp(hdrs[i].key, "Cookie") && hdrs[i].val != NULL) {
		int del_count;

		if (*saved_cookies != NULL)
		  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					   "dacs_add_cgi_env: multiple Cookie headers!");
		*saved_cookies = apr_pstrdup(r->pool, hdrs[i].val);
		del_count = dacs_cookie_zap(r, hdrs[i].val);

		if (debug_enabled(sc_rec))
		  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					   "dacs_add_cgi_env: zap_mode=%d, del_count=%d, saved_cookies=\"%s\"",
					   zap_mode, del_count, *saved_cookies);
	  }
	}
	if (*saved_cookies == NULL)
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "dacs_add_cgi_env: no saved_cookies");
  }
  else if (saved_cookies != NULL && *saved_cookies != NULL) {
	apr_table_unset(r->headers_in, "Cookie");
	apr_table_set(r->headers_in, "Cookie", *saved_cookies);
	if (debug_enabled(sc_rec))
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "dacs_add_cgi_env: restored Cookie=\"%s\"", *saved_cookies);
  }

  if (bufp != NULL && have_proxy_auth(bufp) == NULL) {
	if (sc_rec->debug)
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "dacs_add_cgi_env: setting environment");
	name_start = bufp;
	while (name_start < (dacs_rec->env_buf + dacs_rec->env_buf_len)) {
	  if ((p = strchr(name_start, (int) '=')) == NULL)
		break;
	  *p++ = '\0';
	  value_start = p;
	  if ((p = strchr(value_start, (int) '\n')) == NULL)
		break;
	  *p++ = '\0';
	  apr_table_set(e, name_start, value_start);
	  name_start = p;
	}
  }

  /*
   * These are passed to dacs_acs as environment variables.
   * We cannot pass them through the pipe because dacs_acs needs them
   * early, before its input stream from mod_auth_dacs is processed.
   * -bjb 4-Oct-2007
   */
  if ((extname = dc_rec->dacs_extname) != NULL) {
	if ((c = apr_table_get(sc_rec->dacs_conf, extname)) != NULL)
	  apr_table_set(e, "DACS_CONF", c);
	if ((c = apr_table_get(sc_rec->dacs_site_conf, extname)) != NULL)
	  apr_table_set(e, "DACS_SITE_CONF", c);
  }

  apr_table_set(e, "DACS_MOD_AUTH_DACS", module_built);
  if (debug_enabled(sc_rec))
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "dacs_add_cgi_env: finished");
}

/*
 * Lookup a header in the request.
 */
static char *
lookup_header(const request_rec *r, const char *name)
{
  int i;
  const apr_array_header_t *hdrs_arr;
  const apr_table_entry_t *hdrs;

  hdrs_arr = apr_table_elts(r->headers_in);
  hdrs = (apr_table_entry_t *) hdrs_arr->elts;
  for (i = 0; i < hdrs_arr->nelts; ++i) {
	if (hdrs[i].key != NULL && strcasecmp(hdrs[i].key, name) == 0)
	  return(hdrs[i].val);
  }

  return(NULL);
}

#ifdef NOTDEF
static char *
list_headers(const request_rec *r, const apr_table_t *t)
{
  int i;
  char *list;
  const apr_array_header_t *hdrs_arr;
  const apr_table_entry_t *hdrs;

  hdrs_arr = apr_table_elts(t);
  hdrs = (apr_table_entry_t *) hdrs_arr->elts;

  list = "";
  for (i = 0; i < hdrs_arr->nelts; ++i) {
	if (hdrs[i].key != NULL)
	  list = apr_pstrcat(r->pool, list, (*list == '\0') ? "" : ",",
						 hdrs[i].key, NULL);
  }

  return(list);
}
#endif

/*
 * Carry out "instructions" returned by dacs_acs for modifying the request or
 * the behaviour of Apache.
 * The instructions are newline-terminated lines of text of the form:
 *   "=" <name> "=" <value>
 * where <name> is the type of operation to be performed (case sensitive)
 * and <value> is its argument.
 *
 * Parsing stops at the end of the buffer or if a line does not begin with
 * a '=' character.  ENDP is set to point to the character that caused
 * termination.
 *
 * XXX Unrecognized operations are ignored.
 * XXX consider switching to XML/JSON
 */
static void
acs_control(request_rec *r, dacs_request_rec *dacs_rec, int *http_status,
			DACS_cookie_zap_mode *zap_mode, int *check_only, char **endp)
{
  char *p, *q;
  dacs_server_config *sc_rec
	= (dacs_server_config *) ap_get_module_config(r->server->module_config,
												  &auth_dacs_module);

  p = NULL;
  *zap_mode = DACS_COOKIE_DO_ZAP;
  *check_only = 0;

  if (dacs_rec->env_buf_len > 0) {
	p = dacs_rec->env_buf;

	if (debug_enabled(sc_rec))
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "acs_control:'%s'", p);

	while (*p == '=' && (q = strchr(p + 1, (int) '\n')) != NULL) {
	  if (strncmp(p, "=pass_credentials=yes\n", q - p + 1) == 0) {
		*zap_mode = DACS_COOKIE_NO_ZAP;
		if (debug_enabled(sc_rec))
		  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					   "acs_control: ACS \"pass_credentials=yes\": disabled cookie zapping");
	  }
	  else if (strncmp(p, "=check_only=yes\n", q - p + 1) == 0) {
		*check_only = 1;
		if (debug_enabled(sc_rec))
		  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					   "acs_control: ACS check only");
	  }
	  else if (strncmp(p, "=Unset-DACS_ACS=", 16) == 0) {
		char *s;

		/*
		 * Remove the DACS_ACS argument from the query string.
		 * This involves editing r->args, r->unparsed_uri, r->parsed_uri->query
		 */
		*q = '\0';
		if (debug_enabled(sc_rec)) {
		  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					   "acs_control: unset DACS_ACS");
		  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					   " and: args=\"%s\", unparsed_uri=\"%s\"",
					   (r->args == NULL) ? "" : r->args,
					   (r->unparsed_uri == NULL) ? "" : r->unparsed_uri);
		  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					   " and: parsed_uri->query=\"%s\" the_request=\"%s\"",
					   (r->parsed_uri.query == NULL) ? "" : r->parsed_uri.query,
					   (r->the_request == NULL) ? "" : r->the_request);
		}
		p += 16;
		if (r->args != NULL)
		  r->args = apr_pstrdup(r->pool, p);

		if (r->unparsed_uri != NULL) {
		  if ((s = strchr(r->unparsed_uri, (int) '?')) != NULL) {
			*s = '\0';
			r->unparsed_uri = apr_psprintf(r->pool, "%s%s%s",
										   r->unparsed_uri,
										   (*p != '\0') ? "?" : "", p);
		  }
		}

		if (r->the_request != NULL) {
		  if ((s = strchr(r->the_request, (int) '?')) != NULL) {
			*s = '\0';
			r->the_request = apr_psprintf(r->pool, "%s%s%s",
										   r->the_request,
										   (*p != '\0') ? "?" : "", p);
		  }
		}

		if (r->parsed_uri.query != NULL)
		  r->parsed_uri.query = apr_pstrdup(r->pool, p);

		if (debug_enabled(sc_rec)) {
		  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					   "acs_control: args=\"%s\", unparsed_uri=\"%s\"",
					   (r->args == NULL) ? "" : r->args,
					   (r->unparsed_uri == NULL) ? "" : r->unparsed_uri);
		  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					   " and: parsed_uri->query=\"%s\"",
					   (r->parsed_uri.query == NULL)
					   ? "" : r->parsed_uri.query);
		}
	  }
	  else if (strncmp(p, "=Set-Header=", 12) == 0) {
		char *s;

		*q = '\0';
		if ((s = strchr(p + 12, (int) ':')) != NULL && *(s + 1) == ' ') {
		  *s = '\0';
		  /* This will replace any existing header. */
		  apr_table_set(r->err_headers_out, p + 12, s + 2);
		  if (debug_enabled(sc_rec))
			ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
						 "acs_control: ACS Set-Header: \"%s\" to \"%s\"",
						 p + 12, s + 2);
		  if (strcmp(p + 12, "Status") == 0) {
			*http_status = atoi(s + 2);
			if (debug_enabled(sc_rec))
			  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
						   "acs_control: Status: %d", *http_status);
		  }
		  else if (strcmp(p + 12, "WWW-Authenticate") == 0) {
			char *auth_name;

			if (strncasecmp(s + 2, "Basic ", 6) == 0) {
			  dacs_rec->http_auth = DACS_HTTP_AUTH_BASIC;
			  auth_name = "Basic";
			}
			else if (strncasecmp(s + 2, "Digest ", 6) == 0) {
			  dacs_rec->http_auth = DACS_HTTP_AUTH_DIGEST;
			  auth_name = "Digest";
			}
			else
			  auth_name = "Unknown";
			if (debug_enabled(sc_rec))
			  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
						   "acs_control: WWW-Authenticate is \"%s\"",
						   auth_name);
		  }
		}
		else {
		  /* ELSE invalid format error*/
		}
	  }
	  else if (strncmp(p, "=Add-Header=", 12) == 0) {
		char *s;

		/*
		 * Like Set-Header, but allow multiple values.
		 * It is up to the caller to know if this makes sense for the
		 * particular header.
		 */
		*q = '\0';
		if ((s = strchr(p + 12, (int) ':')) != NULL && *(s + 1) == ' ') {
		  *s = '\0';
		  apr_table_add(r->err_headers_out, p + 12, s + 2);
		  if (debug_enabled(sc_rec))
			ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
						 "acs_control: ACS Add-Header: \"%s\" to \"%s\"",
						 p + 12, s + 2);
		}
		else {
		  /* ELSE invalid format error*/
		}
	  }
	  else if (strncmp(p, "=Delete-Header=", 15) == 0) {
		/*
		 * XXX this does not seem to work - haven't looked at it closely yet.
		 * This should probably be done like mod_headers, i.e. by registering
		 * hooks to make changes at the correct time...
		 */
		*q = '\0';
		apr_table_unset(r->headers_in, p + 15);
		apr_table_unset(r->err_headers_out, p + 15);
		apr_table_unset(r->headers_out, p + 15);

		if (debug_enabled(sc_rec))
		  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					   "acs_control: ACS Delete-Header: \"%s\"", p + 15);
	  }
	  else if (strncmp(p, "=Set-Cookie=", 12) == 0) {
		*q = '\0';
		apr_table_add(r->err_headers_out, "Set-Cookie", p + 12);
		if (debug_enabled(sc_rec))
		  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					   "acs_control: ACS Set-Cookie: %s", p + 12);
	  }
	  else if (strncmp(p, "=Cache-Control=", 15) == 0) {
		*q = '\0';
		apr_table_add(r->err_headers_out, "Cache-Control", p + 15);
		if (debug_enabled(sc_rec))
		  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					   "acs_control: ACS Cache-Control: %s", p + 15);
	  }
	  else if (strncmp(p, "=User=", 6) == 0) {
		*q = '\0';
		r->user = apr_psprintf(r->pool, "%s", p + 6);
		if (debug_enabled(sc_rec))
		  ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
					   "acs_control: ACS set user: \"%s\"", p + 6);
	  }

	  p = q + 1;
	}
  }
  *endp = p;

  if (debug_enabled(sc_rec))
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "acs_control: cookie zapping is now %s",
				 (*zap_mode != DACS_COOKIE_NO_ZAP) ? "enabled" : "disabled");
}

static char *
base64encode(apr_pool_t *p, char *string, int len)
{ 
  int l;
  char *encoded;

  encoded = (char *) apr_palloc(p, 1 + apr_base64_encode_len(len));
  l = apr_base64_encode(encoded, string, len);
  encoded[l] = '\0';

  return(encoded);
}

#ifdef NOTDEF
typedef struct Export_header {
  request_rec *r;
  apr_file_t *outfile;
} Export_header;

static int
export_header(Export_header *exp, const char *key, const char *val)
{

  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, exp->r->server,
			   "export_header: header: \"%s\" -> \"%s\"", key, val);

  return(1);
}
#endif

static char *
proxyreq(request_rec *r)
{

  if (r->proxyreq == PROXYREQ_PROXY)
	return("proxy");

  if (r->proxyreq == PROXYREQ_REVERSE)
	return("proxy_reverse");

  if (r->proxyreq == PROXYREQ_RESPONSE)
	return("proxy_response");

  return("???error???");
}

/*
 * This helper for dacs_add_cgi_vars() return the original request from the
 * HTTP request line, or an empty string.
 * Return a newly allocated string.
 * RFC 2616 S5.1
 */
static char *
get_original_request_uri(request_rec *r)
{
  char *first, *last;

  if (r->the_request == NULL)
	return((char *) apr_pcalloc(r->pool, 1));

  /*
   * Request-Line = Method SP Request-URI SP HTTP-Version CRLF
   * Skip the Method, then all whitespace that immediately follows.
   */
  for (first = r->the_request; *first != '\0' && !apr_isspace(*first); first++)
	;

  while (apr_isspace(*first))
	first++;

  /*
   * The URI starts here.  Advance to the first whitespace character,
   * which ends the URI.
   */
  for (last = first; *last != '\0' && !apr_isspace(*last); last++)
	;

  /* Return a copy of the Request-URI. */
  return(apr_pstrmemdup(r->pool, first, last - first));
}

/*
 * Add standard CGI variables to the environment of the sub-request of R.
 *
 * Similar to server/util_script.c:ap_add_cgi_vars() but does not resolve
 * PATH_TRANSLATED, which shouldn't normally be needed by dacs_acs, because
 * it involves processing a sub-request.  Processing a sub-request here is
 * undesirable because 1) it is relatively expensive since dacs_acs is (may be?)
 * called again, 2) DACS cookies may have been suppressed when this
 * is called, making credentials mysteriously disappear when dacs_acs is called,
 * and 3) it is not clear that it is necessary (for DACS).
 * PATH_TRANSLATED is simply not passed to the sub-request..
 * Incidentally, doing the sub-request here causes a recursive call to
 * dacs_check_auth(), which could be problematic.
 */
static void
dacs_add_cgi_vars(request_rec *r)
{
  apr_table_t *sub_env = r->subprocess_env;

  apr_table_setn(sub_env, "GATEWAY_INTERFACE", "CGI/1.1");
  apr_table_setn(sub_env, "QUERY_STRING", (r->args != NULL) ? r->args : "");
  apr_table_setn(sub_env, "REQUEST_METHOD", r->method);
  apr_table_setn(sub_env, "REQUEST_URI", get_original_request_uri(r));
  apr_table_setn(sub_env, "SERVER_PROTOCOL", r->protocol);

  if (!strcmp(r->protocol, "INCLUDED")) {
	apr_table_setn(sub_env, "SCRIPT_NAME", r->uri);
	if (r->path_info != NULL && r->path_info[0] != '\0')
	  apr_table_setn(sub_env, "PATH_INFO", r->path_info);
  }
  else if (r->path_info == NULL || r->path_info[0] == '\0')
	apr_table_setn(sub_env, "SCRIPT_NAME", r->uri);
  else {
	int path_info_offset = ap_find_path_info(r->uri, r->path_info);

	apr_table_setn(sub_env, "SCRIPT_NAME",
				   apr_pstrndup(r->pool, r->uri, path_info_offset));
	apr_table_setn(sub_env, "PATH_INFO", r->path_info);
  }
}

/*
 * Invoke an external version of the DACS Access Control Service (ACS),
 * passing it various information about the current service request, including
 * a copy of some or all of the POST data stream, using IPC (not through
 * environment strings).
 * ACS returns an indication of whether access is to be granted or denied
 * and environment strings to be exported to an invoked CGI or servlet (if
 * granted) or how to handle the error condition (if denied).
 * Return the HTTP status code that's to be used.
 */
static int
exec_dacs_acs(request_rec *r, const char *extpath, const char *extargs,
			  char **saved_cookies)
{
  int ch, have_get_args, j;
  int i, nalloc, exitcode, need, rc;
  int is_cgi, is_proxy, is_servlet, is_wsgi;
  char *ext_args[DACS_SERVICE_NARGS];
  char *http_cookie, *p, *start_p;
  apr_proc_t *procnew;
  apr_procattr_t *procnew_attr;
  apr_status_t st;
  apr_size_t nbytes;
  apr_exit_why_e exitwhy;
  apr_table_t *old_tab;
  const char *remote_host;
  const char *ctype;
  const char *proxy_auth;
  conn_rec *c = r->connection;
  dacs_request_rec *dacs_rec;
  const char * const *child_env;
  const char **child_arg;
  const char *cp, *t;
  dacs_server_config *sc_rec
	= (dacs_server_config *)
	ap_get_module_config(r->server->module_config, &auth_dacs_module);

  for (j = 0; j < DACS_SERVICE_NARGS; j++)
	ext_args[j] = NULL;

  dacs_rec = get_dacs_request_rec(r);
  ctype = NULL;

  if (debug_enabled(sc_rec)) {
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "*** exec_dacs_acs: pid=%d, post=%d, env=%d ***",
				 getpid(), dacs_rec->post_buf_size, dacs_rec->env_buf_size);
    if (r->method_number == M_GET)
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "exec_dacs_acs: (r->method_number=M_GET)");
    else if (r->method_number == M_POST)
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "exec_dacs_acs: (r->method_number=M_POST)");
    else
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "exec_dacs_acs: (r->method_number=%d)", r->method_number);
    if (r->handler != NULL)
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "exec_dacs_acs: (r->handler=\"%s\")", r->handler);
    if (r->prev != NULL)
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "exec_dacs_acs: internal redirect");
    if (r->next != NULL)
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "exec_dacs_acs: external redirect");
    if (r->main != NULL)
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "exec_dacs_acs: sub-request");
  }

  /* Get the arguments, if necessary. */
  is_cgi = is_proxy = is_servlet = is_wsgi = 0;
  if (r->handler != NULL) {
	is_cgi = (strcmp(r->handler, "cgi-script") == 0);
	is_proxy = (strcmp(r->handler, "proxy-server") == 0);
	is_servlet = (strcmp(r->handler, "jakarta-servlet") == 0);
	is_wsgi = (strcmp(r->handler, "wsgi-script") == 0);
  }
  else
	is_cgi = 1;
  if (debug_enabled(sc_rec))
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "exec_dacs_acs: (cgi=%d, proxy=%d, servlet=%d, wsgi=%d)",
				 is_cgi, is_proxy, is_servlet, is_wsgi);

  /*
   * Capture query arguments, if present, to pass to DACS ACS.
   */
  have_get_args = (r->args != NULL && r->args[0] != '\0');
  need = dacs_rec->post_buf_size + 100;
  if (have_get_args)
	need += strlen(r->args);
  start_p = p = apr_palloc(r->pool, need);
  start_p[0] = '\0';
  if (have_get_args) {
	strcpy(p, r->args);
	p += strlen(p);
  }

  /*
   * If this is a method that is allowed to have a message body, such as
   * POST, get a copy of some or all of the data stream to pass to DACS ACS
   * so that it can do access control tests based its contents.
   * XXX Also handle WebDAV methods (RFC 2518/3253)
   */
  if (r->method_number == M_POST
	  || r->method_number == M_PUT
	  || r->method_number == M_OPTIONS) {
	int is_form_data, is_post_data, is_url_encoded;

	is_post_data = 1;
	is_form_data = is_url_encoded = 0;
	ctype = apr_table_get(r->headers_in, "Content-Type");
	if (ctype != NULL) {
	  char *ct_header, *ct_p;

	  /*
	   * Matching of media type and subtype is ALWAYS case-insensitive
	   * (RFC 2045, 5.1)
	   */
	  ct_header = apr_pstrdup(r->pool, ctype);
	  if ((ct_p = strchr(ct_header, (int) ';')) != NULL
		  || (ct_p = strchr(ct_header, (int) ' ')) != NULL
		  || (ct_p = strchr(ct_header, (int) '\t')) != NULL)
		*ct_p = '\0';
	  if (!strcasecmp(ct_header, "application/x-www-form-urlencoded")
		  || !strcasecmp(ct_header, "application/x-www-form-urlencoded"))
		is_url_encoded = 1;
	  else if (!strcasecmp(ct_header, "multipart/form-data"))
		is_form_data = 1;
	}

	if (debug_enabled(sc_rec)) {
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "exec_dacs_acs: POST, is_url_encoded=%d, is_form_data=%d",
				   is_url_encoded, is_form_data);
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "exec_dacs_acs: Content-Type is %s", ctype);
	}

	if ((is_cgi || is_proxy || is_servlet || is_wsgi)
		&& cgi_pre_handler(r) == -1) {
	  ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
				   "*** exec_dacs_acs: cgi_pre_handler error ***");
	  return(-5);
	}

	if (!dacs_rec->post_buf_eof) {
	  /* Did not see EOF on the stream... */
	  ext_args[DACS_SERVICE_ARGS_TRUNCATED_ARG]
		= apr_psprintf(r->pool, "SERVICE_ARGS_TRUNCATED=\"1\"");
	}

	if (debug_enabled(sc_rec))
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "exec_dacs_acs: post_buf_len=%d, post_buf_eof=%d",
				   dacs_rec->post_buf_len, dacs_rec->post_buf_eof);

	if (dacs_rec->post_buf != NULL && dacs_rec->post_buf_len > 0) {
	  ext_args[DACS_SERVICE_POSTDATA_ARG]
		= apr_psprintf(r->pool, "SERVICE_POSTDATA=\"%s\"",
					   base64encode(r->pool, dacs_rec->post_buf,
									dacs_rec->post_buf_len));
	}
	if (is_url_encoded)
	  p += sprintf(p, "%s%s", have_get_args ? "&" : "", dacs_rec->post_buf);
  }

  /* This copy must escape problematic chars, such as quotes. */
  ext_args[DACS_SERVICE_ARGS]
	= apr_psprintf(r->pool, "SERVICE_ARGS=\"%s\"",
				   base64encode(r->pool, start_p, strlen(start_p)));
  if (debug_enabled(sc_rec)) {
	if (strlen(start_p) > DACS_MAXIMUM_LOG_POST_SIZE) {
	  ch = start_p[DACS_MAXIMUM_LOG_POST_SIZE];
	  start_p[DACS_MAXIMUM_LOG_POST_SIZE] = '\0';
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "exec_dacs_acs: SERVICE_ARGS=\"%s\"...", start_p);
	  start_p[DACS_MAXIMUM_LOG_POST_SIZE] = ch;
	}
	else
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "exec_dacs_acs: SERVICE_ARGS=\"%s\"", start_p);
  }

  /* Give DACS the module's version information to ensure compatibility. */
  ext_args[DACS_MOD_AUTH_DACS_VERSION_ARG]
	= apr_psprintf(r->pool, "SERVICE_MOD_AUTH_DACS_VERSION=\"%s\"",
				   DACS_VERSION_RELEASE);

  if ((http_cookie = lookup_header(r, "Cookie")) != NULL) {
	ext_args[DACS_SERVICE_COOKIE_ARG]
	  = apr_psprintf(r->pool, "SERVICE_COOKIE=\"%s\"", http_cookie);
	if (debug_enabled(sc_rec))
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "exec_dacs_acs: %s", ext_args[DACS_SERVICE_COOKIE_ARG]);
  }

  if (ctype)
	ext_args[DACS_SERVICE_CONTENT_TYPE_ARG]
	  = apr_psprintf(r->pool, "SERVICE_CONTENT_TYPE=\"%s\"", ctype);
  if (r->uri) {
	ext_args[DACS_SERVICE_URI_ARG]
	  = apr_psprintf(r->pool, "SERVICE_URI=\"%s\"", r->uri);
  }
  if (r->method)
	ext_args[DACS_SERVICE_METHOD_ARG]
	  = apr_psprintf(r->pool, "SERVICE_METHOD=\"%s\"", r->method);
  if (r->parsed_uri.query)
	ext_args[DACS_SERVICE_QUERY_ARG]
	  = apr_psprintf(r->pool, "SERVICE_QUERY=\"%s\"", r->parsed_uri.query);
  if (r->filename)
	ext_args[DACS_SERVICE_FILENAME_ARG]
	  = apr_psprintf(r->pool, "SERVICE_FILENAME=\"%s\"", r->filename);
  if (r->proxyreq != PROXYREQ_NONE)
	ext_args[DACS_SERVICE_PROXYREQ_ARG]
	  = apr_psprintf(r->pool, "SERVICE_PROXYREQ=\"%s\"", proxyreq(r));
  if (r->path_info)
	ext_args[DACS_SERVICE_PATH_INFO_ARG]
	  = apr_psprintf(r->pool, "SERVICE_PATH_INFO=\"%s\"", r->path_info);

  remote_host = ap_get_remote_host(c, r->per_dir_config, REMOTE_HOST, NULL);
  if (remote_host != NULL)
	ext_args[DACS_SERVICE_REMOTE_HOST_ARG]
	  = apr_psprintf(r->pool, "SERVICE_REMOTE_HOST=\"%s\"", remote_host);

#ifdef APACHE_2_4_BUILD
  if (c->client_ip)
	ext_args[DACS_SERVICE_REMOTE_ADDR_ARG]
	  = apr_psprintf(r->pool, "SERVICE_REMOTE_ADDR=\"%s\"", c->client_ip);
#else
  if (c->remote_ip)
	ext_args[DACS_SERVICE_REMOTE_ADDR_ARG]
	  = apr_psprintf(r->pool, "SERVICE_REMOTE_ADDR=\"%s\"", c->remote_ip);
#endif

  if (r->hostname)
	ext_args[DACS_SERVICE_HOSTNAME_ARG]
	  = apr_psprintf(r->pool, "SERVICE_HOSTNAME=\"%s\"", r->hostname);

  if (ssl_is_ssl_request(r))
	ext_args[DACS_SERVICE_HTTPS_ARG]
	  = apr_psprintf(r->pool, "SERVICE_HTTPS=\"on\"");
  ext_args[DACS_SERVICE_SERVER_PORT_ARG] =
	apr_psprintf(r->pool, "SERVICE_PORT=\"%u\"", ap_get_server_port(r));
 
  if ((cp = apr_table_get(r->headers_in, "User-Agent")) != NULL)
	ext_args[DACS_SERVICE_USER_AGENT_ARG]
	  = apr_psprintf(r->pool, "SERVICE_USER_AGENT=\"%s\"", cp);

  if ((cp = apr_table_get(r->headers_in, "Content-Length")) != NULL)
	ext_args[DACS_SERVICE_CONTENT_LENGTH_ARG]
	  = apr_psprintf(r->pool, "SERVICE_CONTENT_LENGTH=\"%s\"", cp);

  if ((cp = apr_table_get(r->headers_in, "Content-Encoding")) != NULL)
	ext_args[DACS_SERVICE_CONTENT_ENCODING_ARG]
	  = apr_psprintf(r->pool, "SERVICE_CONTENT_ENCODING=\"%s\"", cp);

  /*
   * An Authorization request header may contain a bunch of fields
   * having double quote delimited values.
   * Surround this directive's value with single quotes to avoid
   * parsing problems in dacs_acs.
   * XXX Each directive's value should probably be base64 encoded.
   */
  if ((cp = apr_table_get(r->headers_in, "Authorization")) != NULL)
	ext_args[DACS_SERVICE_AUTHORIZATION_ARG]
	  = apr_psprintf(r->pool, "SERVICE_AUTHORIZATION='%s'", cp);

  if ((cp = apr_table_get(r->headers_in, "Proxy-Authorization")) != NULL)
	ext_args[DACS_SERVICE_PROXY_AUTHORIZATION_ARG]
	  = apr_psprintf(r->pool, "SERVICE_PROXY_AUTHORIZATION='%s'", cp);

  /*
   * When proxying, ACS may be called twice: once for the original service
   * request and once for the proxy_exec program that sets up and executes
   * the original service request.
   * In this case, the first call to ACS will lead to a
   * DACS-Proxy-Authorization header field being created.  We need to pass
   * this on to ACS when it is called the second time.
   */
  if ((proxy_auth = apr_table_get(r->headers_in, "DACS-Proxy-Authorization"))
	  != NULL)
	ext_args[DACS_PROXY_AUTH_ARG]
	  = apr_psprintf(r->pool, "SERVICE_PROXY_AUTH=\"%s\"", proxy_auth);

  /* Configure the child process' environment and arguments. */
  old_tab = r->subprocess_env;
  r->subprocess_env = apr_table_make(r->pool, 50);

  ap_add_common_vars(r);
  dacs_add_cgi_vars(r);			/* -bjb 21-Jan-2015 */

  if (ssl_is_ssl_request(r))
	ssl_hook_Fixup(r);		/* XXX This probably wasn't intended usage */

  /*
   * DACS cookies are always removed from the environment before invoking
   * dacs_acs so that they are not visible and easily copied.
   * If dacs_acs says so, however, they must later be restored into the
   * environment so that the DACS-wrapped CGI program can access them.
   */
  dacs_add_cgi_env(r, NULL, DACS_COOKIE_FORCE_ZAP, saved_cookies);

  child_env = (const char * const *)
	ap_create_environment(r->pool, r->subprocess_env);
  r->subprocess_env = old_tab;

  /*
   * Construct the argument vector.
   * We need a slot for the command path and one for the terminating NULL.
   */
  child_arg = apr_palloc(r->pool,
						 sizeof(char *) * DACS_INITIAL_CHILD_ARGS);
  nalloc = DACS_INITIAL_CHILD_ARGS;
  child_arg[0] = (char *) extpath;
  for (t = extargs, i = 1; t != NULL && t[0] != '\0'; i++) {
	if (i == (nalloc - 1)) {
	  const char **new;

	  new = apr_palloc(r->pool,
					   (nalloc + DACS_INCR_CHILD_ARGS) * sizeof(char *));
	  memcpy((void *) new, child_arg, nalloc * sizeof(char *));
	  child_arg = new;
	  nalloc += DACS_INCR_CHILD_ARGS;
	}
	child_arg[i] = ap_getword_conf(r->pool, &t);
  }
  child_arg[i] = NULL;

  /* Configure the child process' attributes. */
  st = apr_procattr_create(&procnew_attr, r->pool);
  if (st != APR_SUCCESS) {
	char buf[120];
	rc = -1;

 procattr_fail:
	ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
				 "exec_dacs_acs: apr_procattr_* failed, rc=%d", rc);
	ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
				 "exec_dacs_acs: %s (st=%d)",
				 apr_strerror(st, buf, sizeof(buf)), st);
	return(-3);
  }
  st = apr_procattr_io_set(procnew_attr, APR_FULL_BLOCK, APR_FULL_BLOCK,
						   APR_NO_PIPE);
  if (st != APR_SUCCESS) {
	rc = -2;
	goto procattr_fail;
  }
  st = apr_procattr_dir_set(procnew_attr,
							ap_make_dirstr_parent(r->pool, child_arg[0]));
  if (st != APR_SUCCESS) {
	rc = -3;
	goto procattr_fail;
  }
  st = apr_procattr_cmdtype_set(procnew_attr, APR_PROGRAM);
  if (st != APR_SUCCESS) {
	rc = -4;
	goto procattr_fail;
  }
  st = apr_procattr_detach_set(procnew_attr, 0);
  if (st!= APR_SUCCESS) {
	rc = -5;
	goto procattr_fail;
  }

  procnew = apr_pcalloc(r->pool, sizeof(*procnew));
  if (debug_enabled(sc_rec))
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "exec_dacs_acs: executing \"%s\"", child_arg[0]);
  st = apr_proc_create(procnew, child_arg[0], child_arg, child_env,
					   procnew_attr, r->pool);
  if (st != APR_SUCCESS) {
	rc = -6;
	goto procattr_fail;
  }

  /* The parent process. */

  apr_pool_note_subprocess(r->pool, procnew, APR_KILL_AFTER_TIMEOUT);
  apr_file_pipe_timeout_set(procnew->out, r->server->timeout);

  for (j = 0; j < DACS_SERVICE_NARGS; j++) {
	if (ext_args[j] != NULL) {
	  nbytes = strlen(ext_args[j]);
	  apr_file_write_full(procnew->in, ext_args[j], nbytes, NULL);
#ifdef NOTDEF
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "  %s", ext_args[j]);
#endif
	  nbytes = 1;
	  apr_file_write_full(procnew->in, "\n", nbytes, NULL);
	}
  }

#ifdef NOTDEF
  {
	char *list = list_headers(r, r->headers_in);
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "HTTP Request Headers: %s", list);
  }
#endif

#ifdef NOTDEF
  /*
   * Headers are already exported by httpd as (renamed) environment variables,
   * so there's probably no need to pass them explicitly, except perhaps as a
   * server-independent measure.  But just to show how it would be done...
   * Iterate through each HTTP request header just so that we can pass
   * them on.
   */
  {
	Export_header exports;

	exports.r = r;
	exports.outfile = procnew->in;

	apr_table_do((int (*) (void *, const char *, const char *))
				 export_header, &exports, r->headers_in, NULL);
  }
#endif

  /* Make it null-terminated. */
  nbytes = 2;
  apr_file_write_full(procnew->in, "\n", nbytes, NULL);
  apr_file_close(procnew->in);

  /*
   * Read the environment for a CGI program, if any.
   * The buffer will later be processed by dacs_add_cgi_env().
   */
  st = apr_file_read_full(procnew->out, dacs_rec->env_buf,
						  dacs_rec->env_buf_size - 1, &nbytes);
  if (st != APR_SUCCESS && st != APR_EOF) {
	ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
				 "exec_dacs_acs: error reading env from ACS, nbytes=%lu",
				(unsigned long) nbytes);
	rc = -1;
  }
  else if (st != APR_EOF) {
	ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
				 "exec_dacs_acs: too much env read from ACS");
	rc = -1;
  }
  else {
	dacs_rec->env_buf[nbytes] = '\0';
	dacs_rec->env_buf_len = nbytes;
	rc = 0;
	if (debug_enabled(sc_rec))
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "exec_dacs_acs: read %lu bytes from ACS",
				   (unsigned long) nbytes);
  }

  apr_file_close(procnew->out);

  /* Wait for the process to exit and examine the exit code. */
  st = apr_proc_wait(procnew, &exitcode, &exitwhy, APR_WAIT);
  rc = (st == APR_CHILD_DONE && APR_PROC_CHECK_EXIT(exitwhy)) ? exitcode : -2;
  if (debug_enabled(sc_rec))
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "*** exec_dacs_acs: rc=%d, st=%d, why=%x(%d/%d/%d), code=%d",
				 rc, st, exitwhy, APR_PROC_CHECK_EXIT(exitwhy),
				 APR_PROC_CHECK_SIGNALED(exitwhy),
				 APR_PROC_CHECK_CORE_DUMP(exitwhy), exitcode);

  return(rc);
}

/*
 * Code for an internal DACS goes here.
 */
static int
exec_internal(request_rec *r, const char *extpath)
{

  ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
			   "exec_internal: Unsupported authentication method: \"internal\"");
  return(HTTP_INTERNAL_SERVER_ERROR);
}

/*
 * If there is a POST argument stream, we need to grab it here
 * (at most post_buf_size bytes of it).
 */
static int
cgi_pre_handler(request_rec *r)
{
  int need, rc;
  unsigned long nread;
  dacs_request_rec *dacs_rec = get_dacs_request_rec(r);
  apr_bucket *bucket;
  apr_bucket_brigade *bb;
  apr_status_t rv;
  dacs_server_config *sc_rec
	= (dacs_server_config *)
	ap_get_module_config(r->server->module_config, &auth_dacs_module);

  if (debug_enabled(sc_rec))
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "cgi_pre_handler: (pid=%d)", getpid());

  dacs_rec->post_buf_eof = 0;
  dacs_rec->post_buf_len = 0;
  dacs_rec->post_buf[0] = '\0';

  /*
   * XXX This is a work-around for the case where there is nothing in the
   * POST data stream.  It seems to work but needs more testing.  See below.
   */
  if ((rv = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK)
	return(-1);
  if (ap_should_client_block(r) == 0) {
	/* No POST data stream - don't block below! */
	dacs_rec->post_buf_eof = 1;
	return(0);
  }

  /* Are we configured to not look at the POST data stream?  */
  if (dacs_rec->post_buf_size == 0) {
	if (debug_enabled(sc_rec))
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "cgi_pre_handler: ignoring POST data");
	/* There is something there, but we've been asked not to look at it. */
	dacs_rec->post_buf_eof = 0;
	return(0);
  }

  rc = 0;
  need = dacs_rec->post_buf_size;

  bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);

  nread = 0;
  do {
	apr_size_t len;
	const char *data;

    /*
     * XXX Note that we use AP_MODE_SPECULATIVE here, which is a bit unusual.
     * This seems to work in "common use" but needs to be tested in more
     * complicated situations...
     *
     * I've got to say at this point that I have yet to find any really decent,
     * comprehensive documentation for APR's bucket API.  This makes doing
     * "new" things with it, like this, frustrating.
     *
     * XXX This code is broken if it turns out that the POST data stream
     * is empty and if (I think) the request comes from a browser.
     * It seems that AP_MODE_SPECULATIVE will not return an
     * empty/EOS condition in this case, it will just block.
     * If you use AP_MODE_READBYTES in this context, it will not block.
     * A possible solution might be to use AP_MODE_READBYTES mode and
     * then "unread" it (copy what is read back into a bucket and then insert
     * that bucket back at the head of the bucket brigade).
	 *
	 * See:
	 *  http://marc.theaimsgroup.com/?l=apache-httpd-dev&m=111455033610662&w=2
	 * regarding AP_MODE_SPECULATIVE and EOS.
     */

	if (debug_enabled(sc_rec))
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "cgi_pre_handler: need=%d", need);

	/*
	 * A brigade is a doubly-linked list, where the tail points back to the
	 * head (i.e., a ring).
	 * The sentinel is a special bucket before the first bucket and
	 * after the last bucket; APR_BRIGADE_SENTINEL(bb) returns the magic pointer
	 * value that is the sentinel for brigade bb.  Any apr_bucket value
	 * for brigade bb that is equal to APR_BRIGADE_SENTINAL(bb) represents the
	 * end of the brigade.
	 * APR_BRIGADE_EMPTY(bb) is true if brigade bb is empty
	 * APR_BRIGADE_FIRST(bb) returns a pointer to the first bucket in brigade bb
	 * APR_BRIGADE_LAST(bb) returns a pointer to the last bucket in brigade bb
	 * An EOS bucket indicates that there is no more data coming.
	 */
	rv = ap_get_brigade(r->input_filters, bb, AP_MODE_SPECULATIVE,
						APR_BLOCK_READ, need);
	if (rv != APR_SUCCESS) {
	  ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, r->server,
				   "cgi_pre_handler: ap_get_brigade failed");
	  rc = -1;
	  break;
	}

	if (APR_BRIGADE_EMPTY(bb)) {
	  if (debug_enabled(sc_rec))
		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					 "cgi_pre_handler: empty");
	  dacs_rec->post_buf_eof = 1;
	  break;
	}

	for (bucket = APR_BRIGADE_FIRST(bb); bucket != APR_BRIGADE_SENTINEL(bb);
		 bucket = APR_BUCKET_NEXT(bucket)) {
	  if (APR_BUCKET_IS_EOS(bucket)) {
		if (debug_enabled(sc_rec))
		  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					   "cgi_pre_handler: EOS");
		dacs_rec->post_buf_eof = 1;
		break;
	  }

	  if (debug_enabled(sc_rec))
		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					 "cgi_pre_handler: reading...");

   	  rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
      if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					 "cgi_pre_handler: read failed");
		rc = -1;
		break;
	  }

	  if (len) {
		memcpy(dacs_rec->post_buf + dacs_rec->post_buf_len, data, len);
		dacs_rec->post_buf_len += len;
		need -= len;
		nread += len;
	  }

	  if (debug_enabled(sc_rec))
		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					 "cgi_pre_handler: read %lu (%lu)",
					 (unsigned long) len, nread);

	  if (need == 0) {
        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, r->server,
					 "cgi_pre_handler: post_buf full, no EOS");
		break;
	  }
    }
  } while (0);
  apr_brigade_cleanup(bb);

  dacs_rec->post_buf[dacs_rec->post_buf_len] = '\0';

  if (rc == 0 && !dacs_rec->post_buf_eof) {
	const char *lenp;
	char *endstr;
	apr_off_t clen;

#ifdef NOTDEF
	while (need) {
	  apr_size_t len;
	  const char *data;

	  rv = ap_get_brigade(r->input_filters, bb, AP_MODE_SPECULATIVE,
						  APR_BLOCK_READ, need);
	  if (rv != APR_SUCCESS) {
		ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, r->server,
					 "mod_auth_dacs: second ap_get_brigade failed");
		break;
	  }
	  else if (APR_BRIGADE_EMPTY(bb)) {
		if (debug_enabled(sc_rec))
		  ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, r->server,
					   "mod_auth_dacs: second cgi_pre_handler empty");
		break;
	  }

	  for (bucket = APR_BRIGADE_FIRST(bb); bucket != APR_BRIGADE_SENTINEL(bb);
		   bucket = APR_BUCKET_NEXT(bucket)) {
		if (APR_BUCKET_IS_EOS(bucket)) {
		  need = 0;
		  break;
		}
		rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
			 "mod_auth_dacs: %o%o%o%o", data[0], data[1], data[2], data[3]);
		need -= len;
		nread += len;
		if (debug_enabled(sc_rec))
		  ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
					   "mod_auth_dacs: cgi_pre_handler read %lu (%lu)",
					   (unsigned long) len, nread);
	  }
	  apr_brigade_cleanup(bb);
	}
#endif

	ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
				 "cgi_pre_handler: end of data but no EOS...");

	lenp = apr_table_get(r->headers_in, "Content-Length");
	errno = 0;
	clen = strtol(lenp, &endstr, 10);
	if (errno || (endstr && *endstr) || clen < 0) {
	  ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, r->server,
				   "cgi_pre_handler: Content-Length conversion error: %s", lenp);
	}
	else {
	  if (dacs_rec->post_buf_len == clen) {
		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					 "cgi_pre_handler: read content length=%lu bytes",
					 (unsigned long) clen);
		dacs_rec->post_buf_eof = 1;
	  }
	}
  }

  return(rc);
}

static int debug_sleep_secs = 15;

/*
 * Check for both user authentication and authorization.
 * This may be called recursively.
 */
static int
dacs_check_auth(request_rec *r)
{
  int entry_num, http_status, rc;
  char *p, *saved_cookies, *response;
  const char *extargs, *extname, *extpath, *dacs_method;
  DACS_cookie_zap_mode zap_mode;
  dacs_dir_config *dc_rec
	= (dacs_dir_config *)
	ap_get_module_config(r->per_dir_config, &auth_dacs_module);
  dacs_server_config *sc_rec
	= (dacs_server_config *)
	ap_get_module_config(r->server->module_config, &auth_dacs_module);
  dacs_request_rec *dacs_rec = (dacs_request_rec *)
		ap_get_module_config(r->request_config, &auth_dacs_module);
  static int entry_count = 0;

  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
			   "***** dacs_check_auth: enter %d *****", ++entry_count);
  entry_num = entry_count;

  if (debug_enabled(sc_rec)) {
	char *c_uri = apr_pstrdup(r->pool, r->uri);

	ap_getparents(c_uri);
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "(did_dacs_check=%d, r->uri=\"%s\", c_uri=\"%s\")",
				 (dacs_rec != NULL) ? dacs_rec->did_dacs_check : 0,
				 r->uri, c_uri);
    if (r->handler != NULL)
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "dacs_check_auth: (r->handler=\"%s\")", r->handler);
    if (r->prev != NULL)
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "dacs_check_auth: internal redirect");
    if (r->next != NULL)
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "dacs_check_auth: external redirect");
    if (r->main != NULL)
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "dacs_check_auth: sub-request");
  }

  /* Extract which external was chosen. */
  extname = dc_rec->dacs_extname;

  /* Check if DACS is supposed to handle this request. */
  if (!extname) {
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "dacs_check_auth: declined (not wrapped)");
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "***** dacs_check_auth: finish %d *****", entry_num);
	return(DECLINED);
  }

  /* Get the path associated with that external. */
  if (!(extpath = apr_table_get(sc_rec->dacs_extpath, extname))) {
	ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
				 "dacs_check_auth: invalid AuthDACS keyword (%s)", extname);
	ap_note_auth_failure(r);
	/*
	 * Returning HTTP_UNAUTHORIZED seems to automatically result in the user
	 * being prompted for username/password, which isn't what we want.
	 */
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "***** dacs_check_auth: finish %d *****", entry_num);
	return(HTTP_FORBIDDEN);
  }

  /*
   * If this request has already been processed by DACS, do not try again.
   */
  if (dacs_rec != NULL && dacs_rec->did_dacs_check) {
  	dacs_rec->did_dacs_check++;
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "dacs_check_auth: declined (no re-check)");
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "***** dacs_check_auth: finish %d *****", entry_num);
	return(DECLINED);
  }

  dacs_method = apr_table_get(sc_rec->dacs_method, extname);
  extargs = apr_table_get(sc_rec->dacs_extargs, extname);

  saved_cookies = NULL;
  if (dacs_method && !strcasecmp(dacs_method, "internal"))
	rc = exec_internal(r, extpath);
  else
	rc = exec_dacs_acs(r, extpath, extargs, &saved_cookies);

  {
	char *http_cookie;

	if (saved_cookies)
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "dacs_check_auth: after exec_dacs_acs, saved_cookies=\"%s\"",
				   saved_cookies);
	if ((http_cookie = lookup_header(r, "Cookie")) != NULL)
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "dacs_check_auth: Cookie=\"%s\"", http_cookie);
  }

  if (rc)
	http_status = HTTP_FORBIDDEN;
  else
	http_status = HTTP_OK;

  /* Process control directives sent by dacs_acs. */
  dacs_rec = get_dacs_request_rec(r);
  dacs_rec->check_only = 0;
  zap_mode = DACS_COOKIE_DO_ZAP;
  response = NULL;
  acs_control(r, dacs_rec, &http_status, &zap_mode, &dacs_rec->check_only,
		&response);
  if (debug_enabled(sc_rec))
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "dacs_check_auth: zap_mode=%d, check_only=%d",
				 zap_mode, dacs_rec->check_only);

  /* Note that this request has been processed by DACS. */
  dacs_rec->did_dacs_check = 1;

  if (rc != 0) {
	size_t len;

	/* Access is being denied. */
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "dacs_check_auth: access denied, reset buf, rc=%d", rc);
	dacs_rec->post_buf_len = 0;
	if (response != NULL) {
	  len = strlen(response);
	  if (!dacs_rec->check_only && len > 0 && response[len - 1] == '\n') {
		char *msg = apr_pstrdup(r->pool, response);
		char *type;

		/* Zap the newline. */
		msg[len - 1] = '\0';
		/* See set_error_document(). */
		if (ap_strchr_c(msg, (int) ' '))
		  type = "message";
		else if (msg[0] == '/')
		  type = "local URL";
		else if (ap_is_url(msg))
		  type = "absolute URL";
		else
		  type = "message";
		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					 "dacs_check_auth: setting custom %d response (%s): \"%s\"",
					 http_status, type, msg);
		ap_custom_response(r, http_status, msg);
	  }
	}
  }
  else {
	/* Access is being granted. */

	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "dacs_check_auth: access granted");
	if (dacs_rec->check_only) {
	}
	else {
	  dacs_rec->granted = 1;
	  dacs_add_cgi_env(r, response, zap_mode, &saved_cookies);

	  /*
	   * If this is a sub-request, it seems necessary to restore the Cookie
	   * header in the parent, otherwise DACS cookies can be lost.
	   */
	  if (r->main != NULL) {
		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					 "dacs_check_auth: restoring parent");
		dacs_add_cgi_env(r->main, NULL, DACS_COOKIE_NO_ZAP, &saved_cookies);
	  }
	}
	http_status = HTTP_OK;
  }

  if (dacs_rec->check_only) {
	dacs_rec->post_buf_len = 0;
	if (response != NULL) {
	  size_t len;

	  len = strlen(response);
	  if (len == 0 || response[len - 1] != '\n') {
		/* Invalid, so ignore it. */
		response = NULL;
	  }
	}

	if (http_status == HTTP_OK) {
	  ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
				   "dacs_check_auth: ACS will grant access");

	  if (response == NULL)
		response = "798 Access granted\n";
	}
	else {
	  ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
				   "dacs_check_auth: ACS will deny access");

	  if (response == NULL)
		response = "797 Access denied\n";
	}
	
	ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
				 "dacs_check_auth: setting custom %d response (%s): \"%s\"",
				 http_status,
				 ap_is_url(response) ? "URL" : "not URL", response);
	ap_custom_response(r, HTTP_OK, response);

	r->status = HTTP_OK;
	ap_note_basic_auth_failure(r);

	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "***** dacs_check_auth: finish %d *****", entry_num);
	return(HTTP_OK);
  }

  if (http_status == HTTP_OK) {
	if (r->proxyreq == PROXYREQ_REVERSE) {
	  dacs_request_rec *dacs_rec;

	  if ((dacs_rec = get_dacs_request_rec(r)) == NULL
		  || dacs_rec->env_buf == NULL) {
		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					 "***** dacs_check_auth: finish %d *****", entry_num);
		return(HTTP_FORBIDDEN);
	  }

	  /*
	   * In a proxy situation, ACS returns environment variables that must be
	   * passed on to the CGI program
	   * The header field "Proxy-Authorization" is normally stripped off by
	   * Apache and not passed on to a CGI script, so we can't use it.
	   */
	  if ((p = have_proxy_auth(dacs_rec->env_buf)) != NULL) {
		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
					 "dacs_check_auth: Adding Proxy-Authorization");
		apr_table_set(r->headers_in, "DACS-Proxy-Authorization",
					  apr_psprintf(r->pool, "%s", p));
	  }
	}
	if (debug_enabled(sc_rec))
	  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				   "dacs_check_auth: OK");
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "***** dacs_check_auth: finish %d *****", entry_num);
	return(OK);
  }

  ap_note_basic_auth_failure(r);

  ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
   "dacs_check_auth: %s [%s]: Failed: http_status=%d,r->status=%d,user=\"%s\"",
			   extname, extpath, http_status, r->status,
			   (r->user != NULL) ? r->user : "UNKNOWN");

  /*
   * Note that returning HTTP_UNAUTHORIZED results in the user being
   * automatically prompted for username/password, which isn't usually what
   * we want.
   */
  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
			   "***** dacs_check_auth: finish %d *****", entry_num);
  return(http_status);
}

/*
 * For DACS, this is done as part of authorization checking.
 */
static int
dacs_check_user_id(request_rec *r) 
{
  char *extname;
  dacs_request_rec *dacs_rec = (dacs_request_rec *)
		ap_get_module_config(r->request_config, &auth_dacs_module);
  dacs_dir_config *dc_rec
	= (dacs_dir_config *) ap_get_module_config(r->per_dir_config,
											   &auth_dacs_module);
  dacs_server_config *sc_rec
	= (dacs_server_config *) ap_get_module_config(r->server->module_config,
												  &auth_dacs_module);

  if (debug_enabled(sc_rec)) {
	char *c_uri = apr_pstrdup(r->pool, r->uri);

	ap_getparents(c_uri);
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "dacs_check_user_id: call %d, uri=\"%s\" (\"%s\"), file=\"%s\"",
				(dacs_rec != NULL) ? dacs_rec->did_dacs_check : 0,
				 r->uri, c_uri, r->filename);
  }

  /* Extract which external was chosen. */
  extname = dc_rec->dacs_extname;

  /* Check if we are supposed to handle this request. */
  if (!extname) {
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "dacs_check_user_id: declined");
	return(DECLINED);
  }

#ifdef OLD
  if (dacs_rec != NULL && dacs_rec->did_dacs_check)
  	dacs_rec->did_dacs_check++;
#else
  if (dacs_rec != NULL) {
	if (dacs_rec->did_dacs_check && !dacs_rec->granted
		&& dacs_rec->http_auth != DACS_HTTP_AUTH_NONE)
	  return(HTTP_UNAUTHORIZED);
  }
#endif

  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "dacs_check_user_id: access granted");
  return(OK);
}

static int
dacs_init_handler(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
				  server_rec *s)
{
  char *str;
  dacs_server_config *sc_rec
	= (dacs_server_config *)
	ap_get_module_config(s->module_config, &auth_dacs_module);

#if defined(DACS_VERSION_NUMBER) && defined(DACS_VERSION_RELEASE)
  if (debug_enabled(sc_rec))
	str = apr_psprintf(p, "mod_auth_dacs/%s(%s)",
					   DACS_VERSION_RELEASE, DACS_VERSION_DATE);
  else
	str = apr_psprintf(p, "mod_auth_dacs/%s", DACS_VERSION_RELEASE);
  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
		"dacs_init_handler: using %s", str);
  ap_add_version_component(p, str);
#else
#if DACS_VERSION_NUMBER != 0
  ap_add_version_component(p, "+mod_auth_dacs");
#endif

#endif

#ifndef REQUIRE_PATCHED_SSL
  rewrite_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
#endif

#if defined(APACHE_2_4_BUILD)
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
				 "dacs_init_handler: built for Apache 2.4");
#elif defined(APACHE_2_2_BUILD)
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
				 "dacs_init_handler: built for Apache 2.2");
#else
#error "mod_auth_dacs must be built for either Apache 2.2 or 2.4"
#endif
  return(OK);
}

#ifdef APACHE_2_4_BUILD
static const char *
dacs_parse_config(cmd_parms *cmd, const char *require_line,
				  const void **parsed_require_line)
{
  const char *t, *w;
  int count = 0;
  apr_pool_t *ptemp = cmd->temp_pool;

  t = require_line;
  while ((w = ap_getword_conf(ptemp, &t)) && w[0]) {
	fprintf(stderr, "mod_auth_dacs Require: \"%s\"\n", w);
	/*
	 */
	count++;
  }

#ifdef NOTDEF
  if (count != 0)
	return("'Require dacs' takes no arguments");
#endif

  return(NULL);
}

static const char *
dacs_authn_ap_auth_type(request_rec *r)
{

  return("Basic");
}

static const char *
dacs_authn_ap_auth_name(request_rec *r)
{

  return(NULL);
}

static authz_status
dacs_check_authorization(request_rec *r, const char *require_line,
						 const void *parsed_require_line)
{
  int st;
  static int entry_count = 0;

#ifdef NOTDEF
  /* apr_ipsubnet_test should accept const but doesn't */
  apr_ipsubnet_t **ip = (apr_ipsubnet_t **)parsed_require_line;

  while (*ip) {
	if (apr_ipsubnet_test(*ip, r->useragent_addr))
	  return AUTHZ_GRANTED;
	ip++;
  }

  /* authz_core will log the require line and the result at DEBUG */
  return(AUTHZ_DENIED);
#endif

  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
			   "dacs_check_authorization: enter %d", ++entry_count);
  st = dacs_check_auth(r);

#ifdef APACHE_2_4_BUILD
  if (st == HTTP_PROXY_AUTHENTICATION_REQUIRED) {
	r->status = st;
	r->user = "";
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "dacs_check_authorization: AUTHZ_DENIED_NO_USER");
	return(AUTHZ_DENIED_NO_USER);
  }

  if (st == HTTP_UNAUTHORIZED) {
	/*
	 * HTTP Status Code is 401, which means, confusingly, that the "request
	 * requires user authentication" (RFC 2616 10.4.2).
	 *
	 * authn_ap_auth_type(r) can be called to return the authorization type.
	 * authn_ap_auth_name(r) can be called to return the authorization realm.
	 * See aaa/mod_auth_basic.c and server/core.c
	 */
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "dacs_check_authorization: AUTHZ_DENIED_NO_USER");
	return(AUTHZ_DENIED_NO_USER);
  }
#endif

  if (st == HTTP_OK || st == OK) {
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "dacs_check_authorization: AUTHZ_GRANTED");
	return(AUTHZ_GRANTED);
  }

	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "dacs_check_authorization: AUTHZ_DENIED");
  return(AUTHZ_DENIED);
}

static const authz_provider authz_dacs_provider =
{
  &dacs_check_authorization,
  NULL,
};
#endif

/*
 * This handler is called, after authentication/authorization, to
 * terminate request processing when DACS is only supposed to report
 * its access control decision, not actually execute the request.
 * By returning zero, it tells the control logic to stop calling module
 * handlers and just wrap things up.  If this were not done, any other
 * modules in the chain would do their normal thing, which is exactly what
 * the user asked DACS not to do.
 * 31-July-2015
 */
static int
dacs_handler(request_rec *r)
{
 dacs_request_rec *dacs_rec = (dacs_request_rec *)
		ap_get_module_config(r->request_config, &auth_dacs_module);

  if (dacs_rec == NULL || !dacs_rec->check_only)
    return(DECLINED);

  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
				 "dacs_handler: enforce check_only");

  ap_die(200, r);
  return(0);
}

static void
dacs_register_hooks(apr_pool_t *p)
{
#ifdef APACHE_2_4_BUILD
  /*
   * Make the old provider name an alias for "dacs-authz" to avoid having
   * to modify Apache config files (specifically, "Require dacs" is
   * equivalent to "Require dacs-authz").
   *
   * "If your module requires the old behavior and must perform access control
   * checks on every sub-request with a different URI from the initial
   * request, even if that URI matches the same set of access control
   * configuration directives, then use AP_AUTH_INTERNAL_PER_URI."
   */
  ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "dacs",
			AUTHZ_PROVIDER_VERSION, &authz_dacs_provider,
			AP_AUTH_INTERNAL_PER_URI);

  ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "dacs-authz",
			AUTHZ_PROVIDER_VERSION, &authz_dacs_provider,
			AP_AUTH_INTERNAL_PER_URI);

  ap_hook_check_authn(dacs_check_user_id, NULL, NULL, APR_HOOK_MIDDLE,
			AP_AUTH_INTERNAL_PER_URI);

  ap_hook_handler(dacs_handler, NULL, NULL, APR_HOOK_REALLY_FIRST);

  ap_hook_post_config(dacs_init_handler, NULL, NULL, APR_HOOK_MIDDLE);
#else
  const char *const *aszPost;
  const char *const *aszPre;

#ifdef APACHE_2_2_BUILD
  {
	static const char *const post[] = {
			"mod_authz_user.c", "mod_authz_host.c", NULL
	};

	aszPre = NULL;
	aszPost = post;
  }
#else
  {
	/* Apache 2.0 and earlier is unsupported and untested. */
	static const char *const post[] = { "mod_access.c", "mod_auth.c", NULL };

	aszPre = NULL;
	aszPost = post;
  }
#endif

  ap_hook_post_config(dacs_init_handler, NULL, NULL, APR_HOOK_MIDDLE);

  /*
   * "Determine whether the remote host is permitted access"
   * Used by standard module: mod_authz_host.c
   *
   * This was re-enabled for Apache 2.2.27 (but may also apply to older
   * releases of Apache that were never tested with DACS) because
   * dacs_check_auth() was not being called via ap_hook_auth_checker(),
   * meaning that resources that should be (and were, in previous releases of
   * Apache 2.2) DACS-wrapped suddenly were not.
   */
  if (is_apache_2_2_build) {
      ap_hook_handler(dacs_handler, NULL, NULL, APR_HOOK_FIRST);
	  ap_hook_access_checker(dacs_check_auth, aszPre, aszPost, APR_HOOK_MIDDLE);
  }

#ifdef NOTDEF
/*
NOTE:
Somewhere between Apache 2.2.22 and Apache 2.2.27 a change outside of DACS
occurred that resulted in a subtle change to module processing behaviour.
Areas that were previously DACS-wrapped no longer were, with exactly the
same configuration.

To fix this, now ap_hook_access_checker() is called and ap_hook_check_user_id()
is not, and "Require valid-user" should be omitted.
ap_hook_auth_checker() is called as before.

dacs_check_auth() will now notice if it is called more than once for the
same request and decline them.  Apache should not be configured in such a way
that DACS is invoked more than once for the same request because the
final outcome may not be predictable (e.g., whether or not the user is
redirected).

These changes do not apply to Apache 2.4.
*/
#endif

  if (is_apache_2_2_build) {
	/*
	 * "Determine who the remote user is and verify the password"
	 * Used by standard modules:
	 * mod_auth_basic.c, mod_auth_digest.c, mod_authn_default.c
	 */
	ap_hook_check_user_id(dacs_check_user_id, aszPre, aszPost, APR_HOOK_MIDDLE);
  }

#ifdef NOTDEF
  /*
   * "Determine whether the identified remote user is permitted access"
   * Used by standard modules:
   * mod_authz_default.c, mod_authz_groupfile.c, mod_authz_owner.c,
   * mod_authz_user.c
   */
  ap_hook_auth_checker(dacs_check_auth, aszPre, aszPost, APR_HOOK_MIDDLE);
#endif

#endif
}

module AP_MODULE_DECLARE_DATA auth_dacs_module = {
  STANDARD20_MODULE_STUFF,
  dacs_create_dir_config,		/* dir config creater */
  NULL,			 				/* dir merger -- default is to override */
  dacs_create_server_config,	/* server config */
  dacs_merge_server_config,		/* merge server config */
  dacs_cmds,					/* command table */
  dacs_register_hooks			/* register hooks */
};
