/*
 *  $Id: console.c,v 5.191 2015/06/01 15:36:59 bryan Exp $
 *
 *  Copyright conserver.com, 2000
 *
 *  Maintainer/Enhancer: Bryan Stansell (bryan@conserver.com)
 *
 *  Copyright GNAC, Inc., 1998
 */

/*
 * 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.
 */

#include <compat.h>

#include <pwd.h>

#include <getpassword.h>
#include <cutil.h>
#include <readconf.h>
#include <version.h>
#if HAVE_OPENSSL
# include <openssl/ssl.h>
# include <openssl/err.h>
# include <openssl/opensslv.h>
#endif
#if HAVE_GSSAPI
# include <gssapi/gssapi.h>
#endif
#if USE_IPV6
# include <sys/socket.h>
# include <netdb.h>
#endif


int fReplay = 0, fVersion = 0;
int showExecData = 1;
int chAttn = -1, chEsc = -1;
unsigned short bindPort;
CONSFILE *cfstdout;
int disconnectCount = 0;
STRING *execCmd = (STRING *)0;
CONSFILE *execCmdFile = (CONSFILE *)0;
pid_t execCmdPid = 0;
CONSFILE *gotoConsole = (CONSFILE *)0;
CONSFILE *prevConsole = (CONSFILE *)0;
char *gotoName = (char *)0;
char *prevName = (char *)0;
CONFIG *optConf = (CONFIG *)0;
CONFIG *config = (CONFIG *)0;
FLAG interact = FLAGFALSE;
unsigned int sversion = 0;
#if defined(TIOCGWINSZ)
struct winsize ws;
#endif

#if HAVE_OPENSSL
SSL_CTX *ctx = (SSL_CTX *)0;

void
SetupSSL(void)
{
    if (ctx == (SSL_CTX *)0) {
	char *ciphers;
	SSL_load_error_strings();
	if (!SSL_library_init()) {
	    Error("SSL library initialization failed");
	    Bye(EX_UNAVAILABLE);
	}
	if ((ctx = SSL_CTX_new(SSLv23_method())) == (SSL_CTX *)0) {
	    Error("Creating SSL context failed");
	    Bye(EX_UNAVAILABLE);
	}
	if (SSL_CTX_set_default_verify_paths(ctx) != 1) {
	    Error("Could not load SSL default CA file and/or directory");
	    Bye(EX_UNAVAILABLE);
	}
	if (config->sslcacertificatefile != (char *)0 ||
	    config->sslcacertificatepath != (char *)0) {
	    if (SSL_CTX_load_verify_locations
		(ctx, config->sslcacertificatefile,
		 config->sslcacertificatepath) != 1) {
		if (config->sslcacertificatefile != (char *)0)
		    Error("Could not setup ca certificate file to '%s'",
			  config->sslcacertificatefile);
		if (config->sslcacertificatepath != (char *)0)
		    Error("Could not setup ca certificate path to '%s'",
			  config->sslcacertificatepath);
		Bye(EX_UNAVAILABLE);
	    }
	}
	if (config->sslcredentials != (char *)0) {
	    if (SSL_CTX_use_certificate_chain_file
		(ctx, config->sslcredentials) != 1) {
		Error("Could not load SSL certificate from '%s'",
		      config->sslcredentials);
		Bye(EX_UNAVAILABLE);
	    }
	    if (SSL_CTX_use_PrivateKey_file
		(ctx, config->sslcredentials, SSL_FILETYPE_PEM) != 1) {
		Error("Could not SSL private key from '%s'",
		      config->sslcredentials);
		Bye(EX_UNAVAILABLE);
	    }
	    ciphers = "ALL:!LOW:!EXP:!MD5:!aNULL:@STRENGTH";
	} else {
# if defined(REQ_SERVER_CERT)
	    ciphers = "ALL:!LOW:!EXP:!MD5:!aNULL:@STRENGTH";
# else
	    ciphers = "ALL:!LOW:!EXP:!MD5:@STRENGTH";
# endif
	}
	SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, SSLVerifyCallback);
	SSL_CTX_set_options(ctx,
			    SSL_OP_ALL | SSL_OP_NO_SSLv2 |
			    SSL_OP_SINGLE_DH_USE);
	SSL_CTX_set_mode(ctx,
			 SSL_MODE_ENABLE_PARTIAL_WRITE |
			 SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER |
			 SSL_MODE_AUTO_RETRY);
	if (SSL_CTX_set_cipher_list(ctx, ciphers) != 1) {
	    Error("Setting SSL cipher list failed");
	    Bye(EX_UNAVAILABLE);
	}
    }
}

void
AttemptSSL(CONSFILE *pcf)
{
    SSL *ssl;

    if (ctx == (SSL_CTX *)0) {
	Error("WTF?  The SSL context disappeared?!?!?");
	Bye(EX_UNAVAILABLE);
    }
    if (!(ssl = SSL_new(ctx))) {
	Error("Couldn't create new SSL context");
	Bye(EX_UNAVAILABLE);
    }
    FileSetSSL(pcf, ssl);
    SSL_set_fd(ssl, FileFDNum(pcf));
    CONDDEBUG((1, "About to SSL_connect() on fd %d", FileFDNum(pcf)));
    if (SSL_connect(ssl) <= 0) {
	Error("SSL negotiation failed");
	ERR_print_errors_fp(stderr);
	Bye(EX_UNAVAILABLE);
    }
    FileSetType(pcf, SSLSocket);
    CONDDEBUG((1, "SSL Connection: %s :: %s", SSL_get_cipher_version(ssl),
	       SSL_get_cipher_name(ssl)));
}
#endif

#if HAVE_GSSAPI
gss_name_t gss_server_name = GSS_C_NO_NAME;
gss_ctx_id_t secctx = GSS_C_NO_CONTEXT;
gss_buffer_desc mytok = GSS_C_EMPTY_BUFFER;

int
CanGetGSSContext(const char *servername)
{
    char namestr[128];
    gss_buffer_desc namebuf, dbuf;
    OM_uint32 stmaj, stmin, mctx, dmin;

    snprintf(namestr, 128, "host@%s", servername);
    namebuf.value = namestr;
    namebuf.length = strlen(namestr) + 1;
    stmaj =
	gss_import_name(&stmin, &namebuf, GSS_C_NT_HOSTBASED_SERVICE,
			&gss_server_name);
    /* XXX: handle error */
    if (stmaj != GSS_S_COMPLETE) {
	Error("gss_import_name failed");
	return 0;
    }
    secctx = GSS_C_NO_CONTEXT;
    mytok.length = 0;
    mytok.value = NULL;

    stmaj =
	gss_init_sec_context(&stmin, GSS_C_NO_CREDENTIAL, &secctx,
			     gss_server_name, GSS_C_NULL_OID,
			     GSS_C_MUTUAL_FLAG, 0,
			     GSS_C_NO_CHANNEL_BINDINGS, NULL, NULL, &mytok,
			     NULL, NULL);

    if (stmaj != GSS_S_COMPLETE && stmaj != GSS_S_CONTINUE_NEEDED) {
	gss_release_name(&stmin, &gss_server_name);
	return 0;
    }
    return mytok.length;
}

int
AttemptGSSAPI(CONSFILE *pcf)
{
    OM_uint32 stmaj, stmin;
    gss_buffer_desc servertok;
    char buf[1024];
    int nr;
    int ret;

    FileSetQuoteIAC(pcf, FLAGFALSE);
    FileWrite(pcf, FLAGFALSE, mytok.value, mytok.length);
    FileSetQuoteIAC(pcf, FLAGTRUE);
    nr = FileRead(pcf, buf, sizeof(buf));
    servertok.length = nr;
    servertok.value = buf;

    stmaj =
	gss_init_sec_context(&stmin, GSS_C_NO_CREDENTIAL, &secctx,
			     gss_server_name, GSS_C_NULL_OID,
			     GSS_C_MUTUAL_FLAG, 0,
			     GSS_C_NO_CHANNEL_BINDINGS, &servertok, NULL,
			     &mytok, NULL, NULL);
    gss_release_buffer(&stmin, &mytok);

    ret = (stmaj == GSS_S_COMPLETE);
    gss_release_name(&stmin, &gss_server_name);
    return ret;
}
#endif

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

/* output a long message to the user
 */
static void
Usage(int wantfull)
{
    static char *full[] = {
	"7         strip the high bit off all console data",
	"a(A)      attach politely (and replay last 20 lines)",
	"b(B)      send broadcast message to all users (on master)",
#if HAVE_OPENSSL
	"c cred    load an SSL certificate and key from the PEM encoded file",
#else
	"c cred    ignored - encryption not compiled into code",
#endif
	"C config  override per-user config file",
	"d         disconnect [user][@console]",
	"D         enable debug output, sent to stderr",
	"e esc     set the initial escape characters",
#if HAVE_OPENSSL
	"E         don't attempt encrypted connections",
#else
	"E         ignored - encryption not compiled into code",
#endif
	"f(F)      force read/write connection (and replay)",
	"h         output this message",
	"i(I)      display status info in machine-parseable form (on master)",
	"l user    use username instead of current username",
	"M master  master server to poll first",
	"n         do not read system-wide config file",
	"p port    port to connect to",
	"P         display pids of daemon(s)",
	"q(Q)      send a quit command to the (master) server",
	"r(R)      display (master) daemon version (think 'r'emote version)",
	"s(S)      spy on a console (and replay)",
	"t         send a text message to [user][@console]",
	"u         show users on the various consoles",
#if HAVE_OPENSSL
	"U         allow unencrypted connections if SSL not available",
#else
	"U         ignored - encryption not compiled into code",
#endif
	"v         be more verbose",
	"V         show version information",
	"w(W)      show who is on which console (on master)",
	"x         examine ports and baud rates",
	"z(Z) cmd  send a command to the (master) server (think 'z'ap)",
	(char *)0
    };

    fprintf(stderr, "usage: %s [generic-args] [-aAfFsS] [-e esc] console\n\
       %s [generic-args] [-iIuwWx] [console]\n\
       %s [generic-args] [-hPqQrRV] [-[bB] message] [-d [user][@console]]\n\
                              [-t [user][@console] message] [-[zZ] cmd]\n\n\
       generic-args: [-7DEnUv] [-c cred] [-C config] [-M master]\n\
                     [-p port] [-l username]\n", progname, progname, progname);

    if (wantfull) {
	int i;
	fprintf(stderr, "\n");
	for (i = 0; full[i] != (char *)0; i++)
	    fprintf(stderr, "\t%s\n", full[i]);
    }
}

