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