static char rcsid[] = "sample-bquery.c,v 1.3 1995/11/01 20:33:52 duane Exp";
/*
 *  sample-bquery.c - httpd-based CGI program that processes a user query
 *  from the Mosaic forms interface, sends the query to the appropriate
 *  broker, and then sends the result set back to the user.
 * 
 *  Note that NCSA httpd 1.3 waits for the CGI program to completely 
 *  exit before it sends any data to the client.  Oh well...
 *
 *  This used to be BrokerQuery.cgi before BrokerQuery.pl.cgi...
 *  It is no longer maintained, but it is included as an example CGI.
 *
 *  Usage:  Run from cgi-bin directory under httpd server
 *
 *  Supports the following CGI tags:
 *      host            If defined, specifies the broker host
 *      port            If defined, specifies the broker port number
 *      query           The query string
 *      class           Will AND the keywords to the end of "query"
 *      caseflag        If defined, adds '#index case insensitive' to flags
 *                      otherwise,  adds '#index case sensitive' to flags
 *      wordflag        If defined, adds '#index matchword' to flags
 *      errorflag       If defined, adds '#index error number' to flags
 *      maxresultflag   If defined, adds '#index maxresult number' to flags
 *      opaqueflag      If defined, adds '#opaque' to flags
 *      descflag        If defined, adds '#desc' to flags
 *      verbose         If defined, prints URLs concisely
 *      csumflag        If defined, prints Content Summary links
 *      version         If defined, sends BrokerQuery version.
 *
 *  Formats the results as an <OL> HTML+ list.
 *
 *  Bill Camargo & Darren Hardy, July 1994  
 *
 *  ----------------------------------------------------------------------
 *  Copyright (c) 1994, 1995.  All rights reserved.
 *  
 *    The Harvest software was developed by the Internet Research Task
 *    Force Research Group on Resource Discovery (IRTF-RD):
 *  
 *          Mic Bowman of Transarc Corporation.
 *          Peter Danzig of the University of Southern California.
 *          Darren R. Hardy of the University of Colorado at Boulder.
 *          Udi Manber of the University of Arizona.
 *          Michael F. Schwartz of the University of Colorado at Boulder.
 *          Duane Wessels of the University of Colorado at Boulder.
 *  
 *    This copyright notice applies to software in the Harvest
 *    ``src/'' directory only.  Users should consult the individual
 *    copyright notices in the ``components/'' subdirectories for
 *    copyright information about other software bundled with the
 *    Harvest source code distribution.
 *  
 *  TERMS OF USE
 *    
 *    The Harvest software may be used and re-distributed without
 *    charge, provided that the software origin and research team are
 *    cited in any use of the system.  Most commonly this is
 *    accomplished by including a link to the Harvest Home Page
 *    (http://harvest.cs.colorado.edu/) from the query page of any
 *    Broker you deploy, as well as in the query result pages.  These
 *    links are generated automatically by the standard Broker
 *    software distribution.
 *    
 *    The Harvest software is provided ``as is'', without express or
 *    implied warranty, and with no support nor obligation to assist
 *    in its use, correction, modification or enhancement.  We assume
 *    no liability with respect to the infringement of copyrights,
 *    trade secrets, or any patents, and are not responsible for
 *    consequential damages.  Proper use of the Harvest software is
 *    entirely the responsibility of the user.
 *  
 *  DERIVATIVE WORKS
 *  
 *    Users may make derivative works from the Harvest software, subject 
 *    to the following constraints:
 *  
 *      - You must include the above copyright notice and these 
 *        accompanying paragraphs in all forms of derivative works, 
 *        and any documentation and other materials related to such 
 *        distribution and use acknowledge that the software was 
 *        developed at the above institutions.
 *  
 *      - You must notify IRTF-RD regarding your distribution of 
 *        the derivative work.
 *  
 *      - You must clearly notify users that your are distributing 
 *        a modified version and not the original Harvest software.
 *  
 *      - Any derivative product is also subject to these copyright 
 *        and use restrictions.
 *  
 *    Note that the Harvest software is NOT in the public domain.  We
 *    retain copyright, as specified above.
 *  
 *  HISTORY OF FREE SOFTWARE STATUS
 *  
 *    Originally we required sites to license the software in cases
 *    where they were going to build commercial products/services
 *    around Harvest.  In June 1995 we changed this policy.  We now
 *    allow people to use the core Harvest software (the code found in
 *    the Harvest ``src/'' directory) for free.  We made this change
 *    in the interest of encouraging the widest possible deployment of
 *    the technology.  The Harvest software is really a reference
 *    implementation of a set of protocols and formats, some of which
 *    we intend to standardize.  We encourage commercial
 *    re-implementations of code complying to this set of standards.  
 *  
 */
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <memory.h>
#ifdef _AIX
#include <sys/select.h>
#endif

