]> andersk Git - openssh.git/commitdiff
- djm@cvs.openbsd.org 2004/06/13 15:03:02
authordjm <djm>
Tue, 15 Jun 2004 00:34:08 +0000 (00:34 +0000)
committerdjm <djm>
Tue, 15 Jun 2004 00:34:08 +0000 (00:34 +0000)
     [channels.c channels.h clientloop.c clientloop.h includes.h readconf.c]
     [readconf.h scp.1 sftp.1 ssh.1 ssh.c ssh_config.5]
     implement session multiplexing in the client (the server has supported
     this since 2.0); ok markus@

15 files changed:
ChangeLog
channels.c
channels.h
clientloop.c
clientloop.h
defines.h
includes.h
readconf.c
readconf.h
scp.1
sftp.1
ssh-rand-helper.c
ssh.1
ssh.c
ssh_config.5

index ef91af48a78e44ed7b3dd37c8d202441313eb387..b8cafaa78e626d4a4a8d247b297237b36e0fef4a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
      [ssh.1 ssh_config.5 sshd_config.5]
      List supported ciphers in man pages, tidy up ssh -c;
      "looks fine" jmc@, ok markus@
+   - djm@cvs.openbsd.org 2004/06/13 15:03:02
+     [channels.c channels.h clientloop.c clientloop.h includes.h readconf.c] 
+     [readconf.h scp.1 sftp.1 ssh.1 ssh.c ssh_config.5]
+     implement session multiplexing in the client (the server has supported 
+     this since 2.0); ok markus@
 
 20040603
  - (dtucker) [auth-pam.c] Don't use pam_* namespace for sshd's PAM functions.
index 437befa34b76da773f1ed13b09aaef62e08ee66b..1fb1092c87aee1dc9a82247ac39f35364a64e975 100644 (file)
@@ -39,7 +39,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: channels.c,v 1.203 2004/05/26 23:02:39 markus Exp $");
+RCSID("$OpenBSD: channels.c,v 1.204 2004/06/13 15:03:02 djm Exp $");
 
 #include "ssh.h"
 #include "ssh1.h"
@@ -172,6 +172,7 @@ channel_register_fds(Channel *c, int rfd, int wfd, int efd,
        c->rfd = rfd;
        c->wfd = wfd;
        c->sock = (rfd == wfd) ? rfd : -1;
+       c->ctl_fd = -1; /* XXX: set elsewhere */
        c->efd = efd;
        c->extended_usage = extusage;
 
@@ -263,6 +264,7 @@ channel_new(char *ctype, int type, int rfd, int wfd, int efd,
        c->single_connection = 0;
        c->detach_user = NULL;
        c->confirm = NULL;
+       c->confirm_ctx = NULL;
        c->input_filter = NULL;
        debug("channel %d: new [%s]", found, remote_name);
        return c;
@@ -304,10 +306,11 @@ channel_close_fd(int *fdp)
 static void
 channel_close_fds(Channel *c)
 {
-       debug3("channel %d: close_fds r %d w %d e %d",
-           c->self, c->rfd, c->wfd, c->efd);
+       debug3("channel %d: close_fds r %d w %d e %d c %d",
+           c->self, c->rfd, c->wfd, c->efd, c->ctl_fd);
 
        channel_close_fd(&c->sock);
+       channel_close_fd(&c->ctl_fd);
        channel_close_fd(&c->rfd);
        channel_close_fd(&c->wfd);
        channel_close_fd(&c->efd);
@@ -333,6 +336,8 @@ channel_free(Channel *c)
 
        if (c->sock != -1)
                shutdown(c->sock, SHUT_RDWR);
+       if (c->ctl_fd != -1)
+               shutdown(c->ctl_fd, SHUT_RDWR);
        channel_close_fds(c);
        buffer_free(&c->input);
        buffer_free(&c->output);
@@ -550,12 +555,13 @@ channel_open_message(void)
                case SSH_CHANNEL_X11_OPEN:
                case SSH_CHANNEL_INPUT_DRAINING:
                case SSH_CHANNEL_OUTPUT_DRAINING:
-                       snprintf(buf, sizeof buf, "  #%d %.300s (t%d r%d i%d/%d o%d/%d fd %d/%d)\r\n",
+                       snprintf(buf, sizeof buf,
+                           "  #%d %.300s (t%d r%d i%d/%d o%d/%d fd %d/%d cfd %d)\r\n",
                            c->self, c->remote_name,
                            c->type, c->remote_id,
                            c->istate, buffer_len(&c->input),
                            c->ostate, buffer_len(&c->output),
-                           c->rfd, c->wfd);
+                           c->rfd, c->wfd, c->ctl_fd);
                        buffer_append(&buffer, buf, strlen(buf));
                        continue;
                default:
@@ -596,14 +602,14 @@ channel_request_start(int id, char *service, int wantconfirm)
                logit("channel_request_start: %d: unknown channel id", id);
                return;
        }
-       debug2("channel %d: request %s", id, service) ;
+       debug2("channel %d: request %s confirm %d", id, service, wantconfirm);
        packet_start(SSH2_MSG_CHANNEL_REQUEST);
        packet_put_int(c->remote_id);
        packet_put_cstring(service);
        packet_put_char(wantconfirm);
 }
 void
-channel_register_confirm(int id, channel_callback_fn *fn)
+channel_register_confirm(int id, channel_callback_fn *fn, void *ctx)
 {
        Channel *c = channel_lookup(id);
 
@@ -612,6 +618,7 @@ channel_register_confirm(int id, channel_callback_fn *fn)
                return;
        }
        c->confirm = fn;
+       c->confirm_ctx = ctx;
 }
 void
 channel_register_cleanup(int id, channel_callback_fn *fn)
@@ -729,6 +736,10 @@ channel_pre_open(Channel *c, fd_set * readset, fd_set * writeset)
                    buffer_len(&c->extended) < c->remote_window)
                        FD_SET(c->efd, readset);
        }
+       /* XXX: What about efd? races? */
+       if (compat20 && c->ctl_fd != -1 && 
+           c->istate == CHAN_INPUT_OPEN && c->ostate == CHAN_OUTPUT_OPEN)
+               FD_SET(c->ctl_fd, readset);
 }
 
 static void
