[ic] 2002-07-30 debian unstable w/mod_ssl breaks vanilla mod_interchange

interchange-users@icdevgroup.org interchange-users@icdevgroup.org
Tue Jul 30 17:34:01 2002


Today's apt-get upgrade broke apache/mod_ssl and the vanilla
mod_interchange setup our our debian linux unstable machines.

The infamous:
Tue Jul 30 12:05:56 2002] [error] mod_ssl: SSL error on writing data (OpenSSL library error follows)
[Tue Jul 30 12:05:56 2002] [error] OpenSSL: error:1409F07F:SSL routines:SSL3_WRITE_PENDING:bad write retry
[Tue Jul 30 12:05:56 2002] [error] access to /Catalog/slash.cgi/order failed for 68.10.169.9, reason: error while sending response


The catalogs worked just fine for Mozilla, but broke in random places
on IE.  Go figure.

The fix was the new mod_interchange from kevin@cursor.

We had to modify that, however, **removing** a number of fixups from
the code, breaking at least the URILevels.  The version we got working
is below sig.


-- 

Christopher F. Miller, Publisher                               cfm@maine.com
MaineStreet Communications, Inc           208 Portland Road, Gray, ME  04039
1.207.657.5078                                         http://www.maine.com/
Content/site management, online commerce, internet integration, Debian linux



/*
 *	$Id: mod_interchange.c,v 1.5 2002/04/23 04:15:37 kevin Exp $
 *
 *	Apache Module implementation of the Interchange application server
 *	link programs.
 *
 *	Version: 1.21
 *
 *	Author: Kevin Walsh <kevin@cursor.biz>
 *	Based on original code by Francis J. Lacoste <francis.lacoste@iNsu.COM>
 *
 *	Copyright (c) 1999 Francis J. Lacoste, iNsu Innovations.
 *	Copyright (c) 2000-2002 Cursor Software Limited.
 *	All rights reserved.
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *	02111-1307 USA
 */
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_main.h"
#include "http_protocol.h"
#include "util_script.h"

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#ifdef	OSX
typedef long socklen_t;
#endif

#ifndef	AF_LOCAL
#define	AF_LOCAL	AF_UNIX
#endif

#ifndef	PF_LOCAL
#define	PF_LOCAL	PF_UNIX
#endif

#ifndef SUN_LEN
#define SUN_LEN(su)	(sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
#endif

#define IC_DEFAULT_PORT			7786
#define IC_DEFAULT_ADDR			"127.0.0.1"
#define	IC_DEFAULT_TIMEOUT		10
#define	IC_DEFAULT_LEVELS		1
#define	IC_DEFAULT_CONNECT_TRIES	10
#define	IC_DEFAULT_CONNECT_RETRY_DELAY	2

#define	IC_MAX_DROPLIST			10
#define IC_MAX_SERVERS			2

module MODULE_VAR_EXPORT interchange_module;

typedef struct ic_socket_struct{
	struct sockaddr *sockaddr; /* socket to the Interchange server */
	int family;		/* the socket family in use */
	socklen_t size;		/* the size of the socket structure */
	char *address;		/* human-readable form of the address */
}ic_socket_rec;

typedef struct ic_conf_struct{
	ic_socket_rec *server[IC_MAX_SERVERS];	/* connection to IC server(s) */
	int levels;		/* URI directory levels to pass to IC */
	int connect_tries;	/* number of times to ret to connect to IC */
	int connect_retry_delay; /* delay this many seconds between retries */
	int droplist_no;
	char droplist[IC_MAX_DROPLIST][HUGE_STRING_LEN];
}ic_conf_rec;

typedef struct ic_response_buffer_struct{
	int buff_size;
	int pos;
	char buff[HUGE_STRING_LEN];
}ic_response_buffer;

