/*
** Copyright 1998 - 2009 Double Precision, Inc.
** See COPYING for distribution information.
*/

#if	HAVE_CONFIG_H
#include	"config.h"
#endif

/*
** OK - the poop is that if we include socks.h after stdio.h, SOCKSfwrite
** does not get prototyped.
** If we include socks.h before stdio.h, gcc will complain about getc being
** redefined.  The easiest solution is to simply undef getc, because we
** don't use it here.
*/

#include	"soxwrap/soxwrap.h"
#include	<stdio.h>


#include	<sys/types.h>
#include	<sys/time.h>
#include	<arpa/inet.h>
#include	<pwd.h>
#include	<grp.h>
#include	<stdlib.h>
#include	<string.h>
#include	<ctype.h>
#include	<stdio.h>
#include	<errno.h>
#include	<signal.h>
#if	HAVE_SYS_STAT_H
#include	<sys/stat.h>
#endif
#if	HAVE_FCNTL_H
#include	<fcntl.h>
#endif
#if	HAVE_SYS_IOCTL_H
#include	<sys/ioctl.h>
#endif
#if HAVE_UNISTD_H
#include	<unistd.h>
#endif
#include	"waitlib/waitlib.h"
#include	"rfc1035/rfc1035.h"
#include	"liblock/config.h"
#include	"liblock/liblock.h"
#include	"tcpremoteinfo.h"
#include	"numlib/numlib.h"
#include	"argparse.h"

#include	<netdb.h>


static const char *accessarg=0;
static const char *accesslocal=0;
static const char *denymsgarg=0;
static const char *listenarg=0;
static const char *ipaddrarg=0;
static const char *userarg=0;
static const char *grouparg=0;
static const char *maxprocsarg=0;
static const char *warnarg=0;
static const char *maxperiparg=0;
static const char *maxpercarg=0;
static const char *droparg=0;
static const char *nodnslookup=0;
static const char *noidentlookup=0;
static const char *stderrarg=0;
static const char *stderrloggerarg=0;
static const char *pidarg=0;
static const char *proxyarg=0;
static const char *restartarg=0;
static const char *stoparg=0;
static const char *stderrloggername=0;

static char *lockfilename;

static void setup_block(const char *);

static struct args arginfo[]={
	{"access", &accessarg},
	{"accesslocal", &accesslocal},
	{"denymsg", &denymsgarg},
	{"drop", &droparg},
	{"address", &ipaddrarg},
	{"block", 0, setup_block},
	{"group", &grouparg},
	{"listen", &listenarg},
	{"maxperc", &maxpercarg},
	{"maxperip", &maxperiparg},
	{"maxprocs", &maxprocsarg},
	{"warn", &warnarg},
	{"nodnslookup", &nodnslookup},
	{"noidentlookup", &noidentlookup},
	{"pid", &pidarg},
	{"restart", &restartarg},
	{"stderr", &stderrarg},
	{"stderrlogger", &stderrloggerarg},
	{"stderrloggername", &stderrloggername},
	{"stop", &stoparg},
	{"user", &userarg},
	{"proxy", &proxyarg},
	{0}
	} ;

/* Ports we're listening on: */

static struct portinfo {
	struct portinfo *next;
	const char *ipaddr;	/* Specific IP addr, or 0 */
	const char *servname;	/* Service name/port */

	int fd1, fd2;		/* BSD may need both IPv4 and IPv6 sockets */
} *fdlist=0;
static int maxfd;

static int nprocs, maxperc, maxperip, nwarn;
static pid_t *pids;
static time_t last_alert=0, last_warn=0;
static RFC1035_ADDR *addrs;

static int sighup_received=0;

struct blocklist_s {
	struct blocklist_s *next;
	char *zone;
	char *var;
	struct in_addr ia;	/* 0, anything */
	char *msg;		/* NULL, query for TXT record */
	} *blocklist=0;

extern int openaccess(const char *);
extern void closeaccess();
extern char *chkaccess(const char *);

static void setup_block(const char *blockinfo)
{
struct blocklist_s *newbl=(struct blocklist_s *)malloc(sizeof(*blocklist));
char	*p;
struct blocklist_s **blptr;

	for (blptr= &blocklist; *blptr; blptr=&(*blptr)->next)
		;

	if (!newbl || (newbl->zone=malloc(strlen(blockinfo)+1)) == 0)
	{
		perror("malloc");
		exit(1);
	}

	*blptr=newbl;
	newbl->next=0;
	strcpy(newbl->zone, blockinfo);
	newbl->var=strchr(newbl->zone, ',');
	newbl->msg=0;
	newbl->ia.s_addr=INADDR_ANY;

	if (newbl->var)
		*newbl->var++=0;
	if (newbl->var && *newbl->var)
		newbl->msg=strchr(newbl->var, ',');
	if (newbl->msg)
		*newbl->msg++=0;
	if (newbl->var && (p=strchr(newbl->var, '/')) != 0)
	{
		*p++=0;
		rfc1035_aton_ipv4(p, &newbl->ia);
	}
}

static int isid(const char *p)
{
	while (*p)
	{
		if (*p < '0' || *p > '9')	return (0);
		++p;
	}
	return (1);
}

static RETSIGTYPE sigexit(int n)
{
	kill( -getpid(), SIGTERM);
	_exit(0);

#if RETSIGTYPE != void
	return (0)
#endif
}

static RETSIGTYPE sighup(int n)
{
	sighup_received=1;

	signal(SIGHUP, sighup);

#if RETSIGTYPE != void
	return (0)
#endif
}

/*
** Initialize a single listening socket
*/

static struct portinfo *createport(const char *a, const char *s)
{
	struct portinfo *p=(struct portinfo *)malloc(sizeof(struct portinfo));

	if (!p)
	{
		perror("malloc");
		return (NULL);
	}

	p->next=fdlist;
	fdlist=p;
	p->ipaddr=a;
	p->servname=s;
	p->fd1=p->fd2= -1;
	return (p);
}

