#ifndef LINT
static char sccsid[] =
"%W% Copyright 1992 Livingston Enterprises Inc";
#endif /* LINT */

/*
 *
 *	nettty - tty emulator
 *
 *	Livingston Enterprises, Inc.
 *	6920 Koll Center Parkway
 *	Pleasanton, CA   94566
 *
 *	Copyright 1992 Livingston Enterprises, Inc.
 *
 *	Permission to use, copy, modify, and distribute this software for any
 *	purpose and without fee is hereby granted, provided that this
 *	copyright and permission notice appear on all copies and supporting
 *	documentation, the name of Livingston Enterprises, Inc. not be used
 *	in advertising or publicity pertaining to distribution of the
 *	program without specific prior permission, and notice be given
 *	in supporting documentation that copying and distribution is by
 *	permission of Livingston Enterprises, Inc.   
 *
 *	Livingston Enterprises, Inc. makes no representations about
 *	the suitability of this software for any purpose.  It is
 *	provided "as is" without express or implied warranty.
 *
 *
 *	Nettty is a utility which allows many hosts on a network to share
 *	access to one or more serial ports on a network based Communications
 *	Server which supports these features.  The Livingston PortMaster
 *	productline contains full support for this feature starting with
 *	release 2.2 (dated Nov 11 1991) of the Communications Operating
 *	System.
 *
 *	This program runs as a daemon on host systems which require access
 *	to remote serial ports through UNIX tty devices.  The Resulting
 *	configuration will have the following characteristics:
 * 
 *     Host A            Network                   Communications Server
 * +---------------+                        +--------------------------------+
 * |tty <-> daemon |<---> TCP Socket <----->| port pool <-------> serial port|
 * |               |                     |  | management     |               |
 * |tty <-> daemon |<---> TCP Socket <---|  |                |               |
 * +---------------+                     |  |                |--> serial port|
 *                                       |  |                |               |
 *     Host B                            |  |                |               |
 * +---------------+                     |  |                +--> serial port|
 * |tty <-> daemon |<---> TCP Socket <---|  |                                |
 * |               |                     |  +--------------------------------+
 * +---------------+                     |
 *                                       |
 *     Host C                            |
 * +---------------+                     |
 * |tty <-> daemon |<---> TCP Socket <---+
 * |               |
 * +---------------+
 *	
 *
 *	This is acheived on the PortMaster by using the "netdata" device
 *	service.  Each port to be place in a given pool is set to use the
 *	netdata device service with a specific TCP port number (ie. 6000).
 *	The nettty daemon is started by root at boot time on the hosts with the
 *	following arguments:
 *
 *	-d device - Where device is the name of the desired pseudo tty
 *		    (ie. /dev/ttyr0).
 *	-s server - Where server is the name of the Communications Server
 *		    as defined on the local hosts database.
 *	-p port   - Where port is the TCP port number assigned on the
 *		    Communications Server to the "port pool".
 *	-w seconds -Where seconds specifies the frequency of port open
 *		    retries if no ports are available.  if the -w option
 *		    is not specified and no ports are available, the
 *		    application accessing the tty will receive an error
 *		    indication immediately after opening the tty device.
 *		    If -w is used, the application will block writing
 *		    to the device until a port is made available.
 */

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/dir.h>

#include <netinet/in.h>

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <netdb.h>
#include <strings.h>
#include <sys/ioctl.h>

#if defined(HPUX)
#include <termios.h>
#include <sys/bsdtty.h>
#include <sys/ptyio.h>
#define TIOCTCNTL	TIOCPKT
#endif

#if defined(RS6000) | defined(MIPS) | defined(ULTRIX) | defined(SYS_BSD)
#define TIOCTCNTL       TIOCPKT
#endif

#if defined(MIPS)
#define NO_SETSID	1
#endif /* MIPS */

extern	int errno;
extern	int sys_nerr;
extern	char *sys_errlist[];

void	sig_fatal();
void	sig_hup();

#if !defined(HPUX) & !defined(SUNOS)