static void *ic_create_dir_config(pool *,char *);
static const char *ic_server_cmd(cmd_parms *,void *,const char *);
static const char *ic_serverbackup_cmd(cmd_parms *,void *,const char *);
static const char *ic_server_setup(cmd_parms *,void *,int,const char *arg);
static const char *ic_urilevels_cmd(cmd_parms *,void *,const char *);
static const char *ic_connecttries_cmd(cmd_parms *,void *,const char *);
static const char *ic_connectretrydelay_cmd(cmd_parms *,void *,const char *);
static BUFF *ic_connect(request_rec *,ic_conf_rec *);
static int ic_select(int,int,int,int);
static int ic_send_request(request_rec *,ic_conf_rec *,BUFF *);
static int ic_transfer_response(request_rec *,BUFF *);
static int ic_handler(request_rec *);

/*
 *	ic_create_dir_config()
 *	----------------------
 *	This module's per-directory config creator.
 *	Sets up the default configuration for this location,
 *	which can be overridden using the module's configuration
 *	directives
 */
static void *ic_create_dir_config(pool *p,char *dir)
{
	struct sockaddr_in *inet_sock;
	int i;

	ic_conf_rec *conf_rec = (ic_conf_rec *)ap_pcalloc(p,sizeof(ic_conf_rec));
	if (conf_rec == NULL)
		return NULL;

	/*
	 *	the default connection method is INET to localhost
	 */
	inet_sock = (struct sockaddr_in *)ap_pcalloc(p,sizeof(struct sockaddr_in));
	if (inet_sock == NULL)
		return NULL;

	inet_sock->sin_family = AF_INET;
	inet_aton(IC_DEFAULT_ADDR,&inet_sock->sin_addr);
	inet_sock->sin_port = htons(IC_DEFAULT_PORT);

	conf_rec->server[0] = (ic_socket_rec *)ap_pcalloc(p,sizeof(ic_socket_rec));
	if (conf_rec->server[0] == NULL)
		return NULL;

	conf_rec->server[0]->sockaddr = (struct sockaddr *)inet_sock;
	conf_rec->server[0]->size = sizeof (struct sockaddr_in);
	conf_rec->server[0]->family = PF_INET;
	conf_rec->server[0]->address = IC_DEFAULT_ADDR;

	for (i = 1; i < IC_MAX_SERVERS; i++)
		conf_rec->server[i] = (ic_socket_rec *)NULL;

	conf_rec->levels = 1;
	conf_rec->connect_tries = IC_DEFAULT_CONNECT_TRIES;
	conf_rec->connect_retry_delay = IC_DEFAULT_CONNECT_RETRY_DELAY;
	conf_rec->droplist_no = 0;

	return conf_rec;
}

/*
 *	ic_server_cmd()
 *	---------------
 *	Handle the "InterchangeServer" module configuration directive
 */
static const char *ic_server_cmd(cmd_parms *parms,void *mconfig,const char *arg)
{
	return ic_server_setup(parms,mconfig,0,arg);
}

/*
 *	ic_serverbackup_cmd()
 *	---------------------
 *	Handle the "InterchangeServerBackup" module configuration directive
 */
static const char *ic_serverbackup_cmd(cmd_parms *parms,void *mconfig,const char *arg)
{
	ic_conf_rec *conf_rec = (ic_conf_rec *)mconfig;

	conf_rec->server[1] = (ic_socket_rec *)ap_pcalloc(parms->pool,sizeof(ic_socket_rec));
	if (conf_rec->server[1] == NULL)
		return "not enough memory for backup socket record";

	return ic_server_setup(parms,mconfig,1,arg);
}

/*
 *	ic_server_setup()
 *	-----------------
 *	Do the actual primary/backup server setup on behalf of the
 *	ic_server_cmd() and ic_serverbackup_cmd() functions.
 */