/* expain who we are and which revision we are				(ksb)
 */
static void
Version(void)
{
    int i;
    static STRING *acA1 = (STRING *)0;
    static STRING *acA2 = (STRING *)0;
    char *optionlist[] = {
#if HAVE_DMALLOC
	"dmalloc",
#endif
#if USE_LIBWRAP
	"libwrap",
#endif
#if HAVE_OPENSSL
	"openssl",
#endif
#if HAVE_GSSAPI
	"gssapi",
#endif
#if USE_UNIX_DOMAIN_SOCKETS
	"uds",
#endif
	(char *)0
    };

    if (acA1 == (STRING *)0)
	acA1 = AllocString();
    if (acA2 == (STRING *)0)
	acA2 = AllocString();

    Msg(MyVersion());
#if USE_UNIX_DOMAIN_SOCKETS
    Msg("default socket directory `%s'", UDSDIR);
#else
    Msg("default initial master server `%s'", MASTERHOST);
    Msg("default port referenced as `%s'", DEFPORT);
#endif
    Msg("default escape sequence `%s%s'", FmtCtl(DEFATTN, acA1),
	FmtCtl(DEFESC, acA2));
    Msg("default site-wide configuration in `%s'", CLIENTCONFIGFILE);
    Msg("default per-user configuration in `%s'", "$HOME/.consolerc");

    BuildString((char *)0, acA1);
    if (optionlist[0] == (char *)0)
	BuildString("none", acA1);
    for (i = 0; optionlist[i] != (char *)0; i++) {
	if (i == 0)
	    BuildString(optionlist[i], acA1);
	else {
	    BuildString(", ", acA1);
	    BuildString(optionlist[i], acA1);
	}
    }
    Msg("options: %s", acA1->string);
#if HAVE_DMALLOC
    BuildString((char *)0, acA1);
    BuildStringChar('0' + DMALLOC_VERSION_MAJOR, acA1);
    BuildStringChar('.', acA1);
    BuildStringChar('0' + DMALLOC_VERSION_MINOR, acA1);
    BuildStringChar('.', acA1);
    BuildStringChar('0' + DMALLOC_VERSION_PATCH, acA1);
# if defined(DMALLOC_VERSION_BETA)
    if (DMALLOC_VERSION_BETA != 0) {
	BuildString("-b", acA1);
	BuildStringChar('0' + DMALLOC_VERSION_BETA, acA1);
    }
# endif
    Msg("dmalloc version: %s", acA1->string);
#endif
#if HAVE_OPENSSL
    Msg("openssl version: %s", OPENSSL_VERSION_TEXT);
#endif
    Msg("built with `%s'", CONFIGINVOCATION);
    if (fVerbose)
	printf(COPYRIGHT);
}


/* 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(char **ppcSrc, char *pcOut)
{
    int cvt, n;
    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;
}

/*
 */
static void
ValidateEsc(void)
{
    unsigned char c1, c2;

    if (config->striphigh != FLAGTRUE)
	return;

    if (chAttn == -1 || chEsc == -1) {
	c1 = DEFATTN;
	c2 = DEFESC;
    } else {
	c1 = chAttn;
	c2 = chEsc;
    }
    if (c1 > 127 || c2 > 127) {
	Error("High-bit set in escape sequence: not allowed with -7");
	Bye(EX_UNAVAILABLE);
    }
}

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

    pcTemp = pcText;
    if (ParseChar(&pcTemp, &c1) || ParseChar(&pcTemp, &c2)) {
	Error("poorly formed escape sequence `%s\'", pcText);
	Bye(EX_UNAVAILABLE);
    }
    if ('\000' != *pcTemp) {
	Error("too many characters in new escape sequence at ...`%s\'",
	      pcTemp);
	Bye(EX_UNAVAILABLE);
    }
    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
 */
