1 /*
2  *  Copyright conserver.com, 2000
3  *
4  *  Maintainer/Enhancer: Bryan Stansell (bryan@conserver.com)
5  *
6  *  Copyright GNAC, Inc., 1998
7  */
8 
9 /*
10  * Copyright (c) 1990 The Ohio State University.
11  * All rights reserved.
12  *
13  * Redistribution and use in source and binary forms are permitted
14  * provided that: (1) source distributions retain this entire copyright
15  * notice and comment, and (2) distributions including binaries display
16  * the following acknowledgement:  ``This product includes software
17  * developed by The Ohio State University and its contributors''
18  * in the documentation or other materials provided with the distribution
19  * and in all advertising materials mentioning features or use of this
20  * software. Neither the name of the University nor the names of its
21  * contributors may be used to endorse or promote products derived
22  * from this software without specific prior written permission.
23  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
24  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
25  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
26  */
27 
28 #include <compat.h>
29 
30 #include <pwd.h>
31 
32 #include <getpassword.h>
33 #include <cutil.h>
34 #include <readconf.h>
35 #include <version.h>
36 #if HAVE_OPENSSL
37 # include <openssl/opensslv.h>
38 #endif
39 #if HAVE_GSSAPI
40 # include <gssapi/gssapi.h>
41 #endif
42 #if USE_IPV6
43 # include <sys/socket.h>
44 # include <netdb.h>
45 #endif
46 
47 
48 int fReplay = 0, fVersion = 0;
49 int showExecData = 1;
50 int chAttn = -1, chEsc = -1;
51 unsigned short bindPort;
52 CONSFILE *cfstdout;
53 int disconnectCount = 0;
54 STRING *execCmd = (STRING *)0;
55 CONSFILE *execCmdFile = (CONSFILE *)0;
56 pid_t execCmdPid = 0;
57 CONSFILE *gotoConsole = (CONSFILE *)0;
58 CONSFILE *prevConsole = (CONSFILE *)0;
59 char *gotoName = (char *)0;
60 char *prevName = (char *)0;
61 CONFIG *optConf = (CONFIG *)0;
62 CONFIG *config = (CONFIG *)0;
63 FLAG interact = FLAGFALSE;
64 unsigned int sversion = 0;
65 #if defined(TIOCGWINSZ)
66 struct winsize ws;
67 #endif
68 
69 #if HAVE_OPENSSL
70 SSL_CTX *ctx = (SSL_CTX *)0;
71 
72 void
SetupSSL(void)73 SetupSSL(void)
74 {
75     if (ctx == (SSL_CTX *)0) {
76 	char *ciphers;
77 # if OPENSSL_VERSION_NUMBER < 0x10100000L
78 	SSL_load_error_strings();
79 	if (!SSL_library_init()) {
80 	    Error("SSL library initialization failed");
81 	    Bye(EX_UNAVAILABLE);
82 	}
83 # endif/* OPENSSL_VERSION_NUMBER < 0x10100000L */
84 	if ((ctx = SSL_CTX_new(TLS_method())) == (SSL_CTX *)0) {
85 	    Error("Creating SSL context failed");
86 	    Bye(EX_UNAVAILABLE);
87 	}
88 	if (SSL_CTX_set_default_verify_paths(ctx) != 1) {
89 	    Error("Could not load SSL default CA file and/or directory");
90 	    Bye(EX_UNAVAILABLE);
91 	}
92 	if (config->sslcacertificatefile != (char *)0 ||
93 	    config->sslcacertificatepath != (char *)0) {
94 	    if (SSL_CTX_load_verify_locations
95 		(ctx, config->sslcacertificatefile,
96 		 config->sslcacertificatepath) != 1) {
97 		if (config->sslcacertificatefile != (char *)0)
98 		    Error("Could not setup ca certificate file to '%s'",
99 			  config->sslcacertificatefile);
100 		if (config->sslcacertificatepath != (char *)0)
101 		    Error("Could not setup ca certificate path to '%s'",
102 			  config->sslcacertificatepath);
103 		Bye(EX_UNAVAILABLE);
104 	    }
105 	}
106 	if (config->sslcredentials != (char *)0) {
107 	    if (SSL_CTX_use_certificate_chain_file
108 		(ctx, config->sslcredentials) != 1) {
109 		Error("Could not load SSL certificate from '%s'",
110 		      config->sslcredentials);
111 		Bye(EX_UNAVAILABLE);
112 	    }
113 	    if (SSL_CTX_use_PrivateKey_file
114 		(ctx, config->sslcredentials, SSL_FILETYPE_PEM) != 1) {
115 		Error("Could not SSL private key from '%s'",
116 		      config->sslcredentials);
117 		Bye(EX_UNAVAILABLE);
118 	    }
119 	    ciphers = "ALL:!LOW:!EXP:!MD5:!aNULL:@STRENGTH";
120 	} else {
121 # if defined(REQ_SERVER_CERT)
122 	    ciphers = "ALL:!LOW:!EXP:!MD5:!aNULL:@STRENGTH";
123 # else
124 	    ciphers = "ALL:aNULL:!LOW:!EXP:!MD5:@STRENGTH" CIPHER_SEC0;
125 # endif
126 	}
127 	SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, SSLVerifyCallback);
128 	SSL_CTX_set_options(ctx,
129 			    SSL_OP_ALL | SSL_OP_NO_SSLv2 |
130 			    SSL_OP_SINGLE_DH_USE);
131 	SSL_CTX_set_mode(ctx,
132 			 SSL_MODE_ENABLE_PARTIAL_WRITE |
133 			 SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER |
134 			 SSL_MODE_AUTO_RETRY);
135 	if (SSL_CTX_set_cipher_list(ctx, ciphers) != 1) {
136 	    Error("Setting SSL cipher list failed");
137 	    Bye(EX_UNAVAILABLE);
138 	}
139     }
140 }
141 
142 void
AttemptSSL(CONSFILE * pcf)143 AttemptSSL(CONSFILE *pcf)
144 {
145     SSL *ssl;
146 
147     if (ctx == (SSL_CTX *)0) {
148 	Error("WTF?  The SSL context disappeared?!?!?");
149 	Bye(EX_UNAVAILABLE);
150     }
151     if (!(ssl = SSL_new(ctx))) {
152 	Error("Couldn't create new SSL context");
153 	Bye(EX_UNAVAILABLE);
154     }
155     FileSetSSL(pcf, ssl);
156     SSL_set_fd(ssl, FileFDNum(pcf));
157     CONDDEBUG((1, "About to SSL_connect() on fd %d", FileFDNum(pcf)));
158     if (SSL_connect(ssl) <= 0) {
159 	Error("SSL negotiation failed");
160 	ERR_print_errors_fp(stderr);
161 	Bye(EX_UNAVAILABLE);
162     }
163     FileSetType(pcf, SSLSocket);
164     CONDDEBUG((1, "SSL Connection: %s :: %s", SSL_get_cipher_version(ssl),
165 	       SSL_get_cipher_name(ssl)));
166 }
167 #endif
168 
169 #if HAVE_GSSAPI
170 gss_name_t gss_server_name = GSS_C_NO_NAME;
171 gss_ctx_id_t secctx = GSS_C_NO_CONTEXT;
172 gss_buffer_desc mytok = GSS_C_EMPTY_BUFFER;
173 
174 int
CanGetGSSContext(const char * servername)175 CanGetGSSContext(const char *servername)
176 {
177     char namestr[128];
178     gss_buffer_desc namebuf, dbuf;
179     OM_uint32 stmaj, stmin, mctx, dmin;
180 
181     snprintf(namestr, 128, "host@%s", servername);
182     namebuf.value = namestr;
183     namebuf.length = strlen(namestr) + 1;
184     stmaj =
185 	gss_import_name(&stmin, &namebuf, GSS_C_NT_HOSTBASED_SERVICE,
186 			&gss_server_name);
187     /* XXX: handle error */
188     if (stmaj != GSS_S_COMPLETE) {
189 	Error("gss_import_name failed");
190 	return 0;
191     }
192     secctx = GSS_C_NO_CONTEXT;
193     mytok.length = 0;
194     mytok.value = NULL;
195 
196     stmaj =
197 	gss_init_sec_context(&stmin, GSS_C_NO_CREDENTIAL, &secctx,
198 			     gss_server_name, GSS_C_NULL_OID,
199 			     GSS_C_MUTUAL_FLAG, 0,
200 			     GSS_C_NO_CHANNEL_BINDINGS, NULL, NULL, &mytok,
201 			     NULL, NULL);
202 
203     if (stmaj != GSS_S_COMPLETE && stmaj != GSS_S_CONTINUE_NEEDED) {
204 	gss_release_name(&stmin, &gss_server_name);
205 	return 0;
206     }
207     return mytok.length;
208 }
209 
210 int
AttemptGSSAPI(CONSFILE * pcf)211 AttemptGSSAPI(CONSFILE *pcf)
212 {
213     OM_uint32 stmaj, stmin;
214     gss_buffer_desc servertok;
215     char buf[1024];
216     int nr;
217     int ret;
218 
219     FileSetQuoteIAC(pcf, FLAGFALSE);
220     FileWrite(pcf, FLAGFALSE, mytok.value, mytok.length);
221     FileSetQuoteIAC(pcf, FLAGTRUE);
222     nr = FileRead(pcf, buf, sizeof(buf));
223     servertok.length = nr;
224     servertok.value = buf;
225 
226     stmaj =
227 	gss_init_sec_context(&stmin, GSS_C_NO_CREDENTIAL, &secctx,
228 			     gss_server_name, GSS_C_NULL_OID,
229 			     GSS_C_MUTUAL_FLAG, 0,
230 			     GSS_C_NO_CHANNEL_BINDINGS, &servertok, NULL,
231 			     &mytok, NULL, NULL);
232     gss_release_buffer(&stmin, &mytok);
233 
234     ret = (stmaj == GSS_S_COMPLETE);
235     gss_release_name(&stmin, &gss_server_name);
236     return ret;
237 }
238 #endif
239 
240 /* output a control (or plain) character as a UNIX user would expect it	(ksb)
241  */
242 static void
PutCtlc(int c,FILE * fp)243 PutCtlc(int c, FILE *fp)
244 {
245     if (0 != (0200 & c)) {
246 	putc('M', fp);
247 	putc('-', fp);
248 	c &= ~0200;
249     }
250     if (isprint(c)) {
251 	putc(c, fp);
252 	return;
253     }
254     putc('^', fp);
255     if (c == 0177) {
256 	putc('?', fp);
257 	return;
258     }
259     putc(c + 0100, fp);
260 }
261 
262 /* output a long message to the user
263  */
264 static void
Usage(int wantfull)265 Usage(int wantfull)
266 {
267     static char *full[] = {
268 	"7         strip the high bit off all console data",
269 	"a(A)      attach politely (and replay last 20 lines)",
270 	"b(B)      send broadcast message to all users (on master)",
271 #if HAVE_OPENSSL
272 	"c cred    load an SSL certificate and key from the PEM encoded file",
273 #else
274 	"c cred    ignored - encryption not compiled into code",
275 #endif
276 	"C config  override per-user config file",
277 	"d         disconnect [user][@console]",
278 	"D         enable debug output, sent to stderr",
279 	"e esc     set the initial escape characters",
280 #if HAVE_OPENSSL
281 	"E         don't attempt encrypted connections",
282 #else
283 	"E         ignored - encryption not compiled into code",
284 #endif
285 	"f(F)      force read/write connection (and replay)",
286 	"h         output this message",
287 	"i(I)      display status info in machine-parseable form (on master)",
288 	"l user    use username instead of current username",
289 	"M master  master server to poll first",
290 	"n         do not read system-wide config file",
291 	"p port    port to connect to",
292 	"P         display pids of daemon(s)",
293 	"q(Q)      send a quit command to the (master) server",
294 	"r(R)      display (master) daemon version (think 'r'emote version)",
295 	"s(S)      spy on a console (and replay)",
296 	"t         send a text message to [user][@console]",
297 	"u         show users on the various consoles",
298 #if HAVE_OPENSSL
299 	"U         allow unencrypted connections if SSL not available",
300 #else
301 	"U         ignored - encryption not compiled into code",
302 #endif
303 	"v         be more verbose",
304 	"V         show version information",
305 	"w(W)      show who is on which console (on master)",
306 	"x         examine ports and baud rates",
307 	"z(Z) cmd  send a command to the (master) server (think 'z'ap)",
308 	(char *)0
309     };
310 
311     fprintf(stderr, "usage: %s [generic-args] [-aAfFsS] [-e esc] console\n\
312        %s [generic-args] [-iIuwWx] [console]\n\
313        %s [generic-args] [-hPqQrRV] [-[bB] message] [-d [user][@console]]\n\
314                               [-t [user][@console] message] [-[zZ] cmd]\n\n\
315        generic-args: [-7DEnUv] [-c cred] [-C config] [-M master]\n\
316                      [-p port] [-l username]\n", progname, progname, progname);
317 
318     if (wantfull) {
319 	int i;
320 	fprintf(stderr, "\n");
321 	for (i = 0; full[i] != (char *)0; i++)
322 	    fprintf(stderr, "\t%s\n", full[i]);
323     }
324 }
325 
326 /* expain who we are and which revision we are				(ksb)
327  */
328 static void
Version(void)329 Version(void)
330 {
331     int i;
332     static STRING *acA1 = (STRING *)0;
333     static STRING *acA2 = (STRING *)0;
334     char *optionlist[] = {
335 #if HAVE_DMALLOC
336 	"dmalloc",
337 #endif
338 #if USE_LIBWRAP
339 	"libwrap",
340 #endif
341 #if HAVE_OPENSSL
342 	"openssl",
343 #endif
344 #if HAVE_GSSAPI
345 	"gssapi",
346 #endif
347 #if USE_UNIX_DOMAIN_SOCKETS
348 	"uds",
349 #endif
350 	(char *)0
351     };
352 
353     if (acA1 == (STRING *)0)
354 	acA1 = AllocString();
355     if (acA2 == (STRING *)0)
356 	acA2 = AllocString();
357 
358     Msg(MyVersion());
359 #if USE_UNIX_DOMAIN_SOCKETS
360     Msg("default socket directory `%s'", UDSDIR);
361 #else
362     Msg("default initial master server `%s'", MASTERHOST);
363     Msg("default port referenced as `%s'", DEFPORT);
364 #endif
365     Msg("default escape sequence `%s%s'", FmtCtl(DEFATTN, acA1),
366 	FmtCtl(DEFESC, acA2));
367     Msg("default site-wide configuration in `%s'", CLIENTCONFIGFILE);
368     Msg("default per-user configuration in `%s'", "$HOME/.consolerc");
369 
370     BuildString((char *)0, acA1);
371     if (optionlist[0] == (char *)0)
372 	BuildString("none", acA1);
373     for (i = 0; optionlist[i] != (char *)0; i++) {
374 	if (i == 0)
375 	    BuildString(optionlist[i], acA1);
376 	else {
377 	    BuildString(", ", acA1);
378 	    BuildString(optionlist[i], acA1);
379 	}
380     }
381     Msg("options: %s", acA1->string);
382 #if HAVE_DMALLOC
383     BuildString((char *)0, acA1);
384     BuildStringChar('0' + DMALLOC_VERSION_MAJOR, acA1);
385     BuildStringChar('.', acA1);
386     BuildStringChar('0' + DMALLOC_VERSION_MINOR, acA1);
387     BuildStringChar('.', acA1);
388     BuildStringChar('0' + DMALLOC_VERSION_PATCH, acA1);
389 # if defined(DMALLOC_VERSION_BETA)
390     if (DMALLOC_VERSION_BETA != 0) {
391 	BuildString("-b", acA1);
392 	BuildStringChar('0' + DMALLOC_VERSION_BETA, acA1);
393     }
394 # endif
395     Msg("dmalloc version: %s", acA1->string);
396 #endif
397 #if HAVE_OPENSSL
398     Msg("openssl version: %s", OPENSSL_VERSION_TEXT);
399 #endif
400     Msg("built with `%s'", CONFIGINVOCATION);
401     if (fVerbose)
402 	printf(COPYRIGHT);
403 }
404 
405 
406 /* convert text to control chars, we take `cat -v' style		(ksb)
407  *	^X (or ^x)		contro-x
408  *	M-x			x plus 8th bit
409  *	c			a plain character
410  */
411 static int
ParseChar(char ** ppcSrc,char * pcOut)412 ParseChar(char **ppcSrc, char *pcOut)
413 {
414     int cvt, n;
415     char *pcScan = *ppcSrc;
416 
417     if ('M' == pcScan[0] && '-' == pcScan[1] && '\000' != pcScan[2]) {
418 	cvt = 0x80;
419 	pcScan += 2;
420     } else {
421 	cvt = 0;
422     }
423 
424     if ('\000' == *pcScan) {
425 	return 1;
426     }
427 
428     if ('^' == (n = *pcScan++)) {
429 	if ('\000' == (n = *pcScan++)) {
430 	    return 1;
431 	}
432 	if (islower(n)) {
433 	    n = toupper(n);
434 	}
435 	if ('@' <= n && n <= '_') {
436 	    cvt |= n - '@';
437 	} else if ('?' == *pcScan) {
438 	    cvt |= '\177';
439 	} else {
440 	    return 1;
441 	}
442     } else {
443 	cvt |= n;
444     }
445 
446     if ((char *)0 != pcOut) {
447 	*pcOut = cvt;
448     }
449     *ppcSrc = pcScan;
450     return 0;
451 }
452 
453 /*
454  */
455 static void
ValidateEsc(void)456 ValidateEsc(void)
457 {
458     unsigned char c1, c2;
459 
460     if (config->striphigh != FLAGTRUE)
461 	return;
462 
463     if (chAttn == -1 || chEsc == -1) {
464 	c1 = DEFATTN;
465 	c2 = DEFESC;
466     } else {
467 	c1 = chAttn;
468 	c2 = chEsc;
469     }
470     if (c1 > 127 || c2 > 127) {
471 	Error("High-bit set in escape sequence: not allowed with -7");
472 	Bye(EX_UNAVAILABLE);
473     }
474 }
475 
476 /* find the two characters that makeup the users escape sequence	(ksb)
477  */
478 static void
ParseEsc(char * pcText)479 ParseEsc(char *pcText)
480 {
481     char *pcTemp;
482     char c1, c2;
483 
484     pcTemp = pcText;
485     if (ParseChar(&pcTemp, &c1) || ParseChar(&pcTemp, &c2)) {
486 	Error("poorly formed escape sequence `%s\'", pcText);
487 	Bye(EX_UNAVAILABLE);
488     }
489     if ('\000' != *pcTemp) {
490 	Error("too many characters in new escape sequence at ...`%s\'",
491 	      pcTemp);
492 	Bye(EX_UNAVAILABLE);
493     }
494     chAttn = c1;
495     chEsc = c2;
496 }
497 
498 
499 /* set the port for socket connection					(ksb)
500  * return the fd for the new connection; if we can use the loopback, do
501  * as a side effect we set ThisHost to a short name for this host
502  */
503 CONSFILE *
GetPort(char * pcToHost,unsigned short sPort)504 GetPort(char *pcToHost, unsigned short sPort)
505 {
506     int s;
507 #if USE_IPV6
508     int error;
509     char host[NI_MAXHOST];
510     char serv[NI_MAXSERV];
511     struct addrinfo *ai, *rp, hints;
512 #elif USE_UNIX_DOMAIN_SOCKETS
513     struct sockaddr_un port;
514     static STRING *portPath = (STRING *)0;
515 #else
516     struct hostent *hp = (struct hostent *)0;
517     struct sockaddr_in port;
518 #endif
519 #if HAVE_SETSOCKOPT
520     int one = 1;
521 #endif
522 
523 #if USE_IPV6
524 # if HAVE_MEMSET
525     memset(&hints, 0, sizeof(hints));
526 # else
527     bzero(&hints, sizeof(hints));
528 # endif
529 #else
530 # if HAVE_MEMSET
531     memset((void *)(&port), '\000', sizeof(port));
532 # else
533     bzero((char *)(&port), sizeof(port));
534 # endif
535 #endif
536 
537 #if USE_IPV6
538     hints.ai_flags = AI_ADDRCONFIG;
539     hints.ai_socktype = SOCK_STREAM;
540     snprintf(serv, sizeof(serv), "%hu", sPort);
541 
542     error = getaddrinfo(pcToHost, serv, &hints, &ai);
543     if (error) {
544 	Error("getaddrinfo(%s): %s", pcToHost, gai_strerror(error));
545 	return (CONSFILE *)0;
546     }
547 
548     rp = ai;
549     while (rp) {
550 	error =
551 	    getnameinfo(rp->ai_addr, rp->ai_addrlen, host, sizeof(host),
552 			serv, sizeof(serv),
553 			NI_NUMERICHOST | NI_NUMERICSERV);
554 	if (error) {
555 	    continue;
556 	}
557 	CONDDEBUG((1, "GetPort: hostname=%s, ip=%s, port=%s", pcToHost,
558 		   host, serv));
559 
560 	/* set up the socket to talk to the server for all consoles
561 	 * (it will tell us who to talk to to get a real connection)
562 	 */
563 	s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
564 	if (s != -1) {
565 # if HAVE_SETSOCKOPT
566 	    if (setsockopt
567 		(s, SOL_SOCKET, SO_KEEPALIVE, (char *)&one,
568 		 sizeof(one)) < 0)
569 		goto fail;
570 # endif
571 	    if (connect(s, rp->ai_addr, rp->ai_addrlen) == 0)
572 		goto success;
573 	  fail:
574 	    close(s);
575 	}
576 	rp = rp->ai_next;
577     }
578     Error("Unable to connect to %s:%s", host, serv);
579     return (CONSFILE *)0;
580   success:
581     freeaddrinfo(ai);
582 #elif USE_UNIX_DOMAIN_SOCKETS
583     if (portPath == (STRING *)0)
584 	portPath = AllocString();
585     BuildStringPrint(portPath, "%s/%hu", config->master, sPort);
586     port.sun_family = AF_UNIX;
587     if (portPath->used > sizeof(port.sun_path)) {
588 	Error("GetPort: path to socket too long: %s", portPath->string);
589 	return (CONSFILE *)0;
590     }
591     StrCpy(port.sun_path, portPath->string, sizeof(port.sun_path));
592 
593     CONDDEBUG((1, "GetPort: socket=%s", port.sun_path));
594 
595     /* set up the socket to talk to the server for all consoles
596      * (it will tell us who to talk to to get a real connection)
597      */
598     if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
599 	Error("socket(AF_UNIX,SOCK_STREAM): %s", strerror(errno));
600 	return (CONSFILE *)0;
601     }
602 
603     if (connect(s, (struct sockaddr *)(&port), sizeof(port)) < 0) {
604 	Error("connect(): %s: %s", port.sun_path, strerror(errno));
605 	return (CONSFILE *)0;
606     }
607 #else
608 # if HAVE_INET_ATON
609     if (inet_aton(pcToHost, &(port.sin_addr)) == 0)
610 # else
611     port.sin_addr.s_addr = inet_addr(pcToHost);
612     if ((in_addr_t) (-1) == port.sin_addr.s_addr)
613 # endif
614     {
615 	if ((struct hostent *)0 != (hp = gethostbyname(pcToHost))) {
616 # if HAVE_MEMCPY
617 	    memcpy((char *)&port.sin_addr.s_addr, (char *)hp->h_addr,
618 		   hp->h_length);
619 # else
620 	    bcopy((char *)hp->h_addr, (char *)&port.sin_addr.s_addr,
621 		  hp->h_length);
622 # endif
623 	} else {
624 	    Error("gethostbyname(%s): %s", pcToHost, hstrerror(h_errno));
625 	    return (CONSFILE *)0;
626 	}
627     }
628     port.sin_port = sPort;
629     port.sin_family = AF_INET;
630 
631     if (fDebug) {
632 	if ((struct hostent *)0 != hp && (char *)0 != hp->h_name) {
633 	    CONDDEBUG((1, "GetPort: hostname=%s (%s), ip=%s, port=%hu",
634 		       hp->h_name, pcToHost, inet_ntoa(port.sin_addr),
635 		       ntohs(sPort)));
636 	} else {
637 	    CONDDEBUG((1,
638 		       "GetPort: hostname=<unresolved> (%s), ip=%s, port=%hu",
639 		       pcToHost, inet_ntoa(port.sin_addr), ntohs(sPort)));
640 	}
641     }
642 
643     /* set up the socket to talk to the server for all consoles
644      * (it will tell us who to talk to to get a real connection)
645      */
646     if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
647 	Error("socket(AF_INET,SOCK_STREAM): %s", strerror(errno));
648 	return (CONSFILE *)0;
649     }
650 # if HAVE_SETSOCKOPT
651     if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof(one))
652 	< 0) {
653 	Error("setsockopt(SO_KEEPALIVE): %s", strerror(errno));
654 	close(s);
655 	return (CONSFILE *)0;
656     }
657 # endif
658 
659     if (connect(s, (struct sockaddr *)(&port), sizeof(port)) < 0) {
660 	Error("connect(): %hu@%s: %s", ntohs(port.sin_port), pcToHost,
661 	      strerror(errno));
662 	close(s);
663 	return (CONSFILE *)0;
664     }
665 #endif
666 
667     return FileOpenFD(s, simpleSocket);
668 }
669 
670 
671 /* the next two routines assure that the users tty is in the
672  * correct mode for us to do our thing
673  */
674 static int screwy = 0;
675 static struct termios o_tios;
676 
677 
678 /*
679  * show characters that are already tty processed,
680  * and read characters before cononical processing
681  * we really use cbreak at PUCC because we need even parity...
682  */
683 static void
C2Raw(void)684 C2Raw(void)
685 {
686     struct termios n_tios;
687 
688     if (!isatty(0) || 0 != screwy)
689 	return;
690 
691     if (0 != tcgetattr(0, &o_tios)) {
692 	Error("tcgetattr(0): %s", strerror(errno));
693 	Bye(EX_UNAVAILABLE);
694     }
695     n_tios = o_tios;
696     n_tios.c_iflag &= ~(INLCR | IGNCR | ICRNL | IUCLC | IXON);
697     n_tios.c_oflag &= ~OPOST;
698     n_tios.c_lflag &= ~(ICANON | ISIG | ECHO | IEXTEN);
699     n_tios.c_cc[VMIN] = 1;
700     n_tios.c_cc[VTIME] = 0;
701     if (0 != tcsetattr(0, TCSANOW, &n_tios)) {
702 	Error("tcsetattr(0, TCSANOW): %s", strerror(errno));
703 	Bye(EX_UNAVAILABLE);
704     }
705     screwy = 1;
706 }
707 
708 /*
709  * put the tty back as it was, however that was
710  */
711 static void
C2Cooked(void)712 C2Cooked(void)
713 {
714     if (!screwy)
715 	return;
716     tcsetattr(0, TCSANOW, &o_tios);
717     screwy = 0;
718 }
719 
720 void
DestroyDataStructures(void)721 DestroyDataStructures(void)
722 {
723     C2Cooked();
724     if (cfstdout != (CONSFILE *)0)
725 	FileUnopen(cfstdout);
726     DestroyConfig(pConfig);
727     DestroyConfig(optConf);
728     DestroyConfig(config);
729     DestroyTerminal(pTerm);
730 #if !USE_IPV6
731     if (myAddrs != (struct in_addr *)0)
732 	free(myAddrs);
733 #endif
734     DestroyStrings();
735     if (substData != (SUBST *)0)
736 	free(substData);
737 }
738 
739 char *
ReadReply(CONSFILE * fd,FLAG toEOF)740 ReadReply(CONSFILE *fd, FLAG toEOF)
741 {
742     int nr;
743     static char buf[1024];
744     static STRING *result = (STRING *)0;
745 
746     if (result == (STRING *)0)
747 	result = AllocString();
748     else
749 	BuildString((char *)0, result);
750 
751     while (1) {
752 	int l;
753 	switch (nr = FileRead(fd, buf, sizeof(buf))) {
754 	    case 0:
755 		/* fall through */
756 	    case -1:
757 		if (result->used > 1 || toEOF == FLAGTRUE)
758 		    break;
759 		C2Cooked();
760 		Error("lost connection");
761 		Bye(EX_UNAVAILABLE);
762 	    default:
763 		while ((l = ParseIACBuf(fd, buf, &nr)) >= 0) {
764 		    if (l == 0)
765 			continue;
766 		    BuildStringN(buf, l, result);
767 		    nr -= l;
768 		    MemMove(buf, buf + l, nr);
769 		}
770 		BuildStringN(buf, nr, result);
771 		if (toEOF == FLAGTRUE)	/* if toEOF, read until EOF */
772 		    continue;
773 		if ((result->used > 1) &&
774 		    strchr(result->string, '\n') != (char *)0)
775 		    break;
776 		continue;
777 	}
778 	break;
779     }
780     if (fDebug) {
781 	static STRING *tmpString = (STRING *)0;
782 	if (tmpString == (STRING *)0)
783 	    tmpString = AllocString();
784 	BuildString((char *)0, tmpString);
785 	FmtCtlStr(result->string, result->used - 1, tmpString);
786 	CONDDEBUG((1, "ReadReply: `%s'", tmpString->string));
787     }
788     return result->string;
789 }
790 
791 static void
ReapVirt(void)792 ReapVirt(void)
793 {
794     pid_t pid;
795     int UWbuf;
796 
797     while (-1 != (pid = waitpid(-1, &UWbuf, WNOHANG | WUNTRACED))) {
798 	if (0 == pid)
799 	    break;
800 
801 	/* stopped child is just continued
802 	 */
803 	if (WIFSTOPPED(UWbuf) && 0 == kill(pid, SIGCONT)) {
804 	    Msg("child pid %lu: stopped, sending SIGCONT",
805 		(unsigned long)pid);
806 	    continue;
807 	}
808 
809 	if (WIFEXITED(UWbuf))
810 	    Verbose("child process %lu: exit(%d)", pid,
811 		    WEXITSTATUS(UWbuf));
812 	if (WIFSIGNALED(UWbuf))
813 	    Verbose("child process %lu: signal(%d)", pid, WTERMSIG(UWbuf));
814 	if (pid == execCmdPid) {
815 	    if (WIFEXITED(UWbuf))
816 		FilePrint(cfstdout, FLAGFALSE,
817 			  "[local command terminated - pid %lu: exit(%d)]\r\n",
818 			  pid, WEXITSTATUS(UWbuf));
819 	    if (WIFSIGNALED(UWbuf))
820 		FilePrint(cfstdout, FLAGFALSE,
821 			  "[local command terminated - pid %lu: signal(%d)]\r\n",
822 			  pid, WTERMSIG(UWbuf));
823 	}
824     }
825 }
826 
827 static sig_atomic_t fSawReapVirt = 0;
828 
829 #if HAVE_SIGACTION
830 static
831 #endif
832   RETSIGTYPE
FlagReapVirt(int sig)833 FlagReapVirt(int sig)
834 {
835     fSawReapVirt = 1;
836 #if !HAVE_SIGACTION
837     SimpleSignal(SIGCHLD, FlagReapVirt);
838 #endif
839 }
840 
841 /* invoke the execcmd command */
842 void
ExecCmd(void)843 ExecCmd(void)
844 {
845     int i;
846     pid_t iNewGrp;
847     extern char **environ;
848     int pin[2];
849     int pout[2];
850     static char *apcArgv[] = {
851 	"/bin/sh", "-ce", (char *)0, (char *)0
852     };
853 
854     if (execCmd == (STRING *)0 || execCmd->used <= 1)
855 	return;
856 
857     CONDDEBUG((1, "ExecCmd(): `%s'", execCmd->string));
858 
859     /* pin[0] = parent read, pin[1] = child write */
860     if (pipe(pin) != 0) {
861 	Error("ExecCmd(): pipe(): %s", strerror(errno));
862 	return;
863     }
864     /* pout[0] = child read, pout[l] = parent write */
865     if (pipe(pout) != 0) {
866 	close(pin[0]);
867 	close(pin[1]);
868 	Error("ExecCmd(): pipe(): %s", strerror(errno));
869 	return;
870     }
871 
872     fflush(stdout);
873     fflush(stderr);
874 
875     switch (execCmdPid = fork()) {
876 	case -1:
877 	    return;
878 	case 0:
879 	    thepid = getpid();
880 	    break;
881 	default:
882 	    close(pout[0]);
883 	    close(pin[1]);
884 	    if ((execCmdFile =
885 		 FileOpenPipe(pin[0], pout[1])) == (CONSFILE *)0) {
886 		Error("ExecCmd(): FileOpenPipe(%d,%d) failed", pin[0],
887 		      pout[1]);
888 		close(pin[0]);
889 		close(pout[1]);
890 		kill(execCmdPid, SIGHUP);
891 		return;
892 	    }
893 	    FilePrint(cfstdout, FLAGFALSE,
894 		      "[local command running - pid %lu]\r\n", execCmdPid);
895 	    FD_SET(pin[0], &rinit);
896 	    if (maxfd < pin[0] + 1)
897 		maxfd = pin[0] + 1;
898 	    fflush(stderr);
899 	    return;
900     }
901 
902     close(pin[0]);
903     close(pout[1]);
904 
905     /* put the signals back that we ignore (trapped auto-reset to default)
906      */
907     SimpleSignal(SIGPIPE, SIG_DFL);
908     SimpleSignal(SIGCHLD, SIG_DFL);
909 
910     /* setup new process with clean file descriptors
911      * stderr still goes to stderr...so user sees it
912      */
913 #ifdef HAVE_CLOSEFROM
914     for (i = 3; i <= pout[0] || i <= pin[1]; i++) {
915 	if (i != pout[0] && i != pin[1])
916 	    close(i);
917     }
918     closefrom(i);
919 #else
920     i = GetMaxFiles();
921     for ( /* i above */ ; --i > 2;) {
922 	if (i != pout[0] && i != pin[1])
923 	    close(i);
924     }
925 #endif
926     close(1);
927     close(0);
928 
929 #if HAVE_SETSID
930     iNewGrp = setsid();
931     if (-1 == iNewGrp) {
932 	Error("ExecCmd(): setsid(): %s", strerror(errno));
933 	iNewGrp = thepid;
934     }
935 #else
936     iNewGrp = thepid;
937 #endif
938 
939     if (dup(pout[0]) != 0 || dup(pin[1]) != 1) {
940 	Error("ExecCmd(): fd sync error");
941 	Bye(EX_OSERR);
942     }
943     close(pout[0]);
944     close(pin[1]);
945 
946     tcsetpgrp(0, iNewGrp);
947 
948     apcArgv[2] = execCmd->string;
949 
950     execve(apcArgv[0], apcArgv, environ);
951     Error("ExecCmd(): execve(%s): %s", apcArgv[2], strerror(errno));
952     Bye(EX_OSERR);
953     return;
954 }
955 
956 void
GetUserInput(STRING * str)957 GetUserInput(STRING *str)
958 {
959     char c;
960 
961     if (str == (STRING *)0)
962 	return;
963 
964     BuildString((char *)0, str);
965 
966     for (;;) {
967 	if (read(0, &c, 1) == 0)
968 	    break;
969 	if (c == '\n' || c == '\r') {
970 	    break;
971 	}
972 	if (c >= ' ' && c <= '~') {
973 	    BuildStringChar(c, str);
974 	    FileWrite(cfstdout, FLAGFALSE, &c, 1);
975 	} else if ((c == '\b' || c == 0x7f) && str->used > 1) {
976 	    FileWrite(cfstdout, FLAGFALSE, "\b \b", 3);
977 	    str->string[str->used - 2] = '\000';
978 	    str->used--;
979 	} else if ((c == 0x15) && str->used > 1) {
980 	    while (str->used > 1) {
981 		FileWrite(cfstdout, FLAGFALSE, "\b \b", 3);
982 		str->string[str->used - 2] = '\000';
983 		str->used--;
984 	    }
985 	} else if ((c == 0x17) && str->used > 1) {
986 	    while (str->used > 1 &&
987 		   isspace((int)(str->string[str->used - 2]))) {
988 		FileWrite(cfstdout, FLAGFALSE, "\b \b", 3);
989 		str->string[str->used - 2] = '\000';
990 		str->used--;
991 	    }
992 	    while (str->used > 1 &&
993 		   !isspace((int)(str->string[str->used - 2]))) {
994 		FileWrite(cfstdout, FLAGFALSE, "\b \b", 3);
995 		str->string[str->used - 2] = '\000';
996 		str->used--;
997 	    }
998 	}
999     }
1000 }
1001 
1002 void
DoExec(CONSFILE * pcf)1003 DoExec(CONSFILE *pcf)
1004 {
1005     showExecData = 1;
1006     FileWrite(cfstdout, FLAGFALSE, "exec: ", 6);
1007 
1008     GetUserInput(execCmd);
1009     FileWrite(cfstdout, FLAGFALSE, "]\r\n", 3);
1010 
1011     if (execCmd != (STRING *)0 && execCmd->used > 1) {
1012 	ExecCmd();
1013 	BuildString((char *)0, execCmd);
1014 	if (execCmdFile == (CONSFILE *)0) {	/* exec failed */
1015 	    /* say forget it */
1016 	    FileSetQuoteIAC(pcf, FLAGFALSE);
1017 	    FilePrint(pcf, FLAGFALSE, "%c%c", OB_IAC, OB_ABRT);
1018 	    FileSetQuoteIAC(pcf, FLAGTRUE);
1019 	} else {
1020 	    char *r;
1021 	    /* go back to blocking mode */
1022 	    SetFlags(FileFDNum(pcf), 0, O_NONBLOCK);
1023 	    /* say we're ready */
1024 	    FileSetQuoteIAC(pcf, FLAGFALSE);
1025 	    FilePrint(pcf, FLAGFALSE, "%c%c", OB_IAC, OB_EXEC);
1026 	    FileSetQuoteIAC(pcf, FLAGTRUE);
1027 	    r = ReadReply(pcf, FLAGFALSE);
1028 	    /* now back to non-blocking, now that we've got reply */
1029 	    SetFlags(FileFDNum(pcf), O_NONBLOCK, 0);
1030 	    /* if we aren't still r/w, abort */
1031 	    if (strncmp(r, "[rw]", 4) != 0) {
1032 		FileWrite(cfstdout, FLAGFALSE,
1033 			  "[no longer read-write - aborting command]\r\n",
1034 			  -1);
1035 		FD_CLR(FileFDNum(execCmdFile), &rinit);
1036 		FD_CLR(FileFDOutNum(execCmdFile), &winit);
1037 		FileClose(&execCmdFile);
1038 		FileSetQuoteIAC(pcf, FLAGFALSE);
1039 		FilePrint(pcf, FLAGFALSE, "%c%c", OB_IAC, OB_ABRT);
1040 		FileSetQuoteIAC(pcf, FLAGTRUE);
1041 		kill(execCmdPid, SIGHUP);
1042 	    }
1043 	}
1044     } else {
1045 	/* say forget it */
1046 	FileSetQuoteIAC(pcf, FLAGFALSE);
1047 	FilePrint(pcf, FLAGFALSE, "%c%c", OB_IAC, OB_ABRT);
1048 	FileSetQuoteIAC(pcf, FLAGTRUE);
1049     }
1050 }
1051 
1052 void
ExpandString(char * str,CONSFILE * c)1053 ExpandString(char *str, CONSFILE *c)
1054 {
1055     char s;
1056     short backslash = 0;
1057     short cntrl = 0;
1058     char oct = '\000';
1059     short octs = 0;
1060     static STRING *exp = (STRING *)0;
1061 
1062     if (str == (char *)0 || c == (CONSFILE *)0)
1063 	return;
1064 
1065     if (exp == (STRING *)0)
1066 	exp = AllocString();
1067 
1068     BuildString((char *)0, exp);
1069 
1070     backslash = 0;
1071     cntrl = 0;
1072     while ((s = (*str++)) != '\000') {
1073 	if (octs > 0 && octs < 3 && s >= '0' && s <= '7') {
1074 	    ++octs;
1075 	    oct = oct * 8 + (s - '0');
1076 	    continue;
1077 	}
1078 	if (octs != 0) {
1079 	    BuildStringChar(oct, exp);
1080 	    octs = 0;
1081 	    oct = '\000';
1082 	}
1083 	if (backslash) {
1084 	    backslash = 0;
1085 	    if (s == 'a')
1086 		s = '\a';
1087 	    else if (s == 'b')
1088 		s = '\b';
1089 	    else if (s == 'f')
1090 		s = '\f';
1091 	    else if (s == 'n')
1092 		s = '\n';
1093 	    else if (s == 'r')
1094 		s = '\r';
1095 	    else if (s == 't')
1096 		s = '\t';
1097 	    else if (s == 'v')
1098 		s = '\v';
1099 	    else if (s == '^')
1100 		s = '^';
1101 	    else if (s >= '0' && s <= '7') {
1102 		++octs;
1103 		oct = oct * 8 + (s - '0');
1104 		continue;
1105 	    }
1106 	    BuildStringChar(s, exp);
1107 	    continue;
1108 	}
1109 	if (cntrl) {
1110 	    cntrl = 0;
1111 	    if (s == '?')
1112 		s = 0x7f;	/* delete */
1113 	    else
1114 		s = s & 0x1f;
1115 	    BuildStringChar(s, exp);
1116 	    continue;
1117 	}
1118 	if (s == '\\') {
1119 	    backslash = 1;
1120 	    continue;
1121 	}
1122 	if (s == '^') {
1123 	    cntrl = 1;
1124 	    continue;
1125 	}
1126 	BuildStringChar(s, exp);
1127     }
1128 
1129     if (octs != 0)
1130 	BuildStringChar(oct, exp);
1131 
1132     if (backslash)
1133 	BuildStringChar('\\', exp);
1134 
1135     if (cntrl)
1136 	BuildStringChar('^', exp);
1137 
1138     if (exp->used > 1)
1139 	FileWrite(c, FLAGFALSE, exp->string, exp->used - 1);
1140 }
1141 
1142 void
PrintSubst(CONSFILE * pcf,char * pcMach,char * string,char * subst)1143 PrintSubst(CONSFILE *pcf, char *pcMach, char *string, char *subst)
1144 {
1145     if (string == (char *)0)
1146 	return;
1147 
1148     if (subst != (char *)0) {
1149 	char *str;
1150 	if ((str = StrDup(string)) == (char *)0)
1151 	    OutOfMem();
1152 	substData->data = (void *)config;
1153 	config->console = pcMach;
1154 	ProcessSubst(substData, &str, (char **)0, (char *)0, subst);
1155 	ExpandString(str, pcf);
1156 	free(str);
1157     } else
1158 	ExpandString(string, pcf);
1159 }
1160 
1161 void
Interact(CONSFILE * pcf,char * pcMach)1162 Interact(CONSFILE *pcf, char *pcMach)
1163 {
1164     int i;
1165     int nc;
1166     fd_set rmask, wmask;
1167     int justSuspended = 0;
1168     static char acMesg[8192];
1169 
1170     /* if this is true, it means we successfully moved to a new console
1171      * so we need to close the old one.
1172      */
1173     if (prevConsole != (CONSFILE *)0) {
1174 	FileClose(&prevConsole);
1175 	PrintSubst(cfstdout, prevName, pTerm->detach, pTerm->detachsubst);
1176     }
1177     if (prevName != (char *)0) {
1178 	free(prevName);
1179 	prevName = (char *)0;
1180     }
1181 
1182     /* this is only true in other parts of the code iff pcf == gotoConsole */
1183     if (gotoConsole != (CONSFILE *)0) {
1184 	gotoConsole = (CONSFILE *)0;
1185 	FilePrint(cfstdout, FLAGFALSE, "[returning to `%s'", pcMach);
1186 	FileWrite(pcf, FLAGFALSE, "\n", 1);
1187     }
1188 
1189     PrintSubst(cfstdout, pcMach, pTerm->attach, pTerm->attachsubst);
1190 
1191     C2Raw();
1192 
1193     /* set socket to non-blocking */
1194     SetFlags(FileFDNum(pcf), O_NONBLOCK, 0);
1195 
1196     /* read from stdin and the socket (non-blocking!).
1197      * rmask indicates which descriptors to read from,
1198      * the others are not used, nor is the result from
1199      * select, read, or write.
1200      */
1201     FD_ZERO(&rinit);
1202     FD_ZERO(&winit);
1203     FD_SET(FileFDNum(pcf), &rinit);
1204     FD_SET(0, &rinit);
1205     if (maxfd < FileFDNum(pcf) + 1)
1206 	maxfd = FileFDNum(pcf) + 1;
1207     for (;;) {
1208 	justSuspended = 0;
1209 	if (fSawReapVirt) {
1210 	    fSawReapVirt = 0;
1211 	    ReapVirt();
1212 	}
1213 	/* reset read mask and select on it
1214 	 */
1215 	rmask = rinit;
1216 	wmask = winit;
1217 	if (-1 ==
1218 	    select(maxfd, &rmask, &wmask, (fd_set *)0,
1219 		   (struct timeval *)0)) {
1220 	    if (errno != EINTR) {
1221 		Error("Master(): select(): %s", strerror(errno));
1222 		break;
1223 	    }
1224 	    continue;
1225 	}
1226 
1227 	/* anything from execCmd */
1228 	if (execCmdFile != (CONSFILE *)0) {
1229 	    if (FileCanRead(execCmdFile, &rmask, &wmask)) {
1230 		if ((nc =
1231 		     FileRead(execCmdFile, acMesg, sizeof(acMesg))) < 0) {
1232 		    FD_CLR(FileFDNum(execCmdFile), &rinit);
1233 		    FD_CLR(FileFDOutNum(execCmdFile), &winit);
1234 		    FileClose(&execCmdFile);
1235 		    FileSetQuoteIAC(pcf, FLAGFALSE);
1236 		    FilePrint(pcf, FLAGFALSE, "%c%c", OB_IAC, OB_ABRT);
1237 		    FileSetQuoteIAC(pcf, FLAGTRUE);
1238 		} else {
1239 		    if (config->striphigh == FLAGTRUE) {
1240 			for (i = 0; i < nc; ++i)
1241 			    acMesg[i] &= 127;
1242 		    }
1243 		    FileWrite(pcf, FLAGFALSE, acMesg, nc);
1244 		}
1245 	    } else if (!FileBufEmpty(execCmdFile) &&
1246 		       FileCanWrite(execCmdFile, &rmask, &wmask)) {
1247 		CONDDEBUG((1, "Interact(): flushing fd %d",
1248 			   FileFDNum(execCmdFile)));
1249 		if (FileWrite(execCmdFile, FLAGFALSE, (char *)0, 0) < 0) {
1250 		    /* -bryan */
1251 		    break;
1252 		}
1253 	    }
1254 	}
1255 
1256 	/* anything from socket? */
1257 	if (FileCanRead(pcf, &rmask, &wmask)) {
1258 	    int l;
1259 	    if ((nc = FileRead(pcf, acMesg, sizeof(acMesg))) < 0) {
1260 		/* if we got an error/eof after returning from suspend */
1261 		if (justSuspended) {
1262 		    fprintf(stderr, "\n");
1263 		    Error("lost connection");
1264 		}
1265 		break;
1266 	    }
1267 	    while ((l = ParseIACBuf(pcf, acMesg, &nc)) >= 0) {
1268 		if (l == 0) {
1269 		    if (execCmdFile == (CONSFILE *)0) {
1270 			if (FileSawQuoteExec(pcf) == FLAGTRUE)
1271 			    DoExec(pcf);
1272 			else if (FileSawQuoteSusp(pcf) == FLAGTRUE) {
1273 			    justSuspended = 1;
1274 #if defined(SIGSTOP)
1275 			    FileWrite(cfstdout, FLAGFALSE, "stop]", 5);
1276 			    C2Cooked();
1277 			    PrintSubst(cfstdout, pcMach, pTerm->detach,
1278 				       pTerm->detachsubst);
1279 			    kill(thepid, SIGSTOP);
1280 			    PrintSubst(cfstdout, pcMach, pTerm->attach,
1281 				       pTerm->attachsubst);
1282 			    C2Raw();
1283 			    FileWrite(cfstdout, FLAGFALSE,
1284 				      "[press any character to continue",
1285 				      32);
1286 #else
1287 			    FileWrite(cfstdout, FLAGFALSE,
1288 				      "stop not supported -- press any character to continue",
1289 				      53);
1290 #endif
1291 			} else if (FileSawQuoteGoto(pcf) == FLAGTRUE) {
1292 			    gotoConsole = pcf;
1293 			    if (gotoName != (char *)0)
1294 				free(gotoName);
1295 			    if ((gotoName = StrDup(pcMach)) == (char *)0)
1296 				OutOfMem();
1297 			    C2Cooked();
1298 			    return;
1299 			}
1300 		    } else {
1301 			if (FileSawQuoteAbrt(pcf) == FLAGTRUE) {
1302 			    FD_CLR(FileFDNum(execCmdFile), &rinit);
1303 			    FD_CLR(FileFDOutNum(execCmdFile), &winit);
1304 			    FileClose(&execCmdFile);
1305 			    kill(execCmdPid, SIGHUP);
1306 			}
1307 		    }
1308 		    continue;
1309 		}
1310 		if (config->striphigh == FLAGTRUE) {
1311 		    for (i = 0; i < l; ++i)
1312 			acMesg[i] &= 127;
1313 		}
1314 		if (execCmdFile != (CONSFILE *)0) {
1315 		    FileWrite(execCmdFile, FLAGFALSE, acMesg, l);
1316 		    if (showExecData)
1317 			FileWrite(cfstdout, FLAGFALSE, acMesg, l);
1318 		} else
1319 		    FileWrite(cfstdout, FLAGFALSE, acMesg, l);
1320 		nc -= l;
1321 		MemMove(acMesg, acMesg + l, nc);
1322 	    }
1323 	} else if (!FileBufEmpty(pcf) && FileCanWrite(pcf, &rmask, &wmask)) {
1324 	    CONDDEBUG((1, "Interact(): flushing fd %d", FileFDNum(pcf)));
1325 	    if (FileWrite(pcf, FLAGFALSE, (char *)0, 0) < 0) {
1326 		/* -bryan */
1327 		break;
1328 	    }
1329 	}
1330 
1331 	/* anything from stdin? */
1332 	if (FD_ISSET(0, &rmask)) {
1333 	    if ((nc = read(0, acMesg, sizeof(acMesg))) <= 0) {
1334 		if (screwy)
1335 		    break;
1336 		else {
1337 		    FD_CLR(0, &rinit);
1338 		    continue;
1339 		}
1340 	    }
1341 	    if (execCmdFile == (CONSFILE *)0) {
1342 		if (config->striphigh == FLAGTRUE) {
1343 		    for (i = 0; i < nc; ++i)
1344 			acMesg[i] &= 127;
1345 		}
1346 		FileWrite(pcf, FLAGFALSE, acMesg, nc);
1347 	    } else {
1348 		for (i = 0; i < nc; ++i) {
1349 		    if (acMesg[i] == '\n' || acMesg[i] == '\r')
1350 			FilePrint(cfstdout, FLAGFALSE,
1351 				  "[local command running - pid %lu]\r\n",
1352 				  execCmdPid);
1353 		    else if (acMesg[i] == 0x03) {	/* ctrl-c */
1354 			kill(execCmdPid, SIGHUP);
1355 			FilePrint(cfstdout, FLAGFALSE,
1356 				  "[local command sent SIGHUP - pid %lu]\r\n",
1357 				  execCmdPid);
1358 		    } else if (acMesg[i] == 0x1c) {	/* ctrl-\ */
1359 			kill(execCmdPid, SIGKILL);
1360 			FilePrint(cfstdout, FLAGFALSE,
1361 				  "[local command sent SIGKILL - pid %lu]\r\n",
1362 				  execCmdPid);
1363 		    } else if (acMesg[i] == 'o' || acMesg[i] == 'O') {
1364 			showExecData = !showExecData;
1365 			FilePrint(cfstdout, FLAGFALSE,
1366 				  "[local command data %s]\r\n",
1367 				  showExecData ? "on" : "off");
1368 		    }
1369 		}
1370 	    }
1371 	}
1372     }
1373 
1374     C2Cooked();
1375 
1376     PrintSubst(cfstdout, pcMach, pTerm->detach, pTerm->detachsubst);
1377 
1378     if (fVerbose)
1379 	printf("Console %s closed.\n", pcMach);
1380 }
1381 
1382 /* interact with a group server					(ksb)
1383  */
1384 void
CallUp(CONSFILE * pcf,char * pcMaster,char * pcMach,char * pcHow,char * result)1385 CallUp(CONSFILE *pcf, char *pcMaster, char *pcMach, char *pcHow,
1386        char *result)
1387 {
1388     int fIn = '-';
1389     char *r = (char *)0;
1390 
1391     if (fVerbose) {
1392 	Msg("%s to %s (on %s)", pcHow, pcMach, pcMaster);
1393     }
1394 #if !defined(__CYGWIN__)
1395 # if defined(F_SETOWN)
1396     if (fcntl(FileFDNum(pcf), F_SETOWN, thepid) == -1) {
1397 	Error("fcntl(F_SETOWN,%lu): %d: %s", (unsigned long)thepid,
1398 	      FileFDNum(pcf), strerror(errno));
1399     }
1400 # else
1401 #  if defined(SIOCSPGRP)
1402     {
1403 	int iTemp;
1404 	/* on the HP-UX systems if different
1405 	 */
1406 	iTemp = -thepid;
1407 	if (ioctl(FileFDNum(pcf), SIOCSPGRP, &iTemp) == -1) {
1408 	    Error("ioctl(%d,SIOCSPGRP): %s", FileFDNum(pcf),
1409 		  strerror(errno));
1410 	}
1411     }
1412 #  endif
1413 # endif
1414 #endif
1415     SimpleSignal(SIGCHLD, FlagReapVirt);
1416 
1417     /* if we are going for a particular console
1418      * send sign-on stuff, then wait for some indication of what mode
1419      * we got from the server (if we are the only people on we get write
1420      * access by default, which is fine for most people).
1421      */
1422 
1423     /* how did we do, did we get a read-only or read-write?
1424      */
1425     if (0 == strcmp(result, "[attached]\r\n")) {
1426 	/* OK -- we are good as gold */
1427 	fIn = 'a';
1428     } else if (0 == strcmp(result, "[spy]\r\n") ||
1429 	       0 == strcmp(result, "[ok]\r\n") ||
1430 	       0 == strcmp(result, "[read-only -- initializing]\r\n")) {
1431 	/* Humph, someone else is on
1432 	 * or we have an old version of the server (4.X)
1433 	 */
1434 	fIn = 's';
1435     } else if (0 == strcmp(result, "[console is read-only]\r\n")) {
1436 	fIn = 'r';
1437     } else if (0 == strcmp(result, "[line to console is down]\r\n")) {
1438 	/* ouch, the machine is down on the server */
1439 	fIn = '-';
1440 	Error("%s is down", pcMach);
1441 	if (fVerbose) {
1442 	    printf("[use `");
1443 	    PutCtlc(chAttn, stdout);
1444 	    PutCtlc(chEsc, stdout);
1445 	    printf("o\' to open console line]\n");
1446 	}
1447     } else {
1448 	FilePrint(cfstdout, FLAGFALSE, "%s: %s", pcMach, result);
1449 	Bye(EX_UNAVAILABLE);
1450     }
1451 
1452     /* change escape sequence (if set on the command line)
1453      * and replay the log for the user, if asked
1454      */
1455     if (chAttn == -1 || chEsc == -1) {
1456 	chAttn = DEFATTN;
1457 	chEsc = DEFESC;
1458     } else {
1459 	/* tell the conserver to change escape sequences, assume OK
1460 	 * (we'll find out soon enough)
1461 	 */
1462 	FilePrint(pcf, FLAGFALSE, "%c%ce%c%c", DEFATTN, DEFESC, chAttn,
1463 		  chEsc);
1464 	r = ReadReply(pcf, FLAGFALSE);
1465 	if (strncmp(r, "[redef:", 7) != 0) {
1466 	    Error("protocol botch on redef of escape sequence");
1467 	    Bye(EX_UNAVAILABLE);
1468 	}
1469     }
1470 
1471     /* try to grok the state of the console */
1472     FilePrint(pcf, FLAGFALSE, "%c%c=", chAttn, chEsc);
1473     r = ReadReply(pcf, FLAGFALSE);
1474     if (strncmp(r, "[unknown", 8) != 0 && strncmp(r, "[up]", 4) != 0)
1475 	FileWrite(cfstdout, FLAGFALSE, r, -1);
1476 
1477     /* try to grok the version of the server */
1478     FilePrint(pcf, FLAGFALSE, "%c%c%c", chAttn, chEsc, 0xD6);
1479     r = ReadReply(pcf, FLAGFALSE);
1480     if (strncmp(r, "[unknown", 8) != 0)
1481 	sversion = AtoU(r + 1);
1482 
1483     printf("[Enter `");
1484     PutCtlc(chAttn, stdout);
1485     PutCtlc(chEsc, stdout);
1486     printf("?\' for help]\n");
1487 
1488     /* try and display the MOTD */
1489     FilePrint(pcf, FLAGFALSE, "%c%cm", chAttn, chEsc);
1490     r = ReadReply(pcf, FLAGFALSE);
1491     if (strncmp(r, "[unknown", 8) != 0 &&
1492 	strncmp(r, "[-- MOTD --]", 12) != 0)
1493 	FileWrite(cfstdout, FLAGFALSE, r, -1);
1494 
1495     if (sversion >= 8001014) {
1496 	if (config->playback) {
1497 	    FilePrint(pcf, FLAGFALSE, "%c%cP%hu\r", chAttn, chEsc,
1498 #if defined(TIOCGWINSZ)
1499 		      config->playback == 1 ? ws.ws_row :
1500 #endif
1501 		      config->playback - 1);
1502 	    r = ReadReply(pcf, FLAGFALSE);
1503 	}
1504 
1505 	if (config->replay) {
1506 	    FilePrint(pcf, FLAGFALSE, "%c%cR%hu\r", chAttn, chEsc,
1507 #if defined(TIOCGWINSZ)
1508 		      config->replay == 1 ? ws.ws_row :
1509 #endif
1510 		      config->replay - 1);
1511 	    r = ReadReply(pcf, FLAGFALSE);
1512 	}
1513     }
1514 
1515     FilePrint(pcf, FLAGFALSE, "%c%c;", chAttn, chEsc);
1516     r = ReadReply(pcf, FLAGFALSE);
1517     if (strncmp(r, "[unknown", 8) != 0 &&
1518 	strncmp(r, "[connected]", 11) != 0)
1519 	FileWrite(cfstdout, FLAGFALSE, r, -1);
1520 
1521     /* if the host is not down, finish the connection, and force
1522      * the correct attachment for the user
1523      */
1524     if (fIn != '-') {
1525 	if (fIn == 'r') {
1526 	    if (*pcHow != 's') {
1527 		Error("%s is read-only", pcMach);
1528 	    }
1529 	} else if (fIn != (*pcHow == 'f' ? 'a' : *pcHow)) {
1530 	    FilePrint(pcf, FLAGFALSE, "%c%c%c", chAttn, chEsc, *pcHow);
1531 	}
1532 	if (fReplay) {
1533 	    FilePrint(pcf, FLAGFALSE, "%c%cr", chAttn, chEsc);
1534 	} else if (fVerbose) {
1535 	    FilePrint(pcf, FLAGFALSE, "%c%c\022", chAttn, chEsc);
1536 	}
1537     }
1538     fflush(stdout);
1539     fflush(stderr);
1540 
1541     Interact(pcf, pcMach);
1542 }
1543 
1544 /* shouldn't need more than 3 levels of commands (but alloc 4 just 'cause)
1545  * worst case so far: master, groups, broadcast
1546  *   (cmdarg == broadcast msg)
1547  *
1548  * some sample "stacks" of commands:
1549  *
1550  * console -q:     master, quit
1551  * console -Q:     quit
1552  * console foo:    call, attach            (interact==FLAGTRUE)
1553  * console -f foo: call, force             (interact==FLAGTRUE)
1554  * console -w:     master, groups, group
1555  * console -I:     groups, info
1556  * console -i foo: call, info              (interact==FLAGFALSE)
1557  *
1558  */
1559 char *cmds[4] = { (char *)0, (char *)0, (char *)0, (char *)0 };
1560 
1561 char *cmdarg = (char *)0;
1562 
1563 /* call a machine master for group master ports and machine master ports
1564  * take a list like "1782@localhost:@mentor.cc.purdue.edu:@pop.stat.purdue.edu"
1565  * and send the given command to the group leader at 1782
1566  * and ask the machine master at mentor for more group leaders
1567  * and ask the machine master at pop.stat for more group leaders
1568  */
1569 int
DoCmds(char * master,char * pports,int cmdi)1570 DoCmds(char *master, char *pports, int cmdi)
1571 {
1572     CONSFILE *pcf;
1573     char *t;
1574     char *next;
1575     char *server;
1576     unsigned short port;
1577     char *result = (char *)0;
1578     int len;
1579     char *ports;
1580     char *pcopy;
1581     char *serverName;
1582 #if HAVE_GSSAPI
1583     int toksize;
1584 #endif
1585 
1586     if ((pcopy = ports = StrDup(pports)) == (char *)0)
1587 	OutOfMem();
1588 
1589     len = strlen(ports);
1590     while (len > 0 && (ports[len - 1] == '\r' || ports[len - 1] == '\n'))
1591 	len--;
1592     ports[len] = '\000';
1593 
1594     for ( /* param */ ; *ports != '\000'; ports = next) {
1595 	if ((next = strchr(ports, ':')) == (char *)0)
1596 	    next = "";
1597 	else
1598 	    *next++ = '\000';
1599 
1600 	if ((server = strchr(ports, '@')) != (char *)0) {
1601 	    *server++ = '\000';
1602 	    if (*server == '\000')
1603 		server = master;
1604 	} else
1605 	    server = master;
1606 
1607 #if USE_UNIX_DOMAIN_SOCKETS
1608 	serverName = "localhost";
1609 #else
1610 	serverName = server;
1611 #endif
1612 
1613 	if (*ports == '\000') {
1614 #if USE_IPV6
1615 	    port = bindPort;
1616 #elif USE_UNIX_DOMAIN_SOCKETS
1617 	    port = 0;
1618 #else
1619 	    port = htons(bindPort);
1620 #endif
1621 	} else if (!isdigit((int)(ports[0]))) {
1622 	    Error("invalid port spec for %s: `%s'", serverName, ports);
1623 	    continue;
1624 	} else {
1625 #if USE_IPV6
1626 	    port = (short)atoi(ports);
1627 #elif USE_UNIX_DOMAIN_SOCKETS
1628 	    port = (short)atoi(ports);
1629 #else
1630 	    port = htons((short)atoi(ports));
1631 #endif
1632 	}
1633 
1634       attemptLogin:
1635 	if ((pcf = GetPort(server, port)) == (CONSFILE *)0)
1636 	    continue;
1637 
1638 	FileSetQuoteIAC(pcf, FLAGTRUE);
1639 
1640 	t = ReadReply(pcf, FLAGFALSE);
1641 	if (strcmp(t, "ok\r\n") != 0) {
1642 	    FileClose(&pcf);
1643 	    FilePrint(cfstdout, FLAGFALSE, "%s: %s", serverName, t);
1644 	    continue;
1645 	}
1646 #if HAVE_OPENSSL
1647 	if (config->sslenabled == FLAGTRUE) {
1648 	    FileWrite(pcf, FLAGFALSE, "ssl\r\n", 5);
1649 	    t = ReadReply(pcf, FLAGFALSE);
1650 	    if (strcmp(t, "ok\r\n") == 0) {
1651 		AttemptSSL(pcf);
1652 		if (FileGetType(pcf) != SSLSocket) {
1653 		    Error("Encryption not supported by server `%s'",
1654 			  serverName);
1655 		    FileClose(&pcf);
1656 		    continue;
1657 		}
1658 	    } else if (config->sslrequired == FLAGTRUE) {
1659 		Error("Encryption not supported by server `%s'",
1660 		      serverName);
1661 		FileClose(&pcf);
1662 		continue;
1663 	    }
1664 	}
1665 #endif
1666 #if HAVE_GSSAPI
1667 	if ((toksize = CanGetGSSContext(server)) > 0) {
1668 	    FilePrint(pcf, FLAGFALSE, "gssapi %d\r\n", toksize);
1669 	    t = ReadReply(pcf, FLAGFALSE);
1670 	    if (strcmp(t, "ok\r\n") == 0) {
1671 		if (AttemptGSSAPI(pcf)) {
1672 		    goto gssapi_logged_me_in;
1673 		}
1674 	    }
1675 	}
1676 #endif
1677 
1678 	FilePrint(pcf, FLAGFALSE, "login %s\r\n", config->username);
1679 
1680 	t = ReadReply(pcf, FLAGFALSE);
1681 	if (strncmp(t, "passwd?", 7) == 0) {
1682 	    static int count = 0;
1683 	    static STRING *tmpString = (STRING *)0;
1684 	    char *hostname = (char *)0;
1685 
1686 	    if (t[7] == ' ') {
1687 		hostname = PruneSpace(t + 7);
1688 		if (*hostname == '\000')
1689 		    hostname = serverName;
1690 	    } else
1691 		hostname = serverName;
1692 	    if (tmpString == (STRING *)0)
1693 		tmpString = AllocString();
1694 	    if (tmpString->used <= 1) {
1695 		char *pass;
1696 		BuildStringPrint(tmpString, "Enter %s@%s's password: ",
1697 				 config->username, hostname);
1698 		pass = GetPassword(tmpString->string);
1699 		if (pass == (char *)0) {
1700 		    Error("could not get password from tty for `%s'",
1701 			  serverName);
1702 		    FileClose(&pcf);
1703 		    continue;
1704 		}
1705 		BuildString((char *)0, tmpString);
1706 		BuildString(pass, tmpString);
1707 		BuildString("\r\n", tmpString);
1708 	    }
1709 	    FileWrite(pcf, FLAGFALSE, tmpString->string,
1710 		      tmpString->used - 1);
1711 	    t = ReadReply(pcf, FLAGFALSE);
1712 	    if (strcmp(t, "ok\r\n") != 0) {
1713 		FilePrint(cfstdout, FLAGFALSE, "%s: %s", serverName, t);
1714 		if (++count < 3) {
1715 		    BuildString((char *)0, tmpString);
1716 		    goto attemptLogin;
1717 		}
1718 		Error("too many bad passwords for `%s'", serverName);
1719 		count = 0;
1720 		FileClose(&pcf);
1721 		continue;
1722 	    } else
1723 		count = 0;
1724 	} else if (strcmp(t, "ok\r\n") != 0) {
1725 	    FileClose(&pcf);
1726 	    FilePrint(cfstdout, FLAGFALSE, "%s: %s", serverName, t);
1727 	    continue;
1728 	}
1729 #if HAVE_GSSAPI
1730       gssapi_logged_me_in:
1731 #endif
1732 
1733 	/* now that we're logged in, we can do something */
1734 	/* if we're on the last cmd or the command is 'call' and we
1735 	 * have an arg (always true if it's 'call'), then send the arg
1736 	 */
1737 	if ((cmdi == 0 || cmds[cmdi][0] == 'c') && cmdarg != (char *)0)
1738 	    FilePrint(pcf, FLAGFALSE, "%s %s\r\n", cmds[cmdi], cmdarg);
1739 	else
1740 	    FilePrint(pcf, FLAGFALSE, "%s\r\n", cmds[cmdi]);
1741 
1742 	/* if we haven't gone down the stack, do "normal" stuff.
1743 	 * if we did hit the bottom, we send the exit\r\n now so
1744 	 * that the ReadReply can stop once the socket closes.
1745 	 */
1746 	if (cmdi != 0) {
1747 	    t = ReadReply(pcf, FLAGFALSE);
1748 	    /* save the result */
1749 	    if (result != (char *)0)
1750 		free(result);
1751 	    if ((result = StrDup(t)) == (char *)0)
1752 		OutOfMem();
1753 	}
1754 
1755 	/* if we're working on finding a console */
1756 	if (cmds[cmdi][0] == 'c') {
1757 	    static int limit = 0;
1758 	    /* did we get a redirect? */
1759 	    if (result[0] == '@' || (result[0] >= '0' && result[0] <= '9')) {
1760 		if (limit++ > 10) {
1761 		    Error("forwarding level too deep!");
1762 		    Bye(EX_SOFTWARE);
1763 		}
1764 		FileWrite(pcf, FLAGFALSE, "exit\r\n", 6);
1765 		t = ReadReply(pcf, FLAGTRUE);
1766 	    } else if (interact == FLAGFALSE && result[0] == '[' &&
1767 		       cmdi > 0) {
1768 		FileClose(&pcf);
1769 		/* reconnect to same, but with the next command (info, examine, etc) */
1770 		DoCmds(master, pports, cmdi - 1);
1771 		break;
1772 	    } else {
1773 		/* if we're not trying to connect to a console */
1774 		if (interact == FLAGFALSE) {
1775 		    FilePrint(cfstdout, FLAGFALSE, "%s: %s", serverName,
1776 			      result);
1777 		    FileClose(&pcf);
1778 		    continue;
1779 		}
1780 		if (result[0] != '[') {	/* did we not get a connection? */
1781 		    int len;
1782 
1783 		    limit = 0;
1784 		    FilePrint(cfstdout, FLAGFALSE, "%s: %s", serverName,
1785 			      result);
1786 		    FileWrite(pcf, FLAGFALSE, "exit\r\n", 6);
1787 		    t = ReadReply(pcf, FLAGTRUE);
1788 
1789 		    /* strip off the goodbye from the tail of the result */
1790 		    len = strlen(t);
1791 		    if (len > 8 && strcmp("goodbye\r\n", t + len - 9) == 0) {
1792 			*(t + len - 9) = '\000';
1793 		    }
1794 
1795 		    FileWrite(cfstdout, FLAGFALSE, t, -1);
1796 		    FileClose(&pcf);
1797 		    continue;
1798 		} else {
1799 		    limit = 0;
1800 		    CallUp(pcf, server, cmdarg, cmds[0], result);
1801 		    if (pcf != gotoConsole)
1802 			FileClose(&pcf);
1803 		    break;
1804 		}
1805 	    }
1806 	} else if (cmds[cmdi][0] == 'q') {
1807 	    if (cmdi == 0) {
1808 		t = ReadReply(pcf, FLAGFALSE);
1809 		FilePrint(cfstdout, FLAGFALSE, "%s: %s", serverName, t);
1810 	    } else {
1811 		FilePrint(cfstdout, FLAGFALSE, "%s: %s", serverName,
1812 			  result);
1813 	    }
1814 	    /* only say 'exit' if 'quit' failed...since it's dying anyway */
1815 	    if (t[0] != 'o' || t[1] != 'k') {
1816 		FileWrite(pcf, FLAGFALSE, "exit\r\n", 6);
1817 		t = ReadReply(pcf, FLAGTRUE);
1818 	    }
1819 	} else {
1820 	    /* all done */
1821 	    /* ok, this is whacky.  if cmdi==0, we haven't read back the
1822 	     * reply yet, so 't' is going to have multiple lines out output
1823 	     * since we send the 'exit' command...first line (or set of
1824 	     * lines) would be the previous command, and then a 'goodbye'
1825 	     * (ideally).  we monkey around below because of this.
1826 	     * like i said.  wacky.
1827 	     */
1828 	    FileWrite(pcf, FLAGFALSE, "exit\r\n", 6);
1829 	    t = ReadReply(pcf, cmdi == 0 ? FLAGTRUE : FLAGFALSE);
1830 
1831 	    if (cmdi == 0) {
1832 		int len;
1833 		/* if we hit bottom, this is where we get our results */
1834 		if (result != (char *)0)
1835 		    free(result);
1836 		if ((result = StrDup(t)) == (char *)0)
1837 		    OutOfMem();
1838 		/* strip off the goodbye from the tail of the result */
1839 		len = strlen(result);
1840 		if (len > 8 &&
1841 		    strcmp("goodbye\r\n", result + len - 9) == 0) {
1842 		    len -= 9;
1843 		    *(result + len) = '\000';
1844 		}
1845 		/* if (not 'broadcast' and not 'textmsg') or
1846 		 *   result doesn't start with 'ok' (only checks this if
1847 		 *      it's a 'broadcast' or 'textmsg')
1848 		 */
1849 		if (cmds[0][0] == 'd') {
1850 		    if (result[0] != 'o' || result[1] != 'k') {
1851 			FileWrite(cfstdout, FLAGTRUE, serverName, -1);
1852 			FileWrite(cfstdout, FLAGTRUE, ": ", 2);
1853 			FileWrite(cfstdout, FLAGFALSE, result, len);
1854 		    } else {
1855 			disconnectCount += atoi(result + 19);
1856 		    }
1857 		} else if ((cmds[0][0] != 'b' && cmds[0][0] != 't') ||
1858 			   (result[0] != 'o' || result[1] != 'k')) {
1859 		    /* did a 'master' before this or doing a 'disconnect',
1860 		     * 'reconfig', 'newlogs', or 'up'
1861 		     */
1862 		    if ((cmds[1] != (char *)0 && cmds[1][0] == 'm') ||
1863 			cmds[0][0] == 'd' || cmds[0][0] == 'r' ||
1864 			cmds[0][0] == 'n' || cmds[0][0] == 'u') {
1865 			FileWrite(cfstdout, FLAGTRUE, serverName, -1);
1866 			FileWrite(cfstdout, FLAGTRUE, ": ", 2);
1867 		    }
1868 		    FileWrite(cfstdout, FLAGFALSE, result, len);
1869 		}
1870 	    }
1871 	}
1872 
1873 	FileClose(&pcf);
1874 
1875 	/* this would only be true if we got extra redirects (@... above) */
1876 	if (cmds[cmdi][0] == 'c')
1877 	    DoCmds(server, result, cmdi);
1878 	else if (cmdi > 0)
1879 	    DoCmds(server, result, cmdi - 1);
1880 	if (result != (char *)0)
1881 	    free(result);
1882 	result = (char *)0;
1883     }
1884 
1885     if (result != (char *)0)
1886 	free(result);
1887     free(pcopy);
1888     return 0;
1889 }
1890 
1891 
1892 /* mainline for console client program					(ksb)
1893  * setup who we are, and what our loopback addr is
1894  * parse the cmd line,
1895  * (optionally) get a shutdown passwd
1896  * Gather results
1897  * exit happy or sad
1898  */
1899 int
main(int argc,char ** argv)1900 main(int argc, char **argv)
1901 {
1902     char *pcCmd;
1903     struct passwd *pwdMe = (struct passwd *)0;
1904     int opt;
1905     int fLocal;
1906     static STRING *acPorts = (STRING *)0;
1907     static char acOpts[] =
1908 	"7aAb:B:c:C:d:De:EfFhiIl:M:np:PqQrRsSt:uUvVwWxz:Z:";
1909     extern int optind;
1910     extern int optopt;
1911     extern char *optarg;
1912     static STRING *textMsg = (STRING *)0;
1913     int cmdi;
1914     static STRING *consoleName = (STRING *)0;
1915     short readSystemConf = 1;
1916     char *userConf = (char *)0;
1917     typedef struct zaps {
1918 	char *opt;
1919 	char *cmd;
1920 	char *desc;
1921     } ZAPS;
1922     ZAPS zap[] = {
1923 	{"bringup, SIGUSR1", "up", "bring up any consoles that are down"},
1924 	{"help", (char *)0, "this help message"},
1925 	{"pid", "pid", "display master process ids"},
1926 	{"quit, SIGTERM", "quit", "terminate the server"},
1927 	{"reconfig, SIGHUP", "reconfig",
1928 	 "reread configuration file, then do 'reopen' actions"},
1929 	{"reopen, SIGUSR2", "newlogs",
1930 	 "reopen all logfiles, then do 'bringup' actions"},
1931 	{"version", "version", "display version information"}
1932     };
1933     int isZap = 0;
1934 
1935     isMultiProc = 0;		/* make sure stuff DOESN'T have the pid */
1936 
1937     thepid = getpid();
1938 
1939     if (textMsg == (STRING *)0)
1940 	textMsg = AllocString();
1941     if (acPorts == (STRING *)0)
1942 	acPorts = AllocString();
1943 
1944     if ((char *)0 == (progname = strrchr(argv[0], '/'))) {
1945 	progname = argv[0];
1946     } else {
1947 	++progname;
1948     }
1949 
1950     /* prep the config options */
1951     if ((optConf = (CONFIG *)calloc(1, sizeof(CONFIG))) == (CONFIG *)0)
1952 	OutOfMem();
1953     if ((config = (CONFIG *)calloc(1, sizeof(CONFIG))) == (CONFIG *)0)
1954 	OutOfMem();
1955     if ((pConfig = (CONFIG *)calloc(1, sizeof(CONFIG))) == (CONFIG *)0)
1956 	OutOfMem();
1957     /* and the terminal options */
1958     if ((pTerm = (TERM *)calloc(1, sizeof(TERM))) == (TERM *)0)
1959 	OutOfMem();
1960 
1961     /* command line parsing
1962      */
1963     pcCmd = (char *)0;
1964     fLocal = 0;
1965     while ((opt = getopt(argc, argv, acOpts)) != EOF) {
1966 	switch (opt) {
1967 	    case '7':		/* strip high-bit */
1968 		optConf->striphigh = FLAGTRUE;
1969 		break;
1970 
1971 	    case 'A':		/* attach with log replay */
1972 		fReplay = 1;
1973 		/* fall through */
1974 	    case 'a':		/* attach */
1975 		pcCmd = "attach";
1976 		break;
1977 
1978 	    case 'B':		/* broadcast message */
1979 		fLocal = 1;
1980 		/* fall through */
1981 	    case 'b':
1982 		pcCmd = "broadcast";
1983 		if (cmdarg != (char *)0)
1984 		    free(cmdarg);
1985 		if ((cmdarg = StrDup(optarg)) == (char *)0)
1986 		    OutOfMem();
1987 		break;
1988 
1989 	    case 'C':
1990 		userConf = optarg;
1991 		break;
1992 
1993 	    case 'c':
1994 #if HAVE_OPENSSL
1995 		if ((optConf->sslcredentials =
1996 		     StrDup(optarg)) == (char *)0)
1997 		    OutOfMem();
1998 #endif
1999 		break;
2000 
2001 	    case 'D':
2002 		fDebug++;
2003 		break;
2004 
2005 	    case 'd':
2006 		pcCmd = "disconnect";
2007 		if (cmdarg != (char *)0)
2008 		    free(cmdarg);
2009 		if ((cmdarg = StrDup(optarg)) == (char *)0)
2010 		    OutOfMem();
2011 		break;
2012 
2013 	    case 'E':
2014 #if HAVE_OPENSSL
2015 		optConf->sslenabled = FLAGFALSE;
2016 #endif
2017 		break;
2018 
2019 	    case 'e':		/* set escape chars */
2020 		if ((optConf->escape = StrDup(optarg)) == (char *)0)
2021 		    OutOfMem();
2022 		break;
2023 
2024 	    case 'F':		/* force attach with log replay */
2025 		fReplay = 1;
2026 		/* fall through */
2027 	    case 'f':		/* force attach */
2028 		pcCmd = "force";
2029 		break;
2030 
2031 	    case 'I':
2032 		fLocal = 1;
2033 		/* fall through */
2034 	    case 'i':
2035 		pcCmd = "info";
2036 		break;
2037 
2038 	    case 'l':
2039 		if ((optConf->username = StrDup(optarg)) == (char *)0)
2040 		    OutOfMem();
2041 		break;
2042 
2043 	    case 'M':
2044 		if ((optConf->master = StrDup(optarg)) == (char *)0)
2045 		    OutOfMem();
2046 		break;
2047 
2048 	    case 'n':
2049 		readSystemConf = 0;
2050 		break;
2051 
2052 	    case 'p':
2053 		if ((optConf->port = StrDup(optarg)) == (char *)0)
2054 		    OutOfMem();
2055 		break;
2056 
2057 	    case 'P':		/* send a pid command to the server     */
2058 		pcCmd = "pid";
2059 		break;
2060 
2061 	    case 'Q':		/* only quit this host          */
2062 		fLocal = 1;
2063 		/*fallthough */
2064 	    case 'q':		/* send quit command to server  */
2065 		pcCmd = "quit";
2066 		break;
2067 
2068 	    case 'R':
2069 		fLocal = 1;
2070 		/*fallthrough */
2071 	    case 'r':		/* display daemon version */
2072 		pcCmd = "version";
2073 		break;
2074 
2075 	    case 'S':		/* spy with log replay */
2076 		fReplay = 1;
2077 		/* fall through */
2078 	    case 's':		/* spy */
2079 		pcCmd = "spy";
2080 		break;
2081 
2082 	    case 't':
2083 		BuildString((char *)0, textMsg);
2084 		if (optarg == (char *)0 || *optarg == '\000') {
2085 		    Error("no destination specified for -t", optarg);
2086 		    Bye(EX_UNAVAILABLE);
2087 		} else if (strchr(optarg, ' ') != (char *)0) {
2088 		    Error("-t option cannot contain a space: `%s'",
2089 			  optarg);
2090 		    Bye(EX_UNAVAILABLE);
2091 		}
2092 		BuildString("textmsg ", textMsg);
2093 		BuildString(optarg, textMsg);
2094 		pcCmd = textMsg->string;
2095 		break;
2096 
2097 	    case 'U':
2098 #if HAVE_OPENSSL
2099 		optConf->sslrequired = FLAGFALSE;
2100 #endif
2101 		break;
2102 
2103 	    case 'u':
2104 		pcCmd = "hosts";
2105 		break;
2106 
2107 	    case 'W':
2108 		fLocal = 1;
2109 		/*fallthrough */
2110 	    case 'w':		/* who */
2111 		pcCmd = "group";
2112 		break;
2113 
2114 	    case 'x':
2115 		pcCmd = "examine";
2116 		break;
2117 
2118 	    case 'v':
2119 		fVerbose = 1;
2120 		break;
2121 
2122 	    case 'V':
2123 		fVersion = 1;
2124 		break;
2125 
2126 	    case 'Z':		/* only send cmd this host          */
2127 		fLocal = 1;
2128 		/*fallthough */
2129 	    case 'z':		/* send a command to the server   */
2130 		pcCmd = (char *)0;
2131 		for (isZap = sizeof(zap) / sizeof(ZAPS) - 1; isZap >= 0;
2132 		     isZap--) {
2133 		    char *token = (char *)0;
2134 		    char *str = (char *)0;
2135 		    if (zap[isZap].cmd == (char *)0)	/* skip non-action ones */
2136 			continue;
2137 		    BuildTmpString((char *)0);
2138 		    str = BuildTmpString(zap[isZap].opt);
2139 		    for (token = strtok(str, ", "); token != (char *)0;
2140 			 token = strtok(NULL, ", ")) {
2141 			if (strcasecmp(optarg, token) == 0) {
2142 			    pcCmd = zap[isZap].cmd;
2143 			    isZap++;
2144 			    break;
2145 			}
2146 		    }
2147 		    if (pcCmd)
2148 			break;
2149 		}
2150 		if (isZap < 0) {
2151 		    if (strcasecmp(optarg, "help") == 0) {
2152 			STRING *help;
2153 			help = AllocString();
2154 			BuildString("available -z commands:\n\n", help);
2155 			for (isZap = 0; isZap < sizeof(zap) / sizeof(ZAPS);
2156 			     isZap++) {
2157 			    char *str;
2158 			    BuildTmpString((char *)0);
2159 			    str =
2160 				BuildTmpStringPrint("    %16s   %s\n",
2161 						    zap[isZap].opt,
2162 						    zap[isZap].desc);
2163 			    BuildString(str, help);
2164 			}
2165 			Error(help->string);
2166 		    } else
2167 			Error("invalid -z command: `%s' (try `help')",
2168 			      optarg);
2169 		    Bye(EX_UNAVAILABLE);
2170 		}
2171 		break;
2172 
2173 	    case 'h':		/* huh? */
2174 		Usage(1);
2175 		Bye(EX_OK);
2176 
2177 	    case '\?':		/* huh? */
2178 		Usage(0);
2179 		Bye(EX_UNAVAILABLE);
2180 
2181 	    default:
2182 		Error("option %c needs a parameter", optopt);
2183 		Bye(EX_UNAVAILABLE);
2184 	}
2185     }
2186 
2187     if (fVersion) {
2188 	Version();
2189 	Bye(EX_OK);
2190     }
2191 #if !USE_IPV6
2192     ProbeInterfaces(INADDR_ANY);
2193 #endif
2194 
2195     if (readSystemConf)
2196 	ReadConf(CLIENTCONFIGFILE, FLAGFALSE);
2197 
2198     if (userConf == (char *)0) {
2199 	/* read the config files */
2200 	char *h = (char *)0;
2201 
2202 	if (((h = getenv("HOME")) == (char *)0) &&
2203 	    ((pwdMe = getpwuid(getuid())) == (struct passwd *)0)) {
2204 	    Error("$HOME does not exist and getpwuid fails: %d: %s",
2205 		  (int)(getuid()), strerror(errno));
2206 	} else {
2207 	    if (h == (char *)0) {
2208 		if (pwdMe->pw_dir == (char *)0 ||
2209 		    pwdMe->pw_dir[0] == '\000') {
2210 		    Error("Home directory for uid %d is not defined",
2211 			  (int)(getuid()));
2212 		    Bye(EX_UNAVAILABLE);
2213 		} else {
2214 		    h = pwdMe->pw_dir;
2215 		}
2216 	    }
2217 	}
2218 	if (h != (char *)0) {
2219 	    BuildTmpString((char *)0);
2220 	    BuildTmpString(h);
2221 	    h = BuildTmpString("/.consolerc");
2222 	    ReadConf(h, FLAGFALSE);
2223 	    BuildTmpString((char *)0);
2224 	}
2225     } else
2226 	ReadConf(userConf, FLAGTRUE);
2227 
2228     if (optConf->striphigh != FLAGUNKNOWN)
2229 	config->striphigh = optConf->striphigh;
2230     else if (pConfig->striphigh != FLAGUNKNOWN)
2231 	config->striphigh = pConfig->striphigh;
2232     else
2233 	config->striphigh = FLAGFALSE;
2234 
2235     if (optConf->escape != (char *)0)
2236 	ParseEsc(optConf->escape);
2237     else if (pConfig->escape != (char *)0)
2238 	ParseEsc(pConfig->escape);
2239 
2240     if (optConf->username != (char *)0)
2241 	config->username = StrDup(optConf->username);
2242     else if (pConfig->username != (char *)0)
2243 	config->username = StrDup(pConfig->username);
2244     else
2245 	config->username = (char *)0;
2246 
2247     if (optConf->master != (char *)0 && optConf->master[0] != '\000')
2248 	config->master = StrDup(optConf->master);
2249     else if (pConfig->master != (char *)0 && pConfig->master[0] != '\000')
2250 	config->master = StrDup(pConfig->master);
2251     else
2252 	config->master = StrDup(
2253 #if USE_UNIX_DOMAIN_SOCKETS
2254 				   UDSDIR
2255 #else
2256 				   MASTERHOST	/* which machine is current */
2257 #endif
2258 	    );
2259     if (config->master == (char *)0)
2260 	OutOfMem();
2261 
2262     if (optConf->port != (char *)0 && optConf->port[0] != '\000')
2263 	config->port = StrDup(optConf->port);
2264     else if (pConfig->port != (char *)0 && pConfig->port[0] != '\000')
2265 	config->port = StrDup(pConfig->port);
2266     else
2267 	config->port = StrDup(DEFPORT);
2268     if (config->port == (char *)0)
2269 	OutOfMem();
2270 
2271     if (optConf->replay != 0)
2272 	config->replay = optConf->replay;
2273     else if (pConfig->replay != 0)
2274 	config->replay = pConfig->replay;
2275     else
2276 	config->replay = 0;
2277 
2278     if (optConf->playback != 0)
2279 	config->playback = optConf->playback;
2280     else if (pConfig->playback != 0)
2281 	config->playback = pConfig->playback;
2282     else
2283 	config->playback = 0;
2284 
2285 #if HAVE_OPENSSL
2286     if (optConf->sslcredentials != (char *)0 &&
2287 	optConf->sslcredentials[0] != '\000')
2288 	config->sslcredentials = StrDup(optConf->sslcredentials);
2289     else if (pConfig->sslcredentials != (char *)0 &&
2290 	     pConfig->sslcredentials[0] != '\000')
2291 	config->sslcredentials = StrDup(pConfig->sslcredentials);
2292     else
2293 	config->sslcredentials = (char *)0;
2294     if (pConfig->sslcacertificatefile != (char *)0 &&
2295 	pConfig->sslcacertificatefile[0] != '\000')
2296 	config->sslcacertificatefile =
2297 	    StrDup(pConfig->sslcacertificatefile);
2298     else
2299 	config->sslcacertificatefile = (char *)0;
2300     if (pConfig->sslcacertificatepath != (char *)0 &&
2301 	pConfig->sslcacertificatepath[0] != '\000')
2302 	config->sslcacertificatepath =
2303 	    StrDup(pConfig->sslcacertificatepath);
2304     else
2305 	config->sslcacertificatepath = (char *)0;
2306     if (optConf->sslenabled != FLAGUNKNOWN)
2307 	config->sslenabled = optConf->sslenabled;
2308     else if (pConfig->sslenabled != FLAGUNKNOWN)
2309 	config->sslenabled = pConfig->sslenabled;
2310     else
2311 	config->sslenabled = FLAGTRUE;
2312 
2313     if (optConf->sslrequired != FLAGUNKNOWN)
2314 	config->sslrequired = optConf->sslrequired;
2315     else if (pConfig->sslrequired != FLAGUNKNOWN)
2316 	config->sslrequired = pConfig->sslrequired;
2317     else
2318 	config->sslrequired = FLAGTRUE;
2319 #endif
2320 
2321     /* finish resolving the command to do */
2322     if (pcCmd == (char *)0) {
2323 	pcCmd = "attach";
2324     }
2325 
2326     if (*pcCmd == 'a' || *pcCmd == 'f' || *pcCmd == 's') {
2327 	/* attach, force-attach, and spy */
2328 	if (optind >= argc) {
2329 	    Error("missing console name");
2330 	    Bye(EX_UNAVAILABLE);
2331 	}
2332 	if (cmdarg != (char *)0)
2333 	    free(cmdarg);
2334 	if ((cmdarg = StrDup(argv[optind++])) == (char *)0)
2335 	    OutOfMem();
2336     } else if (*pcCmd == 't') {
2337 	/* text message */
2338 	if (optind >= argc) {
2339 	    Error("missing message text");
2340 	    Bye(EX_UNAVAILABLE);
2341 	}
2342 	if (cmdarg != (char *)0)
2343 	    free(cmdarg);
2344 	if ((cmdarg = StrDup(argv[optind++])) == (char *)0)
2345 	    OutOfMem();
2346     } else if (*pcCmd == 'i' || *pcCmd == 'e' || *pcCmd == 'h' ||
2347 	       *pcCmd == 'g') {
2348 	/* info, e(x)amine, hosts (u), groups (w) */
2349 	if (optind < argc) {
2350 	    if (cmdarg != (char *)0)
2351 		free(cmdarg);
2352 	    if ((cmdarg = StrDup(argv[optind++])) == (char *)0)
2353 		OutOfMem();
2354 	}
2355     }
2356 
2357     if (optind < argc) {
2358 	Error("extra garbage on command line? (%s...)", argv[optind]);
2359 	Bye(EX_UNAVAILABLE);
2360     }
2361 #if !USE_UNIX_DOMAIN_SOCKETS
2362     /* Look for non-numeric characters */
2363     for (opt = 0; config->port[opt] != '\000'; opt++)
2364 	if (!isdigit((int)config->port[opt]))
2365 	    break;
2366 
2367     if (config->port[opt] == '\000') {
2368 	/* numeric only */
2369 	bindPort = atoi(config->port);
2370     } else {
2371 	/* non-numeric only */
2372 	struct servent *pSE;
2373 	if ((pSE =
2374 	     getservbyname(config->port, "tcp")) == (struct servent *)0) {
2375 	    Error("getservbyname(%s) failed", config->port);
2376 	    Bye(EX_UNAVAILABLE);
2377 	} else {
2378 	    bindPort = ntohs((unsigned short)pSE->s_port);
2379 	}
2380     }
2381 #endif
2382 
2383     if (config->username == (char *)0 || config->username[0] == '\000') {
2384 	if (config->username != (char *)0)
2385 	    free(config->username);
2386 	if (((config->username = getenv("LOGNAME")) == (char *)0) &&
2387 	    ((config->username = getenv("USER")) == (char *)0) &&
2388 	    ((pwdMe = getpwuid(getuid())) == (struct passwd *)0)) {
2389 	    Error
2390 		("$LOGNAME and $USER do not exist and getpwuid fails: %d: %s",
2391 		 (int)(getuid()), strerror(errno));
2392 	    Bye(EX_UNAVAILABLE);
2393 	}
2394 	if (config->username == (char *)0) {
2395 	    if (pwdMe->pw_name == (char *)0 || pwdMe->pw_name[0] == '\000') {
2396 		Error("Username for uid %d does not exist",
2397 		      (int)(getuid()));
2398 		Bye(EX_UNAVAILABLE);
2399 	    } else {
2400 		config->username = pwdMe->pw_name;
2401 	    }
2402 	}
2403 	if ((config->username = StrDup(config->username)) == (char *)0)
2404 	    OutOfMem();
2405     }
2406 
2407     if (execCmd == (STRING *)0)
2408 	execCmd = AllocString();
2409 
2410     SimpleSignal(SIGPIPE, SIG_IGN);
2411 
2412     cfstdout = FileOpenFD(1, simpleFile);
2413 
2414     BuildString((char *)0, acPorts);
2415     BuildStringChar('@', acPorts);
2416     BuildString(config->master, acPorts);
2417 
2418 #if HAVE_OPENSSL
2419     SetupSSL();			/* should only do if we want ssl - provide flag! */
2420 #endif
2421 
2422     /* stack up the commands for DoCmds() */
2423     cmdi = -1;
2424     cmds[++cmdi] = pcCmd;
2425 
2426     if (*pcCmd == 'q' || *pcCmd == 'v' || *pcCmd == 'p' || *pcCmd == 'r' ||
2427 	isZap) {
2428 	if (!fLocal)
2429 	    cmds[++cmdi] = "master";
2430     } else if (*pcCmd == 'a' || *pcCmd == 'f' || *pcCmd == 's') {
2431 	ValidateEsc();
2432 	cmds[++cmdi] = "call";
2433 	interact = FLAGTRUE;
2434     } else if (cmdarg != (char *)0 &&
2435 	       (*pcCmd == 'i' || *pcCmd == 'e' || *pcCmd == 'h' ||
2436 		*pcCmd == 'g')) {
2437 	cmds[++cmdi] = "call";
2438     } else {
2439 	cmds[++cmdi] = "groups";
2440 	if (!fLocal)
2441 	    cmds[++cmdi] = "master";
2442     }
2443 
2444 #if defined(TIOCGWINSZ)
2445     if (interact == FLAGTRUE) {
2446 	int fd;
2447 # if HAVE_MEMSET
2448 	memset((void *)(&ws), '\000', sizeof(ws));
2449 # else
2450 	bzero((char *)(&ws), sizeof(ws));
2451 # endif
2452 	if ((fd = open("/dev/tty", O_RDONLY)) != -1) {
2453 	    ioctl(fd, TIOCGWINSZ, &ws);
2454 	}
2455 	close(fd);
2456     }
2457 #endif
2458 
2459     if (fDebug) {
2460 	int i;
2461 	for (i = cmdi; i >= 0; i--) {
2462 	    CONDDEBUG((1, "cmds[%d] = %s", i, cmds[i]));
2463 	}
2464     }
2465 
2466     for (;;) {
2467 	if (gotoConsole == (CONSFILE *)0)
2468 	    DoCmds(config->master, acPorts->string, cmdi);
2469 	else
2470 	    Interact(gotoConsole, gotoName);
2471 
2472 	/* if we didn't ask for another console, done */
2473 	if (gotoConsole == (CONSFILE *)0 && prevConsole == (CONSFILE *)0)
2474 	    break;
2475 
2476 	if (consoleName == (STRING *)0)
2477 	    consoleName = AllocString();
2478 	C2Raw();
2479 	if (prevConsole == (CONSFILE *)0)
2480 	    FileWrite(cfstdout, FLAGFALSE, "console: ", 9);
2481 	else
2482 	    FileWrite(cfstdout, FLAGFALSE, "[console: ", 10);
2483 	GetUserInput(consoleName);
2484 	FileWrite(cfstdout, FLAGFALSE, "]\r\n", 3);
2485 	C2Cooked();
2486 	if (consoleName->used > 1) {
2487 	    if (cmdarg != (char *)0)
2488 		free(cmdarg);
2489 	    if ((cmdarg = StrDup(consoleName->string)) == (char *)0)
2490 		OutOfMem();
2491 	    if (prevConsole == (CONSFILE *)0) {
2492 		prevConsole = gotoConsole;
2493 		gotoConsole = (CONSFILE *)0;
2494 		prevName = gotoName;
2495 		gotoName = (char *)0;
2496 	    }
2497 	} else {
2498 	    if (prevConsole != (CONSFILE *)0) {
2499 		gotoConsole = prevConsole;
2500 		prevConsole = (CONSFILE *)0;
2501 		gotoName = prevName;
2502 		prevName = (char *)0;
2503 	    }
2504 	}
2505     }
2506 
2507     if (cmdarg != (char *)0)
2508 	free(cmdarg);
2509 
2510     if (*pcCmd == 'd')
2511 	FilePrint(cfstdout, FLAGFALSE, "disconnected %d %s\n",
2512 		  disconnectCount,
2513 		  disconnectCount == 1 ? "user" : "users");
2514 
2515     Bye(0);
2516     return 0;			/* noop - Bye() terminates us */
2517 }
2518