@@ -1482,6 +1493,33 @@ channel_handle_efd(Channel *c, fd_set * readset, fd_set * writeset)
        return 1;
 }
 static int
+channel_handle_ctl(Channel *c, fd_set * readset, fd_set * writeset)
+{
+       char buf[16];
+       int len;
+
+       /* Monitor control fd to detect if the slave client exits */
+       if (c->ctl_fd != -1 && FD_ISSET(c->ctl_fd, readset)) {
+               len = read(c->ctl_fd, buf, sizeof(buf));
+               if (len < 0 && (errno == EINTR || errno == EAGAIN))
+                       return 1;
+               if (len <= 0) {
+                       debug2("channel %d: ctl read<=0", c->self);
+                       if (c->type != SSH_CHANNEL_OPEN) {
+                               debug2("channel %d: not open", c->self);
+                               chan_mark_dead(c);
+                               return -1;
+                       } else {
+                               chan_read_failed(c);
+                               chan_write_failed(c);
+                       }
+                       return -1;
+               } else
+                       fatal("%s: unexpected data on ctl fd", __func__);
+       }
+       return 1;
+}
+static int
 channel_check_window(Channel *c)
 {
        if (c->type == SSH_CHANNEL_OPEN &&
@@ -1511,6 +1549,7 @@ channel_post_open(Channel *c, fd_set * readset, fd_set * writeset)
        if (!compat20)
                return;
        channel_handle_efd(c, readset, writeset);
+       channel_handle_ctl(c, readset, writeset);
        channel_check_window(c);
 }
 
@@ -2011,7 +2050,7 @@ channel_input_open_confirmation(int type, u_int32_t seq, void *ctxt)
                c->remote_maxpacket = packet_get_int();
                if (c->confirm) {
                        debug2("callback start");
-                       c->confirm(c->self, NULL);
+                       c->confirm(c->self, c->confirm_ctx);
                        debug2("callback done");
                }
                debug2("channel %d: open confirm rwindow %u rmax %u", c->self,
@@ -2531,6 +2570,27 @@ channel_connect_to(const char *host, u_short port)
        return connect_to(host, port);
 }
 
+void
+channel_send_window_changes(void)
+{
+       int i;
+       struct winsize ws;
+
+       for (i = 0; i < channels_alloc; i++) {
+               if (channels[i] == NULL ||
+                   channels[i]->type != SSH_CHANNEL_OPEN)
+                       continue;
+               if (ioctl(channels[i]->rfd, TIOCGWINSZ, &ws) < 0)
+                       continue;
+               channel_request_start(i, "window-change", 0);
+               packet_put_int(ws.ws_col);
+               packet_put_int(ws.ws_row);
+               packet_put_int(ws.ws_xpixel);
+               packet_put_int(ws.ws_ypixel);
+               packet_send();
+       }
+}
+
 /* -- X11 forwarding */
 
 /*
index 0a49c55ea6b5f1f779b7c76007fd4e75faf00599..41f3cedd34a45f760acea1cf96a470bd4cf6462b 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: channels.h,v 1.72 2004/05/21 11:33:11 djm Exp $       */
+/*     $OpenBSD: channels.h,v 1.73 2004/06/13 15:03:02 djm Exp $       */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -76,6 +76,7 @@ struct Channel {
        int     wfd;            /* write fd */
        int     efd;            /* extended fd */
        int     sock;           /* sock fd */
+       int     ctl_fd;         /* control fd (client sharing) */
        int     isatty;         /* rfd is a tty */
        int     wfd_isatty;     /* wfd is a tty */
        int     force_drain;    /* force close on iEOF */
@@ -105,6 +106,7 @@ struct Channel {
        /* callback */
        channel_callback_fn     *confirm;
        channel_callback_fn     *detach_user;
+       void                    *confirm_ctx;
 
        /* filter */
        channel_filter_fn       *input_filter;
@@ -161,10 +163,11 @@ void       channel_stop_listening(void);
 void    channel_send_open(int);
 void    channel_request_start(int, char *, int);
 void    channel_register_cleanup(int, channel_callback_fn *);
-void    channel_register_confirm(int, channel_callback_fn *);
+void    channel_register_confirm(int, channel_callback_fn *, void *);
 void    channel_register_filter(int, channel_filter_fn *);
 void    channel_cancel_cleanup(int);
 int     channel_close_fd(int *);
+void    channel_send_window_changes(void);
 
 /* protocol handler */
 
index 31e604180a6a409dacd632740f0ee29353f2690a..6401588a92cd170bd8955408ad97ae440dd929fa 100644 (file)
@@ -59,7 +59,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: clientloop.c,v 1.122 2004/05/22 06:32:12 djm Exp $");
+RCSID("$OpenBSD: clientloop.c,v 1.123 2004/06/13 15:03:02 djm Exp $");
 
 #include "ssh.h"
 #include "ssh1.h"
@@ -81,6 +81,9 @@ RCSID("$OpenBSD: clientloop.c,v 1.122 2004/05/22 06:32:12 djm Exp $");
 #include "atomicio.h"
 #include "sshpty.h"
 #include "misc.h"
+#include "monitor_fdpass.h"
+#include "match.h"
+#include "msg.h"
 
 /* import options */
 extern Options options;
@@ -91,6 +94,9 @@ extern int stdin_null_flag;
 /* Flag indicating that no shell has been requested */
 extern int no_shell_flag;
 
+/* Control socket */
+extern int control_fd;
+
 /*
  * Name of the host we are connecting to.  This is the name given on the
  * command line, or the HostName specified for the user-supplied name in a
@@ -131,9 +137,19 @@ static int server_alive_timeouts = 0;
 static void client_init_dispatch(void);
 int    session_ident = -1;
 
+struct confirm_ctx {
+       int want_tty;
+       int want_subsys;
+       Buffer cmd;
+       char *term;
+       struct termios tio;
+};
+
 /*XXX*/
 extern Kex *xxx_kex;
 
+void ssh_process_session2_setup(int, int, int, Buffer *);
+
 /* Restores stdin to blocking mode. */
 
 static void
@@ -291,19 +307,13 @@ client_check_window_change(void)
        /** XXX race */
        received_window_change_signal = 0;
 
-       if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) < 0)
-               return;
-
        debug2("client_check_window_change: changed");
 
        if (compat20) {
-               channel_request_start(session_ident, "window-change", 0);
-               packet_put_int(ws.ws_col);
-               packet_put_int(ws.ws_row);
-               packet_put_int(ws.ws_xpixel);
-               packet_put_int(ws.ws_ypixel);
-               packet_send();
+               channel_send_window_changes();
        } else {
+               if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) < 0)
+                       return;
                packet_start(SSH_CMSG_WINDOW_SIZE);
                packet_put_int(ws.ws_row);
                packet_put_int(ws.ws_col);