/*
 *  DEHTMLIZE - Will encode opaque data in HTML form.
 */
#ifndef DEHTMLIZE
#define DEHTMLIZE
#endif

/* External Functions from util.c */
extern void getword(), unescape_url(), plustospace(), nltospace();
extern char x2c();

/* Local functions */
static void sigdie();
static void cgi_fatal();
static int htmlize_brk_obj();
static int htmlize_opaque();
static int htmlize_url();
static void htmlize_desc();
static int broker_comm();
static int do_read();
static char *xstrerror();
static char *get_time();
static char *de_htmlize();

/* Local variables */
static int read_timeout = 0;

#ifndef BUFSIZ
#define BUFSIZ	8192
#endif

#if BUFSIZ < 8192
#undef BUFSIZ
#define BUFSIZ	8192		/* Need large buffers */
#endif

#define XFER_TIMEOUT 	300	/* 5 minutes */
#define ENTSIZ 		32
#define HARVEST_URL	"http://harvest.cs.colorado.edu/"

typedef struct {
	char name[BUFSIZ];
	char val[BUFSIZ];
} entry;

static entry entries[ENTSIZ];
static char querystr[BUFSIZ];	/* Holds the users Query */
static char userquery[BUFSIZ];	/* Holds the form specified query */
static char userclass[BUFSIZ];	/* Holds the form specified class */
static char commandstr[BUFSIZ];	/* Holds the command to the Broker */
static char errmsg[BUFSIZ];	/* Buffer for error messages */
static char tfile[L_tmpnam + 1];	/* tmpfile */

static int maxresultflag = 0;
static int descflag = 0;
static int opaqueflag = 0;
static int verbose = 0;
static int nobjects = 0;	/* # of objects in result set */
static int nopaque = 0;		/* # of opaque lines in result set */
static int do_content_summary = 0;	/* Print Content Summary link? */

/* 
 *  All of the fluffy error messages to users go here 
 */

#define NO_REPLICA_MSG	\
"</pre><P>\n\
Sorry, there are no known replicas for this broker.\n\
Please, change the <STRONG>Broker Host</STRONG> value on the query page.</P>\n"

#define BROKER_DOWN_MSG \
"</pre><P>\nSorry, the Broker at <STRONG>%s, port %d</STRONG> is unavailable.\n\
Please try again later.</P>\n"

#define BROKER_LOAD_MSG \
"</pre><P>\n\
Sorry, the Broker at <STRONG>%s, port %d</STRONG> is currently too heavily loaded\n\
to process your request.  Try again later or try your query at one\n\
of the replica Brokers by selecting a different \"Broker Host\" on\n\
the query page.\n<em>Most likely this Broker is currently undergoing\n\
some maintenance and will be back shortly.</em></P>"

