#include <sys/time.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <netinet/in.h>
#include <sys/errno.h>


#define SOCKS_TIMEOUT	2*60*60	/* 2hr in seconds */
#define SOCKS_VERSION	4
#define CSTC_RELEASE	"4.2.1"

/*
**  Response commands/codes
*/
#define SOCKS_CONNECT	1
#define SOCKS_BIND	2
#define SOCKS_RESULT	90
#define SOCKS_FAIL	91
#define SOCKS_NO_IDENTD	92 /* Failed to connect to Identd on client machine */
#define SOCKS_BAD_ID	93 /* Client's Identd reported a different user-id */
  
typedef struct {
	unsigned long		host; /* in network byte order */
	unsigned short		port; /* in network byte oreder */
	unsigned char		version;
	unsigned char		cmd;
} Socks_t;

int		Version = 0;
extern int errno;


void die()
{
	exit(1);
}


main(argc, argv)
int	argc;
char	*argv[];
{
	char				c;
	int					in;
	Socks_t				dst;

	if (argc >= 2) {
		printf(" CSTC single-homed, inetd-controlled SOCKS proxy server version %s.\n", CSTC_RELEASE);
		printf(" Does not support clients that use Rrcmd().\n");
		printf(" Modified for Amiga by Ch. Dworzak (dworz@ibm.net).\n");
		exit(1);
	}

	in = dup(0);

	if (socks_GetDst(in, &dst) < 0) {
		exit(1);
	}

	if (dst.version != SOCKS_VERSION) {
		exit(1);
	}

	if (dst.cmd != SOCKS_CONNECT && dst.cmd != SOCKS_BIND) {
		exit(1);
	}

	while (read(in, &c, 1) == 1)
		if (c == '\0')
			break;

	signal(SIGALRM, die);
	alarm(60*2);

	if (dst.cmd == SOCKS_CONNECT) {
		DoConnect(in, &dst);
	}
	if (dst.cmd == SOCKS_BIND) {
		DoNewBind(in, &dst);
	}
}

socks_fail(in, ndst)
int	in;
Socks_t	*ndst;
{
	ndst->cmd = SOCKS_FAIL;
	socks_SendDst(in, ndst);
	exit(1);
}