@@ -335,7 +345,6 @@ server_alive_check(void)
  * Waits until the client can do something (some data becomes available on
  * one of the file descriptors).
  */
-
 static void
 client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp,
     int *maxfdp, int *nallocp, int rekeying)
@@ -381,6 +390,9 @@ client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp,
        if (packet_have_data_to_write())
                FD_SET(connection_out, *writesetp);
 
+       if (control_fd != -1)
+               FD_SET(control_fd, *readsetp);
+
        /*
         * Wait for something to happen.  This will suspend the process until
         * some selected descriptor can be read, written, or has some other
@@ -499,6 +511,176 @@ client_process_net_input(fd_set * readset)
        }
 }
 
+static void
+client_subsystem_reply(int type, u_int32_t seq, void *ctxt)
+{
+       int id;
+       Channel *c;
+       
+       id = packet_get_int();
+       packet_check_eom();
+
+       if ((c = channel_lookup(id)) == NULL) {
+               error("%s: no channel for id %d", __func__, id);
+               return;
+       }
+
+       if (type == SSH2_MSG_CHANNEL_SUCCESS)
+               debug2("Request suceeded on channel %d", id);
+       else if (type == SSH2_MSG_CHANNEL_FAILURE) {
+               error("Request failed on channel %d", id);
+               channel_free(c);
+       }
+}
+
+static void
+client_extra_session2_setup(int id, void *arg)
+{
+       struct confirm_ctx *cctx = arg;
+       Channel *c;
+       
+       if (cctx == NULL)
+               fatal("%s: cctx == NULL", __func__);
+       if ((c = channel_lookup(id)) == NULL)
+               fatal("%s: no channel for id %d", __func__, id);
+
+       client_session2_setup(id, cctx->want_tty, cctx->want_subsys, 
+           cctx->term, &cctx->tio, c->rfd, &cctx->cmd, 
+           client_subsystem_reply);
+       
+       c->confirm_ctx = NULL;
+       buffer_free(&cctx->cmd);
+       free(cctx->term);
+       free(cctx);
+}
+
+static void
+client_process_control(fd_set * readset)
+{
+       Buffer m;
+       Channel *c;
+       int client_fd, new_fd[3], ver;
+       socklen_t addrlen;
+       struct sockaddr_storage addr;
+       struct confirm_ctx *cctx;
+       char *cmd;
+       u_int len;
+       uid_t euid;
+       gid_t egid;
+
+       /*
+        * Accept connection on control socket
+        */
+       if (control_fd == -1 || !FD_ISSET(control_fd, readset))
+               return;
+
+       memset(&addr, 0, sizeof(addr));
+       addrlen = sizeof(addr);
+       if ((client_fd = accept(control_fd,
+           (struct sockaddr*)&addr, &addrlen)) == -1) {
+               error("%s accept: %s", __func__, strerror(errno));
+               return;
+       }
+
+       if (getpeereid(client_fd, &euid, &egid) < 0) {
+               error("%s getpeereid failed: %s", __func__, strerror(errno));
+               close(client_fd);
+               return;
+       }
+       if ((euid != 0) && (getuid() != euid)) {
+               error("control mode uid mismatch: peer euid %u != uid %u",
+                   (u_int) euid, (u_int) getuid());
+               close(client_fd);
+               return;
+       }
+       /* XXX: implement use of ssh-askpass to confirm additional channels */
+
+       unset_nonblock(client_fd);
+
+       buffer_init(&m);
+
+       buffer_put_int(&m, getpid());
+       if (ssh_msg_send(client_fd, /* version */0, &m) == -1) {
+               error("%s: client msg_send failed", __func__);
+               close(client_fd);
+               return;
+       }
+       buffer_clear(&m);
+
+       if (ssh_msg_recv(client_fd, &m) == -1) {
+               error("%s: client msg_recv failed", __func__);
+               close(client_fd);
+               return;
+       }
+
+       if ((ver = buffer_get_char(&m)) != 0) {
+               error("%s: wrong client version %d", __func__, ver);
+               buffer_free(&m);
+               close(client_fd);
+               return;
+       }
+
+       cctx = xmalloc(sizeof(*cctx));
+       memset(cctx, 0, sizeof(*cctx));
+
+       cctx->want_tty = buffer_get_int(&m);
+       cctx->want_subsys = buffer_get_int(&m);
+       cctx->term = buffer_get_string(&m, &len);
+
+       cmd = buffer_get_string(&m, &len);
+       buffer_init(&cctx->cmd);
+       buffer_append(&cctx->cmd, cmd, strlen(cmd));
+
+       debug2("%s: accepted tty %d, subsys %d, cmd %s", __func__,
+           cctx->want_tty, cctx->want_subsys, cmd);
+
+       /* Gather fds from client */
+       new_fd[0] = mm_receive_fd(client_fd);
+       new_fd[1] = mm_receive_fd(client_fd);
+       new_fd[2] = mm_receive_fd(client_fd);
+
+       debug2("%s: got fds stdin %d, stdout %d, stderr %d", __func__,
+           new_fd[0], new_fd[1], new_fd[2]);
+
+       /* Try to pick up ttymodes from client before it goes raw */
+       if (cctx->want_tty && tcgetattr(new_fd[0], &cctx->tio) == -1)
+               error("%s: tcgetattr: %s", __func__, strerror(errno));
+
+       buffer_clear(&m);
+       if (ssh_msg_send(client_fd, /* version */0, &m) == -1) {
+               error("%s: client msg_send failed", __func__);
+               close(client_fd);
+               close(new_fd[0]);
+               close(new_fd[1]);
+               close(new_fd[2]);
+               return;
+       }
+       buffer_free(&m);
+
+       /* enable nonblocking unless tty */
+       if (!isatty(new_fd[0]))
+               set_nonblock(new_fd[0]);
+       if (!isatty(new_fd[1]))
+               set_nonblock(new_fd[1]);
+       if (!isatty(new_fd[2]))
+               set_nonblock(new_fd[2]);
+
+       set_nonblock(client_fd);
+
+       c = channel_new("session", SSH_CHANNEL_OPENING, 
+           new_fd[0], new_fd[1], new_fd[2],
+           CHAN_SES_WINDOW_DEFAULT, CHAN_SES_PACKET_DEFAULT,
+           CHAN_EXTENDED_WRITE, "client-session", /*nonblock*/0);
+
+       /* XXX */
+       c->ctl_fd = client_fd;
+
+       debug3("%s: channel_new: %d", __func__, c->self);
+
+       channel_send_open(c->self);
+       channel_register_confirm(c->self, client_extra_session2_setup, cctx);
+}
+
 static void
 process_cmdline(void)
 {
@@ -901,9 +1083,6 @@ simple_escape_filter(Channel *c, char *buf, int len)
 static void
 client_channel_closed(int id, void *arg)
 {
-       if (id != session_ident)
-               error("client_channel_closed: id %d != session_ident %d",
-                   id, session_ident);
        channel_cancel_cleanup(id);
        session_closed = 1;
        leave_raw_mode();
@@ -937,6 +1116,8 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id)
        connection_in = packet_get_connection_in();
        connection_out = packet_get_connection_out();
        max_fd = MAX(connection_in, connection_out);
+       if (control_fd != -1)
+               max_fd = MAX(max_fd, control_fd);
 
        if (!compat20) {
                /* enable nonblocking unless tty */
@@ -1054,6 +1235,9 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id)
                /* Buffer input from the connection.  */
                client_process_net_input(readset);
 
+               /* Accept control connections.  */
+               client_process_control(readset);
+
                if (quit_pending)
                        break;
 
@@ -1385,7 +1569,7 @@ static void
 client_input_channel_req(int type, u_int32_t seq, void *ctxt)
 {
        Channel *c = NULL;
-       int id, reply, success = 0;
+       int exitval, id, reply, success = 0;
        char *rtype;
 
        id = packet_get_int();
@@ -1395,18 +1579,21 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt)
        debug("client_input_channel_req: channel %d rtype %s reply %d",
            id, rtype, reply);
 
-       if (session_ident == -1) {
-               error("client_input_channel_req: no channel %d", session_ident);
-       } else if (id != session_ident) {
-               error("client_input_channel_req: channel %d: wrong channel: %d",
-                   session_ident, id);
-       }
        c = channel_lookup(id);
        if (c == NULL) {
                error("client_input_channel_req: channel %d: unknown channel", id);
        } else if (strcmp(rtype, "exit-status") == 0) {
-               success = 1;
-               exit_status = packet_get_int();
+               exitval = packet_get_int();
+               if (id == session_ident) {
+                       success = 1;
+                       exit_status = exitval;
+               } else if (c->ctl_fd == -1) {
+                       error("client_input_channel_req: unexpected channel %d",
+                           session_ident);
+               } else {
+                       atomicio(vwrite, c->ctl_fd, &exitval, sizeof(exitval));
+                       success = 1;
+               }
                packet_check_eom();
        }
        if (reply) {
@@ -1437,6 +1624,98 @@ client_input_global_request(int type, u_int32_t seq, void *ctxt)
        xfree(rtype);
 }
 
+void
+client_session2_setup(int id, int want_tty, int want_subsystem, 
+    const char *term, struct termios *tiop, int in_fd, Buffer *cmd, 
+    dispatch_fn *subsys_repl)
+{
+       int len;
+
+       debug2("%s: id %d", __func__, id);
+
+       if (want_tty) {
+               struct winsize ws;
+               struct termios tio;
+
+               /* Store window size in the packet. */
+               if (ioctl(in_fd, TIOCGWINSZ, &ws) < 0)
+                       memset(&ws, 0, sizeof(ws));
+
+               channel_request_start(id, "pty-req", 0);
+               packet_put_cstring(term != NULL ? term : "");
+               packet_put_int(ws.ws_col);
+               packet_put_int(ws.ws_row);
+               packet_put_int(ws.ws_xpixel);
+               packet_put_int(ws.ws_ypixel);
+               tio = get_saved_tio();
+               tty_make_modes(-1, tiop != NULL ? tiop : &tio);
+               packet_send();
+               /* XXX wait for reply */
+       }
+
+       /* Transfer any environment variables from client to server */
+       if (options.num_send_env != 0) {
+               int i, j, matched;
+               extern char **environ;
+               char *name, *val;
+
+               debug("Sending environment.");
+               for (i = 0; environ && environ[i] != NULL; i++) {
+                       /* Split */
+                       name = xstrdup(environ[i]);
+                       if ((val = strchr(name, '=')) == NULL) {
+                               free(name);
+                               continue;
+                       }
+                       *val++ = '\0';
+
+                       matched = 0;
+                       for (j = 0; j < options.num_send_env; j++) {
+                               if (match_pattern(name, options.send_env[j])) {
+                                       matched = 1;
+                                       break;
+                               }
+                       }
+                       if (!matched) {
+                               debug3("Ignored env %s", name);
+                               free(name);
+                               continue;
+                       }
+
+                       debug("Sending env %s = %s", name, val);
+                       channel_request_start(id, "env", 0);
+                       packet_put_cstring(name);
+                       packet_put_cstring(val);
+                       packet_send();
+                       free(name);
+               }
+       }
+
+       len = buffer_len(cmd);
+       if (len > 0) {
+               if (len > 900)
+                       len = 900;
+               if (want_subsystem) {
+                       debug("Sending subsystem: %.*s", len, (u_char*)buffer_ptr(cmd));
+                       channel_request_start(id, "subsystem", subsys_repl != NULL);
+                       if (subsys_repl != NULL) {
+                               /* register callback for reply */
+                               /* XXX we assume that client_loop has already been called */
+                               dispatch_set(SSH2_MSG_CHANNEL_FAILURE, subsys_repl);
+                               dispatch_set(SSH2_MSG_CHANNEL_SUCCESS, subsys_repl);
+                       }
+               } else {
+                       debug("Sending command: %.*s", len, (u_char*)buffer_ptr(cmd));
+                       channel_request_start(id, "exec", 0);
+               }
+               packet_put_string(buffer_ptr(cmd), buffer_len(cmd));
+               packet_send();
+       } else {
+               channel_request_start(id, "shell", 0);
+               packet_send();
+       }
+}
+
 static void
 client_init_dispatch_20(void)
 {
@@ -1503,5 +1782,7 @@ cleanup_exit(int i)
 {
        leave_raw_mode();
        leave_non_blocking();
+       if (options.control_path != NULL && control_fd != -1)
+               unlink(options.control_path);
        _exit(i);
 }
index 56af06bc146090720b9ab2102e71c2185576bfb1..f1e13ac3a93cf2ed3b9fcc3cba65fae3ae849e6e 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: clientloop.h,v 1.8 2003/12/16 15:49:51 markus Exp $   */
+/*     $OpenBSD: clientloop.h,v 1.9 2004/06/13 15:03:02 djm Exp $      */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -38,3 +38,5 @@
 /* Client side main loop for the interactive session. */
 int     client_loop(int, int, int);
 void    client_global_request_reply_fwd(int, u_int32_t, void *);
+void    client_session2_setup(int, int, int, const char *, struct termios *, 
+           int, Buffer *, dispatch_fn *);
index 889b918867b8907a7c3e64c1e2cd924a5a15b4d6..93b5c87d0023bfe1499f0c556400b5e0820fa46a 100644 (file)
--- a/defines.h
+++ b/defines.h
@@ -462,6 +462,9 @@ struct winsize {
         (struct cmsghdr *)NULL)
 #endif /* CMSG_FIRSTHDR */
 
+#ifndef offsetof
+# define offsetof(type, member) ((size_t) &((type *)0)->member)
+#endif
 
 /* Function replacement / compatibility hacks */
 
index ca943c7e657b30b4bb3648e887bf63a28fd762f2..99b70502cff427643425118b7bccd305190cbc76 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: includes.h,v 1.17 2002/01/26 16:44:22 stevesk Exp $   */
+/*     $OpenBSD: includes.h,v 1.18 2004/06/13 15:03:02 djm Exp $       */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -33,6 +33,7 @@ static /**/const char *const rcsid[] = { (char *)rcsid, "\100(#)" msg }
 #include <grp.h>
 #include <time.h>
 #include <dirent.h>
+#include <stddef.h>
 
 #ifdef HAVE_LIMITS_H
 # include <limits.h> /* For PATH_MAX */
index 5aa371ed9a115a0ec7aa4bc4c070e6eb8e298bde..2b1d7cc46d33aa4260bb0e56e98b004c334161a4 100644 (file)
@@ -12,7 +12,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: readconf.c,v 1.131 2004/05/27 00:50:13 dtucker Exp $");
+RCSID("$OpenBSD: readconf.c,v 1.132 2004/06/13 15:03:02 djm Exp $");
 
 #include "ssh.h"
 #include "xmalloc.h"
@@ -106,7 +106,7 @@ typedef enum {
        oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
        oAddressFamily, oGssAuthentication, oGssDelegateCreds,
        oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly,
-       oSendEnv,
+       oSendEnv, oControlPath, oControlMaster,
        oDeprecated, oUnsupported
 } OpCodes;
 
@@ -195,6 +195,8 @@ static struct {
        { "serveraliveinterval", oServerAliveInterval },
        { "serveralivecountmax", oServerAliveCountMax },
        { "sendenv", oSendEnv },
+       { "controlpath", oControlPath },
+       { "controlmaster", oControlMaster },
        { NULL, oBadOption }
 };
 
@@ -764,6 +766,14 @@ parse_int:
                }
                break;
 
+       case oControlPath:
+               charptr = &options->control_path;
+               goto parse_string;
+
+       case oControlMaster:
+               intptr = &options->control_master;
+               goto parse_flag;
+
        case oDeprecated:
                debug("%s line %d: Deprecated option \"%s\"",
                    filename, linenum, keyword);
@@ -905,6 +915,8 @@ initialize_options(Options * options)
        options->server_alive_interval = -1;
        options->server_alive_count_max = -1;
        options->num_send_env = 0;
+       options->control_path = NULL;
+       options->control_master = -1;
 }
 
 /*
@@ -1025,6 +1037,8 @@ fill_default_options(Options * options)
                options->server_alive_interval = 0;
        if (options->server_alive_count_max == -1)
                options->server_alive_count_max = 3;
+       if (options->control_master == -1)
+               options->control_master = 0;
        /* options->proxy_command should not be set by default */
        /* options->user will be set in the main program if appropriate */
        /* options->hostname will be set in the main program if appropriate */