#define O_NOCTTY	0

#endif /* HPUX & SUNOS */

#define NBSIZE		4096

char	tty_device[128];
char	pty_device[128];
char	comm_server[128];
int	server_port;
char	*progname;
u_long	server_addr;
int	ptyfd;
int	netfd;
char	holdbuf[NBSIZE];
char	to_ptybuf[2048];
char	to_netbuf[2048];
int	to_netbytes;
int	to_ptybytes;
int	wait_flag;
int	wait_seconds;


main(argc, argv)
int	argc;
char	**argv;
{
	int	cons;
	int	t;
	int	pid;
	u_long	get_ipaddr();

	/* Process command line arguments */
	*tty_device = '\0';
	*comm_server = '\0';
	server_port = 0;
	wait_flag = 0;

	progname = *argv;

	argc--;
	argv++;

	while(argc) {

		if(**argv != '-') {
			usage();
		}

		switch(*(*argv + 1)) {

		case 'd':
			argc--;
			argv++;
			if(argc == 0) {
				usage();
			}
			strcpy(tty_device, *argv);
			break;

		case 's':
			argc--;
			argv++;
			if(argc == 0) {
				usage();
			}
			strcpy(comm_server, *argv);
			break;

		case 'p':
			argc--;
			argv++;
			if(argc == 0) {
				usage();
			}
			server_port = atoi(*argv);
			break;
		
		case 'w':
			argc--;
			argv++;
			if(argc == 0) {
				usage();
			}
			wait_seconds = atoi(*argv);
			wait_flag = 1;
			break;
		
		default:
			usage();
			break;
		}
		argc--;
		argv++;
	}

	/* Validate arguments */
	if(strncmp(tty_device, "/dev/", 5) != 0 || *comm_server == '\0' ||
					server_port == 0) {
		usage();
	}

	if((server_addr = get_ipaddr(comm_server)) == (u_long)0) {
		fprintf(stderr, "%s: Couldn't resolve host address for %s\n",
				progname, comm_server);
		exit(-1);
	}

	/* Go get the tty device */
	if((ptyfd = getpty(tty_device)) < 0) {
		fprintf(stderr, "%s: Couldn't bind to device %s\n",
				progname, tty_device);
		exit(-1);
	}

	/*
	 *	Disconnect from session
	 */
	pid = fork();
	if(pid < 0) {
		fprintf(stderr, "%s: Couldn't fork\n", progname);
			exit(-1);
	}
	if(pid > 0) {
		exit(0);
	}

	/*
	 *	Disconnect from tty
	 */
#ifdef NO_SETSID
	setpgrp(0, 0);
	t = open("/dev/tty", 2);
	/*  disconnect from tty */
	if(t >= 0) {
		ioctl(t, TIOCNOTTY, (char *)NULL);
		close(t);
	}
	setpgrp(0, getpid());
#else /* NO_SETSID */
	setsid();
#endif /* NO_SETSID */

	for (t = getdtablesize(); t >= 3; t--) {
		if(t != ptyfd) {
			close(t);
		}
	}

	/*
	 * Open system console as stderr
	 */
	cons = open("/dev/console", O_WRONLY | O_NOCTTY);
	if(cons != 2) {
		dup2(cons, 2);
		close(cons);
	}

	signal(SIGHUP, sig_hup);
	signal(SIGINT, sig_fatal);
	signal(SIGQUIT, sig_fatal);
	signal(SIGILL, sig_fatal);
	signal(SIGTRAP, sig_fatal);
	signal(SIGIOT, sig_fatal);
	signal(SIGEMT, sig_fatal);
	signal(SIGFPE, sig_fatal);
	signal(SIGTERM, sig_fatal);

	/* okay - we are talking - wait for the user */
	run_it();
}

