sm redux

John F Haugh II jfh at rpp386.cactus.org
Fri Dec 28 06:56:56 AEST 1990


This is the same thing I posted last week, except that I cleaned it
up and added a few features to make it more like "shell layers".
I was quite surprised upon returning from Christmas vacation to
discover a mailbox full of letter regarding this thing.  I had
originally written it as a quick hack - obviously quite a few of
you found it useful.

You may now "toggle" between two sessions or "delete" a session
by number.  It doesn't do "named" layers like shl.  Maybe later.
It does allow you to put commands in "$HOME/.smrc".  You don't
have to give the full command name anymore either.  Abbreviations
work just fine.

I now run it set-UID root here.  That way my PTY has my name
attached to it and my TTY permissions.  I've not gone over every
little hole, but if you find any little security holes, I'd love
to know about them.  The system() call was replaced to avoid the
IFS bugs, and setuid(getuid()) is called before exec'ing anything.
You have to be able to open the PTY master, and those are
exclusive access devices, so being root doesn't matter there.
The PTY slave isn't touched until the PTY master is, so that's
OK as well.  What have I forgotten?

If there is still more interest I will add support for /etc/utmp
and friends.  [ Actually I'll probably add /etc/utmp support, a
Makefile, a manpage, labeled sessions and ship it off to a
moderated source group regardless of interest. ]
---- cut here ----
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	sm.c
# This archive created: Thu Dec 27 13:45:52 1990
# By:	John F Haugh II (River Parishes Programming, Austin TX)
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'sm.c'" '(14792 characters)'
if test -f 'sm.c'
then
	echo shar: "will not over-write existing file 'sm.c'"
