/*
ptysh.c  "Pseudo Terminal Shell"  v2
"Using & Managing PPP," O'Reilly & Associates, March 1999
http://www.oreilly.com/


This example is for LINUX variants with the 2.0 series kernel.
It assumes BSD style pty device names in the form of /dev/ptyXX and
/dev/ttyXX.  The example is also compatible with LINUX 2.2 series
kernels with BSD style ptys enabled.  Compile with "gcc -o ptysh ptysh.c"


Author: Andrew Sun
Revision: January 17, 1996, January 10, 1998, June 1, 1998
Revision: September 11, 2000, Version 2 update,
          Link dynamically allocated pty to user specified symbolic link


Description:

This program enables the /bin/sh command interpreter to
emulate a "terminal device".  Tip, cu, the UUCP environment,
and PPP normally interact with serial interfaces via terminal
device files, such as /dev/ttyS0.  Rather than limiting these
applications to physical serial interfaces, "ptysh" enables them
to interact with the shell (/bin/sh), and with TCP/IP communication
programs, including telnet and rlogin.


Usage:

Once ptysh is active, ptysh maintains a user specified symbolic
link to a pseudo terminal.  Applications accessing the symbolic
link as a serial device will interact with /bin/sh.  At this point,
an application's chat script can "exec telnet," "exec rlogin,"
or invoke an alternate utility to establish communications over a network.


Security note:

This ptysh requires setuid root privileges to set ownership and
permissions of slave pty devices.
*/

#include <stdio.h>
#include <errno.h>

#include <fcntl.h>

#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>

#include <signal.h>

char *ptyalias;

void exitreq(int sig)
{
	unlink(ptyalias);
	exit(0);
}