index 6680559433f0f4a9a99141ae91f057a6126b4ea0..5e504bece7ffc75d9b309dc56693ad0137a0f3ce 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: readconf.h,v 1.62 2004/04/27 09:46:37 djm Exp $       */
+/*     $OpenBSD: readconf.h,v 1.63 2004/06/13 15:03:02 djm Exp $       */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -108,6 +108,9 @@ typedef struct {
 
        int     num_send_env;
        char   *send_env[MAX_SEND_ENV];
+
+       char    *control_path;
+       int     control_master;
 }       Options;
 
 
diff --git a/scp.1 b/scp.1
index 202ebaadb2495ce01e01d332e4a40bed408712ab..f346b2ae96a0050c969d7fb1c83622b2c7a5cb08 100644 (file)
--- a/scp.1
+++ b/scp.1
@@ -9,7 +9,7 @@
 .\"
 .\" Created: Sun May  7 00:14:37 1995 ylo
 .\"
-.\" $OpenBSD: scp.1,v 1.35 2004/05/04 18:36:07 jmc Exp $
+.\" $OpenBSD: scp.1,v 1.36 2004/06/13 15:03:02 djm Exp $
 .\"
 .Dd September 25, 1999
 .Dt SCP 1
@@ -128,6 +128,8 @@ For full details of the options listed below, and their possible values, see
 .It CompressionLevel
 .It ConnectionAttempts
 .It ConnectTimeout