static const char *ic_server_setup(cmd_parms *parms,void *mconfig,int server,const char *arg)
{
	static char errmsg[100];

	ic_conf_rec *conf_rec = (ic_conf_rec *)mconfig;
	ic_socket_rec *sock_rec = conf_rec->server[server];

	sock_rec->address = ap_pstrdup(parms->pool,arg);
	if (sock_rec->address == NULL)
		return "not enough memory for the socket address";

	/*
	 *	verify type of the argument, which will indicate
	 *	whether we should be using a UNIX or Inet socket
	 *	to connect to the Interchange server
	 */
	if (*arg == '/'){
		/*
		 *	this is to be a UNIX socket
		 */
		struct sockaddr_un *unix_sock;

		unix_sock = (struct sockaddr_un *)ap_pcalloc(parms->pool,sizeof(struct sockaddr_un));
		if (unix_sock == NULL){
			sprintf(errmsg,"not enough memory for %s UNIX socket structure",server ? "primary" : "backup");
			return errmsg;
		}

		unix_sock->sun_family = AF_LOCAL;
		ap_cpystrn(unix_sock->sun_path,sock_rec->address,sizeof(unix_sock->sun_path));
		sock_rec->sockaddr = (struct sockaddr *)unix_sock;
		sock_rec->size = SUN_LEN(unix_sock);
		sock_rec->family = PF_LOCAL;
	}else{
		/*
		 *	this is to be an INET socket
		 *
		 *	the argument is an IP address or hostname followed by
		 *	an optional port specification
		 */
		struct sockaddr_in *inet_sock;
		char **hostaddress;
		char *hostname;

		inet_sock = (struct sockaddr_in *)ap_pcalloc(parms->pool,sizeof(struct sockaddr_in));
		if (inet_sock == NULL){
			sprintf(errmsg,"not enough memory for %s INET socket structure",server ? "primary" : "backup");
			return errmsg;
		}

		inet_sock->sin_family = AF_INET;
		hostaddress = &(sock_rec->address);
		hostname = ap_getword_nc(parms->temp_pool,hostaddress,':');

		if (!inet_aton(hostname,&inet_sock->sin_addr)){
			/*
			 *	address must point to a hostname
			 */
			struct hostent *host = ap_pgethostbyname(parms->temp_pool,hostname);
			if (!host)
				return "invalid hostname specification";

			memcpy(&inet_sock->sin_addr,host->h_addr,sizeof(inet_sock->sin_addr));
		}

		/*
		 *	check if a port number has been specified
		 */
		if (**hostaddress){
			int port = atoi(*hostaddress);

			if (port <= 100 || port > 65535)
				return "invalid port specification";

			inet_sock->sin_port = htons(port);
		}else{
			inet_sock->sin_port = htons(IC_DEFAULT_PORT);
		}

		sock_rec->sockaddr = (struct sockaddr *)inet_sock;
		sock_rec->family = PF_INET;
		sock_rec->size = sizeof(struct sockaddr_in);
	}
	return NULL;
}

/*
 *	ic_urilevels_cmd()
 *	------------------
 *	Handle the "URILevels" module configuration directive
 */
static const char *ic_urilevels_cmd(cmd_parms *parms,void *mconfig,const char *arg)
{
	ic_conf_rec *conf_rec = (ic_conf_rec *)mconfig;

	conf_rec->levels = atoi(arg);
	return NULL;
}

/*
 *	ic_connecttries_cmd()
 *	---------------------
 *	Handle the "ConnectTries" module configuration directive
 */
static const char *ic_connecttries_cmd(cmd_parms *parms,void *mconfig,const char *arg)
{
	ic_conf_rec *conf_rec = (ic_conf_rec *)mconfig;

	conf_rec->connect_tries = atoi(arg);
	return NULL;
}

/*
 *	ic_connectretrydelay_cmd()
 *	--------------------------
 *	Handle the "ConnectRetries" module configuration directive
 */
static const char *ic_connectretrydelay_cmd(cmd_parms *parms,void *mconfig,const char *arg)
{
	ic_conf_rec *conf_rec = (ic_conf_rec *)mconfig;

	conf_rec->connect_retry_delay = atoi(arg);
	return NULL;
}

/*
 *	ic_droprequest_cmd()
 *	--------------------
 *	Handle the "DropRequestList" module configuration directive
 */
static const char *ic_droprequestlist_cmd(cmd_parms *parms,void *mconfig,const char *arg)
{
	ic_conf_rec *conf_rec = (ic_conf_rec *)mconfig;

	if (conf_rec->droplist_no < IC_MAX_DROPLIST)
		strcpy(conf_rec->droplist[conf_rec->droplist_no++],arg);

	return NULL;
}

