char *jcc_rcs = "$Id: jcc.c,v 3.42 1998/10/29 03:11:21 ACJC Exp $";
/* Written and copyright 1997 Anonymous Coders and Junkbusters Corporation.
 * Distributed under the GNU General Public License; see the README file.
 * This code comes with NO WARRANTY. http://www.junkbusters.com/ht/en/gpl.html
 */

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>

#ifdef _WIN32

#include <sys/timeb.h>
#include "win32.h"
#include <io.h>
#include <process.h>

#else

#include <unistd.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/stat.h>

#ifdef __BEOS__
#include <socket.h>	/* BeOS has select() for sockets only. */
#include <OS.h>		/* declarations for threads and stuff. */
#endif

#ifndef FD_ZERO
#include <select.h>
#endif

#endif

#ifdef REGEX
#include <gnu_regex.h>
#endif

#include "jcc.h"
#include "zutil.h"

char *prog;

#define SOCKET_BUF_SIZE		5120

#define BODY	"<body bgcolor=\"#f8f8f0\" link=\"#000078\" alink=\"#ff0022\" vlink=\"#787878\">\n"

char CFAIL[]   = "HTTP/1.0 503 Connect failed\r\n"
		 "Content-Type: text/html\r\n\r\n"
		 "<html>\n"
		 "<head>\n"
		 "<title>Internet Junkbuster: Connect failed</title>\n"
		 "</head>\n"
		 BODY
		 "<h1><center>"
		 BANNER
		 "</center></h1>"
		 "TCP connection to '%s' failed: %s.\n<br>"
		 "</body>\n"
		 "</html>\n"
		 ;

char CNXDOM[]  = "HTTP/1.0 404 Non-existent domain\r\n"
		 "Content-Type: text/html\r\n\r\n"
		 "<html>\n"
		 "<head>\n"
		 "<title>Internet Junkbuster: Non-existent domain</title>\n"
		 "</head>\n"
		 BODY
		 "<h1><center>"
		 BANNER
		 "</center></h1>"
		 "No such domain: %s\n"
		 "</body>\n"
		 "</html>\n"
		 ;
char CSUCCEED[] = "HTTP/1.0 200 Connection established\r\n"
		  "Proxy-Agent: IJ/" VERSION "\r\n"
		  "\r\n"
		  ;

char CHEADER[] = "HTTP/1.0 400 Invalid header received from browser\r\n\r\n";

char SHEADER[] = "HTTP/1.0 502 Invalid header received from server\r\n\r\n";

char VANILLA_WAFER[] =
	"NOTICE=TO_WHOM_IT_MAY_CONCERN_"
	"Do_not_send_me_any_copyrighted_information_other_than_the_"
	"document_that_I_am_requesting_or_any_of_its_necessary_components._"
	"In_particular_do_not_send_me_any_cookies_that_"
	"are_subject_to_a_claim_of_copyright_by_anybody._"
	"Take_notice_that_I_refuse_to_be_bound_by_any_license_condition_"
	"(copyright_or_otherwise)_applying_to_any_cookie._";

char DEFAULT_USER_AGENT[] ="User-Agent: Mozilla/3.01Gold (Macintosh; I; 68K)";

int debug           = 0;
int multi_threaded  = 1;
int hideConsole     = 0;

char *logfile = NULL;
FILE *logfp;

char *blockfile    = NULL;
char *cookiefile   = NULL;
char *trustfile    = NULL;
char *forwardfile  = NULL;
char *aclfile      = NULL;

char *jarfile = NULL;
FILE *jar;

char *referrer   = NULL;
char *uagent     = NULL;
char *from       = NULL;

int suppress_vanilla_wafer = 0;
int add_forwarded      = 0;
int auto_compress      = 0;

struct client_state clients[1];
struct file_list    files[1];

struct list wafer_list[1];
struct list xtra_list[1];
struct list trust_info[1];

struct url_spec * trust_list[64];


int (*loaders[NLOADERS])(struct client_state *csp);

struct gateway gateways[] = {
/* type         function        gw type/host/port,    fw host/port*/
{ "direct",	direct_connect, 0,        NULL, 0,     NULL, 0    },
{ ".",		direct_connect, 0,        NULL, 0,     NULL, 0    },
{ "socks",	socks4_connect, SOCKS_4,  NULL, 1080,  NULL, 0    },
{ "socks4",	socks4_connect, SOCKS_4,  NULL, 1080,  NULL, 0    },
{ "socks4a",	socks4_connect, SOCKS_4A, NULL, 1080,  NULL, 0    },
{ NULL,		NULL,           0,        NULL, 0,     NULL, 0    }
};

