#!mkcmd
# $Mkcmd(*): ${mkcmd-mkcmd} %f
#
from '<sys/types.h>'
from '<sys/time.h>'
from '<sys/socket.h>'
from '<sys/ioctl.h>'
from '<netinet/in.h>'
from '<fcntl.h>'
from '<signal.h>'
from '<stdio.h>'
from '<netdb.h>'
from '<pwd.h>'
from '<errno.h>'
from '<ctype.h>'
from '<pwd.h>'
from '<unistd.h>'

require "std_help.m" "std_version.m" "std_noargs.m"

from '"cons.h"'
from '"machine.h"'

getenv "CONSOLE"
basename "console" ""
usage "Usage"


# buffers
string[32] variable "acMe" {
	before "FindUser(acMe);"
	help "holds the login name of the client user"
}

char* variable "pcCmd" {
	static
	help "internal command string decoded from options"
	after 'if ((char *)0 == %n) {%n = "attach";}'
}

boolean variable "fLocal" {
	static init "0"
}

boolean variable "fReplay" {
	static init "0"
}

char* variable "pcTo" {
	param "machine"
	init '"*"'
	help "connect to the slaved console of this machine"
}
string[256] variable "acMyName" {
	help "what we call ourselves"
}
string[1024] variable "acPorts" {
	local
	after '(void)sprintf(%n, "@%%s", pcInMaster);'
}
string variable "acLocalhost" {
	init '"localhost"'
	help "the loopback device"
}


init "RunTime();"

# options
function 'e' {
	named "ParseEsc"
	param "attn"
	update '%n(%N);'
	help "negotiate a different attention character sequence"
}

char* 'M' {
	named "pcInMaster"
	param "master"
	init 'HOST'
	help "master console server host for this cluster"
}

boolean 'r' {
	named "fRaw"
	user 'if ((char *)0 == pcCmd) {pcCmd = "spy";}'
	help "connection to host should be raw"
}

boolean 'v' {
	named "fVerbose"
	help "be more verbose"
}


exclude "asfduqY"
action 'aA' {
	once
	update 'pcCmd = "attach";'
	user 'fReplay = isupper(%w);'
	help "attach to the named machine (with replay)"
}

action 'fF' {
	once
	update 'pcCmd = "force";'
	user 'fReplay = isupper(%w);'
	help "force connection to the named machine (with replay)"
}
action 'sS' {
	once
	update 'pcCmd = "spy";'
	user 'fReplay = isupper(%w);'
	help "spy connection to the named machine (with replay)"
}

left "pcTo" {
}

action 'Y' {
	exclude "reM"
	update "%rMn = acLocalhost;"
	from '<stdio.h>'
	list {
		param ""
		update "if (argc - %K<getopt_switches>1v > 0) {fprintf(stderr, \"%%s: too many arguments (%%s%%s)\\n\", %b, argv[%K<getopt_switches>1v], argc - %K<getopt_switches>1v > 1 ? \"...\" : \"\");exit(1);}"
		abort 'exit(Gather(CtlSignal, acPorts, pcInMaster, "*", "pid", acMe));'
	}
	help "send a restart signal to the local console server"
}

%c
/* replace a very long ?: expession with something we can read		(ksb)
 */
static char *
ActMap(cKey)
char cKey;
{
	switch (cKey) {
	default:
		break;
	case 'D':
	case 'd':
		return "version";
	case 'x':
	case 'X':
		return "xamine";
	case 'P':
	case 'p':
		return "pid";
	case 'U':
	case 'u':
		return "users";
	case 'W':
	case 'w':
		return "groups";
	}
	fprintf(stderr, "%s: unknown key `%c'\n", progname, cKey);
	exit(1);
}
%%

action 'dDpP' {
	update 'pcCmd = ActMap(%w);'
	user 'fLocal = isupper(%w);'
	help "poll the (primary) console server for version information"
	exclude "re"
	exit {
		update ""
		named "Gather"
		aborts "exit(%n(fLocal ? Ctl : CtlMaster, acPorts, %rMn, pcTo, pcCmd, acMe));"
	}
}

augment action "pP" {
	help "poll the (primary) console server its process id"
}

action 'uUwWxX' {
	update 'pcCmd = ActMap(%w);'
	user 'fLocal = isupper(%w);'
	help "poll the (primary) console groups for console access lists"
	exclude "re"
	exit {
		update ""
		named "Gather"
		aborts "exit(%n(fLocal ? CmdGroup : CmdMaster, acPorts, %rMn, pcTo, pcCmd, acMe));"
	}
}

augment action 'wW' {
	help "poll the (primary) console groups for connected clients lists"
}

augment action 'xX' {
	help "poll the (primary) console groups for console baud rates"
}

action 'qQ' {
	track
	exclude "re"
	update 'pcCmd = "quit";'
	user 'fLocal = isupper(%w);'
	help "send a quit request to the (primary) console server"
	string["CONSOLE_WIRE_PASS+1"] variable "acPass" {
		local
		help "hold the console server password for quit"
	}
	after 'if (%U) {(void)strcpy(acPass, cons_getpass("Enter password:"));}'
	exit {
		update ""
		named "Gather"
		aborts "exit(%n(fLocal ? Ctl : CtlMaster, acPorts, %rMn, acPass, pcCmd, acMe));"
	}
}

augment action 'V' {
	user "Version();"
}

exit {
	update ""
	named "Gather"
	aborts "if (0 == %n(Indir, acPorts, %rMn, pcTo, pcCmd, acMe)) {exit(0);}(void)sprintf(acPorts, \"@%%s\", pcTo);exit(%n(Indir, acPorts, %rMn, pcTo, pcCmd, acMe));"
}


%c
/*
 * Copyright (c) 1990 The Ohio State University.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that: (1) source distributions retain this entire copyright
 * notice and comment, and (2) distributions including binaries display
 * the following acknowledgement:  ``This product includes software
 * developed by The Ohio State University and its contributors''
 * in the documentation or other materials provided with the distribution
 * and in all advertising materials mentioning features or use of this
 * software. Neither the name of the University nor the names of its
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */
%%

%i
#if USE_STRINGS
#include <strings.h>
#else
#include <string.h>
#endif

#if USE_TERMIOS
#include <termios.h>

#else
#if USE_TERMIO
#include <termio.h>