static int parseaddr(const char *p)
{
	char *buf=strdup(p);
	char *q, *a, *s;

	if (!buf)
	{
		perror("malloc");
		return (-1);
	}

	for (q=buf; (q=strtok(q, ",")) != NULL; q=0)
	{
		if ((s=strrchr(q, '.')) != 0)
		{
			*s++=0;
			a=q;
		}
		else
		{
			a=0;
			s=q;
		}

		if (createport(a, s) == NULL)
			return (-1);
	}

	if (ipaddrarg)
	{
		struct portinfo *p;

		for (p=fdlist; p; p=p->next)
		{
			if (p->ipaddr && strcmp(p->ipaddr, "0"))
				continue;
			p->ipaddr=ipaddrarg;
		}
	}

	return (0);
}

/*
** Create one socket, bound to a specific host/port
*/

static int mksocket(const char *ipaddrarg,	/* Host/IP address */
		    const char *servname,	/* Service/port */
		    int flags)

#define MKS_USEAFINET4 1
#define MKS_ERROK 2

{
	struct servent *servptr;
	int	port;
	int	fd;

	RFC1035_ADDR addr;
	RFC1035_NETADDR  netaddr;
	const struct sockaddr *sinaddr;
	int	sinaddrlen;

#if	RFC1035_IPV6
	struct sockaddr_in6	sin6;
#endif

	struct sockaddr_in	sin4;

	int	af;

	servptr=getservbyname(servname, "tcp");
	if (servptr)
		port=servptr->s_port;
	else
	{
		port=atoi(servname);
		if (port <= 0 || port > 65535)
		{
			fprintf(stderr, "Invalid port: %s\n", servname);
			return (-1);
		}
		port=htons(port);
	}

	/* Create an IPv6 or an IPv4 socket */

#if	RFC1035_IPV6
	if (flags & MKS_USEAFINET4)
	{
		fd=socket(PF_INET, SOCK_STREAM, 0);
		af=AF_INET;
	}
	else
#endif
		fd=rfc1035_mksocket(SOCK_STREAM, 0, &af);

	if (fd < 0)
	{
		perror("socket");
		return (-1);
	}

	/* Figure out what to bind based on what socket we created */

	if (ipaddrarg && strcmp(ipaddrarg, "0"))
	{
		if (rfc1035_aton(ipaddrarg, &addr) < 0)
		{
			fprintf(stderr,"Invalid IP address: %s\n", ipaddrarg);
			close(fd);
			return (-1);
		}

		if (rfc1035_mkaddress(af, &netaddr, &addr, port, &sinaddr,
				&sinaddrlen))
		{
			fprintf(stderr,"Unable to bind IP address: %s\n",
				ipaddrarg);
			close(fd);
			return (-1);
		}
	}
	else	/* Bind default address */
	{
#if	RFC1035_IPV6
		if (af == AF_INET6)
		{
			memset(&sin6, 0, sizeof(sin6));
			sin6.sin6_family=AF_INET6;
			sin6.sin6_addr=in6addr_any;
			sin6.sin6_port=port;
			sinaddr=(const struct sockaddr *)&sin6;
			sinaddrlen=sizeof(sin6);
		}
		else
#endif
			if (af == AF_INET)
			{
				sin4.sin_family=AF_INET;
				sin4.sin_addr.s_addr=INADDR_ANY;
				sin4.sin_port=port;
				sinaddr=(const struct sockaddr *)&sin4;
				sinaddrlen=sizeof(sin4);
			}
			else
			{
				errno=EAFNOSUPPORT;
				perror("socket");
				close(fd);
				return (-1);
			}
	}

	{
		int dummy=1;

		if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
		       (const char *)&dummy, sizeof(dummy)) < 0)
		{
			perror("setsockopt");
		}
	}

	if (fcntl(fd, F_SETFD, FD_CLOEXEC))
	{
		perror("fcntl");
		close(fd);
		return (-1);
	}

	if (fcntl(fd, F_SETFL, O_NONBLOCK))
	{
		perror("fcntl");
		close(fd);
		return (-1);
	}

	if (sox_bind(fd, (struct sockaddr *)sinaddr, sinaddrlen) < 0)
	{
		if (flags & MKS_ERROK)
		{
			close(fd);
			return (-2);
		}

		perror("bind");
		close(fd);
		return (-1);
	}

	if (sox_listen(fd,
#ifdef	SOMAXCONN
		       SOMAXCONN
#else
		       5
#endif
		       ))
	{
		if (flags && MKS_ERROK)
		{
			close(fd);
			return (-2);
		}
		perror("listen");
		close(fd);
		return (-1);
	}
	return (fd);
}

static int mksockets()
{
	struct portinfo *p;

	maxfd= -1;

	for (p=fdlist; p; p=p->next)
	{
		int fd;
		struct in_addr addr;
		int fd_flag=0;

		if (p->ipaddr && strcmp(p->ipaddr, "0"))
		{
			/* FreeBSD needs AF_INET binds for IPv4 addys */

			if (rfc1035_aton_ipv4(p->ipaddr, &addr) == 0)
			{
				fd_flag=MKS_USEAFINET4;
			}
		}

		fd=mksocket(p->ipaddr, p->servname, fd_flag);

		if (fd < 0)
			break;

		p->fd1=fd;

		if (fd > maxfd)
			maxfd=fd;

		/* BSD requires both an IPv6 and an IPv4 socket */

#if	RFC1035_IPV6
		if (p->ipaddr == 0 || strcmp(p->ipaddr, "0") == 0)
		{
			fd=mksocket(p->ipaddr, p->servname,
				    (MKS_USEAFINET4|MKS_ERROK));

			if (fd == -2)
				continue;	/* Ok if bind failed */
			if (fd < 0)
				break;

			if (fd > maxfd)
				maxfd=fd;
			p->fd2=fd;
		}
#endif

	}

	if (p)	/* Clean up after ourselves, after an error */
	{
		for (p=fdlist; p; p=p->next)
		{
			if (p->fd1 >= 0)
				close(p->fd1);
			if (p->fd2 >= 0)
				close(p->fd2);
		}
		return (-1);
	}

	return (0);
}