struct gateway *gw_default = gateways;

char *haddr = "127.0.0.1";	/* default binding to localhost */
int   hport = 8000;

struct proxy_args proxy_args[1];

int
write_socket(int fd, char *buf, int n)
{
	if(n <= 0) return(0);

	if(DEBUG(LOG)) fwrite(buf, n, 1, logfp);

#if defined(_WIN32) || defined(__BEOS__)
	return send(fd, buf, n, 0);
#else
	return write(fd, buf, n);
#endif
}

int write_compression_header(struct client_state *csp, char *buf, int n) {
/*	csp->gz_debug_fd = open("gz_debug", O_CREAT | O_BINARY | O_TRUNC | O_WRONLY, 0666); */
	if (csp->gz_debug_fd >= 0) {
		write(csp->gz_debug_fd, buf, n);
	}
	if (write_socket(csp->cfd, buf, n) != n) {
		return -1;
	}
	if (csp->compressing_content) {
		int level = Z_BEST_COMPRESSION;
		int strategy = Z_DEFAULT_STRATEGY;
 		static char gzip_hdr[10] = {
 		  0x1f, (char) 0x8b, (char) Z_DEFLATED, 0, 0,
 		  0, 0, 0, 0, 0x03 };

		csp->gz_crc = crc32(0L, Z_NULL, 0);
		csp->gz_stream = &csp->gz_stream_struct;
		csp->gz_stream->zalloc = (alloc_func)0;
		csp->gz_stream->zfree = (free_func)0;
		csp->gz_stream->opaque = (voidpf)0;
		csp->gz_stream->next_in = 0;
		csp->gz_stream->next_out = 0;
		csp->gz_stream->avail_in = 0;
		csp->gz_stream->avail_out = 0;
		/* windowBits is passed < 0 to suppress zlib header */
		if (deflateInit2(csp->gz_stream, level, Z_DEFLATED, -MAX_WBITS,
		    DEF_MEM_LEVEL, strategy) != Z_OK) {
			csp->gz_stream = 0;
			return -1;
		}
 		memcpy(csp->gz_buffer, gzip_hdr, sizeof(gzip_hdr));
 		csp->gz_stream->next_out = csp->gz_buffer + sizeof(gzip_hdr);
 		csp->gz_stream->avail_out = csp->gz_buffer_size - sizeof(gzip_hdr);
	}
	return 0;
}

int write_compression_tailer(struct client_state *csp) {
	if (csp->compressing_content) {
		char buf[8];
		char *dest = buf;
		int n;
		uLong x;
		int done = 0;
		int err;

		if (csp->gz_stream == 0) {
			return -1;
		}
		for (;;) {
		    uInt len = csp->gz_buffer_size - csp->gz_stream->avail_out;

		    if (len > 0) {
			if (csp->gz_debug_fd >= 0) {
				write(csp->gz_debug_fd, csp->gz_buffer, len);
			}
			if (write_socket(csp->cfd, csp->gz_buffer, len) != len) {
				return -1;
			}
/*			fprintf(logfp, "wrote -- compressed %d bytes\n", len); */
			csp->gz_stream->next_out = csp->gz_buffer;
			csp->gz_stream->avail_out = csp->gz_buffer_size;
		    }
		    if (done) break;
		    err = deflate(csp->gz_stream, Z_FINISH);

		    /* Ignore the second of two consecutive flushes: */
		    if (len == 0 && err == Z_BUF_ERROR) err = Z_OK;

		    /* deflate has finished flushing only when it hasn't used up
		     * all the available space in the output buffer: 
		     */
		    done = (csp->gz_stream->avail_out != 0 || err == Z_STREAM_END);
	     
		    if (err != Z_OK && err != Z_STREAM_END) break;
		}
		if (err != Z_STREAM_END) {
			return -1;
		}

		x = csp->gz_crc;
		for (n = 0; n < 4; n++) {
		    *dest++ = (x & 0xff);
		    x >>= 8;
		}
		x = csp->gz_stream->total_in;
		for (n = 0; n < 4; n++) {
		    *dest++ = (x & 0xff);
		    x >>= 8;
		}
/*		fprintf(logfp, "Compression bufer - uncompressed size = %d\n", csp->gz_stream->total_in); */

		if (csp->gz_debug_fd >= 0) {
			write(csp->gz_debug_fd, buf, sizeof(buf));
		}
		if (write_socket(csp->cfd, buf, sizeof(buf)) != sizeof(buf)) {
			return -1;
		}
	}
	return 0;
}