getpty(tty_device)
char	*tty_device;
{
	int	ptyfd;
	int	on = 1;

	if((ptyfd = get_device(tty_device))==-1) {
		return(-1);
	}
	/* Set modes */
	ioctl(ptyfd, TIOCPKT, &on);
#ifdef HPUX
	ioctl(ptyfd, TIOCMONITOR, &on);
#endif /* HPUX */
	return(ptyfd);
}

get_device(tty_device)
char	*tty_device;
{
	int	ptyfd;
	int	on = 1;
	char	*ptyname;
	char	*findpty();
	char	msg[80];

	if((ptyname = findpty(tty_device)) == (char *)NULL) {
		return(-1);
	}
	if((ptyfd = open(ptyname, O_RDWR | O_NOCTTY)) < 0) {
		sprintf(msg, "%s: get_device %s", progname, ptyname);
		perror(msg);
		return(-1);
	}

	ioctl(ptyfd, FIONBIO, &on);
	return(ptyfd);
}

run_it()
{
	fd_set	readfds;
	fd_set	writefds;
	fd_set	exepfds;
	char	err_msg[64];
	int	wsize;
	int	nwritten;
	int	nread;
	int	cmd;
	struct	timeval timeout;
	int	nds;
	int	status;
	int	on = 1;

	nds = getdtablesize();
	netfd = -1;
	to_netbytes = 0;

	for(;;) {

		if(ptyfd < 0) {
			if((ptyfd = getpty(tty_device)) < 0) {
				fprintf(stderr,
					  "%s: Couldn't re-bind to device %s\n",
					progname, tty_device);
				exit(-1);
			}
		}

		/* set select mask for network socket and pty's */
		FD_ZERO(&readfds);
		FD_ZERO(&writefds);
		FD_ZERO(&exepfds);
		if(to_ptybytes < 1024 && netfd >= 0) {
			FD_SET(netfd, &readfds);
		}
		if(to_netbytes != 0) {
			FD_SET(netfd, &writefds);
		}
		else {
			FD_SET(ptyfd, &readfds);
#ifdef HPUX
			FD_SET(ptyfd, &exepfds);
#endif /* HPUX */
		}
		if(to_ptybytes != 0) {
			FD_SET(ptyfd, &writefds);
		}

#ifdef MIPS
		timeout.tv_sec = 10;
		timeout.tv_usec = 0;
		status = select(nds, &readfds, &writefds, &exepfds, &timeout);
#else /* MIPS */
		status = select(nds, &readfds, &writefds, &exepfds, 0);
#endif /* MIPS */
		if(status == -1) {
			if (errno == EINTR)
				continue;
			if(errno == EBADF) {
				fprintf(stderr, "%s: EBADF ???\n", progname);
				fflush(stderr);
				continue;
			}
			sprintf(err_msg, "select failed error = %d\n", errno);
			fatal(netfd, err_msg);
		}
		if(status == 0) {
#ifdef MIPS
			/* Timeout */
			if(ioctl(ptyfd, FIONBIO, &on) != 0) {
				/* lost connection */
				close(ptyfd);
				ptyfd = -1;
				to_ptybytes = 0;
				if(netfd >= 0) {
					sleep(1);
					close(netfd);
				}
				netfd = -1;
				to_netbytes = 0;
			}
#else /* MIPS */

			/* shouldn't happen... */
			sleep(1);
			continue;
#endif /* MIPS */
		}

		/* okay - here is the tricky part, we need to handle the data */

		/* check for network write first */
		if(to_netbytes != 0 && netfd >= 0 &&
						(FD_ISSET(netfd, &writefds))) {
			/* network is available for writing */
			wsize = to_netbytes*2;
			do {
				wsize /= 2;
				nwritten = write(netfd, to_netbuf, wsize);
			} while (nwritten < 0 && errno==EWOULDBLOCK && wsize>1);

			if (nwritten > 0) {
				to_netbytes -= nwritten;
				if(to_netbytes != 0) {
					memshift(to_netbuf,
					    to_netbuf + nwritten, to_netbytes);
				}
			}
			else if(nwritten < 0) {
				if(errno == EHOSTUNREACH) {
					/*
					 * Since we didn't get an EWOULDBLOCK,
					 * the data actually did get written.
					 */
					to_netbytes -= wsize;
					if(to_netbytes != 0) {
						memshift(to_netbuf,
							to_netbuf + wsize,
							to_netbytes);
					}
				}
				else if(errno != EWOULDBLOCK) {
					/* lost connection */
					close(ptyfd);
					ptyfd = -1;
					to_ptybytes = 0;
					sleep(1);
					close(netfd);
					netfd = -1;
					to_netbytes = 0;
				}
			}
				
		}

		/* check for network read */
		if(netfd >= 0 && FD_ISSET(netfd, &readfds)) {
			net_read();
		}

		/* check for read from port */
#ifdef HPUX
		if(FD_ISSET(ptyfd, &exepfds)){
			/* Check for a trapped ioctl */
			if(ioctl(ptyfd, TIOCREQCHECK, holdbuf) != EINVAL) {
				/* Acknowlege it */
				ioctl(ptyfd, TIOCREQSET, holdbuf);
			}
		}
#endif /* HPUX */
		if(ptyfd >= 0 && to_netbytes < 1024 &&
						FD_ISSET(ptyfd, &readfds)){
			if(netfd < 0) {
				if((netfd = netopen(server_addr,
							server_port)) < 0) {
					/* Couldn't get a port */
					if(wait_flag == 1) {
						sleep(wait_seconds);
					}
					else {
						close(ptyfd);
						ptyfd = -1;
						to_ptybytes = 0;
						to_netbytes = 0;
					}
					continue;
				}
			}
			nread = read(ptyfd, holdbuf, 512 + 1);
			if(nread > 0) {
				cmd = holdbuf[0];
				nread--;
				send_net(&holdbuf[1], nread);
			}
			else if(nread < 0) {
				/* lost connection */
				close(ptyfd);
				ptyfd = -1;
				to_ptybytes = 0;
				if(netfd >= 0) {
					sleep(1);
					close(netfd);
				}
				netfd = -1;
				to_netbytes = 0;
			}
		}

		/* check for writes to port */
		if(to_ptybytes != 0 && (FD_ISSET(ptyfd, &writefds))) {
			wsize = to_ptybytes;
			do {
				nwritten = write(ptyfd,
						to_ptybuf, wsize);
				wsize /= 2;
			} while (nwritten < 0 && errno == EWOULDBLOCK && wsize);
			if (nwritten > 0) {
				to_ptybytes -= nwritten;
				if(to_ptybytes != 0) {
					memshift(to_ptybuf,
						to_ptybuf + nwritten,
						to_ptybytes);
				}
			}
		}
	}
}