CONSFILE *
GetPort(char *pcToHost, unsigned short sPort)
{
    int s;
#if USE_IPV6
    int error;
    char host[NI_MAXHOST];
    char serv[NI_MAXSERV];
    struct addrinfo *ai, *rp, hints;
#elif USE_UNIX_DOMAIN_SOCKETS
    struct sockaddr_un port;
    static STRING *portPath = (STRING *)0;
#else
    struct hostent *hp = (struct hostent *)0;
    struct sockaddr_in port;
#endif
#if HAVE_SETSOCKOPT
    int one = 1;
#endif

#if USE_IPV6
# if HAVE_MEMSET
    memset(&hints, 0, sizeof(hints));
# else
    bzero(&hints, sizeof(hints));
# endif
#else
# if HAVE_MEMSET
    memset((void *)(&port), '\000', sizeof(port));
# else
    bzero((char *)(&port), sizeof(port));
# endif
#endif

#if USE_IPV6
    hints.ai_flags = AI_ADDRCONFIG;
    hints.ai_socktype = SOCK_STREAM;
    snprintf(serv, sizeof(serv), "%hu", sPort);

    error = getaddrinfo(pcToHost, serv, &hints, &ai);
    if (error) {
	Error("getaddrinfo(%s): %s", pcToHost, gai_strerror(error));
	return (CONSFILE *)0;
    }

    rp = ai;
    while (rp) {
	error =
	    getnameinfo(rp->ai_addr, rp->ai_addrlen, host, sizeof(host),
			serv, sizeof(serv),
			NI_NUMERICHOST | NI_NUMERICSERV);
	if (error) {
	    continue;
	}
	CONDDEBUG((1, "GetPort: hostname=%s, ip=%s, port=%s", pcToHost,
		   host, serv));

	/* 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)
	 */
	s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
	if (s != -1) {
# if HAVE_SETSOCKOPT
	    if (setsockopt
		(s, SOL_SOCKET, SO_KEEPALIVE, (char *)&one,
		 sizeof(one)) < 0)
		goto fail;
# endif
	    if (connect(s, rp->ai_addr, rp->ai_addrlen) == 0)
		goto success;
	  fail:
	    close(s);
	}
	rp = rp->ai_next;
    }
    Error("Unable to connect to %s:%s", host, serv);
    return (CONSFILE *)0;
  success:
    freeaddrinfo(ai);
#elif USE_UNIX_DOMAIN_SOCKETS
    if (portPath == (STRING *)0)
	portPath = AllocString();
    BuildStringPrint(portPath, "%s/%hu", config->master, sPort);
    port.sun_family = AF_UNIX;
    if (portPath->used > sizeof(port.sun_path)) {
	Error("GetPort: path to socket too long: %s", portPath->string);
	return (CONSFILE *)0;
    }
    StrCpy(port.sun_path, portPath->string, sizeof(port.sun_path));

    CONDDEBUG((1, "GetPort: socket=%s", port.sun_path));

    /* 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_UNIX, SOCK_STREAM, 0)) < 0) {
	Error("socket(AF_UNIX,SOCK_STREAM): %s", strerror(errno));
	return (CONSFILE *)0;
    }

    if (connect(s, (struct sockaddr *)(&port), sizeof(port)) < 0) {
	Error("connect(): %s: %s", port.sun_path, strerror(errno));
	return (CONSFILE *)0;
    }
#else
# if HAVE_INET_ATON
    if (inet_aton(pcToHost, &(port.sin_addr)) == 0)
# else
    port.sin_addr.s_addr = inet_addr(pcToHost);
    if ((in_addr_t) (-1) == port.sin_addr.s_addr)
# endif
    {
	if ((struct hostent *)0 != (hp = gethostbyname(pcToHost))) {
# if HAVE_MEMCPY
	    memcpy((char *)&port.sin_addr.s_addr, (char *)hp->h_addr,
		   hp->h_length);
# else
	    bcopy((char *)hp->h_addr, (char *)&port.sin_addr.s_addr,
		  hp->h_length);
# endif
	} else {
	    Error("gethostbyname(%s): %s", pcToHost, hstrerror(h_errno));
	    return (CONSFILE *)0;
	}
    }
    port.sin_port = sPort;
    port.sin_family = AF_INET;

    if (fDebug) {
	if ((struct hostent *)0 != hp && (char *)0 != hp->h_name) {
	    CONDDEBUG((1, "GetPort: hostname=%s (%s), ip=%s, port=%hu",
		       hp->h_name, pcToHost, inet_ntoa(port.sin_addr),
		       ntohs(sPort)));
	} else {
	    CONDDEBUG((1,
		       "GetPort: hostname=<unresolved> (%s), ip=%s, port=%hu",
		       pcToHost, inet_ntoa(port.sin_addr), ntohs(sPort)));
	}
    }

    /* 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) {
	Error("socket(AF_INET,SOCK_STREAM): %s", strerror(errno));
	return (CONSFILE *)0;
    }
# if HAVE_SETSOCKOPT
    if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof(one))
	< 0) {
	Error("setsockopt(SO_KEEPALIVE): %s", strerror(errno));
	close(s);
	return (CONSFILE *)0;
    }
# endif

    if (connect(s, (struct sockaddr *)(&port), sizeof(port)) < 0) {
	Error("connect(): %hu@%s: %s", ntohs(port.sin_port), pcToHost,
	      strerror(errno));
	close(s);
	return (CONSFILE *)0;
    }
#endif

    return FileOpenFD(s, simpleSocket);
}


/* the next two routines assure that the users tty is in the
 * correct mode for us to do our thing
 */
static int screwy = 0;
static struct termios o_tios;


/*
 * 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(void)
{
    struct termios n_tios;

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

    if (0 != tcgetattr(0, &o_tios)) {
	Error("tcgetattr(0): %s", strerror(errno));
	Bye(EX_UNAVAILABLE);
    }
    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 | IEXTEN);
    n_tios.c_cc[VMIN] = 1;
    n_tios.c_cc[VTIME] = 0;
    if (0 != tcsetattr(0, TCSANOW, &n_tios)) {
	Error("tcsetattr(0, TCSANOW): %s", strerror(errno));
	Bye(EX_UNAVAILABLE);
    }
    screwy = 1;
}

/*
 * put the tty back as it was, however that was
 */
static void
C2Cooked(void)
{
    if (!screwy)
	return;
    tcsetattr(0, TCSANOW, &o_tios);
    screwy = 0;
}

void
DestroyDataStructures(void)
{
    C2Cooked();
    if (cfstdout != (CONSFILE *)0)
	FileUnopen(cfstdout);
    DestroyConfig(pConfig);
    DestroyConfig(optConf);
    DestroyConfig(config);
    DestroyTerminal(pTerm);
#if !USE_IPV6
    if (myAddrs != (struct in_addr *)0)
	free(myAddrs);
#endif
    DestroyStrings();
    if (substData != (SUBST *)0)
	free(substData);
}

char *
ReadReply(CONSFILE *fd, FLAG toEOF)
{
    int nr;
    static char buf[1024];
    static STRING *result = (STRING *)0;

    if (result == (STRING *)0)
	result = AllocString();
    else
	BuildString((char *)0, result);

    while (1) {
	int l;
	switch (nr = FileRead(fd, buf, sizeof(buf))) {
	    case 0:
		/* fall through */
	    case -1:
		if (result->used > 1 || toEOF == FLAGTRUE)
		    break;
		C2Cooked();
		Error("lost connection");
		Bye(EX_UNAVAILABLE);
	    default:
		while ((l = ParseIACBuf(fd, buf, &nr)) >= 0) {
		    if (l == 0)
			continue;
		    BuildStringN(buf, l, result);
		    nr -= l;
		    MemMove(buf, buf + l, nr);
		}
		BuildStringN(buf, nr, result);
		if (toEOF == FLAGTRUE)	/* if toEOF, read until EOF */
		    continue;
		if ((result->used > 1) &&
		    strchr(result->string, '\n') != (char *)0)
		    break;
		continue;
	}
	break;
    }
    if (fDebug) {
	static STRING *tmpString = (STRING *)0;
	if (tmpString == (STRING *)0)
	    tmpString = AllocString();
	BuildString((char *)0, tmpString);
	FmtCtlStr(result->string, result->used - 1, tmpString);
	CONDDEBUG((1, "ReadReply: `%s'", tmpString->string));
    }
    return result->string;
}

static void
ReapVirt(void)
{
    pid_t pid;
    int UWbuf;

    while (-1 != (pid = waitpid(-1, &UWbuf, WNOHANG | WUNTRACED))) {
	if (0 == pid)
	    break;

	/* stopped child is just continued
	 */
	if (WIFSTOPPED(UWbuf) && 0 == kill(pid, SIGCONT)) {
	    Msg("child pid %lu: stopped, sending SIGCONT",
		(unsigned long)pid);
	    continue;
	}

	if (WIFEXITED(UWbuf))
	    Verbose("child process %lu: exit(%d)", pid,
		    WEXITSTATUS(UWbuf));
	if (WIFSIGNALED(UWbuf))
	    Verbose("child process %lu: signal(%d)", pid, WTERMSIG(UWbuf));
	if (pid == execCmdPid) {
	    if (WIFEXITED(UWbuf))
		FilePrint(cfstdout, FLAGFALSE,
			  "[local command terminated - pid %lu: exit(%d)]\r\n",
			  pid, WEXITSTATUS(UWbuf));
	    if (WIFSIGNALED(UWbuf))
		FilePrint(cfstdout, FLAGFALSE,
			  "[local command terminated - pid %lu: signal(%d)]\r\n",
			  pid, WTERMSIG(UWbuf));
	}
    }
}

static sig_atomic_t fSawReapVirt = 0;

#if HAVE_SIGACTION
static
#endif
  RETSIGTYPE
FlagReapVirt(int sig)
{
    fSawReapVirt = 1;
#if !HAVE_SIGACTION
    SimpleSignal(SIGCHLD, FlagReapVirt);
#endif
}

/* invoke the execcmd command */
void
ExecCmd(void)
{
    int i;
    pid_t iNewGrp;
    extern char **environ;
    int pin[2];
    int pout[2];
    static char *apcArgv[] = {
	"/bin/sh", "-ce", (char *)0, (char *)0
    };

    if (execCmd == (STRING *)0 || execCmd->used <= 1)
	return;

    CONDDEBUG((1, "ExecCmd(): `%s'", execCmd->string));

    /* pin[0] = parent read, pin[1] = child write */
    if (pipe(pin) != 0) {
	Error("ExecCmd(): pipe(): %s", strerror(errno));
	return;
    }
    /* pout[0] = child read, pout[l] = parent write */
    if (pipe(pout) != 0) {
	close(pin[0]);
	close(pin[1]);
	Error("ExecCmd(): pipe(): %s", strerror(errno));
	return;
    }

    fflush(stdout);
    fflush(stderr);

    switch (execCmdPid = fork()) {
	case -1:
	    return;
	case 0:
	    thepid = getpid();
	    break;
	default:
	    close(pout[0]);
	    close(pin[1]);
	    if ((execCmdFile =
		 FileOpenPipe(pin[0], pout[1])) == (CONSFILE *)0) {
		Error("ExecCmd(): FileOpenPipe(%d,%d) failed", pin[0],
		      pout[1]);
		close(pin[0]);
		close(pout[1]);
		kill(execCmdPid, SIGHUP);
		return;
	    }
	    FilePrint(cfstdout, FLAGFALSE,
		      "[local command running - pid %lu]\r\n", execCmdPid);
	    FD_SET(pin[0], &rinit);
	    if (maxfd < pin[0] + 1)
		maxfd = pin[0] + 1;
	    fflush(stderr);
	    return;
    }

    close(pin[0]);
    close(pout[1]);

    /* put the signals back that we ignore (trapped auto-reset to default)
     */
    SimpleSignal(SIGPIPE, SIG_DFL);
    SimpleSignal(SIGCHLD, SIG_DFL);

    /* setup new process with clean file descriptors
     * stderr still goes to stderr...so user sees it
     */
    i = GetMaxFiles();
    for ( /* i above */ ; --i > 3;) {
	if (i != pout[0] && i != pin[1])
	    close(i);
    }
    close(1);
    close(0);

#if HAVE_SETSID
    iNewGrp = setsid();
    if (-1 == iNewGrp) {
	Error("ExecCmd(): setsid(): %s", strerror(errno));
	iNewGrp = thepid;
    }
#else
    iNewGrp = thepid;
#endif

    if (dup(pout[0]) != 0 || dup(pin[1]) != 1) {
	Error("ExecCmd(): fd sync error");
	Bye(EX_OSERR);
    }
    close(pout[0]);
    close(pin[1]);

    tcsetpgrp(0, iNewGrp);

    apcArgv[2] = execCmd->string;

    execve(apcArgv[0], apcArgv, environ);
    Error("ExecCmd(): execve(%s): %s", apcArgv[2], strerror(errno));
    Bye(EX_OSERR);
    return;
}

void
GetUserInput(STRING *str)
{
    char c;

    if (str == (STRING *)0)
	return;

    BuildString((char *)0, str);

    for (;;) {
	if (read(0, &c, 1) == 0)
	    break;
	if (c == '\n' || c == '\r') {
	    break;
	}
	if (c >= ' ' && c <= '~') {
	    BuildStringChar(c, str);
	    FileWrite(cfstdout, FLAGFALSE, &c, 1);
	} else if ((c == '\b' || c == 0x7f) && str->used > 1) {
	    FileWrite(cfstdout, FLAGFALSE, "\b \b", 3);
	    str->string[str->used - 2] = '\000';
	    str->used--;
	} else if ((c == 0x15) && str->used > 1) {
	    while (str->used > 1) {
		FileWrite(cfstdout, FLAGFALSE, "\b \b", 3);
		str->string[str->used - 2] = '\000';
		str->used--;
	    }
	} else if ((c == 0x17) && str->used > 1) {
	    while (str->used > 1 &&
		   isspace((int)(str->string[str->used - 2]))) {
		FileWrite(cfstdout, FLAGFALSE, "\b \b", 3);
		str->string[str->used - 2] = '\000';
		str->used--;
	    }
	    while (str->used > 1 &&
		   !isspace((int)(str->string[str->used - 2]))) {
		FileWrite(cfstdout, FLAGFALSE, "\b \b", 3);
		str->string[str->used - 2] = '\000';
		str->used--;
	    }
	}
    }
}

void
DoExec(CONSFILE *pcf)
{
    showExecData = 1;
    FileWrite(cfstdout, FLAGFALSE, "exec: ", 6);

    GetUserInput(execCmd);
    FileWrite(cfstdout, FLAGFALSE, "]\r\n", 3);

    if (execCmd != (STRING *)0 && execCmd->used > 1) {
	ExecCmd();
	BuildString((char *)0, execCmd);
	if (execCmdFile == (CONSFILE *)0) {	/* exec failed */
	    /* say forget it */
	    FileSetQuoteIAC(pcf, FLAGFALSE);
	    FilePrint(pcf, FLAGFALSE, "%c%c", OB_IAC, OB_ABRT);
	    FileSetQuoteIAC(pcf, FLAGTRUE);
	} else {
	    char *r;
	    /* go back to blocking mode */
	    SetFlags(FileFDNum(pcf), 0, O_NONBLOCK);
	    /* say we're ready */
	    FileSetQuoteIAC(pcf, FLAGFALSE);
	    FilePrint(pcf, FLAGFALSE, "%c%c", OB_IAC, OB_EXEC);
	    FileSetQuoteIAC(pcf, FLAGTRUE);
	    r = ReadReply(pcf, FLAGFALSE);
	    /* now back to non-blocking, now that we've got reply */
	    SetFlags(FileFDNum(pcf), O_NONBLOCK, 0);
	    /* if we aren't still r/w, abort */
	    if (strncmp(r, "[rw]", 4) != 0) {
		FileWrite(cfstdout, FLAGFALSE,
			  "[no longer read-write - aborting command]\r\n",
			  -1);
		FD_CLR(FileFDNum(execCmdFile), &rinit);
		FD_CLR(FileFDOutNum(execCmdFile), &winit);
		FileClose(&execCmdFile);
		FileSetQuoteIAC(pcf, FLAGFALSE);
		FilePrint(pcf, FLAGFALSE, "%c%c", OB_IAC, OB_ABRT);
		FileSetQuoteIAC(pcf, FLAGTRUE);
		kill(execCmdPid, SIGHUP);
	    }
	}
    } else {
	/* say forget it */
	FileSetQuoteIAC(pcf, FLAGFALSE);
	FilePrint(pcf, FLAGFALSE, "%c%c", OB_IAC, OB_ABRT);
	FileSetQuoteIAC(pcf, FLAGTRUE);
    }
}

void
ExpandString(char *str, CONSFILE *c)
{
    char s;
    short backslash = 0;
    short cntrl = 0;
    char oct = '\000';
    short octs = 0;
    static STRING *exp = (STRING *)0;

    if (str == (char *)0 || c == (CONSFILE *)0)
	return;

    if (exp == (STRING *)0)
	exp = AllocString();

    BuildString((char *)0, exp);

    backslash = 0;
    cntrl = 0;
    while ((s = (*str++)) != '\000') {
	if (octs > 0 && octs < 3 && s >= '0' && s <= '7') {
	    ++octs;
	    oct = oct * 8 + (s - '0');
	    continue;
	}
	if (octs != 0) {
	    BuildStringChar(oct, exp);
	    octs = 0;
	    oct = '\000';
	}
	if (backslash) {
	    backslash = 0;
	    if (s == 'a')
		s = '\a';
	    else if (s == 'b')
		s = '\b';
	    else if (s == 'f')
		s = '\f';
	    else if (s == 'n')
		s = '\n';
	    else if (s == 'r')
		s = '\r';
	    else if (s == 't')
		s = '\t';
	    else if (s == 'v')
		s = '\v';
	    else if (s == '^')
		s = '^';
	    else if (s >= '0' && s <= '7') {
		++octs;
		oct = oct * 8 + (s - '0');
		continue;
	    }
	    BuildStringChar(s, exp);
	    continue;
	}
	if (cntrl) {
	    cntrl = 0;
	    if (s == '?')
		s = 0x7f;	/* delete */
	    else
		s = s & 0x1f;
	    BuildStringChar(s, exp);
	    continue;
	}
	if (s == '\\') {
	    backslash = 1;
	    continue;
	}
	if (s == '^') {
	    cntrl = 1;
	    continue;
	}
	BuildStringChar(s, exp);
    }

    if (octs != 0)
	BuildStringChar(oct, exp);

    if (backslash)
	BuildStringChar('\\', exp);

    if (cntrl)
	BuildStringChar('^', exp);

    if (exp->used > 1)
	FileWrite(c, FLAGFALSE, exp->string, exp->used - 1);
}

void
PrintSubst(CONSFILE *pcf, char *pcMach, char *string, char *subst)
{
    if (string == (char *)0)
	return;

    if (subst != (char *)0) {
	char *str;
	if ((str = StrDup(string)) == (char *)0)
	    OutOfMem();
	substData->data = (void *)config;
	config->console = pcMach;
	ProcessSubst(substData, &str, (char **)0, (char *)0, subst);
	ExpandString(str, pcf);
	free(str);
    } else
	ExpandString(string, pcf);
}

void
Interact(CONSFILE *pcf, char *pcMach)
{
    int i;
    int nc;
    fd_set rmask, wmask;
    int justSuspended = 0;
    static char acMesg[8192];

    /* if this is true, it means we successfully moved to a new console
     * so we need to close the old one.
     */
    if (prevConsole != (CONSFILE *)0) {
	FileClose(&prevConsole);
	PrintSubst(cfstdout, prevName, pTerm->detach, pTerm->detachsubst);
    }
    if (prevName != (char *)0) {
	free(prevName);
	prevName = (char *)0;
    }

    /* this is only true in other parts of the code iff pcf == gotoConsole */
    if (gotoConsole != (CONSFILE *)0) {
	gotoConsole = (CONSFILE *)0;
	FilePrint(cfstdout, FLAGFALSE, "[returning to `%s'", pcMach);
	FileWrite(pcf, FLAGFALSE, "\n", 1);
    }

    PrintSubst(cfstdout, pcMach, pTerm->attach, pTerm->attachsubst);

    C2Raw();

    /* set socket to non-blocking */
    SetFlags(FileFDNum(pcf), O_NONBLOCK, 0);

    /* 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_ZERO(&winit);
    FD_SET(FileFDNum(pcf), &rinit);
    FD_SET(0, &rinit);
    if (maxfd < FileFDNum(pcf) + 1)
	maxfd = FileFDNum(pcf) + 1;
    for (;;) {
	justSuspended = 0;
	if (fSawReapVirt) {
	    fSawReapVirt = 0;
	    ReapVirt();
	}
	/* reset read mask and select on it
	 */
	rmask = rinit;
	wmask = winit;
	if (-1 ==
	    select(maxfd, &rmask, &wmask, (fd_set *)0,
		   (struct timeval *)0)) {
	    if (errno != EINTR) {
		Error("Master(): select(): %s", strerror(errno));
		break;
	    }
	    continue;
	}

	/* anything from execCmd */
	if (execCmdFile != (CONSFILE *)0) {
	    if (FileCanRead(execCmdFile, &rmask, &wmask)) {
		if ((nc =
		     FileRead(execCmdFile, acMesg, sizeof(acMesg))) < 0) {
		    FD_CLR(FileFDNum(execCmdFile), &rinit);
		    FD_CLR(FileFDOutNum(execCmdFile), &winit);
		    FileClose(&execCmdFile);
		    FileSetQuoteIAC(pcf, FLAGFALSE);
		    FilePrint(pcf, FLAGFALSE, "%c%c", OB_IAC, OB_ABRT);
		    FileSetQuoteIAC(pcf, FLAGTRUE);
		} else {
		    if (config->striphigh == FLAGTRUE) {
			for (i = 0; i < nc; ++i)
			    acMesg[i] &= 127;
		    }
		    FileWrite(pcf, FLAGFALSE, acMesg, nc);
		}
	    } else if (!FileBufEmpty(execCmdFile) &&
		       FileCanWrite(execCmdFile, &rmask, &wmask)) {
		CONDDEBUG((1, "Interact(): flushing fd %d",
			   FileFDNum(execCmdFile)));
		if (FileWrite(execCmdFile, FLAGFALSE, (char *)0, 0) < 0) {
		    /* -bryan */
		    break;
		}
	    }
	}

	/* anything from socket? */
	if (FileCanRead(pcf, &rmask, &wmask)) {
	    int l;
	    if ((nc = FileRead(pcf, acMesg, sizeof(acMesg))) < 0) {
		/* if we got an error/eof after returning from suspend */
		if (justSuspended) {
		    fprintf(stderr, "\n");
		    Error("lost connection");
		}
		break;
	    }
	    while ((l = ParseIACBuf(pcf, acMesg, &nc)) >= 0) {
		if (l == 0) {
		    if (execCmdFile == (CONSFILE *)0) {
			if (FileSawQuoteExec(pcf) == FLAGTRUE)
			    DoExec(pcf);
			else if (FileSawQuoteSusp(pcf) == FLAGTRUE) {
			    justSuspended = 1;
#if defined(SIGSTOP)
			    FileWrite(cfstdout, FLAGFALSE, "stop]", 5);
			    C2Cooked();
			    PrintSubst(cfstdout, pcMach, pTerm->detach,
				       pTerm->detachsubst);
			    kill(thepid, SIGSTOP);
			    PrintSubst(cfstdout, pcMach, pTerm->attach,
				       pTerm->attachsubst);
			    C2Raw();
			    FileWrite(cfstdout, FLAGFALSE,
				      "[press any character to continue",
				      32);
#else
			    FileWrite(cfstdout, FLAGFALSE,
				      "stop not supported -- press any character to continue",
				      53);
#endif
			} else if (FileSawQuoteGoto(pcf) == FLAGTRUE) {
			    gotoConsole = pcf;
			    if (gotoName != (char *)0)
				free(gotoName);
			    if ((gotoName = StrDup(pcMach)) == (char *)0)
				OutOfMem();
			    C2Cooked();
			    return;
			}
		    } else {
			if (FileSawQuoteAbrt(pcf) == FLAGTRUE) {
			    FD_CLR(FileFDNum(execCmdFile), &rinit);
			    FD_CLR(FileFDOutNum(execCmdFile), &winit);
			    FileClose(&execCmdFile);
			    kill(execCmdPid, SIGHUP);
			}
		    }
		    continue;
		}
		if (config->striphigh == FLAGTRUE) {
		    for (i = 0; i < l; ++i)
			acMesg[i] &= 127;
		}
		if (execCmdFile != (CONSFILE *)0) {
		    FileWrite(execCmdFile, FLAGFALSE, acMesg, l);
		    if (showExecData)
			FileWrite(cfstdout, FLAGFALSE, acMesg, l);
		} else
		    FileWrite(cfstdout, FLAGFALSE, acMesg, l);
		nc -= l;
		MemMove(acMesg, acMesg + l, nc);
	    }
	} else if (!FileBufEmpty(pcf) && FileCanWrite(pcf, &rmask, &wmask)) {
	    CONDDEBUG((1, "Interact(): flushing fd %d", FileFDNum(pcf)));
	    if (FileWrite(pcf, FLAGFALSE, (char *)0, 0) < 0) {
		/* -bryan */
		break;
	    }
	}

	/* anything from stdin? */
	if (FD_ISSET(0, &rmask)) {
	    if ((nc = read(0, acMesg, sizeof(acMesg))) <= 0) {
		if (screwy)
		    break;
		else {
		    FD_CLR(0, &rinit);
		    continue;
		}
	    }
	    if (execCmdFile == (CONSFILE *)0) {
		if (config->striphigh == FLAGTRUE) {
		    for (i = 0; i < nc; ++i)
			acMesg[i] &= 127;
		}
		FileWrite(pcf, FLAGFALSE, acMesg, nc);
	    } else {
		for (i = 0; i < nc; ++i) {
		    if (acMesg[i] == '\n' || acMesg[i] == '\r')
			FilePrint(cfstdout, FLAGFALSE,
				  "[local command running - pid %lu]\r\n",
				  execCmdPid);
		    else if (acMesg[i] == 0x03) {	/* ctrl-c */
			kill(execCmdPid, SIGHUP);
			FilePrint(cfstdout, FLAGFALSE,
				  "[local command sent SIGHUP - pid %lu]\r\n",
				  execCmdPid);
		    } else if (acMesg[i] == 0x1c) {	/* ctrl-\ */
			kill(execCmdPid, SIGKILL);
			FilePrint(cfstdout, FLAGFALSE,
				  "[local command sent SIGKILL - pid %lu]\r\n",
				  execCmdPid);
		    } else if (acMesg[i] == 'o' || acMesg[i] == 'O') {
			showExecData = !showExecData;
			FilePrint(cfstdout, FLAGFALSE,
				  "[local command data %s]\r\n",
				  showExecData ? "on" : "off");
		    }
		}
	    }
	}
    }

    C2Cooked();

    PrintSubst(cfstdout, pcMach, pTerm->detach, pTerm->detachsubst);

    if (fVerbose)
	printf("Console %s closed.\n", pcMach);
}