#define PARSE_ERROR_MSG \
"</pre><P>\n\
Sorry, your query:<hr><pre>    %s\n</pre><hr>\n\
does not have the proper syntax.\n\
You can get <a\n\
href=\"http://harvest.cs.colorado.edu/Harvest/brokers/queryhelp.html\">help</a>\n\
in formulating queries.</P>\n\n\
Common syntax mistakes include:\n\
<ul>\n\
<li> <STRONG>Listing two words without an AND or OR joining them</STRONG>.\n\
For example, <pre>    resource discovery</pre>should be \n\
<pre>    resource AND discovery</pre>\n\
<li> <STRONG>Missing punctuation</STRONG>.  \n\
Structured queries need a colon and optional parentheses.  For example,\n\
<pre>    Type:PostScript</pre>\n\
or,\n\
<pre>    (Type : PostScript)</pre>\n\
<li> <STRONG>Not using quotes for phrases or regular expressions</STRONG>.\n\
For example, the phrase  <pre>    resource discovery</pre>should be \n\
<pre>    \"resource discovery\"</pre>\n\
Or for example, the regular expression\n\
<pre>    res.* disc.*</pre> should be \n\
<pre>    \"res.* disc.*\"</pre>\n\
</ul>\n"

#define MISCONFIG_MSG \
"</pre><P>\n\
Sorry, but that query page or link generated an incorrectly configured query\n\
to the broker.  Please report this error to your systems administrator.</P>\n\
<P><STRONG>If you are using Lynx, try using Mosaic instead.  \n\
We've found a bug in Lynx that is breaking our Broker interface. \n\
We're working on the fix.  Thanks.</STRONG></P>\n"

/*
 *  cgi_fatal() - Print error message to user via stdout; and nicely log
 *  the error to stderr where httpd will save it in error_log.
 */
static void cgi_fatal(s)
char *s;
{
	fprintf(stdout, "%s\n", s);
	fflush(stdout);
#ifdef DO_STDERR_LOGGING
	fprintf(stderr, "[%s] BrokerQuery: %s", get_time(), s);
	fflush(stderr);
#endif
	sigdie(1);
}

/*  sigdie() - Exit gracefully */
static void sigdie(sig)
int sig;
{
	if (tfile[0] != '\0')
		(void)unlink(tfile);
	_exit(1);
}