static int init(int argc, char **argv)
{
int	argn;

struct	group *gr;
int	i;
gid_t	gid=0;
const	char *servname;
int	forced=0;
int	lockfd=-1;
 
	argn=argparse(argc, argv, arginfo);

	if ((stoparg || restartarg) && pidarg == 0)
	{
		fprintf(stderr, "%s: -pid argument is required.\n", argv[0]);
		return (-1);
	}

	if (pidarg)
	{
		lockfilename=malloc(strlen(pidarg)+sizeof(".lock"));
		if (!lockfilename)
		{
			perror("malloc");
			return (-1);
		}
		strcat(strcpy(lockfilename, pidarg), ".lock");
	}

	if (stoparg)
	{
		ll_daemon_stop(lockfilename, pidarg);
		exit(0);
	}

	if (restartarg)
	{
		ll_daemon_restart(lockfilename, pidarg);
		exit(0);
	}

	if (argc - argn < 2)
	{
		fprintf(stderr, "Usage: %s [options] port prog arg1 arg2...\n",
			argv[0]);
		return (-1);
	}

	if (pidarg) { /* -start implied for backwards compatibility */
		lockfd=ll_daemon_start(lockfilename);
		if (lockfd < 0)
		{
			perror("ll_daemon_start");
			return (-1);
		}
	}

	servname=argv[argn++];

	if (parseaddr(servname))
	{
		close(lockfd);
		return (-1);
	}

	if (mksockets())
	{
		close(lockfd);
		return (-1);
	}

	signal(SIGINT, sigexit);
	signal(SIGHUP, sighup);
	signal(SIGTERM, sigexit);

#if 0
	{
	int	fd2;
	int	dummy;

		perror("bind");
		if (!forcebindarg || errno != EADDRINUSE)
		{
			sox_close(fd);
			return (-1);
		}

		/* Poke around */

		if ((fd2=rfc1035_mksocket(SOCK_STREAM, 0, &dummy)) < 0)
			/* Better get same socket as fd */
		{
			perror("socket");
			sox_close(fd);
			return (-1);
		}

		if (sox_connect(fd2, (struct sockaddr *)sinaddr,
			sinaddrlen) == 0)
		{
			sox_close(fd2);
			sox_close(fd);
			return (-1);
		}
		sox_close(fd2);
		savepid();
		sleep(60);
		forced=1;
	}
#endif

	if (pidarg)
		ll_daemon_started(pidarg, lockfd);

	if (grouparg)
	{
		if (isid(grouparg))
			gid=atoi(grouparg);
		else if ((gr=getgrnam(grouparg)) == 0)
		{
			fprintf(stderr, "Group not found: %s\n", grouparg);
			close(lockfd);
			return (-1);
		}
		else	gid=gr->gr_gid;

		libmail_changegroup(gid);
	}

	if (userarg)
	{
	uid_t	uid;

		if (isid(userarg))
		{
			uid=atoi(userarg);
			libmail_changeuidgid(uid, getgid());
		}
		else
		{
		gid_t	g=getgid(), *gp=0;

			if (grouparg)	gp= &g;
			libmail_changeusername(userarg, gp);
		}
	}

	if (pidarg && ll_daemon_resetio())
	{
		perror("ll_daemon_resetio");
		close(lockfd);
		return (-1);
	}

	if (stderrloggerarg)
	{
	pid_t	p;
	int	waitstat;
	int	pipefd[2];
	const char *progname=argv[argn];

		if (pipe(pipefd) < 0)
		{
			perror("pipe");
			return (-1);
		}

		signal(SIGCHLD, SIG_DFL);
		while ((p=fork()) == -1)
		{
			sleep(5);
		}

		if (p == 0)
		{
			signal(SIGHUP, SIG_IGN);
			sox_close(0);
			sox_dup(pipefd[0]);
			sox_close(pipefd[0]);
			sox_close(pipefd[1]);
			sox_close(1);
			open("/dev/null", O_WRONLY);
			sox_close(2);
			sox_dup(1);
			closeaccess();
			while ((p=fork()) == -1)
			{
				sleep(5);
			}
			if (p == 0)
			{
			const char *p=strrchr(progname, '/');

				if (p)	++p;
				else p=progname;

				if (stderrloggername && *stderrloggername)
					p=stderrloggername;

				execl(stderrloggerarg, stderrloggerarg,
						p, (char *)0);
				perror(stderrloggerarg);
				_exit(5);
			}
			_exit(0);
		}
		sox_close(2);
		sox_dup(pipefd[1]);
		sox_close(pipefd[0]);
		sox_close(pipefd[1]);
		while (wait(&waitstat) != p)
			;
	}
	else if (stderrarg)
	{
	int	fd=open(stderrarg, O_WRONLY|O_APPEND|O_CREAT, 0660);

		if (!fd)
		{
			perror(stderrarg);
			return (-1);
		}
		sox_close(2);
		sox_dup(fd);
		sox_close(fd);
	}

	nprocs=40;
	if (maxprocsarg)
	{
		nprocs=atoi(maxprocsarg);
		if (nprocs <= 0)
		{
			fprintf(stderr, "Invalid -maxprocsarg option.\n");
			return (-1);
		}
	}

	nwarn= nprocs - (nprocs / 10 + 1);

	if (warnarg)
	{
		int c=atoi(warnarg);

		if (c >= 0 && c <= nprocs)
			nwarn=c;
	}

	if ((pids=malloc(sizeof(*pids)*nprocs)) == 0)
	{
		perror("malloc");
		return (-1);
	}
	if ((addrs=malloc(sizeof(*addrs)*nprocs)) == 0)
	{
		free(pids);
		perror("malloc");
		return (-1);
	}


	for (i=0; i<nprocs; i++)
		pids[i]= -1;

	maxperc=nprocs;
	maxperip=4;

	if (maxpercarg)
	{
		maxperc=atoi(maxpercarg);
		if (maxperc <= 0)
		{
			fprintf(stderr, "Invalid -maxperc option.\n");
			free(pids);
			free(addrs);
			return (-1);
		}
	}
	if (maxperiparg)
	{
		maxperip=atoi(maxperiparg);
		if (maxperip <= 0)
		{
			fprintf(stderr, "Invalid -maxperip option.\n");
			free(pids);
			free(addrs);
			return (-1);
		}
	}
	if (forced)
	{
		fprintf(stderr, "couriertcpd: ready.\n");
		fflush(stderr);
	}
	return (argn);
}