/*
** Actually connect a socket to the outside world,
*/
DoConnect(in, dst)
int	in;
Socks_t	*dst;
{
	int			out;
	struct sockaddr_in	sin;
	Socks_t			ndst;

	bzero((char *)&sin, sizeof(sin));
	if ((out = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		socks_fail(in, &ndst);

	sin.sin_family = AF_INET;
	sin.sin_port = dst->port;
	sin.sin_addr.s_addr = dst->host;

	ndst.version = Version;
	ndst.cmd = SOCKS_RESULT;

	if (connect(out, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) < 0)
		socks_fail(in, &ndst);

	socks_SendDst(in, &ndst);
	Pump(in, out);
}

/*
**  Set up a socket to be connected to from the outside world.
**   diffrence between this an the Version1 protocal is that
**   the socket has to be bound from a specific host that
**   is passed.
*/
DoNewBind(in, dst)
int	in;
Socks_t	*dst;
{
	int					new, out, len;
	struct sockaddr_in	sin;
	Socks_t				ndst;

	bzero((char *)&sin, sizeof(sin));

	sin.sin_family = AF_INET;
	ndst.version = Version;
	ndst.cmd  = SOCKS_RESULT;
	sin.sin_port = 0;
	sin.sin_addr.s_addr = INADDR_ANY;

	if ((out = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		socks_fail(in, &ndst);

	if (bind(out, (struct sockaddr *)&sin, sizeof(sin)) < 0)
		socks_fail(in, &ndst);

	ndst.port = sin.sin_port;
	ndst.host = sin.sin_addr.s_addr;

	if (listen(out, 1) < 0)
		socks_fail(in, &ndst);

	socks_SendDst(in, &ndst);

	len = sizeof(struct sockaddr_in);
	if ((new = accept(out, (struct sockaddr *)&sin, &len)) < 0)
		socks_fail(in, &ndst);
	close(out);

	if ((dst->host != 0L) && (sin.sin_addr.s_addr != dst->host)) {
		ndst.cmd = SOCKS_FAIL;
		socks_SendDst(in, &ndst);
		exit(1);
	}

	ndst.port = sin.sin_port;
	ndst.host = sin.sin_addr.s_addr;
	socks_SendDst(in, &ndst);
	Pump(in, new);
}

/*
**  Now just pump the packets/character through..
*/
Pump(in, out)
int	in, out;
{
	static char		buf[4096];
	fd_set			fds;
	int			s, n, fdsbits;
	static struct timeval	tout = { SOCKS_TIMEOUT, 0 };
/* >>> Andy McFadden fadden@uts.amdahl.com */
 	struct linger ling;	/* for linger */
 	int length;		/* for linger */

	alarm(0);

 	/*
 	 * ATM: use SO_LINGER so it won't hang up on client
 	 */
 	ling.l_onoff = 1;   /* turn it on */
 	ling.l_linger = /*3*/  10;
 	length = sizeof(ling);
 	if (setsockopt(in,  SOL_SOCKET, SO_LINGER, &ling, length) < 0) ;
 	if (setsockopt(out, SOL_SOCKET, SO_LINGER, &ling, length) < 0) ;
/* <<< Andy McFadden fadden@uts.amdahl.com */

	FD_ZERO(&fds);
	if (in > out)
		fdsbits = in + 1;
	else
		fdsbits = out +1;

	while (1) {
		tout.tv_sec = SOCKS_TIMEOUT;
		tout.tv_usec = 0;
		FD_SET(in, &fds);
		FD_SET(out, &fds);
		if ((s = select(fdsbits, &fds, NULL,NULL, &tout)) > 0) {
			if (FD_ISSET(in, &fds)) {
				if ((n = read(in, buf, sizeof buf)) > 0) {
					if (write(out, buf, n) < 0) {
						goto bad;
					}
				} else {
					goto bad;
				}
			}
			if (FD_ISSET(out, &fds)) {
				if ((n = read(out, buf, sizeof buf)) > 0) {
					if (write(in, buf, n) < 0) {
						goto bad;
					}
				} else {
					goto bad;
				}
			}
		} else if ((s == 0) || ((s < 0) && (errno == EINTR))) {
				continue;
		} else {
			goto bad;
		}
	}

bad:
	;	/* Make the goto happy */
}


int socks_SendDst(s, dst)
int	s;
Socks_t	*dst;
{
	char c[sizeof(Socks_t)];
	char *p = c;
	int i = sizeof(Socks_t), n, ret;
	fd_set	fds;
	int	fdsbits = s + 1;
	struct	timeval timeout;

	c[0] = dst->version;
	c[1] = dst->cmd;
	bcopy(&dst->port, c+2, sizeof(dst->port));
	bcopy(&dst->host, c+2+sizeof(dst->port), sizeof(dst->host));

	while ( i > 0) {
		FD_ZERO(&fds);
		FD_SET(s, &fds);
		timeout.tv_sec = 15;
		timeout.tv_usec = 0;
		if ((ret = select(fdsbits, NULL, &fds, NULL, &timeout)) == 0)
			continue;
		if (ret < 0) {
			if (errno == EINTR)
				continue;
			else {
				return(-1);
			}
		}
		if((n = write(s, p, i)) > 0) {
			p += n;
			i -= n;
		} else if ((n < 0) && ((errno == EWOULDBLOCK) || (errno == EINTR)))
			continue;
		else {
			return (-2);
		}
	}
	return(0);
}

int socks_GetDst(s, dst)
int	s;
Socks_t	*dst;
{
	char	c[sizeof(Socks_t)];
	char	*p = c;
	int	i = sizeof(Socks_t), n, ret;
	fd_set	fds;
	int	fdsbits = s + 1;
	struct	timeval timeout;

	while ( i > 0) {
		FD_ZERO(&fds);
		FD_SET(s, &fds);
		timeout.tv_sec = 15;
		timeout.tv_usec = 0;
		if ((ret = select(fdsbits, &fds, NULL, NULL, &timeout)) == 0)
			continue;
		if (ret < 0) {
			if (errno == EINTR)
				continue;
			else {	
			return(-1);
			}
		}
		if((n = read(s, p, i)) > 0) {
			p += n;
			i -= n;
		} else if ((n < 0) && ((errno == EWOULDBLOCK) || (errno == EINTR)))
			continue;
		else {
			return(-2);
		}
	}

	dst->version = c[0];
	dst->cmd = c[1];
	bcopy(c+2, &dst->port, sizeof(dst->port));
	bcopy(c+2+sizeof(dst->port), &dst->host, sizeof(dst->host));
	return(0);
}

