[BACK]Return to mux.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / ssh

Diff for /src/usr.bin/ssh/mux.c between version 1.9 and 1.10

version 1.9, 2010/01/09 05:04:24 version 1.10, 2010/01/26 01:28:35
Line 17 
Line 17 
   
 /* ssh session multiplexing support */  /* ssh session multiplexing support */
   
   // XXX signal of slave passed to master
   
 /*  /*
  * TODO:   * TODO:
  *   1. partial reads in muxserver_accept_control (maybe make channels   *   - Better signalling from master to slave, especially passing of
  *      from accepted connections)  
  *   2. Better signalling from master to slave, especially passing of  
  *      error messages   *      error messages
  *   3. Better fall-back from mux slave error to new connection.   *   - Better fall-back from mux slave error to new connection.
  *   3. Add/delete forwardings via slave   *   - ExitOnForwardingFailure
  *   4. ExitOnForwardingFailure (after #3 obviously)   *   - Maybe extension mechanisms for multi-X11/multi-agent forwarding
  *   5. Maybe extension mechanisms for multi-X11/multi-agent forwarding   *   - Support ~^Z in mux slaves.
  *   6. Document the mux mini-protocol somewhere.   *   - Inspect or control sessions in master.
  *   7. Support ~^Z in mux slaves.   *   - If we ever support the "signal" channel request, send signals on
  *   8. Inspect or control sessions in master.   *     sessions in master.
  *   9. If we ever support the "signal" channel request, send signals on  
  *      sessions in master.  
  */   */
   
 #include <sys/types.h>  #include <sys/types.h>
Line 43 
Line 41 
   
 #include <errno.h>  #include <errno.h>
 #include <fcntl.h>  #include <fcntl.h>
   #include <poll.h>
 #include <signal.h>  #include <signal.h>
 #include <stdarg.h>  #include <stdarg.h>
 #include <stddef.h>  #include <stddef.h>
Line 53 
Line 52 
 #include <util.h>  #include <util.h>
 #include <paths.h>  #include <paths.h>
   
   #include "atomicio.h"
 #include "xmalloc.h"  #include "xmalloc.h"
 #include "log.h"  #include "log.h"
 #include "ssh.h"  #include "ssh.h"