/* interact with a group server					(ksb)
 */
void
CallUp(CONSFILE *pcf, char *pcMaster, char *pcMach, char *pcHow,
       char *result)
{
    int fIn = '-';
    char *r = (char *)0;

    if (fVerbose) {
	Msg("%s to %s (on %s)", pcHow, pcMach, pcMaster);
    }
#if !defined(__CYGWIN__)
# if defined(F_SETOWN)
    if (fcntl(FileFDNum(pcf), F_SETOWN, thepid) == -1) {
	Error("fcntl(F_SETOWN,%lu): %d: %s", (unsigned long)thepid,
	      FileFDNum(pcf), strerror(errno));
    }
# else
#  if defined(SIOCSPGRP)
    {
	int iTemp;
	/* on the HP-UX systems if different
	 */
	iTemp = -thepid;
	if (ioctl(FileFDNum(pcf), SIOCSPGRP, &iTemp) == -1) {
	    Error("ioctl(%d,SIOCSPGRP): %s", FileFDNum(pcf),
		  strerror(errno));
	}
    }
#  endif
# endif
#endif
    SimpleSignal(SIGCHLD, FlagReapVirt);

    /* 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).
     */

    /* how did we do, did we get a read-only or read-write?
     */
    if (0 == strcmp(result, "[attached]\r\n")) {
	/* OK -- we are good as gold */
	fIn = 'a';
    } else if (0 == strcmp(result, "[spy]\r\n") ||
	       0 == strcmp(result, "[ok]\r\n") ||
	       0 == strcmp(result, "[read-only -- initializing]\r\n")) {
	/* Humph, someone else is on
	 * or we have an old version of the server (4.X)
	 */
	fIn = 's';
    } else if (0 == strcmp(result, "[console is read-only]\r\n")) {
	fIn = 'r';
    } else if (0 == strcmp(result, "[line to console is down]\r\n")) {
	/* ouch, the machine is down on the server */
	fIn = '-';
	Error("%s is down", pcMach);
	if (fVerbose) {
	    printf("[use `");
	    PutCtlc(chAttn, stdout);
	    PutCtlc(chEsc, stdout);
	    printf("o\' to open console line]\n");
	}
    } else {
	FilePrint(cfstdout, FLAGFALSE, "%s: %s", pcMach, result);
	Bye(EX_UNAVAILABLE);
    }

    /* 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, assume OK
	 * (we'll find out soon enough)
	 */
	FilePrint(pcf, FLAGFALSE, "%c%ce%c%c", DEFATTN, DEFESC, chAttn,
		  chEsc);
	r = ReadReply(pcf, FLAGFALSE);
	if (strncmp(r, "[redef:", 7) != 0) {
	    Error("protocol botch on redef of escape sequence");
	    Bye(EX_UNAVAILABLE);
	}
    }

    /* try to grok the state of the console */
    FilePrint(pcf, FLAGFALSE, "%c%c=", chAttn, chEsc);
    r = ReadReply(pcf, FLAGFALSE);
    if (strncmp(r, "[unknown", 8) != 0 && strncmp(r, "[up]", 4) != 0)
	FileWrite(cfstdout, FLAGFALSE, r, -1);

    /* try to grok the version of the server */
    FilePrint(pcf, FLAGFALSE, "%c%c%c", chAttn, chEsc, 0xD6);
    r = ReadReply(pcf, FLAGFALSE);
    if (strncmp(r, "[unknown", 8) != 0)
	sversion = AtoU(r + 1);

    printf("[Enter `");
    PutCtlc(chAttn, stdout);
    PutCtlc(chEsc, stdout);
    printf("?\' for help]\n");

    /* try and display the MOTD */
    FilePrint(pcf, FLAGFALSE, "%c%cm", chAttn, chEsc);
    r = ReadReply(pcf, FLAGFALSE);
    if (strncmp(r, "[unknown", 8) != 0 &&
	strncmp(r, "[-- MOTD --]", 12) != 0)
	FileWrite(cfstdout, FLAGFALSE, r, -1);

    if (sversion >= 8001014) {
	if (config->playback) {
	    FilePrint(pcf, FLAGFALSE, "%c%cP%hu\r", chAttn, chEsc,
#if defined(TIOCGWINSZ)
		      config->playback == 1 ? ws.ws_row :
#endif
		      config->playback - 1);
	    r = ReadReply(pcf, FLAGFALSE);
	}

	if (config->replay) {
	    FilePrint(pcf, FLAGFALSE, "%c%cR%hu\r", chAttn, chEsc,
#if defined(TIOCGWINSZ)
		      config->replay == 1 ? ws.ws_row :
#endif
		      config->replay - 1);
	    r = ReadReply(pcf, FLAGFALSE);
	}
    }

    FilePrint(pcf, FLAGFALSE, "%c%c;", chAttn, chEsc);
    r = ReadReply(pcf, FLAGFALSE);
    if (strncmp(r, "[unknown", 8) != 0 &&
	strncmp(r, "[connected]", 11) != 0)
	FileWrite(cfstdout, FLAGFALSE, r, -1);

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

    Interact(pcf, pcMach);
}