static void run(int, const RFC1035_ADDR *, int, const char *, char **);

static void doreap(pid_t p, int wait_stat)
{
int	n;

	for (n=0; n<nprocs; n++)
		if (p == pids[n])
		{
			pids[n]= -1;
			break;
		}
}

static RETSIGTYPE childsig(int signum)
{
	signum=signum;
	wait_reap(doreap, childsig);
#if RETSIGTYPE != void
	return (0);
#endif
}

static int doallowaccess(char *, int);

#if	RFC1035_IPV6

static int allowaccess(const RFC1035_ADDR *sin, int port)
{
char	buf[RFC1035_MAXNAMESIZE+1+6];
char	*q;
int	i;

	if (IN6_IS_ADDR_V4MAPPED(sin))
	{
	const char *p=inet_ntop(AF_INET6, sin, buf, sizeof(buf)-6);

		if (p && (q=strrchr(buf, ':')) != 0)
			return (doallowaccess(q+1, port));
		return (1);
	}
	q=buf;
	for (i=0; i<sizeof(*sin); i += 2)
	{
#define	B(i) ((unsigned long)((unsigned char *)sin)[i])
	unsigned long n=(B(i) << 8) | B(i+1);
#undef	B
		sprintf(q, ":%04lx", n);
		q += 5;
	}
	*q=0;
	return (doallowaccess(buf, port));
}

#else
static int allowaccess(const RFC1035_ADDR *sin, int port)
{
char	buf[RFC1035_NTOABUFSIZE+6];

	rfc1035_ntoa(sin, buf);
	return (doallowaccess(buf, port));
}
#endif

static int doallowaccess(char *buf, int port)
{
char	*accessptr;
char *p, *q, *r;
int	quote=0;
int	l;

	if (accessarg == 0)	return (1);

	if (port) snprintf(buf+strlen(buf), 7, ".%d", ntohs(port));
	while ((accessptr= *buf ? chkaccess(buf):0) == 0)
	{
		if ((accessptr=strrchr(buf, '.')) == 0
#if RFC1035_IPV6
			&& (accessptr=strrchr(buf, ':')) == 0
#endif
			)
		{
			if (port)
			{
				snprintf(buf, 8, "*.%d", ntohs(port));
				if ((accessptr=chkaccess(buf)) != 0)
					break;
			}
			if ((accessptr=chkaccess("*")) != 0)
				break;
			return (1);
		}
		*accessptr=0;
	}

	if (strncmp(accessptr, "deny", 4) == 0)
	{
		free(accessptr);
		return (0);
	}

	p=accessptr;
	if (strncmp(accessptr, "allow", 5) == 0)
	{
		p += 5;
		if (*p == ',')	++p;
	}

	while ( p && *p )
	{
		q=p;
		r=q;
		while (*p)
		{
			if (*p == ',' && !quote)
			{
				*p++=0;
				break;
			}
			if (!quote && (*p == '"' || *p == '\''))
			{
				quote=*p;
				p++;
				continue;
			}
			if (quote && *p == quote)
			{
				quote=0;
				p++;
				continue;
			}
			*r++=*p++;
		}
		*r=0;
		if (strchr(q, '=') == 0)
		{
		char	*r=malloc(strlen(q)+2);

			if (!r)
			{
				perror("malloc");
				return (0);
			}
			q=strcat(strcpy(r, q), "=");
		}

		while (*q && isspace((int)(unsigned char)*q))	++q;
		while ((l=strlen(q)) > 0
		       && isspace((int)(unsigned char)q[l-1]))
			q[--l]=0;
		putenv(q);
	}
	return (1);
}

/* Wait until we have at least one available slot left */

static int getfreeslot(int *pidptr)
{
	int n;

	for (;;)
	{
		wait_block();

		for (n=0; n<nprocs; n++)
		{
			if (pids[*pidptr] == (pid_t)-1)	break;
			if (++*pidptr >= nprocs)	*pidptr=0;
		}
		if (pids[*pidptr] != (pid_t)-1)
		{
			wait_forchild(doreap, childsig);
			continue;
		}
		break;
	}
	wait_clear(childsig);
	return (*pidptr);
}

static void accepted(int, int, RFC1035_NETADDR *, int, const char *, char **);