#else   /* use ioctl stuff */
#include <sgtty.h>
#include <sys/ioctl.h>
#endif
#endif

#if HAVE_MALLOC_H
#include <malloc.h>
#else
#if NEED_MALLOC_EXTERN
extern char *calloc(), *realloc();
#endif
#endif

#if HAVE_STDLIB_H
#include <stdlib.h>
#else
extern char *getenv();
#endif

extern int errno;

#if !HAVE_STRERROR
extern char *sys_errlist[];
#define strerror(Me) (sys_errlist[Me])
#endif

#if HAVE_SELECT_H
#include <sys/select.h>
#endif

static char rcsid[] =
	"$Id: console.m,v 8.6 2007/10/22 19:12:51 ksb Exp $";
int chAttn = -1, chEsc = -1;

/* we don't know what type of server we connect to, but pass		(ksb)
 * a whole pass phrase if we can
 */
#if !defined(CONSOLE_WIRE_PASS)
#define CONSOLE_WIRE_PASS	256
#endif
#if USE_GETPASSPHRASE
#define cons_getpass	getpassphrase
#else
#define cons_getpass	getpass
#endif

static int Indir();
%%

%c
/* panic -- we have no more momory
 */
static void
OutOfMem()
{
	static char acNoMem[] = ": out of memory\n";
	
	write(2, progname, strlen(progname));
	write(2, acNoMem, sizeof(acNoMem)-1);
	exit(1);
}

/*
 * remove from "host1" those domains common to "host1" and "host2"
 */
static char *
whittle(host1, host2)
char *host1, *host2;
{
	char *p1, *p2;

	p1 = strchr(host1, '.');
	p2 = strchr(host2, '.');
	while (p1 != (char*)0 && p2 != (char*)0) {
		if (strcmp(p1+1, p2+1) == 0) {
			*p1 = '\000';
			break;
		}
		p1 = strchr(p1+1, '.');
		p2 = strchr(p2+1, '.');
	}
	return host1;
}

static char
	acMesg[8192+2],		/* the buffer for startup negotiation	*/
	acThisHost[256];	/* what the remote host would call us	*/
static struct sockaddr_in
	local_port;		/* the looback address, if local use it	*/

/* output a control (or plain) character as a UNIX user would expect it	(ksb)
 */
static void
putCtlc(c, fp)
int c;
FILE *fp;
{
	if (0 != (0200 & c)) {
		(void)putc('M', fp);
		(void)putc('-', fp);
		c &= ~0200;
	}
	if (isprint(c)) {
		(void)putc(c, fp);
		return;
	}
	(void)putc('^', fp);
	if (c == 0177) {
		(void)putc('?', fp);
		return;
	}
	(void)putc(c+0100, fp);
}

#if defined(SERVICE)
static struct servent *pSE;
static char acService[] = SERVICE;
#endif

/* expain who we are and which revision we are				(ksb)
 */
static void
Version()
{
	register unsigned char *puc;

	printf("%s: initial master server `%s\'\n", progname, pcInMaster);
	printf("%s: default escape sequence `", progname);
	putCtlc(DEFATTN, stdout);
	putCtlc(DEFESC, stdout);
	printf("\'\n");
	puc = (unsigned char *)&local_port.sin_addr;
	printf("%s: loopback address for %s is %d.%d.%d.%d\n", progname, acMyName, puc[0], puc[1], puc[2], puc[3]);
#if defined(SERVICE)
	if ((struct servent *)0 == (pSE = getservbyname(acService, "tcp"))) {
		fprintf(stderr, "%s: getservbyname: %s: %s\n", progname, acService, strerror(errno));
		return;
	}
	printf("%s: service name `%s\'", progname, pSE->s_name);
	if (0 != strcmp(pSE->s_name, acService)) {
		printf(" (which we call `%s\')", acService);
	}
	printf(" on port %d\n", ntohs((u_short)pSE->s_port));
#else
#if defined(PORT)
	printf("%s: on port %d\n", progname, PORT);
#else
	printf("%s: no service or port compiled in\n", progname);
	exit(1);
#endif
#endif
}


/* convert text to control chars, we take `cat -v' style		(ksb)
 *	^X (or ^x)		contro-x
 *	M-x			x plus 8th bit
 *	c			a plain character
 */
static int
ParseChar(ppcSrc, pcOut)
char **ppcSrc, *pcOut;
{
	register int cvt, n;
	register char *pcScan = *ppcSrc;

	if ('M' == pcScan[0] && '-' == pcScan[1] && '\000' != pcScan[2]) {
		cvt = 0x80;
		pcScan += 2;
	} else {
		cvt = 0;
	}

	if ('\000' == *pcScan) {
		return 1;
	}

	if ('^' == (n = *pcScan++)) {
		if ('\000' == (n = *pcScan++)) {
			return 1;
		}
		if (islower(n)) {
			n = toupper(n);
		}
		if ('@' <= n &&  n <= '_') {
			cvt |= n - '@';
		} else if ('?' == *pcScan) {
			cvt |= '\177';
		} else {
			return 1;
		}
	} else {
		cvt |= n;
	}
	
	if ((char *)0 != pcOut) {
		*pcOut = cvt;
	}
	*ppcSrc = pcScan;
	return 0;
}

/* find the two characters that makeup the users escape sequence	(ksb)
 */
static void
ParseEsc(pcText)
char *pcText;
{
	auto char *pcTemp;
	auto char c1, c2;

	pcTemp = pcText;
	if (ParseChar(&pcTemp, &c1) || ParseChar(&pcTemp, &c2)) {
		fprintf(stderr, "%s: poorly formed escape sequence `%s\'\n", progname, pcText);
		exit(3);
	}
	if ('\000' != *pcTemp) {
		fprintf(stderr, "%s: too many characters in new escape sequence at ...`%s\'\n", progname, pcTemp);
		exit(3);
	}
	chAttn = c1;
	chEsc = c2;
}


/* set the port for socket connection					(ksb)
 * return the fd for the new connection; if we can use the loopback, do
 * as a side effect we set ThisHost to a short name for this host
 */