int main(argc, argv)
int argc;
char *argv[];
{
	int x, m = 0, errors = 0, wordflag = 0, caseflag = 0, olderr = 0,
	 goterrflag = 0;
	char *s;
	char *cl = getenv("QUERY_STRING");
	char *gl = getenv("REQUEST_METHOD");
	int sport = 0;
	char hhost[BUFSIZ];

	tfile[0] = '\0';
	(void) signal(SIGHUP, sigdie);
	(void) signal(SIGQUIT, sigdie);
	(void) signal(SIGTSTP, sigdie);
	(void) signal(SIGTERM, sigdie);
	(void) signal(SIGABRT, sigdie);
	(void) signal(SIGALRM, sigdie);
	alarm(1600);	/* kill after half hour to prevent zombies */

	/* Print a simple MIME header here.  MIME hdrs end with \n\n */
	printf("Content-type: text/html\n\n");

	if (!gl || (memcmp(gl, "GET", 3))) {
		cgi_fatal("This script must be used with a METHOD of GET.\n");
	}
	if (cl == NULL) {
		cgi_fatal("No query information to decode.\n");
	}
#ifdef DEBUG
	fprintf(stderr, "[%s] BrokerQuery: Received URL: %s\n", get_time(), cl);
#endif

	/* Parse the URL string for the parameters */
	for (x = 0; (cl[0] != '\0') && (x < ENTSIZ); x++) {
		m = x;
		getword(entries[x].val, cl, '&');
		plustospace(entries[x].val);
		unescape_url(entries[x].val);
		nltospace(entries[x].val);	/* strip \n from query */
		getword(entries[x].name, entries[x].val, '=');
	}

	memset(querystr, '\0', BUFSIZ);
	memset(userquery, '\0', BUFSIZ);
	memset(userclass, '\0', BUFSIZ);
	memset(commandstr, '\0', BUFSIZ);
	for (x = 0; x <= m; x++) {
		if (memcmp(entries[x].name, "query", 5) == 0) {
			strncpy(userquery, entries[x].val, BUFSIZ);
		} else if (memcmp(entries[x].name, "class", 5) == 0) {
			strncpy(userclass, entries[x].val, BUFSIZ);
		} else if (memcmp(entries[x].name, "caseflag", 8) == 0) {
			caseflag = 1;
		} else if (memcmp(entries[x].name, "wordflag", 8) == 0) {
			wordflag = 1;
		} else if (memcmp(entries[x].name, "csumflag", 8) == 0) {
			do_content_summary = 1;
		} else if (memcmp(entries[x].name, "opaqueflag", 10) == 0) {
			opaqueflag = 1;
		} else if (memcmp(entries[x].name, "descflag", 8) == 0) {
			descflag = 1;
		} else if (memcmp(entries[x].name, "maxresultflag", 13) == 0) {
			maxresultflag = atoi(entries[x].val);
			if (maxresultflag < 0)
				maxresultflag = 0;
		} else if (memcmp(entries[x].name, "errorflag", 9) == 0) {
			goterrflag = 1;
			errors = atoi(entries[x].val);
			if (errors < 1)
				errors = 0;
			if (errors > 2)
				errors = 2;	/* only 0, 1, or 2 */
			/* 
			 *  We can encode host:port in the host field; used to
			 *  hide the port number for the replica servers from users
			 */
		} else if (memcmp(entries[x].name, "host", 4) == 0) {
			strncpy(hhost, entries[x].val, BUFSIZ);
			if ((s = strchr(hhost, ':')) != NULL) {
				*s = '\0';
				sport = atoi(++s);
			}
		} else if (memcmp(entries[x].name, "port", 4) == 0) {
			sport = atoi(entries[x].val);
		} else if (memcmp(entries[x].name, "verbose", 7) == 0) {
			verbose = 1;
		} else if (memcmp(entries[x].name, "version", 7) == 0) {
			sprintf(errmsg, "Version: %s\n", rcsid);
			cgi_fatal(errmsg);
		}
	}

	/* Sanity checks */
	if (hhost != NULL && !strcmp(hhost, "No Replicas")) {
		sprintf(errmsg, NO_REPLICA_MSG);
		cgi_fatal(errmsg);
	}
	if ((sport == 0) || (hhost == NULL)) {
		sprintf(errmsg, MISCONFIG_MSG);
		cgi_fatal(errmsg);
	}
	/* Append the userclass to the query string */
	if (userclass[0]) {
		sprintf(querystr, "%s AND %s", userclass, userquery);
	} else {
		strcpy(querystr, userquery);
	}

	/* Build the Broker query */
	strcpy(commandstr, "#USER ");
	if (opaqueflag) {
		strcat(commandstr, "#opaque ");
	}
	if (descflag) {
		strcat(commandstr, "#desc ");
	}
	if (goterrflag) {
		char tmpbuf[BUFSIZ];
		sprintf(tmpbuf, "#index error %d ", errors);
		strcat(commandstr, tmpbuf);
	}
	if (maxresultflag > 0) {
		char tmpbuf[BUFSIZ];
		sprintf(tmpbuf, "#index maxresult %d ", maxresultflag);
		strcat(commandstr, tmpbuf);
	}
	if (caseflag) {
		strcat(commandstr, "#index case insensitive ");
	} else {
		strcat(commandstr, "#index case sensitive ");
	}
	if (wordflag) {
		strcat(commandstr, "#index matchword ");
	}
	strcat(commandstr, "#END ");
	strcat(commandstr, querystr);


	/* Print the HTML header for the query result set */
	printf("<HEAD>\n");
	printf("<TITLE>Broker Query Results for: %s</TITLE>\n", querystr);
	printf("</HEAD>\n");
	printf("<H1>Broker Query Results for:</H1>\n");
	printf("<PRE>\n    %s\n</PRE>\n", querystr);
	printf("<HR>\n");

	if (olderr == 1) {
		printf("<P>\n<STRONG>WARNING: You're using an old version of Mosaic.</STRONG></P>");
	}
	broker_comm(hhost, sport, commandstr);
	fflush(stdout);
	exit(0);
}