static int doit(int argn, int argc, char **argv)
{
	char	**ptrs;
	int	pidptr;
	struct portinfo *pi;
	fd_set fdr, fdrcopy;
	int	dummy;

	ptrs=(char **)malloc((argc-argn+1) * sizeof(char *));
	if (!ptrs)
	{
		perror("malloc");
		return (-1);
	}
	for (dummy=0; dummy<argc-argn; dummy++)
	{
		ptrs[dummy]=argv[argn+dummy];
	}
	ptrs[dummy]=0;

	if (listenarg)
	{
		dummy=atoi(listenarg);
		if (dummy <= 0)
		{
			fprintf(stderr, "Invalid -listen option.\n");
			exit(1);
		}
	}

	FD_ZERO(&fdrcopy);
	for (pi=fdlist; pi; pi=pi->next)
	{
		if (pi->fd1 >= 0)
			FD_SET(pi->fd1, &fdrcopy);

		if (pi->fd2 >= 0)
			FD_SET(pi->fd2, &fdrcopy);
	}

	pidptr=0;

	signal(SIGCHLD, childsig);

#if	HAVE_SETPGRP
#if	SETPGRP_VOID
	setpgrp();
#else
	setpgrp(0, 0);
#endif
#else
#if	HAVE_SETPGID
	setpgid(0, 0);
#endif
#endif
#ifdef  TIOCNOTTY

	{
	int fd=open("/dev/tty", O_RDWR);

		if (fd >= 0)
		{
			ioctl(fd, TIOCNOTTY, 0);
			close(fd);
		}
	}
#endif

	signal(SIGPIPE, SIG_IGN);
	for (;;)
	{
		int n;
		int sockfd;
		RFC1035_NETADDR	sin;
		socklen_t	sinl;

		fdr=fdrcopy;

		if (select(maxfd+1, &fdr, NULL, NULL, NULL) <= 0)
		{
			if (errno != EINTR)
				perror("accept");
			continue;
		}

		for (pi=fdlist; pi; pi=pi->next)
		{
			if (pi->fd1 >= 0 && FD_ISSET(pi->fd1, &fdr) &&
			    ((n=getfreeslot(&pidptr)),
			     (sinl = sizeof(sin)),
			     (sockfd=sox_accept(pi->fd1,
						(struct sockaddr *)&sin,
						&sinl))) >= 0)
			{
				accepted(n, sockfd, &sin, sinl,
					 argv[argn], ptrs);
			}

			if (pi->fd2 >= 0 && FD_ISSET(pi->fd2, &fdr) &&
			    ((n=getfreeslot(&pidptr)),
			     (sinl = sizeof(sin)), 
			     (sockfd=sox_accept(pi->fd2,
						(struct sockaddr *)&sin,
						&sinl))) >= 0)
			{
				accepted(n, sockfd, &sin, sinl,
					 argv[argn], ptrs);
			}
		}
	}
}

static void denied(int sockfd)
{
	if (denymsgarg) {
		if (write(sockfd, denymsgarg, strlen(denymsgarg)) < 0 ||
		    write(sockfd, "\n", 1) < 0)
		{
			sox_close(sockfd);
			_exit(1);
		}
	}
	sox_close(sockfd);
	_exit(0);
}

static void accepted(int n, int sockfd, RFC1035_NETADDR *sin, int sinl,
		     const char *prog,
		     char **args)
{
	RFC1035_ADDR addr;
	int	addrport;
#ifdef	SO_LINGER
	int	dummy;
	struct	linger l;
#endif
	pid_t	p;
	int cnt;

	if (rfc1035_sockaddrip(sin, sinl, &addr)
	    || rfc1035_sockaddrport(sin, sinl, &addrport))
	{
		sox_close(sockfd);
		return;
	}

	/* Turn off the CLOEXEC and NONBLOCK bits */

	if (fcntl(sockfd, F_SETFD, 0))
	{
		perror("fcntl");
		sox_close(sockfd);
		return;
	}

	if (fcntl(sockfd, F_SETFL, 0))
	{
		perror("fcntl");
		sox_close(sockfd);
		return;
	}

	if (sighup_received)
	{
		sighup_received=0;
		if (accessarg)
		{
			closeaccess();
			if (openaccess(accessarg))
				perror(accessarg);
		}
	}

#ifdef	SO_KEEPALIVE
	dummy=1;
	if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE,
		       (const char *)&dummy, sizeof(dummy)) < 0)
	{
		perror("setsockopt");
	}
#endif

#ifdef	SO_LINGER
	l.l_onoff=0;
	l.l_linger=0;

	if (setsockopt(sockfd, SOL_SOCKET, SO_LINGER,
		       (const char *)&l, sizeof(l)) < 0)
	{
		perror("setsockopt");
	}
#endif
	wait_block();
	if ((p=fork()) == -1)
	{
		perror("fork");
		sox_close(sockfd);
		return;
	}

	if (p == 0)
	{
		wait_restore(childsig);

		if (accesslocal) /* Lookup local interface address too? */
		{
		RFC1035_NETADDR lsin;
		RFC1035_ADDR laddr;
		int	lport;
		socklen_t	i=sizeof(lsin);
		
			if (sox_getsockname(sockfd, (struct sockaddr *)&lsin, &i) == 0 &&
				rfc1035_sockaddrip(&lsin, i, &laddr) == 0 &&
				rfc1035_sockaddrport(&lsin, i, &lport) == 0 &&
				allowaccess(&laddr,lport) == 0)
			{
				sox_close(sockfd);
				_exit(0);
			}
		}

		if (allowaccess(&addr,0) == 0)
		{
			denied(sockfd);
		}

		run(sockfd, &addr, addrport, prog, args);
	}
	pids[n]=p;

	memcpy(addrs+n, &addr, sizeof(addr));
	sox_close(sockfd);
	wait_clear(childsig);

	for (cnt=n=0; n<nprocs; n++)
		if (pids[n] != (pid_t)-1)
			++cnt;

	if (cnt == nprocs)
	{
		time_t t;

		time(&t);
		if (last_alert == 0 || last_alert > t || last_alert < t - 60)
		{
			last_alert=t;
			fprintf(stderr,
				"ALERT: %d maximum active connections.\n",
				nprocs);
		}
	}
	else if (cnt >= nwarn)
	{
		time_t t;

		time(&t);
		if (last_warn == 0 || last_warn > t || last_warn < t - 60)
		{
			last_warn=t;
			fprintf(stderr, "WARN: %d active connections.\n",
				cnt);
		}
	}
}

static void mysetenv(const char *name, const char *val)
{
char	*p=malloc(strlen(name)+strlen(val)+2);

	if (!p)
	{
		perror("malloc");
		_exit(1);
	}
	putenv(strcat(strcat(strcpy(p, name), "="), val));
}

/*
** Convert IP address to host name.  Make sure the IP address resolves
** backwards and forwards.
*/