int
GetPort(pcToHost, pPort, sPort)
char *pcToHost;
struct sockaddr_in *pPort;
short sPort;
{
	register int s;
	register struct hostent *hp;

#if USE_STRINGS
	(void)bzero((char *)pPort, sizeof(*pPort));
#else
	memset((void *)pPort, '\000', sizeof(*pPort));
#endif
	if (0 == strcmp(pcToHost, strcpy(acThisHost, acMyName))) {
		(void)strcpy(pcToHost, acLocalhost);
#if USE_STRINGS
		(void)bcopy((char *)&local_port.sin_addr, (char *)&pPort->sin_addr, sizeof(local_port.sin_addr));
#else
		memcpy((char *)&pPort->sin_addr, (char *)&local_port.sin_addr, sizeof(local_port.sin_addr));
#endif
	} else if ((struct hostent *)0 != (hp = gethostbyname(pcToHost))) {
#if USE_STRINGS
		(void)bcopy((char *)hp->h_addr, (char *)&pPort->sin_addr, hp->h_length);
#else
		memcpy((char *)&pPort->sin_addr, (char *)hp->h_addr, hp->h_length);
#endif
	} else {
		fprintf(stderr, "%s: gethostbyname: %s: %s\n", progname, pcToHost, hstrerror(h_errno));
		return -1;
	}
	pPort->sin_port = sPort;
	pPort->sin_family = AF_INET;

	/* make hostname short, if we are calling ourself, chop at first dot
	 */
	if (0 == strcmp(pcToHost, acLocalhost)) {
		register char *pcChop;
		if ((char *)0 != (pcChop = strchr(acThisHost, '.'))) {
			*pcChop = '\000';
		}
	} else {
		(void)whittle(acThisHost, pcToHost);
	}

	/* set up the socket to talk to the server for all consoles
	 * (it will tell us who to talk to to get a real connection)
	 */
	if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		fprintf(stderr, "%s: socket: %s\n", progname, strerror(errno));
		return -1;
	}
	if (connect(s, (struct sockaddr *)pPort, sizeof(*pPort)) < 0) {
		fprintf(stderr, "%s: connect: %d@%s: %s\n", progname, ntohs(pPort->sin_port), pcToHost, strerror(errno));
		close(s); 
		return -1;
	}

	return s;
}


/* the next two routines assure that the users tty is in the
 * correct mode for us to do our thing
 */
static int screwy = 0;
#if USE_TERMIOS
static struct termios o_tios;
#else
#if USE_TERMIO
static struct termio o_tio;
#else
static struct sgttyb o_sty;
static struct tchars o_tchars;
static struct ltchars o_ltchars;
#endif
#endif


/*
 * show characters that are already tty processed,
 * and read characters before cononical processing
 * we really use cbreak at PUCC because we need even parity...
 */
static void
c2raw()
{
#if USE_TERMIOS
	auto struct termios n_tios;
#else
#if USE_TERMIO
	auto struct termio n_tio;
#else
	auto struct sgttyb n_sty;
	auto struct tchars n_tchars;
	auto struct ltchars n_ltchars;
#endif
#endif

	if (!isatty(0) || 0 != screwy)
		return;

#if USE_TERMIOS
	if (0 != ioctl(0, TCGETS, & o_tios)) {
		fprintf(stderr, "%s: iotcl: getsw: %s\n", progname, strerror(errno));
		exit(10);
	}
	n_tios = o_tios;
	n_tios.c_iflag &= ~(INLCR|IGNCR|ICRNL|IUCLC|IXON);
	n_tios.c_oflag &= ~OPOST;
	n_tios.c_lflag &= ~(ICANON|ISIG|ECHO);
	n_tios.c_cc[VMIN] = 1;
	n_tios.c_cc[VTIME] = 0;
	if (0 != ioctl(0, TCSETS, & n_tios)) {
		fprintf(stderr, "%s: getarrt: %s\n", progname, strerror(errno));
		exit(10);
	}
#else
#if USE_TERMIO
	if (0 != ioctl(0, TCGETA, & o_tio)) {
		fprintf(stderr, "%s: iotcl: geta: %s\n", progname, strerror(errno));
		exit(10);
	}
	n_tio = o_tio;	
	n_tio.c_iflag &= ~(INLCR|IGNCR|ICRNL|IUCLC|IXON);
	n_tio.c_oflag &= ~OPOST;
	n_tio.c_lflag &= ~(ICANON|ISIG|ECHO|ECHOE|ECHOK|ECHONL);
	n_tio.c_cc[VMIN] = 1;
	n_tio.c_cc[VTIME] = 0;
	if (0 != ioctl(0, TCSETAF, & n_tio)) {
		fprintf(stderr, "%s: iotcl: seta: %s\n", progname, strerror(errno));
		exit(10);
	}
#else
	if (0 != ioctl(0, TIOCGETP, (char *)&o_sty)) {
		fprintf(stderr, "%s: iotcl: getp: %s\n", progname, strerror(errno));
		exit(10);
	}
	n_sty = o_sty;

	n_sty.sg_flags |= CBREAK;
	n_sty.sg_flags &= ~(CRMOD|ECHO);
	n_sty.sg_kill = -1;
	n_sty.sg_erase = -1;
	if (0 != ioctl(0, TIOCSETP, (char *)&n_sty)) {
		fprintf(stderr, "%s: iotcl: setp: %s\n", progname, strerror(errno));
		exit(10);
	}

	/* stty undef all tty chars
	 */
	if (-1 == ioctl(0, TIOCGETC, (char *)&n_tchars)) {
		fprintf(stderr, "%s: ioctl: getc: %s\n", progname, strerror(errno));
		return;
	}
	o_tchars = n_tchars;
	n_tchars.t_intrc = -1;
	n_tchars.t_quitc = -1;
	if (-1 == ioctl(0, TIOCSETC, (char *)&n_tchars)) {
		fprintf(stderr, "%s: ioctl: setc: %s\n", progname, strerror(errno));
		return;
	}
	if (-1 == ioctl(0, TIOCGLTC, (char *)&n_ltchars)) {
		fprintf(stderr, "%s: ioctl: gltc: %s\n", progname, strerror(errno));
		return;
	}
	o_ltchars = n_ltchars;
	n_ltchars.t_suspc = -1;
	n_ltchars.t_dsuspc = -1;
	n_ltchars.t_flushc = -1;
	n_ltchars.t_lnextc = -1;
	if (-1 == ioctl(0, TIOCSLTC, (char *)&n_ltchars)) {
		fprintf(stderr, "%s: ioctl: sltc: %s\n", progname, strerror(errno));
		return;
	}
#endif
#endif
	screwy = 1;
}