/*  Returns: 0 upon success, non-zero upon failure
*/
int write_compression_buf(struct client_state *csp, char *buf, int n) {
	if (n <= 0) {
		return 0;
	}
/*	fprintf(logfp, "write_compression_buf -- uncompressed %d bytes\n", n); */
	if (csp->compressing_content) {
		if (csp->gz_stream == 0) {
			return -1;
		}
		csp->gz_crc = crc32(csp->gz_crc, (const Bytef *)buf, n);
		csp->gz_stream->next_in = buf;
		csp->gz_stream->avail_in = n;

		while (csp->gz_stream->avail_in != 0) {

		    if (csp->gz_stream->avail_out == 0) {

			csp->gz_stream->next_out = csp->gz_buffer;
			if (csp->gz_debug_fd >= 0) {
				write(csp->gz_debug_fd, csp->gz_buffer, csp->gz_buffer_size);
			}
			if (write_socket(csp->cfd, csp->gz_buffer, csp->gz_buffer_size) != csp->gz_buffer_size) {
				return -1;
			}
/*			fprintf(logfp, "wrote -- compressed %d bytes\n", csp->gz_buffer_size); */
			csp->gz_stream->avail_out = csp->gz_buffer_size;
		    }
		    if (deflate(csp->gz_stream, Z_NO_FLUSH) != Z_OK) {
			    return -1;
		    }
		}
	} else {
		if (csp->gz_debug_fd >= 0) {
			write(csp->gz_debug_fd, buf, n);
		}
		if (write_socket(csp->cfd, buf, n) != n) {
			return -1;
		}
	}
	return 0;
}

int
read_socket(int fd, char *buf, int n)
{
	if(n <= 0) return(0);
#if defined(_WIN32) || defined(__BEOS__)
	return recv(fd, buf, n, 0);
#else
	return read(fd, buf, n);
#endif
}

void
close_socket(int fd)
{
#if defined(_WIN32) || defined(__BEOS__)
	closesocket(fd);
#else
	close(fd);
#endif
}