static void ip2host(const RFC1035_ADDR *addr, const char *env)
{
const char *remotehost="softdnserr";
char	buf[RFC1035_MAXNAMESIZE+1];

#if TCPDUSERFC1035
struct rfc1035_res res;
#endif

	if (nodnslookup)	return;

	rfc1035_ntoa(addr, buf);

#if TCPDUSERFC1035

	rfc1035_init_resolv(&res);

	if (rfc1035_ptr(&res, addr, buf) != 0)
	{
		if (errno == ENOENT)
			remotehost=0;
	}
	else
	{
		RFC1035_ADDR *ias;
		unsigned nias, n;

		if (rfc1035_a(&res, buf, &ias, &nias) != 0)
		{
			if (errno == ENOENT)
				remotehost=0;
		}
		else
		{
			remotehost=0;
			for (n=0; n<nias; n++)
			{
			char	a[RFC1035_MAXNAMESIZE];
			char	b[RFC1035_MAXNAMESIZE];

				rfc1035_ntoa(&ias[n], a);
				rfc1035_ntoa(addr, b);

				if (strcmp(a, b) == 0)
				{
					remotehost=buf;
				}
			}
		}
	}
	rfc1035_destroy_resolv(&res);

#else

	{
	struct hostent *he;
	unsigned n;
	struct	in_addr in;

#if	RFC1035_IPV6

		if (IN6_IS_ADDR_V4MAPPED(addr))
			memcpy(&in, (char *)addr + 12, 4);
		else	return;
#else
		in= *addr;
#endif

		he=gethostbyaddr( (char *)&in, sizeof(in), AF_INET);
		if (!he)
		{
			switch (h_errno)	{
			case HOST_NOT_FOUND:
			case NO_DATA:
				remotehost=0;
				break;
			}
		}
		else
		{
			strcpy(buf, he->h_name);
			he=gethostbyname(buf);
			if (!he)
			{
				switch (h_errno)	{
				case HOST_NOT_FOUND:
				case NO_DATA:
					remotehost=0;
					break;
				}
			}
			else for (n=0, remotehost=0; he->h_addr_list[n]; n++)
			{
			struct in_addr hin;

				if (he->h_addrtype != AF_INET ||
					he->h_length < sizeof(hin))
					break;
				memcpy((char *)&hin, he->h_addr_list[n],
					sizeof(hin));
				if (hin.s_addr == in.s_addr)
				{
					remotehost=buf;
					break;
				}
			}

		}
	}
#endif
	if (remotehost)
		mysetenv(env, remotehost);
}

static void mkmymsg(const char *varname, const char *msg)
{
const char *p=getenv("TCPREMOTEIP");
char *q=malloc(strlen(msg)+1+strlen(p));
char *r;

	if (!q)
	{
		perror("malloc");
		exit(1);
	}

	for (r=q; *msg; msg++)
	{
		if (*msg == '@')
		{
			strcpy(r, p);
			while (*r)	r++;
			++msg;
			break;
		}
		*r++=*msg;
	}
	while (*msg)
		*r++=*msg++;
	*r=0;
	mysetenv(varname, q);
	free(q);
}

/*
** check_blocklist is called once for each blocklist query to process.
*/

static void docheckblocklist(struct blocklist_s *p, const char *nameptr)
{
	const char *q;
	const char *varname=p->var;
	char	hostname[RFC1035_MAXNAMESIZE+1];
	char	buf[RFC1035_MAXNAMESIZE+1];
	int	hasnotxt;
	struct blocklist_s *pp;
	struct rfc1035_reply *replyp;
	struct rfc1035_res res;
	int i;

	hostname[0]=0;
	strncat(hostname, nameptr, RFC1035_MAXNAMESIZE);

	if (!varname)	varname="BLOCK";

	if ((q=getenv(varname)) != 0)	return;
					/* Env var already set */

	rfc1035_init_resolv(&res);

	if (p->ia.s_addr == INADDR_ANY)
	{
		/* We're not looking for a particular token IP address */

		if (p->msg == 0 || *p->msg == 0)
		{
			/*
			** We don't have a predefined error message, therefore
			** we expect TXT records.
			*/
			if ((i=rfc1035_resolve_cname(&res,
					hostname,
					RFC1035_TYPE_TXT,
					RFC1035_CLASS_IN, &replyp, 0)) >= 0)
			{
				rfc1035_rr_gettxt(replyp->allrrs[i], 0,
					hostname);
				mysetenv(varname, hostname);
			}
		}
		else
		{
			/*
			** A predefined error message has been supplied, so
			** just look for the IP address.  Perhaps the
			** blacklist does not provide TXT records, so query
			** for A records only.
			*/

			if ((i=rfc1035_resolve_cname(&res,
					hostname,
					RFC1035_TYPE_A,
					RFC1035_CLASS_IN, &replyp, 0)) >= 0)
			{
				mkmymsg(varname, p->msg);
			}
		}

		if (replyp)	rfc1035_replyfree(replyp);
		rfc1035_destroy_resolv(&res);
		return;
	}

	/*
	** Looking for a specific A record in the blacklist.  Possibly due
	** to different messages for different A records.
	** See if all -block options for the same zone have predefined
	** error messages, so we can be satisfied with an A query only.
	*/

	hasnotxt=0;

	for (pp=p; pp; pp=pp->next)
	{
		if (strcmp(pp->zone, p->zone))	continue;
		if (pp->msg == 0 || *pp->msg == 0)	hasnotxt=1;
	}

	/*
	** If no text were provided, we need both A and TXT records, so
	** issue an ANY query, and parse the results.
	*/

	(void)rfc1035_resolve_cname(&res,
			hostname,
			hasnotxt ? RFC1035_TYPE_ANY:RFC1035_TYPE_A,
			RFC1035_CLASS_IN, &replyp, 0);

	if (!replyp)
	{
		rfc1035_destroy_resolv(&res);
		return;
	}

	for (i=0; i<replyp->ancount+replyp->nscount+replyp->arcount; i++)
	{
	int	j;

		/*
		** Go through the DNS response, and check every A record
		** in there.
		*/
		rfc1035_replyhostname(replyp, replyp->allrrs[i]->rrname, buf);
		if (rfc1035_hostnamecmp(buf, hostname))	continue;
		if (replyp->allrrs[i]->rrtype != RFC1035_TYPE_A)
			continue;

		/*
		** Go through the remaining blocklist, and set the environment
		** variable for each block entry for this zone and IP address.
		*/
		for (pp=p; pp; pp=pp->next)
		{
		const char *vvarname=pp->var;

			if (strcmp(pp->zone, p->zone))	continue;

			if (pp->ia.s_addr != replyp->allrrs[i]->rr.inaddr.s_addr)
				continue;

			if (!vvarname)	vvarname="BLOCK";

			/*
			** The -block option was kind enough to supply the
			** error message.
			*/

			if (pp->msg && *pp->msg)
			{
				mkmymsg(vvarname, pp->msg);
				continue;
			}

			/* No predefined message, look for a TXT record. */

			if ((j=rfc1035_replysearch_all(&res,
						       replyp, hostname,
						       RFC1035_TYPE_TXT,
						       RFC1035_CLASS_IN, 0)) >= 0)
			{
				rfc1035_rr_gettxt(replyp->allrrs[j], 0, buf);
				mysetenv(vvarname, buf);
			}
			else	mysetenv(vvarname, "Access denied.");
		}
	}
	rfc1035_replyfree(replyp);
	rfc1035_destroy_resolv(&res);
}