/*
 *	ic_connect()
 *	------------
 *	Connect to the Interchange server
 */
static BUFF *ic_connect(request_rec *r,ic_conf_rec *conf_rec)
{
	BUFF *ic_buff;
	ic_socket_rec *sock_rec;
	int ic_sock,retry,srv;
	int connected = 0;

	/*
	 *	connect the new socket to the Interchange server
	 *
	 *	if the connection to the Interchange server fails then
	 *	retry IC_DEFAULT_CONNECT_TRIES times, sleeping for
	 *	IC_DEFAULT_CONNECT_RETRY_DELAY seconds between each retry
	 */
	for (retry = 0; retry != conf_rec->connect_tries; retry++){
		for (srv = 0; srv != IC_MAX_SERVERS; srv++){
			if ((sock_rec = conf_rec->server[srv]) == NULL)
				break;
			if (srv){
				ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,"Attempting to connect to backup server %d",srv);
			}

			/*
			 *	attempt to connect to the Interchange server
			 */
			ic_sock = ap_psocket(r->pool,sock_rec->family,SOCK_STREAM,0);
			if (ic_sock < 0){
				ap_log_reason("socket",r->uri,r);
				return NULL;
			}
			ap_hard_timeout("ic_connect",r);
			if (connect(ic_sock,sock_rec->sockaddr,sock_rec->size) >= 0){
				connected++;
				break;
			}
			ap_kill_timeout(r);
			ap_pclosesocket(r->pool,ic_sock);
		}
		if (connected)
		    break;
		sleep(conf_rec->connect_retry_delay);
	}
	ap_kill_timeout(r);
	if (retry == conf_rec->connect_tries){
		ap_log_reason("Connection failed",r->uri,r);
		return NULL;
	}

	/*
	 *	create an Apache BUFF structure for our new connection
	 */
	ic_buff = ap_bcreate(r->pool,B_RDWR|B_SOCKET);
	if (!ic_buff){
		ap_log_reason("failed to create BUFF",r->uri,r);
		return NULL;
	}
	ap_bpushfd(ic_buff,ic_sock,ic_sock);
	return ic_buff;
}

/*
 *	ic_select()
 *	-----------
 *	Convenient wrapper for select().
 *	Wait for data to become available on the socket, or
 *	for an error to occur, and return the appropriate status
 *	code to the calling function.
 */
static int ic_select(int sock_rd,int sock_wr,int secs,int usecs)
{
	fd_set sock_set_rd,sock_set_wr;
	fd_set *rd = NULL,*wr = NULL;
	struct timeval tv;
	int rc;

	do{
		if (sock_rd > 0){
			FD_ZERO(&sock_set_rd);
			FD_SET(sock_rd,&sock_set_rd);
			rd = &sock_set_rd;
		}
		if (sock_wr > 0){
			FD_ZERO(&sock_set_wr);
			FD_SET(sock_wr,&sock_set_wr);
			wr = &sock_set_wr;
		}

		tv.tv_sec= secs;
		tv.tv_usec = usecs;
		rc = ap_select(((sock_rd > sock_wr) ? sock_rd : sock_wr) + 1,rd,wr,NULL,&tv);
	}while (rc == 0);
	return rc;
}

/*
 *	ic_send_request()
 *	-----------------
 *	Send the client's page/form request to the Interchange server
 */