void
chat(struct client_state *csp)
{
	char buf[SOCKET_BUF_SIZE], *hdr, *p, *req;
	char *err = NULL;
	char *eno;
	fd_set rfds;
	int n, maxfd, server_body;
	int  accept_gzip	= 0;	/*  Accept-Encoding: gzip */
	char *content_type	= 0;
	char *content_encoding	= 0;
	char *content_length	= 0;
	struct cookie_spec *cs;
	struct gateway *gw;
	struct http_request *http;
	char *iob_buf;
	int iob_len;


	http = csp->http;
	csp->compressing_content = 0;

	/* read the client's request.
	 * note that since we're not using select()
	 * we could get blocked here if a client 
	 * connected, then didn't say anything!
	 */

	for(;;) {
		n = read_socket(csp->cfd, buf, sizeof(buf));

		if(n <= 0) break;		/* error! */

		add_to_iob(csp, buf, n);

		req = get_header(csp);

		if(req == NULL) break;		/* no HTTP request! */

		if(*req == '\0') continue;	/* more to come! */

		parse_http_request(req, http, csp);
		freez(req);
		break;
	}

	if(http->cmd == NULL) {
		strcpy(buf, CHEADER);
		write_socket(csp->cfd, buf, strlen(buf));
		return;
	}

	/* decide how to route the HTTP request */

	if((gw = forward_url(http, csp)) == NULL) {

		fprintf(logfp,
			"%s: gateway spec is NULL!?!?  This can't happen!\n", prog);
		abort();
	}

	/* build the http request to send to the server
	 * we have to do one of the following:
	 *
	 * create = use the original HTTP request to create a new
	 *          HTTP request that has only the path component
	 *          without the http://domainspec
	 * pass   = pass the original HTTP request unchanged
	 *
	 * drop   = drop the HTTP request
	 *	    
	 * here's the matrix:
	 *                        SSL
	 *                    0        1
         *                +--------+--------+
         *                |        |        |
         *             0  | create | drop   |
         *                |        |        |
         *  Forwarding    +--------+--------+
         *                |        |        |
         *             1  | pass   | pass   |
         *                |        |        |
         *                +--------+--------+
         *
         */

	if(gw->forward_host) {
			/* if forwarding, just pass the request as is */
			enlist(csp->headers, http->cmd);
	} else {
		if(http->ssl == 0) {
			/* otherwise elide the host information from the url */
			p = NULL;
			p = strsav(p, http->gpc);
			p = strsav(p, " ");
			p = strsav(p, http->path);
			p = strsav(p, " ");
			p = strsav(p, http->ver);
			enlist(csp->headers, p);
			freez(p);
		}
	}

	/* decide what we're to do with cookies */

	if((cs = cookie_url(http, csp))) {
		csp->accept_server_cookie  = cs->accept_server_cookie;
		csp->send_user_cookie      = cs->send_user_cookie;
	} else {
		csp->accept_server_cookie  = 0;
		csp->send_user_cookie      = 0;
	}

	/* grab the rest of the client's headers */

	for(;;) {
		if(( p = get_header(csp))
		&& (*p == '\0')) {
			n = read_socket(csp->cfd, buf, sizeof(buf));
			if(n <= 0) {
				fprintf(logfp,
					"%s: read from client failed: ", prog);
				fperror(logfp, "");
				return;
			}
			add_to_iob(csp, buf, n);
			continue;
		}

		if(p == NULL) break;

		enlist(csp->headers, p);
		if (strncasecmp(p, "Accept-Encoding: ", 17) == 0) {
		    char *q = p;
		    /* Oh, for a strcasestr()! */
		    while ((q = strchr(p, ' ')) != 0) {
		        /* Found a space */
		        if (strcasecmp(q, " gzip") == 0 ||
		            strncasecmp(q, " gzip,", 6) == 0)
			{
			    accept_gzip = 1;
			    break;
			}
			q++;
		    }
		}
		freez(p);
	}

	if(DEBUG(LOG)) {
		fprintf(logfp, "Client %s gzip\n",
			accept_gzip ? "accepts" : "does not accept");
	}

	/* filter it as required */

	hdr = sed(client_patterns, add_client_headers, csp);

	destroy_list(csp->headers);

	if((p = intercept_url(http, csp))
	|| (p =     block_url(http, csp))
	|| (p =     trust_url(http, csp))) {
		if(DEBUG(GPC)) {
			fprintf(logfp, "%s: GPC\t%s%s crunch!\n",
				prog, http->hostport, http->path);
		}

		write_socket(csp->cfd, p, strlen(p));

		if(DEBUG(LOG)) fwrite(p, strlen(p), 1, logfp);

		freez(p);
		freez(hdr);
		return;
	}

	if(DEBUG(GPC)) {
		fprintf(logfp, "%s: GPC\t%s%s\n",
			prog, http->hostport, http->path);
	}

	if(DEBUG(CON)) {
		if(gw->forward_host) {
			fprintf(logfp,
				"%s: connect via %s:%d to: %s ... ",
					prog,
					gw->forward_host,
					gw->forward_port,
					http->hostport);
		} else {
			fprintf(logfp,
				"%s: connect to: %s ... ",
					prog, http->hostport);
		}
	}

	/* here we connect to the server, gateway, or the forwarder */

	csp->sfd = (gw->conn)(gw, http, csp);

	if(csp->sfd < 0) {
		if(DEBUG(CON)) {
			fprintf(logfp, "%s: connect to: %s failed: ",
					prog, http->hostport);
			fperror(logfp, "");
		}

		if(errno == EINVAL) {
			err = zalloc(strlen(CNXDOM) + strlen(http->host));
			sprintf(err, CNXDOM, http->host);
		} else {
			eno = safe_strerror(errno);
			err = zalloc(strlen(CFAIL) + strlen(http->hostport) + strlen(eno));
			sprintf(err, CFAIL, http->hostport, eno);
		}

		write_socket(csp->cfd, err, strlen(err));

		if(DEBUG(LOG)) fwrite(err, strlen(err), 1, logfp);

		freez(err);
		freez(hdr);
		return;
	}

	if(DEBUG(CON)) {
		fprintf(logfp, "OK\n");
	}

	if(gw->forward_host || (http->ssl == 0)) {
		/* write the client's (modified) header to the server
		 * (along with anything else that may be in the buffer)
		 */

		n = strlen(hdr);
		iob_len = flush_buffer(csp, &iob_buf);

		if((write_socket(csp->sfd, hdr, n) != n)
		|| (iob_len > 0 && write_socket(csp->sfd, iob_buf, iob_len) != iob_len)) {
			if(DEBUG(CON)) {
				fprintf(logfp, "%s: write header to: %s failed: ",
					prog, http->hostport);
				fperror(logfp, "");
			}

			eno = safe_strerror(errno);
			err = zalloc(strlen(CFAIL) + strlen(http->hostport) + strlen(eno));
			sprintf(err, CFAIL, http->hostport, eno);
			write_socket(csp->cfd, err, strlen(err));

			freez(err);
			freez(hdr);
			return;
		}
	} else {
		/* we're running an SSL tunnel and we're not
		 * forwarding, so just send the "connect succeeded"
		 * message to the client, flush the rest, and
		 * get out of the way.
		 */

		if(write_socket(csp->cfd, CSUCCEED, sizeof(CSUCCEED)-1) < 0) {
			freez(hdr);
			return;
		}
		IOB_RESET(csp);
	}

	/* we're finished with the client's header */
	freez(hdr);

	maxfd = ( csp->cfd > csp->sfd ) ? csp->cfd : csp->sfd;

	/* pass data between the client and server
	 * until one or the other shuts down the connection.
	 */

	server_body = 0;

	for(;;) {
		FD_ZERO(&rfds);

		FD_SET(csp->cfd, &rfds);
		FD_SET(csp->sfd, &rfds);

		n = select(maxfd+1, &rfds, NULL, NULL, NULL);

		if(n < 0) {
			fprintf(logfp, "%s: select() failed!: ", prog);
			fperror(logfp, "");
			return;
		}

		/* this is the body of the browser's request
		 * just read it and write it.
		 */

		if(FD_ISSET(csp->cfd, &rfds)) {

			n = read_socket(csp->cfd, buf, sizeof(buf));

			if(n <= 0) break; /* "game over, man" */

			if(write_socket(csp->sfd, buf, n) != n) {
				fprintf(logfp, "%s: write to: %s failed: ",
						prog, http->host);
				fperror(logfp, "");
				return;
			}
			continue;
		}

		/* the server wants to talk.
		 * it could be the header or the body.
		 * if `hdr' is null, then it's the header
		 * otherwise it's the body
		 */

		if(FD_ISSET(csp->sfd, &rfds)) {

			n = read_socket(csp->sfd, buf, sizeof(buf));

			if(n < 0) {
				fprintf(logfp, "%s: read from: %s failed: ",
						prog, http->host);
				fperror(logfp, "");

				eno = safe_strerror(errno);
				sprintf(buf, CFAIL, http->hostport, eno);
				freez(eno);
				write_socket(csp->cfd, buf, strlen(buf));
				return;
			}

			if(n == 0) break; /* "game over, man" */

			/* if this is an SSL connection or we're in the body
			 * of the server document, just write it to the client.
			 */

			if(server_body || http->ssl) {
				/* just write */
				if(write_compression_buf(csp, buf, n) != 0) {
					fprintf(logfp, "%s: write to client failed: ",
							prog);
					fperror(logfp, "");
					return;
				}
				continue;
			} else {
				/* we're still looking for the end of the
				 * server's header ... (does that make header
				 * parsing an "out of body experience" ?
				 */

				/* buffer up the data we just read */
				add_to_iob(csp, buf, n);

				/* get header lines from the iob */
				while((p = get_header(csp))) {
					if(*p == '\0') {
						/* see following note */
						break;
					}
					if (strncasecmp(p, "Content-Type: ", 14) == 0) {
						freez(content_type);
						content_type = p;
					} else if (strncasecmp(p, "Content-Length: ", 16) == 0) {
						freez(content_length);
						content_length = p;
					} else if (strncasecmp(p, "Content-Encoding: ", 18) == 0) {
						freez(content_encoding);
						content_encoding = p;
					} else {
						enlist(csp->headers, p);
						freez(p);
					}
				}

				/* NOTE: there are no "empty" headers so
				 * if the pointer `p' is not NULL we must
				 * assume that we reached the end of the
				 * buffer before we hit the end of the header.
				 *
				 * Since we have to wait for more from the
				 * server before we can parse the headers
				 * we just continue here.
				 */

				if(p) continue;

				/* we have now received the entire header.
				 * filter it and send the result to the client
				 */

				if (auto_compress &&
				    accept_gzip &&
				    content_encoding == 0 &&
				    content_type != 0 &&
				    (strncasecmp(content_type, "Content-Type: text/html", 23) == 0
				  || strncasecmp(content_type, "Content-Type: text/plain", 24) == 0)) {
					/* Can compress output */
					content_encoding = strdup("Content-Encoding: gzip");
					if (DEBUG(LOG) && content_length != 0) {
						fprintf(logfp, "Pre-compression %s\n", content_length);
					}
					freez(content_length);
					csp->compressing_content = 1;
				}
				if (content_length) {
					enlist(csp->headers, content_length);
				}
				if (content_type) {
					enlist(csp->headers, content_type);
				}
				if (content_encoding) {
					enlist(csp->headers, content_encoding);
				}
				freez(content_type);
				freez(content_encoding);
				freez(content_length);

				if(DEBUG(LOG)) {
					fprintf(logfp, "%s result\n",
						csp->compressing_content ? "Compressing" : "Not compressing");
				}

				hdr = sed(
					server_patterns,
					add_server_headers,
					csp);

				n   = strlen(hdr);

				/* write the server's (modified) header to
				 * the client (along with anything else that
				 * may be in the buffer)
				 */

				iob_len = flush_buffer(csp, &iob_buf);

				if ((write_compression_header(csp, hdr, n) != 0)
				 || (write_compression_buf(csp, iob_buf, iob_len) != 0)) {
					if(DEBUG(CON)) {
						fprintf(logfp,
							"%s: write header to client failed: ",
								prog);
						fperror(logfp, "");
					}
					/* the write failed, so don't bother
					 * mentioning it to the client...
					 * it probably can't hear us anyway.
					 */
					freez(hdr);
					return;
				}

				/* we're finished with the server's header */

				freez(hdr);
				server_body = 1;
			}
			continue;
		}

		return; /* huh? we should never get here */
	}
}