else
sed 's/^X//' << \SHAR_EOF > 'sm.c'
X/*
X * This code is in the public domain.  THIS CODE IS PROVIDED ON AN AS-IS
X * BASIS.  THE USER ACCEPTS ALL RISKS ASSOCIATED WITH USING THIS CODE AND
X * IS SOLELY RESPONSIBLE FOR CORRECTING ANY SOFTWARE ERRORS OR DAMAGE
X * CAUSED BY SOFTWARE ERRORS OR MISUSE.
X *
X * Written By: John F Haugh II, 12/21/90
X *
X * Modified to include suggestions made by Pat Myrto (pat at rwing.UUCP)
X * and Dan Bernstein.
X */
X
X#include <sys/types.h>
X#include <sys/termio.h>
X#include <sys/stat.h>
X#include <string.h>
X#include <stdio.h>
X#include <signal.h>
X#include <time.h>
X#include <fcntl.h>
X#include <errno.h>
X
X/*
X * MAXSESSIONS is the number of sessions which a single user can
X * manage with this program at a single time.  16 is plenty.  4 or
X * 5 might be a better idea if pty's are a scarce resource.
X */
X
X#define	MAXSESSIONS	16
X
Xint	childpids[MAXSESSIONS];		/* Process ID of each session leader */
Xint	writepid;			/* Process ID of PTY writing process */
Xint	pspid;				/* Obfuscation is my life            */
Xint	masters[MAXSESSIONS];		/* File descriptor for PTY master    */
Xint	nsessions;			/* High-water mark for session count */
Xint	current = -1;			/* Currently active session          */
Xint	last = -1;			/* Previously active session         */
Xint	caught = 0;			/* Some signal was caught            */
X
Xstruct	termio	sanetty;		/* Initial TTY modes on entry        */
Xstruct	termio	rawtty;			/* Modes used when session is active */
X
Xvoid	exit ();
Xvoid	_exit ();
Xchar	*getlogin ();
Xchar	*getenv ();
Xstruct	passwd	*getpwnam ();
Xextern	char	**environ;
X
X/*
X * parse - see if "s" and "pat" smell alike
X */
X
Xchar *
Xparse (s, pat)
Xchar	*s;
Xchar	*pat;
X{
X	int	match = 0;
X	int	star = 0;
X
X	/*
X	 * Match all of the characters which are identical.  The '*'
X	 * character is used to denote the end of the unique suffix
X	 * for a pattern.  Everything after that is optional, but
X	 * must be matched exactly if given.
X	 */
X
X	while (*s && *pat) {
X		if (*s == *pat && *s) {
X			s++, pat++;
X			continue;
X		}
X		if (*pat == '*') {
X			star++;
X			pat++;
X			continue;
X		}
X		if ((*s == ' ' || *s == '\t') && star)
X			return s;
X		else
X			return 0;
X	}
X
X	/*
X	 * The pattern has been used up - see if whitespace
X	 * follows, or if the input string is also finished.
X	 */
X
X	if (! *pat && (*s == '\0' || *s == ' ' || *s == '\t'))
X		return s;
X
X	/*
X	 * The input string has been used up.  The unique
X	 * prefix must have been matched.
X	 */
X
X	if (! *s && (star || *pat == '*'))
X		return s;
X
X	return 0;
X}
X
X/*
X * murder - reap a single child process
X */
X
Xvoid
Xmurder (sig)
Xint	sig;
X{
X	int	pid;
X	int	i;
X
X	pid = wait ((int *) 0);
X
X	/*
X	 * See what children have died recently.
X	 */
X
X	for (i = 0;pid != -1 && i < nsessions;i++) {
X
X		/*
X		 * Close their master sides and mark the
X		 * session as "available".
X		 */
X
X		if (pid == childpids[i]) {
X			childpids[i] = -1;
X			close (masters[i]);
X			masters[i] = -1;
X			break;
X		}
X	}
X	if (writepid != -1 && pid == writepid)
X		writepid = -1;
X
X	if (pspid != -1 && pid == pspid)
X		pspid = -1;
X
X	signal (sig, murder);
X}
X
X/*
X * catch - catch a signal and set a flag
X */
X
Xvoid
Xcatch (sig)
Xint	sig;
X{
X	caught = 1;
X	signal (sig, catch);
X}
X
X/*
X * reader - read characters from the pty and write to the screen
X */
X
Xint
Xreader (fd)
Xint	fd;
X{
X	char	c;
X	int	cnt;
X
X	/*
X	 * Ignore the SIGINT and SIGQUIT signals.
X	 */
X
X	signal (SIGINT, SIG_IGN);
X	signal (SIGQUIT, SIG_IGN);
X
X	while (1) {
X		if ((cnt = read (fd, &c, 1)) == -1) {
X			if (errno != EINTR)
X				return -1;
X
X			if (caught)
X				return 0;
X			else
X				continue;
X		}
X		if (cnt == 0)
X			return -1;
X
X		write (1, &c, 1);
X	}
X}
X
X/*
X * writer - write characters read from the keyboard down the pty
X */
X
Xwriter (fd)
Xint	fd;
X{
X	char	c;
X	int	cnt;
X	int	zflg = 0;
X
X	signal (SIGINT, SIG_IGN);
X	signal (SIGQUIT, SIG_IGN);
X	signal (SIGHUP, _exit);
X
X	/*
X	 * Read characters until an error is returned or ^Z is seen
X	 * followed by a non-^Z character.
X	 */
X
X	while (1) {
X		errno = 0;
X		if ((cnt = read (0, &c, 1)) == 0)
X			continue;
X
X		/*
X		 * Some signal may have occured, so retry
X		 * the read.
X		 */
X
X		if (cnt == -1) {
X			if (errno == EINTR && caught)
X				continue;
X			else
X				exit (0);
X		}
X
X		/*
X		 * Process a ^Z.  If one was not seen earlier, 
X		 * set a flag and go read another character.
X		 */
X
X		if (c == ('z' & 037)) {
X			if (! zflg++)
X				continue;
X		}
X		
X		/*
X		 * See if a ^Z was seen before.  If so, signal
X		 * the master and exit.
X		 */
X
X		else if (zflg) {
X			kill (getppid (), SIGUSR1);
X			exit (0);
X		}
X
X		/*
X		 * Just output the character as is.
X		 */
X
X		zflg = 0;
X		if (write (fd, &c, 1) != 1)
X			break;
X	}
X	exit (0);
X}
X
X/*
X * usage - command line syntax
X */
X
Xusage ()
X{
X	fprintf (stderr, "usage: sm\n");
X	exit (1);
X}
X
X/*
X * help - built-in command syntax
X */
X
Xhelp ()
X{
X	fprintf (stderr, "Valid commands are:\n");
X	fprintf (stderr, "\tconnect [ # ]\t(connects to session)\n");
X	fprintf (stderr, "\tcreate\t\t(sets up a new pty session)\n");
X	fprintf (stderr, "\tcurrent\t\t(shows current session number)\n");
X	fprintf (stderr, "\tdelete #\t(deletes session)\n");
X	fprintf (stderr, "\thelp\t\tdisplay this message\n");
X	fprintf (stderr, "\tjobs\t\t(shows a ps listing of current session)\n");
X	fprintf (stderr, "\tquit\tor exit (terminate session manager)\n");
X	fprintf (stderr, "\tset #\t\t(# is 0-15 - selets current session)\n");
X	fprintf (stderr, "\ttoggle\t\t(switch to previous session)\n\n");
X	fprintf (stderr, "Commands may be abbreviated to a unique prefix\n\n");
X	fprintf (stderr, "Note - to exit a session back into sm so one can\n");
X	fprintf (stderr, " select another session, type a ^Z and a return\n");
X	fprintf (stderr, " To send ^Z to the shell, type two ^Z chars\n\n");
X}
X
X/*
X * session - create a new session on a pty
X */
X
Xvoid
Xsession ()
X{
X	char	mastername[BUFSIZ];
X	char	slavename[BUFSIZ];
X	char	*digits = "0123456789abcdef";
X	char	*letters = "pqrs";
X	char	*shell;
X	char	*arg;
X	int	oumask;
X	int	i;
X	int	pty;
X	int	ptys = 64;
X	struct	stat	sb;
X
X	/*
X	 * Find the number of the new session.  An error will be
X	 * given if no sessions are available.
X	 */
X
X	for (i = 0;i < nsessions && masters[i] != -1;i++)
X		;
X
X	if (i == MAXSESSIONS) {
X		printf ("out of sessions\n");
X		return;
X	}
X	if (i == nsessions)
X		nsessions++;
X
X	/*
X	 * Save the previous sesssion number.  This is so the
X	 * "toggle" command will work after a "create".
X	 */
X
X	if (current != -1)
X		last = current;
X
X	current = i;
X
X	/*
X	 * Go find the master side of a PTY to use.  Masters are
X	 * found by trying to open them.  Each PTY master is an
X	 * exclusive access device.  If every pty is tried but no
X	 * available ones are found, scream.
X	 */
X
X	for (pty = 0;pty < ptys;pty++) {
X		sprintf (mastername, "/dev/pty%c%c",
X			letters[pty >> 4], digits[pty & 0xf]);
X		if ((masters[i] = open (mastername, O_RDWR)) != -1)
X			break;
X	}
X	if (masters[i] == -1) {
X		printf ("out of ptys\n");
X		return;
X	}
X
X	/*
X	 * Let's make a child process.
X	 */
X
X	switch (childpids[i] = fork ()) {
X		case -1:
X			printf ("out of processes\n");
X			exit (1);
X		case 0:
X
X			/*
X			 * Disassociate from the parent process group
X			 * and tty's.
X			 */
X
X			close (0);
X			close (1);
X			for (i = 0;i < nsessions;i++)
X				close (masters[i]);
X
X			setpgrp ();
X
X			/*
X			 * Reset any signals that have been upset.
X			 */
X
X			signal (SIGINT, SIG_DFL);
X			signal (SIGQUIT, SIG_DFL);
X			signal (SIGCLD, SIG_DFL);
X			signal (SIGHUP, SIG_DFL);
X			signal (SIGUSR1, SIG_DFL);
X
X			/*
X			 * Make up the name of the slave side of the
X			 * PTY and open it.  It will be opened as stdin.
X			 */
X
X			sprintf (slavename, "/dev/tty%c%c",
X				letters[pty >> 4], digits[pty & 0xf]);
X
X			if (open (slavename, O_RDWR) == -1) {
X				fprintf (stderr, "can't open %s\n", slavename);
X				_exit (-1);
X			}
X
X			/*
X			 * Try to change the owner of the master and slave
X			 * side of the PTY.  This will only work if the
X			 * invoker has an effective UID of 0.  Change the
X			 * mode of the slave to be the same as the parent
X			 * tty.
X			 */
X
X			(void) chown (mastername, getuid (), getgid ());
X			(void) chown (slavename, getuid (), getgid ());
X			if (fstat (2, &sb) == 0)
X				(void) chmod (slavename, sb.st_mode & 0777);
X
X			/*
X			 * Close the last open file descriptor and make
X			 * the new stdout and stderr descriptors.  Copy
X			 * the tty modes from the parent tty to the slave
X			 * pty.
X			 */
X
X			close (2);
X			dup (0);
X			dup (0);
X			ioctl (0, TCSETAF, &sanetty);
X
X			/*
X			 * See if the invoker has a shell in their
X			 * environment and use the default value if
X			 * not.
X			 */
X
X			if (! (shell = getenv ("SHELL")))
X				shell = "/bin/sh";
X
X			/*
X			 * Undo any set-UID or set-GID bits on the
X			 * executable.
X			 */
X
X			setgid (getgid ());
X			setuid (getuid ());
X
X			/*
X			 * Start off the new session.
X			 */
X
X			if (arg = strrchr (shell, '/'))
X				arg++;
X			else
X				arg = shell;
X
X			execl (shell, arg, 0);
X			_exit (-1);
X	}
X}
X
X/*
X * quit - kill all active sessions
X */
X
Xquit (sig)
Xint	sig;
X{
X	int	i;
X
X	for (i = 0;i < nsessions;i++) {
X		if (masters[i] != -1) {
X			close (masters[i]);
X			masters[i] = -1;
X			kill (- childpids[i], SIGHUP);
X		}
X	}
X	exit (sig);
X}
X
X/*
X * sm - manage pty sessions
X */
X
Xmain (argc, argv)
Xint	argc;
Xchar	**argv;
X{
X	char	buf[BUFSIZ];
X	char	*cp;
X	int	i;
X	int	pid;
X	FILE	*fp;
X
X	/*
X	 * No arguments are allowed on the command line
X	 */
X
X	if (argc > 1)
X		usage ();
X
X	/*
X	 * Set up all the file descriptors and process IDs
X	 */
X
X	for (i = 0;i < MAXSESSIONS;i++) {
X		childpids[i] = -1;
X		masters[i] = -1;
X	}
X
X	/*
X	 * Get the current tty settings, and make a copy that can
X	 * be tinkered with.  The sane values are used while getting
X	 * commands.  The raw values are used while sessions are
X	 * active.  New sessions are set to have the same tty values
X	 * as the sane values.
X	 */
X
X	ioctl (0, TCGETA, &sanetty);
X	rawtty = sanetty;
X
X	rawtty.c_oflag &= ~OPOST;
X	rawtty.c_lflag = 0;
X	rawtty.c_cc[VMIN] = 1;
X	rawtty.c_cc[VTIME] = 1;
X
X	/*
X	 * SIGCLG is caught to detect when a session has died or when
X	 * the writer for the session has exited.  SIGUSR1 is used to
X	 * signal that a ^Z has been seen.
X	 */
X
X	signal (SIGCLD, murder);
X	signal (SIGUSR1, catch);
X
X	/*
X	 * The file $HOME/.smrc is read for initializing commands.
X	 */
X
X	if (cp = getenv ("HOME")) {
X		sprintf (buf, "%s/.smrc", cp);
X		if (access (buf, 04) != 0 || ! (fp = fopen (buf, "r")))
X			fp = stdin;
X	}
X
X	/*
X	 * This is the main loop.  A line is read and executed.  If
X	 * EOF is read, the loop is exited (except if input is the
X	 * .smrc file).
X	 */
X
X	while (1) {
X
X		/*
X		 * Keyboard signals cause an exit.
X		 */
X
X		signal (SIGINT, quit);
X		signal (SIGQUIT, quit);
X
X		/*
X		 * Prompt for input only when it is not coming from
X		 * the .smrc file.  A single line will be read and
X		 * executed.  The read is retried if an interrupt
X		 * has been seen.
X		 */
X
X		if (fp == stdin) {
X			printf ("pty-> ");
X			fflush (stdout);
X		}
X		while (errno = 0, fgets (buf, sizeof buf, fp) == 0) {
X			if (errno == EINTR) {
X				continue;
X			} else if (fp != stdin) {
X				fclose (fp);
X				fp = stdin;
X				buf[0] = '\0';
X				break;
X			} else {
X				strcpy (buf, "quit");
X				break;
X			}
X		}
X		if (cp = strchr (buf, '\n'))
X			*cp = '\0';
X
X		if (! buf[0])
X			continue;
X
X		/*
X		 * Parse the command.  Each command consists of a
X		 * verb, with some commands accepting a session ID
X		 * to act on.  The command will be accepted if a
X		 * unique prefix of the command is entered.
X		 */
X
X		if (parse (buf, "q*uit") || parse (buf, "e*xit")) {
X
X			/*
X			 * Just give up.
X			 */
X
X			quit (0);
X		} else if (parse (buf, "cr*eate")) {
X
X			/*
X			 * Create a new session and make it current
X			 */
X
X			session ();
X			continue;
X		} else if (parse (buf, "cu*rrent")) {
X
X			/*
X			 * Give the session ID of the current session.
X			 */
X
X			if (current != -1)
X				printf ("current session is %d\n", current);
X			else
X				printf ("no current session\n");
X
X			continue;
X		} else if (cp = parse (buf, "s*et")) {
X
X			/*
X			 * Set the current session ID to #
X			 */
X
X			if (*cp) {
X				i = strtol (cp, &cp, 10);
X				if (*cp == '\0') {
X					last = current;
X					current = i;
X					continue;
X				}
X			}
X			printf ("eh?\n");
X			continue;
X		} else if (parse (buf, "a*ctive")) {
X
X			/*
X			 * List the session IDs of all active sessions
X			 */
X
X			for (i = 0;i < nsessions;i++)
X				if (masters[i] != -1)
X					printf ("%d ", i);
X
X			putchar ('\n');
X			continue;
X		} else if (parse (buf, "j*obs")) {
X			int	pids = 0;
X
X			/*
X			 * Give a "ps" listing of all active sessions
X			 */
X
X			buf[0] = '\0';
X			for (i = 0;i < nsessions;i++) {
X				if (childpids[i] != -1) {
X					if (pids++)
X						strcat (buf, ",");
X
X					sprintf (buf + strlen (buf), "%d",
X						childpids[i]);
X				}
X			}
X			if (pids) {
X				if (! (pspid = fork ())) {
X					setgid (getgid ());
X					setuid (getuid ());
X					execl ("/bin/ps", "ps", "-fp", buf, 0);
X					_exit (1);
X				}
X				while (pspid != -1)
X					pause ();
X			} else {
X				printf ("no jobs\n");
X			}
X			continue;
X		} else if (cp = parse (buf, "co*nnect")) {
X
X			/*
X			 * Connect to the current or named session.
X			 */
X
X			if (*cp) {
X				i = strtol (cp, &cp, 10);
X				if (*cp == '\0') {
X					last = current;
X					current = i;
X					/* FALLTHROUGH */
X				} else {
X					printf ("eh?\n");
X					continue;
X				}
X			}
X		} else if (parse (buf, "t*oggle")) {
X
X			/*
X			 * Toggle between the previous and current session
X			 */
X
X			i = current;
X			current = last;
X			last = i;
X			/* FALLTHROUGH */
X		} else if (cp = parse (buf, "d*elete")) {
X
X			/*
X			 * Delete the named session
X			 */
X
X			if (*cp) {
X				i = strtol (cp, &cp, 10);
X				if (*cp == '\0' && i >= 0 && i < MAXSESSIONS) {
X					if (masters[i] != -1) {
X						close (masters[i]);
X						masters[i] = -1;
X						kill (- childpids[i], SIGHUP);
X						continue;
X					}
X				}
X			}
X			printf ("eh?\n");
X			continue;
X		} else {
X
X			/*
X			 * The command was not recognized
X			 */
X
X			help ();
X			continue;
X		}
X
X		/*
X		 * Validate the session number.  It must be in the
X		 * range 0 .. (MAXSESSIONS-1) to be valid.  The current
X		 * session must also be associated with an open PTY.
X		 */
X
X		if (current < 0 || current >= MAXSESSIONS)
X			current = -1;
X
X		if (current == -1 || masters[current] == -1) {
X			printf ("no current session\n");
X			current = -1;
X			continue;
X		}
X
X		/*
X		 * Let's make a process to read from the child ...
X		 */
X
X		switch (writepid = fork ()) {
X			case -1:
X				kill (childpids[current], SIGKILL);
X				perror ("fork");
X				break;
X			case 0:
X				writer (masters[current]);
X				exit (1);
X		}
X
X		/*
X		 * Set up the raw TTY modes and start writing to
X		 * the child.
X		 */
X
X		ioctl (0, TCSETAF, &rawtty);
X
X		if (reader (masters[current]) == -1) {
X			close (masters[current]);
X			masters[current] = -1;
X			childpids[current] = -1;
X			current = -1;
X			if (writepid > 0)
X				kill (writepid, SIGTERM);
X		}
X
X		/*
X		 * Reset the tty modes to resume the command loop.
X		 */
X
X		ioctl (0, TCSETA, &sanetty);
X	}
X	exit (0);
X}
SHAR_EOF
if test 14792 -ne "`wc -c < 'sm.c'`"
then
	echo shar: "error transmitting 'sm.c'" '(should have been 14792 characters)'
fi
fi
exit 0
#	End of shell archive
-- 
John F. Haugh II                             UUCP: ...!cs.utexas.edu!rpp386!jfh
Ma Bell: (512) 832-8832                           Domain: jfh at rpp386.cactus.org
"While you are here, your wives and girlfriends are dating handsome American
 movie and TV stars. Stars like Tom Selleck, Bruce Willis, and Bart Simpson."



More information about the Alt.sources mailing list