Line 77 
Line 77 
 extern char *host;  extern char *host;
 extern int subsystem_flag;  extern int subsystem_flag;
 extern Buffer command;  extern Buffer command;
   extern volatile sig_atomic_t quit_pending;
   extern char *stdio_forward_host;
   extern int stdio_forward_port;
   
 /* Context for session open confirmation callback */  /* Context for session open confirmation callback */
 struct mux_session_confirm_ctx {  struct mux_session_confirm_ctx {
         int want_tty;          u_int want_tty;
         int want_subsys;          u_int want_subsys;
         int want_x_fwd;          u_int want_x_fwd;
         int want_agent_fwd;          u_int want_agent_fwd;
         Buffer cmd;          Buffer cmd;
         char *term;          char *term;
         struct termios tio;          struct termios tio;
Line 93 
Line 96 
 /* fd to control socket */  /* fd to control socket */
 int muxserver_sock = -1;  int muxserver_sock = -1;
   
   /* client request id */
   u_int muxclient_request_id = 0;
   
 /* Multiplexing control command */  /* Multiplexing control command */
 u_int muxclient_command = 0;  u_int muxclient_command = 0;
   
Line 102 
Line 108 
 /* PID of multiplex server */  /* PID of multiplex server */
 static u_int muxserver_pid = 0;  static u_int muxserver_pid = 0;
   
   static Channel *mux_listener_channel = NULL;
   
 /* ** Multiplexing master support */  struct mux_master_state {
           int hello_rcvd;
   };
   
   /* mux protocol messages */
   #define MUX_MSG_HELLO           0x00000001
   #define MUX_C_NEW_SESSION       0x10000002
   #define MUX_C_ALIVE_CHECK       0x10000004
   #define MUX_C_TERMINATE         0x10000005
   #define MUX_C_OPEN_FWD          0x10000006
   #define MUX_C_CLOSE_FWD         0x10000007
   #define MUX_C_NEW_STDIO_FWD     0x10000008
   #define MUX_S_OK                0x80000001
   #define MUX_S_PERMISSION_DENIED 0x80000002
   #define MUX_S_FAILURE           0x80000003
   #define MUX_S_EXIT_MESSAGE      0x80000004
   #define MUX_S_ALIVE             0x80000005
   #define MUX_S_SESSION_OPENED    0x80000006
   
   /* type codes for MUX_C_OPEN_FWD and MUX_C_CLOSE_FWD */
   #define MUX_FWD_LOCAL   1
   #define MUX_FWD_REMOTE  2
   #define MUX_FWD_DYNAMIC 3
   
   static void mux_session_confirm(int, void *);
   
   static int process_mux_master_hello(u_int, Channel *, Buffer *, Buffer *);
   static int process_mux_new_session(u_int, Channel *, Buffer *, Buffer *);
   static int process_mux_alive_check(u_int, Channel *, Buffer *, Buffer *);
   static int process_mux_terminate(u_int, Channel *, Buffer *, Buffer *);
   static int process_mux_open_fwd(u_int, Channel *, Buffer *, Buffer *);
   static int process_mux_close_fwd(u_int, Channel *, Buffer *, Buffer *);
   static int process_mux_stdio_fwd(u_int, Channel *, Buffer *, Buffer *);
   
   static const struct {
           u_int type;
           int (*handler)(u_int, Channel *, Buffer *, Buffer *);
   } mux_master_handlers[] = {
           { MUX_MSG_HELLO, process_mux_master_hello },
           { MUX_C_NEW_SESSION, process_mux_new_session },
           { MUX_C_ALIVE_CHECK, process_mux_alive_check },
           { MUX_C_TERMINATE, process_mux_terminate },
           { MUX_C_OPEN_FWD, process_mux_open_fwd },
           { MUX_C_CLOSE_FWD, process_mux_close_fwd },
           { MUX_C_NEW_STDIO_FWD, process_mux_stdio_fwd },
           { 0, NULL }
   };
   
   /* Cleanup callback fired on closure of mux slave _session_ channel */
   /* ARGSUSED */
   static void
   mux_master_session_cleanup_cb(int cid, void *unused)
   {
           Channel *cc, *c = channel_by_id(cid);
   
           debug3("%s: entering for channel %d", __func__, cid);
           if (c == NULL)
                   fatal("%s: channel_by_id(%i) == NULL", __func__, cid);
           if (c->ctl_chan != -1) {
                   if ((cc = channel_by_id(c->ctl_chan)) == NULL)
                           fatal("%s: channel %d missing control channel %d",
                               __func__, c->self, c->ctl_chan);
                   c->ctl_chan = -1;
                   cc->remote_id = -1;
                   chan_rcvd_oclose(cc);
           }
           channel_cancel_cleanup(c->self);
   }
   
   /* Cleanup callback fired on closure of mux slave _control_ channel */
   /* ARGSUSED */
   static void
   mux_master_control_cleanup_cb(int cid, void *unused)
   {
           Channel *sc, *c = channel_by_id(cid);
   
           debug3("%s: entering for channel %d", __func__, cid);
           if (c == NULL)
                   fatal("%s: channel_by_id(%i) == NULL", __func__, cid);
           if (c->remote_id != -1) {
                   if ((sc = channel_by_id(c->remote_id)) == NULL)
                           debug2("%s: channel %d n session channel %d",
                               __func__, c->self, c->remote_id);
                   c->remote_id = -1;
                   sc->ctl_chan = -1;
                   chan_mark_dead(sc);
           }
           channel_cancel_cleanup(c->self);
   }
   
   /* Check mux client environment variables before passing them to mux master. */
   static int
   env_permitted(char *env)
   {
           int i, ret;
           char name[1024], *cp;
   
           if ((cp = strchr(env, '=')) == NULL || cp == env)
                   return 0;
           ret = snprintf(name, sizeof(name), "%.*s", (int)(cp - env), env);
           if (ret <= 0 || (size_t)ret >= sizeof(name)) {
                   error("env_permitted: name '%.100s...' too long", env);
                   return 0;
           }
   
           for (i = 0; i < options.num_send_env; i++)
                   if (match_pattern(name, options.send_env[i]))
                           return 1;
   
           return 0;
   }
   
   /* Mux master protocol message handlers */
   
   static int
   process_mux_master_hello(u_int rid, Channel *c, Buffer *m, Buffer *r)
   {
           u_int ver;
           struct mux_master_state *state = (struct mux_master_state *)c->mux_ctx;
   
           if (state == NULL)
                   fatal("%s: channel %d: c->mux_ctx == NULL", __func__, c->self);
           if (state->hello_rcvd) {
                   error("%s: HELLO received twice", __func__);
                   return -1;
           }
           if (buffer_get_int_ret(&ver, m) != 0) {
    malf:
                   error("%s: malformed message", __func__);
                   return -1;
           }
           if (ver != SSHMUX_VER) {
                   error("Unsupported multiplexing protocol version %d "
                       "(expected %d)", ver, SSHMUX_VER);
                   return -1;
           }
           debug2("%s: channel %d slave version %u", __func__, c->self, ver);
   
           /* No extensions are presently defined */
           while (buffer_len(m) > 0) {
                   char *name = buffer_get_string_ret(m, NULL);
                   char *value = buffer_get_string_ret(m, NULL);
   
                   if (name == NULL || value == NULL) {
                           if (name != NULL)
                                   xfree(name);
                           goto malf;
                   }
                   debug2("Unrecognised slave extension \"%s\"", name);
                   xfree(name);
                   xfree(value);
           }
           state->hello_rcvd = 1;
           return 0;
   }
   
   static int
   process_mux_new_session(u_int rid, Channel *c, Buffer *m, Buffer *r)
   {
           Channel *nc;
           struct mux_session_confirm_ctx *cctx;
           char *reserved, *cmd, *cp;
           u_int i, j, len, env_len, escape_char, window, packetmax;
           int new_fd[3];
   
           /* Reply for SSHMUX_COMMAND_OPEN */
           cctx = xcalloc(1, sizeof(*cctx));
           cctx->term = NULL;
           cmd = NULL;
           if ((reserved = buffer_get_string_ret(m, NULL)) == NULL ||
               buffer_get_int_ret(&cctx->want_tty, m) != 0 ||
               buffer_get_int_ret(&cctx->want_x_fwd, m) != 0 ||
               buffer_get_int_ret(&cctx->want_agent_fwd, m) != 0 ||
               buffer_get_int_ret(&cctx->want_subsys, m) != 0 ||
               buffer_get_int_ret(&escape_char, m) != 0 ||
               (cctx->term = buffer_get_string_ret(m, &len)) == NULL ||
               (cmd = buffer_get_string_ret(m, &len)) == NULL) {
    malf:
                   if (cctx->term != NULL)
                           xfree(cctx->term);
                   error("%s: malformed message", __func__);
                   return -1;
           }
           xfree(reserved);
   
           cctx->env = NULL;
           env_len = 0;
           while (buffer_len(m) > 0) {
   #define MUX_MAX_ENV_VARS        4096
                   if ((cp = buffer_get_string_ret(m, &len)) == NULL) {
                           xfree(cmd);
                           goto malf;
                   }
                   if (!env_permitted(cp)) {
                           xfree(cp);
                           continue;
                   }
                   cctx->env = xrealloc(cctx->env, env_len + 2,
                       sizeof(*cctx->env));
                   cctx->env[env_len++] = cp;
                   cctx->env[env_len] = NULL;
                   if (env_len > MUX_MAX_ENV_VARS) {
                           error(">%d environment variables received, ignoring "
                               "additional", MUX_MAX_ENV_VARS);
                           break;
                   }
           }
   
           debug2("%s: channel %d: request tty %d, X %d, agent %d, subsys %d, "
               "term \"%s\", cmd \"%s\", env %u", __func__, c->self,
               cctx->want_tty, cctx->want_x_fwd, cctx->want_agent_fwd,
               cctx->want_subsys, cctx->term, cmd, env_len);
   
           buffer_init(&cctx->cmd);
           buffer_append(&cctx->cmd, cmd, strlen(cmd));
           xfree(cmd);
   
           /* Gather fds from client */
           for(i = 0; i < 3; i++) {
                   if ((new_fd[i] = mm_receive_fd(c->sock)) == -1) {
                           error("%s: failed to receive fd %d from slave",
                               __func__, i);
                           for (j = 0; j < i; j++)
                                   close(new_fd[j]);
                           for (j = 0; j < env_len; j++)
                                   xfree(cctx->env[j]);
                           if (env_len > 0)
                                   xfree(cctx->env);
                           xfree(cctx->term);
                           buffer_free(&cctx->cmd);
                           xfree(cctx);
   
                           /* prepare reply */
                           buffer_put_int(r, MUX_S_FAILURE);
                           buffer_put_int(r, rid);
                           buffer_put_cstring(r,
                               "did not receive file descriptors");
                           return -1;
                   }
           }
   
           debug3("%s: got fds stdin %d, stdout %d, stderr %d", __func__,
               new_fd[0], new_fd[1], new_fd[2]);
   
           /* XXX support multiple child sessions in future */
           if (c->remote_id != -1) {
                   debug2("%s: session already open", __func__);
                   /* prepare reply */
                   buffer_put_int(r, MUX_S_FAILURE);
                   buffer_put_int(r, rid);
                   buffer_put_cstring(r, "Multiple sessions not supported");
    cleanup:
                   close(new_fd[0]);
                   close(new_fd[1]);
                   close(new_fd[2]);
                   xfree(cctx->term);
                   if (env_len != 0) {
                           for (i = 0; i < env_len; i++)
                                   xfree(cctx->env[i]);
                           xfree(cctx->env);
                   }
                   buffer_free(&cctx->cmd);
                   return 0;
           }
   
           if (options.control_master == SSHCTL_MASTER_ASK ||
               options.control_master == SSHCTL_MASTER_AUTO_ASK) {
                   if (!ask_permission("Allow shared connection to %s? ", host)) {
                           debug2("%s: session refused by user", __func__);
                           /* prepare reply */
                           buffer_put_int(r, MUX_S_PERMISSION_DENIED);
                           buffer_put_int(r, rid);
                           buffer_put_cstring(r, "Permission denied");
                           goto cleanup;
                   }
           }
   
           /* 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));
   
           /* 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]);
   
           window = CHAN_SES_WINDOW_DEFAULT;
           packetmax = CHAN_SES_PACKET_DEFAULT;
           if (cctx->want_tty) {
                   window >>= 1;
                   packetmax >>= 1;
           }
   
           nc = channel_new("session", SSH_CHANNEL_OPENING,
               new_fd[0], new_fd[1], new_fd[2], window, packetmax,
               CHAN_EXTENDED_WRITE, "client-session", /*nonblock*/0);
   
           nc->ctl_chan = c->self;         /* link session -> control channel */
           c->remote_id = nc->self;        /* link control -> session channel */
   
           if (cctx->want_tty && escape_char != 0xffffffff) {
                   channel_register_filter(nc->self,
                       client_simple_escape_filter, NULL,
                       client_filter_cleanup,
                       client_new_escape_filter_ctx((int)escape_char));
           }
   
           debug2("%s: channel_new: %d linked to control channel %d",
               __func__, nc->self, nc->ctl_chan);
   
           channel_send_open(nc->self);
           channel_register_open_confirm(nc->self, mux_session_confirm, cctx);
           channel_register_cleanup(nc->self, mux_master_session_cleanup_cb, 0);
   
           /* prepare reply */
           /* XXX defer until mux_session_confirm() fires */
           buffer_put_int(r, MUX_S_SESSION_OPENED);
           buffer_put_int(r, rid);
           buffer_put_int(r, nc->self);
   
           return 0;
   }
   
   static int
   process_mux_alive_check(u_int rid, Channel *c, Buffer *m, Buffer *r)
   {
           debug2("%s: channel %d: alive check", __func__, c->self);
   
           /* prepare reply */
           buffer_put_int(r, MUX_S_ALIVE);
           buffer_put_int(r, rid);
           buffer_put_int(r, (u_int)getpid());
   
           return 0;
   }
   
   static int
   process_mux_terminate(u_int rid, Channel *c, Buffer *m, Buffer *r)
   {
           debug2("%s: channel %d: terminate request", __func__, c->self);
   
           if (options.control_master == SSHCTL_MASTER_ASK ||
               options.control_master == SSHCTL_MASTER_AUTO_ASK) {
                   if (!ask_permission("Terminate shared connection to %s? ",
                       host)) {
                           debug2("%s: termination refused by user", __func__);
                           buffer_put_int(r, MUX_S_PERMISSION_DENIED);
                           buffer_put_int(r, rid);
                           buffer_put_cstring(r, "Permission denied");
                           return 0;
                   }
           }
   
           quit_pending = 1;
           buffer_put_int(r, MUX_S_OK);
           buffer_put_int(r, rid);
           /* XXX exit happens too soon - message never makes it to client */
           return 0;
   }
   
   static char *
   format_forward(u_int ftype, Forward *fwd)
   {
           char *ret;
   
           switch (ftype) {
           case MUX_FWD_LOCAL:
                   xasprintf(&ret, "local forward %.200s:%d -> %.200s:%d",
                       (fwd->listen_host == NULL) ?
                       (options.gateway_ports ? "*" : "LOCALHOST") :
                       fwd->listen_host, fwd->listen_port,
                       fwd->connect_host, fwd->connect_port);
                   break;
           case MUX_FWD_DYNAMIC:
                   xasprintf(&ret, "dynamic forward %.200s:%d -> *",
                       (fwd->listen_host == NULL) ?
                       (options.gateway_ports ? "*" : "LOCALHOST") :
                        fwd->listen_host, fwd->listen_port);
                   break;
           case MUX_FWD_REMOTE:
                   xasprintf(&ret, "remote forward %.200s:%d -> %.200s:%d",
                       (fwd->listen_host == NULL) ?
                       "LOCALHOST" : fwd->listen_host,
                       fwd->listen_port,
                       fwd->connect_host, fwd->connect_port);
                   break;
           default:
                   fatal("%s: unknown forward type %u", __func__, ftype);
           }
           return ret;
   }
   
   static int
   compare_host(const char *a, const char *b)
   {
           if (a == NULL && b == NULL)
                   return 1;
           if (a == NULL || b == NULL)
                   return 0;
           return strcmp(a, b) == 0;
   }
   
   static int
   compare_forward(Forward *a, Forward *b)
   {
           if (!compare_host(a->listen_host, b->listen_host))
                   return 0;
           if (a->listen_port != b->listen_port)
                   return 0;
           if (!compare_host(a->connect_host, b->connect_host))
                   return 0;
           if (a->connect_port != b->connect_port)
                   return 0;
   
           return 1;
   }
   
   static int
   process_mux_open_fwd(u_int rid, Channel *c, Buffer *m, Buffer *r)
   {
           Forward fwd;
           char *fwd_desc = NULL;
           u_int ftype;
           int i, ret = 0, freefwd = 1;
   
           fwd.listen_host = fwd.connect_host = NULL;
           if (buffer_get_int_ret(&ftype, m) != 0 ||
               (fwd.listen_host = buffer_get_string_ret(m, NULL)) == NULL ||
               buffer_get_int_ret(&fwd.listen_port, m) != 0 ||
               (fwd.connect_host = buffer_get_string_ret(m, NULL)) == NULL ||
               buffer_get_int_ret(&fwd.connect_port, m) != 0) {
                   error("%s: malformed message", __func__);
                   ret = -1;
                   goto out;
           }
   
           if (*fwd.listen_host == '\0') {
                   xfree(fwd.listen_host);
                   fwd.listen_host = NULL;
           }
           if (*fwd.connect_host == '\0') {
                   xfree(fwd.connect_host);
                   fwd.connect_host = NULL;
           }
   
           debug2("%s: channel %d: request %s", __func__, c->self,
               (fwd_desc = format_forward(ftype, &fwd)));
   
           if (ftype != MUX_FWD_LOCAL && ftype != MUX_FWD_REMOTE &&
               ftype != MUX_FWD_DYNAMIC) {
                   logit("%s: invalid forwarding type %u", __func__, ftype);
    invalid:
                   xfree(fwd.listen_host);
                   xfree(fwd.connect_host);
                   buffer_put_int(r, MUX_S_FAILURE);
                   buffer_put_int(r, rid);
                   buffer_put_cstring(r, "Invalid forwarding request");
                   return 0;
           }
           /* XXX support rport0 forwarding with reply of port assigned */
           if (fwd.listen_port == 0 || fwd.listen_port >= 65536) {
                   logit("%s: invalid listen port %u", __func__,
                       fwd.listen_port);
                   goto invalid;
           }
           if (fwd.connect_port >= 65536 || (ftype != MUX_FWD_DYNAMIC &&
               ftype != MUX_FWD_REMOTE && fwd.connect_port == 0)) {
                   logit("%s: invalid connect port %u", __func__,
                       fwd.connect_port);
                   goto invalid;
           }
           if (ftype != MUX_FWD_DYNAMIC && fwd.connect_host == NULL) {
                   logit("%s: missing connect host", __func__);
                   goto invalid;
           }
   
           /* Skip forwards that have already been requested */
           switch (ftype) {
           case MUX_FWD_LOCAL:
           case MUX_FWD_DYNAMIC:
                   for (i = 0; i < options.num_local_forwards; i++) {
                           if (compare_forward(&fwd,
                               options.local_forwards + i)) {
    exists:
                                   debug2("%s: found existing forwarding",
                                       __func__);
                                   buffer_put_int(r, MUX_S_OK);
                                   buffer_put_int(r, rid);
                                   goto out;
                           }
                   }
                   break;
           case MUX_FWD_REMOTE:
                   for (i = 0; i < options.num_remote_forwards; i++) {
                           if (compare_forward(&fwd,
                               options.remote_forwards + i))
                                   goto exists;
                   }
                   break;
           }
   
           if (options.control_master == SSHCTL_MASTER_ASK ||
               options.control_master == SSHCTL_MASTER_AUTO_ASK) {
                   if (!ask_permission("Open %s on %s?", fwd_desc, host)) {
                           debug2("%s: forwarding refused by user", __func__);
                           buffer_put_int(r, MUX_S_PERMISSION_DENIED);
                           buffer_put_int(r, rid);
                           buffer_put_cstring(r, "Permission denied");
                           goto out;
                   }
           }
   
           if (ftype == MUX_FWD_LOCAL || ftype == MUX_FWD_DYNAMIC) {
                   if (options.num_local_forwards + 1 >=
                       SSH_MAX_FORWARDS_PER_DIRECTION ||
                       channel_setup_local_fwd_listener(fwd.listen_host,
                       fwd.listen_port, fwd.connect_host, fwd.connect_port,
                       options.gateway_ports) < 0) {
    fail:
                           logit("slave-requested %s failed", fwd_desc);
                           buffer_put_int(r, MUX_S_FAILURE);
                           buffer_put_int(r, rid);
                           buffer_put_cstring(r, "Port forwarding failed");
                           goto out;
                   }
                   add_local_forward(&options, &fwd);
                   freefwd = 0;
           } else {
                   /* XXX wait for remote to confirm */
                   if (options.num_remote_forwards + 1 >=
                       SSH_MAX_FORWARDS_PER_DIRECTION ||
                       channel_request_remote_forwarding(fwd.listen_host,
                       fwd.listen_port, fwd.connect_host, fwd.connect_port) < 0)
                           goto fail;
                   add_remote_forward(&options, &fwd);
                   freefwd = 0;
           }
           buffer_put_int(r, MUX_S_OK);
           buffer_put_int(r, rid);
    out:
           if (fwd_desc != NULL)
                   xfree(fwd_desc);
           if (freefwd) {
                   if (fwd.listen_host != NULL)
                           xfree(fwd.listen_host);
                   if (fwd.connect_host != NULL)
                           xfree(fwd.connect_host);
           }
           return ret;
   }
   
   static int
   process_mux_close_fwd(u_int rid, Channel *c, Buffer *m, Buffer *r)
   {
           Forward fwd;
           char *fwd_desc = NULL;
           u_int ftype;
           int ret = 0;
   
           fwd.listen_host = fwd.connect_host = NULL;
           if (buffer_get_int_ret(&ftype, m) != 0 ||
               (fwd.listen_host = buffer_get_string_ret(m, NULL)) == NULL ||
               buffer_get_int_ret(&fwd.listen_port, m) != 0 ||
               (fwd.connect_host = buffer_get_string_ret(m, NULL)) == NULL ||
               buffer_get_int_ret(&fwd.connect_port, m) != 0) {
                   error("%s: malformed message", __func__);
                   ret = -1;
                   goto out;
           }
   
           if (*fwd.listen_host == '\0') {
                   xfree(fwd.listen_host);
                   fwd.listen_host = NULL;
           }
           if (*fwd.connect_host == '\0') {
                   xfree(fwd.connect_host);
                   fwd.connect_host = NULL;
           }
   
           debug2("%s: channel %d: request %s", __func__, c->self,
               (fwd_desc = format_forward(ftype, &fwd)));
   
           /* XXX implement this */
           buffer_put_int(r, MUX_S_FAILURE);
           buffer_put_int(r, rid);
           buffer_put_cstring(r, "unimplemented");
   
    out:
           if (fwd_desc != NULL)
                   xfree(fwd_desc);
           if (fwd.listen_host != NULL)
                   xfree(fwd.listen_host);
           if (fwd.connect_host != NULL)
                   xfree(fwd.connect_host);
   
           return ret;
   }
   
   static int
   process_mux_stdio_fwd(u_int rid, Channel *c, Buffer *m, Buffer *r)
   {
           Channel *nc;
           char *reserved, *chost;
           u_int cport, i, j;
           int new_fd[2];
   
           if ((reserved = buffer_get_string_ret(m, NULL)) == NULL ||
              (chost = buffer_get_string_ret(m, NULL)) == NULL ||
               buffer_get_int_ret(&cport, m) != 0) {
                   if (chost != NULL)
                           xfree(chost);
                   error("%s: malformed message", __func__);
                   return -1;
           }
           xfree(reserved);
   
           debug2("%s: channel %d: request stdio fwd to %s:%u",
               __func__, c->self, chost, cport);
   
           /* Gather fds from client */
           for(i = 0; i < 2; i++) {
                   if ((new_fd[i] = mm_receive_fd(c->sock)) == -1) {
                           error("%s: failed to receive fd %d from slave",
                               __func__, i);
                           for (j = 0; j < i; j++)
                                   close(new_fd[j]);
                           xfree(chost);
   
                           /* prepare reply */
                           buffer_put_int(r, MUX_S_FAILURE);
                           buffer_put_int(r, rid);
                           buffer_put_cstring(r,
                               "did not receive file descriptors");
                           return -1;
                   }
           }
   
           debug3("%s: got fds stdin %d, stdout %d", __func__,
               new_fd[0], new_fd[1]);
   
           /* XXX support multiple child sessions in future */
           if (c->remote_id != -1) {
                   debug2("%s: session already open", __func__);
                   /* prepare reply */
                   buffer_put_int(r, MUX_S_FAILURE);
                   buffer_put_int(r, rid);
                   buffer_put_cstring(r, "Multiple sessions not supported");
    cleanup:
                   close(new_fd[0]);
                   close(new_fd[1]);
                   xfree(chost);
                   return 0;
           }
   
           if (options.control_master == SSHCTL_MASTER_ASK ||
               options.control_master == SSHCTL_MASTER_AUTO_ASK) {
                   if (!ask_permission("Allow forward to to %s:%u? ",
                       chost, cport)) {
                           debug2("%s: stdio fwd refused by user", __func__);
                           /* prepare reply */
                           buffer_put_int(r, MUX_S_PERMISSION_DENIED);
                           buffer_put_int(r, rid);
                           buffer_put_cstring(r, "Permission denied");
                           goto cleanup;
                   }
           }
   
           /* enable nonblocking unless tty */
           if (!isatty(new_fd[0]))
                   set_nonblock(new_fd[0]);
           if (!isatty(new_fd[1]))
                   set_nonblock(new_fd[1]);
   
           nc = channel_connect_stdio_fwd(chost, cport, new_fd[0], new_fd[1]);
   
           nc->ctl_chan = c->self;         /* link session -> control channel */
           c->remote_id = nc->self;        /* link control -> session channel */
   
           debug2("%s: channel_new: %d linked to control channel %d",
               __func__, nc->self, nc->ctl_chan);
   
           channel_register_cleanup(nc->self, mux_master_session_cleanup_cb, 0);
   
           /* prepare reply */
           /* XXX defer until channel confirmed */
           buffer_put_int(r, MUX_S_SESSION_OPENED);
           buffer_put_int(r, rid);
           buffer_put_int(r, nc->self);
   
           return 0;
   }
   
   /* Channel callbacks fired on read/write from mux slave fd */
   static int
   mux_master_read_cb(Channel *c)
   {
           struct mux_master_state *state = (struct mux_master_state *)c->mux_ctx;
           Buffer in, out;
           void *ptr;
           u_int type, rid, have, i;
           int ret = -1;
   
           /* Setup ctx and  */
           if (c->mux_ctx == NULL) {
                   state = xcalloc(1, sizeof(state));
                   c->mux_ctx = state;
                   channel_register_cleanup(c->self,
                       mux_master_control_cleanup_cb, 0);
   
                   /* Send hello */
                   buffer_init(&out);
                   buffer_put_int(&out, MUX_MSG_HELLO);
                   buffer_put_int(&out, SSHMUX_VER);
                   /* no extensions */
                   buffer_put_string(&c->output, buffer_ptr(&out),
                       buffer_len(&out));
                   buffer_free(&out);
                   debug3("%s: channel %d: hello sent", __func__, c->self);
                   return 0;
           }
   
           buffer_init(&in);
           buffer_init(&out);
   
           /* Channel code ensures that we receive whole packets */
           if ((ptr = buffer_get_string_ptr_ret(&c->input, &have)) == NULL) {
    malf:
                   error("%s: malformed message", __func__);
                   goto out;
           }
           buffer_append(&in, ptr, have);
   
           if (buffer_get_int_ret(&type, &in) != 0)
                   goto malf;
           debug3("%s: channel %d packet type 0x%08x len %u",
               __func__, c->self, type, buffer_len(&in));
   
           if (type == MUX_MSG_HELLO)
                   rid = 0;
           else {
                   if (!state->hello_rcvd) {
                           error("%s: expected MUX_MSG_HELLO(0x%08x), "
                               "received 0x%08x", __func__, MUX_MSG_HELLO, type);
                           goto out;
                   }
                   if (buffer_get_int_ret(&rid, &in) != 0)
                           goto malf;
           }
   
           for (i = 0; mux_master_handlers[i].handler != NULL; i++) {
                   if (type == mux_master_handlers[i].type) {
                           ret = mux_master_handlers[i].handler(rid, c, &in, &out);
                           break;
                   }
           }
           if (mux_master_handlers[i].handler == NULL) {
                   error("%s: unsupported mux message 0x%08x", __func__, type);
                   buffer_put_int(&out, MUX_S_FAILURE);
                   buffer_put_int(&out, rid);
                   buffer_put_cstring(&out, "unsupported request");
                   ret = 0;
           }
           /* Enqueue reply packet */
           if (buffer_len(&out) != 0) {
                   buffer_put_string(&c->output, buffer_ptr(&out),
                       buffer_len(&out));
           }
    out:
           buffer_free(&in);
           buffer_free(&out);
           return ret;
   }
   
   void
   mux_exit_message(Channel *c, int exitval)
   {
           Buffer m;
           Channel *mux_chan;
   
           debug3("%s: channel %d: exit message, evitval %d", __func__, c->self,
               exitval);
   
           if ((mux_chan = channel_by_id(c->ctl_chan)) == NULL)
                   fatal("%s: channel %d missing mux channel %d",
                       __func__, c->self, c->ctl_chan);
   
           /* Append exit message packet to control socket output queue */
           buffer_init(&m);
           buffer_put_int(&m, MUX_S_EXIT_MESSAGE);
           buffer_put_int(&m, c->self);
           buffer_put_int(&m, exitval);
   
           buffer_put_string(&mux_chan->output, buffer_ptr(&m), buffer_len(&m));
           buffer_free(&m);
   }
   
 /* Prepare a mux master to listen on a Unix domain socket. */  /* Prepare a mux master to listen on a Unix domain socket. */
 void  void
 muxserver_listen(void)  muxserver_listen(void)
Line 151 
Line 955 
                 fatal("%s listen(): %s", __func__, strerror(errno));                  fatal("%s listen(): %s", __func__, strerror(errno));
   
         set_nonblock(muxserver_sock);          set_nonblock(muxserver_sock);
   
           mux_listener_channel = channel_new("mux listener",
               SSH_CHANNEL_MUX_LISTENER, muxserver_sock, muxserver_sock, -1,
               CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT,
               0, addr.sun_path, 1);
           mux_listener_channel->mux_rcb = mux_master_read_cb;
           debug3("%s: mux listener channel %d fd %d", __func__,
               mux_listener_channel->self, mux_listener_channel->sock);
 }  }
   
 /* Callback on open confirmation in mux master for a mux client session. */  /* Callback on open confirmation in mux master for a mux client session. */