/*
 * put the tty back as it was, however that was
 */
static void
c2cooked()
{
	if (!screwy)
		return;
#if USE_TERMIOS
	(void)ioctl(0, TCSETS, (char *)&o_tios);
#else
#if USE_TERMIO
	(void)ioctl(0, TCSETA, (char *)&o_tio);
#else
	(void)ioctl(0, TIOCSETP, (char *)&o_sty);
	(void)ioctl(0, TIOCSETC, (char *)&o_tchars);
	(void)ioctl(0, TIOCSLTC, (char *)&o_ltchars);
#endif
#endif
	screwy = 0;
}



/* send out some data along the connection				(ksb)
 */
static void
SendOut(fd, pcBuf, iLen)
int fd, iLen;
char *pcBuf;
{
	register int nr;

	while (0 != iLen) {
		if (-1 == (nr = write(fd, pcBuf, iLen))) {
			c2cooked();
			fprintf(stderr, "%s: lost connection\n", progname);
			exit(3);
		}
		iLen -= nr;
		pcBuf += nr;
	}
}

/* read a reply from the console server					(ksb)
 * if pcWnat == (char *)0 we strip \r\n from the end and return strlen
 */
static int
ReadReply(fd, pcBuf, iLen, pcWant)
int fd, iLen;
char *pcBuf, *pcWant;
{
	register int nr, j, iKeep;
	
	iKeep = iLen;
	for (j = 0; j < iLen; /* j+=nr */) {
		switch (nr = read(fd, &pcBuf[j], iLen-1)) {
		case 0:
			if (iKeep != iLen) {
				break;
			}
			/* fall through */
		case -1:
			c2cooked();
			fprintf(stderr, "%s: lost connection\n", progname);
			exit(3);
		default:
			j += nr;
			iLen -= nr;
			if ('\n' == pcBuf[j-1]) {
				pcBuf[j] = '\000';
				break;
			}
			if (0 == iLen) {
				c2cooked();
				fprintf(stderr, "%s: reply too long\n", progname);
				exit(3);
			}
			continue;
		}
		break;
	}
	/* in this case the called wants a line of text
	 * remove the cr/lf sequence and any trtailing spaces
	 * (s/[ \t\r\n]*$//)
	 */
	if ((char *)0 == pcWant) {
		while (0 != j && isspace(pcBuf[j-1])) {
			pcBuf[--j] = '\000';
		}
		return j;
	}
	return strcmp(pcBuf, pcWant);
}

#if DO_POWER
/* we are on a control port, shift to power controller			(ksb)
 * and send commands to it, then hang up to get back on the console
 */
static int
PowerCtl(s, pcKey, pcController, pcMaster)
int s;
char *pcKey, *pcController, *pcMaster;
{
	register int nc;
	register char **ppc;
	auto fd_set rmask, rinit;
	static char *apcHelp[] = {
		" help",
		" .  quit power mode",
		" 0  power down the unit",
		" 1  power up the unit",
		" #  describe power status of unit",
		" ?  output this help message",
		" w  which key and controller line infomation",
		" (other commands might be accepted by some units)",
		(char *)0
	};

	(void)sprintf(acMesg, "%c%c;", DEFATTN, DEFESC);
	SendOut(s, acMesg, 3);
	if (0 != ReadReply(s, acMesg, sizeof(acMesg), "[login:\r\n") && 0 != strcmp(acMesg, "\r\n[login:\r\n")) {
		CSTROUT(1," login ");
		SendOut(1, acMesg, strlen(acMesg));
		return 1;
	}

	(void)sprintf(acMesg, "%s\n", pcKey);
	SendOut(s, acMesg, strlen(acMesg));
	if (0 != ReadReply(s, acMesg, sizeof(acMesg), "host:\r\n")) {
		CSTROUT(1, " ");
		SendOut(1, pcKey, strlen(pcKey));
		CSTROUT(1, " ");
		SendOut(1, acMesg, strlen(acMesg));
		return 2;
	}

	/* which host we want, and a passwd if asked for one
	 */
	(void)sprintf(acMesg, "%s\n", pcController);
	SendOut(s, acMesg, strlen(acMesg));
	(void)ReadReply(s, acMesg, sizeof(acMesg), (char *)0);
	if (0 == strcmp(acMesg, "passwd:")) {
		auto char acCtlPass[CONSOLE_WIRE_PASS+1];
		c2cooked();
		(void)strcpy(acCtlPass, cons_getpass(" need password:"));
		c2raw();
		(void)sprintf(acMesg, "%s\n", acCtlPass);
		SendOut(s, acMesg, strlen(acMesg));
		(void)ReadReply(s, acMesg, sizeof(acMesg), (char *)0);
	}

	/* how did we do, did we get a read-only or read-write?
	 */
	if (0 != strcmp(acMesg, "attached]")) {
		register char *pcClose;
		if ((char *)0 != (pcClose = strchr(acMesg, ']')))
			*pcClose = '\000';
		CSTROUT(1, " aborted ");
		write(1, acMesg, strlen(acMesg));
		return 3;
	}

	FD_ZERO(& rinit);
	FD_SET(s, &rinit);
	FD_SET(0, &rinit);
	/* reset read mask and select on it
	 * ZZZ set a timeout on power line attach of 10 minutes? -- ksb
	 */
	while (rmask = rinit, -1 != select(sizeof(rmask)*8, &rmask, (fd_set *)0, (fd_set *)0, (struct timeval *)0)) {
		/* anything from socket? */
		if (FD_ISSET(s, &rmask)) {
			if ((nc = read(s, acMesg, sizeof(acMesg))) == 0) {
				break;
			}
#if STRIP8
			/* clear parity? */
			for (i = 0; i < nc; ++i)
				acMesg[i] &= 127;
#endif
			SendOut(1, acMesg, nc);
		}
		/* anything from stdin? */
		if (!FD_ISSET(0, &rmask)) {
			continue;
		}
		if ((nc = read(0, acMesg, 1)) == 0) {
			break;
		}
		if ('q' == acMesg[0] || 'Q' == acMesg[0] || '.' == acMesg[0]) {
			break;
		}

		/* must be a controller command, send any of:
		 * \n	  status of all lines? [not on the POW-R-Switch]
		 * key #  power status
		 * key 1  power on
		 * key 0  power off
		 * key _  [where _ is something else] unknown pass through
		 */
		switch (acMesg[0]) {
		case '?':	/* meta help */
			for (ppc = apcHelp; (char *)0 != *ppc; ++ppc) {
				(void)write(1, *ppc, strlen(*ppc));
				(void)write(1, "\r\n", 2);
			}
			/* sprintf(acMesg, "?\n");  and break? XXX */
			continue;
		case 'w':	/* which line */
			sprintf(acMesg, " %s line %s on %s", pcKey, pcController, pcMaster);
			(void)write(1, acMesg, strlen(acMesg));
			continue;
		case '\r':	/* power might want newlines passed */
		case '\n':
			sprintf(acMesg, "\n");
			break;
		case '#':
			sprintf(acMesg, "%s#\n", pcKey);
			break;
		case '0':
		case '-':
		case 'd':
			sprintf(acMesg, "%s0\n", pcKey);
			break;
		case '1':
		case '+':
		case 'u':
			sprintf(acMesg, "%s1\n", pcKey);
			break;
		default:	/* try anything else as a command */
			sprintf(acMesg, "%s%c\n", pcKey, acMesg[0]);
			break;
		}
		/* The power controller might not say anything if the
		 * key is wrong, show the User that we sent something -- ksb
		 */
		CSTROUT(1, " ");
		SendOut(s, acMesg, strlen(acMesg));
	}
	close(s);
	CSTROUT(1, " done");
	return 0;
}
#endif