+.It ControlMaster
+.It ControlPath
 .It GlobalKnownHostsFile
 .It GSSAPIAuthentication
 .It GSSAPIDelegateCredentials
diff --git a/sftp.1 b/sftp.1
index 795a0342f4af8b99d23c1c5a01f976d85dd68326..7f0ef112156ae1520f36576bca5b23a2dbeb4e1c 100644 (file)
--- a/sftp.1
+++ b/sftp.1
@@ -1,4 +1,4 @@
-.\" $OpenBSD: sftp.1,v 1.54 2004/05/02 23:02:17 dtucker Exp $
+.\" $OpenBSD: sftp.1,v 1.55 2004/06/13 15:03:02 djm Exp $
 .\"
 .\" Copyright (c) 2001 Damien Miller.  All rights reserved.
 .\"
@@ -154,6 +154,8 @@ For full details of the options listed below, and their possible values, see
 .It CompressionLevel
 .It ConnectionAttempts
 .It ConnectTimeout
+.It ControlMaster
+.It ControlPath
 .It GlobalKnownHostsFile
 .It GSSAPIAuthentication
 .It GSSAPIDelegateCredentials
index 9c9c49560c4c89e94a2b7210026898cdcc457437..5fed283f17a75c8b34ebc3dc33f57ea411bc6350 100644 (file)
@@ -69,10 +69,6 @@ extern char *__progname;
 char *__progname;
 #endif
 
