char *jcc_rcs = "$Id: jcc.c,v 1.1.1.1 2000/03/18 00:00:00 anw 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 <windows.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
#ifndef __riscos__
#include <select.h>
#else /* __riscos__ */
#include <sys/select.h>
#include <unixlib/local.h>
_riscosify_control = __RISCOSIFY_NO_PROCESS;
#endif /* __riscos__ */
#endif

#endif

#ifdef REGEX
#include "gnu_regex.h"
#endif

#include "jcc.h"

char *prog;

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

char CFAIL[]   = "HTTP/1.0 503 Connect failed\n"
		 "Content-Type: text/html\n\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\n"
		 "Content-Type: text/html\n\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\n"
		  "Proxy-Agent: IJ/" VERSION "\n\n"
		  ;

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

char SHEADER[] = "HTTP/1.0 502 Invalid header received from server\n\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 (X11; I; Linux 2.0.32 i586)";

char BLANKGIF[] = "HTTP/1.0 200 OK\r\n"
                  "Pragma: no-cache\r\n"
                  "Content-type: image/gif\r\n\r\n"
                  "GIF89a\001\000\001\000\200\000\000\377\377\377\000\000"
                  "\000!\371\004\001\000\000\000\000,\000\000\000\000\001"
                  "\000\001\000\000\002\002D\001\000;";

char JBGIF[] = "HTTP/1.0 200 OK\r\n"
                  "Pragma: no-cache\r\n"
                  "Content-type: image/gif\r\n\r\n"
                  "GIF89aD\000\013\000\360\000\000\000\000\000\377\377\377!"
                  "\371\004\001\000\000\001\000,\000\000\000\000D\000\013\000"
                  "\000\002a\214\217\251\313\355\277\000\200G&K\025\316hC\037"
                  "\200\234\230Y\2309\235S\230\266\206\372J\253<\3131\253\271"
                  "\270\215\342\254\013\203\371\202\264\334P\207\332\020o\266"
                  "N\215I\332=\211\312\3513\266:\026AK)\364\370\365aobr\305"
                  "\372\003S\275\274k2\354\254z\347?\335\274x\306^9\374\276"
                  "\037Q\000\000;";


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

#ifdef _WIN32
#define sleep(N)	Sleep(((N) * 1000))
#endif

char *logfile = NULL;
FILE *logfp;

char *imagefile    = NULL; /* swa */
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;
char *suppress_message	= NULL; /* anw */

int suppress_vanilla_wafer = 0;
int add_forwarded      = 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 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;

/* anw start */
int   stats_enabled = 0;	/* statistics enabled */
int   urls_read=0;		/* total nr of urls read inc rejected */
int   urls_rejected=0;		/* total nr of urls rejected */

int   suppress_blocklists = 0;  /* suppress listing sblock and simage */
/* anw end */

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
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[BUFSIZ], *hdr, *p, *req;
	char *err = NULL;
	char *eno;
	fd_set rfds;
	int n, maxfd, server_body;
	struct cookie_spec *cs;
	struct gateway *gw;
	struct http_request *http;

	http = csp->http;

	/* 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);
		freez(p);
	}

	/* filter it as required */

	hdr = sed(client_patterns, add_client_headers, csp);

	destroy_list(csp->headers);

	urls_read++; /* anw */

	if((p = intercept_url(http, csp))
	|| (p =     block_url(http, csp))
	|| (p =     trust_url(http, csp))) {

		urls_rejected++; /* anw */

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

		/* now use block_imageurl */
		if (tinygif > 0) {
		  if ( block_imageurl( http,csp ) ) {
		    if(DEBUG(GPC)) {
		      fprintf(logfp, "%s: GPC\t%s%s image crunch!\n",
			      prog, http->hostport, http->path);
		    }
		    if (tinygif == 1)
		      write_socket(csp->cfd, BLANKGIF, sizeof(BLANKGIF)-1);
		    else
		      write_socket(csp->cfd, JBGIF, sizeof(JBGIF)-1);
		  } else {
		    write_socket(csp->cfd, p, strlen(p));
		  }

		} else
		  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);

		if((write_socket(csp->sfd, hdr, n) != n)
		|| (flush_socket(csp->sfd, csp   ) <  0)) {
			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_socket(csp->cfd, buf, n) != n) {
					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;
					}
					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
				 */

				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)
				 */

				if((write_socket(csp->cfd, hdr, n) != n)
				|| (flush_socket(csp->cfd, csp   ) <  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)
{
	chat(csp);
	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
#ifdef __riscos__
	default_configfile = "Choices:junkbuster.config";
#endif /* __riscos__ */

	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, "tinygif") == 0) {
				tinygif = atoi(arg);
 				continue;
 			}

			if(strcmp(cmd, "add-forwarded-header") == 0) {
				add_forwarded = 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, "imagefile") == 0) { /* swa */
				imagefile = 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;
			}

			if(strcmp(cmd, "include-stats") == 0) {
				stats_enabled = 1;
				continue;
			}

			if(strcmp(cmd, "suppress-blocklists") == 0) {

				if (arg[0] != '\0') {
					suppress_message = strdup(arg);
				} else {
					/*
					 * There will be NO reference in
					 * proxy-args.
					 */
					suppress_message = NULL;
				}

				suppress_blocklists =	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(imagefile)    add_loader(load_imagefile); /* swa */

	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;
		}
#ifndef __riscos__
		setbuf(jar, NULL);
#else /* __riscos__ */
		fclose(jar);
#endif /* __riscos__ */
	}

	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();

#if !defined(_WIN32) && !defined(__riscos__)
	signal(SIGPIPE, SIG_IGN);
	signal(SIGCHLD, SIG_IGN);
#endif /* !_WIN32 && !__riscos__ */

#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(_CYGWIN) && !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);
}