/* call a machine master for group master ports and machine master ports
 * take a list like "1782@localhost:mentor.cc.purdue.edu:@pop.stat.purdue.edu"
 */
static int
Gather(pfi, pcPorts, pcMaster, pcTo, pcCmd, pcWho)
int (*pfi)();
char *pcPorts, *pcMaster, *pcTo, *pcCmd, *pcWho;
{
	register int s;
	register short j;
	register char *pcNext, *pcServer;
	auto char acExcg[256];
	auto struct sockaddr_in client_port;
	auto int iRet = 0;

	for (/* param */; '\000' != *pcPorts; pcPorts = pcNext) {
		if ((char *)0 == (pcNext = strchr(pcPorts, ':')))
			pcNext = "";
		else
			*pcNext++ = '\000';

		if ((char *)0 != (pcServer = strchr(pcPorts, '@'))) {
			*pcServer++ = '\000';
			if ('\000' != *pcServer) {
				(void)strcpy(acExcg, pcServer);
			}
		} else if (isdigit(pcPorts[0])) {
			(void)strcpy(acExcg, pcMaster);
		} else {
			(void)strcpy(acExcg, pcPorts);
			pcPorts = "";
		}

		if ('\000' == *pcPorts) {
#if defined(SERVICE)
			/* in net order -- ksb */
			j = pSE->s_port;
#else
#if defined(PORT)
			j = htons(PORT);
#else
			fprintf(stderr, "%s: no port or service compiled in?\n", progname);
			exit(8);
#endif
#endif
		} else if (!isdigit(pcPorts[0])) {
			/* XXX we should look up the service here */
			fprintf(stderr, "%s: %s: %s\n", progname, pcMaster, pcPorts);
			iRet += 32;
			continue;
		} else {
			j = htons((short)atoi(pcPorts));
		}

		if (-1 == (s = GetPort(acExcg, & client_port, j))) {
			iRet += 8;
		} else {
			if (0 != ReadReply(s, acMesg, sizeof(acMesg), "ok\r\n")) {
				fprintf(stderr, "%s: %s: %s", progname, acExcg, acMesg);
				iRet += 16;
			} else {
				iRet += (*pfi)(s, acExcg, pcTo, pcCmd, pcWho);
			}
			(void)close(s);
		}
		if ((char *)0 != pcServer) {
			*pcServer = '@';
		}
	}
	return iRet;
}


static int SawUrg = 0;
#if DO_POWER
static char acPowCtl[128];

/* We take a moment to call the power controller and ask		(ksb)
 * them about the console line we were chatting with.  We'll be right
 * back after this little diversion.
 * For your enjoyment: note we can power a power controller as well.
 */
int
Power(pcControl, pcLine, pcCurrent)
char *pcControl, *pcLine, *pcCurrent;
{
	register char *pcAt;

	if ((char *)0 != (pcAt = strchr(pcControl, '@'))) {
		*pcAt++ = '\000';
	} else {
		pcAt = pcCurrent;
	}
	return Gather(Indir, pcAt, pcAt, pcControl, acPowCtl, pcLine);
}
#endif /* power controller */

/* when the conserver program gets the suspend sequence it will send us
 * an out of band command to suspend ourself.  We just tell the reader
 * routine we saw one
 */
SIGRET_T
oob(sig)
int sig;
{
	++SawUrg;
}


/* interact with a group server					(ksb)
 */