memshift(target, source, length)
char	*target;
char	*source;
int	length;
{
	int	wsize;

	if(target == source) {
		return;
	}
	while(length) {
		if(length > NBSIZE) {
			wsize = NBSIZE;
		}
		else {
			wsize = length;
		}
		memcpy(holdbuf, source, wsize);
		memcpy(target, holdbuf, wsize);
		source += wsize;
		target += wsize;
		length -= wsize;
	}
}

net_read()
{
	int	nleft;

	/*
	 * Read as much data as we can queue.
	 */

	errno = 0;
	nleft = read(netfd, to_ptybuf + to_ptybytes,
					sizeof(to_ptybuf) - to_ptybytes);
	if(nleft <= 0) {
		if(errno == EWOULDBLOCK) {
			return;
		}
		/* lost connection */
		close(ptyfd);
		ptyfd = -1;
		to_ptybytes = 0;
		sleep(1);
		close(netfd);
		netfd = -1;
		to_netbytes = 0;
		return;
	}
	to_ptybytes += nleft;
	return;
}

send_net(buffer, length)
char	*buffer;
int	length;
{
	memcpy(to_netbuf + to_netbytes, buffer, length);
	to_netbytes += length;
}

netopen(ip_addr, port_addr)
u_long		ip_addr;
int		port_addr;
{
	struct sockaddr_in sin;
	int	s;
	u_short	local_port;
	u_long	nip_addr;

	/* Open a new socket */
	s = socket(AF_INET, SOCK_STREAM, 0);
	if(s < 0)
		return(-1);

	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = INADDR_ANY;
	local_port = 1025;
	do {
		local_port++;
		sin.sin_port = htons((u_short)local_port);
	} while((bind(s, (caddr_t)&sin, sizeof (sin)) < 0) &&
						local_port < 64000);
	if(local_port >= 64000) {
		close(s);
		return(-1);
	}

	/* Connect to the remote system */
	sin.sin_family = AF_INET;
	nip_addr = htonl(ip_addr);
	memcpy((caddr_t)&sin.sin_addr, &nip_addr, sizeof(u_long));
	sin.sin_port = htons((u_short)port_addr);

	if(connect(s, &sin, sizeof (sin)) < 0) {
		close(s);
		return(-1);
	}
	sleep(1);
	return(s);
}