void
serve(struct client_state *csp)
{
	char compress_buf[SOCKET_BUF_SIZE];
	csp->gz_buffer = compress_buf;
	csp->gz_buffer_size = sizeof(compress_buf);
	csp->gz_debug_fd = -1;
	chat(csp);
	write_compression_tailer(csp);
	if (csp->gz_debug_fd >= 0) {
		close(csp->gz_debug_fd);
	}
	if (csp->gz_stream != 0) {
		deflateEnd(csp->gz_stream);
	}
	close_socket(csp->cfd);

	if(csp->sfd >= 0) {
		close_socket(csp->sfd);
	}

	csp->active = 0;
}

#ifdef __BEOS__
int32
server_thread(void *data)
{
	serve((struct client_state *) data);
	return 0;
}
#endif

int
main(int argc, char *argv[])
{
	char buf[BUFSIZ];
	int cfd, bfd;
	char *p, *q;
	extern char *optarg;
	extern int optind;
	struct client_state *csp;

	char *default_configfile = NULL;
	char *configfile = NULL;
	FILE *configfp = NULL;

	int err = 0;

	prog = argv[0];

	logfp = stdout;

	init_proxy_args(argc, argv);

	cfd  = -1;

#ifdef _WIN32
	default_configfile = "junkbstr.ini";
#endif

	configfile = default_configfile;

	if(argc > 1) {
		configfile = argv[1];
	}

	if(configfile) {
		if((configfp = fopen(configfile, "r")) == NULL) {
			if(configfile != default_configfile) {
				fprintf(logfp,
					"%s: can't open configuration file '%s': ",
						prog, configfile);
				fperror(logfp, "");
				exit(1);
			}
		}
	}

	if(configfp) {
		int line_num = 0;
		while(fgets(buf, sizeof(buf), configfp)) {
			char cmd[BUFSIZ];
			char arg[BUFSIZ];
			char tmp[BUFSIZ];

			line_num++;

			strcpy(tmp, buf);

			if((p = strpbrk(tmp, "#\r\n"))) *p = '\0';

			p = tmp;

			/* leading skip whitespace */
			while(*p && ((*p == ' ') || (*p == '\t'))) p++;

			q = cmd;

			while(*p && (*p != ' ') && (*p != '\t')) *q++ = *p++;

			*q = '\0';

			while(*p && ((*p == ' ') || (*p == '\t'))) p++;

			strcpy(arg, p);

			p = arg + strlen(arg) - 1;

			/* ignore trailing whitespace */
			while(*p && ((*p == ' ') || (*p == '\t'))) *p-- = '\0';

			if(*cmd == '\0') continue;

			/* insure the command field is lower case */
			for(p=cmd; *p; p++) if(isupper(*p)) *p = tolower(*p);

			savearg(cmd, arg);

			if(strcmp(cmd, "trustfile") == 0) {
				trustfile = strdup(arg);
				continue;
			}

			if(strcmp(cmd, "trust_info_url") == 0) {
				enlist(trust_info, arg);
				continue;
			}

			if(strcmp(cmd, "debug") == 0) {
				debug |= atoi(arg);
				continue;
			}

			if(strcmp(cmd, "add-forwarded-header") == 0) {
				add_forwarded = 1;
				continue;
			}

			if(strcmp(cmd, "auto-compress") == 0) {
				auto_compress = 1;
				continue;
			}

			if(strcmp(cmd, "single-threaded") == 0) {
				multi_threaded = 0;
				continue;
			}

			if(strcmp(cmd, "suppress-vanilla-wafer") == 0) {
				suppress_vanilla_wafer = 1;
				continue;
			}

			if(strcmp(cmd, "wafer") == 0) {
				enlist(wafer_list, arg);
				continue;
			}

			if(strcmp(cmd, "add-header") == 0) {
				enlist(xtra_list,  arg);
				continue;
			}

			if(strcmp(cmd, "cookiefile") == 0) {
				cookiefile = strdup(arg);
				continue;
			}

			if(strcmp(cmd, "logfile") == 0) {
				logfile = strdup(arg);
				continue;
			}

			if(strcmp(cmd, "blockfile") == 0) {
				blockfile = strdup(arg);
				continue;
			}

			if(strcmp(cmd, "jarfile") == 0) {
				jarfile = strdup(arg);
				continue;
			}

			if(strcmp(cmd, "listen-address") == 0) {
				haddr = strdup(arg);
				continue;
			}

			if(strcmp(cmd, "forwardfile") == 0) {
				forwardfile = strdup(arg);
				continue;
			}

			if(strcmp(cmd, "aclfile") == 0) {
				aclfile = strdup(arg);
				continue;
			}

			if(strcmp(cmd, "user-agent") == 0) {
				uagent = strdup(arg);
				continue;
			}

			if((strcmp(cmd, "referrer") == 0)
			|| (strcmp(cmd, "referer" ) == 0)) {
				referrer = strdup(arg);
				continue;
			}

			if(strcmp(cmd, "from") == 0) {
				from = strdup(arg);
				continue;
			}

			if(strcmp(cmd, "hide-console") == 0) {
				hideConsole = 1;
				continue;
			}

			fprintf(logfp,
				"%s: unrecognized directive "
				"in configuration file "
				"at line number %d:\n%s",
				prog, line_num, buf);
			err = 1;
		}
		fclose(configfp);
	}

	if(err) exit(1);

#ifdef _WIN32
	InitWin32();
#endif

	if(logfile) {
		FILE *tlog = fopen(logfile, "a");
		if(tlog == NULL) {
			fprintf(logfp, "%s: can't open logfile '%s': ",
				prog, logfile);
			fperror(logfp, "");
			err = 1;
		}
		logfp = tlog;
	}

	setbuf(logfp, NULL);

	if(cookiefile)   add_loader(load_cookiefile);

	if(blockfile)    add_loader(load_blockfile);

	if(trustfile)    add_loader(load_trustfile);

	if(forwardfile)  add_loader(load_forwardfile);

	if(aclfile)      add_loader(load_aclfile);

	if(jarfile) {
		jar = fopen(jarfile, "a");
		if(jar == NULL) {
			fprintf(logfp, "%s: can't open jarfile '%s': ",
				prog, jarfile);
			fperror(logfp, "");
			err = 1;
		}
		setbuf(jar, NULL);
	}

	if(haddr) {
		if((p = strchr(haddr, ':'))) {
			*p++ = '\0';
			if(*p) hport = atoi(p);
		}

		if(hport <= 0) {
			*--p = ':' ;
			fprintf(logfp, "%s: invalid bind port spec %s",
				prog, haddr);
			err = 1;
		}
		if(*haddr == '\0') haddr = NULL;
	}

	if(run_loader(NULL)) err = 1;

	if(err) exit(1);

	/* if we're logging cookies in a cookie jar,
	 * and the user has not supplied any wafers,
	 * and the user has not told us to suppress the vanilla wafer,
	 * then send the vanilla wafer.
	 */
	if((jarfile != NULL)
	&& (wafer_list->next == NULL)
	&& (suppress_vanilla_wafer == 0)) {
		enlist(wafer_list, VANILLA_WAFER);
	}

	if(DEBUG(CON)) {
		fprintf(logfp, "%s: bind (%s, %d)\n",
			prog, haddr ? haddr : "INADDR_ANY", hport);
	}

	bfd = bind_port(haddr, hport);

	if(bfd < 0) {
		fprintf(logfp, "%s: can't bind %s:%d: ",
			prog, haddr ? haddr : "INADDR_ANY", hport);
		fperror(logfp, "");
		fprintf(logfp,
			"There may be another junkbuster or some other "
			"proxy running on port %d\n", hport);
		err = 1;
	}

	if(err) exit(1);

	end_proxy_args();

#ifndef _WIN32
	signal(SIGPIPE, SIG_IGN);
	signal(SIGCHLD, SIG_IGN);
#endif

#ifdef _WIN32
{
	/* print a verbose messages about FAQ's and such */
	extern char *win32_blurb;
	if(logfp == stdout) fprintf(logfp, win32_blurb);
}
#endif

	for(;;) {

#if !defined(_WIN32) && !defined(__BEOS__)
		while(waitpid(-1, NULL, WNOHANG) > 0) {
			/* zombie children */
		}
#endif
		sweep();

		if(DEBUG(CON)) {
			fprintf(logfp, "%s: accept connection ... ", prog);
		}

		cfd = accept_connection(bfd);

		if(cfd < 0) {
			if(DEBUG(CON)) {
				fprintf(logfp, "%s: accept failed: ", prog);
				fperror(logfp, "");
			}
			continue;
		} else {
			if(DEBUG(CON)) {
				fprintf(logfp, "OK\n");
			}
		}

		csp = (struct client_state *) malloc(sizeof(*csp));

		if(csp == NULL) {
			fprintf(logfp, "%s: malloc(%d) for csp failed: ", prog, sizeof(*csp));
			fperror(logfp, "");
			close_socket(cfd);
			continue;
		}

		memset(csp, '\0', sizeof(*csp));

		csp->active = 1;
		csp->cfd    = cfd;
		csp->sfd    =  -1;
		csp->ip_addr_str  = remote_ip_str;
		csp->ip_addr_long = remote_ip_long;

		/* add it to the list of clients */
		csp->next = clients->next;
		clients->next = csp;

		if(run_loader(csp)) {
			fprintf(logfp, "%s: a loader failed - must exit\n", prog);
			exit(1);
		}

		if(multi_threaded) {
			int child_id;

/* this is a switch() statment in the C preprocessor - ugh */
#undef SELECTED_ONE_OPTION

#if defined(_WIN32) && !defined(SELECTED_ONE_OPTION)
#define SELECTED_ONE_OPTION
			child_id = _beginthread(
					(void*)serve,
					64 * 1024,
					csp);
#endif

#if defined(__BEOS__) && !defined(SELECTED_ONE_OPTION)
#define SELECTED_ONE_OPTION
			{
				thread_id tid = spawn_thread
					(server_thread, "server", B_NORMAL_PRIORITY, csp);

				if ((tid >= 0) && (resume_thread(tid) == B_OK)) {
					child_id = (int) tid;
				} else {
					child_id = -1;
				}
			}
#endif

#if !defined(SELECTED_ONE_OPTION)
			child_id = fork();
#endif

#undef SELECTED_ONE_OPTION
/* end of cpp switch() */

			if(child_id < 0) {	/* failed */
				fprintf(logfp, "%s: can't fork: ", prog);
				fperror(logfp, "");

				sprintf(buf , "%s: can't fork: errno = %d",
					prog, errno);

				write_socket(csp->cfd, buf, strlen(buf));
				close_socket(csp->cfd);
				csp->active = 0;
				sleep(5);
				continue;
			}
#if !defined(_WIN32) && !defined(__BEOS__)
			/* This block is only needed when using fork().
			 * When using threads, the server thread was
			 * created and run by the call to _beginthread().
			 */
			if(child_id == 0) {	/* child */

				serve(csp);
				_exit(0);

			} else {		/* parent */

				/* in a fork()'d environment, the parent's
				 * copy of the client socket and the CSP
				 * are not used.
				 */

				close_socket(csp->cfd);
				csp->active = 0;
			}
#endif
		} else {
			serve(csp);
		}
	}
	/* NOTREACHED */
}


char *
safe_strerror(int err)
{
	char buf[BUFSIZ];
	char *s = NULL;

#ifndef   NOSTRERROR
	s = strerror(err);
#endif /* NOSTRERROR */

	if(s == NULL) {
		sprintf(buf, "(errno = %d)", err);
		s = buf;
	}

	return(strdup(s));
}

void
fperror(FILE *fp, char *str)
{
	char *eno = safe_strerror(errno);

	if(str && *str) {
		fprintf(fp, "%s: %s\n", str, eno);
	} else {
		fprintf(fp, "%s\n", eno);
	}
	freez(eno);
}