static void check_blocklist_ipv4(struct blocklist_s *p,
	const struct in_addr *ia)
{
unsigned a,b,c,d;
char	hostname[RFC1035_MAXNAMESIZE+1];
const unsigned char *q=(const unsigned char *)ia;

	/* Calculate DNS query hostname */

	a=q[0];
	b=q[1];
	c=q[2];
	d=q[3];

	sprintf(hostname, "%u.%u.%u.%u.%s", d, c, b, a, p->zone);
	docheckblocklist(p, hostname);
}

#if	RFC1035_IPV6

static void check_blocklist(struct blocklist_s *p, const RFC1035_ADDR *ia)
{
	if (IN6_IS_ADDR_V4MAPPED(ia))
	{
	struct in_addr ia4;

		memcpy(&ia4, (const char *)ia + 12, 4);
		check_blocklist_ipv4(p, &ia4);
	}
}

#else
static void check_blocklist(struct blocklist_s *p, const RFC1035_ADDR *ia)
{
	check_blocklist_ipv4(p, ia);
}
#endif

static void check_drop(int sockfd)
{
	const char *p, *q;
	char *r;

	p=droparg;

	if (p && !*p)
		p="BLOCK";

	for (; p && *p; q=p)
	{
		if (*p == ',')
		{
			q= ++p;
			continue;
		}

		for (q=p; *q; ++q)
			if (*q == ',')
				break;

		r=malloc(q-p+1);

		if (!r)
		{
			perror("malloc");
			_exit(1);
		}

		memcpy(r, p, q-p);
		r[q-p]=0;

		p=getenv(r);
		free(r);

		if (p && *p)
		{
			fprintf(stderr,
				"WARN: dropped blocked connection from %s\n",
				getenv("TCPREMOTEIP"));
			denied(sockfd);
		}
	}
}

static void proxy();

static void run(int fd, const RFC1035_ADDR *addr, int addrport,
	const char *prog, char **argv)
{
RFC1035_NETADDR lsin;
RFC1035_ADDR laddr;
int	lport;

socklen_t	i;
int	ipcnt, ccnt;
char	buf[RFC1035_MAXNAMESIZE+128];
struct blocklist_s *bl;
const char *remoteinfo;
const char *p;

	i=sizeof(lsin);
	if (sox_getsockname(fd, (struct sockaddr *)&lsin, &i) ||
		rfc1035_sockaddrip(&lsin, i, &laddr) ||
		rfc1035_sockaddrport(&lsin, i, &lport))
	{
		fprintf(stderr, "getsockname failed.\n");
		exit(1);
	}

	if (!noidentlookup && (remoteinfo=tcpremoteinfo(
		&laddr, lport,
		addr, addrport, 0)) != 0)
	{
	char	*q=malloc(sizeof("TCPREMOTEINFO=")+strlen(remoteinfo));

		if (!q)
		{
			perror("malloc");
			_exit(1);
		}

		strcat(strcpy(q, "TCPREMOTEINFO="), remoteinfo);
		putenv(q);
	}

/* check if it's an exception to the global ip limit */
	if( (p=getenv("MAXCPERIP")) != NULL )
	{
		int j = atoi(p);

		if( j > 0 )
			maxperip = j;
	}

	for (i=0, ipcnt=ccnt=0; i<nprocs; i++)
	{
	RFC1035_ADDR *psin;
	int	j;

		if (pids[i] == (pid_t)-1)	continue;

		psin=addrs+i;

		for (j=0; j<sizeof(*addr); j++)
			if ( ((char *)addr)[j] != ((char *)psin)[j])
				break;

		if (j >= sizeof(*addr) &&
			++ipcnt >= maxperip)
		{
			rfc1035_ntoa(addr, buf);
			fprintf(stderr,"ALERT: Maximum connection limit reached for %s\n",buf);
			_exit(0);	/* Too many from same IP address */
		}

		if ( j >= sizeof(*addr)-1 &&
			++ccnt >= maxperc)
			_exit(0);	/* Too many from same netblock */

	}

	rfc1035_ntoa(addr, buf);
	mysetenv("TCPREMOTEIP", buf);
	sprintf(buf, "%d", ntohs(addrport));
	mysetenv("TCPREMOTEPORT", buf);
	ip2host(addr, "TCPREMOTEHOST");

	rfc1035_ntoa(&laddr, buf);
	mysetenv("TCPLOCALIP", buf);
	sprintf(buf, "%d", ntohs(lport));
	mysetenv("TCPLOCALPORT", buf);
	ip2host(&laddr, "TCPLOCALHOST");

	for (bl=blocklist; bl; bl=bl->next)
		check_blocklist(bl, addr);

	check_drop(fd);
	sox_close(0);
	sox_close(1);
	sox_dup(fd);
	sox_dup(fd);
	sox_close(fd);
	if (stderrarg && strcmp(stderrarg, "socket") == 0)
	{
		sox_close(2);
		sox_dup(1);
	}
	proxy();
	signal(SIGPIPE, SIG_DFL);

	execv(prog, argv);
	perror(prog);
	exit(1);
}