static int
CallUp(s, pcMaster, pcMach, pcHow, pcUser)
int s;
char *pcMaster, *pcMach, *pcHow, *pcUser;
{
	register int nc;
	register int fIn;
	auto fd_set rmask, rinit;
	extern int atoi();
#if DO_POWER
	auto char acCtlr[4096-128];
#endif

	if (fVerbose) {
		printf("%s: %s to %s (%son %s)\n", progname, pcHow, pcMach, fRaw ? "raw " : "", pcMaster);
	}
#if defined(F_SETOWN)
	if (-1 == fcntl(s, F_SETOWN, getpid())) {
		fprintf(stderr, "%s: fcntl: %d: %s\n", progname, s, strerror(errno));
	}
#else
#if defined(SIOCSPGRP)
	{
	auto int iTemp;
	/* on the HP-UX systems it is different
	 */
	iTemp = -getpid();
	if (-1 == ioctl(s, SIOCSPGRP, & iTemp)) {
		fprintf(stderr, "%s: ioctl: %d: %s\n", progname, s, strerror(errno));
	}
	}
#endif
#endif
#if defined(SIGURG)
	(void)signal(SIGURG, oob);
#endif

#if DO_POWER
	if (acPowCtl == pcHow) {
		return PowerCtl(s, acPowCtl, pcMach, pcMaster);
	}
#endif

	/* change escape sequence (if set on the command line)
	 * and replay the log for the user, if asked
	 */
	if (chAttn == -1 || chEsc == -1) {
		chAttn = DEFATTN;
		chEsc = DEFESC;
	} else {
		/* tell the conserver to change escape sequences, assmue OK
		 * (we'll find out soon enough)
		 */
		(void)sprintf(acMesg, "%c%ce%c%c", DEFATTN, DEFESC, chAttn, chEsc);
		SendOut(s, acMesg, 5);
		if (0 == ReadReply(s, acMesg, sizeof(acMesg), (char *)0)) {
			fprintf(stderr, "protocol botch on redef of escape sequence\n");
			exit(8);
		}
	}
	if (fVerbose) {
		printf("Enter `");
		putCtlc(chAttn, stdout);
		putCtlc(chEsc, stdout);
		printf("?\' for help.\n");
	}


	/* if we are going for a particular console
	 * send sign-on stuff, then wait for some indication of what mode
	 * we got from the server (if we are the only people on we get write
	 * access by default, which is fine for most people).
	 */
	if (!fRaw) {
		/* begin connect with who we are
		 */
		(void)sprintf(acMesg, "%c%c;", chAttn, chEsc);
		SendOut(s, acMesg, 3);
		if (0 != ReadReply(s, acMesg, sizeof(acMesg), "[login:\r\n") && 0 != strcmp(acMesg, "\r\n[login:\r\n")) {
			fprintf(stderr, "%s: call: %s\n", progname, acMesg);
			exit(2);
		}

		(void)sprintf(acMesg, "%s@%s\n", pcUser, acThisHost);
		SendOut(s, acMesg, strlen(acMesg));
		if (0 != ReadReply(s, acMesg, sizeof(acMesg), "host:\r\n")) {
			fprintf(stderr, "%s: %s\n", progname, acMesg);
			exit(2);
		}

		/* which host we want, and a passwd if asked for one
		 */
		(void)sprintf(acMesg, "%s\n", pcMach);
		SendOut(s, acMesg, strlen(acMesg));
		(void)ReadReply(s, acMesg, sizeof(acMesg), (char *)0);
		if (0 == strcmp(acMesg, "passwd:")) {
			auto char acHostPass[CONSOLE_WIRE_PASS+1];
			(void)strcpy(acHostPass, cons_getpass("Enter password:"));
			(void)sprintf(acMesg, "%s\n", acHostPass);
			SendOut(s, acMesg, strlen(acMesg));
			(void)ReadReply(s, acMesg, sizeof(acMesg), (char *)0);
		}

		/* how did we do, did we get a read-only or read-write?
		 */
		if (0 == strcmp(acMesg, "attached]")) {
			/* OK -- we are good as gold */
			fIn = 'a';
		} else if (0 == strcmp(acMesg, "spy]") || 0 == strcmp(acMesg, "ok]")) {
			/* Humph, someone else is on
			 * or we have an old version of the server (4.X)
			 */
			fIn = 's';
		} else if (0 == strcmp(acMesg, "host is read-only]")) {
			fIn = 'r';
		} else if (0 == strcmp(acMesg, "line to host is down]")) {
			/* ouch, the machine is down on the server */
			fIn = '-';
			fprintf(stderr, "%s: %s is down\n", progname, pcMach);
			if (fVerbose) {
				printf("[use `");
				putCtlc(chAttn, stdout);
				putCtlc(chEsc, stdout);
				printf("o\' to open console line]\n");
			}
		} else if (0 == strcmp(acMesg, "no -- on ctl]")) {
			fIn = '-';
			fprintf(stderr, "%s: %s is a control port\n", progname, pcMach);
			if (fVerbose) {
				printf("[use `");
				putCtlc(chAttn, stdout);
				putCtlc(chEsc, stdout);
				printf(";\' to open a console line]\n");
			}
		} else {
			fprintf(stderr, "%s: %s: %s\n", progname, pcMach, acMesg);
			c2cooked();
			exit(5);
		}
	}

	/* if the host is not down, finish the connection, and force
	 * the correct attachment for the user
	 */
	if ('-' != fIn) {
		if (fIn == 'r') {
			if ('s' != *pcHow) {
				fprintf(stderr, "%s: %s is read-only\n", progname, pcMach);
			}
		} else if (fIn != ('f' == *pcHow ? 'a' : *pcHow)) {
			(void)sprintf(acMesg, "%c%c%c", chAttn, chEsc, *pcHow);
			SendOut(s, acMesg, 3);
		}
		if (fReplay) {
			(void)sprintf(acMesg, "%c%cr", chAttn, chEsc);
			SendOut(s, acMesg, 3);
		} else if (fVerbose) {
			(void)sprintf(acMesg, "%c%c\022", chAttn, chEsc);
			SendOut(s, acMesg, 3);
		}
	}
	(void)fflush(stdout);
	(void)fflush(stderr);

	c2raw();

	/* read from stdin and the socket (non-blocking!).
	 * rmask indicates which descriptors to read from,
	 * the others are not used, nor is the result from
	 * select, read, or write.
	 */
	FD_ZERO(& rinit);
	FD_SET(s, &rinit);
	FD_SET(0, &rinit);
	for (;;) {
		/* reset read mask and select on it
		 */
		rmask = rinit;
		while (-1 == select(sizeof(rmask)*8, &rmask, (fd_set *)0, (fd_set *)0, (struct timeval *)0)) {
			static char acCmd[64];

			rmask = rinit;

			/* if we were suspened, try again */
			if (EINTR != errno || !SawUrg) {
				continue;
			}
			SawUrg = 0;
#if defined(SIGURG)
			(void)signal(SIGURG, oob);
#endif

			/* get the pending urgent message
			 */
			while (recv(s, acCmd, 1, MSG_OOB) < 0) {
				switch (errno) {
				case EWOULDBLOCK:
					/* clear any pending input to make room */
					(void)read(s, acCmd, sizeof(acCmd));
					CSTROUT(1, ".");
					continue;
				case EINVAL:
				default:
					fprintf(stderr, "%s: recv: %d: %s\r\n", progname, s, strerror(errno));
					sleep(1);
					continue;
				}
			}
			switch (acCmd[0]) {
			case OB_SUSP:
#if defined(SIGSTOP)
				CSTROUT(1, "stop]");
				c2cooked();
				(void)kill(getpid(), SIGSTOP);
				c2raw();
				CSTROUT(1, "[press any character to continue");
#else
				CSTROUT(1, "stop not supported -- press any character to continue");
#endif
				break;
			case OB_DROP:
				CSTROUT(1, "dropped by server]\r\n");
				c2cooked();
				exit(1);
				/*NOTREACHED*/
			case OB_POWER:
#if DO_POWER
				CSTROUT(1, "power");
				/* get address to call and prefix */
				SendOut(s, "k", 1);
				if (0 == ReadReply(s, acPowCtl, sizeof(acPowCtl), (char *)0)) {
					CSTROUT(1, "no key");
					SendOut(s, "b", 1);
					break;
				}
				SendOut(s, "s", 1);
				if (0 == ReadReply(s, acCtlr, sizeof(acCtlr), (char *)0)) {
					CSTROUT(1, "no controller");
					SendOut(s, "b", 1);
					break;
				}
				Power(acCtlr, pcMach, pcMaster);
#else
				CSTROUT(1, "no client support");
#endif
				SendOut(s, "b", 1);
				break;
			default:
				fprintf(stderr, "%s: unknown out of band command `%c\'\r\n", progname, acCmd[0]);
				(void)fflush(stderr);
				break;
			}
		}

		/* anything from socket? */
		if (FD_ISSET(s, &rmask)) {
			if ((nc = read(s, acMesg, sizeof(acMesg))) == 0) {
				break;
			}
#if STRIP8
			/* clear parity? */
			for (i = 0; i < nc; ++i)
				acMesg[i] &= 127;
#endif
			SendOut(1, acMesg, nc);
		}
		/* anything from stdin? */
		if (FD_ISSET(0, &rmask)) {
			if ((nc = read(0, acMesg, sizeof(acMesg))) == 0)
				break;
			SendOut(s, acMesg, nc);
		}
	}
	c2cooked();
	if (fVerbose)
		printf("Console %s closed.\n", pcMach);
	return 0;
}