-#ifndef offsetof
-# define offsetof(type, member) ((size_t) &((type *)0)->member)
-#endif
-
 #define WHITESPACE " \t\n"
 
 #ifndef RUSAGE_SELF
diff --git a/ssh.1 b/ssh.1
index 6cef0851df39242a87f2fe9419855b43965f6219..b70102be58ac51f18510275a52d721e321c723c5 100644 (file)
--- 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.189 2004/06/13 14:01:42 dtucker Exp $
+.\" $OpenBSD: ssh.1,v 1.190 2004/06/13 15:03:02 djm Exp $
 .Dd September 25, 1999
 .Dt SSH 1
 .Os
@@ -43,7 +43,7 @@
 .Nd OpenSSH SSH client (remote login program)
 .Sh SYNOPSIS
 .Nm ssh
-.Op Fl 1246AaCfgkNnqsTtVvXxY
+.Op Fl 1246AaCfgkMNnqSsTtVvXxY
 .Op Fl b Ar bind_address
 .Op Fl c Ar cipher_spec
 .Op Fl D Ar port
@@ -605,6 +605,17 @@ be specified in order of preference.
 See the
 .Cm MACs
 keyword for more information.
+.It Fl M
+Places the
+.Nm
+client into
+.Dq master
+mode for connection sharing.
+Refer to the description of
+.Cm ControlMaster
+in
+.Xr ssh_config 5
+for details.
 .It Fl N
 Do not execute a remote command.
 This is useful for just forwarding ports
@@ -649,6 +660,8 @@ For full details of the options listed below, and their possible values, see
 .It CompressionLevel
 .It ConnectionAttempts
 .It ConnectTimeout
+.It ControlMaster
+.It ControlPath
 .It DynamicForward
 .It EscapeChar
 .It ForwardAgent
@@ -724,6 +737,15 @@ IPv6 addresses can be specified with an alternative syntax:
 .Ar hostport .
 .Xc
 .Sm on
+.It Fl S
+Places the
+.Nm
+client into slave mode for connection sharing.
+Refer to the description of
+.Cm ControlMaster
+in
+.Xr ssh_config 5
+for details.
 .It Fl s
 May be used to request invocation of a subsystem on the remote system.
 Subsystems are a feature of the SSH2 protocol which facilitate the use
diff --git a/ssh.c b/ssh.c
index 3c21fa37d92fdd3789c3a0585a6e971eb4bfd1c9..1c6ec8b6a29f5db1ca7e0a6ded314338c7158217 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -40,7 +40,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: ssh.c,v 1.213 2004/05/08 00:01:37 deraadt Exp $");
+RCSID("$OpenBSD: ssh.c,v 1.214 2004/06/13 15:03:02 djm Exp $");
 
 #include <openssl/evp.h>
 #include <openssl/err.h>
@@ -53,21 +53,24 @@ RCSID("$OpenBSD: ssh.c,v 1.213 2004/05/08 00:01:37 deraadt Exp $");
 #include "xmalloc.h"
 #include "packet.h"
 #include "buffer.h"
+#include "bufaux.h"
 #include "channels.h"
 #include "key.h"
 #include "authfd.h"
 #include "authfile.h"
 #include "pathnames.h"
+#include "dispatch.h"
 #include "clientloop.h"
 #include "log.h"
 #include "readconf.h"
 #include "sshconnect.h"
-#include "dispatch.h"
 #include "misc.h"
 #include "kex.h"
 #include "mac.h"
 #include "sshpty.h"
 #include "match.h"
+#include "msg.h"
+#include "monitor_fdpass.h"
 
 #ifdef SMARTCARD
 #include "scard.h"
@@ -141,6 +144,13 @@ static int client_global_request_id = 0;
 /* pid of proxycommand child process */
 pid_t proxy_command_pid = 0;
 
+/* fd to control socket */
+int control_fd = -1;
+
+/* Only used in control client mode */
+volatile sig_atomic_t control_client_terminate = 0;
+u_int control_server_pid = 0;
+
 /* Prints a help message to the user.  This function never returns. */
 
 static void
@@ -158,6 +168,7 @@ usage(void)
 static int ssh_session(void);
 static int ssh_session2(void);
 static void load_public_identity_files(void);
