Annotation of src/usr.bin/ssh/mux.c, Revision 1.3
1.3 ! djm 1: /* $OpenBSD: mux.c,v 1.2 2008/06/12 03:40:52 djm Exp $ */
1.1 djm 2: /*
3: * Copyright (c) 2002-2008 Damien Miller <djm@openbsd.org>
4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the above
7: * copyright notice and this permission notice appear in all copies.
8: *
9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16: */
17:
18: /* ssh session multiplexing support */
19:
1.2 djm 20: /*
21: * TODO:
22: * 1. partial reads in muxserver_accept_control (maybe make channels
23: * from accepted connections)
24: * 2. Better signalling from master to slave, especially passing of
25: * error messages
26: * 3. Better fall-back from mux slave error to new connection.
27: * 3. Add/delete forwardings via slave
28: * 4. ExitOnForwardingFailure (after #3 obviously)
29: * 5. Maybe extension mechanisms for multi-X11/multi-agent forwarding
30: * 6. Document the mux mini-protocol somewhere.
1.3 ! djm 31: * 7. Support ~^Z in mux slaves.
! 32: * 8. Inspect or control sessions in master.
! 33: * 9. If we ever support the "signal" channel request, send signals on
! 34: * sessions in master.
1.2 djm 35: */
36:
1.1 djm 37: #include <sys/types.h>
38: #include <sys/param.h>
39: #include <sys/queue.h>
40: #include <sys/stat.h>
41: #include <sys/socket.h>
42: #include <sys/un.h>
43:
44: #include <errno.h>
45: #include <fcntl.h>
46: #include <signal.h>
47: #include <stdarg.h>
48: #include <stddef.h>
49: #include <stdlib.h>
50: #include <stdio.h>
51: #include <string.h>
52: #include <unistd.h>
53: #include <util.h>
54: #include <paths.h>
55:
56: #include "xmalloc.h"
57: #include "log.h"
58: #include "ssh.h"
59: #include "pathnames.h"
60: #include "misc.h"
61: #include "match.h"
62: #include "buffer.h"
63: #include "channels.h"
64: #include "msg.h"
65: #include "packet.h"
66: #include "monitor_fdpass.h"
67: #include "sshpty.h"
68: #include "key.h"
69: #include "readconf.h"
70: #include "clientloop.h"
71:
72: /* from ssh.c */
73: extern int tty_flag;
74: extern Options options;
75: extern int stdin_null_flag;
76: extern char *host;
77: int subsystem_flag;
78: extern Buffer command;
79:
1.2 djm 80: /* Context for session open confirmation callback */
81: struct mux_session_confirm_ctx {
82: int want_tty;
83: int want_subsys;
84: int want_x_fwd;
85: int want_agent_fwd;
86: Buffer cmd;
87: char *term;
88: struct termios tio;
89: char **env;
90: };
91:
1.1 djm 92: /* fd to control socket */
93: int muxserver_sock = -1;
94:
95: /* Multiplexing control command */
96: u_int muxclient_command = 0;
97:
98: /* Set when signalled. */
99: static volatile sig_atomic_t muxclient_terminate = 0;
100:
101: /* PID of multiplex server */
102: static u_int muxserver_pid = 0;
103:
104:
105: /* ** Multiplexing master support */
106:
107: /* Prepare a mux master to listen on a Unix domain socket. */
108: void
109: muxserver_listen(void)
110: {
111: struct sockaddr_un addr;
112: mode_t old_umask;
113:
114: if (options.control_path == NULL ||
115: options.control_master == SSHCTL_MASTER_NO)
116: return;
117:
118: debug("setting up multiplex master socket");
119:
120: memset(&addr, '\0', sizeof(addr));
121: addr.sun_family = AF_UNIX;
122: addr.sun_len = offsetof(struct sockaddr_un, sun_path) +
123: strlen(options.control_path) + 1;
124:
125: if (strlcpy(addr.sun_path, options.control_path,
126: sizeof(addr.sun_path)) >= sizeof(addr.sun_path))
127: fatal("ControlPath too long");
128:
129: if ((muxserver_sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
130: fatal("%s socket(): %s", __func__, strerror(errno));
131:
132: old_umask = umask(0177);
133: if (bind(muxserver_sock, (struct sockaddr *)&addr, addr.sun_len) == -1) {
134: muxserver_sock = -1;
135: if (errno == EINVAL || errno == EADDRINUSE)
136: fatal("ControlSocket %s already exists",
137: options.control_path);
138: else
139: fatal("%s bind(): %s", __func__, strerror(errno));
140: }
141: umask(old_umask);
142:
143: if (listen(muxserver_sock, 64) == -1)
144: fatal("%s listen(): %s", __func__, strerror(errno));
145:
146: set_nonblock(muxserver_sock);
147: }
148:
149: /* Callback on open confirmation in mux master for a mux client session. */
150: static void
1.2 djm 151: mux_session_confirm(int id, void *arg)
1.1 djm 152: {
153: struct mux_session_confirm_ctx *cctx = arg;
154: const char *display;
155: Channel *c;
156: int i;
157:
158: if (cctx == NULL)
159: fatal("%s: cctx == NULL", __func__);
160: if ((c = channel_lookup(id)) == NULL)
161: fatal("%s: no channel for id %d", __func__, id);
162:
163: display = getenv("DISPLAY");
164: if (cctx->want_x_fwd && options.forward_x11 && display != NULL) {
165: char *proto, *data;
166: /* Get reasonable local authentication information. */
167: client_x11_get_proto(display, options.xauth_location,
168: options.forward_x11_trusted, &proto, &data);
169: /* Request forwarding with authentication spoofing. */
170: debug("Requesting X11 forwarding with authentication spoofing.");
171: x11_request_forwarding_with_spoofing(id, display, proto, data);
172: /* XXX wait for reply */
173: }
174:
175: if (cctx->want_agent_fwd && options.forward_agent) {
176: debug("Requesting authentication agent forwarding.");
177: channel_request_start(id, "auth-agent-req@openssh.com", 0);
178: packet_send();
179: }
180:
181: client_session2_setup(id, cctx->want_tty, cctx->want_subsys,
182: cctx->term, &cctx->tio, c->rfd, &cctx->cmd, cctx->env);
183:
184: c->open_confirm_ctx = NULL;
185: buffer_free(&cctx->cmd);
186: xfree(cctx->term);
187: if (cctx->env != NULL) {
188: for (i = 0; cctx->env[i] != NULL; i++)
189: xfree(cctx->env[i]);
190: xfree(cctx->env);
191: }
192: xfree(cctx);
193: }
194:
195: /*
196: * Accept a connection on the mux master socket and process the
197: * client's request. Returns flag indicating whether mux master should
198: * begin graceful close.
199: */
200: int
201: muxserver_accept_control(void)
202: {
203: Buffer m;
204: Channel *c;
205: int client_fd, new_fd[3], ver, allowed, window, packetmax;
206: socklen_t addrlen;
207: struct sockaddr_storage addr;
208: struct mux_session_confirm_ctx *cctx;
209: char *cmd;
1.2 djm 210: u_int i, j, len, env_len, mux_command, flags, escape_char;
1.1 djm 211: uid_t euid;
212: gid_t egid;
213: int start_close = 0;
214:
215: /*
216: * Accept connection on control socket
217: */
218: memset(&addr, 0, sizeof(addr));
219: addrlen = sizeof(addr);
220: if ((client_fd = accept(muxserver_sock,
221: (struct sockaddr*)&addr, &addrlen)) == -1) {
222: error("%s accept: %s", __func__, strerror(errno));
223: return 0;
224: }
225:
226: if (getpeereid(client_fd, &euid, &egid) < 0) {
227: error("%s getpeereid failed: %s", __func__, strerror(errno));
228: close(client_fd);
229: return 0;
230: }
231: if ((euid != 0) && (getuid() != euid)) {
232: error("control mode uid mismatch: peer euid %u != uid %u",
233: (u_int) euid, (u_int) getuid());
234: close(client_fd);
235: return 0;
236: }
237:
238: /* XXX handle asynchronously */
239: unset_nonblock(client_fd);
240:
241: /* Read command */
242: buffer_init(&m);
243: if (ssh_msg_recv(client_fd, &m) == -1) {
244: error("%s: client msg_recv failed", __func__);
245: close(client_fd);
246: buffer_free(&m);
247: return 0;
248: }
249: if ((ver = buffer_get_char(&m)) != SSHMUX_VER) {
250: error("%s: wrong client version %d", __func__, ver);
251: buffer_free(&m);
252: close(client_fd);
253: return 0;
254: }
255:
256: allowed = 1;
257: mux_command = buffer_get_int(&m);
258: flags = buffer_get_int(&m);
259:
260: buffer_clear(&m);
261:
262: switch (mux_command) {
263: case SSHMUX_COMMAND_OPEN:
264: if (options.control_master == SSHCTL_MASTER_ASK ||
265: options.control_master == SSHCTL_MASTER_AUTO_ASK)
266: allowed = ask_permission("Allow shared connection "
267: "to %s? ", host);
268: /* continue below */
269: break;
270: case SSHMUX_COMMAND_TERMINATE:
271: if (options.control_master == SSHCTL_MASTER_ASK ||
272: options.control_master == SSHCTL_MASTER_AUTO_ASK)
273: allowed = ask_permission("Terminate shared connection "
274: "to %s? ", host);
275: if (allowed)
276: start_close = 1;
277: /* FALLTHROUGH */
278: case SSHMUX_COMMAND_ALIVE_CHECK:
279: /* Reply for SSHMUX_COMMAND_TERMINATE and ALIVE_CHECK */
280: buffer_clear(&m);
281: buffer_put_int(&m, allowed);
282: buffer_put_int(&m, getpid());
283: if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) {
284: error("%s: client msg_send failed", __func__);
285: close(client_fd);
286: buffer_free(&m);
287: return start_close;
288: }
289: buffer_free(&m);
290: close(client_fd);
291: return start_close;
292: default:
293: error("Unsupported command %d", mux_command);
294: buffer_free(&m);
295: close(client_fd);
296: return 0;
297: }
298:
299: /* Reply for SSHMUX_COMMAND_OPEN */
300: buffer_clear(&m);
301: buffer_put_int(&m, allowed);
302: buffer_put_int(&m, getpid());
303: if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) {
304: error("%s: client msg_send failed", __func__);
305: close(client_fd);
306: buffer_free(&m);
307: return 0;
308: }
309:
310: if (!allowed) {
311: error("Refused control connection");
312: close(client_fd);
313: buffer_free(&m);
314: return 0;
315: }
316:
317: buffer_clear(&m);
318: if (ssh_msg_recv(client_fd, &m) == -1) {
319: error("%s: client msg_recv failed", __func__);
320: close(client_fd);
321: buffer_free(&m);
322: return 0;
323: }
324: if ((ver = buffer_get_char(&m)) != SSHMUX_VER) {
325: error("%s: wrong client version %d", __func__, ver);
326: buffer_free(&m);
327: close(client_fd);
328: return 0;
329: }
330:
331: cctx = xcalloc(1, sizeof(*cctx));
332: cctx->want_tty = (flags & SSHMUX_FLAG_TTY) != 0;
333: cctx->want_subsys = (flags & SSHMUX_FLAG_SUBSYS) != 0;
334: cctx->want_x_fwd = (flags & SSHMUX_FLAG_X11_FWD) != 0;
335: cctx->want_agent_fwd = (flags & SSHMUX_FLAG_AGENT_FWD) != 0;
336: cctx->term = buffer_get_string(&m, &len);
1.2 djm 337: escape_char = buffer_get_int(&m);
1.1 djm 338:
339: cmd = buffer_get_string(&m, &len);
340: buffer_init(&cctx->cmd);
341: buffer_append(&cctx->cmd, cmd, strlen(cmd));
342:
343: env_len = buffer_get_int(&m);
344: env_len = MIN(env_len, 4096);
345: debug3("%s: receiving %d env vars", __func__, env_len);
346: if (env_len != 0) {
347: cctx->env = xcalloc(env_len + 1, sizeof(*cctx->env));
348: for (i = 0; i < env_len; i++)
349: cctx->env[i] = buffer_get_string(&m, &len);
350: cctx->env[i] = NULL;
351: }
352:
353: debug2("%s: accepted tty %d, subsys %d, cmd %s", __func__,
354: cctx->want_tty, cctx->want_subsys, cmd);
355: xfree(cmd);
356:
357: /* Gather fds from client */
358: for(i = 0; i < 3; i++) {
359: if ((new_fd[i] = mm_receive_fd(client_fd)) == -1) {
360: error("%s: failed to receive fd %d from slave",
361: __func__, i);
362: for (j = 0; j < i; j++)
363: close(new_fd[j]);
364: for (j = 0; j < env_len; j++)
365: xfree(cctx->env[j]);
366: if (env_len > 0)
367: xfree(cctx->env);
368: xfree(cctx->term);
369: buffer_free(&cctx->cmd);
370: close(client_fd);
371: xfree(cctx);
372: return 0;
373: }
374: }
375:
376: debug2("%s: got fds stdin %d, stdout %d, stderr %d", __func__,
377: new_fd[0], new_fd[1], new_fd[2]);
378:
379: /* Try to pick up ttymodes from client before it goes raw */
380: if (cctx->want_tty && tcgetattr(new_fd[0], &cctx->tio) == -1)
381: error("%s: tcgetattr: %s", __func__, strerror(errno));
382:
383: /* This roundtrip is just for synchronisation of ttymodes */
384: buffer_clear(&m);
385: if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) {
386: error("%s: client msg_send failed", __func__);
387: close(client_fd);
388: close(new_fd[0]);
389: close(new_fd[1]);
390: close(new_fd[2]);
391: buffer_free(&m);
392: xfree(cctx->term);
393: if (env_len != 0) {
394: for (i = 0; i < env_len; i++)
395: xfree(cctx->env[i]);
396: xfree(cctx->env);
397: }
398: return 0;
399: }
400: buffer_free(&m);
401:
402: /* enable nonblocking unless tty */
403: if (!isatty(new_fd[0]))
404: set_nonblock(new_fd[0]);
405: if (!isatty(new_fd[1]))
406: set_nonblock(new_fd[1]);
407: if (!isatty(new_fd[2]))
408: set_nonblock(new_fd[2]);
409:
410: set_nonblock(client_fd);
411:
412: window = CHAN_SES_WINDOW_DEFAULT;
413: packetmax = CHAN_SES_PACKET_DEFAULT;
414: if (cctx->want_tty) {
415: window >>= 1;
416: packetmax >>= 1;
417: }
418:
419: c = channel_new("session", SSH_CHANNEL_OPENING,
420: new_fd[0], new_fd[1], new_fd[2], window, packetmax,
421: CHAN_EXTENDED_WRITE, "client-session", /*nonblock*/0);
422:
423: c->ctl_fd = client_fd;
1.2 djm 424: if (cctx->want_tty && escape_char != 0xffffffff) {
425: channel_register_filter(c->self,
426: client_simple_escape_filter, NULL,
427: client_new_escape_filter_ctx((int)escape_char));
428: }
1.1 djm 429:
430: debug3("%s: channel_new: %d", __func__, c->self);
431:
432: channel_send_open(c->self);
1.2 djm 433: channel_register_open_confirm(c->self, mux_session_confirm, cctx);
1.1 djm 434: return 0;
435: }
436:
437: /* ** Multiplexing client support */
438:
439: /* Exit signal handler */
440: static void
441: control_client_sighandler(int signo)
442: {
443: muxclient_terminate = signo;
444: }
445:
446: /*
447: * Relay signal handler - used to pass some signals from mux client to
448: * mux master.
449: */
450: static void
451: control_client_sigrelay(int signo)
452: {
453: int save_errno = errno;
454:
455: if (muxserver_pid > 1)
456: kill(muxserver_pid, signo);
457:
458: errno = save_errno;
459: }
460:
461: /* Check mux client environment variables before passing them to mux master. */
462: static int
463: env_permitted(char *env)
464: {
465: int i, ret;
466: char name[1024], *cp;
467:
468: if ((cp = strchr(env, '=')) == NULL || cp == env)
469: return (0);
470: ret = snprintf(name, sizeof(name), "%.*s", (int)(cp - env), env);
471: if (ret <= 0 || (size_t)ret >= sizeof(name))
472: fatal("env_permitted: name '%.100s...' too long", env);
473:
474: for (i = 0; i < options.num_send_env; i++)
475: if (match_pattern(name, options.send_env[i]))
476: return (1);
477:
478: return (0);
479: }
480:
481: /* Multiplex client main loop. */
482: void
483: muxclient(const char *path)
484: {
485: struct sockaddr_un addr;
486: int i, r, fd, sock, exitval[2], num_env;
487: Buffer m;
488: char *term;
489: extern char **environ;
490: u_int flags;
491:
492: if (muxclient_command == 0)
493: muxclient_command = SSHMUX_COMMAND_OPEN;
494:
495: switch (options.control_master) {
496: case SSHCTL_MASTER_AUTO:
497: case SSHCTL_MASTER_AUTO_ASK:
498: debug("auto-mux: Trying existing master");
499: /* FALLTHROUGH */
500: case SSHCTL_MASTER_NO:
501: break;
502: default:
503: return;
504: }
505:
506: memset(&addr, '\0', sizeof(addr));
507: addr.sun_family = AF_UNIX;
508: addr.sun_len = offsetof(struct sockaddr_un, sun_path) +
509: strlen(path) + 1;
510:
511: if (strlcpy(addr.sun_path, path,
512: sizeof(addr.sun_path)) >= sizeof(addr.sun_path))
513: fatal("ControlPath too long");
514:
515: if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
516: fatal("%s socket(): %s", __func__, strerror(errno));
517:
518: if (connect(sock, (struct sockaddr *)&addr, addr.sun_len) == -1) {
519: if (muxclient_command != SSHMUX_COMMAND_OPEN) {
520: fatal("Control socket connect(%.100s): %s", path,
521: strerror(errno));
522: }
523: if (errno == ENOENT)
524: debug("Control socket \"%.100s\" does not exist", path);
525: else {
526: error("Control socket connect(%.100s): %s", path,
527: strerror(errno));
528: }
529: close(sock);
530: return;
531: }
532:
533: if (stdin_null_flag) {
534: if ((fd = open(_PATH_DEVNULL, O_RDONLY)) == -1)
535: fatal("open(/dev/null): %s", strerror(errno));
536: if (dup2(fd, STDIN_FILENO) == -1)
537: fatal("dup2: %s", strerror(errno));
538: if (fd > STDERR_FILENO)
539: close(fd);
540: }
541:
542: term = getenv("TERM");
543:
544: flags = 0;
545: if (tty_flag)
546: flags |= SSHMUX_FLAG_TTY;
547: if (subsystem_flag)
548: flags |= SSHMUX_FLAG_SUBSYS;
549: if (options.forward_x11)
550: flags |= SSHMUX_FLAG_X11_FWD;
551: if (options.forward_agent)
552: flags |= SSHMUX_FLAG_AGENT_FWD;
553:
554: signal(SIGPIPE, SIG_IGN);
555:
556: buffer_init(&m);
557:
558: /* Send our command to server */
559: buffer_put_int(&m, muxclient_command);
560: buffer_put_int(&m, flags);
561: if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1)
562: fatal("%s: msg_send", __func__);
563: buffer_clear(&m);
564:
565: /* Get authorisation status and PID of controlee */
566: if (ssh_msg_recv(sock, &m) == -1)
567: fatal("%s: msg_recv", __func__);
568: if (buffer_get_char(&m) != SSHMUX_VER)
569: fatal("%s: wrong version", __func__);
570: if (buffer_get_int(&m) != 1)
571: fatal("Connection to master denied");
572: muxserver_pid = buffer_get_int(&m);
573:
574: buffer_clear(&m);
575:
576: switch (muxclient_command) {
577: case SSHMUX_COMMAND_ALIVE_CHECK:
578: fprintf(stderr, "Master running (pid=%d)\r\n",
579: muxserver_pid);
580: exit(0);
581: case SSHMUX_COMMAND_TERMINATE:
582: fprintf(stderr, "Exit request sent.\r\n");
583: exit(0);
584: case SSHMUX_COMMAND_OPEN:
1.2 djm 585: buffer_put_cstring(&m, term ? term : "");
586: if (options.escape_char == SSH_ESCAPECHAR_NONE)
587: buffer_put_int(&m, 0xffffffff);
588: else
589: buffer_put_int(&m, options.escape_char);
590: buffer_append(&command, "\0", 1);
591: buffer_put_cstring(&m, buffer_ptr(&command));
592:
593: if (options.num_send_env == 0 || environ == NULL) {
594: buffer_put_int(&m, 0);
595: } else {
596: /* Pass environment */
597: num_env = 0;
598: for (i = 0; environ[i] != NULL; i++) {
599: if (env_permitted(environ[i]))
600: num_env++; /* Count */
601: }
602: buffer_put_int(&m, num_env);
603: for (i = 0; environ[i] != NULL && num_env >= 0; i++) {
604: if (env_permitted(environ[i])) {
605: num_env--;
606: buffer_put_cstring(&m, environ[i]);
607: }
608: }
609: }
1.1 djm 610: break;
611: default:
1.2 djm 612: fatal("unrecognised muxclient_command %d", muxclient_command);
1.1 djm 613: }
614:
615: if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1)
616: fatal("%s: msg_send", __func__);
617:
618: if (mm_send_fd(sock, STDIN_FILENO) == -1 ||
619: mm_send_fd(sock, STDOUT_FILENO) == -1 ||
620: mm_send_fd(sock, STDERR_FILENO) == -1)
621: fatal("%s: send fds failed", __func__);
622:
623: /* Wait for reply, so master has a chance to gather ttymodes */
624: buffer_clear(&m);
625: if (ssh_msg_recv(sock, &m) == -1)
626: fatal("%s: msg_recv", __func__);
627: if (buffer_get_char(&m) != SSHMUX_VER)
628: fatal("%s: wrong version", __func__);
629: buffer_free(&m);
630:
631: signal(SIGHUP, control_client_sighandler);
632: signal(SIGINT, control_client_sighandler);
633: signal(SIGTERM, control_client_sighandler);
634: signal(SIGWINCH, control_client_sigrelay);
635:
636: if (tty_flag)
637: enter_raw_mode();
638:
639: /*
640: * Stick around until the controlee closes the client_fd.
641: * Before it does, it is expected to write this process' exit
642: * value (one int). This process must read the value and wait for
643: * the closure of the client_fd; if this one closes early, the
644: * multiplex master will terminate early too (possibly losing data).
645: */
646: exitval[0] = 0;
647: for (i = 0; !muxclient_terminate && i < (int)sizeof(exitval);) {
648: r = read(sock, (char *)exitval + i, sizeof(exitval) - i);
649: if (r == 0) {
650: debug2("Received EOF from master");
651: break;
652: }
653: if (r == -1) {
654: if (errno == EINTR)
655: continue;
656: fatal("%s: read %s", __func__, strerror(errno));
657: }
658: i += r;
659: }
660:
661: close(sock);
662: leave_raw_mode();
663: if (i > (int)sizeof(int))
664: fatal("%s: master returned too much data (%d > %lu)",
665: __func__, i, sizeof(int));
666: if (muxclient_terminate) {
667: debug2("Exiting on signal %d", muxclient_terminate);
668: exitval[0] = 255;
669: } else if (i < (int)sizeof(int)) {
670: debug2("Control master terminated unexpectedly");
671: exitval[0] = 255;
672: } else
673: debug2("Received exit status from master %d", exitval[0]);
674:
675: if (tty_flag && options.log_level != SYSLOG_LEVEL_QUIET)
676: fprintf(stderr, "Shared connection to %s closed.\r\n", host);
677:
678: exit(exitval[0]);
679: }