Line 164 
Line 976 
   
         if (cctx == NULL)          if (cctx == NULL)
                 fatal("%s: cctx == NULL", __func__);                  fatal("%s: cctx == NULL", __func__);
         if ((c = channel_lookup(id)) == NULL)          if ((c = channel_by_id(id)) == NULL)
                 fatal("%s: no channel for id %d", __func__, id);                  fatal("%s: no channel for id %d", __func__, id);
   
         display = getenv("DISPLAY");          display = getenv("DISPLAY");
Line 199 
Line 1011 
         xfree(cctx);          xfree(cctx);
 }  }
   
   /* ** Multiplexing client support */
   
   /* Exit signal handler */
   static void
   control_client_sighandler(int signo)
   {
           muxclient_terminate = signo;
   }
   
 /*  /*
  * Accept a connection on the mux master socket and process the   * Relay signal handler - used to pass some signals from mux client to
  * client's request. Returns flag indicating whether mux master should   * mux master.
  * begin graceful close.  
  */   */
 int  static void
 muxserver_accept_control(void)  control_client_sigrelay(int signo)
 {  {
         Buffer m;          int save_errno = errno;
         Channel *c;  
         int client_fd, new_fd[3], ver, allowed, window, packetmax;  
         socklen_t addrlen;  
         struct sockaddr_storage addr;  
         struct mux_session_confirm_ctx *cctx;  
         char *cmd;  
         u_int i, j, len, env_len, mux_command, flags, escape_char;  
         uid_t euid;  
         gid_t egid;  
         int start_close = 0;  
   
         /*          if (muxserver_pid > 1)
          * Accept connection on control socket                  kill(muxserver_pid, signo);
          */  
         memset(&addr, 0, sizeof(addr));          errno = save_errno;
         addrlen = sizeof(addr);  }
         if ((client_fd = accept(muxserver_sock,  
             (struct sockaddr*)&addr, &addrlen)) == -1) {  static int
                 error("%s accept: %s", __func__, strerror(errno));  mux_client_read(int fd, Buffer *b, u_int need)
                 return 0;  {
           u_int have;
           ssize_t len;
           u_char *p;
           struct pollfd pfd;
   
           pfd.fd = fd;
           pfd.events = POLLIN;
           p = buffer_append_space(b, need);
           for (have = 0; have < need; ) {
                   if (muxclient_terminate) {
                           errno = EINTR;
                           return -1;
                   }
                   len = read(fd, p + have, need - have);
                   if (len < 0) {
                           switch (errno) {
                           case EAGAIN:
                                   (void)poll(&pfd, 1, -1);
                                   /* FALLTHROUGH */
                           case EINTR:
                                   continue;
                           default:
                                   return -1;
                           }
                   }
                   if (len == 0) {
                           errno = EPIPE;
                           return -1;
                   }
                   have += (u_int)len;
         }          }
           return 0;
   }
   
         if (getpeereid(client_fd, &euid, &egid) < 0) {  static int
                 error("%s getpeereid failed: %s", __func__, strerror(errno));  mux_client_write_packet(int fd, Buffer *m)
                 close(client_fd);  {
                 return 0;          Buffer queue;
           u_int have, need;
           int oerrno, len;
           u_char *ptr;
           struct pollfd pfd;
   
           pfd.fd = fd;
           pfd.events = POLLOUT;
           buffer_init(&queue);
           buffer_put_string(&queue, buffer_ptr(m), buffer_len(m));
   
           need = buffer_len(&queue);
           ptr = buffer_ptr(&queue);
   
           for (have = 0; have < need; ) {
                   if (muxclient_terminate) {
                           buffer_free(&queue);
                           errno = EINTR;
                           return -1;
                   }
                   len = write(fd, ptr + have, need - have);
                   if (len < 0) {
                           switch (errno) {
                           case EAGAIN:
                                   (void)poll(&pfd, 1, -1);
                                   /* FALLTHROUGH */
                           case EINTR:
                                   continue;
                           default:
                                   oerrno = errno;
                                   buffer_free(&queue);
                                   errno = oerrno;
                                   return -1;
                           }
                   }
                   if (len == 0) {
                           buffer_free(&queue);
                           errno = EPIPE;
                           return -1;
                   }
                   have += (u_int)len;
         }          }
         if ((euid != 0) && (getuid() != euid)) {          buffer_free(&queue);
                 error("control mode uid mismatch: peer euid %u != uid %u",          return 0;
                     (u_int) euid, (u_int) getuid());  }
                 close(client_fd);  
                 return 0;  static int
   mux_client_read_packet(int fd, Buffer *m)
   {
           Buffer queue;
           u_int need, have;
           void *ptr;
           int oerrno;
   
           buffer_init(&queue);
           if (mux_client_read(fd, &queue, 4) != 0) {
                   if ((oerrno = errno) == EPIPE)
                   debug3("%s: read header failed: %s", __func__, strerror(errno));
                   errno = oerrno;
                   return -1;
         }          }
           need = get_u32(buffer_ptr(&queue));
           if (mux_client_read(fd, &queue, need) != 0) {
                   oerrno = errno;
                   debug3("%s: read body failed: %s", __func__, strerror(errno));
                   errno = oerrno;
                   return -1;
           }
           ptr = buffer_get_string_ptr(&queue, &have);
           buffer_append(m, ptr, have);
           buffer_free(&queue);
           return 0;
   }
   
         /* XXX handle asynchronously */  static int
         unset_nonblock(client_fd);  mux_client_hello_exchange(int fd)
   {
           Buffer m;
           u_int type, ver;
   
         /* Read command */  
         buffer_init(&m);          buffer_init(&m);
         if (ssh_msg_recv(client_fd, &m) == -1) {          buffer_put_int(&m, MUX_MSG_HELLO);
                 error("%s: client msg_recv failed", __func__);          buffer_put_int(&m, SSHMUX_VER);
                 close(client_fd);          /* no extensions */
   
           if (mux_client_write_packet(fd, &m) != 0)
                   fatal("%s: write packet: %s", __func__, strerror(errno));
   
           buffer_clear(&m);
   
           /* Read their HELLO */
           if (mux_client_read_packet(fd, &m) != 0) {
                 buffer_free(&m);                  buffer_free(&m);
                 return 0;                  return -1;
         }          }
         if ((ver = buffer_get_char(&m)) != SSHMUX_VER) {  
                 error("%s: wrong client version %d", __func__, ver);          type = buffer_get_int(&m);
           if (type != MUX_MSG_HELLO)
                   fatal("%s: expected HELLO (%u) received %u",
                       __func__, MUX_MSG_HELLO, type);
           ver = buffer_get_int(&m);
           if (ver != SSHMUX_VER)
                   fatal("Unsupported multiplexing protocol version %d "
                       "(expected %d)", ver, SSHMUX_VER);
           debug2("%s: master version %u", __func__, ver);
           /* No extensions are presently defined */
           while (buffer_len(&m) > 0) {
                   char *name = buffer_get_string(&m, NULL);
                   char *value = buffer_get_string(&m, NULL);
   
                   debug2("Unrecognised master extension \"%s\"", name);
                   xfree(name);
                   xfree(value);
           }
           buffer_free(&m);
           return 0;
   }
   
   static u_int
   mux_client_request_alive(int fd)
   {
           Buffer m;
           char *e;
           u_int pid, type, rid;
   
           debug3("%s: entering", __func__);
   
           buffer_init(&m);
           buffer_put_int(&m, MUX_C_ALIVE_CHECK);
           buffer_put_int(&m, muxclient_request_id);
   
           if (mux_client_write_packet(fd, &m) != 0)
                   fatal("%s: write packet: %s", __func__, strerror(errno));
   
           buffer_clear(&m);
   
           /* Read their reply */
           if (mux_client_read_packet(fd, &m) != 0) {
                 buffer_free(&m);                  buffer_free(&m);
                 close(client_fd);  
                 return 0;                  return 0;
         }          }
   
         allowed = 1;          type = buffer_get_int(&m);
         mux_command = buffer_get_int(&m);          if (type != MUX_S_ALIVE) {
         flags = buffer_get_int(&m);                  e = buffer_get_string(&m, NULL);
                   fatal("%s: master returned error: %s", __func__, e);
           }
   
           if ((rid = buffer_get_int(&m)) != muxclient_request_id)
                   fatal("%s: out of sequence reply: my id %u theirs %u",
                       __func__, muxclient_request_id, rid);
           pid = buffer_get_int(&m);
           buffer_free(&m);
   
           debug3("%s: done pid = %u", __func__, pid);
   
           muxclient_request_id++;
   
           return pid;
   }
   
   static void
   mux_client_request_terminate(int fd)
   {
           Buffer m;
           char *e;
           u_int type, rid;
   
           debug3("%s: entering", __func__);
   
           buffer_init(&m);
           buffer_put_int(&m, MUX_C_TERMINATE);
           buffer_put_int(&m, muxclient_request_id);
   
           if (mux_client_write_packet(fd, &m) != 0)
                   fatal("%s: write packet: %s", __func__, strerror(errno));
   
         buffer_clear(&m);          buffer_clear(&m);
   
         switch (mux_command) {          /* Read their reply */
         case SSHMUX_COMMAND_OPEN:          if (mux_client_read_packet(fd, &m) != 0) {
                 if (options.control_master == SSHCTL_MASTER_ASK ||                  /* Remote end exited already */
                     options.control_master == SSHCTL_MASTER_AUTO_ASK)                  if (errno == EPIPE) {
                         allowed = ask_permission("Allow shared connection "  
                             "to %s? ", host);  
                 /* continue below */  
                 break;  
         case SSHMUX_COMMAND_TERMINATE:  
                 if (options.control_master == SSHCTL_MASTER_ASK ||  
                     options.control_master == SSHCTL_MASTER_AUTO_ASK)  
                         allowed = ask_permission("Terminate shared connection "  
                             "to %s? ", host);  
                 if (allowed)  
                         start_close = 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, SSHMUX_VER, &m) == -1) {  
                         error("%s: client msg_send failed", __func__);  
                         close(client_fd);  
                         buffer_free(&m);                          buffer_free(&m);
                         return start_close;                          return;
                 }                  }
                 buffer_free(&m);                  fatal("%s: read from master failed: %s",
                 close(client_fd);                      __func__, strerror(errno));
                 return start_close;          }
   
           type = buffer_get_int(&m);
           if ((rid = buffer_get_int(&m)) != muxclient_request_id)
                   fatal("%s: out of sequence reply: my id %u theirs %u",
                       __func__, muxclient_request_id, rid);
           switch (type) {
           case MUX_S_OK:
                   break;
           case MUX_S_PERMISSION_DENIED:
                   e = buffer_get_string(&m, NULL);
                   fatal("Master refused termination request: %s", e);
           case MUX_S_FAILURE:
                   e = buffer_get_string(&m, NULL);
                   fatal("%s: termination request failed: %s", __func__, e);
         default:          default:
                 error("Unsupported command %d", mux_command);                  fatal("%s: unexpected response from master 0x%08x",
                 buffer_free(&m);                      __func__, type);
                 close(client_fd);  
                 return 0;  
         }          }
           buffer_free(&m);
           muxclient_request_id++;
   }
   
         /* Reply for SSHMUX_COMMAND_OPEN */  static int
   mux_client_request_forward(int fd, u_int ftype, Forward *fwd)
   {
           Buffer m;
           char *e, *fwd_desc;
           u_int type, rid;
   
           fwd_desc = format_forward(ftype, fwd);
           debug("Requesting %s", fwd_desc);
           xfree(fwd_desc);
   
           buffer_init(&m);
           buffer_put_int(&m, MUX_C_OPEN_FWD);
           buffer_put_int(&m, muxclient_request_id);
           buffer_put_int(&m, ftype);
           buffer_put_cstring(&m,
               fwd->listen_host == NULL ? "" : fwd->listen_host);
           buffer_put_int(&m, fwd->listen_port);
           buffer_put_cstring(&m,
               fwd->connect_host == NULL ? "" : fwd->connect_host);
           buffer_put_int(&m, fwd->connect_port);
   
           if (mux_client_write_packet(fd, &m) != 0)
                   fatal("%s: write packet: %s", __func__, strerror(errno));
   
         buffer_clear(&m);          buffer_clear(&m);
         buffer_put_int(&m, allowed);  
         buffer_put_int(&m, getpid());          /* Read their reply */
         if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) {          if (mux_client_read_packet(fd, &m) != 0) {
                 error("%s: client msg_send failed", __func__);  
                 close(client_fd);  
                 buffer_free(&m);                  buffer_free(&m);
                 return 0;                  return -1;
         }          }
   
         if (!allowed) {          type = buffer_get_int(&m);
                 error("Refused control connection");          if ((rid = buffer_get_int(&m)) != muxclient_request_id)
                 close(client_fd);                  fatal("%s: out of sequence reply: my id %u theirs %u",
                       __func__, muxclient_request_id, rid);
           switch (type) {
           case MUX_S_OK:
                   break;
           case MUX_S_PERMISSION_DENIED:
                   e = buffer_get_string(&m, NULL);
                 buffer_free(&m);                  buffer_free(&m);
                 return 0;                  error("Master refused forwarding request: %s", e);
                   return -1;
           case MUX_S_FAILURE:
                   e = buffer_get_string(&m, NULL);
                   buffer_free(&m);
                   error("%s: session request failed: %s", __func__, e);
                   return -1;
           default:
                   fatal("%s: unexpected response from master 0x%08x",
                       __func__, type);
         }          }
           buffer_free(&m);
   
         buffer_clear(&m);          muxclient_request_id++;
         if (ssh_msg_recv(client_fd, &m) == -1) {          return 0;
                 error("%s: client msg_recv failed", __func__);  }
                 close(client_fd);  
                 buffer_free(&m);  static int
                 return 0;  mux_client_request_forwards(int fd)
   {
           int i;
   
           debug3("%s: requesting forwardings: %d local, %d remote", __func__,
               options.num_local_forwards, options.num_remote_forwards);
   
           /* XXX ExitOnForwardingFailure */
           for (i = 0; i < options.num_local_forwards; i++) {
                   if (mux_client_request_forward(fd,
                       options.local_forwards[i].connect_port == 0 ?
                       MUX_FWD_DYNAMIC : MUX_FWD_LOCAL,
                       options.local_forwards + i) != 0)
                           return -1;
         }          }
         if ((ver = buffer_get_char(&m)) != SSHMUX_VER) {          for (i = 0; i < options.num_remote_forwards; i++) {
                 error("%s: wrong client version %d", __func__, ver);                  if (mux_client_request_forward(fd, MUX_FWD_REMOTE,
                 buffer_free(&m);                      options.remote_forwards + i) != 0)
                 close(client_fd);                          return -1;
                 return 0;  
         }          }
           return 0;
   }
   
         cctx = xcalloc(1, sizeof(*cctx));  static int
         cctx->want_tty = (flags & SSHMUX_FLAG_TTY) != 0;  mux_client_request_session(int fd)
         cctx->want_subsys = (flags & SSHMUX_FLAG_SUBSYS) != 0;  {
         cctx->want_x_fwd = (flags & SSHMUX_FLAG_X11_FWD) != 0;          Buffer m;
         cctx->want_agent_fwd = (flags & SSHMUX_FLAG_AGENT_FWD) != 0;          char *e, *term;
         cctx->term = buffer_get_string(&m, &len);          u_int i, rid, sid, esid, exitval, type, exitval_seen;
         escape_char = buffer_get_int(&m);          extern char **environ;
           int devnull;
   
         cmd = buffer_get_string(&m, &len);          debug3("%s: entering", __func__);
         buffer_init(&cctx->cmd);  
         buffer_append(&cctx->cmd, cmd, strlen(cmd));  
   
         env_len = buffer_get_int(&m);          if ((muxserver_pid = mux_client_request_alive(fd)) == 0) {
         env_len = MIN(env_len, 4096);                  error("%s: master alive request failed", __func__);
         debug3("%s: receiving %d env vars", __func__, env_len);                  return -1;
         if (env_len != 0) {  
                 cctx->env = xcalloc(env_len + 1, sizeof(*cctx->env));  
                 for (i = 0; i < env_len; i++)  
                         cctx->env[i] = buffer_get_string(&m, &len);  
                 cctx->env[i] = NULL;  
         }          }
   
         debug2("%s: accepted tty %d, subsys %d, cmd %s", __func__,          signal(SIGPIPE, SIG_IGN);
             cctx->want_tty, cctx->want_subsys, cmd);  
         xfree(cmd);  
   
         /* Gather fds from client */          if (stdin_null_flag) {
         for(i = 0; i < 3; i++) {                  if ((devnull = open(_PATH_DEVNULL, O_RDONLY)) == -1)
                 if ((new_fd[i] = mm_receive_fd(client_fd)) == -1) {                          fatal("open(/dev/null): %s", strerror(errno));
                         error("%s: failed to receive fd %d from slave",                  if (dup2(devnull, STDIN_FILENO) == -1)
                             __func__, i);                          fatal("dup2: %s", strerror(errno));
                         for (j = 0; j < i; j++)                  if (devnull > STDERR_FILENO)
                                 close(new_fd[j]);                          close(devnull);
                         for (j = 0; j < env_len; j++)  
                                 xfree(cctx->env[j]);  
                         if (env_len > 0)  
                                 xfree(cctx->env);  
                         xfree(cctx->term);  
                         buffer_free(&cctx->cmd);  
                         close(client_fd);  
                         xfree(cctx);  
                         return 0;  
                 }  
         }          }
   
         debug2("%s: got fds stdin %d, stdout %d, stderr %d", __func__,          term = getenv("TERM");
             new_fd[0], new_fd[1], new_fd[2]);  
   
         /* Try to pick up ttymodes from client before it goes raw */          buffer_init(&m);
         if (cctx->want_tty && tcgetattr(new_fd[0], &cctx->tio) == -1)          buffer_put_int(&m, MUX_C_NEW_SESSION);
                 error("%s: tcgetattr: %s", __func__, strerror(errno));          buffer_put_int(&m, muxclient_request_id);
           buffer_put_cstring(&m, ""); /* reserved */
           buffer_put_int(&m, tty_flag);
           buffer_put_int(&m, options.forward_x11);
           buffer_put_int(&m, options.forward_agent);
           buffer_put_int(&m, subsystem_flag);
           buffer_put_int(&m, options.escape_char == SSH_ESCAPECHAR_NONE ?
               0xffffffff : (u_int)options.escape_char);
           buffer_put_cstring(&m, term == NULL ? "" : term);
           buffer_put_string(&m, buffer_ptr(&command), buffer_len(&command));
   
         /* This roundtrip is just for synchronisation of ttymodes */          if (options.num_send_env > 0 && environ != NULL) {
         buffer_clear(&m);                  /* Pass environment */
         if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) {                  for (i = 0; environ[i] != NULL; i++) {
                 error("%s: client msg_send failed", __func__);                          if (env_permitted(environ[i])) {
                 close(client_fd);                                  buffer_put_cstring(&m, environ[i]);
                 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 0;  
         }          }
         buffer_free(&m);  
   
         /* enable nonblocking unless tty */          if (mux_client_write_packet(fd, &m) != 0)
         if (!isatty(new_fd[0]))                  fatal("%s: write packet: %s", __func__, strerror(errno));
                 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);          /* Send the stdio file descriptors */
           if (mm_send_fd(fd, STDIN_FILENO) == -1 ||
               mm_send_fd(fd, STDOUT_FILENO) == -1 ||
               mm_send_fd(fd, STDERR_FILENO) == -1)
                   fatal("%s: send fds failed", __func__);
   
         window = CHAN_SES_WINDOW_DEFAULT;          debug3("%s: session request sent", __func__);
         packetmax = CHAN_SES_PACKET_DEFAULT;  
         if (cctx->want_tty) {          /* Read their reply */
                 window >>= 1;          buffer_clear(&m);
                 packetmax >>= 1;          if (mux_client_read_packet(fd, &m) != 0) {
                   error("%s: read from master failed: %s",
                       __func__, strerror(errno));
                   buffer_free(&m);
                   return -1;
         }          }
   
         c = channel_new("session", SSH_CHANNEL_OPENING,  
             new_fd[0], new_fd[1], new_fd[2], window, packetmax,  
             CHAN_EXTENDED_WRITE, "client-session", /*nonblock*/0);  
   
         c->ctl_fd = client_fd;          type = buffer_get_int(&m);
         if (cctx->want_tty && escape_char != 0xffffffff) {          if ((rid = buffer_get_int(&m)) != muxclient_request_id)
                 channel_register_filter(c->self,                  fatal("%s: out of sequence reply: my id %u theirs %u",
                     client_simple_escape_filter, NULL,                      __func__, muxclient_request_id, rid);
                     client_filter_cleanup,          switch (type) {
                     client_new_escape_filter_ctx((int)escape_char));          case MUX_S_SESSION_OPENED:
                   sid = buffer_get_int(&m);
                   debug("%s: master session id: %u", __func__, sid);
                   break;
           case MUX_S_PERMISSION_DENIED:
                   e = buffer_get_string(&m, NULL);
                   buffer_free(&m);
                   error("Master refused forwarding request: %s", e);
                   return -1;
           case MUX_S_FAILURE:
                   e = buffer_get_string(&m, NULL);
                   buffer_free(&m);
                   error("%s: forwarding request failed: %s", __func__, e);
                   return -1;
           default:
                   buffer_free(&m);
                   error("%s: unexpected response from master 0x%08x",
                       __func__, type);
                   return -1;
         }          }
           muxclient_request_id++;
   
         debug3("%s: channel_new: %d", __func__, c->self);          signal(SIGHUP, control_client_sighandler);
           signal(SIGINT, control_client_sighandler);
           signal(SIGTERM, control_client_sighandler);
           signal(SIGWINCH, control_client_sigrelay);
   
         channel_send_open(c->self);          if (tty_flag)
         channel_register_open_confirm(c->self, mux_session_confirm, cctx);                  enter_raw_mode(force_tty_flag);
         return 0;  
 }  
   
 /* ** Multiplexing client support */          /*
            * Stick around until the controlee closes the client_fd.
            * Before it does, it is expected to write an exit message.
            * This process must read the value and wait for the closure of
            * the client_fd; if this one closes early, the multiplex master will
            * terminate early too (possibly losing data).
            */
           for (exitval = 255, exitval_seen = 0;;) {
                   buffer_clear(&m);
                   if (mux_client_read_packet(fd, &m) != 0)
                           break;
                   type = buffer_get_int(&m);
                   if (type != MUX_S_EXIT_MESSAGE) {
                           e = buffer_get_string(&m, NULL);
                           fatal("%s: master returned error: %s", __func__, e);
                   }
                   if ((esid = buffer_get_int(&m)) != sid)
                           fatal("%s: exit on unknown session: my id %u theirs %u",
                               __func__, sid, esid);
                   debug("%s: master session id: %u", __func__, sid);
                   if (exitval_seen)
                           fatal("%s: exitval sent twice", __func__);
                   exitval = buffer_get_int(&m);
                   exitval_seen = 1;
           }
   
 /* Exit signal handler */          close(fd);
 static void          leave_raw_mode(force_tty_flag);
 control_client_sighandler(int signo)  
 {  
         muxclient_terminate = signo;  
 }  
   
 /*          if (muxclient_terminate) {
  * Relay signal handler - used to pass some signals from mux client to                  debug2("Exiting on signal %d", muxclient_terminate);
  * mux master.                  exitval = 255;
  */          } else if (!exitval_seen) {
 static void                  debug2("Control master terminated unexpectedly");
 control_client_sigrelay(int signo)                  exitval = 255;
 {          } else
         int save_errno = errno;                  debug2("Received exit status from master %d", exitval);
   
         if (muxserver_pid > 1)          if (tty_flag && options.log_level != SYSLOG_LEVEL_QUIET)
                 kill(muxserver_pid, signo);                  fprintf(stderr, "Shared connection to %s closed.\r\n", host);
   
         errno = save_errno;          exit(exitval);
 }  }
   
 /* Check mux client environment variables before passing them to mux master. */  
 static int  static int
 env_permitted(char *env)  mux_client_request_stdio_fwd(int fd)
 {  {
         int i, ret;          Buffer m;
         char name[1024], *cp;          char *e;
           u_int type, rid, sid;
           int devnull;
   
         if ((cp = strchr(env, '=')) == NULL || cp == env)          debug3("%s: entering", __func__);
                 return (0);  
         ret = snprintf(name, sizeof(name), "%.*s", (int)(cp - env), env);  
         if (ret <= 0 || (size_t)ret >= sizeof(name))  
                 fatal("env_permitted: name '%.100s...' too long", env);  
   
         for (i = 0; i < options.num_send_env; i++)          if ((muxserver_pid = mux_client_request_alive(fd)) == 0) {
                 if (match_pattern(name, options.send_env[i]))                  error("%s: master alive request failed", __func__);
                         return (1);                  return -1;
           }
   
         return (0);          signal(SIGPIPE, SIG_IGN);
   
           if (stdin_null_flag) {
                   if ((devnull = open(_PATH_DEVNULL, O_RDONLY)) == -1)
                           fatal("open(/dev/null): %s", strerror(errno));
                   if (dup2(devnull, STDIN_FILENO) == -1)
                           fatal("dup2: %s", strerror(errno));
                   if (devnull > STDERR_FILENO)
                           close(devnull);
           }
   
           buffer_init(&m);
           buffer_put_int(&m, MUX_C_NEW_STDIO_FWD);
           buffer_put_int(&m, muxclient_request_id);
           buffer_put_cstring(&m, ""); /* reserved */
           buffer_put_cstring(&m, stdio_forward_host);
           buffer_put_int(&m, stdio_forward_port);
   
           if (mux_client_write_packet(fd, &m) != 0)
                   fatal("%s: write packet: %s", __func__, strerror(errno));
   
           /* Send the stdio file descriptors */
           if (mm_send_fd(fd, STDIN_FILENO) == -1 ||
               mm_send_fd(fd, STDOUT_FILENO) == -1)
                   fatal("%s: send fds failed", __func__);
   
           debug3("%s: stdio forward request sent", __func__);
   
           /* Read their reply */
           buffer_clear(&m);
   
           if (mux_client_read_packet(fd, &m) != 0) {
                   error("%s: read from master failed: %s",
                       __func__, strerror(errno));
                   buffer_free(&m);
                   return -1;
           }
   
           type = buffer_get_int(&m);
           if ((rid = buffer_get_int(&m)) != muxclient_request_id)
                   fatal("%s: out of sequence reply: my id %u theirs %u",
                       __func__, muxclient_request_id, rid);
           switch (type) {
           case MUX_S_SESSION_OPENED:
                   sid = buffer_get_int(&m);
                   debug("%s: master session id: %u", __func__, sid);
                   break;
           case MUX_S_PERMISSION_DENIED:
                   e = buffer_get_string(&m, NULL);
                   buffer_free(&m);
                   fatal("Master refused forwarding request: %s", e);
           case MUX_S_FAILURE:
                   e = buffer_get_string(&m, NULL);
                   buffer_free(&m);
                   fatal("%s: stdio forwarding request failed: %s", __func__, e);
           default:
                   buffer_free(&m);
                   error("%s: unexpected response from master 0x%08x",
                       __func__, type);
                   return -1;
           }
           muxclient_request_id++;
   
           signal(SIGHUP, control_client_sighandler);
           signal(SIGINT, control_client_sighandler);
           signal(SIGTERM, control_client_sighandler);
           signal(SIGWINCH, control_client_sigrelay);
   
           /*
            * Stick around until the controlee closes the client_fd.
            */
           buffer_clear(&m);
           if (mux_client_read_packet(fd, &m) != 0) {
                   if (errno == EPIPE ||
                       (errno == EINTR && muxclient_terminate != 0))
                           return 0;
                   fatal("%s: mux_client_read_packet: %s",
                       __func__, strerror(errno));
           }
           fatal("%s: master returned unexpected message %u", __func__, type);
 }  }
   
 /* Multiplex client main loop. */  /* Multiplex client main loop. */