/* shouldn't need more than 3 levels of commands (but alloc 4 just 'cause)
 * worst case so far: master, groups, broadcast
 *   (cmdarg == broadcast msg)
 *
 * some sample "stacks" of commands:
 *
 * console -q:     master, quit
 * console -Q:     quit
 * console foo:    call, attach            (interact==FLAGTRUE)
 * console -f foo: call, force             (interact==FLAGTRUE)
 * console -w:     master, groups, group
 * console -I:     groups, info
 * console -i foo: call, info              (interact==FLAGFALSE)
 *
 */
char *cmds[4] = { (char *)0, (char *)0, (char *)0, (char *)0 };

char *cmdarg = (char *)0;

/* 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"
 * and send the given command to the group leader at 1782
 * and ask the machine master at mentor for more group leaders
 * and ask the machine master at pop.stat for more group leaders
 */
int
DoCmds(char *master, char *pports, int cmdi)
{
    CONSFILE *pcf;
    char *t;
    char *next;
    char *server;
    unsigned short port;
    char *result = (char *)0;
    int len;
    char *ports;
    char *pcopy;
    char *serverName;
#if HAVE_GSSAPI
    int toksize;
#endif

    if ((pcopy = ports = StrDup(pports)) == (char *)0)
	OutOfMem();

    len = strlen(ports);
    while (len > 0 && (ports[len - 1] == '\r' || ports[len - 1] == '\n'))
	len--;
    ports[len] = '\000';

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

	if ((server = strchr(ports, '@')) != (char *)0) {
	    *server++ = '\000';
	    if (*server == '\000')
		server = master;
	} else
	    server = master;

#if USE_UNIX_DOMAIN_SOCKETS
	serverName = "localhost";
#else
	serverName = server;
#endif

	if (*ports == '\000') {
#if USE_IPV6
	    port = bindPort;
#elif USE_UNIX_DOMAIN_SOCKETS
	    port = 0;
#else
	    port = htons(bindPort);
#endif
	} else if (!isdigit((int)(ports[0]))) {
	    Error("invalid port spec for %s: `%s'", serverName, ports);
	    continue;
	} else {
#if USE_IPV6
	    port = (short)atoi(ports);
#elif USE_UNIX_DOMAIN_SOCKETS
	    port = (short)atoi(ports);
#else
	    port = htons((short)atoi(ports));
#endif
	}

      attemptLogin:
	if ((pcf = GetPort(server, port)) == (CONSFILE *)0)
	    continue;

	FileSetQuoteIAC(pcf, FLAGTRUE);

	t = ReadReply(pcf, FLAGFALSE);
	if (strcmp(t, "ok\r\n") != 0) {
	    FileClose(&pcf);
	    FilePrint(cfstdout, FLAGFALSE, "%s: %s", serverName, t);
	    continue;
	}
#if HAVE_OPENSSL
	if (config->sslenabled == FLAGTRUE) {
	    FileWrite(pcf, FLAGFALSE, "ssl\r\n", 5);
	    t = ReadReply(pcf, FLAGFALSE);
	    if (strcmp(t, "ok\r\n") == 0) {
		AttemptSSL(pcf);
		if (FileGetType(pcf) != SSLSocket) {
		    Error("Encryption not supported by server `%s'",
			  serverName);
		    FileClose(&pcf);
		    continue;
		}
	    } else if (config->sslrequired == FLAGTRUE) {
		Error("Encryption not supported by server `%s'",
		      serverName);
		FileClose(&pcf);
		continue;
	    }
	}
#endif
#if HAVE_GSSAPI
	if ((toksize = CanGetGSSContext(server)) > 0) {
	    FilePrint(pcf, FLAGFALSE, "gssapi %d\r\n", toksize);
	    t = ReadReply(pcf, FLAGFALSE);
	    if (strcmp(t, "ok\r\n") == 0) {
		if (AttemptGSSAPI(pcf)) {
		    goto gssapi_logged_me_in;
		}
	    }
	}
#endif

	FilePrint(pcf, FLAGFALSE, "login %s\r\n", config->username);

	t = ReadReply(pcf, FLAGFALSE);
	if (strncmp(t, "passwd?", 7) == 0) {
	    static int count = 0;
	    static STRING *tmpString = (STRING *)0;
	    char *hostname = (char *)0;

	    if (t[7] == ' ') {
		hostname = PruneSpace(t + 7);
		if (*hostname == '\000')
		    hostname = serverName;
	    } else
		hostname = serverName;
	    if (tmpString == (STRING *)0)
		tmpString = AllocString();
	    if (tmpString->used <= 1) {
		char *pass;
		BuildStringPrint(tmpString, "Enter %s@%s's password: ",
				 config->username, hostname);
		pass = GetPassword(tmpString->string);
		if (pass == (char *)0) {
		    Error("could not get password from tty for `%s'",
			  serverName);
		    FileClose(&pcf);
		    continue;
		}
		BuildString((char *)0, tmpString);
		BuildString(pass, tmpString);
		BuildString("\r\n", tmpString);
	    }
	    FileWrite(pcf, FLAGFALSE, tmpString->string,
		      tmpString->used - 1);
	    t = ReadReply(pcf, FLAGFALSE);
	    if (strcmp(t, "ok\r\n") != 0) {
		FilePrint(cfstdout, FLAGFALSE, "%s: %s", serverName, t);
		if (++count < 3) {
		    BuildString((char *)0, tmpString);
		    goto attemptLogin;
		}
		Error("too many bad passwords for `%s'", serverName);
		count = 0;
		FileClose(&pcf);
		continue;
	    } else
		count = 0;
	} else if (strcmp(t, "ok\r\n") != 0) {
	    FileClose(&pcf);
	    FilePrint(cfstdout, FLAGFALSE, "%s: %s", serverName, t);
	    continue;
	}
#if HAVE_GSSAPI
      gssapi_logged_me_in:
#endif

	/* now that we're logged in, we can do something */
	/* if we're on the last cmd or the command is 'call' and we
	 * have an arg (always true if it's 'call'), then send the arg
	 */
	if ((cmdi == 0 || cmds[cmdi][0] == 'c') && cmdarg != (char *)0)
	    FilePrint(pcf, FLAGFALSE, "%s %s\r\n", cmds[cmdi], cmdarg);
	else
	    FilePrint(pcf, FLAGFALSE, "%s\r\n", cmds[cmdi]);

	/* if we haven't gone down the stack, do "normal" stuff.
	 * if we did hit the bottom, we send the exit\r\n now so
	 * that the ReadReply can stop once the socket closes.
	 */
	if (cmdi != 0) {
	    t = ReadReply(pcf, FLAGFALSE);
	    /* save the result */
	    if (result != (char *)0)
		free(result);
	    if ((result = StrDup(t)) == (char *)0)
		OutOfMem();
	}

	/* if we're working on finding a console */
	if (cmds[cmdi][0] == 'c') {
	    static int limit = 0;
	    /* did we get a redirect? */
	    if (result[0] == '@' || (result[0] >= '0' && result[0] <= '9')) {
		if (limit++ > 10) {
		    Error("forwarding level too deep!");
		    Bye(EX_SOFTWARE);
		}
		FileWrite(pcf, FLAGFALSE, "exit\r\n", 6);
		t = ReadReply(pcf, FLAGTRUE);
	    } else {
		/* if we're not trying to connect to a console */
		if (interact == FLAGFALSE) {
		    FilePrint(cfstdout, FLAGFALSE, "%s: %s", serverName,
			      result);
		    FileClose(&pcf);
		    continue;
		}
		if (result[0] != '[') {	/* did we not get a connection? */
		    int len;

		    limit = 0;
		    FilePrint(cfstdout, FLAGFALSE, "%s: %s", serverName,
			      result);
		    FileWrite(pcf, FLAGFALSE, "exit\r\n", 6);
		    t = ReadReply(pcf, FLAGTRUE);

		    /* strip off the goodbye from the tail of the result */
		    len = strlen(t);
		    if (len > 8 && strcmp("goodbye\r\n", t + len - 9) == 0) {
			*(t + len - 9) = '\000';
		    }

		    FileWrite(cfstdout, FLAGFALSE, t, -1);
		    FileClose(&pcf);
		    continue;
		} else {
		    limit = 0;
		    CallUp(pcf, server, cmdarg, cmds[0], result);
		    if (pcf != gotoConsole)
			FileClose(&pcf);
		    break;
		}
	    }
	} else if (cmds[cmdi][0] == 'q') {
	    if (cmdi == 0) {
		t = ReadReply(pcf, FLAGFALSE);
		FilePrint(cfstdout, FLAGFALSE, "%s: %s", serverName, t);
	    } else {
		FilePrint(cfstdout, FLAGFALSE, "%s: %s", serverName,
			  result);
	    }
	    /* only say 'exit' if 'quit' failed...since it's dying anyway */
	    if (t[0] != 'o' || t[1] != 'k') {
		FileWrite(pcf, FLAGFALSE, "exit\r\n", 6);
		t = ReadReply(pcf, FLAGTRUE);
	    }
	} else {
	    /* all done */
	    /* ok, this is whacky.  if cmdi==0, we haven't read back the
	     * reply yet, so 't' is going to have multiple lines out output
	     * since we send the 'exit' command...first line (or set of
	     * lines) would be the previous command, and then a 'goodbye'
	     * (ideally).  we monkey around below because of this.
	     * like i said.  wacky.
	     */
	    FileWrite(pcf, FLAGFALSE, "exit\r\n", 6);
	    t = ReadReply(pcf, cmdi == 0 ? FLAGTRUE : FLAGFALSE);

	    if (cmdi == 0) {
		int len;
		/* if we hit bottom, this is where we get our results */
		if (result != (char *)0)
		    free(result);
		if ((result = StrDup(t)) == (char *)0)
		    OutOfMem();
		/* strip off the goodbye from the tail of the result */
		len = strlen(result);
		if (len > 8 &&
		    strcmp("goodbye\r\n", result + len - 9) == 0) {
		    len -= 9;
		    *(result + len) = '\000';
		}
		/* if (not 'broadcast' and not 'textmsg') or 
		 *   result doesn't start with 'ok' (only checks this if
		 *      it's a 'broadcast' or 'textmsg')
		 */
		if (cmds[0][0] == 'd') {
		    if (result[0] != 'o' || result[1] != 'k') {
			FileWrite(cfstdout, FLAGTRUE, serverName, -1);
			FileWrite(cfstdout, FLAGTRUE, ": ", 2);
			FileWrite(cfstdout, FLAGFALSE, result, len);
		    } else {
			disconnectCount += atoi(result + 19);
		    }
		} else if ((cmds[0][0] != 'b' && cmds[0][0] != 't') ||
			   (result[0] != 'o' || result[1] != 'k')) {
		    /* did a 'master' before this or doing a 'disconnect',
		     * 'reconfig', 'newlogs', or 'up'
		     */
		    if ((cmds[1] != (char *)0 && cmds[1][0] == 'm') ||
			cmds[0][0] == 'd' || cmds[0][0] == 'r' ||
			cmds[0][0] == 'n' || cmds[0][0] == 'u') {
			FileWrite(cfstdout, FLAGTRUE, serverName, -1);
			FileWrite(cfstdout, FLAGTRUE, ": ", 2);
		    }
		    FileWrite(cfstdout, FLAGFALSE, result, len);
		}
	    }
	}

	FileClose(&pcf);

	/* this would only be true if we got extra redirects (@... above) */
	if (cmds[cmdi][0] == 'c' && interact == FLAGTRUE)
	    DoCmds(server, result, cmdi);
	else if (cmdi > 0)
	    DoCmds(server, result, cmdi - 1);
	if (result != (char *)0)
	    free(result);
	result = (char *)0;
    }

    if (result != (char *)0)
	free(result);
    free(pcopy);
    return 0;
}


