diff options
author | markus@openbsd.org <markus@openbsd.org> | 2016-09-30 09:19:13 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2016-10-01 02:45:10 +1000 |
commit | 8d0578478586e283e751ca51e7b0690631da139a (patch) | |
tree | 3621da2b97213f8ff0b434f5fd239dfd4f50d83d | |
parent | b7689155f3f5c4999846c07a852b1c7a43b09cec (diff) |
upstream commit
ssh proxy mux mode (-O proxy; idea from Simon Tatham): - mux
client speaks the ssh-packet protocol directly over unix-domain socket. - mux
server acts as a proxy, translates channel IDs and relays to the server. - no
filedescriptor passing necessary. - combined with unix-domain forwarding it's
even possible to run mux client and server on different machines. feedback
& ok djm@
Upstream-ID: 666a2fb79f58e5c50e246265fb2b9251e505c25b
-rw-r--r-- | channels.c | 392 | ||||
-rw-r--r-- | channels.h | 12 | ||||
-rw-r--r-- | clientloop.c | 45 | ||||
-rw-r--r-- | clientloop.h | 5 | ||||
-rw-r--r-- | mux.c | 69 | ||||
-rw-r--r-- | opacket.h | 4 | ||||
-rw-r--r-- | packet.c | 93 | ||||
-rw-r--r-- | packet.h | 6 | ||||
-rw-r--r-- | ssh.c | 27 |
9 files changed, 612 insertions, 41 deletions
@@ -1,4 +1,4 @@ -/* $OpenBSD: channels.c,v 1.353 2016/09/19 07:52:42 natano Exp $ */ +/* $OpenBSD: channels.c,v 1.354 2016/09/30 09:19:13 markus Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -71,6 +71,7 @@ #include "ssh.h" #include "ssh1.h" #include "ssh2.h" +#include "ssherr.h" #include "packet.h" #include "log.h" #include "misc.h" @@ -120,6 +121,7 @@ typedef struct { char *listen_host; /* Remote side should listen address. */ char *listen_path; /* Remote side should listen path. */ int listen_port; /* Remote side should listen port. */ + Channel *downstream; /* Downstream mux*/ } ForwardPermission; /* List of all permitted host/port pairs to connect by the user. */ @@ -183,6 +185,7 @@ static int IPv4or6 = AF_UNSPEC; /* helper */ static void port_open_helper(Channel *c, char *rtype); +static const char *channel_rfwd_bind_host(const char *listen_host); /* non-blocking connect helpers */ static int connect_next(struct channel_connect *); @@ -207,6 +210,20 @@ channel_by_id(int id) return c; } +Channel * +channel_by_remote_id(int remote_id) +{ + Channel *c; + u_int i; + + for (i = 0; i < channels_alloc; i++) { + c = channels[i]; + if (c != NULL && c->remote_id == remote_id) + return c; + } + return NULL; +} + /* * Returns the channel if it is allowed to receive protocol messages. * Private channels, like listening sockets, may not receive messages. @@ -229,6 +246,7 @@ channel_lookup(int id) case SSH_CHANNEL_INPUT_DRAINING: case SSH_CHANNEL_OUTPUT_DRAINING: case SSH_CHANNEL_ABANDONED: + case SSH_CHANNEL_MUX_PROXY: return (c); } logit("Non-public channel %d, type %d.", id, c->type); @@ -410,14 +428,56 @@ channel_free(Channel *c) { char *s; u_int i, n; + Channel *other; struct channel_confirm *cc; - for (n = 0, i = 0; i < channels_alloc; i++) - if (channels[i]) + for (n = 0, i = 0; i < channels_alloc; i++) { + if ((other = channels[i]) != NULL) { n++; + + /* detach from mux client and prepare for closing */ + if (c->type == SSH_CHANNEL_MUX_CLIENT && + other->type == SSH_CHANNEL_MUX_PROXY && + other->mux_ctx == c) { + other->mux_ctx = NULL; + other->type = SSH_CHANNEL_OPEN; + other->istate = CHAN_INPUT_CLOSED; + other->ostate = CHAN_OUTPUT_CLOSED; + } + } + } debug("channel %d: free: %s, nchannels %u", c->self, c->remote_name ? c->remote_name : "???", n); + /* XXX more MUX cleanup: remove remote forwardings */ + if (c->type == SSH_CHANNEL_MUX_CLIENT) { + for (i = 0; i < (u_int)num_permitted_opens; i++) { + if (permitted_opens[i].downstream != c) + continue; + /* cancel on the server, since mux client is gone */ + debug("channel %d: cleanup remote forward for %s:%u", + c->self, + permitted_opens[i].listen_host, + permitted_opens[i].listen_port); + packet_start(SSH2_MSG_GLOBAL_REQUEST); + packet_put_cstring("cancel-tcpip-forward"); + packet_put_char(0); + packet_put_cstring(channel_rfwd_bind_host( + permitted_opens[i].listen_host)); + packet_put_int(permitted_opens[i].listen_port); + packet_send(); + /* unregister */ + permitted_opens[i].listen_port = 0; + permitted_opens[i].port_to_connect = 0; + free(permitted_opens[i].host_to_connect); + permitted_opens[i].host_to_connect = NULL; + free(permitted_opens[i].listen_host); + permitted_opens[i].listen_host = NULL; + permitted_opens[i].listen_path = NULL; + permitted_opens[i].downstream = NULL; + } + } + s = channel_open_message(); debug3("channel %d: status: %s", c->self, s); free(s); @@ -563,6 +623,7 @@ channel_still_open(void) case SSH_CHANNEL_OPEN: case SSH_CHANNEL_X11_OPEN: case SSH_CHANNEL_MUX_CLIENT: + case SSH_CHANNEL_MUX_PROXY: return 1; case SSH_CHANNEL_INPUT_DRAINING: case SSH_CHANNEL_OUTPUT_DRAINING: @@ -596,6 +657,7 @@ channel_find_open(void) case SSH_CHANNEL_RPORT_LISTENER: case SSH_CHANNEL_MUX_LISTENER: case SSH_CHANNEL_MUX_CLIENT: + case SSH_CHANNEL_MUX_PROXY: case SSH_CHANNEL_OPENING: case SSH_CHANNEL_CONNECTING: case SSH_CHANNEL_ZOMBIE: @@ -621,7 +683,6 @@ channel_find_open(void) return -1; } - /* * Returns a message describing the currently open forwarded connections, * suitable for sending to the client. The message contains crlf pairs for @@ -650,7 +711,6 @@ channel_open_message(void) case SSH_CHANNEL_AUTH_SOCKET: case SSH_CHANNEL_ZOMBIE: case SSH_CHANNEL_ABANDONED: - case SSH_CHANNEL_MUX_CLIENT: case SSH_CHANNEL_MUX_LISTENER: case SSH_CHANNEL_UNIX_LISTENER: case SSH_CHANNEL_RUNIX_LISTENER: @@ -663,6 +723,8 @@ channel_open_message(void) case SSH_CHANNEL_X11_OPEN: case SSH_CHANNEL_INPUT_DRAINING: case SSH_CHANNEL_OUTPUT_DRAINING: + case SSH_CHANNEL_MUX_PROXY: + case SSH_CHANNEL_MUX_CLIENT: snprintf(buf, sizeof buf, " #%d %.300s (t%d r%d i%u/%d o%u/%d fd %d/%d cc %d)\r\n", c->self, c->remote_name, @@ -2360,6 +2422,278 @@ channel_output_poll(void) } } +/* -- mux proxy support */ + +/* + * When multiplexing channel messages for mux clients we have to deal + * with downstream messages from the mux client and upstream messages + * from the ssh server: + * 1) Handling downstream messages is straightforward and happens + * in channel_proxy_downstream(): + * - We forward all messages (mostly) unmodified to the server. + * - However, in order to route messages from upstream to the correct + * downstream client, we have to replace the channel IDs used by the + * mux clients with a unique channel ID because the mux clients might + * use conflicting channel IDs. + * - so we inspect and change both SSH2_MSG_CHANNEL_OPEN and + * SSH2_MSG_CHANNEL_OPEN_CONFIRMATION messages, create a local + * SSH_CHANNEL_MUX_PROXY channel and replace the mux clients ID + * with the newly allocated channel ID. + * 2) Upstream messages are received by matching SSH_CHANNEL_MUX_PROXY + * channels and procesed by channel_proxy_upstream(). The local channel ID + * is then translated back to the original mux client ID. + * 3) In both cases we need to keep track of matching SSH2_MSG_CHANNEL_CLOSE + * messages so we can clean up SSH_CHANNEL_MUX_PROXY channels. + * 4) The SSH_CHANNEL_MUX_PROXY channels also need to closed when the + * downstream mux client are removed. + * 5) Handling SSH2_MSG_CHANNEL_OPEN messages from the upstream server + * requires more work, because they are not addressed to a specific + * channel. E.g. client_request_forwarded_tcpip() needs to figure + * out whether the request is addressed to the local client or a + * specific downstream client based on the listen-address/port. + * 6) Agent and X11-Forwarding have a similar problem and are currenly + * not supported as the matching session/channel cannot be identified + * easily. + */ + +/* + * receive packets from downstream mux clients: + * channel callback fired on read from mux client, creates + * SSH_CHANNEL_MUX_PROXY channels and translates channel IDs + * on channel creation. + */ +int +channel_proxy_downstream(Channel *downstream) +{ + Channel *c = NULL; + struct ssh *ssh = active_state; + struct sshbuf *original = NULL, *modified = NULL; + const u_char *cp; + char *ctype = NULL, *listen_host = NULL; + u_char type; + size_t have; + int ret = -1, r, id, remote_id, listen_port, idx; + + /* sshbuf_dump(&downstream->input, stderr); */ + if ((r = sshbuf_get_string_direct(&downstream->input, &cp, &have)) + != 0) { + error("%s: malformed message: %s", __func__, ssh_err(r)); + return -1; + } + if (have < 2) { + error("%s: short message", __func__); + return -1; + } + type = cp[1]; + /* skip padlen + type */ + cp += 2; + have -= 2; + if (ssh_packet_log_type(type)) + debug3("%s: channel %u: down->up: type %u", __func__, + downstream->self, type); + + switch (type) { + case SSH2_MSG_CHANNEL_OPEN: + if ((original = sshbuf_from(cp, have)) == NULL || + (modified = sshbuf_new()) == NULL) { + error("%s: alloc", __func__); + goto out; + } + if ((r = sshbuf_get_cstring(original, &ctype, NULL)) != 0 || + (r = sshbuf_get_u32(original, &id)) != 0) { + error("%s: parse error %s", __func__, ssh_err(r)); + goto out; + } + c = channel_new("mux proxy", SSH_CHANNEL_MUX_PROXY, + -1, -1, -1, 0, 0, 0, ctype, 1); + c->mux_ctx = downstream; /* point to mux client */ + c->mux_downstream_id = id; /* original downstream id */ + if ((r = sshbuf_put_cstring(modified, ctype)) != 0 || + (r = sshbuf_put_u32(modified, c->self)) != 0 || + (r = sshbuf_putb(modified, original)) != 0) { + error("%s: compose error %s", __func__, ssh_err(r)); + channel_free(c); + goto out; + } + break; + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + /* + * Almost the same as SSH2_MSG_CHANNEL_OPEN, except then we + * need to parse 'remote_id' instead of 'ctype'. + */ + if ((original = sshbuf_from(cp, have)) == NULL || + (modified = sshbuf_new()) == NULL) { + error("%s: alloc", __func__); + goto out; + } + if ((r = sshbuf_get_u32(original, &remote_id)) != 0 || + (r = sshbuf_get_u32(original, &id)) != 0) { + error("%s: parse error %s", __func__, ssh_err(r)); + goto out; + } + c = channel_new("mux proxy", SSH_CHANNEL_MUX_PROXY, + -1, -1, -1, 0, 0, 0, "mux-down-connect", 1); + c->mux_ctx = downstream; /* point to mux client */ + c->mux_downstream_id = id; + c->remote_id = remote_id; + if ((r = sshbuf_put_u32(modified, remote_id)) != 0 || + (r = sshbuf_put_u32(modified, c->self)) != 0 || + (r = sshbuf_putb(modified, original)) != 0) { + error("%s: compose error %s", __func__, ssh_err(r)); + channel_free(c); + goto out; + } + break; + case SSH2_MSG_GLOBAL_REQUEST: + if ((original = sshbuf_from(cp, have)) == NULL) { + error("%s: alloc", __func__); + goto out; + } + if ((r = sshbuf_get_cstring(original, &ctype, NULL)) != 0) { + error("%s: parse error %s", __func__, ssh_err(r)); + goto out; + } + if (strcmp(ctype, "tcpip-forward") != 0) { + error("%s: unsupported request %s", __func__, ctype); + goto out; + } + if ((r = sshbuf_get_u8(original, NULL)) != 0 || + (r = sshbuf_get_cstring(original, &listen_host, NULL)) != 0 || + (r = sshbuf_get_u32(original, &listen_port)) != 0) { + error("%s: parse error %s", __func__, ssh_err(r)); + goto out; + } + /* Record that connection to this host/port is permitted. */ + permitted_opens = xreallocarray(permitted_opens, + num_permitted_opens + 1, sizeof(*permitted_opens)); + idx = num_permitted_opens++; + permitted_opens[idx].host_to_connect = xstrdup("<mux>"); + permitted_opens[idx].port_to_connect = -1; + permitted_opens[idx].listen_host = listen_host; + permitted_opens[idx].listen_port = listen_port; + permitted_opens[idx].downstream = downstream; + listen_host = NULL; + break; + case SSH2_MSG_CHANNEL_CLOSE: + if (have < 4) + break; + remote_id = PEEK_U32(cp); + if ((c = channel_by_remote_id(remote_id)) != NULL) { + if (c->flags & CHAN_CLOSE_RCVD) + channel_free(c); + else + c->flags |= CHAN_CLOSE_SENT; + } + break; + } + if (modified) { + if ((r = sshpkt_start(ssh, type)) != 0 || + (r = sshpkt_putb(ssh, modified)) != 0 || + (r = sshpkt_send(ssh)) != 0) { + error("%s: send %s", __func__, ssh_err(r)); + goto out; + } + } else { + if ((r = sshpkt_start(ssh, type)) != 0 || + (r = sshpkt_put(ssh, cp, have)) != 0 || + (r = sshpkt_send(ssh)) != 0) { + error("%s: send %s", __func__, ssh_err(r)); + goto out; + } + } + ret = 0; + out: + free(ctype); + free(listen_host); + sshbuf_free(original); + sshbuf_free(modified); + return ret; +} + +/* + * receive packets from upstream server and de-multiplex packets + * to correct downstream: + * implemented as a helper for channel input handlers, + * replaces local (proxy) channel ID with downstream channel ID. + */ +int +channel_proxy_upstream(Channel *c, int type, u_int32_t seq, void *ctxt) +{ + struct ssh *ssh = active_state; + struct sshbuf *b = NULL; + Channel *downstream; + const u_char *cp = NULL; + size_t len; + int r; + + /* + * When receiving packets from the peer we need to check whether we + * need to forward the packets to the mux client. In this case we + * restore the orignal channel id and keep track of CLOSE messages, + * so we can cleanup the channel. + */ + if (c == NULL || c->type != SSH_CHANNEL_MUX_PROXY) + return 0; + if ((downstream = c->mux_ctx) == NULL) + return 0; + switch (type) { + case SSH2_MSG_CHANNEL_CLOSE: + case SSH2_MSG_CHANNEL_DATA: + case SSH2_MSG_CHANNEL_EOF: + case SSH2_MSG_CHANNEL_EXTENDED_DATA: + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + case SSH2_MSG_CHANNEL_OPEN_FAILURE: + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: + case SSH2_MSG_CHANNEL_SUCCESS: + case SSH2_MSG_CHANNEL_FAILURE: + case SSH2_MSG_CHANNEL_REQUEST: + break; + default: + debug2("%s: channel %u: unsupported type %u", __func__, + c->self, type); + return 0; + } + if ((b = sshbuf_new()) == NULL) { + error("%s: alloc reply", __func__); + goto out; + } + /* get remaining payload (after id) */ + cp = sshpkt_ptr(ssh, &len); + if (cp == NULL) { + error("%s: no packet", __func__); + goto out; + } + /* translate id and send to muxclient */ + if ((r = sshbuf_put_u8(b, 0)) != 0 || /* padlen */ + (r = sshbuf_put_u8(b, type)) != 0 || + (r = sshbuf_put_u32(b, c->mux_downstream_id)) != 0 || + (r = sshbuf_put(b, cp, len)) != 0 || + (r = sshbuf_put_stringb(&downstream->output, b)) != 0) { + error("%s: compose for muxclient %s", __func__, ssh_err(r)); + goto out; + } + /* sshbuf_dump(b, stderr); */ + if (ssh_packet_log_type(type)) + debug3("%s: channel %u: up->down: type %u", __func__, c->self, + type); + out: + /* update state */ + switch (type) { + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + /* record remote_id for SSH2_MSG_CHANNEL_CLOSE */ + if (cp && len > 4) + c->remote_id = PEEK_U32(cp); + break; + case SSH2_MSG_CHANNEL_CLOSE: + if (c->flags & CHAN_CLOSE_SENT) + channel_free(c); + else + c->flags |= CHAN_CLOSE_RCVD; + break; + } + sshbuf_free(b); + return 1; +} /* -- protocol input */ @@ -2377,6 +2711,8 @@ channel_input_data(int type, u_int32_t seq, void *ctxt) c = channel_lookup(id); if (c == NULL) packet_disconnect("Received data for nonexistent channel %d.", id); + if (channel_proxy_upstream(c, type, seq, ctxt)) + return 0; /* Ignore any data for non-open channels (might happen on close) */ if (c->type != SSH_CHANNEL_OPEN && @@ -2439,6 +2775,8 @@ channel_input_extended_data(int type, u_int32_t seq, void *ctxt) if (c == NULL) packet_disconnect("Received extended_data for bad channel %d.", id); + if (channel_proxy_upstream(c, type, seq, ctxt)) + return 0; if (c->type != SSH_CHANNEL_OPEN) { logit("channel %d: ext data for non open", id); return 0; @@ -2484,6 +2822,8 @@ channel_input_ieof(int type, u_int32_t seq, void *ctxt) c = channel_lookup(id); if (c == NULL) packet_disconnect("Received ieof for nonexistent channel %d.", id); + if (channel_proxy_upstream(c, type, seq, ctxt)) + return 0; chan_rcvd_ieof(c); /* XXX force input close */ @@ -2508,7 +2848,8 @@ channel_input_close(int type, u_int32_t seq, void *ctxt) c = channel_lookup(id); if (c == NULL) packet_disconnect("Received close for nonexistent channel %d.", id); - + if (channel_proxy_upstream(c, type, seq, ctxt)) + return 0; /* * Send a confirmation that we have closed the channel and no more * data is coming for it. @@ -2543,9 +2884,11 @@ channel_input_oclose(int type, u_int32_t seq, void *ctxt) int id = packet_get_int(); Channel *c = channel_lookup(id); - packet_check_eom(); if (c == NULL) packet_disconnect("Received oclose for nonexistent channel %d.", id); + if (channel_proxy_upstream(c, type, seq, ctxt)) + return 0; + packet_check_eom(); chan_rcvd_oclose(c); return 0; } @@ -2557,10 +2900,12 @@ channel_input_close_confirmation(int type, u_int32_t seq, void *ctxt) int id = packet_get_int(); Channel *c = channel_lookup(id); - packet_check_eom(); if (c == NULL) packet_disconnect("Received close confirmation for " "out-of-range channel %d.", id); + if (channel_proxy_upstream(c, type, seq, ctxt)) + return 0; + packet_check_eom(); if (c->type != SSH_CHANNEL_CLOSED && c->type != SSH_CHANNEL_ABANDONED) packet_disconnect("Received close confirmation for " "non-closed channel %d (type %d).", id, c->type); @@ -2578,7 +2923,12 @@ channel_input_open_confirmation(int type, u_int32_t seq, void *ctxt) id = packet_get_int(); c = channel_lookup(id); - if (c==NULL || c->type != SSH_CHANNEL_OPENING) + if (c==NULL) + packet_disconnect("Received open confirmation for " + "unknown channel %d.", id); + if (channel_proxy_upstream(c, type, seq, ctxt)) + return 0; + if (c->type != SSH_CHANNEL_OPENING) packet_disconnect("Received open confirmation for " "non-opening channel %d.", id); remote_id = packet_get_int(); @@ -2628,7 +2978,12 @@ channel_input_open_failure(int type, u_int32_t seq, void *ctxt) id = packet_get_int(); c = channel_lookup(id); - if (c==NULL || c->type != SSH_CHANNEL_OPENING) + if (c==NULL) + packet_disconnect("Received open failure for " + "unknown channel %d.", id); + if (channel_proxy_upstream(c, type, seq, ctxt)) + return 0; + if (c->type != SSH_CHANNEL_OPENING) packet_disconnect("Received open failure for " "non-opening channel %d.", id); if (compat20) { @@ -2672,6 +3027,8 @@ channel_input_window_adjust(int type, u_int32_t seq, void *ctxt) logit("Received window adjust for non-open channel %d.", id); return 0; } + if (channel_proxy_upstream(c, type, seq, ctxt)) + return 0; adjust = packet_get_int(); packet_check_eom(); debug2("channel %d: rcvd adjust %u", id, adjust); @@ -2726,14 +3083,15 @@ channel_input_status_confirm(int type, u_int32_t seq, void *ctxt) packet_set_alive_timeouts(0); id = packet_get_int(); - packet_check_eom(); - debug2("channel_input_status_confirm: type %d id %d", type, id); if ((c = channel_lookup(id)) == NULL) { logit("channel_input_status_confirm: %d: unknown", id); return 0; } + if (channel_proxy_upstream(c, type, seq, ctxt)) + return 0; + packet_check_eom(); if ((cc = TAILQ_FIRST(&c->status_confirms)) == NULL) return 0; cc->cb(type, c, cc->ctx); @@ -3287,6 +3645,7 @@ channel_request_remote_forwarding(struct Forward *fwd) permitted_opens[idx].listen_path = NULL; permitted_opens[idx].listen_port = fwd->listen_port; } + permitted_opens[idx].downstream = NULL; } return (idx); } @@ -3382,6 +3741,7 @@ channel_request_rforward_cancel_tcpip(const char *host, u_short port) free(permitted_opens[i].listen_host); permitted_opens[i].listen_host = NULL; permitted_opens[i].listen_path = NULL; + permitted_opens[i].downstream = NULL; return 0; } @@ -3419,6 +3779,7 @@ channel_request_rforward_cancel_streamlocal(const char *path) permitted_opens[i].listen_host = NULL; free(permitted_opens[i].listen_path); permitted_opens[i].listen_path = NULL; + permitted_opens[i].downstream = NULL; return 0; } @@ -3501,6 +3862,7 @@ channel_add_permitted_opens(char *host, int port) permitted_opens[num_permitted_opens].listen_host = NULL; permitted_opens[num_permitted_opens].listen_path = NULL; permitted_opens[num_permitted_opens].listen_port = 0; + permitted_opens[num_permitted_opens].downstream = NULL; num_permitted_opens++; all_opens_permitted = 0; @@ -3763,6 +4125,10 @@ connect_to(const char *name, int port, char *ctype, char *rname) return c; } +/* + * returns either the newly connected channel or the downstream channel + * that needs to deal with this connection. + */ Channel * channel_connect_by_listen_address(const char *listen_host, u_short listen_port, char *ctype, char *rname) @@ -3772,6 +4138,8 @@ channel_connect_by_listen_address(const char *listen_host, for (i = 0; i < num_permitted_opens; i++) { if (open_listen_match_tcpip(&permitted_opens[i], listen_host, listen_port, 1)) { + if (permitted_opens[i].downstream) + return permitted_opens[i].downstream; return connect_to( permitted_opens[i].host_to_connect, permitted_opens[i].port_to_connect, ctype, rname); @@ -1,4 +1,4 @@ -/* $OpenBSD: channels.h,v 1.118 2015/07/01 02:26:31 djm Exp $ */ +/* $OpenBSD: channels.h,v 1.119 2016/09/30 09:19:13 markus Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> @@ -58,7 +58,8 @@ #define SSH_CHANNEL_ABANDONED 17 /* Abandoned session, eg mux */ #define SSH_CHANNEL_UNIX_LISTENER 18 /* Listening on a domain socket. */ #define SSH_CHANNEL_RUNIX_LISTENER 19 /* Listening to a R-style domain socket. */ -#define SSH_CHANNEL_MAX_TYPE 20 +#define SSH_CHANNEL_MUX_PROXY 20 /* proxy channel for mux-slave */ +#define SSH_CHANNEL_MAX_TYPE 21 #define CHANNEL_CANCEL_PORT_STATIC -1 @@ -162,6 +163,7 @@ struct Channel { mux_callback_fn *mux_rcb; void *mux_ctx; int mux_pause; + int mux_downstream_id; }; #define CHAN_EXTENDED_IGNORE 0 @@ -209,6 +211,7 @@ struct Channel { /* channel management */ Channel *channel_by_id(int); +Channel *channel_by_remote_id(int); Channel *channel_lookup(int); Channel *channel_new(char *, int, int, int, int, u_int, u_int, int, char *, int); void channel_set_fds(int, int, int, int, int, int, int, u_int); @@ -228,6 +231,11 @@ void channel_cancel_cleanup(int); int channel_close_fd(int *); void channel_send_window_changes(void); +/* mux proxy support */ + +int channel_proxy_downstream(Channel *mc); +int channel_proxy_upstream(Channel *, int, u_int32_t, void *); + /* protocol handler */ int channel_input_close(int, u_int32_t, void *); diff --git a/clientloop.c b/clientloop.c index 58e71224..4289a408 100644 --- a/clientloop.c +++ b/clientloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: clientloop.c,v 1.288 2016/09/17 18:00:27 tedu Exp $ */ +/* $OpenBSD: clientloop.c,v 1.289 2016/09/30 09:19:13 markus Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -1883,11 +1883,14 @@ client_input_agent_open(int type, u_int32_t seq, void *ctxt) } static Channel * -client_request_forwarded_tcpip(const char *request_type, int rchan) +client_request_forwarded_tcpip(const char *request_type, int rchan, + u_int rwindow, u_int rmaxpack) { Channel *c = NULL; + struct sshbuf *b = NULL; char *listen_address, *originator_address; u_short listen_port, originator_port; + int r; /* Get rest of the packet */ listen_address = packet_get_string(NULL); @@ -1902,6 +1905,31 @@ client_request_forwarded_tcpip(const char *request_type, int rchan) c = channel_connect_by_listen_address(listen_address, listen_port, "forwarded-tcpip", originator_address); + if (c != NULL && c->type == SSH_CHANNEL_MUX_CLIENT) { + if ((b = sshbuf_new()) == NULL) { + error("%s: alloc reply", __func__); + goto out; + } + /* reconstruct and send to muxclient */ + if ((r = sshbuf_put_u8(b, 0)) != 0 || /* padlen */ + (r = sshbuf_put_u8(b, SSH2_MSG_CHANNEL_OPEN)) != 0 || + (r = sshbuf_put_cstring(b, request_type)) != 0 || + (r = sshbuf_put_u32(b, rchan)) != 0 || + (r = sshbuf_put_u32(b, rwindow)) != 0 || + (r = sshbuf_put_u32(b, rmaxpack)) != 0 || + (r = sshbuf_put_cstring(b, listen_address)) != 0 || + (r = sshbuf_put_u32(b, listen_port)) != 0 || + (r = sshbuf_put_cstring(b, originator_address)) != 0 || + (r = sshbuf_put_u32(b, originator_port)) != 0 || + (r = sshbuf_put_stringb(&c->output, b)) != 0) { + error("%s: compose for muxclient %s", __func__, + ssh_err(r)); + goto out; + } + } + + out: + sshbuf_free(b); free(originator_address); free(listen_address); return c; @@ -2057,7 +2085,8 @@ client_input_channel_open(int type, u_int32_t seq, void *ctxt) ctype, rchan, rwindow, rmaxpack); if (strcmp(ctype, "forwarded-tcpip") == 0) { - c = client_request_forwarded_tcpip(ctype, rchan); + c = client_request_forwarded_tcpip(ctype, rchan, rwindow, + rmaxpack); } else if (strcmp(ctype, "forwarded-streamlocal@openssh.com") == 0) { c = client_request_forwarded_streamlocal(ctype, rchan); } else if (strcmp(ctype, "x11") == 0) { @@ -2065,8 +2094,9 @@ client_input_channel_open(int type, u_int32_t seq, void *ctxt) } else if (strcmp(ctype, "auth-agent@openssh.com") == 0) { c = client_request_agent(ctype, rchan); } -/* XXX duplicate : */ - if (c != NULL) { + if (c != NULL && c->type == SSH_CHANNEL_MUX_CLIENT) { + debug3("proxied to downstream: %s", ctype); + } else if (c != NULL) { debug("confirm %s", ctype); c->remote_id = rchan; c->remote_window = rwindow; @@ -2102,6 +2132,9 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt) char *rtype; id = packet_get_int(); + c = channel_lookup(id); + if (channel_proxy_upstream(c, type, seq, ctxt)) + return 0; rtype = packet_get_string(NULL); reply = packet_get_char(); @@ -2110,7 +2143,7 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt) if (id == -1) { error("client_input_channel_req: request for channel -1"); - } else if ((c = channel_lookup(id)) == NULL) { + } else if (c == NULL) { error("client_input_channel_req: channel %d: " "unknown channel", id); } else if (strcmp(rtype, "eow@openssh.com") == 0) { diff --git a/clientloop.h b/clientloop.h index f4d4c69b..ae83aa8c 100644 --- a/clientloop.h +++ b/clientloop.h @@ -1,4 +1,4 @@ -/* $OpenBSD: clientloop.h,v 1.32 2016/01/13 23:04:47 djm Exp $ */ +/* $OpenBSD: clientloop.h,v 1.33 2016/09/30 09:19:13 markus Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> @@ -71,9 +71,10 @@ void client_expect_confirm(int, const char *, enum confirm_action); #define SSHMUX_COMMAND_FORWARD 5 /* Forward only, no command */ #define SSHMUX_COMMAND_STOP 6 /* Disable mux but not conn */ #define SSHMUX_COMMAND_CANCEL_FWD 7 /* Cancel forwarding(s) */ +#define SSHMUX_COMMAND_PROXY 8 /* Open new connection */ void muxserver_listen(void); -void muxclient(const char *); +int muxclient(const char *); void mux_exit_message(Channel *, int); void mux_tty_alloc_failed(Channel *); @@ -1,4 +1,4 @@ -/* $OpenBSD: mux.c,v 1.61 2016/08/08 22:40:57 dtucker Exp $ */ +/* $OpenBSD: mux.c,v 1.62 2016/09/30 09:19:13 markus Exp $ */ /* * Copyright (c) 2002-2008 Damien Miller <djm@openbsd.org> * @@ -79,6 +79,7 @@ #include "key.h" #include "readconf.h" #include "clientloop.h" +#include "ssherr.h" /* from ssh.c */ extern int tty_flag; @@ -144,6 +145,7 @@ struct mux_master_state { #define MUX_C_CLOSE_FWD 0x10000007 #define MUX_C_NEW_STDIO_FWD 0x10000008 #define MUX_C_STOP_LISTENING 0x10000009 +#define MUX_C_PROXY 0x1000000f #define MUX_S_OK 0x80000001 #define MUX_S_PERMISSION_DENIED 0x80000002 #define MUX_S_FAILURE 0x80000003 @@ -152,6 +154,7 @@ struct mux_master_state { #define MUX_S_SESSION_OPENED 0x80000006 #define MUX_S_REMOTE_PORT 0x80000007 #define MUX_S_TTY_ALLOC_FAIL 0x80000008 +#define MUX_S_PROXY 0x8000000f /* type codes for MUX_C_OPEN_FWD and MUX_C_CLOSE_FWD */ #define MUX_FWD_LOCAL 1 @@ -169,6 +172,7 @@ 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 int process_mux_stop_listening(u_int, Channel *, Buffer *, Buffer *); +static int process_mux_proxy(u_int, Channel *, Buffer *, Buffer *); static const struct { u_int type; @@ -182,6 +186,7 @@ static const struct { { MUX_C_CLOSE_FWD, process_mux_close_fwd }, { MUX_C_NEW_STDIO_FWD, process_mux_stdio_fwd }, { MUX_C_STOP_LISTENING, process_mux_stop_listening }, + { MUX_C_PROXY, process_mux_proxy }, { 0, NULL } }; @@ -1110,6 +1115,18 @@ process_mux_stop_listening(u_int rid, Channel *c, Buffer *m, Buffer *r) return 0; } +static int +process_mux_proxy(u_int rid, Channel *c, Buffer *m, Buffer *r) +{ + debug("%s: channel %d: proxy request", __func__, c->self); + + c->mux_rcb = channel_proxy_downstream; + buffer_put_int(r, MUX_S_PROXY); + buffer_put_int(r, rid); + + return 0; +} + /* Channel callbacks fired on read/write from mux slave fd */ static int mux_master_read_cb(Channel *c) @@ -1960,6 +1977,41 @@ mux_client_request_session(int fd) } static int +mux_client_proxy(int fd) +{ + Buffer m; + char *e; + u_int type, rid; + + buffer_init(&m); + buffer_put_int(&m, MUX_C_PROXY); + 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); + return 0; + } + type = buffer_get_int(&m); + if (type != MUX_S_PROXY) { + 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); + buffer_free(&m); + + debug3("%s: done", __func__); + muxclient_request_id++; + return 0; +} + +static int mux_client_request_stdio_fwd(int fd) { Buffer m; @@ -2105,7 +2157,7 @@ mux_client_request_stop_listening(int fd) } /* Multiplex client main loop. */ -void +int muxclient(const char *path) { struct sockaddr_un addr; @@ -2128,7 +2180,7 @@ muxclient(const char *path) case SSHCTL_MASTER_NO: break; default: - return; + return -1; } memset(&addr, '\0', sizeof(addr)); @@ -2164,14 +2216,14 @@ muxclient(const char *path) strerror(errno)); } close(sock); - return; + return -1; } set_nonblock(sock); if (mux_client_hello_exchange(sock) != 0) { error("%s: master hello exchange failed", __func__); close(sock); - return; + return -1; } switch (muxclient_command) { @@ -2191,10 +2243,10 @@ muxclient(const char *path) case SSHMUX_COMMAND_OPEN: if (mux_client_forwards(sock, 0) != 0) { error("%s: master forward request failed", __func__); - return; + return -1; } mux_client_request_session(sock); - return; + return -1; case SSHMUX_COMMAND_STDIO_FWD: mux_client_request_stdio_fwd(sock); exit(0); @@ -2207,6 +2259,9 @@ muxclient(const char *path) error("%s: master cancel forward request failed", __func__); exit(0); + case SSHMUX_COMMAND_PROXY: + mux_client_proxy(sock); + return (sock); default: fatal("unrecognised muxclient_command %d", muxclient_command); } |