/*
 *  broker_comm() - Communicate with the Broker at host serverstr on port
 *  port.  Issue the query Qstr and return the results to the user in HTML.
 */
static int broker_comm(serverstr, port, Qstr)
char *serverstr;
int port;
char *Qstr;
{
	FILE *sfp;
	int sock, n, done = 0, sv;
	char hpptr[BUFSIZ], ret[BUFSIZ], *retptr = NULL;
	struct sockaddr_in me;
	struct hostent *hp;

	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		sprintf(errmsg, "Cannot open a socket: %s\n", xstrerror(errno));
		cgi_fatal(errmsg);
	}
	me.sin_family = AF_INET;
	if ((hp = gethostbyname(serverstr)) == NULL) {
		sprintf(errmsg, "%s: Unknown host.\n", serverstr);
		cgi_fatal(errmsg);
	}
	memcpy((char *) &me.sin_addr, (char *) hp->h_addr, hp->h_length);
	me.sin_port = htons(port);

	if (connect(sock, (struct sockaddr *) &me, sizeof me) < 0) {
		if (errno == ECONNREFUSED) {
			printf(BROKER_DOWN_MSG, serverstr, port);
		} else if (errno == ETIMEDOUT) {
			printf(BROKER_LOAD_MSG, serverstr, port);
		}
		sprintf(errmsg, "Cannot connect to %s on port %d: %s\n",
			serverstr, port, xstrerror(errno));
		cgi_fatal(errmsg);
	}
#ifdef DEBUG
	fprintf(stderr, "[%s] BrokerQuery: Making connection %s:%d\n",
		get_time(), serverstr, port);
#endif
	tmpnam(tfile);
	if ((sfp = fopen(tfile, "w+")) == NULL) {
		sprintf(errmsg, "%s: %s\n", tfile, xstrerror(errno));
		cgi_fatal(errmsg);
	}
	if (write(sock, Qstr, strlen(Qstr)) < 0) {
		sprintf(errmsg, "Cannot write data to %s: %s\n",
			hp->h_name, xstrerror(errno));
		cgi_fatal(errmsg);
	}
	/* Talk to broker, and save the results in a tfile */
	/* Use select so that if the broker goes down, we'll timeout & exit */
	while (1) {
		read_timeout = 0;
		n = do_read(sock, ret, BUFSIZ);
		if (n == 0)
			break;	/* nothing to do */
		if (n < 0) {
			if (read_timeout == 1) {
				printf(BROKER_LOAD_MSG, serverstr, port);
				sprintf(errmsg,
					"Cannot read data from %s: Timeout after %d seconds.\n",
					hp->h_name, XFER_TIMEOUT);
			} else {
				sprintf(errmsg, "Cannot read data from %s: %s\n",
					hp->h_name, xstrerror(errno));
			}
			cgi_fatal(errmsg);
		}
		fwrite(ret, 1, n, sfp);
	}
	close(sock);
	rewind(sfp);

	/* Now process the broker's results */
	hpptr[0] = '\0';
	done = 0;
	printf("<PRE WIDTH=\"132\"><OL>\n");
	while (!done) {
		retptr = (char *) malloc(BUFSIZ);
		retptr[0] = '\0';
		if (fgets(retptr, BUFSIZ, sfp) == NULL) {
			done = 1;
			break;
		}
		sv = atoi(retptr);
		switch (sv) {
		case 101:	/* Message to the user */
		case 102:	/* ?????, opaque data size? */
			fprintf(stdout, "<B>%s</B>\n", retptr + 6);
			break;
		case 120:	/* The URL of the match */
			htmlize_url(retptr + 6);
			break;
		case 122:	/* Opaque data */
			htmlize_opaque(retptr + 6);
			break;
		case 124:	/* The description line of the match */
			htmlize_desc(atoi(retptr + 6), sfp);
			break;
		case 125:	/* URL to the content summary */
			if (do_content_summary)
				htmlize_brk_obj(retptr + 6);
			break;
		case 126:	/* URL to the Broker Home Page */
			strncpy(hpptr, retptr + 6, BUFSIZ);
			break;
		case 130:	/* Object Ends, start a new one */
			printf("\n");
			break;
		case 103:	/* End of the Broker Results */
		case 111:	/* Error Message that ends Broker Results */
			printf("</OL></PRE>\n");
			if (memcmp(retptr + 6, "PARSE ERROR", 10) == 0)
				printf(PARSE_ERROR_MSG, querystr);
			else if (memcmp(retptr + 6, "Broker is too heavily loaded", 27) == 0)
				printf(BROKER_LOAD_MSG);
			else
				printf("<P>\n<B>%s</B><BR>\n", retptr + 6);
			done = 1;
			break;
		case 200:	/* Broker Protocol Version, ignore */
		default:
			break;
		}
		free(retptr);
	}
	/* Clean up */
	fclose(sfp);
	(void) unlink(tfile);

	/* Print the trailing portion of the result set */
	printf("\n");
	if (((nobjects >= maxresultflag && opaqueflag == 0) ||
	     (nopaque >= maxresultflag && opaqueflag != 0)) &&
	    (maxresultflag > 0)) {
		printf("<STRONG>WARNING: The result set was truncated at %d matched %s.</STRONG><BR>\n", maxresultflag, opaqueflag == 0 ? "objects" : "lines");
	}
	if (hpptr[0] != '\0') {
		printf("Return to the <a href=\"%s\">Broker Home Page</a>.<BR>\n", hpptr);
	}
	printf("</P><HR>\n");
	printf("<address>This Broker was built using the ");
	printf("<a href=\"%s\">Harvest</a> system.\n", HARVEST_URL);
	printf("</address>\n");
	return 0;
}