Line 491 
Line 1622 
 muxclient(const char *path)  muxclient(const char *path)
 {  {
         struct sockaddr_un addr;          struct sockaddr_un addr;
         int i, r, fd, sock, exitval[2], num_env;          int sock;
         Buffer m;          u_int pid;
         char *term;  
         extern char **environ;  
         u_int allowed, flags;  
   
         if (muxclient_command == 0)          if (muxclient_command == 0) {
                 muxclient_command = SSHMUX_COMMAND_OPEN;                  if (stdio_forward_host != NULL)
                           muxclient_command = SSHMUX_COMMAND_STDIO_FWD;
                   else
                           muxclient_command = SSHMUX_COMMAND_OPEN;
           }
   
         switch (options.control_master) {          switch (options.control_master) {
         case SSHCTL_MASTER_AUTO:          case SSHCTL_MASTER_AUTO:
Line 524 
Line 1656 
                 fatal("%s socket(): %s", __func__, strerror(errno));                  fatal("%s socket(): %s", __func__, strerror(errno));
   
         if (connect(sock, (struct sockaddr *)&addr, addr.sun_len) == -1) {          if (connect(sock, (struct sockaddr *)&addr, addr.sun_len) == -1) {
                 if (muxclient_command != SSHMUX_COMMAND_OPEN) {                  switch (muxclient_command) {
                   case SSHMUX_COMMAND_OPEN:
                   case SSHMUX_COMMAND_STDIO_FWD:
                           break;
                   default:
                         fatal("Control socket connect(%.100s): %s", path,                          fatal("Control socket connect(%.100s): %s", path,
                             strerror(errno));                              strerror(errno));
                 }                  }
Line 537 
Line 1673 
                 close(sock);                  close(sock);
                 return;                  return;
         }          }
           set_nonblock(sock);
   
         if (stdin_null_flag) {          if (mux_client_hello_exchange(sock) != 0) {
                 if ((fd = open(_PATH_DEVNULL, O_RDONLY)) == -1)                  error("%s: master hello exchange failed", __func__);
                         fatal("open(/dev/null): %s", strerror(errno));  
                 if (dup2(fd, STDIN_FILENO) == -1)  
                         fatal("dup2: %s", strerror(errno));  
                 if (fd > STDERR_FILENO)  
                         close(fd);  
         }  
   
         term = getenv("TERM");  
   
         flags = 0;  
         if (tty_flag)  
                 flags |= SSHMUX_FLAG_TTY;  
         if (subsystem_flag)  
                 flags |= SSHMUX_FLAG_SUBSYS;  
         if (options.forward_x11)  
                 flags |= SSHMUX_FLAG_X11_FWD;  
         if (options.forward_agent)  
                 flags |= SSHMUX_FLAG_AGENT_FWD;  
   
         signal(SIGPIPE, SIG_IGN);  
   
         buffer_init(&m);  
   
         /* Send our command to server */  
         buffer_put_int(&m, muxclient_command);  
         buffer_put_int(&m, flags);  
         if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1) {  
                 error("%s: msg_send", __func__);  
  muxerr:  
                 close(sock);                  close(sock);
                 buffer_free(&m);  
                 if (muxclient_command != SSHMUX_COMMAND_OPEN)  
                         cleanup_exit(255);  
                 logit("Falling back to non-multiplexed connection");  
                 xfree(options.control_path);  
                 options.control_path = NULL;  
                 options.control_master = SSHCTL_MASTER_NO;  
                 return;                  return;
         }          }
         buffer_clear(&m);  
   
         /* Get authorisation status and PID of controlee */  
         if (ssh_msg_recv(sock, &m) == -1) {  
                 error("%s: Did not receive reply from master", __func__);  
                 goto muxerr;  
         }  
         if (buffer_get_char(&m) != SSHMUX_VER) {  
                 error("%s: Master replied with wrong version", __func__);  
                 goto muxerr;  
         }  
         if (buffer_get_int_ret(&allowed, &m) != 0) {  
                 error("%s: bad server reply", __func__);  
                 goto muxerr;  
         }  
         if (allowed != 1) {  
                 error("Connection to master denied");  
                 goto muxerr;  
         }  
         muxserver_pid = buffer_get_int(&m);  
   
         buffer_clear(&m);  
   
         switch (muxclient_command) {          switch (muxclient_command) {
         case SSHMUX_COMMAND_ALIVE_CHECK:          case SSHMUX_COMMAND_ALIVE_CHECK:
                 fprintf(stderr, "Master running (pid=%d)\r\n",                  if ((pid = mux_client_request_alive(sock)) == 0)
                     muxserver_pid);                          fatal("%s: master alive check failed", __func__);
                   fprintf(stderr, "Master running (pid=%d)\r\n", pid);
                 exit(0);                  exit(0);
         case SSHMUX_COMMAND_TERMINATE:          case SSHMUX_COMMAND_TERMINATE:
                   mux_client_request_terminate(sock);
                 fprintf(stderr, "Exit request sent.\r\n");                  fprintf(stderr, "Exit request sent.\r\n");
                 exit(0);                  exit(0);
         case SSHMUX_COMMAND_OPEN:          case SSHMUX_COMMAND_OPEN:
                 buffer_put_cstring(&m, term ? term : "");                  if (mux_client_request_forwards(sock) != 0) {
                 if (options.escape_char == SSH_ESCAPECHAR_NONE)                          error("%s: master forward request failed", __func__);
                         buffer_put_int(&m, 0xffffffff);                          return;
                 else  
                         buffer_put_int(&m, options.escape_char);  
                 buffer_append(&command, "\0", 1);  
                 buffer_put_cstring(&m, buffer_ptr(&command));  
   
                 if (options.num_send_env == 0 || environ == NULL) {  
                         buffer_put_int(&m, 0);  
                 } else {  
                         /* Pass environment */  
                         num_env = 0;  
                         for (i = 0; environ[i] != NULL; i++) {  
                                 if (env_permitted(environ[i]))  
                                         num_env++; /* Count */  
                         }  
                         buffer_put_int(&m, num_env);  
                 for (i = 0; environ[i] != NULL && num_env >= 0; i++) {  
                                 if (env_permitted(environ[i])) {  
                                         num_env--;  
                                         buffer_put_cstring(&m, environ[i]);  
                                 }  
                         }  
                 }                  }
                 break;                  mux_client_request_session(sock);
                   return;
           case SSHMUX_COMMAND_STDIO_FWD:
                   mux_client_request_stdio_fwd(sock);
                   exit(0);
         default:          default:
                 fatal("unrecognised muxclient_command %d", muxclient_command);                  fatal("unrecognised muxclient_command %d", muxclient_command);
         }          }
   
         if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1) {  
                 error("%s: msg_send", __func__);  
                 goto muxerr;  
         }  
   
         if (mm_send_fd(sock, STDIN_FILENO) == -1 ||  
             mm_send_fd(sock, STDOUT_FILENO) == -1 ||  
             mm_send_fd(sock, STDERR_FILENO) == -1) {  
                 error("%s: send fds failed", __func__);  
                 goto muxerr;  
         }  
   
         /*  
          * Mux errors are non-recoverable from this point as the master  
          * has ownership of the session now.  
          */  
   
         /* 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) != SSHMUX_VER)  
                 fatal("%s: wrong version", __func__);  
         buffer_free(&m);  
   
         signal(SIGHUP, control_client_sighandler);  
         signal(SIGINT, control_client_sighandler);  
         signal(SIGTERM, control_client_sighandler);  
         signal(SIGWINCH, control_client_sigrelay);  
   
         if (tty_flag)  
                 enter_raw_mode(force_tty_flag);  
   
         /*  
          * Stick around until the controlee closes the client_fd.  
          * Before it does, it is expected to write this process' exit  
          * value (one int). This process must read the value and wait for  
          * the closure of the client_fd; if this one closes early, the  
          * multiplex master will terminate early too (possibly losing data).  
          */  
         exitval[0] = 0;  
         for (i = 0; !muxclient_terminate && i < (int)sizeof(exitval);) {  
                 r = read(sock, (char *)exitval + i, sizeof(exitval) - i);  
                 if (r == 0) {  
                         debug2("Received EOF from master");  
                         break;  
                 }  
                 if (r == -1) {  
                         if (errno == EINTR)  
                                 continue;  
                         fatal("%s: read %s", __func__, strerror(errno));  
                 }  
                 i += r;  
         }  
   
         close(sock);  
         leave_raw_mode(force_tty_flag);  
         if (i > (int)sizeof(int))  
                 fatal("%s: master returned too much data (%d > %lu)",  
                     __func__, i, (u_long)sizeof(int));  
         if (muxclient_terminate) {  
                 debug2("Exiting on signal %d", muxclient_terminate);  
                 exitval[0] = 255;  
         } else if (i < (int)sizeof(int)) {  
                 debug2("Control master terminated unexpectedly");  
                 exitval[0] = 255;  
         } else  
                 debug2("Received exit status from master %d", exitval[0]);  
   
         if (tty_flag && options.log_level != SYSLOG_LEVEL_QUIET)  
                 fprintf(stderr, "Shared connection to %s closed.\r\n", host);  
   
         exit(exitval[0]);  
 }  }

Legend:
Removed from v.1.9  
changed lines
  Added in v.1.10