- djm@cvs.openbsd.org 2004/11/07 00:01:46
     [clientloop.c clientloop.h ssh.1 ssh.c]
     add basic control of a running multiplex master connection; including the
     ability to check its status and request it to exit; ok markus@
diff --git a/ChangeLog b/ChangeLog
index 8711874..fe96f8d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -4,6 +4,10 @@
      [sftp.c]
      command editing and history support via libedit; ok markus@
      thanks to hshoexer@ and many testers on tech@ too
+   - djm@cvs.openbsd.org 2004/11/07 00:01:46
+     [clientloop.c clientloop.h ssh.1 ssh.c]
+     add basic control of a running multiplex master connection; including the
+     ability to check its status and request it to exit; ok markus@
 
 20041105
  - (dtucker) OpenBSD CVS Sync
@@ -1848,4 +1852,4 @@
    - (djm) Trim deprecated options from INSTALL. Mention UsePAM
    - (djm) Fix quote handling in sftp; Patch from admorten AT umich.edu
 
-$Id: ChangeLog,v 1.3579 2004/11/07 09:04:10 dtucker Exp $
+$Id: ChangeLog,v 1.3580 2004/11/07 09:06:19 dtucker Exp $
diff --git a/clientloop.c b/clientloop.c
index d77337b..033a98a 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -59,7 +59,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: clientloop.c,v 1.133 2004/10/29 22:53:56 djm Exp $");
+RCSID("$OpenBSD: clientloop.c,v 1.134 2004/11/07 00:01:46 djm Exp $");
 
 #include "ssh.h"
 #include "ssh1.h"
@@ -561,7 +561,7 @@
 	struct sockaddr_storage addr;
 	struct confirm_ctx *cctx;
 	char *cmd;
-	u_int len, env_len;
+	u_int len, env_len, command, flags;
 	uid_t euid;
 	gid_t egid;
 
@@ -591,24 +591,74 @@
 		return;
 	}
 
-	allowed = 1;
-	if (options.control_master == 2)
-		allowed = ask_permission("Allow shared connection to %s? ",
-		    host);
-
 	unset_nonblock(client_fd);
 
+	/* Read command */
 	buffer_init(&m);
+	if (ssh_msg_recv(client_fd, &m) == -1) {
+		error("%s: client msg_recv failed", __func__);
+		close(client_fd);
+		buffer_free(&m);
+		return;
+	}
+	if ((ver = buffer_get_char(&m)) != 1) {
+		error("%s: wrong client version %d", __func__, ver);
+		buffer_free(&m);
+		close(client_fd);
+		return;
+	}
 
+	allowed = 1;
+	command = buffer_get_int(&m);
+	flags = buffer_get_int(&m);
+
+	buffer_clear(&m);
+
+	switch (command) {
+	case SSHMUX_COMMAND_OPEN:
+		if (options.control_master == 2)
+			allowed = ask_permission("Allow shared connection "
+			    "to %s? ", host);
+		/* continue below */
+		break;
+	case SSHMUX_COMMAND_TERMINATE:
+		if (options.control_master == 2)
+			allowed = ask_permission("Terminate shared connection "
+			    "to %s? ", host);
+		if (allowed)
+			quit_pending = 1;
+		/* FALLTHROUGH */	
+	case SSHMUX_COMMAND_ALIVE_CHECK:
+		/* Reply for SSHMUX_COMMAND_TERMINATE and ALIVE_CHECK */
+		buffer_clear(&m);
+		buffer_put_int(&m, allowed);
+		buffer_put_int(&m, getpid());
+		if (ssh_msg_send(client_fd, /* version */1, &m) == -1) {
+			error("%s: client msg_send failed", __func__);
+			close(client_fd);
+			buffer_free(&m);
+			return;
+		}
+		buffer_free(&m);
+		close(client_fd);
+		return;
+	default:
+		error("Unsupported command %d", command);
+		buffer_free(&m);
+		close(client_fd);
+		return;
+	}
+
+	/* Reply for SSHMUX_COMMAND_OPEN */
+	buffer_clear(&m);
 	buffer_put_int(&m, allowed);
 	buffer_put_int(&m, getpid());