static int htmlize_url(url)
char *url;
{
	char *fn, *host, *path;
	char tmp[BUFSIZ], nbuf[BUFSIZ];
	int n;

	sprintf(nbuf, "%d.", ++nobjects);
	n = strlen(url);
	url[n - 1] = '\0';
	if (verbose == 0 || (strstr(url, "://") == NULL)) {
		printf("<LI><EM>URL</EM>: <a href=\"%s\">%s</a>\n", url, url);
		return (1);
	}
	fn = strrchr(url, '/');
	fn++;
	if (strlen(fn) == 0)
		printf("<LI><EM>filename</EM>: <a href=\"%s\">%s</a>\n",
		       url, url);
	else
		printf("<LI><EM>filename</EM>: <a href=\"%s\">%s</a>\n",
		       url, fn);
	host = strstr(url, "//");
	host += 2;
	path = strchr(host, '/');
	n = path - host;
	memset(tmp, '\0', BUFSIZ);
	memcpy(tmp, host, n);
	printf("<EM>host:</EM> %s\n", tmp);
	n = fn - path;
	memset(tmp, '\0', BUFSIZ);
	memcpy(tmp, path, n);
	printf("<EM>path:</EM> %s\n", tmp);

	return (1);
}

static int htmlize_opaque(data)
char *data;
{
	nopaque++;
	if (opaqueflag == 1) {
#ifdef DEHTMLIZE
		printf("%s", de_htmlize(data));
#else
		printf("%s", data);	/* print exactly as-is */
#endif
	}
	return (1);
}