/*************************************************************************
 *
 *	Function: get_ipaddr
 *
 *	Purpose: Return an IP address in host long notation from a host
 *		 name or address in dot notation.
 *
 *************************************************************************/

u_long
get_ipaddr(host)
char	*host;
{
	struct hostent *hp;
	static char	buffer[32];
	u_long		ipstr2long();

	if(good_ipaddr(host) == 0) {
		return(ipstr2long(host));
	}
	else if((hp = gethostbyname(host)) == (struct hostent *)NULL) {
		return((u_long)0);
	}
	return(*(u_long *)hp->h_addr);
}

/*************************************************************************
 *
 *	Function: good_ipaddr
 *
 *	Purpose: Check for valid IP address in standard dot notation.
 *
 *************************************************************************/

good_ipaddr(addr)
char	*addr;
{
	int	dot_count;
	int	digit_count;

	dot_count = 0;
	digit_count = 0;
	while(*addr != '\0' && *addr != ' ') {
		if(*addr == '.') {
			dot_count++;
			digit_count = 0;
		}
		else if(!isdigit(*addr)) {
			dot_count = 5;
		}
		else {
			digit_count++;
			if(digit_count > 3) {
				dot_count = 5;
			}
		}
		addr++;
	}
	if(dot_count != 3) {
		return(-1);
	}
	else {
		return(0);
	}
}

/*************************************************************************
 *
 *	Function: ipstr2long
 *
 *	Purpose: Return an IP address in host long notation from
 *		 one supplied in standard dot notation.
 *
 *************************************************************************/

u_long
ipstr2long(ip_str)
char	*ip_str;
{
	char	buf[6];
	char	*ptr;
	int	i;
	int	count;
	u_long	ipaddr;
	int	cur_byte;

	ipaddr = (u_long)0;
	for(i = 0;i < 4;i++) {
		ptr = buf;
		count = 0;
		*ptr = '\0';
		while(*ip_str != '.' && *ip_str != '\0' && count < 4) {
			if(!isdigit(*ip_str)) {
				return((u_long)0);
			}
			*ptr++ = *ip_str++;
			count++;
		}
		if(count >= 4 || count == 0) {
			return((u_long)0);
		}
		*ptr = '\0';
		cur_byte = atoi(buf);
		if(cur_byte < 0 || cur_byte > 255) {
			return((u_long)0);
		}
		ip_str++;
		ipaddr = ipaddr << 8 | (u_long)cur_byte;
	}
	return(ipaddr);
}

fatal(netfd, msg)
int	netfd;
char	*msg;
{
	fprintf(stderr, "nettty: fatal(%d): %s\n", errno, msg);
	fflush(stderr);
	shutdown(netfd, 2);
	exit(1);
}

void
sig_fatal(sig)
int	sig;
{
	fprintf(stderr, "nettty: exit on signal (%d)\n", sig);
	fflush(stderr);
	shutdown(netfd, 2);
	exit(1);
}

