version 1.371, 2017/09/19 12:10:30 |
version 1.372, 2017/09/21 19:16:53 |
|
|
/* non-blocking connect helpers */ |
/* non-blocking connect helpers */ |
static int connect_next(struct channel_connect *); |
static int connect_next(struct channel_connect *); |
static void channel_connect_ctx_free(struct channel_connect *); |
static void channel_connect_ctx_free(struct channel_connect *); |
|
static Channel *rdynamic_connect_prepare(struct ssh *, char *, char *); |
|
static int rdynamic_connect_finish(struct ssh *, Channel *); |
|
|
/* Setup helper */ |
/* Setup helper */ |
static void channel_handler_init(struct ssh_channels *sc); |
static void channel_handler_init(struct ssh_channels *sc); |
|
|
case SSH_CHANNEL_LARVAL: |
case SSH_CHANNEL_LARVAL: |
case SSH_CHANNEL_CONNECTING: |
case SSH_CHANNEL_CONNECTING: |
case SSH_CHANNEL_DYNAMIC: |
case SSH_CHANNEL_DYNAMIC: |
|
case SSH_CHANNEL_RDYNAMIC_OPEN: |
|
case SSH_CHANNEL_RDYNAMIC_FINISH: |
case SSH_CHANNEL_OPENING: |
case SSH_CHANNEL_OPENING: |
case SSH_CHANNEL_OPEN: |
case SSH_CHANNEL_OPEN: |
case SSH_CHANNEL_ABANDONED: |
case SSH_CHANNEL_ABANDONED: |
|
|
case SSH_CHANNEL_CLOSED: |
case SSH_CHANNEL_CLOSED: |
case SSH_CHANNEL_AUTH_SOCKET: |
case SSH_CHANNEL_AUTH_SOCKET: |
case SSH_CHANNEL_DYNAMIC: |
case SSH_CHANNEL_DYNAMIC: |
|
case SSH_CHANNEL_RDYNAMIC_OPEN: |
case SSH_CHANNEL_CONNECTING: |
case SSH_CHANNEL_CONNECTING: |
case SSH_CHANNEL_ZOMBIE: |
case SSH_CHANNEL_ZOMBIE: |
case SSH_CHANNEL_ABANDONED: |
case SSH_CHANNEL_ABANDONED: |
|
|
continue; |
continue; |
case SSH_CHANNEL_OPENING: |
case SSH_CHANNEL_OPENING: |
case SSH_CHANNEL_OPEN: |
case SSH_CHANNEL_OPEN: |
|
case SSH_CHANNEL_RDYNAMIC_FINISH: |
case SSH_CHANNEL_X11_OPEN: |
case SSH_CHANNEL_X11_OPEN: |
case SSH_CHANNEL_MUX_CLIENT: |
case SSH_CHANNEL_MUX_CLIENT: |
case SSH_CHANNEL_MUX_PROXY: |
case SSH_CHANNEL_MUX_PROXY: |
|
|
switch (c->type) { |
switch (c->type) { |
case SSH_CHANNEL_CLOSED: |
case SSH_CHANNEL_CLOSED: |
case SSH_CHANNEL_DYNAMIC: |
case SSH_CHANNEL_DYNAMIC: |
|
case SSH_CHANNEL_RDYNAMIC_OPEN: |
|
case SSH_CHANNEL_RDYNAMIC_FINISH: |
case SSH_CHANNEL_X11_LISTENER: |
case SSH_CHANNEL_X11_LISTENER: |
case SSH_CHANNEL_PORT_LISTENER: |
case SSH_CHANNEL_PORT_LISTENER: |
case SSH_CHANNEL_RPORT_LISTENER: |
case SSH_CHANNEL_RPORT_LISTENER: |
|
|
case SSH_CHANNEL_OPENING: |
case SSH_CHANNEL_OPENING: |
case SSH_CHANNEL_CONNECTING: |
case SSH_CHANNEL_CONNECTING: |
case SSH_CHANNEL_DYNAMIC: |
case SSH_CHANNEL_DYNAMIC: |
|
case SSH_CHANNEL_RDYNAMIC_OPEN: |
|
case SSH_CHANNEL_RDYNAMIC_FINISH: |
case SSH_CHANNEL_OPEN: |
case SSH_CHANNEL_OPEN: |
case SSH_CHANNEL_X11_OPEN: |
case SSH_CHANNEL_X11_OPEN: |
case SSH_CHANNEL_MUX_PROXY: |
case SSH_CHANNEL_MUX_PROXY: |
|
|
|
|
/* try to decode a socks4 header */ |
/* try to decode a socks4 header */ |
static int |
static int |
channel_decode_socks4(struct ssh *ssh, Channel *c, |
channel_decode_socks4(Channel *c, struct sshbuf *input, struct sshbuf *output) |
fd_set *readset, fd_set *writeset) |
|
{ |
{ |
const u_char *p; |
const u_char *p; |
char *host; |
char *host; |
|
|
|
|
debug2("channel %d: decode socks4", c->self); |
debug2("channel %d: decode socks4", c->self); |
|
|
have = sshbuf_len(c->input); |
have = sshbuf_len(input); |
len = sizeof(s4_req); |
len = sizeof(s4_req); |
if (have < len) |
if (have < len) |
return 0; |
return 0; |
p = sshbuf_ptr(c->input); |
p = sshbuf_ptr(input); |
|
|
need = 1; |
need = 1; |
/* SOCKS4A uses an invalid IP address 0.0.0.x */ |
/* SOCKS4A uses an invalid IP address 0.0.0.x */ |
|
|
} |
} |
if (found < need) |
if (found < need) |
return 0; |
return 0; |
if ((r = sshbuf_get(c->input, &s4_req.version, 1)) != 0 || |
if ((r = sshbuf_get(input, &s4_req.version, 1)) != 0 || |
(r = sshbuf_get(c->input, &s4_req.command, 1)) != 0 || |
(r = sshbuf_get(input, &s4_req.command, 1)) != 0 || |
(r = sshbuf_get(c->input, &s4_req.dest_port, 2)) != 0 || |
(r = sshbuf_get(input, &s4_req.dest_port, 2)) != 0 || |
(r = sshbuf_get(c->input, &s4_req.dest_addr, 4)) != 0) { |
(r = sshbuf_get(input, &s4_req.dest_addr, 4)) != 0) { |
debug("channels %d: decode socks4: %s", c->self, ssh_err(r)); |
debug("channels %d: decode socks4: %s", c->self, ssh_err(r)); |
return -1; |
return -1; |
} |
} |
have = sshbuf_len(c->input); |
have = sshbuf_len(input); |
p = sshbuf_ptr(c->input); |
p = sshbuf_ptr(input); |
if (memchr(p, '\0', have) == NULL) { |
if (memchr(p, '\0', have) == NULL) { |
error("channel %d: decode socks4: user not nul terminated", |
error("channel %d: decode socks4: user not nul terminated", |
c->self); |
c->self); |
|
|
debug2("channel %d: decode socks4: user %s/%d", c->self, p, len); |
debug2("channel %d: decode socks4: user %s/%d", c->self, p, len); |
len++; /* trailing '\0' */ |
len++; /* trailing '\0' */ |
strlcpy(username, p, sizeof(username)); |
strlcpy(username, p, sizeof(username)); |
if ((r = sshbuf_consume(c->input, len)) != 0) { |
if ((r = sshbuf_consume(input, len)) != 0) { |
fatal("%s: channel %d: consume: %s", __func__, |
fatal("%s: channel %d: consume: %s", __func__, |
c->self, ssh_err(r)); |
c->self, ssh_err(r)); |
} |
} |
|
|
host = inet_ntoa(s4_req.dest_addr); |
host = inet_ntoa(s4_req.dest_addr); |
c->path = xstrdup(host); |
c->path = xstrdup(host); |
} else { /* SOCKS4A: two strings */ |
} else { /* SOCKS4A: two strings */ |
have = sshbuf_len(c->input); |
have = sshbuf_len(input); |
p = sshbuf_ptr(c->input); |
p = sshbuf_ptr(input); |
if (memchr(p, '\0', have) == NULL) { |
if (memchr(p, '\0', have) == NULL) { |
error("channel %d: decode socks4a: host not nul " |
error("channel %d: decode socks4a: host not nul " |
"terminated", c->self); |
"terminated", c->self); |
|
|
return -1; |
return -1; |
} |
} |
c->path = xstrdup(p); |
c->path = xstrdup(p); |
if ((r = sshbuf_consume(c->input, len)) != 0) { |
if ((r = sshbuf_consume(input, len)) != 0) { |
fatal("%s: channel %d: consume: %s", __func__, |
fatal("%s: channel %d: consume: %s", __func__, |
c->self, ssh_err(r)); |
c->self, ssh_err(r)); |
} |
} |
|
|
s4_rsp.command = 90; /* cd: req granted */ |
s4_rsp.command = 90; /* cd: req granted */ |
s4_rsp.dest_port = 0; /* ignored */ |
s4_rsp.dest_port = 0; /* ignored */ |
s4_rsp.dest_addr.s_addr = INADDR_ANY; /* ignored */ |
s4_rsp.dest_addr.s_addr = INADDR_ANY; /* ignored */ |
if ((r = sshbuf_put(c->output, &s4_rsp, sizeof(s4_rsp))) != 0) { |
if ((r = sshbuf_put(output, &s4_rsp, sizeof(s4_rsp))) != 0) { |
fatal("%s: channel %d: append reply: %s", __func__, |
fatal("%s: channel %d: append reply: %s", __func__, |
c->self, ssh_err(r)); |
c->self, ssh_err(r)); |
} |
} |
|
|
#define SSH_SOCKS5_SUCCESS 0x00 |
#define SSH_SOCKS5_SUCCESS 0x00 |
|
|
static int |
static int |
channel_decode_socks5(struct ssh *ssh, Channel *c, |
channel_decode_socks5(Channel *c, struct sshbuf *input, struct sshbuf *output) |
fd_set *readset, fd_set *writeset) |
|
{ |
{ |
/* XXX use get/put_u8 instead of trusting struct padding */ |
/* XXX use get/put_u8 instead of trusting struct padding */ |
struct { |
struct { |
|
|
int r; |
int r; |
|
|
debug2("channel %d: decode socks5", c->self); |
debug2("channel %d: decode socks5", c->self); |
p = sshbuf_ptr(c->input); |
p = sshbuf_ptr(input); |
if (p[0] != 0x05) |
if (p[0] != 0x05) |
return -1; |
return -1; |
have = sshbuf_len(c->input); |
have = sshbuf_len(input); |
if (!(c->flags & SSH_SOCKS5_AUTHDONE)) { |
if (!(c->flags & SSH_SOCKS5_AUTHDONE)) { |
/* format: ver | nmethods | methods */ |
/* format: ver | nmethods | methods */ |
if (have < 2) |
if (have < 2) |
|
|
c->self); |
c->self); |
return -1; |
return -1; |
} |
} |
if ((r = sshbuf_consume(c->input, nmethods + 2)) != 0) { |
if ((r = sshbuf_consume(input, nmethods + 2)) != 0) { |
fatal("%s: channel %d: consume: %s", __func__, |
fatal("%s: channel %d: consume: %s", __func__, |
c->self, ssh_err(r)); |
c->self, ssh_err(r)); |
} |
} |
/* version, method */ |
/* version, method */ |
if ((r = sshbuf_put_u8(c->output, 0x05)) != 0 || |
if ((r = sshbuf_put_u8(output, 0x05)) != 0 || |
(r = sshbuf_put_u8(c->output, SSH_SOCKS5_NOAUTH)) != 0) { |
(r = sshbuf_put_u8(output, SSH_SOCKS5_NOAUTH)) != 0) { |
fatal("%s: channel %d: append reply: %s", __func__, |
fatal("%s: channel %d: append reply: %s", __func__, |
c->self, ssh_err(r)); |
c->self, ssh_err(r)); |
} |
} |
FD_SET(c->sock, writeset); |
|
c->flags |= SSH_SOCKS5_AUTHDONE; |
c->flags |= SSH_SOCKS5_AUTHDONE; |
debug2("channel %d: socks5 auth done", c->self); |
debug2("channel %d: socks5 auth done", c->self); |
return 0; /* need more */ |
return 0; /* need more */ |
|
|
need++; |
need++; |
if (have < need) |
if (have < need) |
return 0; |
return 0; |
if ((r = sshbuf_consume(c->input, sizeof(s5_req))) != 0) { |
if ((r = sshbuf_consume(input, sizeof(s5_req))) != 0) { |
fatal("%s: channel %d: consume: %s", __func__, |
fatal("%s: channel %d: consume: %s", __func__, |
c->self, ssh_err(r)); |
c->self, ssh_err(r)); |
} |
} |
if (s5_req.atyp == SSH_SOCKS5_DOMAIN) { |
if (s5_req.atyp == SSH_SOCKS5_DOMAIN) { |
/* host string length */ |
/* host string length */ |
if ((r = sshbuf_consume(c->input, 1)) != 0) { |
if ((r = sshbuf_consume(input, 1)) != 0) { |
fatal("%s: channel %d: consume: %s", __func__, |
fatal("%s: channel %d: consume: %s", __func__, |
c->self, ssh_err(r)); |
c->self, ssh_err(r)); |
} |
} |
} |
} |
if ((r = sshbuf_get(c->input, &dest_addr, addrlen)) != 0 || |
if ((r = sshbuf_get(input, &dest_addr, addrlen)) != 0 || |
(r = sshbuf_get(c->input, &dest_port, 2)) != 0) { |
(r = sshbuf_get(input, &dest_port, 2)) != 0) { |
debug("channel %d: parse addr/port: %s", c->self, ssh_err(r)); |
debug("channel %d: parse addr/port: %s", c->self, ssh_err(r)); |
return -1; |
return -1; |
} |
} |
|
|
s5_rsp.atyp = SSH_SOCKS5_IPV4; |
s5_rsp.atyp = SSH_SOCKS5_IPV4; |
dest_port = 0; /* ignored */ |
dest_port = 0; /* ignored */ |
|
|
if ((r = sshbuf_put(c->output, &s5_rsp, sizeof(s5_rsp))) != 0 || |
if ((r = sshbuf_put(output, &s5_rsp, sizeof(s5_rsp))) != 0 || |
(r = sshbuf_put_u32(c->output, ntohl(INADDR_ANY))) != 0 || |
(r = sshbuf_put_u32(output, ntohl(INADDR_ANY))) != 0 || |
(r = sshbuf_put(c->output, &dest_port, sizeof(dest_port))) != 0) |
(r = sshbuf_put(output, &dest_port, sizeof(dest_port))) != 0) |
fatal("%s: channel %d: append reply: %s", __func__, |
fatal("%s: channel %d: append reply: %s", __func__, |
c->self, ssh_err(r)); |
c->self, ssh_err(r)); |
return 1; |
return 1; |
|
|
/* XXX sshbuf_peek_u8? */ |
/* XXX sshbuf_peek_u8? */ |
switch (p[0]) { |
switch (p[0]) { |
case 0x04: |
case 0x04: |
ret = channel_decode_socks4(ssh, c, readset, writeset); |
ret = channel_decode_socks4(c, c->input, c->output); |
break; |
break; |
case 0x05: |
case 0x05: |
ret = channel_decode_socks5(ssh, c, readset, writeset); |
ret = channel_decode_socks5(c, c->input, c->output); |
break; |
break; |
default: |
default: |
ret = -1; |
ret = -1; |
|
|
debug2("channel %d: pre_dynamic: need more", c->self); |
debug2("channel %d: pre_dynamic: need more", c->self); |
/* need more */ |
/* need more */ |
FD_SET(c->sock, readset); |
FD_SET(c->sock, readset); |
|
if (sshbuf_len(c->output)) |
|
FD_SET(c->sock, writeset); |
} else { |
} else { |
/* switch to the next state */ |
/* switch to the next state */ |
c->type = SSH_CHANNEL_OPENING; |
c->type = SSH_CHANNEL_OPENING; |
|
|
} |
} |
} |
} |
|
|
|
/* simulate read-error */ |
|
static void |
|
rdynamic_close(struct ssh *ssh, Channel *c) |
|
{ |
|
c->type = SSH_CHANNEL_OPEN; |
|
chan_read_failed(ssh, c); |
|
sshbuf_reset(c->input); |
|
chan_ibuf_empty(ssh, c); |
|
sshbuf_reset(c->output); |
|
chan_write_failed(ssh, c); |
|
} |
|
|
|
/* reverse dynamic port forwarding */ |
|
static void |
|
channel_before_prepare_select_rdynamic(struct ssh *ssh, Channel *c) |
|
{ |
|
const u_char *p; |
|
u_int have, len; |
|
int r, ret; |
|
|
|
have = sshbuf_len(c->output); |
|
debug2("channel %d: pre_rdynamic: have %d", c->self, have); |
|
/* sshbuf_dump(c->output, stderr); */ |
|
/* EOF received */ |
|
if (c->flags & CHAN_EOF_RCVD) { |
|
if ((r = sshbuf_consume(c->output, have)) != 0) { |
|
fatal("%s: channel %d: consume: %s", |
|
__func__, c->self, ssh_err(r)); |
|
} |
|
rdynamic_close(ssh, c); |
|
return; |
|
} |
|
/* check if the fixed size part of the packet is in buffer. */ |
|
if (have < 3) |
|
return; |
|
/* try to guess the protocol */ |
|
p = sshbuf_ptr(c->output); |
|
switch (p[0]) { |
|
case 0x04: |
|
/* switch input/output for reverse forwarding */ |
|
ret = channel_decode_socks4(c, c->output, c->input); |
|
break; |
|
case 0x05: |
|
ret = channel_decode_socks5(c, c->output, c->input); |
|
break; |
|
default: |
|
ret = -1; |
|
break; |
|
} |
|
if (ret < 0) { |
|
rdynamic_close(ssh, c); |
|
} else if (ret == 0) { |
|
debug2("channel %d: pre_rdynamic: need more", c->self); |
|
/* send socks request to peer */ |
|
len = sshbuf_len(c->input); |
|
if (len > 0 && len < c->remote_window) { |
|
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_DATA)) != 0 || |
|
(r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || |
|
(r = sshpkt_put_stringb(ssh, c->input)) != 0 || |
|
(r = sshpkt_send(ssh)) != 0) { |
|
fatal("%s: channel %i: rdynamic: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
|
if ((r = sshbuf_consume(c->input, len)) != 0) { |
|
fatal("%s: channel %d: consume: %s", |
|
__func__, c->self, ssh_err(r)); |
|
} |
|
c->remote_window -= len; |
|
} |
|
} else if (rdynamic_connect_finish(ssh, c) < 0) { |
|
/* the connect failed */ |
|
rdynamic_close(ssh, c); |
|
} |
|
} |
|
|
/* This is our fake X11 server socket. */ |
/* This is our fake X11 server socket. */ |
static void |
static void |
channel_post_x11_listener(struct ssh *ssh, Channel *c, |
channel_post_x11_listener(struct ssh *ssh, Channel *c, |
|
|
channel_post_connecting(struct ssh *ssh, Channel *c, |
channel_post_connecting(struct ssh *ssh, Channel *c, |
fd_set *readset, fd_set *writeset) |
fd_set *readset, fd_set *writeset) |
{ |
{ |
int err = 0, sock, r; |
int err = 0, sock, isopen, r; |
socklen_t sz = sizeof(err); |
socklen_t sz = sizeof(err); |
|
|
if (!FD_ISSET(c->sock, writeset)) |
if (!FD_ISSET(c->sock, writeset)) |
return; |
return; |
if (!c->have_remote_id) |
if (!c->have_remote_id) |
fatal(":%s: channel %d: no remote id", __func__, c->self); |
fatal(":%s: channel %d: no remote id", __func__, c->self); |
|
/* for rdynamic the OPEN_CONFIRMATION has been sent already */ |
|
isopen = (c->type == SSH_CHANNEL_RDYNAMIC_FINISH); |
if (getsockopt(c->sock, SOL_SOCKET, SO_ERROR, &err, &sz) < 0) { |
if (getsockopt(c->sock, SOL_SOCKET, SO_ERROR, &err, &sz) < 0) { |
err = errno; |
err = errno; |
error("getsockopt SO_ERROR failed"); |
error("getsockopt SO_ERROR failed"); |
|
|
c->self, c->connect_ctx.host, c->connect_ctx.port); |
c->self, c->connect_ctx.host, c->connect_ctx.port); |
channel_connect_ctx_free(&c->connect_ctx); |
channel_connect_ctx_free(&c->connect_ctx); |
c->type = SSH_CHANNEL_OPEN; |
c->type = SSH_CHANNEL_OPEN; |
if ((r = sshpkt_start(ssh, |
if (isopen) { |
SSH2_MSG_CHANNEL_OPEN_CONFIRMATION)) != 0 || |
/* no message necessary */ |
(r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || |
} else { |
(r = sshpkt_put_u32(ssh, c->self)) != 0 || |
if ((r = sshpkt_start(ssh, |
(r = sshpkt_put_u32(ssh, c->local_window)) != 0 || |
SSH2_MSG_CHANNEL_OPEN_CONFIRMATION)) != 0 || |
(r = sshpkt_put_u32(ssh, c->local_maxpacket)) != 0) { |
(r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || |
fatal("%s: channel %i: confirm: %s", __func__, |
(r = sshpkt_put_u32(ssh, c->self)) != 0 || |
c->self, ssh_err(r)); |
(r = sshpkt_put_u32(ssh, c->local_window)) != 0 || |
|
(r = sshpkt_put_u32(ssh, c->local_maxpacket)) |
|
!= 0) |
|
fatal("%s: channel %i: confirm: %s", __func__, |
|
c->self, ssh_err(r)); |
|
if ((r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: channel %i: %s", __func__, c->self, |
|
ssh_err(r)); |
} |
} |
} else { |
} else { |
debug("channel %d: connection failed: %s", |
debug("channel %d: connection failed: %s", |
|
|
error("connect_to %.100s port %d: failed.", |
error("connect_to %.100s port %d: failed.", |
c->connect_ctx.host, c->connect_ctx.port); |
c->connect_ctx.host, c->connect_ctx.port); |
channel_connect_ctx_free(&c->connect_ctx); |
channel_connect_ctx_free(&c->connect_ctx); |
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_OPEN_FAILURE)) != 0 || |
if (isopen) { |
(r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || |
rdynamic_close(ssh, c); |
(r = sshpkt_put_u32(ssh, SSH2_OPEN_CONNECT_FAILED)) != 0) { |
} else { |
fatal("%s: channel %i: failure: %s", __func__, |
if ((r = sshpkt_start(ssh, |
c->self, ssh_err(r)); |
SSH2_MSG_CHANNEL_OPEN_FAILURE)) != 0 || |
|
(r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || |
|
(r = sshpkt_put_u32(ssh, SSH2_OPEN_CONNECT_FAILED)) |
|
!= 0) |
|
fatal("%s: channel %i: failure: %s", __func__, |
|
c->self, ssh_err(r)); |
|
if ((datafellows & SSH_BUG_OPENFAILURE) == 0 && |
|
((r = sshpkt_put_cstring(ssh, strerror(err))) != 0 || |
|
(r = sshpkt_put_cstring(ssh, "")) != 0)) |
|
fatal("%s: channel %i: failure: %s", __func__, |
|
c->self, ssh_err(r)); |
|
if ((r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: channel %i: %s", __func__, c->self, |
|
ssh_err(r)); |
|
chan_mark_dead(ssh, c); |
} |
} |
if ((datafellows & SSH_BUG_OPENFAILURE) == 0 && |
|
((r = sshpkt_put_cstring(ssh, strerror(err))) != 0 || |
|
(r = sshpkt_put_cstring(ssh, "")) != 0)) { |
|
fatal("%s: channel %i: failure: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
|
chan_mark_dead(ssh, c); |
|
} |
} |
if ((r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: channel %i: %s", __func__, c->self, ssh_err(r)); |
|
} |
} |
|
|
static int |
static int |
|
|
pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; |
pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; |
pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; |
pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; |
pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; |
pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; |
|
pre[SSH_CHANNEL_RDYNAMIC_FINISH] = &channel_pre_connecting; |
pre[SSH_CHANNEL_MUX_LISTENER] = &channel_pre_listener; |
pre[SSH_CHANNEL_MUX_LISTENER] = &channel_pre_listener; |
pre[SSH_CHANNEL_MUX_CLIENT] = &channel_pre_mux_client; |
pre[SSH_CHANNEL_MUX_CLIENT] = &channel_pre_mux_client; |
|
|
|
|
post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; |
post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; |
post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; |
post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; |
post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; |
post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; |
|
post[SSH_CHANNEL_RDYNAMIC_FINISH] = &channel_post_connecting; |
post[SSH_CHANNEL_MUX_LISTENER] = &channel_post_mux_listener; |
post[SSH_CHANNEL_MUX_LISTENER] = &channel_post_mux_listener; |
post[SSH_CHANNEL_MUX_CLIENT] = &channel_post_mux_client; |
post[SSH_CHANNEL_MUX_CLIENT] = &channel_post_mux_client; |
|
|
|
|
} |
} |
|
|
/* |
/* |
|
* Create sockets before allocating the select bitmasks. |
|
* This is necessary for things that need to happen after reading |
|
* the network-input but before channel_prepare_select(). |
|
*/ |
|
static void |
|
channel_before_prepare_select(struct ssh *ssh) |
|
{ |
|
struct ssh_channels *sc = ssh->chanctxt; |
|
Channel *c; |
|
u_int i, oalloc; |
|
|
|
for (i = 0, oalloc = sc->channels_alloc; i < oalloc; i++) { |
|
c = sc->channels[i]; |
|
if (c == NULL) |
|
continue; |
|
if (c->type == SSH_CHANNEL_RDYNAMIC_OPEN) |
|
channel_before_prepare_select_rdynamic(ssh, c); |
|
} |
|
} |
|
|
|
/* |
* Allocate/update select bitmasks and add any bits relevant to channels in |
* Allocate/update select bitmasks and add any bits relevant to channels in |
* select bitmasks. |
* select bitmasks. |
*/ |
*/ |
|
|
{ |
{ |
u_int n, sz, nfdset; |
u_int n, sz, nfdset; |
|
|
|
channel_before_prepare_select(ssh); /* might update channel_max_fd */ |
|
|
n = MAXIMUM(*maxfdp, ssh->chanctxt->channel_max_fd); |
n = MAXIMUM(*maxfdp, ssh->chanctxt->channel_max_fd); |
|
|
nfdset = howmany(n+1, NFDBITS); |
nfdset = howmany(n+1, NFDBITS); |
|
|
|
|
/* Ignore any data for non-open channels (might happen on close) */ |
/* Ignore any data for non-open channels (might happen on close) */ |
if (c->type != SSH_CHANNEL_OPEN && |
if (c->type != SSH_CHANNEL_OPEN && |
|
c->type != SSH_CHANNEL_RDYNAMIC_OPEN && |
|
c->type != SSH_CHANNEL_RDYNAMIC_FINISH && |
c->type != SSH_CHANNEL_X11_OPEN) |
c->type != SSH_CHANNEL_X11_OPEN) |
return 0; |
return 0; |
|
|
|
|
if ((c = channel_lookup(ssh, id)) == NULL) { |
if ((c = channel_lookup(ssh, id)) == NULL) { |
logit("Received window adjust for non-open channel %d.", id); |
logit("Received window adjust for non-open channel %d.", id); |
return 0; |
return 0; |
} |
} |
|
|
if (channel_proxy_upstream(c, type, seq, ssh)) |
if (channel_proxy_upstream(c, type, seq, ssh)) |
return 0; |
return 0; |
|
|
} |
} |
|
|
/* |
/* |
* Return CONNECTING channel to remote host:port or local socket path, |
* Return connecting socket to remote host:port or local socket path, |
* passing back the failure reason if appropriate. |
* passing back the failure reason if appropriate. |
*/ |
*/ |
static Channel * |
static int |
connect_to_reason(struct ssh *ssh, const char *name, int port, |
connect_to_helper(struct ssh *ssh, const char *name, int port, int socktype, |
char *ctype, char *rname, int *reason, const char **errmsg) |
char *ctype, char *rname, struct channel_connect *cctx, |
|
int *reason, const char **errmsg) |
{ |
{ |
struct addrinfo hints; |
struct addrinfo hints; |
int gaierr; |
int gaierr; |
int sock = -1; |
int sock = -1; |
char strport[NI_MAXSERV]; |
char strport[NI_MAXSERV]; |
struct channel_connect cctx; |
|
Channel *c; |
|
|
|
memset(&cctx, 0, sizeof(cctx)); |
|
|
|
if (port == PORT_STREAMLOCAL) { |
if (port == PORT_STREAMLOCAL) { |
struct sockaddr_un *sunaddr; |
struct sockaddr_un *sunaddr; |
struct addrinfo *ai; |
struct addrinfo *ai; |
|
|
if (strlen(name) > sizeof(sunaddr->sun_path)) { |
if (strlen(name) > sizeof(sunaddr->sun_path)) { |
error("%.100s: %.100s", name, strerror(ENAMETOOLONG)); |
error("%.100s: %.100s", name, strerror(ENAMETOOLONG)); |
return (NULL); |
return -1; |
} |
} |
|
|
/* |
/* |
|
|
ai->ai_addr = (struct sockaddr *)(ai + 1); |
ai->ai_addr = (struct sockaddr *)(ai + 1); |
ai->ai_addrlen = sizeof(*sunaddr); |
ai->ai_addrlen = sizeof(*sunaddr); |
ai->ai_family = AF_UNIX; |
ai->ai_family = AF_UNIX; |
ai->ai_socktype = SOCK_STREAM; |
ai->ai_socktype = socktype; |
ai->ai_protocol = PF_UNSPEC; |
ai->ai_protocol = PF_UNSPEC; |
sunaddr = (struct sockaddr_un *)ai->ai_addr; |
sunaddr = (struct sockaddr_un *)ai->ai_addr; |
sunaddr->sun_family = AF_UNIX; |
sunaddr->sun_family = AF_UNIX; |
strlcpy(sunaddr->sun_path, name, sizeof(sunaddr->sun_path)); |
strlcpy(sunaddr->sun_path, name, sizeof(sunaddr->sun_path)); |
cctx.aitop = ai; |
cctx->aitop = ai; |
} else { |
} else { |
memset(&hints, 0, sizeof(hints)); |
memset(&hints, 0, sizeof(hints)); |
hints.ai_family = ssh->chanctxt->IPv4or6; |
hints.ai_family = ssh->chanctxt->IPv4or6; |
hints.ai_socktype = SOCK_STREAM; |
hints.ai_socktype = socktype; |
snprintf(strport, sizeof strport, "%d", port); |
snprintf(strport, sizeof strport, "%d", port); |
if ((gaierr = getaddrinfo(name, strport, &hints, &cctx.aitop)) |
if ((gaierr = getaddrinfo(name, strport, &hints, &cctx->aitop)) |
!= 0) { |
!= 0) { |
if (errmsg != NULL) |
if (errmsg != NULL) |
*errmsg = ssh_gai_strerror(gaierr); |
*errmsg = ssh_gai_strerror(gaierr); |
|
|
*reason = SSH2_OPEN_CONNECT_FAILED; |
*reason = SSH2_OPEN_CONNECT_FAILED; |
error("connect_to %.100s: unknown host (%s)", name, |
error("connect_to %.100s: unknown host (%s)", name, |
ssh_gai_strerror(gaierr)); |
ssh_gai_strerror(gaierr)); |
return NULL; |
return -1; |
} |
} |
} |
} |
|
|
cctx.host = xstrdup(name); |
cctx->host = xstrdup(name); |
cctx.port = port; |
cctx->port = port; |
cctx.ai = cctx.aitop; |
cctx->ai = cctx->aitop; |
|
|
if ((sock = connect_next(&cctx)) == -1) { |
if ((sock = connect_next(cctx)) == -1) { |
error("connect to %.100s port %d failed: %s", |
error("connect to %.100s port %d failed: %s", |
name, port, strerror(errno)); |
name, port, strerror(errno)); |
|
return -1; |
|
} |
|
|
|
return sock; |
|
} |
|
|
|
/* Return CONNECTING channel to remote host:port or local socket path */ |
|
static Channel * |
|
connect_to(struct ssh *ssh, const char *host, int port, |
|
char *ctype, char *rname) |
|
{ |
|
struct channel_connect cctx; |
|
Channel *c; |
|
int sock; |
|
|
|
memset(&cctx, 0, sizeof(cctx)); |
|
sock = connect_to_helper(ssh, host, port, SOCK_STREAM, ctype, rname, |
|
&cctx, NULL, NULL); |
|
if (sock == -1) { |
channel_connect_ctx_free(&cctx); |
channel_connect_ctx_free(&cctx); |
return NULL; |
return NULL; |
} |
} |
c = channel_new(ssh, ctype, SSH_CHANNEL_CONNECTING, sock, sock, -1, |
c = channel_new(ssh, ctype, SSH_CHANNEL_CONNECTING, sock, sock, -1, |
CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1); |
CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1); |
|
c->host_port = port; |
|
c->path = xstrdup(host); |
c->connect_ctx = cctx; |
c->connect_ctx = cctx; |
|
|
return c; |
return c; |
} |
} |
|
|
/* Return CONNECTING channel to remote host:port or local socket path */ |
|
static Channel * |
|
connect_to(struct ssh *ssh, const char *name, int port, |
|
char *ctype, char *rname) |
|
{ |
|
return connect_to_reason(ssh, name, port, ctype, rname, NULL, NULL); |
|
} |
|
|
|
/* |
/* |
* returns either the newly connected channel or the downstream channel |
* returns either the newly connected channel or the downstream channel |
* that needs to deal with this connection. |
* that needs to deal with this connection. |
|
|
if (open_listen_match_tcpip(fp, listen_host, listen_port, 1)) { |
if (open_listen_match_tcpip(fp, listen_host, listen_port, 1)) { |
if (fp->downstream) |
if (fp->downstream) |
return fp->downstream; |
return fp->downstream; |
|
if (fp->port_to_connect == 0) |
|
return rdynamic_connect_prepare(ssh, |
|
ctype, rname); |
return connect_to(ssh, |
return connect_to(ssh, |
fp->host_to_connect, fp->port_to_connect, |
fp->host_to_connect, fp->port_to_connect, |
ctype, rname); |
ctype, rname); |
|
|
char *ctype, char *rname, int *reason, const char **errmsg) |
char *ctype, char *rname, int *reason, const char **errmsg) |
{ |
{ |
struct ssh_channels *sc = ssh->chanctxt; |
struct ssh_channels *sc = ssh->chanctxt; |
|
struct channel_connect cctx; |
|
Channel *c; |
u_int i, permit, permit_adm = 1; |
u_int i, permit, permit_adm = 1; |
|
int sock; |
ForwardPermission *fp; |
ForwardPermission *fp; |
|
|
permit = sc->all_opens_permitted; |
permit = sc->all_opens_permitted; |
|
|
*reason = SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED; |
*reason = SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED; |
return NULL; |
return NULL; |
} |
} |
return connect_to_reason(ssh, host, port, ctype, rname, reason, errmsg); |
|
|
memset(&cctx, 0, sizeof(cctx)); |
|
sock = connect_to_helper(ssh, host, port, SOCK_STREAM, ctype, rname, |
|
&cctx, reason, errmsg); |
|
if (sock == -1) { |
|
channel_connect_ctx_free(&cctx); |
|
return NULL; |
|
} |
|
|
|
c = channel_new(ssh, ctype, SSH_CHANNEL_CONNECTING, sock, sock, -1, |
|
CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1); |
|
c->host_port = port; |
|
c->path = xstrdup(host); |
|
c->connect_ctx = cctx; |
|
|
|
return c; |
} |
} |
|
|
/* Check if connecting to that path is permitted and connect. */ |
/* Check if connecting to that path is permitted and connect. */ |
|
|
fatal("%s: channel %u: send window-change: %s", |
fatal("%s: channel %u: send window-change: %s", |
__func__, i, ssh_err(r)); |
__func__, i, ssh_err(r)); |
} |
} |
|
} |
|
|
|
/* Return RDYNAMIC_OPEN channel: channel allows SOCKS, but is not connected */ |
|
static Channel * |
|
rdynamic_connect_prepare(struct ssh *ssh, char *ctype, char *rname) |
|
{ |
|
Channel *c; |
|
int r; |
|
|
|
c = channel_new(ssh, ctype, SSH_CHANNEL_RDYNAMIC_OPEN, -1, -1, -1, |
|
CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1); |
|
c->host_port = 0; |
|
c->path = NULL; |
|
|
|
/* |
|
* We need to open the channel before we have a FD, |
|
* so that we can get SOCKS header from peer. |
|
*/ |
|
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION)) != 0 || |
|
(r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || |
|
(r = sshpkt_put_u32(ssh, c->self)) != 0 || |
|
(r = sshpkt_put_u32(ssh, c->local_window)) != 0 || |
|
(r = sshpkt_put_u32(ssh, c->local_maxpacket)) != 0) { |
|
fatal("%s: channel %i: confirm: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
|
return c; |
|
} |
|
|
|
/* Return CONNECTING socket to remote host:port or local socket path */ |
|
static int |
|
rdynamic_connect_finish(struct ssh *ssh, Channel *c) |
|
{ |
|
struct channel_connect cctx; |
|
int sock; |
|
|
|
memset(&cctx, 0, sizeof(cctx)); |
|
sock = connect_to_helper(ssh, c->path, c->host_port, SOCK_STREAM, NULL, |
|
NULL, &cctx, NULL, NULL); |
|
if (sock == -1) |
|
channel_connect_ctx_free(&cctx); |
|
else { |
|
/* similar to SSH_CHANNEL_CONNECTING but we've already sent the open */ |
|
c->type = SSH_CHANNEL_RDYNAMIC_FINISH; |
|
c->connect_ctx = cctx; |
|
channel_register_fds(ssh, c, sock, sock, -1, 0, 1, 0); |
|
} |
|
return sock; |
} |
} |
|
|
/* -- X11 forwarding */ |
/* -- X11 forwarding */ |