static int ic_send_request(request_rec *r,ic_conf_rec *conf_rec,BUFF *ic_buff)
{
	char **env,**e;
	int env_count,rc,level;
	char request_uri[HUGE_STRING_LEN],*rurip = request_uri;
	request_uri[0] = '\0';

	/*
	 *	send the Interchange-link arg parameter
	 *	(this is always empty for a CGI request)
	 */
	ap_hard_timeout("ic_send_request",r);
	if (ap_bputs("arg 0\n",ic_buff) < 0){
		ap_log_reason("error writing to Interchange",r->uri,r);
		return HTTP_INTERNAL_SERVER_ERROR;
	}
	ap_reset_timeout(r);

	/*
	 *	initialize the environment to send to Interchange
	 */
	ap_add_common_vars(r);
	ap_add_cgi_vars(r);
	env = ap_create_environment(r->pool,r->subprocess_env);

	/*
	 *	count the number of environment variables present
	 *	(ignore the PATH_INFO and REDIRECT_* variables)
	 */
	for (e = env,env_count = 0; *e != NULL; e++,env_count++){
	  ;
	}
	/*	env_count++;  cfm or do we need one for the count below? */

	/*
	 *	send the actual environment variables to Interchange
	 *
	 *	send the environment variable count to Interchange
	 */
	if (ap_bprintf(ic_buff,"env %d\n",env_count) < 0){
		ap_log_reason("error writing to Interchange",r->uri,r);
		return HTTP_INTERNAL_SERVER_ERROR;
	}
	ap_reset_timeout(r);

	level = conf_rec->levels;
	for (e = env; *e != NULL; ++e){
	  /* ap_log_reason("ENV ",*e,r); */ /* cfm */
	  if (ap_bprintf(ic_buff,"%d %s\n",strlen(*e),*e) < 0){
	    ap_log_reason("error writing to Interchange",r->uri,r);
	    return HTTP_INTERNAL_SERVER_ERROR;
	  }
	}

	ap_reset_timeout(r);

	/*
	 *	send the request body, if any
	 */
	if (ap_should_client_block(r)){
		char buffer[HUGE_STRING_LEN];
		int len_read;
		long length = r->remaining;

		if (ap_bprintf(ic_buff,"entity\n%ld ",length) < 0){
			ap_log_reason("error writing to Interchange",r->uri,r);
			return HTTP_INTERNAL_SERVER_ERROR;
		}

		/*
		 *	read a block of data from the client and send
		 *	it to the Interchange server, until there
		 *	is nothing more to read from the client
		 */
		while ((len_read = ap_get_client_block(r,buffer,sizeof(buffer))) > 0){
			ap_reset_timeout(r);

			if (ap_bwrite(ic_buff,buffer,len_read) != len_read){
				ap_log_reason("error writing client block to Interchange",r->uri,r);
				return HTTP_INTERNAL_SERVER_ERROR;
			}
			ap_reset_timeout(r);
		}
		if (len_read < 0){
			ap_log_reason("error reading block from client",r->uri,r);
			return HTTP_INTERNAL_SERVER_ERROR;
		}

		/*
		 *	send an end of line character to Interchange
		 */
		if (ap_bputc('\n',ic_buff) < 0){
			ap_log_reason("error writing to Interchange",r->uri,r);
			return HTTP_INTERNAL_SERVER_ERROR;
		}	
	}

	/*
	 *	all data has been sent, so send the "end" marker
	 */
	if (ap_bputs("end\n",ic_buff) < 0){
		ap_log_reason("error writing the end marker to Interchange",r->uri,r);
		return HTTP_INTERNAL_SERVER_ERROR;
	}
	ap_reset_timeout(r);
	if (ap_bflush(ic_buff) < 0){
		ap_log_reason("error flushing data to Interchange",r->uri,r);
		return HTTP_INTERNAL_SERVER_ERROR;
	}

	ap_kill_timeout(r);
	/* ap_log_reason("DEBUG 99",r->uri,r); */
	return OK;
}

/*
 *	ic_transfer_response()
 *	----------------------
 *	Read the response from the Interchange server
 *	and relay it to the client
 */