void
sig_hup(sig)
int	sig;
{
	fprintf(stderr, "nettty: caught signal SIGHUP\n");
	fflush(stderr);
}

usage()
{
	fprintf(stderr,
		"Usage: %s -d device -s server -p port [ -w seconds ]\n",
				progname);
	exit(-1);
}

#ifdef HPUX
getdtablesize()
{
	return(NFDBITS);
}
#endif

#if defined(RS6000)
#include <dirent.h>
#define direct dirent
#endif

#ifdef HPUX
#define MAJOR_TTY	17
#define MAJOR_PTY	16
#else /* HPUX */
#define MAJOR_TTY	20
#define MAJOR_PTY	21
#endif /* HPUX */

#ifdef SYS_BSD
#undef MAJOR_TTY
#undef MAJOR_PTY
#define MAJOR_TTY	5
#define MAJOR_PTY	6
#endif /* SYS_BSD */

static char	*dev2pty();

static char	cache_ttyname[128];
static char	cache_ptyname[128];
static char	find_pty_device[256];

char	*
findpty(ttyname)
char	*ttyname;
{
	struct stat	stb;
	dev_t		device_type;
	char		*ptyname;

	/*
	 * The device will come in using the form:
	 * 	"/dev/ttyp1"
	 * this needs to be converted to the "pty" format name
	 * which represents the other side of the psuedo device.
	 * If we don't find it using this simple conversion, then
	 * we will see if it is cached from the previous lookup for the
	 * port and finally we will look for the coresponding pty using
	 * readdir in /dev
	 */
	if(strncmp(ttyname, "/dev/tty", 8) == 0) {
		strcpy(find_pty_device, ttyname);
		find_pty_device[5] = 'p';
		if (stat(find_pty_device, &stb) == 0) {
			return(find_pty_device);
		}
	}

#if defined(RS6000)
	/* Try the RS6000 form of multiplexed ttys/ptys */
	if(strncmp(ttyname, "/dev/pts/", 9) == 0) {
		strcpy(find_pty_device, ttyname);
		find_pty_device[7] = 'c';
		if (stat(find_pty_device, &stb) == 0) {
			return(find_pty_device);
		}
	}
	return((char *)NULL);
#else

	/* Check the cache */
	if(strcmp(ttyname, cache_ttyname) == 0) {
		return(cache_ptyname);
	}

	/* Use the more involved process */
	if(stat(ttyname, &stb) < 0) {
		return((char *)NULL);
	}
	if(stb.st_mode & S_IFMT != S_IFCHR) {
		return((char *)NULL);
	}
	if(major(stb.st_rdev) != MAJOR_TTY) {
		return((char *)NULL);
	}
	device_type = makedev(MAJOR_PTY, minor(stb.st_rdev));

	/* Use readdir */
	ptyname = dev2pty(device_type);
	if(ptyname != (char *)NULL) {
		/* Add this entry to the cache */
		strcpy(cache_ptyname, ptyname);
		strcpy(cache_ttyname, ttyname);
	}
	return(ptyname);
#endif
}

static char	*
dev2pty(devno)
dev_t	devno;
{
	register struct direct	*dp;
	DIR			*dirp;
	struct stat		stb;
	char			tmpname[256];

	if ((dirp = opendir("/dev")) == NULL) {
		return((char *)NULL);
	}
	errno = 0;
	if ((dp = readdir(dirp)) == NULL) {
		closedir(dirp);
		return((char *)NULL);
	}
	for( ; dp != NULL; dp = readdir(dirp)) {
		strncpy(tmpname, dp->d_name, dp->d_namlen);
		tmpname[dp->d_namlen] = '\0';
		sprintf(find_pty_device, "/dev/%s", tmpname);
		if(stat(find_pty_device, &stb) < 0) {
			continue;
		}
		if(stb.st_mode & S_IFMT != S_IFCHR) {
			continue;
		}
		if(stb.st_rdev == devno) {
			closedir(dirp);
			return(find_pty_device);
		}
	}
	closedir(dirp);
	return((char *)NULL);
}