/* the group leader tells is the server to connect to			(ksb)
 * we use CallUp to start a session with each target, or forward it
 */
static int
Indir(s, pcMaster, pcMach, pcCmd, pcWho)
int s;
char *pcMaster, *pcMach, *pcCmd, *pcWho;
{
	auto char acPorts[4097];

	/* send request for master list
	 */
	(void)sprintf(acPorts, "call:%s\r\n", pcMach);
	SendOut(s, acPorts, strlen(acPorts));

	/* get the ports number */
	if (0 >= ReadReply(s, acPorts, sizeof(acPorts), (char *)0)) {
		fprintf(stderr, "%s: %s: call forward broken\n", progname, pcMaster);
		exit(1);
	}

	if ('@' == acPorts[0]) {
		static int iLimit = 0;
		if (iLimit++ > 10) {
			fprintf(stderr, "%s: forwarding level too deep!\n", progname);
			return 1;
		}
		return Gather(Indir, acPorts, pcMaster, pcMach, pcCmd, pcWho);
	}
	/* send the command to each master
	 */
	return Gather(CallUp, acPorts, pcMaster, pcMach, pcCmd, pcWho);
}

#define BUF_G1		(MAXGRP*80)
#define BUF_MIN		80
#define BUF_CHUNK	(2*132)

/* Cmd is implemented seperately from above because of the need buffer	(ksb)
 * the ports' output.  It's about the same as what's above otherwise.
 * We trick lint because we have to be call compatible (prototype'd)
 * the same as all the other Gather functions.
 */
/*ARGSUSED*/
static int
Cmd(s, pcMaster, pcMach, pcCmd, pcWho)
int s;
char *pcMaster, *pcMach, *pcCmd, *pcWho;
{
	static int iMax = 0;
	static char *pcBuf = (char *)0;
	register int nr, iRem, i, fBrace;

	/* setup the big buffer for the server output
	 */
	if ((char *)0 == pcBuf) {
		iMax = BUF_G1;
		if ((char *)0 == (pcBuf = calloc(BUF_G1, sizeof(char)))) {
			OutOfMem();
		}
	}

	/* send sign-on stuff, then wait for a reply, like "ok\r\n"
	 * before allowing a write
	 */
	(void)sprintf(acMesg, "%c%c%c%c%c.", DEFATTN, DEFESC, *pcCmd, DEFATTN, DEFESC);
	SendOut(s, acMesg, 6);

	/* read the server's reply,
	 * We buffer until we close the connection because it
	 * wouldn't be fair to ask the server to keep up with
	 * itself :-)  {if we are inside a console connection}.
	 */
	iRem = iMax;
	i = 0;
	while (0 < (nr = read(s, pcBuf+i, iRem))) {
		i += nr;
		iRem -= nr;
		if (iRem >= BUF_MIN) {
			continue;
		}
		iMax += BUF_CHUNK;
		if ((char *)0 == (pcBuf = realloc(pcBuf, iMax))) {
			OutOfMem();
		}
		iRem += BUF_CHUNK;
	}
	/* edit out the command lines [...]
	 */
	iRem = fBrace = 0;
	for (nr = 0; nr < i; ++nr) {
		if (0 != fBrace) {
			if (']' == pcBuf[nr]) {
				fBrace = 0;
			}
			continue;
		}
		switch (pcBuf[nr]) {
		case '\r':
			if (0 == iRem)
				continue;
			break;
		case '\n':
			if (0 == iRem)
				continue;
			(void)putchar('\n');
			iRem = 0;
			continue;
		case '[':
			fBrace = 1;
			continue;
		}
		(void)putchar(pcBuf[nr]);
		iRem = 1;
	}
	(void)fflush(stdout);

	return 0;
}

/* the masters tell us the group masters with a "groups" command	(ksb)
 */