static int ic_transfer_response(request_rec *r,BUFF *ic_buff)
{
	const char *location;
	BUFF *client_buff = r->connection->client;
	int rc,ic_sock;
	char sbuf[MAX_STRING_LEN],argsbuffer[HUGE_STRING_LEN];

	/*
	 *	get the socket we are using to talk to the
	 *	Interchange server, and wait for Interchange to
	 *	send us some data
	 */
	ic_sock = ap_bfileno(ic_buff,B_RD);
	rc = ic_select(ic_sock,0,IC_DEFAULT_TIMEOUT,0);
	if (rc < 0){
		ap_log_reason("Timeout on Interchange header read",r->uri,r);
		return HTTP_INTERNAL_SERVER_ERROR;
	}

	/*
	 *	check the HTTP header to make sure that it looks valid
	 */
	if (ap_scan_script_header_err_buff(r,ic_buff,sbuf)){
		ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,"Malformed header return by Interchange: %s",sbuf);
		return HTTP_INTERNAL_SERVER_ERROR;
	}

	/*
	 *	check the header for an HTTP redirect request
	 */
	location = ap_table_get(r->headers_out,"Location");
	if (r->status == 200 && location){
		fd_set sock_set;

		/*
		 *	check if we need to do an external redirect
		 *	(this is usually the case if an Interchange
		 *	[bounce] tag has been used)
		 */
		if (*location != '/')
			return REDIRECT;

		/*
		 *	we are here because we need to do an internal redirect
		 *
		 *	soak up any data from the Interchange socket
		 */
		rc = ic_select(ic_sock,0,IC_DEFAULT_TIMEOUT,0);
		if (rc < 0){
			ap_log_reason("Select timeout on Interchange socket read",r->uri,r);
			return HTTP_INTERNAL_SERVER_ERROR;
		}

		/*
		 *	soak up any body-text sent by the Interchange server
		 */
		ap_soft_timeout("mod_interchange: Interchange read",r);
		while (ap_bgets(argsbuffer,HUGE_STRING_LEN,ic_buff) > 0)
			;
		ap_kill_timeout(r);

		/*
		 *	always use the GET method for internal redirects
		 *	also, unset the Content-Length so that nothing
		 *	else tries to re-read the text we just soaked up
		 */
		r->method = ap_pstrdup(r->pool,"GET");
		r->method_number = M_GET;
		ap_table_unset(r->headers_in,"Content-Length");
		ap_internal_redirect_handler(location,r);
		return OK;
	}

	/*
	 *	we were not redirected, so send the HTTP headers
	 *	to the client
	 */
	ap_hard_timeout("mod_interchange: Client write",r);
	ap_send_http_header(r);
	if (ap_rflush(r) < 0){
		ap_log_reason("error sending headers to client",r->uri,r);
		return HTTP_INTERNAL_SERVER_ERROR;
	}

	/*
	 *	if Interchange is sending body text (HTML), then
	 *	relay this to the client
	 */
	if (!r->header_only){
		ap_reset_timeout(r);
		if ((rc = ap_bnonblock(ic_buff,B_RD)) != 0){
			ap_log_reason("error turning non blocking I/O on Interchange socket",r->uri,r);
			return HTTP_INTERNAL_SERVER_ERROR;
		}
		ap_bsetflag(ic_buff,B_SAFEREAD,1);
		if (ap_send_fb(ic_buff,r) <= 0){
			ap_log_reason("error sending response body to client",r->uri,r);
			return HTTP_INTERNAL_SERVER_ERROR;
		}
	}
	ap_kill_timeout(r);

	/*
	 *	close the Interchange socket and return
	 */
	ap_bclose(ic_buff);
	return OK;
}

/*
 *	ic_handler()
 *	------------
 *	module content handler
 */
static int ic_handler(request_rec *r)
{
	ic_conf_rec *conf_rec;
	BUFF *ic_buff;
	int i,result;

	if (r->method_number == M_OPTIONS){
		r->allowed |= (1 << M_GET);
		r->allowed |= (1 << M_POST);
		return DECLINED;
	}

	if ((result = ap_setup_client_block(r,REQUEST_CHUNKED_ERROR)) != OK)
		return result;

	/*
	 *	get our configuration
	 */
	conf_rec = (ic_conf_rec *)ap_get_module_config(r->per_dir_config,&interchange_module);
	if (!conf_rec){
		ap_log_reason("interchange-handler not configured properly",r->uri,r);
		return HTTP_INTERNAL_SERVER_ERROR;
	}

	/*
	 *	check if the requested URI matches strings
	 *	in the drop list
	 */
	for (i = 0; i < conf_rec->droplist_no; i++){
		if (strstr(r->uri,conf_rec->droplist[i])){
			ap_log_reason("interchange-handler match found in the drop list",r->uri,r);
			ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,"Requested URI (%s) matches drop list entry (%s)",r->uri,conf_rec->droplist[i]);
			return DECLINED;
		}
	}

	/*
	 *	connect to the Interchange server
	 */
	ic_buff = ic_connect(r,conf_rec);
	if (!ic_buff)
		return HTTP_INTERNAL_SERVER_ERROR;

	/*
	 *	send the client's request to Interchange
	 */
	result = ic_send_request(r,conf_rec,ic_buff);
	if (result != OK)
		return result;

	/*
	 *	receive the response from the Interchange server
	 *	and relay that response to the client
	 */
	return ic_transfer_response(r,ic_buff);
}