/* mainline for console client program					(ksb)
 * setup who we are, and what our loopback addr is
 * parse the cmd line,
 * (optionally) get a shutdown passwd
 * Gather results
 * exit happy or sad
 */
int
main(int argc, char **argv)
{
    char *pcCmd;
    struct passwd *pwdMe = (struct passwd *)0;
    int opt;
    int fLocal;
    static STRING *acPorts = (STRING *)0;
    static char acOpts[] =
	"7aAb:B:c:C:d:De:EfFhiIl:M:np:PqQrRsSt:uUvVwWxz:Z:";
    extern int optind;
    extern int optopt;
    extern char *optarg;
    static STRING *textMsg = (STRING *)0;
    int cmdi;
    static STRING *consoleName = (STRING *)0;
    short readSystemConf = 1;
    char *userConf = (char *)0;
    typedef struct zaps {
	char *opt;
	char *cmd;
	char *desc;
    } ZAPS;
    ZAPS zap[] = {
	{"bringup, SIGUSR1", "up", "bring up any consoles that are down"},
	{"help", (char *)0, "this help message"},
	{"pid", "pid", "display master process ids"},
	{"quit, SIGTERM", "quit", "terminate the server"},
	{"reconfig, SIGHUP", "reconfig",
	 "reread configuration file, then do 'reopen' actions"},
	{"reopen, SIGUSR2", "newlogs",
	 "reopen all logfiles, then do 'bringup' actions"},
	{"version", "version", "display version information"}
    };
    int isZap = 0;

    isMultiProc = 0;		/* make sure stuff DOESN'T have the pid */

    thepid = getpid();

    if (textMsg == (STRING *)0)
	textMsg = AllocString();
    if (acPorts == (STRING *)0)
	acPorts = AllocString();

    if ((char *)0 == (progname = strrchr(argv[0], '/'))) {
	progname = argv[0];
    } else {
	++progname;
    }

    /* prep the config options */
    if ((optConf = (CONFIG *)calloc(1, sizeof(CONFIG))) == (CONFIG *)0)
	OutOfMem();
    if ((config = (CONFIG *)calloc(1, sizeof(CONFIG))) == (CONFIG *)0)
	OutOfMem();
    if ((pConfig = (CONFIG *)calloc(1, sizeof(CONFIG))) == (CONFIG *)0)
	OutOfMem();
    /* and the terminal options */
    if ((pTerm = (TERM *)calloc(1, sizeof(TERM))) == (TERM *)0)
	OutOfMem();

    /* command line parsing
     */
    pcCmd = (char *)0;
    fLocal = 0;
    while ((opt = getopt(argc, argv, acOpts)) != EOF) {
	switch (opt) {
	    case '7':		/* strip high-bit */
		optConf->striphigh = FLAGTRUE;
		break;

	    case 'A':		/* attach with log replay */
		fReplay = 1;
		/* fall through */
	    case 'a':		/* attach */
		pcCmd = "attach";
		break;

	    case 'B':		/* broadcast message */
		fLocal = 1;
		/* fall through */
	    case 'b':
		pcCmd = "broadcast";
		if (cmdarg != (char *)0)
		    free(cmdarg);
		if ((cmdarg = StrDup(optarg)) == (char *)0)
		    OutOfMem();
		break;

	    case 'C':
		userConf = optarg;
		break;

	    case 'c':
#if HAVE_OPENSSL
		if ((optConf->sslcredentials =
		     StrDup(optarg)) == (char *)0)
		    OutOfMem();
#endif
		break;

	    case 'D':
		fDebug++;
		break;

	    case 'd':
		pcCmd = "disconnect";
		if (cmdarg != (char *)0)
		    free(cmdarg);
		if ((cmdarg = StrDup(optarg)) == (char *)0)
		    OutOfMem();
		break;

	    case 'E':
#if HAVE_OPENSSL
		optConf->sslenabled = FLAGFALSE;
#endif
		break;

	    case 'e':		/* set escape chars */
		if ((optConf->escape = StrDup(optarg)) == (char *)0)
		    OutOfMem();
		break;

	    case 'F':		/* force attach with log replay */
		fReplay = 1;
		/* fall through */
	    case 'f':		/* force attach */
		pcCmd = "force";
		break;

	    case 'I':
		fLocal = 1;
		/* fall through */
	    case 'i':
		pcCmd = "info";
		break;

	    case 'l':
		if ((optConf->username = StrDup(optarg)) == (char *)0)
		    OutOfMem();
		break;

	    case 'M':
		if ((optConf->master = StrDup(optarg)) == (char *)0)
		    OutOfMem();
		break;

	    case 'n':
		readSystemConf = 0;
		break;

	    case 'p':
		if ((optConf->port = StrDup(optarg)) == (char *)0)
		    OutOfMem();
		break;

	    case 'P':		/* send a pid command to the server     */
		pcCmd = "pid";
		break;

	    case 'Q':		/* only quit this host          */
		fLocal = 1;
		/*fallthough */
	    case 'q':		/* send quit command to server  */
		pcCmd = "quit";
		break;

	    case 'R':
		fLocal = 1;
		/*fallthrough */
	    case 'r':		/* display daemon version */
		pcCmd = "version";
		break;

	    case 'S':		/* spy with log replay */
		fReplay = 1;
		/* fall through */
	    case 's':		/* spy */
		pcCmd = "spy";
		break;

	    case 't':
		BuildString((char *)0, textMsg);
		if (optarg == (char *)0 || *optarg == '\000') {
		    Error("no destination specified for -t", optarg);
		    Bye(EX_UNAVAILABLE);
		} else if (strchr(optarg, ' ') != (char *)0) {
		    Error("-t option cannot contain a space: `%s'",
			  optarg);
		    Bye(EX_UNAVAILABLE);
		}
		BuildString("textmsg ", textMsg);
		BuildString(optarg, textMsg);
		pcCmd = textMsg->string;
		break;

	    case 'U':
#if HAVE_OPENSSL
		optConf->sslrequired = FLAGFALSE;
#endif
		break;

	    case 'u':
		pcCmd = "hosts";
		break;

	    case 'W':
		fLocal = 1;
		/*fallthrough */
	    case 'w':		/* who */
		pcCmd = "group";
		break;

	    case 'x':
		pcCmd = "examine";
		break;

	    case 'v':
		fVerbose = 1;
		break;

	    case 'V':
		fVersion = 1;
		break;

	    case 'Z':		/* only send cmd this host          */
		fLocal = 1;
		/*fallthough */
	    case 'z':		/* send a command to the server   */
		pcCmd = (char *)0;
		for (isZap = sizeof(zap) / sizeof(ZAPS) - 1; isZap >= 0;
		     isZap--) {
		    char *token = (char *)0;
		    char *str = (char *)0;
		    if (zap[isZap].cmd == (char *)0)	/* skip non-action ones */
			continue;
		    BuildTmpString((char *)0);
		    str = BuildTmpString(zap[isZap].opt);
		    for (token = strtok(str, ", "); token != (char *)0;
			 token = strtok(NULL, ", ")) {
			if (strcasecmp(optarg, token) == 0) {
			    pcCmd = zap[isZap].cmd;
			    isZap++;
			    break;
			}
		    }
		    if (pcCmd)
			break;
		}
		if (isZap < 0) {
		    if (strcasecmp(optarg, "help") == 0) {
			STRING *help;
			help = AllocString();
			BuildString("available -z commands:\n\n", help);
			for (isZap = 0; isZap < sizeof(zap) / sizeof(ZAPS);
			     isZap++) {
			    char *str;
			    BuildTmpString((char *)0);
			    str =
				BuildTmpStringPrint("    %16s   %s\n",
						    zap[isZap].opt,
						    zap[isZap].desc);
			    BuildString(str, help);
			}
			Error(help->string);
		    } else
			Error("invalid -z command: `%s' (try `help')",
			      optarg);
		    Bye(EX_UNAVAILABLE);
		}
		break;

	    case 'h':		/* huh? */
		Usage(1);
		Bye(EX_OK);

	    case '\?':		/* huh? */
		Usage(0);
		Bye(EX_UNAVAILABLE);

	    default:
		Error("option %c needs a parameter", optopt);
		Bye(EX_UNAVAILABLE);
	}
    }

    if (fVersion) {
	Version();
	Bye(EX_OK);
    }
#if !USE_IPV6
    ProbeInterfaces(INADDR_ANY);
#endif

    if (readSystemConf)
	ReadConf(CLIENTCONFIGFILE, FLAGFALSE);

    if (userConf == (char *)0) {
	/* read the config files */
	char *h = (char *)0;

	if (((h = getenv("HOME")) == (char *)0) &&
	    ((pwdMe = getpwuid(getuid())) == (struct passwd *)0)) {
	    Error("$HOME does not exist and getpwuid fails: %d: %s",
		  (int)(getuid()), strerror(errno));
	} else {
	    if (h == (char *)0) {
		if (pwdMe->pw_dir == (char *)0 ||
		    pwdMe->pw_dir[0] == '\000') {
		    Error("Home directory for uid %d is not defined",
			  (int)(getuid()));
		    Bye(EX_UNAVAILABLE);
		} else {
		    h = pwdMe->pw_dir;
		}
	    }
	}
	if (h != (char *)0) {
	    BuildTmpString((char *)0);
	    BuildTmpString(h);
	    h = BuildTmpString("/.consolerc");
	    ReadConf(h, FLAGFALSE);
	    BuildTmpString((char *)0);
	}
    } else
	ReadConf(userConf, FLAGTRUE);

    if (optConf->striphigh != FLAGUNKNOWN)
	config->striphigh = optConf->striphigh;
    else if (pConfig->striphigh != FLAGUNKNOWN)
	config->striphigh = pConfig->striphigh;
    else
	config->striphigh = FLAGFALSE;

    if (optConf->escape != (char *)0)
	ParseEsc(optConf->escape);
    else if (pConfig->escape != (char *)0)
	ParseEsc(pConfig->escape);

    if (optConf->username != (char *)0)
	config->username = StrDup(optConf->username);
    else if (pConfig->username != (char *)0)
	config->username = StrDup(pConfig->username);
    else
	config->username = (char *)0;

    if (optConf->master != (char *)0 && optConf->master[0] != '\000')
	config->master = StrDup(optConf->master);
    else if (pConfig->master != (char *)0 && pConfig->master[0] != '\000')
	config->master = StrDup(pConfig->master);
    else
	config->master = StrDup(
#if USE_UNIX_DOMAIN_SOCKETS
				   UDSDIR
#else
				   MASTERHOST	/* which machine is current */
#endif
	    );
    if (config->master == (char *)0)
	OutOfMem();

    if (optConf->port != (char *)0 && optConf->port[0] != '\000')
	config->port = StrDup(optConf->port);
    else if (pConfig->port != (char *)0 && pConfig->port[0] != '\000')
	config->port = StrDup(pConfig->port);
    else
	config->port = StrDup(DEFPORT);
    if (config->port == (char *)0)
	OutOfMem();

    if (optConf->replay != 0)
	config->replay = optConf->replay;
    else if (pConfig->replay != 0)
	config->replay = pConfig->replay;
    else
	config->replay = 0;

    if (optConf->playback != 0)
	config->playback = optConf->playback;
    else if (pConfig->playback != 0)
	config->playback = pConfig->playback;
    else
	config->playback = 0;

#if HAVE_OPENSSL
    if (optConf->sslcredentials != (char *)0 &&
	optConf->sslcredentials[0] != '\000')
	config->sslcredentials = StrDup(optConf->sslcredentials);
    else if (pConfig->sslcredentials != (char *)0 &&
	     pConfig->sslcredentials[0] != '\000')
	config->sslcredentials = StrDup(pConfig->sslcredentials);
    else
	config->sslcredentials = (char *)0;
    if (pConfig->sslcacertificatefile != (char *)0 &&
	pConfig->sslcacertificatefile[0] != '\000')
	config->sslcacertificatefile =
	    StrDup(pConfig->sslcacertificatefile);
    else
	config->sslcacertificatefile = (char *)0;
    if (pConfig->sslcacertificatepath != (char *)0 &&
	pConfig->sslcacertificatepath[0] != '\000')
	config->sslcacertificatepath =
	    StrDup(pConfig->sslcacertificatepath);
    else
	config->sslcacertificatepath = (char *)0;
    if (optConf->sslenabled != FLAGUNKNOWN)
	config->sslenabled = optConf->sslenabled;
    else if (pConfig->sslenabled != FLAGUNKNOWN)
	config->sslenabled = pConfig->sslenabled;
    else
	config->sslenabled = FLAGTRUE;

    if (optConf->sslrequired != FLAGUNKNOWN)
	config->sslrequired = optConf->sslrequired;
    else if (pConfig->sslrequired != FLAGUNKNOWN)
	config->sslrequired = pConfig->sslrequired;
    else
	config->sslrequired = FLAGTRUE;
#endif

    /* finish resolving the command to do */
    if (pcCmd == (char *)0) {
	pcCmd = "attach";
    }

    if (*pcCmd == 'a' || *pcCmd == 'f' || *pcCmd == 's') {
	/* attach, force-attach, and spy */
	if (optind >= argc) {
	    Error("missing console name");
	    Bye(EX_UNAVAILABLE);
	}
	if (cmdarg != (char *)0)
	    free(cmdarg);
	if ((cmdarg = StrDup(argv[optind++])) == (char *)0)
	    OutOfMem();
    } else if (*pcCmd == 't') {
	/* text message */
	if (optind >= argc) {
	    Error("missing message text");
	    Bye(EX_UNAVAILABLE);
	}
	if (cmdarg != (char *)0)
	    free(cmdarg);
	if ((cmdarg = StrDup(argv[optind++])) == (char *)0)
	    OutOfMem();
    } else if (*pcCmd == 'i' || *pcCmd == 'e' || *pcCmd == 'h' ||
	       *pcCmd == 'g') {
	/* info, e(x)amine, hosts (u), groups (w) */
	if (optind < argc) {
	    if (cmdarg != (char *)0)
		free(cmdarg);
	    if ((cmdarg = StrDup(argv[optind++])) == (char *)0)
		OutOfMem();
	}
    }

    if (optind < argc) {
	Error("extra garbage on command line? (%s...)", argv[optind]);
	Bye(EX_UNAVAILABLE);
    }
#if !USE_UNIX_DOMAIN_SOCKETS
    /* Look for non-numeric characters */
    for (opt = 0; config->port[opt] != '\000'; opt++)
	if (!isdigit((int)config->port[opt]))
	    break;

    if (config->port[opt] == '\000') {
	/* numeric only */
	bindPort = atoi(config->port);
    } else {
	/* non-numeric only */
	struct servent *pSE;
	if ((pSE =
	     getservbyname(config->port, "tcp")) == (struct servent *)0) {
	    Error("getservbyname(%s) failed", config->port);
	    Bye(EX_UNAVAILABLE);
	} else {
	    bindPort = ntohs((unsigned short)pSE->s_port);
	}
    }
#endif

    if (config->username == (char *)0 || config->username[0] == '\000') {
	if (config->username != (char *)0)
	    free(config->username);
	if (((config->username = getenv("LOGNAME")) == (char *)0) &&
	    ((config->username = getenv("USER")) == (char *)0) &&
	    ((pwdMe = getpwuid(getuid())) == (struct passwd *)0)) {
	    Error
		("$LOGNAME and $USER do not exist and getpwuid fails: %d: %s",
		 (int)(getuid()), strerror(errno));
	    Bye(EX_UNAVAILABLE);
	}
	if (config->username == (char *)0) {
	    if (pwdMe->pw_name == (char *)0 || pwdMe->pw_name[0] == '\000') {
		Error("Username for uid %d does not exist",
		      (int)(getuid()));
		Bye(EX_UNAVAILABLE);
	    } else {
		config->username = pwdMe->pw_name;
	    }
	}
	if ((config->username = StrDup(config->username)) == (char *)0)
	    OutOfMem();
    }

    if (execCmd == (STRING *)0)
	execCmd = AllocString();

    SimpleSignal(SIGPIPE, SIG_IGN);

    cfstdout = FileOpenFD(1, simpleFile);

    BuildString((char *)0, acPorts);
    BuildStringChar('@', acPorts);
    BuildString(config->master, acPorts);

#if HAVE_OPENSSL
    SetupSSL();			/* should only do if we want ssl - provide flag! */
#endif

    /* stack up the commands for DoCmds() */
    cmdi = -1;
    cmds[++cmdi] = pcCmd;

    if (*pcCmd == 'q' || *pcCmd == 'v' || *pcCmd == 'p' || *pcCmd == 'r' ||
	isZap) {
	if (!fLocal)
	    cmds[++cmdi] = "master";
    } else if (*pcCmd == 'a' || *pcCmd == 'f' || *pcCmd == 's') {
	ValidateEsc();
	cmds[++cmdi] = "call";
	interact = FLAGTRUE;
    } else if (cmdarg != (char *)0 &&
	       (*pcCmd == 'i' || *pcCmd == 'e' || *pcCmd == 'h' ||
		*pcCmd == 'g')) {
	cmds[++cmdi] = "call";
    } else {
	cmds[++cmdi] = "groups";
	if (!fLocal)
	    cmds[++cmdi] = "master";
    }

#if defined(TIOCGWINSZ)
    if (interact == FLAGTRUE) {
	int fd;
# if HAVE_MEMSET
	memset((void *)(&ws), '\000', sizeof(ws));
# else
	bzero((char *)(&ws), sizeof(ws));
# endif
	if ((fd = open("/dev/tty", O_RDONLY)) != -1) {
	    ioctl(fd, TIOCGWINSZ, &ws);
	}
	close(fd);
    }
#endif

    if (fDebug) {
	int i;
	for (i = cmdi; i >= 0; i--) {
	    CONDDEBUG((1, "cmds[%d] = %s", i, cmds[i]));
	}
    }

    for (;;) {
	if (gotoConsole == (CONSFILE *)0)
	    DoCmds(config->master, acPorts->string, cmdi);
	else
	    Interact(gotoConsole, gotoName);

	/* if we didn't ask for another console, done */
	if (gotoConsole == (CONSFILE *)0 && prevConsole == (CONSFILE *)0)
	    break;

	if (consoleName == (STRING *)0)
	    consoleName = AllocString();
	C2Raw();
	if (prevConsole == (CONSFILE *)0)
	    FileWrite(cfstdout, FLAGFALSE, "console: ", 9);
	else
	    FileWrite(cfstdout, FLAGFALSE, "[console: ", 10);
	GetUserInput(consoleName);
	FileWrite(cfstdout, FLAGFALSE, "]\r\n", 3);
	C2Cooked();
	if (consoleName->used > 1) {
	    if (cmdarg != (char *)0)
		free(cmdarg);
	    if ((cmdarg = StrDup(consoleName->string)) == (char *)0)
		OutOfMem();
	    if (prevConsole == (CONSFILE *)0) {
		prevConsole = gotoConsole;
		gotoConsole = (CONSFILE *)0;
		prevName = gotoName;
		gotoName = (char *)0;
	    }
	} else {
	    if (prevConsole != (CONSFILE *)0) {
		gotoConsole = prevConsole;
		prevConsole = (CONSFILE *)0;
		gotoName = prevName;
		prevName = (char *)0;
	    }
	}
    }

    if (cmdarg != (char *)0)
	free(cmdarg);

    if (*pcCmd == 'd')
	FilePrint(cfstdout, FLAGFALSE, "disconnected %d %s\n",
		  disconnectCount,
		  disconnectCount == 1 ? "user" : "users");

    Bye(0);
    return 0;			/* noop - Bye() terminates us */
}