static int
CmdGroup(s, pcMaster, pcMach, pcCmd, pcWho)
int s;
char *pcMaster, *pcMach, *pcCmd, *pcWho;
{
	auto char acPorts[4097];
	register int iNull;

	/* send request for master list
	 */
	(void)sprintf(acPorts, "groups\r\n", pcCmd);
	SendOut(s, acPorts, strlen(acPorts));

	/* get the port numbers for the groups on this master
	 */
	if (0 > (iNull = ReadReply(s, acPorts, sizeof(acPorts), (char *)0))) {
		fprintf(stderr, "%s: %s: group forward broken\n", progname, pcMaster);
		exit(1);
	}

	/* an empty list means this console server node _just_ forwards
	 */
	if (0 == iNull) {
		if (fVerbose) {
			printf("%s:<none>\r\n", pcMaster);
		}
		return 0;
	}
	if (fVerbose) {
		printf("%s:\r\n", pcMaster);
	}
	/* to the command to each master
	 */
	return Gather(Cmd, acPorts, pcMaster, pcMach, pcCmd, pcWho);
}


/* the master tells us the machine masters with a "master" command	(ksb)
 * we ask each of those for the group members
 */
static int
CmdMaster(s, pcMaster, pcMach, pcCmd, pcWho)
int s;
char *pcMaster, *pcMach, *pcCmd, *pcWho;
{
	auto char acPorts[4097];
	register int iNull;

	/* send request for master list
	 */
	CSTROUT(s, "master\r\n");

	/* get the ports number */
	if (0 > (iNull = ReadReply(s, acPorts, sizeof(acPorts), (char *)0))) {
		fprintf(stderr, "%s: %s: master forward broken\n", progname, pcMaster);
		exit(1);
	}
	if (0 == iNull) {
		if (fVerbose) {
			printf("%s: master list empyt\r\n", pcMaster);
		}
		return 0;
	}
	if (fVerbose) {
		printf("%s: %s\r\n", pcMaster, acPorts);
	}
	/* to the command to each master
	 */
	return Gather(CmdGroup, acPorts, pcMaster, pcMach, pcCmd, pcWho);
}


/* The masters tell us the group masters with a "groups" command.	(ksb)
 * We trick lint because we have to be call compatible (prototype'd)
 * the same as all the other Gather functions.
 */
/*ARGSUSED*/
static int
Ctl(s, pcMaster, pcMach, pcCmd, pcWho)
int s;
char *pcMaster, *pcMach, *pcCmd, *pcWho;
{
	auto char acPorts[4097];

	/* send request for master list
	 */
	(void)sprintf(acPorts, "%s:%s\r\n", pcCmd, pcMach);
	SendOut(s, acPorts, strlen(acPorts));

	/* get the ports number */
	if (0 >= ReadReply(s, acPorts, sizeof(acPorts), (char *)0)) {
		fprintf(stderr, "%s: group leader died?\n", progname);
		return 1;
	}
	if (fVerbose) {
		printf("%s:\r\n", pcMaster);
	}
	printf("%s: %s\r\n", pcMaster, acPorts);

	/* to the command to each master
	 */
	return 0;
}


/* the master tells us the machine masters with a "master" command	(ksb)
 * we tell each of those the command we want them to do
 */
static int
CtlMaster(s, pcMaster, pcMach, pcCmd, pcWho)
int s;
char *pcMaster, *pcMach, *pcCmd, *pcWho;
{
	auto char acPorts[4097];

	/* send request for master list
	 */
	CSTROUT(s, "master\r\n");

	/* get the ports number */
	if (0 >= ReadReply(s, acPorts, sizeof(acPorts), (char *)0)) {
		fprintf(stderr, "%s: %s: master forward broken\n", progname, pcMaster);
		exit(1);
	}
	/* to the command to each master
	 */
	return Gather(Ctl, acPorts, pcMaster, pcMach, pcCmd, pcWho);
}

%%
from '<pwd.h>'
%c
/* get the user's password entry name, believe $USER or $LOGNAME	(ksb)
 * if they have the correct uid
 */
char *
FindUser(pcOut)
char *pcOut;
{
	register char *pcEnv;
	register struct passwd *pwdMe;

	if (((char *)0 != (pcEnv = getenv("USER")) || (char *)0 != (pcEnv = getenv("LOGNAME"))) &&
	    (struct passwd *)0 != (pwdMe = getpwnam(pcEnv)) &&
	    getuid() == pwdMe->pw_uid) {
		/* use the login $USER is set to, if it is our (real) uid */;
	} else if ((struct passwd *)0 == (pwdMe = getpwuid(getuid()))) {
		fprintf(stderr, "%s: getpwuid: %d: %s\n", progname, getuid(), strerror(errno));
		exit(1);
	}
	return strcpy(pcOut, pwdMe->pw_name);
}

/* Lookup runtime information, like the loopback interface address	(ksb)
 * to use (should be 127.0.0.1, but let's look) and the service if
 * we don't know the port.
 */
static void
RunTime()
{
	register struct hostent *hp;

	if ((struct hostent *)0 != (hp = gethostbyname(acLocalhost))) {
#if USE_STRINGS
		(void)bcopy((char *)hp->h_addr, (char *)&local_port.sin_addr, hp->h_length);
#else
		memcpy((char *)&local_port.sin_addr, (char *)hp->h_addr, hp->h_length);
#endif
	} else {
		acLocalhost[0] = '\000';
	}
	if (-1 == gethostname(acMyName, sizeof(acMyName))) {
		fprintf(stderr, "%s: gethostname: %s\n", progname, strerror(errno));
		exit(1);
	}

#if defined(SERVICE)
	if ((struct servent *)0 == (pSE = getservbyname(acService, "tcp"))) {
		fprintf(stderr, "%s: getservbyname: %s: %s\n", progname, acService, strerror(errno));
		exit(1);
	}
#endif
}

/* like the above, but we signal the console server with a USR2		(ksb)
 */
/*ARGSUSED*/
static int
CtlSignal(s, pcMaster, pcMach, pcCmd, pcWho)
int s;
char *pcMaster, *pcMach, *pcCmd, *pcWho;
{
	register int iPid;
	auto char acPorts[4097];

	/* send request for master list
	 */
	(void)sprintf(acPorts, "%s:%s\r\n", pcCmd, pcMach);
	SendOut(s, acPorts, strlen(acPorts));

	/* get the ports number */
	if (0 >= ReadReply(s, acPorts, sizeof(acPorts), (char *)0)) {
		fprintf(stderr, "%s: group leader died?\n", progname);
		return 1;
	}
	iPid = atoi(acPorts);
	if (fVerbose) {
		printf("%s: kill -USR2 %d\r\n", pcMaster, iPid);
	}
	return -1 == kill(iPid, SIGUSR2);
}
%%
