/* xroute.c, by Kartik Subbarao <subbarao@concorde.fc.hp.com> */

# include <stdio.h>
# include <sys/types.h>
# include <sys/time.h>
# include <netdb.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <sys/socket.h>
# include <string.h>
# include <sys/wait.h>
#ifdef sgi
# define _BSD_SIGNALS
#endif
# include <signal.h>
# include <errno.h>

# define XPORT 6000

char buf[8192];

usage()
{
	fprintf(stderr,"usage: xroute [-n numclients] [-d gdisp] host[:display]\n");
	exit(1);
}

#ifdef hpux
waiter() {int st;while (wait3(&st, WNOHANG, NULL) > 0);signal(SIGCHLD, waiter);}
#else
waiter() { union wait st; while (wait3(&st, WNOHANG, NULL) > 0); }
#endif

extern char *getenv();

main(argc, argv)
int argc; char *argv[];
{
	int o; extern char *optarg; extern int optind; char *lhost, *p;
	int ldisp, gdisp = -1; /* display numbers */
	int ls, gs, rs; /* sockets -- local, gateway, remote */
	struct sockaddr_in rad; int radlen = sizeof rad;
	int numcli = 1, pid;
	
	while ((o = getopt(argc, argv, "n:d:")) != -1)
		switch (o) {	
		case 'n': if (optarg) numcli = atoi(optarg); break;
		case 'd': if (optarg) gdisp = atoi(optarg); break;
		default: usage();
		}
	
	if (!(lhost = argv[optind]))
		if (!(lhost = getenv("DISPLAY"))) usage();

	ldisp = (p = strchr(lhost, ':')) && p[1] ? atoi(p+1) : 0;
	if (p) *p = '\0';

	gs = tcpssocket(gdisp);
	signal(SIGCHLD, waiter);
	signal(SIGHUP, SIG_IGN);

	if (fork()) exit(0); /* background ourself */

	for (; numcli; numcli--) {
		while ((rs = accept(gs, &rad, &radlen)) == -1 && errno == EINTR);
		if (rs == -1) exit(1);
		switch (fork()) {
		case -1: exit(1);
		case 0: 
			close(gs); 
			ls = tcpcsocket(lhost, XPORT + ldisp); route(ls, rs); 
			break;
		default: close(rs);
		}
	}
	exit(0);
}

route(ls, rs)
int ls, rs;
{
	fd_set rdset, trdset, wrset, twrset;
	int maxfds, n, tmp; char *b;
	
	maxfds = (ls > rs ? ls : rs) + 1;
	FD_ZERO(&rdset); FD_ZERO(&trdset); FD_ZERO(&wrset); FD_ZERO(&twrset);
	FD_SET(ls, &rdset); FD_SET(ls, &wrset);
	FD_SET(rs, &rdset); FD_SET(rs, &wrset);
	trdset = rdset; twrset = wrset;
	
	for (;; rdset = trdset, wrset = twrset) {
		if (select(maxfds, &rdset, NULL, NULL, NULL) == -1) exit(1);
		if (select(maxfds, NULL, &wrset, NULL, NULL) == -1) exit(1);

		if (FD_ISSET(ls, &rdset) && FD_ISSET(rs, &wrset)) {
			if ((n = read(ls, buf, sizeof buf)) <= 0) exit(n);
			for (b = buf; n; n -= tmp, b += tmp)
				if ((tmp = write(rs, b, n)) <= 0) exit(tmp);
		}
		if (FD_ISSET(rs, &rdset) && FD_ISSET(ls, &wrset)) {
			if ((n = read(rs, buf, sizeof buf)) <= 0) exit(n);
			for (b = buf; n; n -= tmp, b += tmp)
				if ((tmp = write(ls, b, n)) <= 0) exit(tmp);
		}
	}
}

tcpssocket(gdisp)
int gdisp;
{
	int i, s; struct sockaddr_in sa;
   	 
    if ((s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
		perror("socket"), exit(1);

   	sa.sin_family = AF_INET; sa.sin_addr.s_addr = htonl(INADDR_ANY);

	if (gdisp == -1) /* If it wasn't specified */
		for (i = XPORT; ; i++) { /* find the first available port > XPORT */
			sa.sin_port = htons(i); 
			if (bind(s, &sa, sizeof(sa)) == 0) {
				printf("%d\n", i-XPORT); break;
			}
		}
	else {
		sa.sin_port = htons(XPORT+gdisp); 
		if (bind(s, &sa, sizeof(sa)) == -1) perror("bind"), exit(1);
	}

    if (listen(s, 5) == -1) perror("listen"), exit(1);

	return s;
}

tcpcsocket(host, port)
char *host; int port;
{
	int s, e;
	struct sockaddr_in sa;
	struct hostent *hent;
	extern char *sys_errlist[];

	if ((s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) exit(1);

	if (! (hent = gethostbyname(host))) {
		e = errno;
		if ((sa.sin_addr.s_addr = inet_addr(host)) == -1)
			fprintf(stderr, "%s: %s\n", host, sys_errlist[e]), exit(1);
	}
   	else bcopy(hent->h_addr, (char *) &sa.sin_addr, hent->h_length);

	sa.sin_family = AF_INET; sa.sin_port = htons(port);

	if (connect(s, &sa, sizeof(sa)) == -1)
		fprintf(stderr, "%s: connect: %s\n", host, sys_errlist[errno]), exit(1);

	return s;
}