main(int argc, char *argv[])
{

int shellpid;
uid_t uid;	/* Our real UID */

char iobuf12[2000], iobuf21[2000];
char *iobuf12p, *iobuf21p;
int iofdcnt, iowrcnt, iordcnt12, iordcnt21;

int fd1m, fd1s, fd2m, fd2s;
char mastername[12], slavename[12];
struct stat statbuf;

fd_set readfds, writefds;

void (*sigvold)();

seteuid( uid=getuid() );	/* Run unprivileged */

/* Command line options */
if (argc < 2 ) {
	fprintf(stderr, "Usage: %s /dev/ptyalias\n", argv[0]);
	exit(1);
}
ptyalias = argv[1];

/* Pty alias file must not already exist */
if ( lstat(ptyalias, &statbuf) >= 0) {
	fprintf(stderr, "File %s exists\n", ptyalias);
	exit(1);
}

/* Set signal handling */
sigvold =  signal(SIGINT, SIG_IGN);
if ( sigvold == SIG_DFL ) {
	signal(SIGINT, exitreq);
}
signal(SIGTERM, exitreq);
signal(SIGHUP, SIG_IGN);

for (;;) {

/* Create serial interface ptys */
if ( (fd1m = getpty(mastername)) < 0 ) {
	perror("open serial pty master"); exit(1);
}
	strcpy(slavename, mastername);
	slavename[5] = 't';
/*	printf("serial pty: %s\n", slavename); */
	seteuid(0);
if ( (fd1s = open(slavename, O_RDWR)) < 0 ) {
	perror("open serial pty slave"); exit(1);
}
	fchown(fd1s, uid, -1);
	fchmod(fd1s, S_IRUSR | S_IWUSR);
	seteuid(uid);

/* Symlink pty slave */
if (symlink(slavename, ptyalias) < 0 ) {
	perror("symlink"); exit(1);
}

/* Detect "open" for serial pty slave */
FD_ZERO (&readfds);
FD_ZERO (&writefds);
FD_SET (fd1m, &readfds);
/* FD_SET (fd1m, &writefds); */	/* Remove this if wait for open fails */
while ( select (64, &readfds , &writefds, (fd_set *) 0, NULL) < 1 )
	;
close (fd1s);	/* Application has serial pty slave now */

/* Create shell ptys */
if ( (fd2m = getpty(mastername)) < 0 ) {
	perror("open shell pty master"); exit(1);
}
	strcpy(slavename, mastername);
	slavename[5] = 't';
/*	printf("shell pty: %s\n", slavename); */

/* Invoke /bin/sh as a subprocess */
if (! (shellpid = fork()) ) {
	close (fd1m);
	close (fd2m);
	setsid();

	seteuid(0);
	if ( (fd2s = open(slavename, O_RDWR)) < 0 ) {
		perror("open shell pty slave"); exit(1);
	}
	fchown(fd2s, uid, -1);
	fchmod(fd2s, S_IRUSR | S_IWUSR);
	setuid(uid);

	dup2 (fd2s, 0);		/* stdin */
	dup2 (fd2s, 1);		/* stdout */
	dup2 (fd2s, 2);		/* stderr */
	system ("stty sane");
	execl ("/bin/sh", "sh", (char *) 0);
}

iordcnt12 = 0;	/* I/O read character count, fd1m to fd2m */
iordcnt21 = 0;	/* I/O read character count, fd2m to fd1m */

for (;;) {

FD_ZERO ( &readfds );
FD_ZERO ( &writefds );
if (iordcnt12) {
	FD_SET (fd2m, &writefds);
} else {
	FD_SET (fd1m, &readfds);
}
if (iordcnt21) {
	FD_SET (fd1m, &writefds);
} else {
	FD_SET (fd2m, &readfds);
}

iofdcnt = select (64, &readfds, &writefds, (fd_set *) 0, NULL);
if ( iofdcnt < 0 )
	if ( errno == EINTR ) continue;
	else {
		perror("select"); exit(1);
	}

if ( FD_ISSET (fd1m, &readfds) ) {

	/* Read from fd1m data to be written to fd2m */
	iobuf12p = iobuf12;
	iordcnt12 = read (fd1m, iobuf12, 2000);
	if ( iordcnt12 <= 0 ) break;
}
if ( FD_ISSET (fd1m, &writefds) ) {

	/* Write to fd1m with data read from fd2m */
	iowrcnt = write (fd1m, iobuf21p, iordcnt21);
	if ( iowrcnt <= 0 ) break;
	iordcnt21 -= iowrcnt;
	iobuf21p += iowrcnt;
}
if ( FD_ISSET (fd2m, &readfds) ) {

	/* Read from fd2m data to be written to fd1m */
	iobuf21p = iobuf21;
	iordcnt21 = read (fd2m, iobuf21, 2000);
	if ( iordcnt21 <= 0 ) break;
}
if ( FD_ISSET (fd2m, &writefds) ) {

	/* Write to fd2m with data read from fd1m */
	iowrcnt = write (fd2m, iobuf12p, iordcnt12);
	if ( iowrcnt <= 0 ) break;
	iordcnt12 -= iowrcnt;
	iobuf12p += iowrcnt;
}

}
close (fd1m);
close (fd2m);
if (unlink(ptyalias) < 0) {
	perror("unlink"); exit(1);
}
sleep(3);	/* Delay for pty close */
wait(NULL);
}

}

/* Search and allocate a master pty */
int getpty( char ptyname[] )
{
	char *p1, *p2, *p;
	struct stat statbuf;
	int fd;
	int i;

	strcpy( ptyname, "/dev/ptyXX" );
	p1 = &ptyname[8]; p2 = &ptyname[9];
	for( p = "pqrstuvwxyz"; *p; p++) {
		*p1 = *p; *p2 = '0';
		if ( stat(ptyname, &statbuf) < 0 ) { break; }
		for( i=0; i<16; i++ ) {
			*p2 = "0123456789abcdef"[i];
			fd = open( ptyname, O_RDWR );
			if ( fd >= 0 ) { return fd; }
		}
	}
	return -1;
}