/*
 *	the module's configuration directives
 */
static command_rec ic_cmds[] ={
	{
		"InterchangeServer",	/* directive name */
		ic_server_cmd,		/* config action routine */
		NULL,			/* argument to include in call */
		ACCESS_CONF,		/* where available */
		TAKE1,			/* arguments */
		"Address of the primary Interchange server - for use in a <Location> block"
					/* directive description */
	},
	{
		"InterchangeServerBackup",	/* directive name */
		ic_serverbackup_cmd,		/* config action routine */
		NULL,			/* argument to include in call */
		ACCESS_CONF,		/* where available */
		TAKE1,			/* arguments */
		"Address of the backup Interchange server - for use in a <Location> block"
					/* directive description */
	},
	{
		"URILevels",		/* directive name */
		ic_urilevels_cmd,	/* config action routine */
		NULL,			/* argument to include in call */
		ACCESS_CONF,		/* where available */
		TAKE1,			/* arguments */
		"The number of URI directory levels to pass on to Interchange"
					/* directive description */
	},
	{
		"ConnectTries",		/* directive name */
		ic_connecttries_cmd,	/* config action routine */
		NULL,			/* argument to include in call */
		ACCESS_CONF,		/* where available */
		TAKE1,			/* arguments */
		"The number of connection attempts to make before giving up"
					/* directive description */
	},
	{
		"ConnectRetryDelay",	/* directive name */
		ic_connectretrydelay_cmd, /* config action routine */
		NULL,			/* argument to include in call */
		ACCESS_CONF,		/* where available */
		TAKE1,			/* arguments */
		"The number of connection attempts to make before giving up"
					/* directive description */
	},
	{
		"DropRequestList",	/* directive name */
		ic_droprequestlist_cmd,	/* config action routine */
		NULL,			/* argument to include in call */
		ACCESS_CONF,		/* where available */
		ITERATE,		/* arguments */
		"Drop the URI request if it contains the specified string"
					/* directive description */
	},
	{NULL}
};

/*
 *	make the name of the content handler known to Apache
 */
static handler_rec ic_handlers[] ={
	{"interchange-handler",ic_handler},
	{NULL}
};

/*
 *	tell Apache what phases of the transaction we handle
 */
module MODULE_VAR_EXPORT interchange_module ={
	STANDARD_MODULE_STUFF,
	NULL,			/* module initializer                 */
	ic_create_dir_config,	/* per-directory config creator       */
	NULL,			/* dir config merger                  */
	NULL,			/* server config creator              */
	NULL,			/* server config merger               */
	ic_cmds,		/* command table                      */
	ic_handlers,		/* [7]  content handlers              */
	NULL,			/* [2]  URI-to-filename translation   */
	NULL,			/* [5]  check/validate user_id        */
	NULL,			/* [6]  check user_id is valid *here* */
	NULL,			/* [4]  check access by host address  */
	NULL,			/* [7]  MIME type checker/setter      */
	NULL,			/* [8]  fixups                        */
	NULL,			/* [9]  logger                        */
	NULL,			/* [3]  header parser                 */
	NULL,			/* process initialization             */
	NULL,			/* process exit/cleanup               */
	NULL			/* [1]  post read_request handling    */
};