int main(int argc, char **argv)
{
int argn=init(argc, argv);
int rc;

	if (argn < 0)
	{
		exit(1);
	}
	if (accessarg && openaccess(accessarg))
		perror(accessarg);
	if (accesslocal && !accessarg)
		fprintf(stderr,"-accesslocal requires -access\n");
	rc=doit(argn, argc, argv);
	kill( -getpid(), SIGTERM);
	exit(rc);
	return (0);
}

#if 1

static void proxy()
{
}

#else

/*
** SOCKSv5 does not support wildcards binds, for now, so there's no
** reason to manually proxy anything, yet.
*/


/***************************************************************************

Manual proxy, to support SOCKS encryption.  Because encrypted connection to
the SOCKS server is supported transparently in libsocks5, we can't just
run the app, because we'll lose libsocks5's intercept of read/write, et al

***************************************************************************/

struct proxybuf {
	char buffer[BUFSIZ];
	char	*p;
	int buffered;
	int rfd, wfd;
	} ;

static void proxy_init(struct proxybuf *b, int r, int w)
{
	b->buffered=0;
	b->rfd=r;
	b->wfd=w;
}

static int proxy_setfd(struct proxybuf *b, fd_set *r, fd_set *w, int *max)
{
	/* If we have something buffered, write it out */

	if (b->buffered)
	{
		FD_SET(b->wfd, w);
		if (b->wfd > *max)	*max=b->wfd;
		return (1);
	}

	if (b->rfd < 0)
	{
		if (b->wfd >= 0)
		{
			sox_close(b->wfd);
			b->wfd= -1;
		}
		return (0);	/* Nothing else to do */
	}

	if (b->rfd > *max)	*max=b->rfd;
	FD_SET(b->rfd, r);
	return (1);
}

static void proxy_dofd(struct proxybuf *b, fd_set *r, fd_set *w)
{
	if (b->buffered)
	{
		if (FD_ISSET(b->wfd, w))
		{
		int	n=sox_write(b->wfd, b->p, b->buffered);

			if (n <= 0)
			{
				sox_close(b->rfd);
				sox_close(b->wfd);
				b->rfd=b->wfd= -1;
				b->buffered=0;
				return;
			}
			b->p += n;
			b->buffered -= n;
		}
		return;
	}

	if (b->rfd >= 0 && FD_ISSET(b->rfd, r))
	{
	int	n=sox_read(b->rfd, b->buffer, sizeof(b->buffer));

		if (n <= 0)
		{
			sox_close(b->rfd);
			sox_close(b->wfd);
			b->rfd=b->wfd= -1;
			b->buffered=0;
			return;
		}
		b->p = b->buffer;
		b->buffered=n;
	}
}

static void proxy_do(struct proxybuf *);

static void proxy()
{
int	pipefd0[2], pipefd1[2], pipefd2[2];
pid_t	p, p2;
int	waitstat;
struct proxybuf proxy_[3];

	if (!proxyarg)	return;

	if (pipe(pipefd0) || pipe(pipefd1) || pipe(pipefd2))
	{
		perror("pipe");
		exit(1);
	}

	p=fork();
	if (p == -1)
	{
		perror("fork");
		exit(1);
	}

	/*
	** The parent goes on its merry way, but first makes sure that the
	** child process is OK.
	*/

	if (p)
	{
		while ((p2=wait(&waitstat)) != p)
		{
			if (p2 == -1 && errno != EINTR)
			{
				perror("wait");
				exit(1);
			}
		}
		if (waitstat)
			exit(0);
		sox_close(0);
		sox_close(1);
		sox_close(2);
		errno=EINVAL;
		if (sox_dup(pipefd0[0]) != 0 ||
			sox_dup(pipefd1[1]) != 1 ||
			sox_dup(pipefd2[1]) != 2)
		{
			perror("dup(app)");
			exit(1);
		}
		sox_close(pipefd0[0]);
		sox_close(pipefd0[1]);
		sox_close(pipefd1[0]);
		sox_close(pipefd1[1]);
		sox_close(pipefd2[0]);
		sox_close(pipefd2[1]);
		return;
	}

	p=fork();
	if (p == -1)	exit(1);
	if (p)	exit(0);

	sox_close(pipefd0[0]);
	sox_close(pipefd1[1]);
	sox_close(pipefd2[1]);

	proxy_init(&proxy_[0], 0, pipefd0[1]);
	proxy_init(&proxy_[1], pipefd1[0], 1);
	proxy_init(&proxy_[2], pipefd2[0], 2);
	proxy_do(proxy_);
	exit(0);
}

static void proxy_do(struct proxybuf *p)
{
fd_set	r, w;

	for (;;)
	{
	int	m=0;
	int	rc0, rc1, rc2;

		FD_ZERO(&r);
		FD_ZERO(&w);

		rc0=proxy_setfd(p, &r, &w, &m);
		rc1=proxy_setfd(p+1, &r, &w, &m);
		rc2=proxy_setfd(p+2, &r, &w, &m);

		if (rc0 == 0 && rc1 == 0 && rc2 == 0)
			break;

		if (rc0 == 0 || rc1 == 0 || rc2 == 0)
			alarm(10);

		if (select(m+1, &r, &w, 0, 0) < 0)
		{
			perror("select");
			break;
		}

		proxy_dofd(p, &r, &w);
		proxy_dofd(p+1, &r, &w);
		proxy_dofd(p+2, &r, &w);
	}
}
#endif