-	if (ssh_msg_send(client_fd, /* version */0, &m) == -1) {
+	if (ssh_msg_send(client_fd, /* version */1, &m) == -1) {
 		error("%s: client msg_send failed", __func__);
 		close(client_fd);
 		buffer_free(&m);
 		return;
 	}
-	buffer_clear(&m);
 
 	if (!allowed) {
 		error("Refused control connection");
@@ -617,14 +667,14 @@
 		return;
 	}
 
+	buffer_clear(&m);
 	if (ssh_msg_recv(client_fd, &m) == -1) {
 		error("%s: client msg_recv failed", __func__);
 		close(client_fd);
 		buffer_free(&m);
 		return;
 	}
-
-	if ((ver = buffer_get_char(&m)) != 0) {
+	if ((ver = buffer_get_char(&m)) != 1) {
 		error("%s: wrong client version %d", __func__, ver);
 		buffer_free(&m);
 		close(client_fd);
@@ -633,9 +683,8 @@
 
 	cctx = xmalloc(sizeof(*cctx));
 	memset(cctx, 0, sizeof(*cctx));
-
-	cctx->want_tty = buffer_get_int(&m);
-	cctx->want_subsys = buffer_get_int(&m);
+	cctx->want_tty = (flags & SSHMUX_FLAG_TTY) != 0;
+	cctx->want_subsys = (flags & SSHMUX_FLAG_SUBSYS) != 0;
 	cctx->term = buffer_get_string(&m, &len);
 
 	cmd = buffer_get_string(&m, &len);
@@ -667,14 +716,21 @@
 	if (cctx->want_tty && tcgetattr(new_fd[0], &cctx->tio) == -1)
 		error("%s: tcgetattr: %s", __func__, strerror(errno));
 
+	/* This roundtrip is just for synchronisation of ttymodes */
 	buffer_clear(&m);
-	if (ssh_msg_send(client_fd, /* version */0, &m) == -1) {
+	if (ssh_msg_send(client_fd, /* version */1, &m) == -1) {
 		error("%s: client msg_send failed", __func__);
 		close(client_fd);
 		close(new_fd[0]);
 		close(new_fd[1]);
 		close(new_fd[2]);
 		buffer_free(&m);
+		xfree(cctx->term);
+		if (env_len != 0) {
+			for (i = 0; i < env_len; i++)
+				xfree(cctx->env[i]);
+			xfree(cctx->env);
+		}
 		return;
 	}
 	buffer_free(&m);
diff --git a/clientloop.h b/clientloop.h
index 9992d59..b23c111 100644
--- a/clientloop.h
+++ b/clientloop.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: clientloop.h,v 1.11 2004/07/11 17:48:47 deraadt Exp $	*/
+/*	$OpenBSD: clientloop.h,v 1.12 2004/11/07 00:01:46 djm Exp $	*/
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -40,3 +40,11 @@
 void	 client_global_request_reply_fwd(int, u_int32_t, void *);
 void	 client_session2_setup(int, int, int, const char *, struct termios *,
 	    int, Buffer *, char **, dispatch_fn *);
+
+/* Multiplexing control protocol flags */
+#define SSHMUX_COMMAND_OPEN		1	/* Open new connection */
+#define SSHMUX_COMMAND_ALIVE_CHECK	2	/* Check master is alive */
+#define SSHMUX_COMMAND_TERMINATE	3	/* Ask master to exit */
+
+#define SSHMUX_FLAG_TTY			(1)	/* Request tty on open */
+#define SSHMUX_FLAG_SUBSYS		(1<<1)	/* Subsystem request on open */
diff --git a/ssh.1 b/ssh.1
index 06cb60c..ec83319 100644
--- a/ssh.1
+++ b/ssh.1
@@ -34,7 +34,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: ssh.1,v 1.197 2004/10/07 10:10:24 djm Exp $
+.\" $OpenBSD: ssh.1,v 1.198 2004/11/07 00:01:46 djm Exp $
 .Dd September 25, 1999
 .Dt SSH 1
 .Os
@@ -62,6 +62,7 @@
 .Ek
 .Op Fl l Ar login_name
 .Op Fl m Ar mac_spec
+.Op Fl O Ar ctl_cmd
 .Op Fl o Ar option
 .Bk -words
 .Op Fl p Ar port
@@ -74,7 +75,7 @@
 .Sm on
 .Xc
 .Oc
-.Op Fl S Ar ctl
+.Op Fl S Ar ctl_path
 .Oo Ar user Ns @ Oc Ns Ar hostname
 .Op Ar command
 .Sh DESCRIPTION
@@ -613,6 +614,18 @@
 See the
 .Cm MACs
 keyword for more information.
+.It Fl O Ar ctl_cmd
+Control an active connection multiplexing master process.
+When the
+.Fl O
+option is specified, the 
+.Ar ctl_cmd
+argument is interpreted and passed to the master process.
+Valid commands are:
+.Dq check
+(check that the master process is running) and 
+.Dq exit
+(request the master to exit).
 .It Fl N
 Do not execute a remote command.
 This is useful for just forwarding ports
@@ -735,7 +748,7 @@
 .Ar hostport .
 .Xc
 .Sm on
-.It Fl S Ar ctl
+.It Fl S Ar ctl_path
 Specifies the location of a control socket for connection sharing.
 Refer to the description of
 .Cm ControlPath
diff --git a/ssh.c b/ssh.c
index c231c5f..dfe9b25 100644
--- a/ssh.c
+++ b/ssh.c
@@ -40,7 +40,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: ssh.c,v 1.228 2004/09/23 13:00:04 djm Exp $");
+RCSID("$OpenBSD: ssh.c,v 1.229 2004/11/07 00:01:46 djm Exp $");
 
 #include <openssl/evp.h>
 #include <openssl/err.h>
@@ -144,6 +144,9 @@
 /* fd to control socket */
 int control_fd = -1;
 
+/* Multiplexing control command */
+static u_int mux_command = SSHMUX_COMMAND_OPEN;
+
 /* Only used in control client mode */
 volatile sig_atomic_t control_client_terminate = 0;
 u_int control_server_pid = 0;
@@ -236,7 +239,7 @@
 
 again:
 	while ((opt = getopt(ac, av,
-	    "1246ab:c:e:fgi:kl:m:no:p:qstvxACD:F:I:L:MNPR:S:TVXY")) != -1) {
+	    "1246ab:c:e:fgi:kl:m:no:p:qstvxACD:F:I:L:MNO:PR:S:TVXY")) != -1) {
 		switch (opt) {
 		case '1':
 			options.protocol = SSH_PROTO_1;
@@ -270,6 +273,14 @@
 		case 'g':
 			options.gateway_ports = 1;
 			break;
+		case 'O':
+			if (strcmp(optarg, "check") == 0)
+				mux_command = SSHMUX_COMMAND_ALIVE_CHECK;
+			else if (strcmp(optarg, "exit") == 0)
+				mux_command = SSHMUX_COMMAND_TERMINATE;
+			else
+				fatal("Invalid multiplex command.");
+			break;
 		case 'P':	/* deprecated */
 			options.use_privileged_port = 0;
 			break;
@@ -1251,8 +1262,9 @@
 	struct sockaddr_un addr;
 	int i, r, fd, sock, exitval, num_env, addr_len;
 	Buffer m;
-	char *cp;
+	char *term;
 	extern char **environ;
+	u_int  flags;
 
 	if (stdin_null_flag) {
 		if ((fd = open(_PATH_DEVNULL, O_RDONLY)) == -1)
@@ -1278,26 +1290,52 @@
 	if (connect(sock, (struct sockaddr*)&addr, addr_len) == -1)
 		fatal("Couldn't connect to %s: %s", path, strerror(errno));
 
-	if ((cp = getenv("TERM")) == NULL)
-		cp = "";
+	if ((term = getenv("TERM")) == NULL)
+		term = "";
+
+	flags = 0;
+	if (tty_flag)
+		flags |= SSHMUX_FLAG_TTY;
+	if (subsystem_flag)
+		flags |= SSHMUX_FLAG_SUBSYS;
 
 	buffer_init(&m);
 
-	/* Get PID of controlee */
+	/* Send our command to server */
+	buffer_put_int(&m, mux_command);
+	buffer_put_int(&m, flags);
+	if (ssh_msg_send(sock, /* version */1, &m) == -1)
+		fatal("%s: msg_send", __func__);
+	buffer_clear(&m);
+
+	/* Get authorisation status and PID of controlee */
 	if (ssh_msg_recv(sock, &m) == -1)
 		fatal("%s: msg_recv", __func__);
-	if (buffer_get_char(&m) != 0)
+	if (buffer_get_char(&m) != 1)
 		fatal("%s: wrong version", __func__);
-	/* Connection allowed? */
 	if (buffer_get_int(&m) != 1)
 		fatal("Connection to master denied");
 	control_server_pid = buffer_get_int(&m);
 
 	buffer_clear(&m);
-	buffer_put_int(&m, tty_flag);
-	buffer_put_int(&m, subsystem_flag);
-	buffer_put_cstring(&m, cp);
 
+	switch (mux_command) {
+	case SSHMUX_COMMAND_ALIVE_CHECK:
+		fprintf(stderr, "Master running (pid=%d)\r\n", 
+		    control_server_pid);
+		exit(0);
+	case SSHMUX_COMMAND_TERMINATE:
+		fprintf(stderr, "Exit request sent.\r\n");
+		exit(0);
+	case SSHMUX_COMMAND_OPEN:
+		/* continue below */
+		break;
+	default:
+		fatal("silly mux_command %d", mux_command);
+	}
+
+	/* SSHMUX_COMMAND_OPEN */
+	buffer_put_cstring(&m, term);
 	buffer_append(&command, "\0", 1);
 	buffer_put_cstring(&m, buffer_ptr(&command));
 
@@ -1319,7 +1357,7 @@
 			}
 	}
 
-	if (ssh_msg_send(sock, /* version */0, &m) == -1)
+	if (ssh_msg_send(sock, /* version */1, &m) == -1)
 		fatal("%s: msg_send", __func__);
 
 	mm_send_fd(sock, STDIN_FILENO);
@@ -1330,8 +1368,8 @@
 	buffer_clear(&m);
 	if (ssh_msg_recv(sock, &m) == -1)
 		fatal("%s: msg_recv", __func__);
-	if (buffer_get_char(&m) != 0)
-		fatal("%s: master returned error", __func__);
+	if (buffer_get_char(&m) != 1)
+		fatal("%s: wrong version", __func__);
 	buffer_free(&m);
 
 	signal(SIGHUP, control_client_sighandler);