mirror of
https://github.com/bstansell/conserver.git
synced 2024-12-20 05:17:54 +00:00
2533 lines
62 KiB
C
2533 lines
62 KiB
C
/*
|
|
* $Id: console.c,v 5.184 2009/09/27 22:32:03 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
|
|
|
|
|
|
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
|
|
#if PROTOTYPES
|
|
SetupSSL(void)
|
|
#else
|
|
SetupSSL()
|
|
#endif
|
|
{
|
|
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->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 {
|
|
ciphers = "ALL:!LOW:!EXP:!MD5:@STRENGTH";
|
|
}
|
|
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
|
|
#if PROTOTYPES
|
|
AttemptSSL(CONSFILE *pcf)
|
|
#else
|
|
AttemptSSL(pcf)
|
|
CONSFILE *pcf;
|
|
#endif
|
|
{
|
|
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
|
|
#if PROTOTYPES
|
|
CanGetGSSContext(const char *servername)
|
|
#else
|
|
CanGetGSSContext(servername)
|
|
const char *servername;
|
|
#endif
|
|
{
|
|
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
|
|
#if PROTOTYPES
|
|
AttemptGSSAPI(CONSFILE *pcf)
|
|
#else
|
|
AttemptGSSAPI(pcf)
|
|
CONSFILE *pcf;
|
|
#endif
|
|
{
|
|
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(NULL, &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
|
|
#if PROTOTYPES
|
|
PutCtlc(int c, FILE *fp)
|
|
#else
|
|
PutCtlc(c, fp)
|
|
int c;
|
|
FILE *fp;
|
|
#endif
|
|
{
|
|
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
|
|
#if PROTOTYPES
|
|
Usage(int wantfull)
|
|
#else
|
|
Usage(wantfull)
|
|
int wantfull;
|
|
#endif
|
|
{
|
|
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
|
|
#if PROTOTYPES
|
|
Version()
|
|
#else
|
|
Version()
|
|
#endif
|
|
{
|
|
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 HAVE_PAM
|
|
"pam",
|
|
#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
|
|
#if PROTOTYPES
|
|
ParseChar(char **ppcSrc, char *pcOut)
|
|
#else
|
|
ParseChar(ppcSrc, pcOut)
|
|
char **ppcSrc, *pcOut;
|
|
#endif
|
|
{
|
|
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
|
|
#if PROTOTYPES
|
|
ValidateEsc()
|
|
#else
|
|
ValidateEsc()
|
|
#endif
|
|
{
|
|
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
|
|
#if PROTOTYPES
|
|
ParseEsc(char *pcText)
|
|
#else
|
|
ParseEsc(pcText)
|
|
char *pcText;
|
|
#endif
|
|
{
|
|
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 *
|
|
#if PROTOTYPES
|
|
GetPort(char *pcToHost, unsigned short sPort)
|
|
#else
|
|
GetPort(pcToHost, sPort)
|
|
char *pcToHost;
|
|
unsigned short sPort;
|
|
#endif
|
|
{
|
|
int s;
|
|
#if 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_MEMSET
|
|
memset((void *)(&port), '\000', sizeof(port));
|
|
#else
|
|
bzero((char *)(&port), sizeof(port));
|
|
#endif
|
|
|
|
#if 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 (connect(s, (struct sockaddr *)(&port), sizeof(port)) < 0) {
|
|
Error("connect(): %hu@%s: %s", ntohs(port.sin_port), pcToHost,
|
|
strerror(errno));
|
|
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
|
|
#if PROTOTYPES
|
|
C2Raw()
|
|
#else
|
|
C2Raw()
|
|
#endif
|
|
{
|
|
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
|
|
#if PROTOTYPES
|
|
C2Cooked()
|
|
#else
|
|
C2Cooked()
|
|
#endif
|
|
{
|
|
if (!screwy)
|
|
return;
|
|
tcsetattr(0, TCSANOW, &o_tios);
|
|
screwy = 0;
|
|
}
|
|
|
|
void
|
|
#if PROTOTYPES
|
|
DestroyDataStructures(void)
|
|
#else
|
|
DestroyDataStructures()
|
|
#endif
|
|
{
|
|
C2Cooked();
|
|
if (cfstdout != (CONSFILE *)0)
|
|
FileUnopen(cfstdout);
|
|
DestroyConfig(pConfig);
|
|
DestroyConfig(optConf);
|
|
DestroyConfig(config);
|
|
DestroyTerminal(pTerm);
|
|
if (myAddrs != (struct in_addr *)0)
|
|
free(myAddrs);
|
|
DestroyStrings();
|
|
if (substData != (SUBST *)0)
|
|
free(substData);
|
|
}
|
|
|
|
char *
|
|
#if PROTOTYPES
|
|
ReadReply(CONSFILE *fd, FLAG toEOF)
|
|
#else
|
|
ReadReply(fd, toEOF)
|
|
CONSFILE *fd;
|
|
FLAG toEOF;
|
|
#endif
|
|
{
|
|
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
|
|
#if PROTOTYPES
|
|
ReapVirt(void)
|
|
#else
|
|
ReapVirt()
|
|
#endif
|
|
{
|
|
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
|
|
#if PROTOTYPES
|
|
FlagReapVirt(int sig)
|
|
#else
|
|
FlagReapVirt(sig)
|
|
int sig;
|
|
#endif
|
|
{
|
|
fSawReapVirt = 1;
|
|
#if !HAVE_SIGACTION
|
|
SimpleSignal(SIGCHLD, FlagReapVirt);
|
|
#endif
|
|
}
|
|
|
|
/* invoke the execcmd command */
|
|
void
|
|
#if PROTOTYPES
|
|
ExecCmd(void)
|
|
#else
|
|
ExecCmd()
|
|
#endif
|
|
{
|
|
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
|
|
#if PROTOTYPES
|
|
GetUserInput(STRING *str)
|
|
#else
|
|
GetUserInput(str)
|
|
STRING *str;
|
|
#endif
|
|
{
|
|
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
|
|
#if PROTOTYPES
|
|
DoExec(CONSFILE *pcf)
|
|
#else
|
|
DoExec(pcf)
|
|
CONSFILE *pcf;
|
|
#endif
|
|
{
|
|
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
|
|
#if PROTOTYPES
|
|
ExpandString(char *str, CONSFILE *c)
|
|
#else
|
|
ExpandString(str, c)
|
|
char *str;
|
|
CONSFILE *c;
|
|
#endif
|
|
{
|
|
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
|
|
#if PROTOTYPES
|
|
PrintSubst(CONSFILE *pcf, char *pcMach, char *string, char *subst)
|
|
#else
|
|
PrintSubst(pcf, pcMach, string, subst)
|
|
CONSFILE *pcf;
|
|
char *pcMach;
|
|
char *string;
|
|
char *subst;
|
|
#endif
|
|
{
|
|
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
|
|
#if PROTOTYPES
|
|
Interact(CONSFILE *pcf, char *pcMach)
|
|
#else
|
|
Interact(pcf, pcMach)
|
|
CONSFILE *pcf;
|
|
char *pcMach;
|
|
#endif
|
|
{
|
|
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_SET(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
|
|
#if PROTOTYPES
|
|
CallUp(CONSFILE *pcf, char *pcMaster, char *pcMach, char *pcHow,
|
|
char *result)
|
|
#else
|
|
CallUp(pcf, pcMaster, pcMach, pcHow, result)
|
|
CONSFILE *pcf;
|
|
char *pcMaster, *pcMach, *pcHow, *result;
|
|
#endif
|
|
{
|
|
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
|
|
#if PROTOTYPES
|
|
DoCmds(char *master, char *pports, int cmdi)
|
|
#else
|
|
DoCmds(master, pports, cmdi)
|
|
char *master;
|
|
char *pports;
|
|
int cmdi;
|
|
#endif
|
|
{
|
|
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_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_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
|
|
#if PROTOTYPES
|
|
main(int argc, char **argv)
|
|
#else
|
|
main(argc, argv)
|
|
int argc;
|
|
char **argv;
|
|
#endif
|
|
{
|
|
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);
|
|
}
|
|
|
|
ProbeInterfaces(INADDR_ANY);
|
|
|
|
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 (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 */
|
|
}
|