static int htmlize_brk_obj(obj_url)
char *obj_url;
{
	char *urlstart, *s;

	/* We need to save the start of the URL for the object */
	if ((urlstart = malloc(strlen(obj_url) + 1)) == NULL)
		return (0);
	strcpy(urlstart, obj_url);
	s = strchr(urlstart, '/');
	if (s == NULL)
		return (0);
	s = strchr(s + 1, '/');
	if (s == NULL)
		return (0);
	s = strchr(s + 1, '/');
	if (s == NULL)
		return (0);
	*s = '\0';

	/* We only want the pathname, so chop it off */
	s = strchr(obj_url, '/');
	if (s == NULL)
		return (0);
	s = strchr(s + 1, '/');
	if (s == NULL)
		return (0);
	s = strchr(s + 1, '/');
	if (s == NULL)
		return (0);
	s++;

	printf("<a href=\"%s/Harvest/cgi-bin/DisplayObject.cgi?object=/%s\">Underlying indexing data</a>\n", urlstart, s);
	free(urlstart);
	return (1);
}

static void htmlize_desc(size, fp)
int size;
FILE *fp;
{
	char *descbuf;

	if (size < 1)
		return;
	if ((descbuf = (char *) malloc(size + 1)) == NULL) {
		cgi_fatal("Internal Error: malloc failed.");
	}
	memset(descbuf, '\0', size + 1);
	if (fread(descbuf, 1, size, fp) == 0) {
		sprintf(errmsg, "Cannot read description: %s\n",
			xstrerror(errno));
		cgi_fatal(errmsg);
	}
#ifdef DEHTMLIZE
	printf("<STRONG>Description: %s</STRONG>\n",
	       de_htmlize(descbuf));
#else
	printf("<STRONG>Description: %s</STRONG>\n", descbuf);
#endif
	return;
}

/*
 * do_read() - read that performs timeout.
 */
static int do_read(s, buf, sz)
int s;
char *buf;
int sz;
{
	fd_set readDetect;
	struct timeval timeout;
	int err, readBytes = 0;
	extern int select();

	memset(&timeout, '\0', sizeof(struct timeval));

	/* read until timeout or bytes are read */
	while (1) {
		FD_ZERO(&readDetect);
		FD_SET(s, &readDetect);
		timeout.tv_sec = XFER_TIMEOUT;
		timeout.tv_usec = 0;

		/* wait for data for seconds */
		err = select(s + 1, &readDetect, NULL, NULL, &timeout);
		if (err < 0) {
			if (errno == EINTR)
				continue;
			if (errno == ECONNRESET)
				return(0);
			return (-1);
		}
		/* timeout on the read */
		if (err == 0) {
			read_timeout = 1;/* so other procs know we timed out */
			return (-1);
		}
		/* 
		 *  Sometimes the Broker will destroy the socket 
 		 *  before we have a chance to read EOF.
		 */
		if (FD_ISSET(s, &readDetect)) {
			if ((readBytes = read(s, buf, sz)) < 0)
                                if (errno == ECONNRESET)
                                        return(0);
			break;
		}
	}
	return (readBytes);
}

/*
 *   xstrerror() - same as strerror(3)
 */
static char *xstrerror(n)
int n;
{
	extern int sys_nerr;
#if !defined(__FreeBSD__)
	extern char *sys_errlist[];
#endif

	if (n < 0 || n >= sys_nerr)
		return (NULL);
	return (sys_errlist[n]);
}

/* get_time from NCSA httpd */
static char *get_time()
{
	time_t t;
	char *time_string;

	t = time(NULL);
	time_string = ctime(&t);
	time_string[strlen(time_string) - 1] = '\0';
	return (time_string);
}


static char *de_htmlize(buf)
char *buf;
{
	static char newbuf[4096];
	char *s, *t;

	for (s = buf, t = newbuf; *s; s++)
		switch (*s) {
		case '<':
			*t++ = '&';
			*t++ = 'l';
			*t++ = 't';
			*t++ = ';';
			break;
		case '>':
			*t++ = '&';
			*t++ = 'g';
			*t++ = 't';
			*t++ = ';';
			break;
		case '&':
			*t++ = '&';
			*t++ = 'a';
			*t++ = 'm';
			*t++ = 'p';
			*t++ = ';';
			break;
		default:
			*t++ = *s;
			break;
		}

	*t = 0;
	return newbuf;
}