+static void control_client(const char *path);
 
 /*
  * Main program for the ssh client.
@@ -228,7 +239,7 @@ main(int ac, char **av)
 
 again:
        while ((opt = getopt(ac, av,
-           "1246ab:c:e:fgi:kl:m:no:p:qstvxACD:F:I:L:NPR:TVXY")) != -1) {
+           "1246ab:c:e:fgi:kl:m:no:p:qstvxACD:F:I:L:MNPR:S:TVXY")) != -1) {
                switch (opt) {
                case '1':
                        options.protocol = SSH_PROTO_1;
@@ -364,6 +375,9 @@ again:
                                exit(1);
                        }
                        break;
+               case 'M':
+                       options.control_master = 1;
+                       break;
                case 'p':
                        options.port = a2port(optarg);
                        if (options.port == 0) {
@@ -432,6 +446,13 @@ again:
                case 's':
                        subsystem_flag = 1;
                        break;
+               case 'S':
+                       if (options.control_path != NULL)
+                               free(options.control_path);
+                       options.control_path = xstrdup(optarg);
+                       if (options.control_master == -1)
+                               options.control_master = 0;
+                       break;
                case 'b':
                        options.bind_address = optarg;
                        break;
@@ -566,6 +587,13 @@ again:
            strcmp(options.proxy_command, "none") == 0)
                options.proxy_command = NULL;
 
+       if (options.control_path != NULL) {
+               options.control_path = tilde_expand_filename(
+                  options.control_path, original_real_uid);
+       }
+       if (options.control_path != NULL && options.control_master == 0)
+               control_client(options.control_path); /* This doesn't return */
+
        /* Open a connection to the remote host. */
        if (ssh_connect(host, &hostaddr, options.port,
            options.address_family, options.connection_attempts,
@@ -678,6 +706,9 @@ again:
        exit_status = compat20 ? ssh_session2() : ssh_session();
        packet_close();
 
+       if (options.control_path != NULL && control_fd != -1)
+               unlink(options.control_path);
+
        /*
         * Send SIGHUP to proxy command if used. We don't wait() in
         * case it hangs and instead rely on init to reap the child
@@ -974,7 +1005,7 @@ ssh_session(void)
 }
 
 static void
-client_subsystem_reply(int type, u_int32_t seq, void *ctxt)
+ssh_subsystem_reply(int type, u_int32_t seq, void *ctxt)
 {
        int id, len;
 
@@ -1006,40 +1037,50 @@ client_global_request_reply_fwd(int type, u_int32_t seq, void *ctxt)
                    options.remote_forwards[i].port);
 }
 
-/* request pty/x11/agent/tcpfwd/shell for channel */
 static void
-ssh_session2_setup(int id, void *arg)
+ssh_control_listener(void)
 {
-       int len;
-       int interactive = 0;
-       struct termios tio;
+       struct sockaddr_un addr;
+       mode_t old_umask;
+       
+       if (options.control_path == NULL || options.control_master != 1)
+               return;
 
-       debug2("ssh_session2_setup: id %d", id);
+       memset(&addr, '\0', sizeof(addr));
+       addr.sun_family = AF_UNIX;
+       addr.sun_len = offsetof(struct sockaddr_un, sun_path) +
+           strlen(options.control_path) + 1;
 
-       if (tty_flag) {
-               struct winsize ws;
-               char *cp;
-               cp = getenv("TERM");
-               if (!cp)
-                       cp = "";
-               /* Store window size in the packet. */
-               if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) < 0)
-                       memset(&ws, 0, sizeof(ws));
+       if (strlcpy(addr.sun_path, options.control_path,
+           sizeof(addr.sun_path)) >= sizeof(addr.sun_path))
+               fatal("ControlPath too long");
 
-               channel_request_start(id, "pty-req", 0);
-               packet_put_cstring(cp);
-               packet_put_int(ws.ws_col);
-               packet_put_int(ws.ws_row);
-               packet_put_int(ws.ws_xpixel);
-               packet_put_int(ws.ws_ypixel);
-               tio = get_saved_tio();
-               tty_make_modes(/*ignored*/ 0, &tio);
-               packet_send();
-               interactive = 1;
-               /* XXX wait for reply */
+       if ((control_fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+               fatal("%s socket(): %s\n", __func__, strerror(errno));
+
+       old_umask = umask(0177);
+       if (bind(control_fd, (struct sockaddr*)&addr, addr.sun_len) == -1) {
+               control_fd = -1;
+               if (errno == EINVAL)
+                       fatal("ControlSocket %s already exists",
+                           options.control_path);
+               else
+                       fatal("%s bind(): %s\n", __func__, strerror(errno));
        }
-       if (options.forward_x11 &&
-           getenv("DISPLAY") != NULL) {
+       umask(old_umask);
+
+       if (listen(control_fd, 64) == -1)
+               fatal("%s listen(): %s\n", __func__, strerror(errno));
+
+       set_nonblock(control_fd);
+}
+
+/* request pty/x11/agent/tcpfwd/shell for channel */
+static void
+ssh_session2_setup(int id, void *arg)
+{
+       int interactive = tty_flag;
+       if (options.forward_x11 && getenv("DISPLAY") != NULL) {
                char *proto, *data;
                /* Get reasonable local authentication information. */
                x11_get_proto(&proto, &data);
@@ -1057,65 +1098,8 @@ ssh_session2_setup(int id, void *arg)
                packet_send();
        }
 
-       /* Transfer any environment variables from client to server */
-       if (options.num_send_env != 0) {
-               int i, j, matched;
-               extern char **environ;
-               char *name, *val;
-
-               debug("Sending environment.");
-               for (i = 0; environ && environ[i] != NULL; i++) {
-                       /* Split */
-                       name = xstrdup(environ[i]);
-                       if ((val = strchr(name, '=')) == NULL) {
-                               free(name);
-                               continue;
-                       }
-                       *val++ = '\0';
-
-                       matched = 0;
-                       for (j = 0; j < options.num_send_env; j++) {
-                               if (match_pattern(name, options.send_env[j])) {
-                                       matched = 1;
-                                       break;
-                               }
-                       }
-                       if (!matched) {
-                               debug3("Ignored env %s", name);
-                               free(name);
-                               continue;
-                       }
-
-                       debug("Sending env %s = %s", name, val);
-                       channel_request_start(id, "env", 0);
-                       packet_put_cstring(name);
-                       packet_put_cstring(val);
-                       packet_send();
-                       free(name);
-               }
-       }
-
-       len = buffer_len(&command);
-       if (len > 0) {
-               if (len > 900)
-                       len = 900;
-               if (subsystem_flag) {
-                       debug("Sending subsystem: %.*s", len, (u_char *)buffer_ptr(&command));
-                       channel_request_start(id, "subsystem", /*want reply*/ 1);
-                       /* register callback for reply */
-                       /* XXX we assume that client_loop has already been called */
-                       dispatch_set(SSH2_MSG_CHANNEL_FAILURE, &client_subsystem_reply);
-                       dispatch_set(SSH2_MSG_CHANNEL_SUCCESS, &client_subsystem_reply);
-               } else {
-                       debug("Sending command: %.*s", len, (u_char *)buffer_ptr(&command));
-                       channel_request_start(id, "exec", 0);
-               }
-               packet_put_string(buffer_ptr(&command), buffer_len(&command));
-               packet_send();
-       } else {
-               channel_request_start(id, "shell", 0);
-               packet_send();
-       }
+       client_session2_setup(id, tty_flag, subsystem_flag, getenv("TERM"),
+           NULL, fileno(stdin), &command, &ssh_subsystem_reply);
 
        packet_set_interactive(interactive);
 }
@@ -1161,7 +1145,7 @@ ssh_session2_open(void)
 
        channel_send_open(c->self);
        if (!no_shell_flag)
-               channel_register_confirm(c->self, ssh_session2_setup);
+               channel_register_confirm(c->self, ssh_session2_setup, NULL);
 
        return c->self;
 }
@@ -1173,6 +1157,7 @@ ssh_session2(void)
 
        /* XXX should be pre-session */
        ssh_init_forwarding();
+       ssh_control_listener();
 
        if (!no_shell_flag || (datafellows & SSH_BUG_DUMMYCHAN))
                id = ssh_session2_open();
@@ -1226,3 +1211,110 @@ load_public_identity_files(void)
                options.identity_keys[i] = public;
        }
 }
+
+static void
+control_client_sighandler(int signo)
+{
+       control_client_terminate = signo;
+}
+
+static void
+control_client_sigrelay(int signo)
+{
+       if (control_server_pid > 1)
+               kill(control_server_pid, signo);
+}
+
+static void
+control_client(const char *path)
+{
+       struct sockaddr_un addr;
+       int r, sock, exitval;
+       Buffer m;
+       char *cp;
+       
+       memset(&addr, '\0', sizeof(addr));
+       addr.sun_family = AF_UNIX;
+       addr.sun_len = offsetof(struct sockaddr_un, sun_path) +
+           strlen(path) + 1;
+
+       if (strlcpy(addr.sun_path, path,
+           sizeof(addr.sun_path)) >= sizeof(addr.sun_path))
+               fatal("ControlPath too long");
+
+       if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+               fatal("%s socket(): %s", __func__, strerror(errno));
+
+       if (connect(sock, (struct sockaddr*)&addr, addr.sun_len) == -1)
+               fatal("Couldn't connect to %s: %s", path, strerror(errno));
+
+       if ((cp = getenv("TERM")) == NULL)
+               cp = "";
+
+       signal(SIGINT, control_client_sighandler);
+       signal(SIGTERM, control_client_sighandler);
+       signal(SIGWINCH, control_client_sigrelay);
+
+       buffer_init(&m);
+
+       /* Get PID of controlee */
+       if (ssh_msg_recv(sock, &m) == -1)
+               fatal("%s: msg_recv", __func__);
+       if (buffer_get_char(&m) != 0)
+               fatal("%s: wrong version", __func__);
+       control_server_pid = buffer_get_int(&m);
+
+       /* XXX: env passing */
+
+       buffer_clear(&m);
+       buffer_put_int(&m, tty_flag);
+       buffer_put_int(&m, subsystem_flag);
+       buffer_put_cstring(&m, cp);
+
+       buffer_append(&command, "\0", 1);
+       buffer_put_cstring(&m, buffer_ptr(&command));
+
+       if (ssh_msg_send(sock, /* version */0, &m) == -1)
+               fatal("%s: msg_send", __func__);
+
+       mm_send_fd(sock, STDIN_FILENO);
+       mm_send_fd(sock, STDOUT_FILENO);
+       mm_send_fd(sock, STDERR_FILENO);
+
+       /* Wait for reply, so master has a chance to gather ttymodes */
+       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__);
+       buffer_free(&m);
+
+       if (tty_flag)
+               enter_raw_mode();
+
+       /* Stick around until the controlee closes the client_fd */
+       exitval = 0;
+       for (;!control_client_terminate;) {
+               r = read(sock, &exitval, sizeof(exitval));
+               if (r == 0) {
+                       debug2("Received EOF from master");
+                       break;
+               }
+               if (r > 0)
+                       debug2("Received exit status from master %d", exitval);
+               if (r == -1 && errno != EINTR)
+                       fatal("%s: read %s", __func__, strerror(errno));
+       }
+
+       if (control_client_terminate)
+               debug2("Exiting on signal %d", control_client_terminate);
+
+       close(sock);
+
+       leave_raw_mode();
+
+       if (tty_flag && options.log_level != SYSLOG_LEVEL_QUIET)
+               fprintf(stderr, "Connection to master closed.\r\n");
+
+       exit(exitval);
+}
index 46d3012c8a3897cb4e3bdc707531586ac2ef5f05..bab11d313a928e1dafc29b102c290bb752b5ac7e 100644 (file)
@@ -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_config.5,v 1.35 2004/06/13 14:01:42 dtucker Exp $
+.\" $OpenBSD: ssh_config.5,v 1.36 2004/06/13 15:03:02 djm Exp $
 .Dd September 25, 1999
 .Dt SSH_CONFIG 5
 .Os
@@ -256,6 +256,28 @@ will act as a SOCKS server.
 Multiple forwardings may be specified, and
 additional forwardings can be given on the command line.
 Only the superuser can forward privileged ports.
+.It Cm ControlMaster
+Enables the sharing of multiple sessions over a single network connection.
+When set to
+.Dq yes
+.Nm ssh
+will listen for connections on a control socket specified using the
+.Cm ControlPath
+argument.
+Additional sessions can connect to this socket using the same
+.Cm ControlPath
+with
+.Cm ControlMaster
+set to
+.Dq no
+(the default.)
+These sessions will reuse the master instance's network connection rather
+than initiating new ones.
+.It Cm ControlPath
+Specify a the path to the control socket used for connection sharing.
+See
+.Cm ControlMaster
+above.
 .It Cm EnableSSHKeysign
 Setting this option to
 .Dq yes
This page took 0.120653 seconds and 5 git commands to generate.