version 1.366, 2017/08/30 03:59:08 |
version 1.367, 2017/09/12 06:32:07 |
|
|
|
|
#include <errno.h> |
#include <errno.h> |
#include <fcntl.h> |
#include <fcntl.h> |
|
#include <limits.h> |
#include <netdb.h> |
#include <netdb.h> |
|
#include <stdarg.h> |
#include <stdint.h> |
#include <stdint.h> |
#include <stdio.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <stdlib.h> |
#include <string.h> |
#include <string.h> |
#include <termios.h> |
#include <termios.h> |
#include <unistd.h> |
#include <unistd.h> |
#include <stdarg.h> |
|
|
|
#include "xmalloc.h" |
#include "xmalloc.h" |
#include "ssh.h" |
#include "ssh.h" |
#include "ssh2.h" |
#include "ssh2.h" |
#include "ssherr.h" |
#include "ssherr.h" |
|
#include "sshbuf.h" |
#include "packet.h" |
#include "packet.h" |
#include "log.h" |
#include "log.h" |
#include "misc.h" |
#include "misc.h" |
#include "buffer.h" |
|
#include "channels.h" |
#include "channels.h" |
#include "compat.h" |
#include "compat.h" |
#include "canohost.h" |
#include "canohost.h" |
|
|
#include "authfd.h" |
#include "authfd.h" |
#include "pathnames.h" |
#include "pathnames.h" |
|
|
/* -- channel core */ |
/* -- agent forwarding */ |
|
#define NUM_SOCKS 10 |
|
|
/* |
/* -- tcp forwarding */ |
* Pointer to an array containing all allocated channels. The array is |
/* special-case port number meaning allow any port */ |
* dynamically extended as needed. |
#define FWD_PERMIT_ANY_PORT 0 |
*/ |
|
static Channel **channels = NULL; |
|
|
|
/* |
/* special-case wildcard meaning allow any host */ |
* Size of the channel array. All slots of the array must always be |
#define FWD_PERMIT_ANY_HOST "*" |
* initialized (at least the type field); unused slots set to NULL |
|
*/ |
|
static u_int channels_alloc = 0; |
|
|
|
/* |
/* -- X11 forwarding */ |
* Maximum file descriptor value used in any of the channels. This is |
/* Maximum number of fake X11 displays to try. */ |
* updated in channel_new. |
#define MAX_DISPLAYS 1000 |
*/ |
|
static int channel_max_fd = 0; |
|
|
|
|
|
/* -- tcp forwarding */ |
|
|
|
/* |
/* |
* Data structure for storing which hosts are permitted for forward requests. |
* Data structure for storing which hosts are permitted for forward requests. |
* The local sides of any remote forwards are stored in this array to prevent |
* The local sides of any remote forwards are stored in this array to prevent |
|
|
Channel *downstream; /* Downstream mux*/ |
Channel *downstream; /* Downstream mux*/ |
} ForwardPermission; |
} ForwardPermission; |
|
|
/* List of all permitted host/port pairs to connect by the user. */ |
typedef void chan_fn(struct ssh *, Channel *c, |
static ForwardPermission *permitted_opens = NULL; |
fd_set *readset, fd_set *writeset); |
|
|
/* List of all permitted host/port pairs to connect by the admin. */ |
/* Master structure for channels state */ |
static ForwardPermission *permitted_adm_opens = NULL; |
struct ssh_channels { |
|
/* |
|
* Pointer to an array containing all allocated channels. The array |
|
* is dynamically extended as needed. |
|
*/ |
|
Channel **channels; |
|
|
/* Number of permitted host/port pairs in the array permitted by the user. */ |
/* |
static int num_permitted_opens = 0; |
* Size of the channel array. All slots of the array must always be |
|
* initialized (at least the type field); unused slots set to NULL |
|
*/ |
|
u_int channels_alloc; |
|
|
/* Number of permitted host/port pair in the array permitted by the admin. */ |
/* |
static int num_adm_permitted_opens = 0; |
* Maximum file descriptor value used in any of the channels. This is |
|
* updated in channel_new. |
|
*/ |
|
int channel_max_fd; |
|
|
/* special-case port number meaning allow any port */ |
/* |
#define FWD_PERMIT_ANY_PORT 0 |
* 'channel_pre*' are called just before select() to add any bits |
|
* relevant to channels in the select bitmasks. |
|
* |
|
* 'channel_post*': perform any appropriate operations for |
|
* channels which have events pending. |
|
*/ |
|
chan_fn **channel_pre; |
|
chan_fn **channel_post; |
|
|
/* special-case wildcard meaning allow any host */ |
/* -- tcp forwarding */ |
#define FWD_PERMIT_ANY_HOST "*" |
|
|
|
/* |
/* List of all permitted host/port pairs to connect by the user. */ |
* If this is true, all opens are permitted. This is the case on the server |
ForwardPermission *permitted_opens; |
* on which we have to trust the client anyway, and the user could do |
|
* anything after logging in anyway. |
|
*/ |
|
static int all_opens_permitted = 0; |
|
|
|
|
/* List of all permitted host/port pairs to connect by the admin. */ |
|
ForwardPermission *permitted_adm_opens; |
|
|
/* -- X11 forwarding */ |
/* |
|
* Number of permitted host/port pairs in the array permitted by |
|
* the user. |
|
*/ |
|
u_int num_permitted_opens; |
|
|
/* Maximum number of fake X11 displays to try. */ |
/* |
#define MAX_DISPLAYS 1000 |
* Number of permitted host/port pair in the array permitted by |
|
* the admin. |
|
*/ |
|
u_int num_adm_permitted_opens; |
|
|
/* Saved X11 local (client) display. */ |
/* |
static char *x11_saved_display = NULL; |
* If this is true, all opens are permitted. This is the case on |
|
* the server on which we have to trust the client anyway, and the |
|
* user could do anything after logging in anyway. |
|
*/ |
|
int all_opens_permitted; |
|
|
/* Saved X11 authentication protocol name. */ |
/* -- X11 forwarding */ |
static char *x11_saved_proto = NULL; |
|
|
|
/* Saved X11 authentication data. This is the real data. */ |
/* Saved X11 local (client) display. */ |
static char *x11_saved_data = NULL; |
char *x11_saved_display; |
static u_int x11_saved_data_len = 0; |
|
|
|
/* Deadline after which all X11 connections are refused */ |
/* Saved X11 authentication protocol name. */ |
static u_int x11_refuse_time; |
char *x11_saved_proto; |
|
|
/* |
/* Saved X11 authentication data. This is the real data. */ |
* Fake X11 authentication data. This is what the server will be sending us; |
char *x11_saved_data; |
* we should replace any occurrences of this by the real data. |
u_int x11_saved_data_len; |
*/ |
|
static u_char *x11_fake_data = NULL; |
|
static u_int x11_fake_data_len; |
|
|
|
|
/* Deadline after which all X11 connections are refused */ |
|
u_int x11_refuse_time; |
|
|
/* -- agent forwarding */ |
/* |
|
* Fake X11 authentication data. This is what the server will be |
|
* sending us; we should replace any occurrences of this by the |
|
* real data. |
|
*/ |
|
u_char *x11_fake_data; |
|
u_int x11_fake_data_len; |
|
|
#define NUM_SOCKS 10 |
/* AF_UNSPEC or AF_INET or AF_INET6 */ |
|
int IPv4or6; |
|
}; |
|
|
/* AF_UNSPEC or AF_INET or AF_INET6 */ |
|
static int IPv4or6 = AF_UNSPEC; |
|
|
|
/* helper */ |
/* helper */ |
static void port_open_helper(Channel *c, char *rtype); |
static void port_open_helper(struct ssh *ssh, Channel *c, char *rtype); |
static const char *channel_rfwd_bind_host(const char *listen_host); |
static const char *channel_rfwd_bind_host(const char *listen_host); |
|
|
/* 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 *); |
|
|
|
/* Setup helper */ |
|
static void channel_handler_init(struct ssh_channels *sc); |
|
|
/* -- channel core */ |
/* -- channel core */ |
|
|
|
void |
|
channel_init_channels(struct ssh *ssh) |
|
{ |
|
struct ssh_channels *sc; |
|
|
|
if ((sc = calloc(1, sizeof(*sc))) == NULL || |
|
(sc->channel_pre = calloc(SSH_CHANNEL_MAX_TYPE, |
|
sizeof(*sc->channel_pre))) == NULL || |
|
(sc->channel_post = calloc(SSH_CHANNEL_MAX_TYPE, |
|
sizeof(*sc->channel_post))) == NULL) |
|
fatal("%s: allocation failed", __func__); |
|
sc->channels_alloc = 10; |
|
sc->channels = xcalloc(sc->channels_alloc, sizeof(*sc->channels)); |
|
sc->IPv4or6 = AF_UNSPEC; |
|
channel_handler_init(sc); |
|
|
|
ssh->chanctxt = sc; |
|
} |
|
|
Channel * |
Channel * |
channel_by_id(int id) |
channel_by_id(struct ssh *ssh, int id) |
{ |
{ |
Channel *c; |
Channel *c; |
|
|
if (id < 0 || (u_int)id >= channels_alloc) { |
if (id < 0 || (u_int)id >= ssh->chanctxt->channels_alloc) { |
logit("channel_by_id: %d: bad id", id); |
logit("%s: %d: bad id", __func__, id); |
return NULL; |
return NULL; |
} |
} |
c = channels[id]; |
c = ssh->chanctxt->channels[id]; |
if (c == NULL) { |
if (c == NULL) { |
logit("channel_by_id: %d: bad id: channel free", id); |
logit("%s: %d: bad id: channel free", __func__, id); |
return NULL; |
return NULL; |
} |
} |
return c; |
return c; |
} |
} |
|
|
Channel * |
Channel * |
channel_by_remote_id(int remote_id) |
channel_by_remote_id(struct ssh *ssh, int remote_id) |
{ |
{ |
Channel *c; |
Channel *c; |
u_int i; |
u_int i; |
|
|
for (i = 0; i < channels_alloc; i++) { |
for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { |
c = channels[i]; |
c = ssh->chanctxt->channels[i]; |
if (c != NULL && c->remote_id == remote_id) |
if (c != NULL && c->remote_id == remote_id) |
return c; |
return c; |
} |
} |
|
|
* Private channels, like listening sockets, may not receive messages. |
* Private channels, like listening sockets, may not receive messages. |
*/ |
*/ |
Channel * |
Channel * |
channel_lookup(int id) |
channel_lookup(struct ssh *ssh, int id) |
{ |
{ |
Channel *c; |
Channel *c; |
|
|
if ((c = channel_by_id(id)) == NULL) |
if ((c = channel_by_id(ssh, id)) == NULL) |
return (NULL); |
return NULL; |
|
|
switch (c->type) { |
switch (c->type) { |
case SSH_CHANNEL_X11_OPEN: |
case SSH_CHANNEL_X11_OPEN: |
|
|
case SSH_CHANNEL_OPEN: |
case SSH_CHANNEL_OPEN: |
case SSH_CHANNEL_ABANDONED: |
case SSH_CHANNEL_ABANDONED: |
case SSH_CHANNEL_MUX_PROXY: |
case SSH_CHANNEL_MUX_PROXY: |
return (c); |
return c; |
} |
} |
logit("Non-public channel %d, type %d.", id, c->type); |
logit("Non-public channel %d, type %d.", id, c->type); |
return (NULL); |
return NULL; |
} |
} |
|
|
/* |
/* |
|
|
* when the channel consumer/producer is ready, e.g. shell exec'd |
* when the channel consumer/producer is ready, e.g. shell exec'd |
*/ |
*/ |
static void |
static void |
channel_register_fds(Channel *c, int rfd, int wfd, int efd, |
channel_register_fds(struct ssh *ssh, Channel *c, int rfd, int wfd, int efd, |
int extusage, int nonblock, int is_tty) |
int extusage, int nonblock, int is_tty) |
{ |
{ |
|
struct ssh_channels *sc = ssh->chanctxt; |
|
|
/* Update the maximum file descriptor value. */ |
/* Update the maximum file descriptor value. */ |
channel_max_fd = MAXIMUM(channel_max_fd, rfd); |
sc->channel_max_fd = MAXIMUM(sc->channel_max_fd, rfd); |
channel_max_fd = MAXIMUM(channel_max_fd, wfd); |
sc->channel_max_fd = MAXIMUM(sc->channel_max_fd, wfd); |
channel_max_fd = MAXIMUM(channel_max_fd, efd); |
sc->channel_max_fd = MAXIMUM(sc->channel_max_fd, efd); |
|
|
if (rfd != -1) |
if (rfd != -1) |
fcntl(rfd, F_SETFD, FD_CLOEXEC); |
fcntl(rfd, F_SETFD, FD_CLOEXEC); |
|
|
* remote_name to be freed. |
* remote_name to be freed. |
*/ |
*/ |
Channel * |
Channel * |
channel_new(char *ctype, int type, int rfd, int wfd, int efd, |
channel_new(struct ssh *ssh, char *ctype, int type, int rfd, int wfd, int efd, |
u_int window, u_int maxpack, int extusage, char *remote_name, int nonblock) |
u_int window, u_int maxpack, int extusage, char *remote_name, int nonblock) |
{ |
{ |
int found; |
struct ssh_channels *sc = ssh->chanctxt; |
u_int i; |
u_int i, found; |
Channel *c; |
Channel *c; |
|
|
/* Do initial allocation if this is the first call. */ |
|
if (channels_alloc == 0) { |
|
channels_alloc = 10; |
|
channels = xcalloc(channels_alloc, sizeof(Channel *)); |
|
for (i = 0; i < channels_alloc; i++) |
|
channels[i] = NULL; |
|
} |
|
/* Try to find a free slot where to put the new channel. */ |
/* Try to find a free slot where to put the new channel. */ |
for (found = -1, i = 0; i < channels_alloc; i++) |
for (i = 0; i < sc->channels_alloc; i++) { |
if (channels[i] == NULL) { |
if (sc->channels[i] == NULL) { |
/* Found a free slot. */ |
/* Found a free slot. */ |
found = (int)i; |
found = i; |
break; |
break; |
} |
} |
if (found < 0) { |
|
/* There are no free slots. Take last+1 slot and expand the array. */ |
|
found = channels_alloc; |
|
if (channels_alloc > 10000) |
|
fatal("channel_new: internal error: channels_alloc %d " |
|
"too big.", channels_alloc); |
|
channels = xreallocarray(channels, channels_alloc + 10, |
|
sizeof(Channel *)); |
|
channels_alloc += 10; |
|
debug2("channel: expanding %d", channels_alloc); |
|
for (i = found; i < channels_alloc; i++) |
|
channels[i] = NULL; |
|
} |
} |
|
if (i >= sc->channels_alloc) { |
|
/* |
|
* There are no free slots. Take last+1 slot and expand |
|
* the array. |
|
*/ |
|
found = sc->channels_alloc; |
|
if (sc->channels_alloc > CHANNELS_MAX_CHANNELS) |
|
fatal("%s: internal error: channels_alloc %d too big", |
|
__func__, sc->channels_alloc); |
|
sc->channels = xrecallocarray(sc->channels, sc->channels_alloc, |
|
sc->channels_alloc + 10, sizeof(*sc->channels)); |
|
sc->channels_alloc += 10; |
|
debug2("channel: expanding %d", sc->channels_alloc); |
|
} |
/* Initialize and return new channel. */ |
/* Initialize and return new channel. */ |
c = channels[found] = xcalloc(1, sizeof(Channel)); |
c = sc->channels[found] = xcalloc(1, sizeof(Channel)); |
buffer_init(&c->input); |
if ((c->input = sshbuf_new()) == NULL || |
buffer_init(&c->output); |
(c->output = sshbuf_new()) == NULL || |
buffer_init(&c->extended); |
(c->extended = sshbuf_new()) == NULL) |
c->path = NULL; |
fatal("%s: sshbuf_new failed", __func__); |
c->listening_addr = NULL; |
|
c->listening_port = 0; |
|
c->ostate = CHAN_OUTPUT_OPEN; |
c->ostate = CHAN_OUTPUT_OPEN; |
c->istate = CHAN_INPUT_OPEN; |
c->istate = CHAN_INPUT_OPEN; |
c->flags = 0; |
channel_register_fds(ssh, c, rfd, wfd, efd, extusage, nonblock, 0); |
channel_register_fds(c, rfd, wfd, efd, extusage, nonblock, 0); |
|
c->notbefore = 0; |
|
c->self = found; |
c->self = found; |
c->type = type; |
c->type = type; |
c->ctype = ctype; |
c->ctype = ctype; |
c->local_window = window; |
c->local_window = window; |
c->local_window_max = window; |
c->local_window_max = window; |
c->local_consumed = 0; |
|
c->local_maxpacket = maxpack; |
c->local_maxpacket = maxpack; |
c->remote_id = -1; |
c->remote_id = -1; |
c->remote_name = xstrdup(remote_name); |
c->remote_name = xstrdup(remote_name); |
c->remote_window = 0; |
|
c->remote_maxpacket = 0; |
|
c->force_drain = 0; |
|
c->single_connection = 0; |
|
c->detach_user = NULL; |
|
c->detach_close = 0; |
|
c->open_confirm = NULL; |
|
c->open_confirm_ctx = NULL; |
|
c->input_filter = NULL; |
|
c->output_filter = NULL; |
|
c->filter_ctx = NULL; |
|
c->filter_cleanup = NULL; |
|
c->ctl_chan = -1; |
c->ctl_chan = -1; |
c->mux_rcb = NULL; |
|
c->mux_ctx = NULL; |
|
c->mux_pause = 0; |
|
c->delayed = 1; /* prevent call to channel_post handler */ |
c->delayed = 1; /* prevent call to channel_post handler */ |
TAILQ_INIT(&c->status_confirms); |
TAILQ_INIT(&c->status_confirms); |
debug("channel %d: new [%s]", found, remote_name); |
debug("channel %d: new [%s]", found, remote_name); |
return c; |
return c; |
} |
} |
|
|
static int |
static void |
channel_find_maxfd(void) |
channel_find_maxfd(struct ssh_channels *sc) |
{ |
{ |
u_int i; |
u_int i; |
int max = 0; |
int max = 0; |
Channel *c; |
Channel *c; |
|
|
for (i = 0; i < channels_alloc; i++) { |
for (i = 0; i < sc->channels_alloc; i++) { |
c = channels[i]; |
c = sc->channels[i]; |
if (c != NULL) { |
if (c != NULL) { |
max = MAXIMUM(max, c->rfd); |
max = MAXIMUM(max, c->rfd); |
max = MAXIMUM(max, c->wfd); |
max = MAXIMUM(max, c->wfd); |
max = MAXIMUM(max, c->efd); |
max = MAXIMUM(max, c->efd); |
} |
} |
} |
} |
return max; |
sc->channel_max_fd = max; |
} |
} |
|
|
int |
int |
channel_close_fd(int *fdp) |
channel_close_fd(struct ssh *ssh, int *fdp) |
{ |
{ |
|
struct ssh_channels *sc = ssh->chanctxt; |
int ret = 0, fd = *fdp; |
int ret = 0, fd = *fdp; |
|
|
if (fd != -1) { |
if (fd != -1) { |
ret = close(fd); |
ret = close(fd); |
*fdp = -1; |
*fdp = -1; |
if (fd == channel_max_fd) |
if (fd == sc->channel_max_fd) |
channel_max_fd = channel_find_maxfd(); |
channel_find_maxfd(sc); |
} |
} |
return ret; |
return ret; |
} |
} |
|
|
/* Close all channel fd/socket. */ |
/* Close all channel fd/socket. */ |
static void |
static void |
channel_close_fds(Channel *c) |
channel_close_fds(struct ssh *ssh, Channel *c) |
{ |
{ |
channel_close_fd(&c->sock); |
channel_close_fd(ssh, &c->sock); |
channel_close_fd(&c->rfd); |
channel_close_fd(ssh, &c->rfd); |
channel_close_fd(&c->wfd); |
channel_close_fd(ssh, &c->wfd); |
channel_close_fd(&c->efd); |
channel_close_fd(ssh, &c->efd); |
} |
} |
|
|
|
static void |
|
fwd_perm_clear(ForwardPermission *fp) |
|
{ |
|
free(fp->host_to_connect); |
|
free(fp->listen_host); |
|
free(fp->listen_path); |
|
bzero(fp, sizeof(*fp)); |
|
} |
|
|
|
enum { FWDPERM_USER, FWDPERM_ADMIN }; |
|
|
|
static int |
|
fwd_perm_list_add(struct ssh *ssh, int which, |
|
const char *host_to_connect, int port_to_connect, |
|
const char *listen_host, const char *listen_path, int listen_port, |
|
Channel *downstream) |
|
{ |
|
ForwardPermission **fpl; |
|
u_int n, *nfpl; |
|
|
|
switch (which) { |
|
case FWDPERM_USER: |
|
fpl = &ssh->chanctxt->permitted_opens; |
|
nfpl = &ssh->chanctxt->num_permitted_opens; |
|
break; |
|
case FWDPERM_ADMIN: |
|
fpl = &ssh->chanctxt->permitted_adm_opens; |
|
nfpl = &ssh->chanctxt->num_adm_permitted_opens; |
|
break; |
|
default: |
|
fatal("%s: invalid list %d", __func__, which); |
|
} |
|
|
|
if (*nfpl >= INT_MAX) |
|
fatal("%s: overflow", __func__); |
|
|
|
*fpl = xrecallocarray(*fpl, *nfpl, *nfpl + 1, sizeof(**fpl)); |
|
n = (*nfpl)++; |
|
#define MAYBE_DUP(s) ((s == NULL) ? NULL : xstrdup(s)) |
|
(*fpl)[n].host_to_connect = MAYBE_DUP(host_to_connect); |
|
(*fpl)[n].port_to_connect = port_to_connect; |
|
(*fpl)[n].listen_host = MAYBE_DUP(listen_host); |
|
(*fpl)[n].listen_path = MAYBE_DUP(listen_path); |
|
(*fpl)[n].listen_port = listen_port; |
|
(*fpl)[n].downstream = downstream; |
|
#undef MAYBE_DUP |
|
return (int)n; |
|
} |
|
|
|
static void |
|
mux_remove_remote_forwardings(struct ssh *ssh, Channel *c) |
|
{ |
|
struct ssh_channels *sc = ssh->chanctxt; |
|
ForwardPermission *fp; |
|
int r; |
|
u_int i; |
|
|
|
for (i = 0; i < sc->num_permitted_opens; i++) { |
|
fp = &sc->permitted_opens[i]; |
|
if (fp->downstream != c) |
|
continue; |
|
|
|
/* cancel on the server, since mux client is gone */ |
|
debug("channel %d: cleanup remote forward for %s:%u", |
|
c->self, fp->listen_host, fp->listen_port); |
|
if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || |
|
(r = sshpkt_put_cstring(ssh, |
|
"cancel-tcpip-forward")) != 0 || |
|
(r = sshpkt_put_u8(ssh, 0)) != 0 || |
|
(r = sshpkt_put_cstring(ssh, |
|
channel_rfwd_bind_host(fp->listen_host))) != 0 || |
|
(r = sshpkt_put_u32(ssh, fp->listen_port)) != 0 || |
|
(r = sshpkt_send(ssh)) != 0) { |
|
fatal("%s: channel %i: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
|
fwd_perm_clear(fp); /* unregister */ |
|
} |
|
} |
|
|
/* Free the channel and close its fd/socket. */ |
/* Free the channel and close its fd/socket. */ |
void |
void |
channel_free(Channel *c) |
channel_free(struct ssh *ssh, Channel *c) |
{ |
{ |
|
struct ssh_channels *sc = ssh->chanctxt; |
char *s; |
char *s; |
u_int i, n; |
u_int i, n; |
Channel *other; |
Channel *other; |
struct channel_confirm *cc; |
struct channel_confirm *cc; |
|
|
for (n = 0, i = 0; i < channels_alloc; i++) { |
for (n = 0, i = 0; i < sc->channels_alloc; i++) { |
if ((other = channels[i]) != NULL) { |
if ((other = sc->channels[i]) == NULL) |
n++; |
continue; |
|
n++; |
/* detach from mux client and prepare for closing */ |
/* detach from mux client and prepare for closing */ |
if (c->type == SSH_CHANNEL_MUX_CLIENT && |
if (c->type == SSH_CHANNEL_MUX_CLIENT && |
other->type == SSH_CHANNEL_MUX_PROXY && |
other->type == SSH_CHANNEL_MUX_PROXY && |
other->mux_ctx == c) { |
other->mux_ctx == c) { |
other->mux_ctx = NULL; |
other->mux_ctx = NULL; |
other->type = SSH_CHANNEL_OPEN; |
other->type = SSH_CHANNEL_OPEN; |
other->istate = CHAN_INPUT_CLOSED; |
other->istate = CHAN_INPUT_CLOSED; |
other->ostate = CHAN_OUTPUT_CLOSED; |
other->ostate = CHAN_OUTPUT_CLOSED; |
} |
|
} |
} |
} |
} |
debug("channel %d: free: %s, nchannels %u", c->self, |
debug("channel %d: free: %s, nchannels %u", c->self, |
c->remote_name ? c->remote_name : "???", n); |
c->remote_name ? c->remote_name : "???", n); |
|
|
/* XXX more MUX cleanup: remove remote forwardings */ |
if (c->type == SSH_CHANNEL_MUX_CLIENT) |
if (c->type == SSH_CHANNEL_MUX_CLIENT) { |
mux_remove_remote_forwardings(ssh, c); |
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(); |
s = channel_open_message(ssh); |
debug3("channel %d: status: %s", c->self, s); |
debug3("channel %d: status: %s", c->self, s); |
free(s); |
free(s); |
|
|
channel_close_fds(c); |
channel_close_fds(ssh, c); |
buffer_free(&c->input); |
sshbuf_free(c->input); |
buffer_free(&c->output); |
sshbuf_free(c->output); |
buffer_free(&c->extended); |
sshbuf_free(c->extended); |
|
c->input = c->output = c->extended = NULL; |
free(c->remote_name); |
free(c->remote_name); |
c->remote_name = NULL; |
c->remote_name = NULL; |
free(c->path); |
free(c->path); |
|
|
c->listening_addr = NULL; |
c->listening_addr = NULL; |
while ((cc = TAILQ_FIRST(&c->status_confirms)) != NULL) { |
while ((cc = TAILQ_FIRST(&c->status_confirms)) != NULL) { |
if (cc->abandon_cb != NULL) |
if (cc->abandon_cb != NULL) |
cc->abandon_cb(c, cc->ctx); |
cc->abandon_cb(ssh, c, cc->ctx); |
TAILQ_REMOVE(&c->status_confirms, cc, entry); |
TAILQ_REMOVE(&c->status_confirms, cc, entry); |
explicit_bzero(cc, sizeof(*cc)); |
explicit_bzero(cc, sizeof(*cc)); |
free(cc); |
free(cc); |
} |
} |
if (c->filter_cleanup != NULL && c->filter_ctx != NULL) |
if (c->filter_cleanup != NULL && c->filter_ctx != NULL) |
c->filter_cleanup(c->self, c->filter_ctx); |
c->filter_cleanup(ssh, c->self, c->filter_ctx); |
channels[c->self] = NULL; |
sc->channels[c->self] = NULL; |
|
bzero(c, sizeof(*c)); |
free(c); |
free(c); |
} |
} |
|
|
void |
void |
channel_free_all(void) |
channel_free_all(struct ssh *ssh) |
{ |
{ |
u_int i; |
u_int i; |
|
|
for (i = 0; i < channels_alloc; i++) |
for (i = 0; i < ssh->chanctxt->channels_alloc; i++) |
if (channels[i] != NULL) |
if (ssh->chanctxt->channels[i] != NULL) |
channel_free(channels[i]); |
channel_free(ssh, ssh->chanctxt->channels[i]); |
} |
} |
|
|
/* |
/* |
|
|
* descriptors after a fork. |
* descriptors after a fork. |
*/ |
*/ |
void |
void |
channel_close_all(void) |
channel_close_all(struct ssh *ssh) |
{ |
{ |
u_int i; |
u_int i; |
|
|
for (i = 0; i < channels_alloc; i++) |
for (i = 0; i < ssh->chanctxt->channels_alloc; i++) |
if (channels[i] != NULL) |
if (ssh->chanctxt->channels[i] != NULL) |
channel_close_fds(channels[i]); |
channel_close_fds(ssh, ssh->chanctxt->channels[i]); |
} |
} |
|
|
/* |
/* |
* Stop listening to channels. |
* Stop listening to channels. |
*/ |
*/ |
void |
void |
channel_stop_listening(void) |
channel_stop_listening(struct ssh *ssh) |
{ |
{ |
u_int i; |
u_int i; |
Channel *c; |
Channel *c; |
|
|
for (i = 0; i < channels_alloc; i++) { |
for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { |
c = channels[i]; |
c = ssh->chanctxt->channels[i]; |
if (c != NULL) { |
if (c != NULL) { |
switch (c->type) { |
switch (c->type) { |
case SSH_CHANNEL_AUTH_SOCKET: |
case SSH_CHANNEL_AUTH_SOCKET: |
|
|
case SSH_CHANNEL_X11_LISTENER: |
case SSH_CHANNEL_X11_LISTENER: |
case SSH_CHANNEL_UNIX_LISTENER: |
case SSH_CHANNEL_UNIX_LISTENER: |
case SSH_CHANNEL_RUNIX_LISTENER: |
case SSH_CHANNEL_RUNIX_LISTENER: |
channel_close_fd(&c->sock); |
channel_close_fd(ssh, &c->sock); |
channel_free(c); |
channel_free(ssh, c); |
break; |
break; |
} |
} |
} |
} |
|
|
* more channel is overfull. |
* more channel is overfull. |
*/ |
*/ |
int |
int |
channel_not_very_much_buffered_data(void) |
channel_not_very_much_buffered_data(struct ssh *ssh) |
{ |
{ |
u_int i; |
u_int i; |
|
u_int maxsize = ssh_packet_get_maxsize(ssh); |
Channel *c; |
Channel *c; |
|
|
for (i = 0; i < channels_alloc; i++) { |
for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { |
c = channels[i]; |
c = ssh->chanctxt->channels[i]; |
if (c != NULL && c->type == SSH_CHANNEL_OPEN) { |
if (c == NULL || c->type != SSH_CHANNEL_OPEN) |
if (buffer_len(&c->output) > packet_get_maxsize()) { |
continue; |
debug2("channel %d: big output buffer %u > %u", |
if (sshbuf_len(c->output) > maxsize) { |
c->self, buffer_len(&c->output), |
debug2("channel %d: big output buffer %zu > %u", |
packet_get_maxsize()); |
c->self, sshbuf_len(c->output), maxsize); |
return 0; |
return 0; |
} |
|
} |
} |
} |
} |
return 1; |
return 1; |
|
|
|
|
/* Returns true if any channel is still open. */ |
/* Returns true if any channel is still open. */ |
int |
int |
channel_still_open(void) |
channel_still_open(struct ssh *ssh) |
{ |
{ |
u_int i; |
u_int i; |
Channel *c; |
Channel *c; |
|
|
for (i = 0; i < channels_alloc; i++) { |
for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { |
c = channels[i]; |
c = ssh->chanctxt->channels[i]; |
if (c == NULL) |
if (c == NULL) |
continue; |
continue; |
switch (c->type) { |
switch (c->type) { |
|
|
|
|
/* Returns the id of an open channel suitable for keepaliving */ |
/* Returns the id of an open channel suitable for keepaliving */ |
int |
int |
channel_find_open(void) |
channel_find_open(struct ssh *ssh) |
{ |
{ |
u_int i; |
u_int i; |
Channel *c; |
Channel *c; |
|
|
for (i = 0; i < channels_alloc; i++) { |
for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { |
c = channels[i]; |
c = ssh->chanctxt->channels[i]; |
if (c == NULL || c->remote_id < 0) |
if (c == NULL || c->remote_id < 0) |
continue; |
continue; |
switch (c->type) { |
switch (c->type) { |
|
|
* newlines. |
* newlines. |
*/ |
*/ |
char * |
char * |
channel_open_message(void) |
channel_open_message(struct ssh *ssh) |
{ |
{ |
Buffer buffer; |
struct sshbuf *buf; |
Channel *c; |
Channel *c; |
char buf[1024], *cp; |
|
u_int i; |
u_int i; |
|
int r; |
|
char *ret; |
|
|
buffer_init(&buffer); |
if ((buf = sshbuf_new()) == NULL) |
snprintf(buf, sizeof buf, "The following connections are open:\r\n"); |
fatal("%s: sshbuf_new", __func__); |
buffer_append(&buffer, buf, strlen(buf)); |
if ((r = sshbuf_putf(buf, |
for (i = 0; i < channels_alloc; i++) { |
"The following connections are open:\r\n")) != 0) |
c = channels[i]; |
fatal("%s: sshbuf_putf: %s", __func__, ssh_err(r)); |
|
for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { |
|
c = ssh->chanctxt->channels[i]; |
if (c == NULL) |
if (c == NULL) |
continue; |
continue; |
switch (c->type) { |
switch (c->type) { |
|
|
case SSH_CHANNEL_X11_OPEN: |
case SSH_CHANNEL_X11_OPEN: |
case SSH_CHANNEL_MUX_PROXY: |
case SSH_CHANNEL_MUX_PROXY: |
case SSH_CHANNEL_MUX_CLIENT: |
case SSH_CHANNEL_MUX_CLIENT: |
snprintf(buf, sizeof buf, |
if ((r = sshbuf_putf(buf, " #%d %.300s " |
" #%d %.300s (t%d r%d i%u/%d o%u/%d fd %d/%d cc %d)\r\n", |
"(t%d r%d i%u/%zu o%u/%zu fd %d/%d cc %d)\r\n", |
c->self, c->remote_name, |
c->self, c->remote_name, |
c->type, c->remote_id, |
c->type, c->remote_id, |
c->istate, buffer_len(&c->input), |
c->istate, sshbuf_len(c->input), |
c->ostate, buffer_len(&c->output), |
c->ostate, sshbuf_len(c->output), |
c->rfd, c->wfd, c->ctl_chan); |
c->rfd, c->wfd, c->ctl_chan)) != 0) |
buffer_append(&buffer, buf, strlen(buf)); |
fatal("%s: sshbuf_putf: %s", |
|
__func__, ssh_err(r)); |
continue; |
continue; |
default: |
default: |
fatal("channel_open_message: bad channel type %d", c->type); |
fatal("%s: bad channel type %d", __func__, c->type); |
/* NOTREACHED */ |
/* NOTREACHED */ |
} |
} |
} |
} |
buffer_append(&buffer, "\0", 1); |
if ((ret = sshbuf_dup_string(buf)) == NULL) |
cp = xstrdup((char *)buffer_ptr(&buffer)); |
fatal("%s: sshbuf_dup_string", __func__); |
buffer_free(&buffer); |
sshbuf_free(buf); |
return cp; |
return ret; |
} |
} |
|
|
|
static void |
|
open_preamble(struct ssh *ssh, const char *where, Channel *c, const char *type) |
|
{ |
|
int r; |
|
|
|
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_OPEN)) != 0 || |
|
(r = sshpkt_put_cstring(ssh, type)) != 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: open: %s", where, c->self, ssh_err(r)); |
|
} |
|
} |
|
|
void |
void |
channel_send_open(int id) |
channel_send_open(struct ssh *ssh, int id) |
{ |
{ |
Channel *c = channel_lookup(id); |
Channel *c = channel_lookup(ssh, id); |
|
int r; |
|
|
if (c == NULL) { |
if (c == NULL) { |
logit("channel_send_open: %d: bad id", id); |
logit("channel_send_open: %d: bad id", id); |
return; |
return; |
} |
} |
debug2("channel %d: send open", id); |
debug2("channel %d: send open", id); |
packet_start(SSH2_MSG_CHANNEL_OPEN); |
open_preamble(ssh, __func__, c, c->ctype); |
packet_put_cstring(c->ctype); |
if ((r = sshpkt_send(ssh)) != 0) |
packet_put_int(c->self); |
fatal("%s: channel %i: %s", __func__, c->self, ssh_err(r)); |
packet_put_int(c->local_window); |
|
packet_put_int(c->local_maxpacket); |
|
packet_send(); |
|
} |
} |
|
|
void |
void |
channel_request_start(int id, char *service, int wantconfirm) |
channel_request_start(struct ssh *ssh, int id, char *service, int wantconfirm) |
{ |
{ |
Channel *c = channel_lookup(id); |
Channel *c = channel_lookup(ssh, id); |
|
int r; |
|
|
if (c == NULL) { |
if (c == NULL) { |
logit("channel_request_start: %d: unknown channel id", id); |
logit("%s: %d: unknown channel id", __func__, id); |
return; |
return; |
} |
} |
debug2("channel %d: request %s confirm %d", id, service, wantconfirm); |
debug2("channel %d: request %s confirm %d", id, service, wantconfirm); |
packet_start(SSH2_MSG_CHANNEL_REQUEST); |
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_REQUEST)) != 0 || |
packet_put_int(c->remote_id); |
(r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || |
packet_put_cstring(service); |
(r = sshpkt_put_cstring(ssh, service)) != 0 || |
packet_put_char(wantconfirm); |
(r = sshpkt_put_u8(ssh, wantconfirm)) != 0) { |
|
fatal("%s: channel %i: %s", __func__, c->self, ssh_err(r)); |
|
} |
} |
} |
|
|
void |
void |
channel_register_status_confirm(int id, channel_confirm_cb *cb, |
channel_register_status_confirm(struct ssh *ssh, int id, |
channel_confirm_abandon_cb *abandon_cb, void *ctx) |
channel_confirm_cb *cb, channel_confirm_abandon_cb *abandon_cb, void *ctx) |
{ |
{ |
struct channel_confirm *cc; |
struct channel_confirm *cc; |
Channel *c; |
Channel *c; |
|
|
if ((c = channel_lookup(id)) == NULL) |
if ((c = channel_lookup(ssh, id)) == NULL) |
fatal("channel_register_expect: %d: bad id", id); |
fatal("%s: %d: bad id", __func__, id); |
|
|
cc = xcalloc(1, sizeof(*cc)); |
cc = xcalloc(1, sizeof(*cc)); |
cc->cb = cb; |
cc->cb = cb; |
|
|
} |
} |
|
|
void |
void |
channel_register_open_confirm(int id, channel_open_fn *fn, void *ctx) |
channel_register_open_confirm(struct ssh *ssh, int id, |
|
channel_open_fn *fn, void *ctx) |
{ |
{ |
Channel *c = channel_lookup(id); |
Channel *c = channel_lookup(ssh, id); |
|
|
if (c == NULL) { |
if (c == NULL) { |
logit("channel_register_open_confirm: %d: bad id", id); |
logit("%s: %d: bad id", __func__, id); |
return; |
return; |
} |
} |
c->open_confirm = fn; |
c->open_confirm = fn; |
|
|
} |
} |
|
|
void |
void |
channel_register_cleanup(int id, channel_callback_fn *fn, int do_close) |
channel_register_cleanup(struct ssh *ssh, int id, |
|
channel_callback_fn *fn, int do_close) |
{ |
{ |
Channel *c = channel_by_id(id); |
Channel *c = channel_by_id(ssh, id); |
|
|
if (c == NULL) { |
if (c == NULL) { |
logit("channel_register_cleanup: %d: bad id", id); |
logit("%s: %d: bad id", __func__, id); |
return; |
return; |
} |
} |
c->detach_user = fn; |
c->detach_user = fn; |
|
|
} |
} |
|
|
void |
void |
channel_cancel_cleanup(int id) |
channel_cancel_cleanup(struct ssh *ssh, int id) |
{ |
{ |
Channel *c = channel_by_id(id); |
Channel *c = channel_by_id(ssh, id); |
|
|
if (c == NULL) { |
if (c == NULL) { |
logit("channel_cancel_cleanup: %d: bad id", id); |
logit("%s: %d: bad id", __func__, id); |
return; |
return; |
} |
} |
c->detach_user = NULL; |
c->detach_user = NULL; |
|
|
} |
} |
|
|
void |
void |
channel_register_filter(int id, channel_infilter_fn *ifn, |
channel_register_filter(struct ssh *ssh, int id, channel_infilter_fn *ifn, |
channel_outfilter_fn *ofn, channel_filter_cleanup_fn *cfn, void *ctx) |
channel_outfilter_fn *ofn, channel_filter_cleanup_fn *cfn, void *ctx) |
{ |
{ |
Channel *c = channel_lookup(id); |
Channel *c = channel_lookup(ssh, id); |
|
|
if (c == NULL) { |
if (c == NULL) { |
logit("channel_register_filter: %d: bad id", id); |
logit("%s: %d: bad id", __func__, id); |
return; |
return; |
} |
} |
c->input_filter = ifn; |
c->input_filter = ifn; |
|
|
} |
} |
|
|
void |
void |
channel_set_fds(int id, int rfd, int wfd, int efd, |
channel_set_fds(struct ssh *ssh, int id, int rfd, int wfd, int efd, |
int extusage, int nonblock, int is_tty, u_int window_max) |
int extusage, int nonblock, int is_tty, u_int window_max) |
{ |
{ |
Channel *c = channel_lookup(id); |
Channel *c = channel_lookup(ssh, id); |
|
int r; |
|
|
if (c == NULL || c->type != SSH_CHANNEL_LARVAL) |
if (c == NULL || c->type != SSH_CHANNEL_LARVAL) |
fatal("channel_activate for non-larval channel %d.", id); |
fatal("channel_activate for non-larval channel %d.", id); |
channel_register_fds(c, rfd, wfd, efd, extusage, nonblock, is_tty); |
channel_register_fds(ssh, c, rfd, wfd, efd, extusage, nonblock, is_tty); |
c->type = SSH_CHANNEL_OPEN; |
c->type = SSH_CHANNEL_OPEN; |
c->local_window = c->local_window_max = window_max; |
c->local_window = c->local_window_max = window_max; |
packet_start(SSH2_MSG_CHANNEL_WINDOW_ADJUST); |
|
packet_put_int(c->remote_id); |
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_WINDOW_ADJUST)) != 0 || |
packet_put_int(c->local_window); |
(r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || |
packet_send(); |
(r = sshpkt_put_u32(ssh, c->local_window)) != 0 || |
|
(r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: channel %i: %s", __func__, c->self, ssh_err(r)); |
} |
} |
|
|
/* |
|
* 'channel_pre*' are called just before select() to add any bits relevant to |
|
* channels in the select bitmasks. |
|
*/ |
|
/* |
|
* 'channel_post*': perform any appropriate operations for channels which |
|
* have events pending. |
|
*/ |
|
typedef void chan_fn(Channel *c, fd_set *readset, fd_set *writeset); |
|
chan_fn *channel_pre[SSH_CHANNEL_MAX_TYPE]; |
|
chan_fn *channel_post[SSH_CHANNEL_MAX_TYPE]; |
|
|
|
/* ARGSUSED */ |
|
static void |
static void |
channel_pre_listener(Channel *c, fd_set *readset, fd_set *writeset) |
channel_pre_listener(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
FD_SET(c->sock, readset); |
FD_SET(c->sock, readset); |
} |
} |
|
|
/* ARGSUSED */ |
|
static void |
static void |
channel_pre_connecting(Channel *c, fd_set *readset, fd_set *writeset) |
channel_pre_connecting(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
debug3("channel %d: waiting for connection", c->self); |
debug3("channel %d: waiting for connection", c->self); |
FD_SET(c->sock, writeset); |
FD_SET(c->sock, writeset); |
} |
} |
|
|
static void |
static void |
channel_pre_open(Channel *c, fd_set *readset, fd_set *writeset) |
channel_pre_open(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
if (c->istate == CHAN_INPUT_OPEN && |
if (c->istate == CHAN_INPUT_OPEN && |
c->remote_window > 0 && |
c->remote_window > 0 && |
buffer_len(&c->input) < c->remote_window && |
sshbuf_len(c->input) < c->remote_window && |
buffer_check_alloc(&c->input, CHAN_RBUF)) |
sshbuf_check_reserve(c->input, CHAN_RBUF) == 0) |
FD_SET(c->rfd, readset); |
FD_SET(c->rfd, readset); |
if (c->ostate == CHAN_OUTPUT_OPEN || |
if (c->ostate == CHAN_OUTPUT_OPEN || |
c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { |
c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { |
if (buffer_len(&c->output) > 0) { |
if (sshbuf_len(c->output) > 0) { |
FD_SET(c->wfd, writeset); |
FD_SET(c->wfd, writeset); |
} else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { |
} else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { |
if (CHANNEL_EFD_OUTPUT_ACTIVE(c)) |
if (CHANNEL_EFD_OUTPUT_ACTIVE(c)) |
debug2("channel %d: obuf_empty delayed efd %d/(%d)", |
debug2("channel %d: " |
c->self, c->efd, buffer_len(&c->extended)); |
"obuf_empty delayed efd %d/(%zu)", c->self, |
|
c->efd, sshbuf_len(c->extended)); |
else |
else |
chan_obuf_empty(c); |
chan_obuf_empty(ssh, c); |
} |
} |
} |
} |
/** XXX check close conditions, too */ |
/** XXX check close conditions, too */ |
if (c->efd != -1 && !(c->istate == CHAN_INPUT_CLOSED && |
if (c->efd != -1 && !(c->istate == CHAN_INPUT_CLOSED && |
c->ostate == CHAN_OUTPUT_CLOSED)) { |
c->ostate == CHAN_OUTPUT_CLOSED)) { |
if (c->extended_usage == CHAN_EXTENDED_WRITE && |
if (c->extended_usage == CHAN_EXTENDED_WRITE && |
buffer_len(&c->extended) > 0) |
sshbuf_len(c->extended) > 0) |
FD_SET(c->efd, writeset); |
FD_SET(c->efd, writeset); |
else if (c->efd != -1 && !(c->flags & CHAN_EOF_SENT) && |
else if (c->efd != -1 && !(c->flags & CHAN_EOF_SENT) && |
(c->extended_usage == CHAN_EXTENDED_READ || |
(c->extended_usage == CHAN_EXTENDED_READ || |
c->extended_usage == CHAN_EXTENDED_IGNORE) && |
c->extended_usage == CHAN_EXTENDED_IGNORE) && |
buffer_len(&c->extended) < c->remote_window) |
sshbuf_len(c->extended) < c->remote_window) |
FD_SET(c->efd, readset); |
FD_SET(c->efd, readset); |
} |
} |
/* XXX: What about efd? races? */ |
/* XXX: What about efd? races? */ |
|
|
* Returns: 0 = need more data, -1 = wrong cookie, 1 = ok |
* Returns: 0 = need more data, -1 = wrong cookie, 1 = ok |
*/ |
*/ |
static int |
static int |
x11_open_helper(Buffer *b) |
x11_open_helper(struct ssh *ssh, struct sshbuf *b) |
{ |
{ |
|
struct ssh_channels *sc = ssh->chanctxt; |
u_char *ucp; |
u_char *ucp; |
u_int proto_len, data_len; |
u_int proto_len, data_len; |
|
|
/* Is this being called after the refusal deadline? */ |
/* Is this being called after the refusal deadline? */ |
if (x11_refuse_time != 0 && (u_int)monotime() >= x11_refuse_time) { |
if (sc->x11_refuse_time != 0 && |
|
(u_int)monotime() >= sc->x11_refuse_time) { |
verbose("Rejected X11 connection after ForwardX11Timeout " |
verbose("Rejected X11 connection after ForwardX11Timeout " |
"expired"); |
"expired"); |
return -1; |
return -1; |
} |
} |
|
|
/* Check if the fixed size part of the packet is in buffer. */ |
/* Check if the fixed size part of the packet is in buffer. */ |
if (buffer_len(b) < 12) |
if (sshbuf_len(b) < 12) |
return 0; |
return 0; |
|
|
/* Parse the lengths of variable-length fields. */ |
/* Parse the lengths of variable-length fields. */ |
ucp = buffer_ptr(b); |
ucp = sshbuf_mutable_ptr(b); |
if (ucp[0] == 0x42) { /* Byte order MSB first. */ |
if (ucp[0] == 0x42) { /* Byte order MSB first. */ |
proto_len = 256 * ucp[6] + ucp[7]; |
proto_len = 256 * ucp[6] + ucp[7]; |
data_len = 256 * ucp[8] + ucp[9]; |
data_len = 256 * ucp[8] + ucp[9]; |
|
|
} |
} |
|
|
/* Check if the whole packet is in buffer. */ |
/* Check if the whole packet is in buffer. */ |
if (buffer_len(b) < |
if (sshbuf_len(b) < |
12 + ((proto_len + 3) & ~3) + ((data_len + 3) & ~3)) |
12 + ((proto_len + 3) & ~3) + ((data_len + 3) & ~3)) |
return 0; |
return 0; |
|
|
/* Check if authentication protocol matches. */ |
/* Check if authentication protocol matches. */ |
if (proto_len != strlen(x11_saved_proto) || |
if (proto_len != strlen(sc->x11_saved_proto) || |
memcmp(ucp + 12, x11_saved_proto, proto_len) != 0) { |
memcmp(ucp + 12, sc->x11_saved_proto, proto_len) != 0) { |
debug2("X11 connection uses different authentication protocol."); |
debug2("X11 connection uses different authentication protocol."); |
return -1; |
return -1; |
} |
} |
/* Check if authentication data matches our fake data. */ |
/* Check if authentication data matches our fake data. */ |
if (data_len != x11_fake_data_len || |
if (data_len != sc->x11_fake_data_len || |
timingsafe_bcmp(ucp + 12 + ((proto_len + 3) & ~3), |
timingsafe_bcmp(ucp + 12 + ((proto_len + 3) & ~3), |
x11_fake_data, x11_fake_data_len) != 0) { |
sc->x11_fake_data, sc->x11_fake_data_len) != 0) { |
debug2("X11 auth data does not match fake data."); |
debug2("X11 auth data does not match fake data."); |
return -1; |
return -1; |
} |
} |
/* Check fake data length */ |
/* Check fake data length */ |
if (x11_fake_data_len != x11_saved_data_len) { |
if (sc->x11_fake_data_len != sc->x11_saved_data_len) { |
error("X11 fake_data_len %d != saved_data_len %d", |
error("X11 fake_data_len %d != saved_data_len %d", |
x11_fake_data_len, x11_saved_data_len); |
sc->x11_fake_data_len, sc->x11_saved_data_len); |
return -1; |
return -1; |
} |
} |
/* |
/* |
|
|
* data. |
* data. |
*/ |
*/ |
memcpy(ucp + 12 + ((proto_len + 3) & ~3), |
memcpy(ucp + 12 + ((proto_len + 3) & ~3), |
x11_saved_data, x11_saved_data_len); |
sc->x11_saved_data, sc->x11_saved_data_len); |
return 1; |
return 1; |
} |
} |
|
|
static void |
static void |
channel_pre_x11_open(Channel *c, fd_set *readset, fd_set *writeset) |
channel_pre_x11_open(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
int ret = x11_open_helper(&c->output); |
int ret = x11_open_helper(ssh, c->output); |
|
|
/* c->force_drain = 1; */ |
/* c->force_drain = 1; */ |
|
|
if (ret == 1) { |
if (ret == 1) { |
c->type = SSH_CHANNEL_OPEN; |
c->type = SSH_CHANNEL_OPEN; |
channel_pre_open(c, readset, writeset); |
channel_pre_open(ssh, c, readset, writeset); |
} else if (ret == -1) { |
} else if (ret == -1) { |
logit("X11 connection rejected because of wrong authentication."); |
logit("X11 connection rejected because of wrong authentication."); |
debug2("X11 rejected %d i%d/o%d", c->self, c->istate, c->ostate); |
debug2("X11 rejected %d i%d/o%d", |
chan_read_failed(c); |
c->self, c->istate, c->ostate); |
buffer_clear(&c->input); |
chan_read_failed(ssh, c); |
chan_ibuf_empty(c); |
sshbuf_reset(c->input); |
buffer_clear(&c->output); |
chan_ibuf_empty(ssh, c); |
chan_write_failed(c); |
sshbuf_reset(c->output); |
|
chan_write_failed(ssh, c); |
debug2("X11 closed %d i%d/o%d", c->self, c->istate, c->ostate); |
debug2("X11 closed %d i%d/o%d", c->self, c->istate, c->ostate); |
} |
} |
} |
} |
|
|
static void |
static void |
channel_pre_mux_client(Channel *c, fd_set *readset, fd_set *writeset) |
channel_pre_mux_client(struct ssh *ssh, |
|
Channel *c, fd_set *readset, fd_set *writeset) |
{ |
{ |
if (c->istate == CHAN_INPUT_OPEN && !c->mux_pause && |
if (c->istate == CHAN_INPUT_OPEN && !c->mux_pause && |
buffer_check_alloc(&c->input, CHAN_RBUF)) |
sshbuf_check_reserve(c->input, CHAN_RBUF) == 0) |
FD_SET(c->rfd, readset); |
FD_SET(c->rfd, readset); |
if (c->istate == CHAN_INPUT_WAIT_DRAIN) { |
if (c->istate == CHAN_INPUT_WAIT_DRAIN) { |
/* clear buffer immediately (discard any partial packet) */ |
/* clear buffer immediately (discard any partial packet) */ |
buffer_clear(&c->input); |
sshbuf_reset(c->input); |
chan_ibuf_empty(c); |
chan_ibuf_empty(ssh, c); |
/* Start output drain. XXX just kill chan? */ |
/* Start output drain. XXX just kill chan? */ |
chan_rcvd_oclose(c); |
chan_rcvd_oclose(ssh, c); |
} |
} |
if (c->ostate == CHAN_OUTPUT_OPEN || |
if (c->ostate == CHAN_OUTPUT_OPEN || |
c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { |
c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { |
if (buffer_len(&c->output) > 0) |
if (sshbuf_len(c->output) > 0) |
FD_SET(c->wfd, writeset); |
FD_SET(c->wfd, writeset); |
else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN) |
else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN) |
chan_obuf_empty(c); |
chan_obuf_empty(ssh, c); |
} |
} |
} |
} |
|
|
/* try to decode a socks4 header */ |
/* try to decode a socks4 header */ |
/* ARGSUSED */ |
|
static int |
static int |
channel_decode_socks4(Channel *c, fd_set *readset, fd_set *writeset) |
channel_decode_socks4(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
char *p, *host; |
const u_char *p; |
|
char *host; |
u_int len, have, i, found, need; |
u_int len, have, i, found, need; |
char username[256]; |
char username[256]; |
struct { |
struct { |
|
|
u_int16_t dest_port; |
u_int16_t dest_port; |
struct in_addr dest_addr; |
struct in_addr dest_addr; |
} s4_req, s4_rsp; |
} s4_req, s4_rsp; |
|
int r; |
|
|
debug2("channel %d: decode socks4", c->self); |
debug2("channel %d: decode socks4", c->self); |
|
|
have = buffer_len(&c->input); |
have = sshbuf_len(c->input); |
len = sizeof(s4_req); |
len = sizeof(s4_req); |
if (have < len) |
if (have < len) |
return 0; |
return 0; |
p = (char *)buffer_ptr(&c->input); |
p = sshbuf_ptr(c->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; |
buffer_get(&c->input, (char *)&s4_req.version, 1); |
if ((r = sshbuf_get(c->input, &s4_req.version, 1)) != 0 || |
buffer_get(&c->input, (char *)&s4_req.command, 1); |
(r = sshbuf_get(c->input, &s4_req.command, 1)) != 0 || |
buffer_get(&c->input, (char *)&s4_req.dest_port, 2); |
(r = sshbuf_get(c->input, &s4_req.dest_port, 2)) != 0 || |
buffer_get(&c->input, (char *)&s4_req.dest_addr, 4); |
(r = sshbuf_get(c->input, &s4_req.dest_addr, 4)) != 0) { |
have = buffer_len(&c->input); |
debug("channels %d: decode socks4: %s", c->self, ssh_err(r)); |
p = (char *)buffer_ptr(&c->input); |
return -1; |
|
} |
|
have = sshbuf_len(c->input); |
|
p = sshbuf_ptr(c->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); |
|
|
} |
} |
len = strlen(p); |
len = strlen(p); |
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' */ |
if (len > have) |
|
fatal("channel %d: decode socks4: len %d > have %d", |
|
c->self, len, have); |
|
strlcpy(username, p, sizeof(username)); |
strlcpy(username, p, sizeof(username)); |
buffer_consume(&c->input, len); |
if ((r = sshbuf_consume(c->input, len)) != 0) { |
|
fatal("%s: channel %d: consume: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
free(c->path); |
free(c->path); |
c->path = NULL; |
c->path = NULL; |
if (need == 1) { /* SOCKS4: one string */ |
if (need == 1) { /* SOCKS4: one string */ |
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 = buffer_len(&c->input); |
have = sshbuf_len(c->input); |
p = (char *)buffer_ptr(&c->input); |
p = sshbuf_ptr(c->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); |
buffer_consume(&c->input, len); |
if ((r = sshbuf_consume(c->input, len)) != 0) { |
|
fatal("%s: channel %d: consume: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
} |
} |
c->host_port = ntohs(s4_req.dest_port); |
c->host_port = ntohs(s4_req.dest_port); |
|
|
|
|
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 */ |
buffer_append(&c->output, &s4_rsp, sizeof(s4_rsp)); |
if ((r = sshbuf_put(c->output, &s4_rsp, sizeof(s4_rsp))) != 0) { |
|
fatal("%s: channel %d: append reply: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
return 1; |
return 1; |
} |
} |
|
|
|
|
#define SSH_SOCKS5_CONNECT 0x01 |
#define SSH_SOCKS5_CONNECT 0x01 |
#define SSH_SOCKS5_SUCCESS 0x00 |
#define SSH_SOCKS5_SUCCESS 0x00 |
|
|
/* ARGSUSED */ |
|
static int |
static int |
channel_decode_socks5(Channel *c, fd_set *readset, fd_set *writeset) |
channel_decode_socks5(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
|
/* XXX use get/put_u8 instead of trusting struct padding */ |
struct { |
struct { |
u_int8_t version; |
u_int8_t version; |
u_int8_t command; |
u_int8_t command; |
|
|
} s5_req, s5_rsp; |
} s5_req, s5_rsp; |
u_int16_t dest_port; |
u_int16_t dest_port; |
char dest_addr[255+1], ntop[INET6_ADDRSTRLEN]; |
char dest_addr[255+1], ntop[INET6_ADDRSTRLEN]; |
u_char *p; |
const u_char *p; |
u_int have, need, i, found, nmethods, addrlen, af; |
u_int have, need, i, found, nmethods, addrlen, af; |
|
int r; |
|
|
debug2("channel %d: decode socks5", c->self); |
debug2("channel %d: decode socks5", c->self); |
p = buffer_ptr(&c->input); |
p = sshbuf_ptr(c->input); |
if (p[0] != 0x05) |
if (p[0] != 0x05) |
return -1; |
return -1; |
have = buffer_len(&c->input); |
have = sshbuf_len(c->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; |
} |
} |
buffer_consume(&c->input, nmethods + 2); |
if ((r = sshbuf_consume(c->input, nmethods + 2)) != 0) { |
buffer_put_char(&c->output, 0x05); /* version */ |
fatal("%s: channel %d: consume: %s", __func__, |
buffer_put_char(&c->output, SSH_SOCKS5_NOAUTH); /* method */ |
c->self, ssh_err(r)); |
|
} |
|
/* version, method */ |
|
if ((r = sshbuf_put_u8(c->output, 0x05)) != 0 || |
|
(r = sshbuf_put_u8(c->output, SSH_SOCKS5_NOAUTH)) != 0) { |
|
fatal("%s: channel %d: append reply: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
FD_SET(c->sock, writeset); |
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); |
|
|
need++; |
need++; |
if (have < need) |
if (have < need) |
return 0; |
return 0; |
buffer_consume(&c->input, sizeof(s5_req)); |
if ((r = sshbuf_consume(c->input, sizeof(s5_req))) != 0) { |
if (s5_req.atyp == SSH_SOCKS5_DOMAIN) |
fatal("%s: channel %d: consume: %s", __func__, |
buffer_consume(&c->input, 1); /* host string length */ |
c->self, ssh_err(r)); |
buffer_get(&c->input, &dest_addr, addrlen); |
} |
buffer_get(&c->input, (char *)&dest_port, 2); |
if (s5_req.atyp == SSH_SOCKS5_DOMAIN) { |
|
/* host string length */ |
|
if ((r = sshbuf_consume(c->input, 1)) != 0) { |
|
fatal("%s: channel %d: consume: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
|
} |
|
if ((r = sshbuf_get(c->input, &dest_addr, addrlen)) != 0 || |
|
(r = sshbuf_get(c->input, &dest_port, 2)) != 0) { |
|
debug("channel %d: parse addr/port: %s", c->self, ssh_err(r)); |
|
return -1; |
|
} |
dest_addr[addrlen] = '\0'; |
dest_addr[addrlen] = '\0'; |
free(c->path); |
free(c->path); |
c->path = NULL; |
c->path = NULL; |
|
|
s5_rsp.atyp = SSH_SOCKS5_IPV4; |
s5_rsp.atyp = SSH_SOCKS5_IPV4; |
dest_port = 0; /* ignored */ |
dest_port = 0; /* ignored */ |
|
|
buffer_append(&c->output, &s5_rsp, sizeof(s5_rsp)); |
if ((r = sshbuf_put(c->output, &s5_rsp, sizeof(s5_rsp))) != 0 || |
buffer_put_int(&c->output, ntohl(INADDR_ANY)); /* bind address */ |
(r = sshbuf_put_u32(c->output, ntohl(INADDR_ANY))) != 0 || |
buffer_append(&c->output, &dest_port, sizeof(dest_port)); |
(r = sshbuf_put(c->output, &dest_port, sizeof(dest_port))) != 0) |
|
fatal("%s: channel %d: append reply: %s", __func__, |
|
c->self, ssh_err(r)); |
return 1; |
return 1; |
} |
} |
|
|
Channel * |
Channel * |
channel_connect_stdio_fwd(const char *host_to_connect, u_short port_to_connect, |
channel_connect_stdio_fwd(struct ssh *ssh, |
int in, int out) |
const char *host_to_connect, u_short port_to_connect, int in, int out) |
{ |
{ |
Channel *c; |
Channel *c; |
|
|
debug("channel_connect_stdio_fwd %s:%d", host_to_connect, |
debug("%s %s:%d", __func__, host_to_connect, port_to_connect); |
port_to_connect); |
|
|
|
c = channel_new("stdio-forward", SSH_CHANNEL_OPENING, in, out, |
c = channel_new(ssh, "stdio-forward", SSH_CHANNEL_OPENING, in, out, |
-1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, |
-1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, |
0, "stdio-forward", /*nonblock*/0); |
0, "stdio-forward", /*nonblock*/0); |
|
|
|
|
c->listening_port = 0; |
c->listening_port = 0; |
c->force_drain = 1; |
c->force_drain = 1; |
|
|
channel_register_fds(c, in, out, -1, 0, 1, 0); |
channel_register_fds(ssh, c, in, out, -1, 0, 1, 0); |
port_open_helper(c, "direct-tcpip"); |
port_open_helper(ssh, c, "direct-tcpip"); |
|
|
return c; |
return c; |
} |
} |
|
|
/* dynamic port forwarding */ |
/* dynamic port forwarding */ |
static void |
static void |
channel_pre_dynamic(Channel *c, fd_set *readset, fd_set *writeset) |
channel_pre_dynamic(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
u_char *p; |
const u_char *p; |
u_int have; |
u_int have; |
int ret; |
int ret; |
|
|
have = buffer_len(&c->input); |
have = sshbuf_len(c->input); |
debug2("channel %d: pre_dynamic: have %d", c->self, have); |
debug2("channel %d: pre_dynamic: have %d", c->self, have); |
/* buffer_dump(&c->input); */ |
/* sshbuf_dump(c->input, stderr); */ |
/* check if the fixed size part of the packet is in buffer. */ |
/* check if the fixed size part of the packet is in buffer. */ |
if (have < 3) { |
if (have < 3) { |
/* need more */ |
/* need more */ |
|
|
return; |
return; |
} |
} |
/* try to guess the protocol */ |
/* try to guess the protocol */ |
p = buffer_ptr(&c->input); |
p = sshbuf_ptr(c->input); |
|
/* XXX sshbuf_peek_u8? */ |
switch (p[0]) { |
switch (p[0]) { |
case 0x04: |
case 0x04: |
ret = channel_decode_socks4(c, readset, writeset); |
ret = channel_decode_socks4(ssh, c, readset, writeset); |
break; |
break; |
case 0x05: |
case 0x05: |
ret = channel_decode_socks5(c, readset, writeset); |
ret = channel_decode_socks5(ssh, c, readset, writeset); |
break; |
break; |
default: |
default: |
ret = -1; |
ret = -1; |
break; |
break; |
} |
} |
if (ret < 0) { |
if (ret < 0) { |
chan_mark_dead(c); |
chan_mark_dead(ssh, c); |
} else if (ret == 0) { |
} else if (ret == 0) { |
debug2("channel %d: pre_dynamic: need more", c->self); |
debug2("channel %d: pre_dynamic: need more", c->self); |
/* need more */ |
/* need more */ |
|
|
} else { |
} else { |
/* switch to the next state */ |
/* switch to the next state */ |
c->type = SSH_CHANNEL_OPENING; |
c->type = SSH_CHANNEL_OPENING; |
port_open_helper(c, "direct-tcpip"); |
port_open_helper(ssh, c, "direct-tcpip"); |
} |
} |
} |
} |
|
|
/* This is our fake X11 server socket. */ |
/* This is our fake X11 server socket. */ |
/* ARGSUSED */ |
|
static void |
static void |
channel_post_x11_listener(Channel *c, fd_set *readset, fd_set *writeset) |
channel_post_x11_listener(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
Channel *nc; |
Channel *nc; |
struct sockaddr_storage addr; |
struct sockaddr_storage addr; |
int newsock, oerrno; |
int r, newsock, oerrno, remote_port; |
socklen_t addrlen; |
socklen_t addrlen; |
char buf[16384], *remote_ipaddr; |
char buf[16384], *remote_ipaddr; |
int remote_port; |
|
|
|
if (FD_ISSET(c->sock, readset)) { |
if (!FD_ISSET(c->sock, readset)) |
debug("X11 connection requested."); |
return; |
addrlen = sizeof(addr); |
|
newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen); |
|
if (c->single_connection) { |
|
oerrno = errno; |
|
debug2("single_connection: closing X11 listener."); |
|
channel_close_fd(&c->sock); |
|
chan_mark_dead(c); |
|
errno = oerrno; |
|
} |
|
if (newsock < 0) { |
|
if (errno != EINTR && errno != EWOULDBLOCK && |
|
errno != ECONNABORTED) |
|
error("accept: %.100s", strerror(errno)); |
|
if (errno == EMFILE || errno == ENFILE) |
|
c->notbefore = monotime() + 1; |
|
return; |
|
} |
|
set_nodelay(newsock); |
|
remote_ipaddr = get_peer_ipaddr(newsock); |
|
remote_port = get_peer_port(newsock); |
|
snprintf(buf, sizeof buf, "X11 connection from %.200s port %d", |
|
remote_ipaddr, remote_port); |
|
|
|
nc = channel_new("accepted x11 socket", |
debug("X11 connection requested."); |
SSH_CHANNEL_OPENING, newsock, newsock, -1, |
addrlen = sizeof(addr); |
c->local_window_max, c->local_maxpacket, 0, buf, 1); |
newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen); |
packet_start(SSH2_MSG_CHANNEL_OPEN); |
if (c->single_connection) { |
packet_put_cstring("x11"); |
oerrno = errno; |
packet_put_int(nc->self); |
debug2("single_connection: closing X11 listener."); |
packet_put_int(nc->local_window_max); |
channel_close_fd(ssh, &c->sock); |
packet_put_int(nc->local_maxpacket); |
chan_mark_dead(ssh, c); |
/* originator ipaddr and port */ |
errno = oerrno; |
packet_put_cstring(remote_ipaddr); |
|
if (datafellows & SSH_BUG_X11FWD) { |
|
debug2("ssh2 x11 bug compat mode"); |
|
} else { |
|
packet_put_int(remote_port); |
|
} |
|
packet_send(); |
|
free(remote_ipaddr); |
|
} |
} |
|
if (newsock < 0) { |
|
if (errno != EINTR && errno != EWOULDBLOCK && |
|
errno != ECONNABORTED) |
|
error("accept: %.100s", strerror(errno)); |
|
if (errno == EMFILE || errno == ENFILE) |
|
c->notbefore = monotime() + 1; |
|
return; |
|
} |
|
set_nodelay(newsock); |
|
remote_ipaddr = get_peer_ipaddr(newsock); |
|
remote_port = get_peer_port(newsock); |
|
snprintf(buf, sizeof buf, "X11 connection from %.200s port %d", |
|
remote_ipaddr, remote_port); |
|
|
|
nc = channel_new(ssh, "accepted x11 socket", |
|
SSH_CHANNEL_OPENING, newsock, newsock, -1, |
|
c->local_window_max, c->local_maxpacket, 0, buf, 1); |
|
open_preamble(ssh, __func__, nc, "x11"); |
|
if ((r = sshpkt_put_cstring(ssh, remote_ipaddr)) != 0) { |
|
fatal("%s: channel %i: reply %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
|
if ((datafellows & SSH_BUG_X11FWD) != 0) |
|
debug2("channel %d: ssh2 x11 bug compat mode", nc->self); |
|
else if ((r = sshpkt_put_u32(ssh, remote_port)) != 0) { |
|
fatal("%s: channel %i: reply %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
|
if ((r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: channel %i: send %s", __func__, c->self, ssh_err(r)); |
|
free(remote_ipaddr); |
} |
} |
|
|
static void |
static void |
port_open_helper(Channel *c, char *rtype) |
port_open_helper(struct ssh *ssh, Channel *c, char *rtype) |
{ |
{ |
char buf[1024]; |
|
char *local_ipaddr = get_local_ipaddr(c->sock); |
char *local_ipaddr = get_local_ipaddr(c->sock); |
int local_port = c->sock == -1 ? 65536 : get_local_port(c->sock); |
int local_port = c->sock == -1 ? 65536 : get_local_port(c->sock); |
char *remote_ipaddr = get_peer_ipaddr(c->sock); |
char *remote_ipaddr = get_peer_ipaddr(c->sock); |
int remote_port = get_peer_port(c->sock); |
int remote_port = get_peer_port(c->sock); |
|
int r; |
|
|
if (remote_port == -1) { |
if (remote_port == -1) { |
/* Fake addr/port to appease peers that validate it (Tectia) */ |
/* Fake addr/port to appease peers that validate it (Tectia) */ |
|
|
remote_port = 65535; |
remote_port = 65535; |
} |
} |
|
|
snprintf(buf, sizeof buf, |
free(c->remote_name); |
|
xasprintf(&c->remote_name, |
"%s: listening port %d for %.100s port %d, " |
"%s: listening port %d for %.100s port %d, " |
"connect from %.200s port %d to %.100s port %d", |
"connect from %.200s port %d to %.100s port %d", |
rtype, c->listening_port, c->path, c->host_port, |
rtype, c->listening_port, c->path, c->host_port, |
remote_ipaddr, remote_port, local_ipaddr, local_port); |
remote_ipaddr, remote_port, local_ipaddr, local_port); |
|
|
free(c->remote_name); |
open_preamble(ssh, __func__, c, rtype); |
c->remote_name = xstrdup(buf); |
|
|
|
packet_start(SSH2_MSG_CHANNEL_OPEN); |
|
packet_put_cstring(rtype); |
|
packet_put_int(c->self); |
|
packet_put_int(c->local_window_max); |
|
packet_put_int(c->local_maxpacket); |
|
if (strcmp(rtype, "direct-tcpip") == 0) { |
if (strcmp(rtype, "direct-tcpip") == 0) { |
/* target host, port */ |
/* target host, port */ |
packet_put_cstring(c->path); |
if ((r = sshpkt_put_cstring(ssh, c->path)) != 0 || |
packet_put_int(c->host_port); |
(r = sshpkt_put_u32(ssh, c->host_port)) != 0) { |
|
fatal("%s: channel %i: reply %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
} else if (strcmp(rtype, "direct-streamlocal@openssh.com") == 0) { |
} else if (strcmp(rtype, "direct-streamlocal@openssh.com") == 0) { |
/* target path */ |
/* target path */ |
packet_put_cstring(c->path); |
if ((r = sshpkt_put_cstring(ssh, c->path)) != 0) { |
|
fatal("%s: channel %i: reply %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
} else if (strcmp(rtype, "forwarded-streamlocal@openssh.com") == 0) { |
} else if (strcmp(rtype, "forwarded-streamlocal@openssh.com") == 0) { |
/* listen path */ |
/* listen path */ |
packet_put_cstring(c->path); |
if ((r = sshpkt_put_cstring(ssh, c->path)) != 0) { |
|
fatal("%s: channel %i: reply %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
} else { |
} else { |
/* listen address, port */ |
/* listen address, port */ |
packet_put_cstring(c->path); |
if ((r = sshpkt_put_cstring(ssh, c->path)) != 0 || |
packet_put_int(local_port); |
(r = sshpkt_put_u32(ssh, local_port)) != 0) { |
|
fatal("%s: channel %i: reply %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
} |
} |
if (strcmp(rtype, "forwarded-streamlocal@openssh.com") == 0) { |
if (strcmp(rtype, "forwarded-streamlocal@openssh.com") == 0) { |
/* reserved for future owner/mode info */ |
/* reserved for future owner/mode info */ |
packet_put_cstring(""); |
if ((r = sshpkt_put_cstring(ssh, "")) != 0) { |
|
fatal("%s: channel %i: reply %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
} else { |
} else { |
/* originator host and port */ |
/* originator host and port */ |
packet_put_cstring(remote_ipaddr); |
if ((r = sshpkt_put_cstring(ssh, remote_ipaddr)) != 0 || |
packet_put_int((u_int)remote_port); |
(r = sshpkt_put_u32(ssh, (u_int)remote_port)) != 0) { |
|
fatal("%s: channel %i: reply %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
} |
} |
packet_send(); |
if ((r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: channel %i: send %s", __func__, c->self, ssh_err(r)); |
free(remote_ipaddr); |
free(remote_ipaddr); |
free(local_ipaddr); |
free(local_ipaddr); |
} |
} |
|
|
} |
} |
|
|
void |
void |
channel_set_x11_refuse_time(u_int refuse_time) |
channel_set_x11_refuse_time(struct ssh *ssh, u_int refuse_time) |
{ |
{ |
x11_refuse_time = refuse_time; |
ssh->chanctxt->x11_refuse_time = refuse_time; |
} |
} |
|
|
/* |
/* |
* This socket is listening for connections to a forwarded TCP/IP port. |
* This socket is listening for connections to a forwarded TCP/IP port. |
*/ |
*/ |
/* ARGSUSED */ |
|
static void |
static void |
channel_post_port_listener(Channel *c, fd_set *readset, fd_set *writeset) |
channel_post_port_listener(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
Channel *nc; |
Channel *nc; |
struct sockaddr_storage addr; |
struct sockaddr_storage addr; |
|
|
socklen_t addrlen; |
socklen_t addrlen; |
char *rtype; |
char *rtype; |
|
|
if (FD_ISSET(c->sock, readset)) { |
if (!FD_ISSET(c->sock, readset)) |
debug("Connection to port %d forwarding " |
return; |
"to %.100s port %d requested.", |
|
c->listening_port, c->path, c->host_port); |
|
|
|
if (c->type == SSH_CHANNEL_RPORT_LISTENER) { |
debug("Connection to port %d forwarding to %.100s port %d requested.", |
nextstate = SSH_CHANNEL_OPENING; |
c->listening_port, c->path, c->host_port); |
rtype = "forwarded-tcpip"; |
|
} else if (c->type == SSH_CHANNEL_RUNIX_LISTENER) { |
|
nextstate = SSH_CHANNEL_OPENING; |
|
rtype = "forwarded-streamlocal@openssh.com"; |
|
} else if (c->host_port == PORT_STREAMLOCAL) { |
|
nextstate = SSH_CHANNEL_OPENING; |
|
rtype = "direct-streamlocal@openssh.com"; |
|
} else if (c->host_port == 0) { |
|
nextstate = SSH_CHANNEL_DYNAMIC; |
|
rtype = "dynamic-tcpip"; |
|
} else { |
|
nextstate = SSH_CHANNEL_OPENING; |
|
rtype = "direct-tcpip"; |
|
} |
|
|
|
addrlen = sizeof(addr); |
if (c->type == SSH_CHANNEL_RPORT_LISTENER) { |
newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen); |
nextstate = SSH_CHANNEL_OPENING; |
if (newsock < 0) { |
rtype = "forwarded-tcpip"; |
if (errno != EINTR && errno != EWOULDBLOCK && |
} else if (c->type == SSH_CHANNEL_RUNIX_LISTENER) { |
errno != ECONNABORTED) |
nextstate = SSH_CHANNEL_OPENING; |
error("accept: %.100s", strerror(errno)); |
rtype = "forwarded-streamlocal@openssh.com"; |
if (errno == EMFILE || errno == ENFILE) |
} else if (c->host_port == PORT_STREAMLOCAL) { |
c->notbefore = monotime() + 1; |
nextstate = SSH_CHANNEL_OPENING; |
return; |
rtype = "direct-streamlocal@openssh.com"; |
} |
} else if (c->host_port == 0) { |
if (c->host_port != PORT_STREAMLOCAL) |
nextstate = SSH_CHANNEL_DYNAMIC; |
set_nodelay(newsock); |
rtype = "dynamic-tcpip"; |
nc = channel_new(rtype, nextstate, newsock, newsock, -1, |
} else { |
c->local_window_max, c->local_maxpacket, 0, rtype, 1); |
nextstate = SSH_CHANNEL_OPENING; |
nc->listening_port = c->listening_port; |
rtype = "direct-tcpip"; |
nc->host_port = c->host_port; |
} |
if (c->path != NULL) |
|
nc->path = xstrdup(c->path); |
|
|
|
if (nextstate != SSH_CHANNEL_DYNAMIC) |
addrlen = sizeof(addr); |
port_open_helper(nc, rtype); |
newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen); |
|
if (newsock < 0) { |
|
if (errno != EINTR && errno != EWOULDBLOCK && |
|
errno != ECONNABORTED) |
|
error("accept: %.100s", strerror(errno)); |
|
if (errno == EMFILE || errno == ENFILE) |
|
c->notbefore = monotime() + 1; |
|
return; |
} |
} |
|
if (c->host_port != PORT_STREAMLOCAL) |
|
set_nodelay(newsock); |
|
nc = channel_new(ssh, rtype, nextstate, newsock, newsock, -1, |
|
c->local_window_max, c->local_maxpacket, 0, rtype, 1); |
|
nc->listening_port = c->listening_port; |
|
nc->host_port = c->host_port; |
|
if (c->path != NULL) |
|
nc->path = xstrdup(c->path); |
|
|
|
if (nextstate != SSH_CHANNEL_DYNAMIC) |
|
port_open_helper(ssh, nc, rtype); |
} |
} |
|
|
/* |
/* |
* This is the authentication agent socket listening for connections from |
* This is the authentication agent socket listening for connections from |
* clients. |
* clients. |
*/ |
*/ |
/* ARGSUSED */ |
|
static void |
static void |
channel_post_auth_listener(Channel *c, fd_set *readset, fd_set *writeset) |
channel_post_auth_listener(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
Channel *nc; |
Channel *nc; |
int newsock; |
int r, newsock; |
struct sockaddr_storage addr; |
struct sockaddr_storage addr; |
socklen_t addrlen; |
socklen_t addrlen; |
|
|
if (FD_ISSET(c->sock, readset)) { |
if (!FD_ISSET(c->sock, readset)) |
addrlen = sizeof(addr); |
return; |
newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen); |
|
if (newsock < 0) { |
addrlen = sizeof(addr); |
error("accept from auth socket: %.100s", |
newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen); |
strerror(errno)); |
if (newsock < 0) { |
if (errno == EMFILE || errno == ENFILE) |
error("accept from auth socket: %.100s", strerror(errno)); |
c->notbefore = monotime() + 1; |
if (errno == EMFILE || errno == ENFILE) |
return; |
c->notbefore = monotime() + 1; |
} |
return; |
nc = channel_new("accepted auth socket", |
|
SSH_CHANNEL_OPENING, newsock, newsock, -1, |
|
c->local_window_max, c->local_maxpacket, |
|
0, "accepted auth socket", 1); |
|
packet_start(SSH2_MSG_CHANNEL_OPEN); |
|
packet_put_cstring("auth-agent@openssh.com"); |
|
packet_put_int(nc->self); |
|
packet_put_int(c->local_window_max); |
|
packet_put_int(c->local_maxpacket); |
|
packet_send(); |
|
} |
} |
|
nc = channel_new(ssh, "accepted auth socket", |
|
SSH_CHANNEL_OPENING, newsock, newsock, -1, |
|
c->local_window_max, c->local_maxpacket, |
|
0, "accepted auth socket", 1); |
|
open_preamble(ssh, __func__, nc, "auth-agent@openssh.com"); |
|
if ((r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: channel %i: %s", __func__, c->self, ssh_err(r)); |
} |
} |
|
|
/* ARGSUSED */ |
|
static void |
static void |
channel_post_connecting(Channel *c, fd_set *readset, fd_set *writeset) |
channel_post_connecting(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
int err = 0, sock; |
int err = 0, sock, r; |
socklen_t sz = sizeof(err); |
socklen_t sz = sizeof(err); |
|
|
if (FD_ISSET(c->sock, writeset)) { |
if (!FD_ISSET(c->sock, writeset)) |
if (getsockopt(c->sock, SOL_SOCKET, SO_ERROR, &err, &sz) < 0) { |
return; |
err = errno; |
|
error("getsockopt SO_ERROR failed"); |
if (getsockopt(c->sock, SOL_SOCKET, SO_ERROR, &err, &sz) < 0) { |
|
err = errno; |
|
error("getsockopt SO_ERROR failed"); |
|
} |
|
if (err == 0) { |
|
debug("channel %d: connected to %s port %d", |
|
c->self, c->connect_ctx.host, c->connect_ctx.port); |
|
channel_connect_ctx_free(&c->connect_ctx); |
|
c->type = SSH_CHANNEL_OPEN; |
|
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)); |
} |
} |
if (err == 0) { |
} else { |
debug("channel %d: connected to %s port %d", |
debug("channel %d: connection failed: %s", |
c->self, c->connect_ctx.host, c->connect_ctx.port); |
c->self, strerror(err)); |
channel_connect_ctx_free(&c->connect_ctx); |
/* Try next address, if any */ |
c->type = SSH_CHANNEL_OPEN; |
if ((sock = connect_next(&c->connect_ctx)) > 0) { |
packet_start(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); |
close(c->sock); |
packet_put_int(c->remote_id); |
c->sock = c->rfd = c->wfd = sock; |
packet_put_int(c->self); |
channel_find_maxfd(ssh->chanctxt); |
packet_put_int(c->local_window); |
return; |
packet_put_int(c->local_maxpacket); |
|
} else { |
|
debug("channel %d: connection failed: %s", |
|
c->self, strerror(err)); |
|
/* Try next address, if any */ |
|
if ((sock = connect_next(&c->connect_ctx)) > 0) { |
|
close(c->sock); |
|
c->sock = c->rfd = c->wfd = sock; |
|
channel_max_fd = channel_find_maxfd(); |
|
return; |
|
} |
|
/* Exhausted all addresses */ |
|
error("connect_to %.100s port %d: failed.", |
|
c->connect_ctx.host, c->connect_ctx.port); |
|
channel_connect_ctx_free(&c->connect_ctx); |
|
packet_start(SSH2_MSG_CHANNEL_OPEN_FAILURE); |
|
packet_put_int(c->remote_id); |
|
packet_put_int(SSH2_OPEN_CONNECT_FAILED); |
|
if (!(datafellows & SSH_BUG_OPENFAILURE)) { |
|
packet_put_cstring(strerror(err)); |
|
packet_put_cstring(""); |
|
} |
|
chan_mark_dead(c); |
|
} |
} |
packet_send(); |
/* Exhausted all addresses */ |
|
error("connect_to %.100s port %d: failed.", |
|
c->connect_ctx.host, c->connect_ctx.port); |
|
channel_connect_ctx_free(&c->connect_ctx); |
|
if ((r = sshpkt_start(ssh, 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)); |
|
} |
|
chan_mark_dead(ssh, c); |
} |
} |
|
if ((r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: channel %i: %s", __func__, c->self, ssh_err(r)); |
} |
} |
|
|
/* ARGSUSED */ |
|
static int |
static int |
channel_handle_rfd(Channel *c, fd_set *readset, fd_set *writeset) |
channel_handle_rfd(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
char buf[CHAN_RBUF]; |
char buf[CHAN_RBUF]; |
int len; |
ssize_t len; |
|
int r; |
|
|
if (c->rfd != -1 && |
if (c->rfd == -1 || !FD_ISSET(c->rfd, readset)) |
FD_ISSET(c->rfd, readset)) { |
return 1; |
len = read(c->rfd, buf, sizeof(buf)); |
|
if (len < 0 && (errno == EINTR || errno == EAGAIN)) |
len = read(c->rfd, buf, sizeof(buf)); |
return 1; |
if (len < 0 && (errno == EINTR || errno == EAGAIN)) |
if (len <= 0) { |
return 1; |
debug2("channel %d: read<=0 rfd %d len %d", |
if (len <= 0) { |
c->self, c->rfd, len); |
debug2("channel %d: read<=0 rfd %d len %zd", |
if (c->type != SSH_CHANNEL_OPEN) { |
c->self, c->rfd, len); |
debug2("channel %d: not open", c->self); |
if (c->type != SSH_CHANNEL_OPEN) { |
chan_mark_dead(c); |
debug2("channel %d: not open", c->self); |
return -1; |
chan_mark_dead(ssh, c); |
} else { |
|
chan_read_failed(c); |
|
} |
|
return -1; |
return -1; |
} |
|
if (c->input_filter != NULL) { |
|
if (c->input_filter(c, buf, len) == -1) { |
|
debug2("channel %d: filter stops", c->self); |
|
chan_read_failed(c); |
|
} |
|
} else if (c->datagram) { |
|
buffer_put_string(&c->input, buf, len); |
|
} else { |
} else { |
buffer_append(&c->input, buf, len); |
chan_read_failed(ssh, c); |
} |
} |
|
return -1; |
} |
} |
|
if (c->input_filter != NULL) { |
|
if (c->input_filter(ssh, c, buf, len) == -1) { |
|
debug2("channel %d: filter stops", c->self); |
|
chan_read_failed(ssh, c); |
|
} |
|
} else if (c->datagram) { |
|
if ((r = sshbuf_put_string(c->input, buf, len)) != 0) |
|
fatal("%s: channel %d: put datagram: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} else if ((r = sshbuf_put(c->input, buf, len)) != 0) { |
|
fatal("%s: channel %d: put data: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
return 1; |
return 1; |
} |
} |
|
|
/* ARGSUSED */ |
|
static int |
static int |
channel_handle_wfd(Channel *c, fd_set *readset, fd_set *writeset) |
channel_handle_wfd(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
struct termios tio; |
struct termios tio; |
u_char *data = NULL, *buf; |
u_char *data = NULL, *buf; /* XXX const; need filter API change */ |
u_int dlen, olen = 0; |
size_t dlen, olen = 0; |
int len; |
int r, len; |
|
|
|
if (c->wfd == -1 || !FD_ISSET(c->wfd, writeset) || |
|
sshbuf_len(c->output) == 0) |
|
return 1; |
|
|
/* Send buffered output data to the socket. */ |
/* Send buffered output data to the socket. */ |
if (c->wfd != -1 && |
olen = sshbuf_len(c->output); |
FD_ISSET(c->wfd, writeset) && |
if (c->output_filter != NULL) { |
buffer_len(&c->output) > 0) { |
if ((buf = c->output_filter(ssh, c, &data, &dlen)) == NULL) { |
olen = buffer_len(&c->output); |
debug2("channel %d: filter stops", c->self); |
if (c->output_filter != NULL) { |
if (c->type != SSH_CHANNEL_OPEN) |
if ((buf = c->output_filter(c, &data, &dlen)) == NULL) { |
chan_mark_dead(ssh, c); |
debug2("channel %d: filter stops", c->self); |
else |
if (c->type != SSH_CHANNEL_OPEN) |
chan_write_failed(ssh, c); |
chan_mark_dead(c); |
return -1; |
else |
|
chan_write_failed(c); |
|
return -1; |
|
} |
|
} else if (c->datagram) { |
|
buf = data = buffer_get_string(&c->output, &dlen); |
|
} else { |
|
buf = data = buffer_ptr(&c->output); |
|
dlen = buffer_len(&c->output); |
|
} |
} |
|
} else if (c->datagram) { |
|
if ((r = sshbuf_get_string(c->output, &data, &dlen)) != 0) |
|
fatal("%s: channel %d: get datagram: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} else { |
|
buf = data = sshbuf_mutable_ptr(c->output); |
|
dlen = sshbuf_len(c->output); |
|
} |
|
|
if (c->datagram) { |
if (c->datagram) { |
/* ignore truncated writes, datagrams might get lost */ |
/* ignore truncated writes, datagrams might get lost */ |
len = write(c->wfd, buf, dlen); |
len = write(c->wfd, data, dlen); |
free(data); |
free(data); |
if (len < 0 && (errno == EINTR || errno == EAGAIN)) |
|
return 1; |
|
if (len <= 0) { |
|
if (c->type != SSH_CHANNEL_OPEN) |
|
chan_mark_dead(c); |
|
else |
|
chan_write_failed(c); |
|
return -1; |
|
} |
|
goto out; |
|
} |
|
|
|
len = write(c->wfd, buf, dlen); |
|
if (len < 0 && (errno == EINTR || errno == EAGAIN)) |
if (len < 0 && (errno == EINTR || errno == EAGAIN)) |
return 1; |
return 1; |
if (len <= 0) { |
if (len <= 0) |
if (c->type != SSH_CHANNEL_OPEN) { |
goto write_fail; |
debug2("channel %d: not open", c->self); |
goto out; |
chan_mark_dead(c); |
} |
return -1; |
|
} else { |
len = write(c->wfd, buf, dlen); |
chan_write_failed(c); |
if (len < 0 && (errno == EINTR || errno == EAGAIN)) |
} |
return 1; |
|
if (len <= 0) { |
|
write_fail: |
|
if (c->type != SSH_CHANNEL_OPEN) { |
|
debug2("channel %d: not open", c->self); |
|
chan_mark_dead(ssh, c); |
return -1; |
return -1; |
|
} else { |
|
chan_write_failed(ssh, c); |
} |
} |
if (c->isatty && dlen >= 1 && buf[0] != '\r') { |
return -1; |
if (tcgetattr(c->wfd, &tio) == 0 && |
} |
!(tio.c_lflag & ECHO) && (tio.c_lflag & ICANON)) { |
if (c->isatty && dlen >= 1 && buf[0] != '\r') { |
/* |
if (tcgetattr(c->wfd, &tio) == 0 && |
* Simulate echo to reduce the impact of |
!(tio.c_lflag & ECHO) && (tio.c_lflag & ICANON)) { |
* traffic analysis. We need to match the |
/* |
* size of a SSH2_MSG_CHANNEL_DATA message |
* Simulate echo to reduce the impact of |
* (4 byte channel id + buf) |
* traffic analysis. We need to match the |
*/ |
* size of a SSH2_MSG_CHANNEL_DATA message |
packet_send_ignore(4 + len); |
* (4 byte channel id + buf) |
packet_send(); |
*/ |
} |
if ((r = sshpkt_msg_ignore(ssh, 4+len)) != 0 || |
|
(r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: channel %d: ignore: %s", |
|
__func__, c->self, ssh_err(r)); |
} |
} |
buffer_consume(&c->output, len); |
|
} |
} |
|
if ((r = sshbuf_consume(c->output, len)) != 0) { |
|
fatal("%s: channel %d: consume: %s", |
|
__func__, c->self, ssh_err(r)); |
|
} |
out: |
out: |
if (olen > 0) |
c->local_consumed += olen - sshbuf_len(c->output); |
c->local_consumed += olen - buffer_len(&c->output); |
|
return 1; |
return 1; |
} |
} |
|
|
static int |
static int |
channel_handle_efd(Channel *c, fd_set *readset, fd_set *writeset) |
channel_handle_efd_write(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
|
int r; |
|
ssize_t len; |
|
|
|
if (!FD_ISSET(c->efd, writeset) || sshbuf_len(c->extended) == 0) |
|
return 1; |
|
|
|
len = write(c->efd, sshbuf_ptr(c->extended), |
|
sshbuf_len(c->extended)); |
|
debug2("channel %d: written %zd to efd %d", c->self, len, c->efd); |
|
if (len < 0 && (errno == EINTR || errno == EAGAIN)) |
|
return 1; |
|
if (len <= 0) { |
|
debug2("channel %d: closing write-efd %d", c->self, c->efd); |
|
channel_close_fd(ssh, &c->efd); |
|
} else { |
|
if ((r = sshbuf_consume(c->extended, len)) != 0) { |
|
fatal("%s: channel %d: consume: %s", |
|
__func__, c->self, ssh_err(r)); |
|
} |
|
c->local_consumed += len; |
|
} |
|
return 1; |
|
} |
|
|
|
static int |
|
channel_handle_efd_read(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
|
{ |
char buf[CHAN_RBUF]; |
char buf[CHAN_RBUF]; |
int len; |
int r; |
|
ssize_t len; |
|
|
/** XXX handle drain efd, too */ |
if (!FD_ISSET(c->efd, readset)) |
if (c->efd != -1) { |
return 1; |
if (c->extended_usage == CHAN_EXTENDED_WRITE && |
|
FD_ISSET(c->efd, writeset) && |
len = read(c->efd, buf, sizeof(buf)); |
buffer_len(&c->extended) > 0) { |
debug2("channel %d: read %zd from efd %d", c->self, len, c->efd); |
len = write(c->efd, buffer_ptr(&c->extended), |
if (len < 0 && (errno == EINTR || errno == EAGAIN)) |
buffer_len(&c->extended)); |
return 1; |
debug2("channel %d: written %d to efd %d", |
if (len <= 0) { |
c->self, len, c->efd); |
debug2("channel %d: closing read-efd %d", |
if (len < 0 && (errno == EINTR || errno == EAGAIN)) |
c->self, c->efd); |
return 1; |
channel_close_fd(ssh, &c->efd); |
if (len <= 0) { |
} else { |
debug2("channel %d: closing write-efd %d", |
if (c->extended_usage == CHAN_EXTENDED_IGNORE) { |
c->self, c->efd); |
debug3("channel %d: discard efd", |
channel_close_fd(&c->efd); |
c->self); |
} else { |
} else if ((r = sshbuf_put(c->extended, buf, len)) != 0) { |
buffer_consume(&c->extended, len); |
fatal("%s: channel %d: append: %s", |
c->local_consumed += len; |
__func__, c->self, ssh_err(r)); |
} |
|
} else if (c->efd != -1 && |
|
(c->extended_usage == CHAN_EXTENDED_READ || |
|
c->extended_usage == CHAN_EXTENDED_IGNORE) && |
|
FD_ISSET(c->efd, readset)) { |
|
len = read(c->efd, buf, sizeof(buf)); |
|
debug2("channel %d: read %d from efd %d", |
|
c->self, len, c->efd); |
|
if (len < 0 && (errno == EINTR || errno == EAGAIN)) |
|
return 1; |
|
if (len <= 0) { |
|
debug2("channel %d: closing read-efd %d", |
|
c->self, c->efd); |
|
channel_close_fd(&c->efd); |
|
} else { |
|
if (c->extended_usage == CHAN_EXTENDED_IGNORE) { |
|
debug3("channel %d: discard efd", |
|
c->self); |
|
} else |
|
buffer_append(&c->extended, buf, len); |
|
} |
|
} |
} |
} |
} |
return 1; |
return 1; |
} |
} |
|
|
/* ARGSUSED */ |
|
static int |
static int |
channel_check_window(Channel *c) |
channel_handle_efd(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
|
if (c->efd == -1) |
|
return 1; |
|
|
|
/** XXX handle drain efd, too */ |
|
|
|
if (c->extended_usage == CHAN_EXTENDED_WRITE) |
|
return channel_handle_efd_write(ssh, c, readset, writeset); |
|
else if (c->extended_usage == CHAN_EXTENDED_READ || |
|
c->extended_usage == CHAN_EXTENDED_IGNORE) |
|
return channel_handle_efd_read(ssh, c, readset, writeset); |
|
|
|
return 1; |
|
} |
|
|
|
static int |
|
channel_check_window(struct ssh *ssh, Channel *c) |
|
{ |
|
int r; |
|
|
if (c->type == SSH_CHANNEL_OPEN && |
if (c->type == SSH_CHANNEL_OPEN && |
!(c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD)) && |
!(c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD)) && |
((c->local_window_max - c->local_window > |
((c->local_window_max - c->local_window > |
c->local_maxpacket*3) || |
c->local_maxpacket*3) || |
c->local_window < c->local_window_max/2) && |
c->local_window < c->local_window_max/2) && |
c->local_consumed > 0) { |
c->local_consumed > 0) { |
packet_start(SSH2_MSG_CHANNEL_WINDOW_ADJUST); |
if ((r = sshpkt_start(ssh, |
packet_put_int(c->remote_id); |
SSH2_MSG_CHANNEL_WINDOW_ADJUST)) != 0 || |
packet_put_int(c->local_consumed); |
(r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || |
packet_send(); |
(r = sshpkt_put_u32(ssh, c->local_consumed)) != 0 || |
|
(r = sshpkt_send(ssh)) != 0) { |
|
fatal("%s: channel %i: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
debug2("channel %d: window %d sent adjust %d", |
debug2("channel %d: window %d sent adjust %d", |
c->self, c->local_window, |
c->self, c->local_window, |
c->local_consumed); |
c->local_consumed); |
|
|
} |
} |
|
|
static void |
static void |
channel_post_open(Channel *c, fd_set *readset, fd_set *writeset) |
channel_post_open(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
channel_handle_rfd(c, readset, writeset); |
channel_handle_rfd(ssh, c, readset, writeset); |
channel_handle_wfd(c, readset, writeset); |
channel_handle_wfd(ssh, c, readset, writeset); |
channel_handle_efd(c, readset, writeset); |
channel_handle_efd(ssh, c, readset, writeset); |
channel_check_window(c); |
channel_check_window(ssh, c); |
} |
} |
|
|
static u_int |
static u_int |
read_mux(Channel *c, u_int need) |
read_mux(struct ssh *ssh, Channel *c, u_int need) |
{ |
{ |
char buf[CHAN_RBUF]; |
char buf[CHAN_RBUF]; |
int len; |
ssize_t len; |
u_int rlen; |
u_int rlen; |
|
int r; |
|
|
if (buffer_len(&c->input) < need) { |
if (sshbuf_len(c->input) < need) { |
rlen = need - buffer_len(&c->input); |
rlen = need - sshbuf_len(c->input); |
len = read(c->rfd, buf, MINIMUM(rlen, CHAN_RBUF)); |
len = read(c->rfd, buf, MINIMUM(rlen, CHAN_RBUF)); |
if (len < 0 && (errno == EINTR || errno == EAGAIN)) |
if (len < 0 && (errno == EINTR || errno == EAGAIN)) |
return buffer_len(&c->input); |
return sshbuf_len(c->input); |
if (len <= 0) { |
if (len <= 0) { |
debug2("channel %d: ctl read<=0 rfd %d len %d", |
debug2("channel %d: ctl read<=0 rfd %d len %zd", |
c->self, c->rfd, len); |
c->self, c->rfd, len); |
chan_read_failed(c); |
chan_read_failed(ssh, c); |
return 0; |
return 0; |
} else |
} else if ((r = sshbuf_put(c->input, buf, len)) != 0) { |
buffer_append(&c->input, buf, len); |
fatal("%s: channel %d: append: %s", |
|
__func__, c->self, ssh_err(r)); |
|
} |
} |
} |
return buffer_len(&c->input); |
return sshbuf_len(c->input); |
} |
} |
|
|
static void |
static void |
channel_post_mux_client(Channel *c, fd_set *readset, fd_set *writeset) |
channel_post_mux_client_read(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
u_int need; |
u_int need; |
ssize_t len; |
|
|
|
if (c->rfd != -1 && !c->mux_pause && FD_ISSET(c->rfd, readset) && |
if (c->rfd == -1 || !FD_ISSET(c->rfd, readset)) |
(c->istate == CHAN_INPUT_OPEN || |
return; |
c->istate == CHAN_INPUT_WAIT_DRAIN)) { |
if (c->istate != CHAN_INPUT_OPEN && c->istate != CHAN_INPUT_WAIT_DRAIN) |
/* |
return; |
* Don't not read past the precise end of packets to |
if (c->mux_pause) |
* avoid disrupting fd passing. |
return; |
*/ |
|
if (read_mux(c, 4) < 4) /* read header */ |
/* |
return; |
* Don't not read past the precise end of packets to |
need = get_u32(buffer_ptr(&c->input)); |
* avoid disrupting fd passing. |
|
*/ |
|
if (read_mux(ssh, c, 4) < 4) /* read header */ |
|
return; |
|
/* XXX sshbuf_peek_u32 */ |
|
need = PEEK_U32(sshbuf_ptr(c->input)); |
#define CHANNEL_MUX_MAX_PACKET (256 * 1024) |
#define CHANNEL_MUX_MAX_PACKET (256 * 1024) |
if (need > CHANNEL_MUX_MAX_PACKET) { |
if (need > CHANNEL_MUX_MAX_PACKET) { |
debug2("channel %d: packet too big %u > %u", |
debug2("channel %d: packet too big %u > %u", |
c->self, CHANNEL_MUX_MAX_PACKET, need); |
c->self, CHANNEL_MUX_MAX_PACKET, need); |
chan_rcvd_oclose(c); |
chan_rcvd_oclose(ssh, c); |
return; |
return; |
} |
|
if (read_mux(c, need + 4) < need + 4) /* read body */ |
|
return; |
|
if (c->mux_rcb(c) != 0) { |
|
debug("channel %d: mux_rcb failed", c->self); |
|
chan_mark_dead(c); |
|
return; |
|
} |
|
} |
} |
|
if (read_mux(ssh, c, need + 4) < need + 4) /* read body */ |
|
return; |
|
if (c->mux_rcb(ssh, c) != 0) { |
|
debug("channel %d: mux_rcb failed", c->self); |
|
chan_mark_dead(ssh, c); |
|
return; |
|
} |
|
} |
|
|
if (c->wfd != -1 && FD_ISSET(c->wfd, writeset) && |
static void |
buffer_len(&c->output) > 0) { |
channel_post_mux_client_write(struct ssh *ssh, Channel *c, |
len = write(c->wfd, buffer_ptr(&c->output), |
fd_set *readset, fd_set *writeset) |
buffer_len(&c->output)); |
{ |
if (len < 0 && (errno == EINTR || errno == EAGAIN)) |
ssize_t len; |
return; |
int r; |
if (len <= 0) { |
|
chan_mark_dead(c); |
if (c->wfd == -1 || !FD_ISSET(c->wfd, writeset) || |
return; |
sshbuf_len(c->output) == 0) |
} |
return; |
buffer_consume(&c->output, len); |
|
|
len = write(c->wfd, sshbuf_ptr(c->output), sshbuf_len(c->output)); |
|
if (len < 0 && (errno == EINTR || errno == EAGAIN)) |
|
return; |
|
if (len <= 0) { |
|
chan_mark_dead(ssh, c); |
|
return; |
} |
} |
|
if ((r = sshbuf_consume(c->output, len)) != 0) |
|
fatal("%s: channel %d: consume: %s", __func__, |
|
c->self, ssh_err(r)); |
} |
} |
|
|
static void |
static void |
channel_post_mux_listener(Channel *c, fd_set *readset, fd_set *writeset) |
channel_post_mux_client(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
{ |
{ |
|
channel_post_mux_client_read(ssh, c, readset, writeset); |
|
channel_post_mux_client_write(ssh, c, readset, writeset); |
|
} |
|
|
|
static void |
|
channel_post_mux_listener(struct ssh *ssh, Channel *c, |
|
fd_set *readset, fd_set *writeset) |
|
{ |
Channel *nc; |
Channel *nc; |
struct sockaddr_storage addr; |
struct sockaddr_storage addr; |
socklen_t addrlen; |
socklen_t addrlen; |
|
|
close(newsock); |
close(newsock); |
return; |
return; |
} |
} |
nc = channel_new("multiplex client", SSH_CHANNEL_MUX_CLIENT, |
nc = channel_new(ssh, "multiplex client", SSH_CHANNEL_MUX_CLIENT, |
newsock, newsock, -1, c->local_window_max, |
newsock, newsock, -1, c->local_window_max, |
c->local_maxpacket, 0, "mux-control", 1); |
c->local_maxpacket, 0, "mux-control", 1); |
nc->mux_rcb = c->mux_rcb; |
nc->mux_rcb = c->mux_rcb; |
debug3("%s: new mux channel %d fd %d", __func__, |
debug3("%s: new mux channel %d fd %d", __func__, nc->self, nc->sock); |
nc->self, nc->sock); |
|
/* establish state */ |
/* establish state */ |
nc->mux_rcb(nc); |
nc->mux_rcb(ssh, nc); |
/* mux state transitions must not elicit protocol messages */ |
/* mux state transitions must not elicit protocol messages */ |
nc->flags |= CHAN_LOCAL; |
nc->flags |= CHAN_LOCAL; |
} |
} |
|
|
static void |
static void |
channel_handler_init(void) |
channel_handler_init(struct ssh_channels *sc) |
{ |
{ |
int i; |
chan_fn **pre, **post; |
|
|
for (i = 0; i < SSH_CHANNEL_MAX_TYPE; i++) { |
if ((pre = calloc(SSH_CHANNEL_MAX_TYPE, sizeof(*pre))) == NULL || |
channel_pre[i] = NULL; |
(post = calloc(SSH_CHANNEL_MAX_TYPE, sizeof(*post))) == NULL) |
channel_post[i] = NULL; |
fatal("%s: allocation failed", __func__); |
} |
|
channel_pre[SSH_CHANNEL_OPEN] = &channel_pre_open; |
|
channel_pre[SSH_CHANNEL_X11_OPEN] = &channel_pre_x11_open; |
|
channel_pre[SSH_CHANNEL_PORT_LISTENER] = &channel_pre_listener; |
|
channel_pre[SSH_CHANNEL_RPORT_LISTENER] = &channel_pre_listener; |
|
channel_pre[SSH_CHANNEL_UNIX_LISTENER] = &channel_pre_listener; |
|
channel_pre[SSH_CHANNEL_RUNIX_LISTENER] = &channel_pre_listener; |
|
channel_pre[SSH_CHANNEL_X11_LISTENER] = &channel_pre_listener; |
|
channel_pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; |
|
channel_pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; |
|
channel_pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; |
|
channel_pre[SSH_CHANNEL_MUX_LISTENER] = &channel_pre_listener; |
|
channel_pre[SSH_CHANNEL_MUX_CLIENT] = &channel_pre_mux_client; |
|
|
|
channel_post[SSH_CHANNEL_OPEN] = &channel_post_open; |
pre[SSH_CHANNEL_OPEN] = &channel_pre_open; |
channel_post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener; |
pre[SSH_CHANNEL_X11_OPEN] = &channel_pre_x11_open; |
channel_post[SSH_CHANNEL_RPORT_LISTENER] = &channel_post_port_listener; |
pre[SSH_CHANNEL_PORT_LISTENER] = &channel_pre_listener; |
channel_post[SSH_CHANNEL_UNIX_LISTENER] = &channel_post_port_listener; |
pre[SSH_CHANNEL_RPORT_LISTENER] = &channel_pre_listener; |
channel_post[SSH_CHANNEL_RUNIX_LISTENER] = &channel_post_port_listener; |
pre[SSH_CHANNEL_UNIX_LISTENER] = &channel_pre_listener; |
channel_post[SSH_CHANNEL_X11_LISTENER] = &channel_post_x11_listener; |
pre[SSH_CHANNEL_RUNIX_LISTENER] = &channel_pre_listener; |
channel_post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; |
pre[SSH_CHANNEL_X11_LISTENER] = &channel_pre_listener; |
channel_post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; |
pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; |
channel_post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; |
pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; |
channel_post[SSH_CHANNEL_MUX_LISTENER] = &channel_post_mux_listener; |
pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; |
channel_post[SSH_CHANNEL_MUX_CLIENT] = &channel_post_mux_client; |
pre[SSH_CHANNEL_MUX_LISTENER] = &channel_pre_listener; |
|
pre[SSH_CHANNEL_MUX_CLIENT] = &channel_pre_mux_client; |
|
|
|
post[SSH_CHANNEL_OPEN] = &channel_post_open; |
|
post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener; |
|
post[SSH_CHANNEL_RPORT_LISTENER] = &channel_post_port_listener; |
|
post[SSH_CHANNEL_UNIX_LISTENER] = &channel_post_port_listener; |
|
post[SSH_CHANNEL_RUNIX_LISTENER] = &channel_post_port_listener; |
|
post[SSH_CHANNEL_X11_LISTENER] = &channel_post_x11_listener; |
|
post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; |
|
post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; |
|
post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; |
|
post[SSH_CHANNEL_MUX_LISTENER] = &channel_post_mux_listener; |
|
post[SSH_CHANNEL_MUX_CLIENT] = &channel_post_mux_client; |
|
|
|
sc->channel_pre = pre; |
|
sc->channel_post = post; |
} |
} |
|
|
/* gc dead channels */ |
/* gc dead channels */ |
static void |
static void |
channel_garbage_collect(Channel *c) |
channel_garbage_collect(struct ssh *ssh, Channel *c) |
{ |
{ |
if (c == NULL) |
if (c == NULL) |
return; |
return; |
if (c->detach_user != NULL) { |
if (c->detach_user != NULL) { |
if (!chan_is_dead(c, c->detach_close)) |
if (!chan_is_dead(ssh, c, c->detach_close)) |
return; |
return; |
debug2("channel %d: gc: notify user", c->self); |
debug2("channel %d: gc: notify user", c->self); |
c->detach_user(c->self, NULL); |
c->detach_user(ssh, c->self, NULL); |
/* if we still have a callback */ |
/* if we still have a callback */ |
if (c->detach_user != NULL) |
if (c->detach_user != NULL) |
return; |
return; |
debug2("channel %d: gc: user detached", c->self); |
debug2("channel %d: gc: user detached", c->self); |
} |
} |
if (!chan_is_dead(c, 1)) |
if (!chan_is_dead(ssh, c, 1)) |
return; |
return; |
debug2("channel %d: garbage collecting", c->self); |
debug2("channel %d: garbage collecting", c->self); |
channel_free(c); |
channel_free(ssh, c); |
} |
} |
|
|
|
enum channel_table { CHAN_PRE, CHAN_POST }; |
|
|
static void |
static void |
channel_handler(struct ssh *ssh, chan_fn *ftab[], |
channel_handler(struct ssh *ssh, int table, |
fd_set *readset, fd_set *writeset, time_t *unpause_secs) |
fd_set *readset, fd_set *writeset, time_t *unpause_secs) |
{ |
{ |
static int did_init = 0; |
struct ssh_channels *sc = ssh->chanctxt; |
|
chan_fn **ftab = table == CHAN_PRE ? sc->channel_pre : sc->channel_post; |
u_int i, oalloc; |
u_int i, oalloc; |
Channel *c; |
Channel *c; |
time_t now; |
time_t now; |
|
|
if (!did_init) { |
|
channel_handler_init(); |
|
did_init = 1; |
|
} |
|
now = monotime(); |
now = monotime(); |
if (unpause_secs != NULL) |
if (unpause_secs != NULL) |
*unpause_secs = 0; |
*unpause_secs = 0; |
for (i = 0, oalloc = channels_alloc; i < oalloc; i++) { |
for (i = 0, oalloc = sc->channels_alloc; i < oalloc; i++) { |
c = channels[i]; |
c = sc->channels[i]; |
if (c == NULL) |
if (c == NULL) |
continue; |
continue; |
if (c->delayed) { |
if (c->delayed) { |
if (ftab == channel_pre) |
if (table == CHAN_PRE) |
c->delayed = 0; |
c->delayed = 0; |
else |
else |
continue; |
continue; |
|
|
* Run handlers that are not paused. |
* Run handlers that are not paused. |
*/ |
*/ |
if (c->notbefore <= now) |
if (c->notbefore <= now) |
(*ftab[c->type])(c, readset, writeset); |
(*ftab[c->type])(ssh, c, readset, writeset); |
else if (unpause_secs != NULL) { |
else if (unpause_secs != NULL) { |
/* |
/* |
* Collect the time that the earliest |
* Collect the time that the earliest |
|
|
*unpause_secs = c->notbefore - now; |
*unpause_secs = c->notbefore - now; |
} |
} |
} |
} |
channel_garbage_collect(c); |
channel_garbage_collect(ssh, c); |
} |
} |
if (unpause_secs != NULL && *unpause_secs != 0) |
if (unpause_secs != NULL && *unpause_secs != 0) |
debug3("%s: first channel unpauses in %d seconds", |
debug3("%s: first channel unpauses in %d seconds", |
|
|
{ |
{ |
u_int n, sz, nfdset; |
u_int n, sz, nfdset; |
|
|
n = MAXIMUM(*maxfdp, channel_max_fd); |
n = MAXIMUM(*maxfdp, ssh->chanctxt->channel_max_fd); |
|
|
nfdset = howmany(n+1, NFDBITS); |
nfdset = howmany(n+1, NFDBITS); |
/* Explicitly test here, because xrealloc isn't always called */ |
/* Explicitly test here, because xrealloc isn't always called */ |
|
|
memset(*writesetp, 0, sz); |
memset(*writesetp, 0, sz); |
|
|
if (!ssh_packet_is_rekeying(ssh)) |
if (!ssh_packet_is_rekeying(ssh)) |
channel_handler(ssh, channel_pre, *readsetp, *writesetp, |
channel_handler(ssh, CHAN_PRE, *readsetp, *writesetp, |
minwait_secs); |
minwait_secs); |
} |
} |
|
|
|
|
void |
void |
channel_after_select(struct ssh *ssh, fd_set *readset, fd_set *writeset) |
channel_after_select(struct ssh *ssh, fd_set *readset, fd_set *writeset) |
{ |
{ |
channel_handler(ssh, channel_post, readset, writeset, NULL); |
channel_handler(ssh, CHAN_POST, readset, writeset, NULL); |
} |
} |
|
|
|
/* |
|
* Enqueue data for channels with open or draining c->input. |
|
*/ |
|
static void |
|
channel_output_poll_input_open(struct ssh *ssh, Channel *c) |
|
{ |
|
size_t len, dlen; |
|
int r; |
|
|
|
if ((len = sshbuf_len(c->input)) == 0) { |
|
if (c->istate == CHAN_INPUT_WAIT_DRAIN) { |
|
/* |
|
* input-buffer is empty and read-socket shutdown: |
|
* tell peer, that we will not send more data: |
|
* send IEOF. |
|
* hack for extended data: delay EOF if EFD still |
|
* in use. |
|
*/ |
|
if (CHANNEL_EFD_INPUT_ACTIVE(c)) |
|
debug2("channel %d: " |
|
"ibuf_empty delayed efd %d/(%zu)", |
|
c->self, c->efd, sshbuf_len(c->extended)); |
|
else |
|
chan_ibuf_empty(ssh, c); |
|
} |
|
return; |
|
} |
|
|
|
if (c->datagram) { |
|
/* Check datagram will fit; drop if not */ |
|
if ((r = sshbuf_peek_string_direct(c->input, NULL, &dlen)) != 0) |
|
fatal("%s: channel %d: peek datagram: %s", __func__, |
|
c->self, ssh_err(r)); |
|
/* |
|
* XXX this does tail-drop on the datagram queue which is |
|
* usually suboptimal compared to head-drop. Better to have |
|
* backpressure at read time? (i.e. read + discard) |
|
*/ |
|
if (dlen > c->remote_window || dlen > c->remote_maxpacket) { |
|
debug("channel %d: datagram too big", c->self); |
|
return; |
|
} |
|
/* Enqueue it */ |
|
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: datagram: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
|
c->remote_window -= dlen; |
|
return; |
|
} |
|
|
|
/* Enqueue packet for buffered data. */ |
|
if (len > c->remote_window) |
|
len = c->remote_window; |
|
if (len > c->remote_maxpacket) |
|
len = c->remote_maxpacket; |
|
if (len == 0) |
|
return; |
|
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_DATA)) != 0 || |
|
(r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || |
|
(r = sshpkt_put_string(ssh, sshbuf_ptr(c->input), len)) != 0 || |
|
(r = sshpkt_send(ssh)) != 0) { |
|
fatal("%s: channel %i: data: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
|
if ((r = sshbuf_consume(c->input, len)) != 0) |
|
fatal("%s: channel %i: consume: %s", __func__, |
|
c->self, ssh_err(r)); |
|
c->remote_window -= len; |
|
} |
|
|
|
/* |
|
* Enqueue data for channels with open c->extended in read mode. |
|
*/ |
|
static void |
|
channel_output_poll_extended_read(struct ssh *ssh, Channel *c) |
|
{ |
|
size_t len; |
|
int r; |
|
|
|
if ((len = sshbuf_len(c->extended)) == 0) |
|
return; |
|
|
|
debug2("channel %d: rwin %u elen %zu euse %d", c->self, |
|
c->remote_window, sshbuf_len(c->extended), c->extended_usage); |
|
if (len > c->remote_window) |
|
len = c->remote_window; |
|
if (len > c->remote_maxpacket) |
|
len = c->remote_maxpacket; |
|
if (len == 0) |
|
return; |
|
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_EXTENDED_DATA)) != 0 || |
|
(r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || |
|
(r = sshpkt_put_u32(ssh, SSH2_EXTENDED_DATA_STDERR)) != 0 || |
|
(r = sshpkt_put_string(ssh, sshbuf_ptr(c->extended), len)) != 0 || |
|
(r = sshpkt_send(ssh)) != 0) { |
|
fatal("%s: channel %i: data: %s", __func__, |
|
c->self, ssh_err(r)); |
|
} |
|
if ((r = sshbuf_consume(c->extended, len)) != 0) |
|
fatal("%s: channel %i: consume: %s", __func__, |
|
c->self, ssh_err(r)); |
|
c->remote_window -= len; |
|
debug2("channel %d: sent ext data %zu", c->self, len); |
|
} |
|
|
/* If there is data to send to the connection, enqueue some of it now. */ |
/* If there is data to send to the connection, enqueue some of it now. */ |
void |
void |
channel_output_poll(void) |
channel_output_poll(struct ssh *ssh) |
{ |
{ |
|
struct ssh_channels *sc = ssh->chanctxt; |
Channel *c; |
Channel *c; |
u_int i, len; |
u_int i; |
|
|
for (i = 0; i < channels_alloc; i++) { |
for (i = 0; i < sc->channels_alloc; i++) { |
c = channels[i]; |
c = sc->channels[i]; |
if (c == NULL) |
if (c == NULL) |
continue; |
continue; |
|
|
|
|
continue; |
continue; |
if ((c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD))) { |
if ((c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD))) { |
/* XXX is this true? */ |
/* XXX is this true? */ |
debug3("channel %d: will not send data after close", c->self); |
debug3("channel %d: will not send data after close", |
|
c->self); |
continue; |
continue; |
} |
} |
|
|
/* Get the amount of buffered data for this channel. */ |
/* Get the amount of buffered data for this channel. */ |
if ((c->istate == CHAN_INPUT_OPEN || |
if (c->istate == CHAN_INPUT_OPEN || |
c->istate == CHAN_INPUT_WAIT_DRAIN) && |
c->istate == CHAN_INPUT_WAIT_DRAIN) |
(len = buffer_len(&c->input)) > 0) { |
channel_output_poll_input_open(ssh, c); |
if (c->datagram) { |
|
if (len > 0) { |
|
u_char *data; |
|
u_int dlen; |
|
|
|
data = buffer_get_string(&c->input, |
|
&dlen); |
|
if (dlen > c->remote_window || |
|
dlen > c->remote_maxpacket) { |
|
debug("channel %d: datagram " |
|
"too big for channel", |
|
c->self); |
|
free(data); |
|
continue; |
|
} |
|
packet_start(SSH2_MSG_CHANNEL_DATA); |
|
packet_put_int(c->remote_id); |
|
packet_put_string(data, dlen); |
|
packet_send(); |
|
c->remote_window -= dlen; |
|
free(data); |
|
} |
|
continue; |
|
} |
|
/* |
|
* Send some data for the other side over the secure |
|
* connection. |
|
*/ |
|
if (len > c->remote_window) |
|
len = c->remote_window; |
|
if (len > c->remote_maxpacket) |
|
len = c->remote_maxpacket; |
|
if (len > 0) { |
|
packet_start(SSH2_MSG_CHANNEL_DATA); |
|
packet_put_int(c->remote_id); |
|
packet_put_string(buffer_ptr(&c->input), len); |
|
packet_send(); |
|
buffer_consume(&c->input, len); |
|
c->remote_window -= len; |
|
} |
|
} else if (c->istate == CHAN_INPUT_WAIT_DRAIN) { |
|
/* |
|
* input-buffer is empty and read-socket shutdown: |
|
* tell peer, that we will not send more data: send IEOF. |
|
* hack for extended data: delay EOF if EFD still in use. |
|
*/ |
|
if (CHANNEL_EFD_INPUT_ACTIVE(c)) |
|
debug2("channel %d: ibuf_empty delayed efd %d/(%d)", |
|
c->self, c->efd, buffer_len(&c->extended)); |
|
else |
|
chan_ibuf_empty(c); |
|
} |
|
/* Send extended data, i.e. stderr */ |
/* Send extended data, i.e. stderr */ |
if (!(c->flags & CHAN_EOF_SENT) && |
if (!(c->flags & CHAN_EOF_SENT) && |
c->remote_window > 0 && |
c->extended_usage == CHAN_EXTENDED_READ) |
(len = buffer_len(&c->extended)) > 0 && |
channel_output_poll_extended_read(ssh, c); |
c->extended_usage == CHAN_EXTENDED_READ) { |
|
debug2("channel %d: rwin %u elen %u euse %d", |
|
c->self, c->remote_window, buffer_len(&c->extended), |
|
c->extended_usage); |
|
if (len > c->remote_window) |
|
len = c->remote_window; |
|
if (len > c->remote_maxpacket) |
|
len = c->remote_maxpacket; |
|
packet_start(SSH2_MSG_CHANNEL_EXTENDED_DATA); |
|
packet_put_int(c->remote_id); |
|
packet_put_int(SSH2_EXTENDED_DATA_STDERR); |
|
packet_put_string(buffer_ptr(&c->extended), len); |
|
packet_send(); |
|
buffer_consume(&c->extended, len); |
|
c->remote_window -= len; |
|
debug2("channel %d: sent ext data %d", c->self, len); |
|
} |
|
} |
} |
} |
} |
|
|
|
|
* on channel creation. |
* on channel creation. |
*/ |
*/ |
int |
int |
channel_proxy_downstream(Channel *downstream) |
channel_proxy_downstream(struct ssh *ssh, Channel *downstream) |
{ |
{ |
Channel *c = NULL; |
Channel *c = NULL; |
struct ssh *ssh = active_state; |
|
struct sshbuf *original = NULL, *modified = NULL; |
struct sshbuf *original = NULL, *modified = NULL; |
const u_char *cp; |
const u_char *cp; |
char *ctype = NULL, *listen_host = NULL; |
char *ctype = NULL, *listen_host = NULL; |
|
|
int ret = -1, r, idx; |
int ret = -1, r, idx; |
u_int id, remote_id, listen_port; |
u_int id, remote_id, listen_port; |
|
|
/* sshbuf_dump(&downstream->input, stderr); */ |
/* sshbuf_dump(downstream->input, stderr); */ |
if ((r = sshbuf_get_string_direct(&downstream->input, &cp, &have)) |
if ((r = sshbuf_get_string_direct(downstream->input, &cp, &have)) |
!= 0) { |
!= 0) { |
error("%s: malformed message: %s", __func__, ssh_err(r)); |
error("%s: malformed message: %s", __func__, ssh_err(r)); |
return -1; |
return -1; |
|
|
error("%s: parse error %s", __func__, ssh_err(r)); |
error("%s: parse error %s", __func__, ssh_err(r)); |
goto out; |
goto out; |
} |
} |
c = channel_new("mux proxy", SSH_CHANNEL_MUX_PROXY, |
c = channel_new(ssh, "mux proxy", SSH_CHANNEL_MUX_PROXY, |
-1, -1, -1, 0, 0, 0, ctype, 1); |
-1, -1, -1, 0, 0, 0, ctype, 1); |
c->mux_ctx = downstream; /* point to mux client */ |
c->mux_ctx = downstream; /* point to mux client */ |
c->mux_downstream_id = id; /* original downstream id */ |
c->mux_downstream_id = id; /* original downstream id */ |
|
|
(r = sshbuf_put_u32(modified, c->self)) != 0 || |
(r = sshbuf_put_u32(modified, c->self)) != 0 || |
(r = sshbuf_putb(modified, original)) != 0) { |
(r = sshbuf_putb(modified, original)) != 0) { |
error("%s: compose error %s", __func__, ssh_err(r)); |
error("%s: compose error %s", __func__, ssh_err(r)); |
channel_free(c); |
channel_free(ssh, c); |
goto out; |
goto out; |
} |
} |
break; |
break; |
|
|
error("%s: parse error %s", __func__, ssh_err(r)); |
error("%s: parse error %s", __func__, ssh_err(r)); |
goto out; |
goto out; |
} |
} |
c = channel_new("mux proxy", SSH_CHANNEL_MUX_PROXY, |
c = channel_new(ssh, "mux proxy", SSH_CHANNEL_MUX_PROXY, |
-1, -1, -1, 0, 0, 0, "mux-down-connect", 1); |
-1, -1, -1, 0, 0, 0, "mux-down-connect", 1); |
c->mux_ctx = downstream; /* point to mux client */ |
c->mux_ctx = downstream; /* point to mux client */ |
c->mux_downstream_id = id; |
c->mux_downstream_id = id; |
|
|
(r = sshbuf_put_u32(modified, c->self)) != 0 || |
(r = sshbuf_put_u32(modified, c->self)) != 0 || |
(r = sshbuf_putb(modified, original)) != 0) { |
(r = sshbuf_putb(modified, original)) != 0) { |
error("%s: compose error %s", __func__, ssh_err(r)); |
error("%s: compose error %s", __func__, ssh_err(r)); |
channel_free(c); |
channel_free(ssh, c); |
goto out; |
goto out; |
} |
} |
break; |
break; |
|
|
goto out; |
goto out; |
} |
} |
/* Record that connection to this host/port is permitted. */ |
/* Record that connection to this host/port is permitted. */ |
permitted_opens = xreallocarray(permitted_opens, |
idx = fwd_perm_list_add(ssh, FWDPERM_USER, "<mux>", -1, |
num_permitted_opens + 1, sizeof(*permitted_opens)); |
listen_host, NULL, (int)listen_port, downstream); |
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 = (int)listen_port; |
|
permitted_opens[idx].downstream = downstream; |
|
listen_host = NULL; |
listen_host = NULL; |
break; |
break; |
case SSH2_MSG_CHANNEL_CLOSE: |
case SSH2_MSG_CHANNEL_CLOSE: |
if (have < 4) |
if (have < 4) |
break; |
break; |
remote_id = PEEK_U32(cp); |
remote_id = PEEK_U32(cp); |
if ((c = channel_by_remote_id(remote_id)) != NULL) { |
if ((c = channel_by_remote_id(ssh, remote_id)) != NULL) { |
if (c->flags & CHAN_CLOSE_RCVD) |
if (c->flags & CHAN_CLOSE_RCVD) |
channel_free(c); |
channel_free(ssh, c); |
else |
else |
c->flags |= CHAN_CLOSE_SENT; |
c->flags |= CHAN_CLOSE_SENT; |
} |
} |
|
|
(r = sshbuf_put_u8(b, type)) != 0 || |
(r = sshbuf_put_u8(b, type)) != 0 || |
(r = sshbuf_put_u32(b, c->mux_downstream_id)) != 0 || |
(r = sshbuf_put_u32(b, c->mux_downstream_id)) != 0 || |
(r = sshbuf_put(b, cp, len)) != 0 || |
(r = sshbuf_put(b, cp, len)) != 0 || |
(r = sshbuf_put_stringb(&downstream->output, b)) != 0) { |
(r = sshbuf_put_stringb(downstream->output, b)) != 0) { |
error("%s: compose for muxclient %s", __func__, ssh_err(r)); |
error("%s: compose for muxclient %s", __func__, ssh_err(r)); |
goto out; |
goto out; |
} |
} |
|
|
break; |
break; |
case SSH2_MSG_CHANNEL_CLOSE: |
case SSH2_MSG_CHANNEL_CLOSE: |
if (c->flags & CHAN_CLOSE_SENT) |
if (c->flags & CHAN_CLOSE_SENT) |
channel_free(c); |
channel_free(ssh, c); |
else |
else |
c->flags |= CHAN_CLOSE_RCVD; |
c->flags |= CHAN_CLOSE_RCVD; |
break; |
break; |
|
|
|
|
/* -- protocol input */ |
/* -- protocol input */ |
|
|
/* ARGSUSED */ |
/* Parse a channel ID from the current packet */ |
|
static int |
|
channel_parse_id(struct ssh *ssh, const char *where, const char *what) |
|
{ |
|
u_int32_t id; |
|
int r; |
|
|
|
if ((r = sshpkt_get_u32(ssh, &id)) != 0) { |
|
error("%s: parse id: %s", where, ssh_err(r)); |
|
ssh_packet_disconnect(ssh, "Invalid %s message", what); |
|
} |
|
if (id > INT_MAX) { |
|
error("%s: bad channel id %u: %s", where, id, ssh_err(r)); |
|
ssh_packet_disconnect(ssh, "Invalid %s channel id", what); |
|
} |
|
return (int)id; |
|
} |
|
|
|
/* Lookup a channel from an ID in the current packet */ |
|
static Channel * |
|
channel_from_packet_id(struct ssh *ssh, const char *where, const char *what) |
|
{ |
|
int id = channel_parse_id(ssh, where, what); |
|
Channel *c; |
|
|
|
if ((c = channel_lookup(ssh, id)) == NULL) { |
|
ssh_packet_disconnect(ssh, |
|
"%s packet referred to nonexistent channel %d", what, id); |
|
} |
|
return c; |
|
} |
|
|
int |
int |
channel_input_data(int type, u_int32_t seq, struct ssh *ssh) |
channel_input_data(int type, u_int32_t seq, struct ssh *ssh) |
{ |
{ |
int id; |
|
const u_char *data; |
const u_char *data; |
u_int data_len, win_len; |
size_t data_len, win_len; |
Channel *c; |
Channel *c = channel_from_packet_id(ssh, __func__, "data"); |
|
int r; |
|
|
/* Get the channel number and verify it. */ |
|
id = packet_get_int(); |
|
c = channel_lookup(id); |
|
if (c == NULL) |
|
packet_disconnect("Received data for nonexistent channel %d.", id); |
|
if (channel_proxy_upstream(c, type, seq, ssh)) |
if (channel_proxy_upstream(c, type, seq, ssh)) |
return 0; |
return 0; |
|
|
|
|
return 0; |
return 0; |
|
|
/* Get the data. */ |
/* Get the data. */ |
data = packet_get_string_ptr(&data_len); |
if ((r = sshpkt_get_string_direct(ssh, &data, &data_len)) != 0) |
|
fatal("%s: channel %d: get data: %s", __func__, |
|
c->self, ssh_err(r)); |
|
ssh_packet_check_eom(ssh); |
|
|
win_len = data_len; |
win_len = data_len; |
if (c->datagram) |
if (c->datagram) |
win_len += 4; /* string length header */ |
win_len += 4; /* string length header */ |
|
|
/* |
/* |
* Ignore data for protocol > 1.3 if output end is no longer open. |
* The sending side reduces its window as it sends data, so we |
* For protocol 2 the sending side is reducing its window as it sends |
* must 'fake' consumption of the data in order to ensure that window |
* data, so we must 'fake' consumption of the data in order to ensure |
* updates are sent back. Otherwise the connection might deadlock. |
* that window updates are sent back. Otherwise the connection might |
|
* deadlock. |
|
*/ |
*/ |
if (c->ostate != CHAN_OUTPUT_OPEN) { |
if (c->ostate != CHAN_OUTPUT_OPEN) { |
c->local_window -= win_len; |
c->local_window -= win_len; |
|
|
} |
} |
|
|
if (win_len > c->local_maxpacket) { |
if (win_len > c->local_maxpacket) { |
logit("channel %d: rcvd big packet %d, maxpack %d", |
logit("channel %d: rcvd big packet %zu, maxpack %u", |
c->self, win_len, c->local_maxpacket); |
c->self, win_len, c->local_maxpacket); |
|
return 0; |
} |
} |
if (win_len > c->local_window) { |
if (win_len > c->local_window) { |
logit("channel %d: rcvd too much data %d, win %d", |
logit("channel %d: rcvd too much data %zu, win %u", |
c->self, win_len, c->local_window); |
c->self, win_len, c->local_window); |
return 0; |
return 0; |
} |
} |
c->local_window -= win_len; |
c->local_window -= win_len; |
|
|
if (c->datagram) |
if (c->datagram) { |
buffer_put_string(&c->output, data, data_len); |
if ((r = sshbuf_put_string(c->output, data, data_len)) != 0) |
else |
fatal("%s: channel %d: append datagram: %s", |
buffer_append(&c->output, data, data_len); |
__func__, c->self, ssh_err(r)); |
packet_check_eom(); |
} else if ((r = sshbuf_put(c->output, data, data_len)) != 0) |
|
fatal("%s: channel %d: append data: %s", |
|
__func__, c->self, ssh_err(r)); |
|
|
return 0; |
return 0; |
} |
} |
|
|
/* ARGSUSED */ |
|
int |
int |
channel_input_extended_data(int type, u_int32_t seq, struct ssh *ssh) |
channel_input_extended_data(int type, u_int32_t seq, struct ssh *ssh) |
{ |
{ |
int id; |
const u_char *data; |
char *data; |
size_t data_len; |
u_int data_len, tcode; |
u_int32_t tcode; |
Channel *c; |
Channel *c = channel_from_packet_id(ssh, __func__, "extended data"); |
|
int r; |
|
|
/* Get the channel number and verify it. */ |
|
id = packet_get_int(); |
|
c = channel_lookup(id); |
|
|
|
if (c == NULL) |
|
packet_disconnect("Received extended_data for bad channel %d.", id); |
|
if (channel_proxy_upstream(c, type, seq, ssh)) |
if (channel_proxy_upstream(c, type, seq, ssh)) |
return 0; |
return 0; |
if (c->type != SSH_CHANNEL_OPEN) { |
if (c->type != SSH_CHANNEL_OPEN) { |
logit("channel %d: ext data for non open", id); |
logit("channel %d: ext data for non open", c->self); |
return 0; |
return 0; |
} |
} |
if (c->flags & CHAN_EOF_RCVD) { |
if (c->flags & CHAN_EOF_RCVD) { |
if (datafellows & SSH_BUG_EXTEOF) |
if (datafellows & SSH_BUG_EXTEOF) |
debug("channel %d: accepting ext data after eof", id); |
debug("channel %d: accepting ext data after eof", |
|
c->self); |
else |
else |
packet_disconnect("Received extended_data after EOF " |
ssh_packet_disconnect(ssh, "Received extended_data " |
"on channel %d.", id); |
"after EOF on channel %d.", c->self); |
} |
} |
tcode = packet_get_int(); |
|
|
if ((r = sshpkt_get_u32(ssh, &tcode)) != 0) { |
|
error("%s: parse tcode: %s", __func__, ssh_err(r)); |
|
ssh_packet_disconnect(ssh, "Invalid extended_data message"); |
|
} |
if (c->efd == -1 || |
if (c->efd == -1 || |
c->extended_usage != CHAN_EXTENDED_WRITE || |
c->extended_usage != CHAN_EXTENDED_WRITE || |
tcode != SSH2_EXTENDED_DATA_STDERR) { |
tcode != SSH2_EXTENDED_DATA_STDERR) { |
logit("channel %d: bad ext data", c->self); |
logit("channel %d: bad ext data", c->self); |
return 0; |
return 0; |
} |
} |
data = packet_get_string(&data_len); |
if ((r = sshpkt_get_string_direct(ssh, &data, &data_len)) != 0) { |
packet_check_eom(); |
error("%s: parse data: %s", __func__, ssh_err(r)); |
|
ssh_packet_disconnect(ssh, "Invalid extended_data message"); |
|
} |
|
ssh_packet_check_eom(ssh); |
|
|
if (data_len > c->local_window) { |
if (data_len > c->local_window) { |
logit("channel %d: rcvd too much extended_data %d, win %d", |
logit("channel %d: rcvd too much extended_data %zu, win %u", |
c->self, data_len, c->local_window); |
c->self, data_len, c->local_window); |
free(data); |
|
return 0; |
return 0; |
} |
} |
debug2("channel %d: rcvd ext data %d", c->self, data_len); |
debug2("channel %d: rcvd ext data %zu", c->self, data_len); |
|
/* XXX sshpkt_getb? */ |
|
if ((r = sshbuf_put(c->extended, data, data_len)) != 0) |
|
error("%s: append: %s", __func__, ssh_err(r)); |
c->local_window -= data_len; |
c->local_window -= data_len; |
buffer_append(&c->extended, data, data_len); |
|
free(data); |
|
return 0; |
return 0; |
} |
} |
|
|
/* ARGSUSED */ |
|
int |
int |
channel_input_ieof(int type, u_int32_t seq, struct ssh *ssh) |
channel_input_ieof(int type, u_int32_t seq, struct ssh *ssh) |
{ |
{ |
int id; |
Channel *c = channel_from_packet_id(ssh, __func__, "ieof"); |
Channel *c; |
|
|
|
id = packet_get_int(); |
ssh_packet_check_eom(ssh); |
packet_check_eom(); |
|
c = channel_lookup(id); |
|
if (c == NULL) |
|
packet_disconnect("Received ieof for nonexistent channel %d.", id); |
|
if (channel_proxy_upstream(c, type, seq, ssh)) |
if (channel_proxy_upstream(c, type, seq, ssh)) |
return 0; |
return 0; |
chan_rcvd_ieof(c); |
chan_rcvd_ieof(ssh, c); |
|
|
/* XXX force input close */ |
/* XXX force input close */ |
if (c->force_drain && c->istate == CHAN_INPUT_OPEN) { |
if (c->force_drain && c->istate == CHAN_INPUT_OPEN) { |
debug("channel %d: FORCE input drain", c->self); |
debug("channel %d: FORCE input drain", c->self); |
c->istate = CHAN_INPUT_WAIT_DRAIN; |
c->istate = CHAN_INPUT_WAIT_DRAIN; |
if (buffer_len(&c->input) == 0) |
if (sshbuf_len(c->input) == 0) |
chan_ibuf_empty(c); |
chan_ibuf_empty(ssh, c); |
} |
} |
return 0; |
return 0; |
} |
} |
|
|
/* ARGSUSED */ |
|
int |
int |
channel_input_oclose(int type, u_int32_t seq, struct ssh *ssh) |
channel_input_oclose(int type, u_int32_t seq, struct ssh *ssh) |
{ |
{ |
int id = packet_get_int(); |
Channel *c = channel_from_packet_id(ssh, __func__, "oclose"); |
Channel *c = channel_lookup(id); |
|
|
|
if (c == NULL) |
|
packet_disconnect("Received oclose for nonexistent channel %d.", id); |
|
if (channel_proxy_upstream(c, type, seq, ssh)) |
if (channel_proxy_upstream(c, type, seq, ssh)) |
return 0; |
return 0; |
packet_check_eom(); |
ssh_packet_check_eom(ssh); |
chan_rcvd_oclose(c); |
chan_rcvd_oclose(ssh, c); |
return 0; |
return 0; |
} |
} |
|
|
/* ARGSUSED */ |
|
int |
int |
channel_input_open_confirmation(int type, u_int32_t seq, struct ssh *ssh) |
channel_input_open_confirmation(int type, u_int32_t seq, struct ssh *ssh) |
{ |
{ |
int id, remote_id; |
Channel *c = channel_from_packet_id(ssh, __func__, "open confirmation"); |
Channel *c; |
u_int32_t remote_window, remote_maxpacket; |
|
int r; |
|
|
id = packet_get_int(); |
|
c = channel_lookup(id); |
|
|
|
if (c==NULL) |
|
packet_disconnect("Received open confirmation for " |
|
"unknown channel %d.", id); |
|
if (channel_proxy_upstream(c, type, seq, ssh)) |
if (channel_proxy_upstream(c, type, seq, ssh)) |
return 0; |
return 0; |
if (c->type != SSH_CHANNEL_OPENING) |
if (c->type != SSH_CHANNEL_OPENING) |
packet_disconnect("Received open confirmation for " |
packet_disconnect("Received open confirmation for " |
"non-opening channel %d.", id); |
"non-opening channel %d.", c->self); |
remote_id = packet_get_int(); |
/* |
/* Record the remote channel number and mark that the channel is now open. */ |
* Record the remote channel number and mark that the channel |
c->remote_id = remote_id; |
* is now open. |
c->type = SSH_CHANNEL_OPEN; |
*/ |
|
c->remote_id = channel_parse_id(ssh, __func__, "open confirmation"); |
|
if ((r = sshpkt_get_u32(ssh, &remote_window)) != 0 || |
|
(r = sshpkt_get_u32(ssh, &remote_maxpacket)) != 0) { |
|
error("%s: window/maxpacket: %s", __func__, ssh_err(r)); |
|
packet_disconnect("Invalid open confirmation message"); |
|
} |
|
ssh_packet_check_eom(ssh); |
|
|
c->remote_window = packet_get_int(); |
c->remote_window = remote_window; |
c->remote_maxpacket = packet_get_int(); |
c->remote_maxpacket = remote_maxpacket; |
|
c->type = SSH_CHANNEL_OPEN; |
if (c->open_confirm) { |
if (c->open_confirm) { |
debug2("callback start"); |
debug2("%s: channel %d: callback start", __func__, c->self); |
c->open_confirm(c->self, 1, c->open_confirm_ctx); |
c->open_confirm(ssh, c->self, 1, c->open_confirm_ctx); |
debug2("callback done"); |
debug2("%s: channel %d: callback done", __func__, c->self); |
} |
} |
debug2("channel %d: open confirm rwindow %u rmax %u", c->self, |
debug2("channel %d: open confirm rwindow %u rmax %u", c->self, |
c->remote_window, c->remote_maxpacket); |
c->remote_window, c->remote_maxpacket); |
packet_check_eom(); |
|
return 0; |
return 0; |
} |
} |
|
|
|
|
return "unknown reason"; |
return "unknown reason"; |
} |
} |
|
|
/* ARGSUSED */ |
|
int |
int |
channel_input_open_failure(int type, u_int32_t seq, struct ssh *ssh) |
channel_input_open_failure(int type, u_int32_t seq, struct ssh *ssh) |
{ |
{ |
int id, reason; |
Channel *c = channel_from_packet_id(ssh, __func__, "open failure"); |
char *msg = NULL, *lang = NULL; |
u_int32_t reason; |
Channel *c; |
char *msg = NULL; |
|
int r; |
|
|
id = packet_get_int(); |
|
c = channel_lookup(id); |
|
|
|
if (c==NULL) |
|
packet_disconnect("Received open failure for " |
|
"unknown channel %d.", id); |
|
if (channel_proxy_upstream(c, type, seq, ssh)) |
if (channel_proxy_upstream(c, type, seq, ssh)) |
return 0; |
return 0; |
if (c->type != SSH_CHANNEL_OPENING) |
if (c->type != SSH_CHANNEL_OPENING) |
packet_disconnect("Received open failure for " |
packet_disconnect("Received open failure for " |
"non-opening channel %d.", id); |
"non-opening channel %d.", c->self); |
reason = packet_get_int(); |
if ((r = sshpkt_get_u32(ssh, &reason)) != 0) { |
if (!(datafellows & SSH_BUG_OPENFAILURE)) { |
error("%s: reason: %s", __func__, ssh_err(r)); |
msg = packet_get_string(NULL); |
packet_disconnect("Invalid open failure message"); |
lang = packet_get_string(NULL); |
|
} |
} |
logit("channel %d: open failed: %s%s%s", id, |
if ((datafellows & SSH_BUG_OPENFAILURE) == 0) { |
|
/* skip language */ |
|
if ((r = sshpkt_get_cstring(ssh, &msg, NULL)) != 0 || |
|
(r = sshpkt_get_string_direct(ssh, NULL, NULL)) == 0) { |
|
error("%s: message/lang: %s", __func__, ssh_err(r)); |
|
packet_disconnect("Invalid open failure message"); |
|
} |
|
} |
|
ssh_packet_check_eom(ssh); |
|
logit("channel %d: open failed: %s%s%s", c->self, |
reason2txt(reason), msg ? ": ": "", msg ? msg : ""); |
reason2txt(reason), msg ? ": ": "", msg ? msg : ""); |
free(msg); |
free(msg); |
free(lang); |
|
if (c->open_confirm) { |
if (c->open_confirm) { |
debug2("callback start"); |
debug2("%s: channel %d: callback start", __func__, c->self); |
c->open_confirm(c->self, 0, c->open_confirm_ctx); |
c->open_confirm(ssh, c->self, 0, c->open_confirm_ctx); |
debug2("callback done"); |
debug2("%s: channel %d: callback done", __func__, c->self); |
} |
} |
packet_check_eom(); |
|
/* Schedule the channel for cleanup/deletion. */ |
/* Schedule the channel for cleanup/deletion. */ |
chan_mark_dead(c); |
chan_mark_dead(ssh, c); |
return 0; |
return 0; |
} |
} |
|
|
/* ARGSUSED */ |
|
int |
int |
channel_input_window_adjust(int type, u_int32_t seq, struct ssh *ssh) |
channel_input_window_adjust(int type, u_int32_t seq, struct ssh *ssh) |
{ |
{ |
|
int id = channel_parse_id(ssh, __func__, "window adjust"); |
Channel *c; |
Channel *c; |
int id; |
u_int32_t adjust; |
u_int adjust, tmp; |
u_int new_rwin; |
|
int r; |
|
|
/* Get the channel number and verify it. */ |
if ((c = channel_lookup(ssh, id)) == NULL) { |
id = packet_get_int(); |
|
c = channel_lookup(id); |
|
|
|
if (c == 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; |
adjust = packet_get_int(); |
if ((r = sshpkt_get_u32(ssh, &adjust)) != 0) { |
packet_check_eom(); |
error("%s: adjust: %s", __func__, ssh_err(r)); |
debug2("channel %d: rcvd adjust %u", id, adjust); |
packet_disconnect("Invalid window adjust message"); |
if ((tmp = c->remote_window + adjust) < c->remote_window) |
} |
|
ssh_packet_check_eom(ssh); |
|
debug2("channel %d: rcvd adjust %u", c->self, adjust); |
|
if ((new_rwin = c->remote_window + adjust) < c->remote_window) { |
fatal("channel %d: adjust %u overflows remote window %u", |
fatal("channel %d: adjust %u overflows remote window %u", |
id, adjust, c->remote_window); |
c->self, adjust, c->remote_window); |
c->remote_window = tmp; |
} |
|
c->remote_window = new_rwin; |
return 0; |
return 0; |
} |
} |
|
|
/* ARGSUSED */ |
|
int |
int |
channel_input_status_confirm(int type, u_int32_t seq, struct ssh *ssh) |
channel_input_status_confirm(int type, u_int32_t seq, struct ssh *ssh) |
{ |
{ |
|
int id = channel_parse_id(ssh, __func__, "status confirm"); |
Channel *c; |
Channel *c; |
struct channel_confirm *cc; |
struct channel_confirm *cc; |
int id; |
|
|
|
/* Reset keepalive timeout */ |
/* Reset keepalive timeout */ |
packet_set_alive_timeouts(0); |
packet_set_alive_timeouts(0); |
|
|
id = packet_get_int(); |
debug2("%s: type %d id %d", __func__, type, id); |
debug2("channel_input_status_confirm: type %d id %d", type, id); |
|
|
|
if ((c = channel_lookup(id)) == NULL) { |
if ((c = channel_lookup(ssh, id)) == NULL) { |
logit("channel_input_status_confirm: %d: unknown", id); |
logit("%s: %d: unknown", __func__, 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; |
packet_check_eom(); |
ssh_packet_check_eom(ssh); |
if ((cc = TAILQ_FIRST(&c->status_confirms)) == NULL) |
if ((cc = TAILQ_FIRST(&c->status_confirms)) == NULL) |
return 0; |
return 0; |
cc->cb(type, c, cc->ctx); |
cc->cb(ssh, type, c, cc->ctx); |
TAILQ_REMOVE(&c->status_confirms, cc, entry); |
TAILQ_REMOVE(&c->status_confirms, cc, entry); |
explicit_bzero(cc, sizeof(*cc)); |
explicit_bzero(cc, sizeof(*cc)); |
free(cc); |
free(cc); |
|
|
/* -- tcp forwarding */ |
/* -- tcp forwarding */ |
|
|
void |
void |
channel_set_af(int af) |
channel_set_af(struct ssh *ssh, int af) |
{ |
{ |
IPv4or6 = af; |
ssh->chanctxt->IPv4or6 = af; |
} |
} |
|
|
|
|
|
|
} |
} |
|
|
static int |
static int |
channel_setup_fwd_listener_tcpip(int type, struct Forward *fwd, |
channel_setup_fwd_listener_tcpip(struct ssh *ssh, int type, |
int *allocated_listen_port, struct ForwardOptions *fwd_opts) |
struct Forward *fwd, int *allocated_listen_port, |
|
struct ForwardOptions *fwd_opts) |
{ |
{ |
Channel *c; |
Channel *c; |
int sock, r, success = 0, wildcard = 0, is_client; |
int sock, r, success = 0, wildcard = 0, is_client; |
|
|
* set to NULL and hints.ai_flags is not AI_PASSIVE |
* set to NULL and hints.ai_flags is not AI_PASSIVE |
*/ |
*/ |
memset(&hints, 0, sizeof(hints)); |
memset(&hints, 0, sizeof(hints)); |
hints.ai_family = IPv4or6; |
hints.ai_family = ssh->chanctxt->IPv4or6; |
hints.ai_flags = wildcard ? AI_PASSIVE : 0; |
hints.ai_flags = wildcard ? AI_PASSIVE : 0; |
hints.ai_socktype = SOCK_STREAM; |
hints.ai_socktype = SOCK_STREAM; |
snprintf(strport, sizeof strport, "%d", fwd->listen_port); |
snprintf(strport, sizeof strport, "%d", fwd->listen_port); |
|
|
* If allocating a port for -R forwards, then use the |
* If allocating a port for -R forwards, then use the |
* same port for all address families. |
* same port for all address families. |
*/ |
*/ |
if (type == SSH_CHANNEL_RPORT_LISTENER && fwd->listen_port == 0 && |
if (type == SSH_CHANNEL_RPORT_LISTENER && |
allocated_listen_port != NULL && *allocated_listen_port > 0) |
fwd->listen_port == 0 && allocated_listen_port != NULL && |
|
*allocated_listen_port > 0) |
*lport_p = htons(*allocated_listen_port); |
*lport_p = htons(*allocated_listen_port); |
|
|
if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), |
if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), |
strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { |
strport, sizeof(strport), |
|
NI_NUMERICHOST|NI_NUMERICSERV) != 0) { |
error("%s: getnameinfo failed", __func__); |
error("%s: getnameinfo failed", __func__); |
continue; |
continue; |
} |
} |
|
|
|
|
/* Bind the socket to the address. */ |
/* Bind the socket to the address. */ |
if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { |
if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { |
/* address can be in use ipv6 address is already bound */ |
/* |
|
* address can be in if use ipv6 address is |
|
* already bound |
|
*/ |
verbose("bind: %.100s", strerror(errno)); |
verbose("bind: %.100s", strerror(errno)); |
close(sock); |
close(sock); |
continue; |
continue; |
|
|
* fwd->listen_port == 0 requests a dynamically allocated port - |
* fwd->listen_port == 0 requests a dynamically allocated port - |
* record what we got. |
* record what we got. |
*/ |
*/ |
if (type == SSH_CHANNEL_RPORT_LISTENER && fwd->listen_port == 0 && |
if (type == SSH_CHANNEL_RPORT_LISTENER && |
|
fwd->listen_port == 0 && |
allocated_listen_port != NULL && |
allocated_listen_port != NULL && |
*allocated_listen_port == 0) { |
*allocated_listen_port == 0) { |
*allocated_listen_port = get_local_port(sock); |
*allocated_listen_port = get_local_port(sock); |
|
|
} |
} |
|
|
/* Allocate a channel number for the socket. */ |
/* Allocate a channel number for the socket. */ |
c = channel_new("port listener", type, sock, sock, -1, |
c = channel_new(ssh, "port listener", type, sock, sock, -1, |
CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, |
CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, |
0, "port listener", 1); |
0, "port listener", 1); |
c->path = xstrdup(host); |
c->path = xstrdup(host); |
|
|
} |
} |
|
|
static int |
static int |
channel_setup_fwd_listener_streamlocal(int type, struct Forward *fwd, |
channel_setup_fwd_listener_streamlocal(struct ssh *ssh, int type, |
struct ForwardOptions *fwd_opts) |
struct Forward *fwd, struct ForwardOptions *fwd_opts) |
{ |
{ |
struct sockaddr_un sunaddr; |
struct sockaddr_un sunaddr; |
const char *path; |
const char *path; |
|
|
debug("Local forwarding listening on path %s.", fwd->listen_path); |
debug("Local forwarding listening on path %s.", fwd->listen_path); |
|
|
/* Allocate a channel number for the socket. */ |
/* Allocate a channel number for the socket. */ |
c = channel_new("unix listener", type, sock, sock, -1, |
c = channel_new(ssh, "unix listener", type, sock, sock, -1, |
CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, |
CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, |
0, "unix listener", 1); |
0, "unix listener", 1); |
c->path = xstrdup(path); |
c->path = xstrdup(path); |
|
|
} |
} |
|
|
static int |
static int |
channel_cancel_rport_listener_tcpip(const char *host, u_short port) |
channel_cancel_rport_listener_tcpip(struct ssh *ssh, |
|
const char *host, u_short port) |
{ |
{ |
u_int i; |
u_int i; |
int found = 0; |
int found = 0; |
|
|
for (i = 0; i < channels_alloc; i++) { |
for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { |
Channel *c = channels[i]; |
Channel *c = ssh->chanctxt->channels[i]; |
if (c == NULL || c->type != SSH_CHANNEL_RPORT_LISTENER) |
if (c == NULL || c->type != SSH_CHANNEL_RPORT_LISTENER) |
continue; |
continue; |
if (strcmp(c->path, host) == 0 && c->listening_port == port) { |
if (strcmp(c->path, host) == 0 && c->listening_port == port) { |
debug2("%s: close channel %d", __func__, i); |
debug2("%s: close channel %d", __func__, i); |
channel_free(c); |
channel_free(ssh, c); |
found = 1; |
found = 1; |
} |
} |
} |
} |
|
|
return (found); |
return found; |
} |
} |
|
|
static int |
static int |
channel_cancel_rport_listener_streamlocal(const char *path) |
channel_cancel_rport_listener_streamlocal(struct ssh *ssh, const char *path) |
{ |
{ |
u_int i; |
u_int i; |
int found = 0; |
int found = 0; |
|
|
for (i = 0; i < channels_alloc; i++) { |
for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { |
Channel *c = channels[i]; |
Channel *c = ssh->chanctxt->channels[i]; |
if (c == NULL || c->type != SSH_CHANNEL_RUNIX_LISTENER) |
if (c == NULL || c->type != SSH_CHANNEL_RUNIX_LISTENER) |
continue; |
continue; |
if (c->path == NULL) |
if (c->path == NULL) |
continue; |
continue; |
if (strcmp(c->path, path) == 0) { |
if (strcmp(c->path, path) == 0) { |
debug2("%s: close channel %d", __func__, i); |
debug2("%s: close channel %d", __func__, i); |
channel_free(c); |
channel_free(ssh, c); |
found = 1; |
found = 1; |
} |
} |
} |
} |
|
|
return (found); |
return found; |
} |
} |
|
|
int |
int |
channel_cancel_rport_listener(struct Forward *fwd) |
channel_cancel_rport_listener(struct ssh *ssh, struct Forward *fwd) |
{ |
{ |
if (fwd->listen_path != NULL) |
if (fwd->listen_path != NULL) { |
return channel_cancel_rport_listener_streamlocal(fwd->listen_path); |
return channel_cancel_rport_listener_streamlocal(ssh, |
else |
fwd->listen_path); |
return channel_cancel_rport_listener_tcpip(fwd->listen_host, fwd->listen_port); |
} else { |
|
return channel_cancel_rport_listener_tcpip(ssh, |
|
fwd->listen_host, fwd->listen_port); |
|
} |
} |
} |
|
|
static int |
static int |
channel_cancel_lport_listener_tcpip(const char *lhost, u_short lport, |
channel_cancel_lport_listener_tcpip(struct ssh *ssh, |
int cport, struct ForwardOptions *fwd_opts) |
const char *lhost, u_short lport, int cport, |
|
struct ForwardOptions *fwd_opts) |
{ |
{ |
u_int i; |
u_int i; |
int found = 0; |
int found = 0; |
const char *addr = channel_fwd_bind_addr(lhost, NULL, 1, fwd_opts); |
const char *addr = channel_fwd_bind_addr(lhost, NULL, 1, fwd_opts); |
|
|
for (i = 0; i < channels_alloc; i++) { |
for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { |
Channel *c = channels[i]; |
Channel *c = ssh->chanctxt->channels[i]; |
if (c == NULL || c->type != SSH_CHANNEL_PORT_LISTENER) |
if (c == NULL || c->type != SSH_CHANNEL_PORT_LISTENER) |
continue; |
continue; |
if (c->listening_port != lport) |
if (c->listening_port != lport) |
|
|
continue; |
continue; |
if (addr == NULL || strcmp(c->listening_addr, addr) == 0) { |
if (addr == NULL || strcmp(c->listening_addr, addr) == 0) { |
debug2("%s: close channel %d", __func__, i); |
debug2("%s: close channel %d", __func__, i); |
channel_free(c); |
channel_free(ssh, c); |
found = 1; |
found = 1; |
} |
} |
} |
} |
|
|
return (found); |
return found; |
} |
} |
|
|
static int |
static int |
channel_cancel_lport_listener_streamlocal(const char *path) |
channel_cancel_lport_listener_streamlocal(struct ssh *ssh, const char *path) |
{ |
{ |
u_int i; |
u_int i; |
int found = 0; |
int found = 0; |
|
|
return 0; |
return 0; |
} |
} |
|
|
for (i = 0; i < channels_alloc; i++) { |
for (i = 0; i < ssh->chanctxt->channels_alloc; i++) { |
Channel *c = channels[i]; |
Channel *c = ssh->chanctxt->channels[i]; |
if (c == NULL || c->type != SSH_CHANNEL_UNIX_LISTENER) |
if (c == NULL || c->type != SSH_CHANNEL_UNIX_LISTENER) |
continue; |
continue; |
if (c->listening_addr == NULL) |
if (c->listening_addr == NULL) |
continue; |
continue; |
if (strcmp(c->listening_addr, path) == 0) { |
if (strcmp(c->listening_addr, path) == 0) { |
debug2("%s: close channel %d", __func__, i); |
debug2("%s: close channel %d", __func__, i); |
channel_free(c); |
channel_free(ssh, c); |
found = 1; |
found = 1; |
} |
} |
} |
} |
|
|
return (found); |
return found; |
} |
} |
|
|
int |
int |
channel_cancel_lport_listener(struct Forward *fwd, int cport, struct ForwardOptions *fwd_opts) |
channel_cancel_lport_listener(struct ssh *ssh, |
|
struct Forward *fwd, int cport, struct ForwardOptions *fwd_opts) |
{ |
{ |
if (fwd->listen_path != NULL) |
if (fwd->listen_path != NULL) { |
return channel_cancel_lport_listener_streamlocal(fwd->listen_path); |
return channel_cancel_lport_listener_streamlocal(ssh, |
else |
fwd->listen_path); |
return channel_cancel_lport_listener_tcpip(fwd->listen_host, fwd->listen_port, cport, fwd_opts); |
} else { |
|
return channel_cancel_lport_listener_tcpip(ssh, |
|
fwd->listen_host, fwd->listen_port, cport, fwd_opts); |
|
} |
} |
} |
|
|
/* protocol local port fwd, used by ssh */ |
/* protocol local port fwd, used by ssh */ |
int |
int |
channel_setup_local_fwd_listener(struct Forward *fwd, struct ForwardOptions *fwd_opts) |
channel_setup_local_fwd_listener(struct ssh *ssh, |
|
struct Forward *fwd, struct ForwardOptions *fwd_opts) |
{ |
{ |
if (fwd->listen_path != NULL) { |
if (fwd->listen_path != NULL) { |
return channel_setup_fwd_listener_streamlocal( |
return channel_setup_fwd_listener_streamlocal(ssh, |
SSH_CHANNEL_UNIX_LISTENER, fwd, fwd_opts); |
SSH_CHANNEL_UNIX_LISTENER, fwd, fwd_opts); |
} else { |
} else { |
return channel_setup_fwd_listener_tcpip(SSH_CHANNEL_PORT_LISTENER, |
return channel_setup_fwd_listener_tcpip(ssh, |
fwd, NULL, fwd_opts); |
SSH_CHANNEL_PORT_LISTENER, fwd, NULL, fwd_opts); |
} |
} |
} |
} |
|
|
/* protocol v2 remote port fwd, used by sshd */ |
/* protocol v2 remote port fwd, used by sshd */ |
int |
int |
channel_setup_remote_fwd_listener(struct Forward *fwd, |
channel_setup_remote_fwd_listener(struct ssh *ssh, struct Forward *fwd, |
int *allocated_listen_port, struct ForwardOptions *fwd_opts) |
int *allocated_listen_port, struct ForwardOptions *fwd_opts) |
{ |
{ |
if (fwd->listen_path != NULL) { |
if (fwd->listen_path != NULL) { |
return channel_setup_fwd_listener_streamlocal( |
return channel_setup_fwd_listener_streamlocal(ssh, |
SSH_CHANNEL_RUNIX_LISTENER, fwd, fwd_opts); |
SSH_CHANNEL_RUNIX_LISTENER, fwd, fwd_opts); |
} else { |
} else { |
return channel_setup_fwd_listener_tcpip( |
return channel_setup_fwd_listener_tcpip(ssh, |
SSH_CHANNEL_RPORT_LISTENER, fwd, allocated_listen_port, |
SSH_CHANNEL_RPORT_LISTENER, fwd, allocated_listen_port, |
fwd_opts); |
fwd_opts); |
} |
} |
|
|
* channel_update_permitted_opens(). |
* channel_update_permitted_opens(). |
*/ |
*/ |
int |
int |
channel_request_remote_forwarding(struct Forward *fwd) |
channel_request_remote_forwarding(struct ssh *ssh, struct Forward *fwd) |
{ |
{ |
int success = 0, idx = -1; |
int r, success = 0, idx = -1; |
|
char *host_to_connect, *listen_host, *listen_path; |
|
int port_to_connect, listen_port; |
|
|
/* Send the forward request to the remote side. */ |
/* Send the forward request to the remote side. */ |
packet_start(SSH2_MSG_GLOBAL_REQUEST); |
|
if (fwd->listen_path != NULL) { |
if (fwd->listen_path != NULL) { |
packet_put_cstring("streamlocal-forward@openssh.com"); |
if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || |
packet_put_char(1); /* boolean: want reply */ |
(r = sshpkt_put_cstring(ssh, |
packet_put_cstring(fwd->listen_path); |
"streamlocal-forward@openssh.com")) != 0 || |
|
(r = sshpkt_put_u8(ssh, 1)) != 0 || /* want reply */ |
|
(r = sshpkt_put_cstring(ssh, fwd->listen_path)) != 0 || |
|
(r = sshpkt_send(ssh)) != 0 || |
|
(r = ssh_packet_write_wait(ssh)) != 0) |
|
fatal("%s: request streamlocal: %s", |
|
__func__, ssh_err(r)); |
} else { |
} else { |
packet_put_cstring("tcpip-forward"); |
if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || |
packet_put_char(1); /* boolean: want reply */ |
(r = sshpkt_put_cstring(ssh, "tcpip-forward")) != 0 || |
packet_put_cstring(channel_rfwd_bind_host(fwd->listen_host)); |
(r = sshpkt_put_u8(ssh, 1)) != 0 || /* want reply */ |
packet_put_int(fwd->listen_port); |
(r = sshpkt_put_cstring(ssh, |
|
channel_rfwd_bind_host(fwd->listen_host))) != 0 || |
|
(r = sshpkt_put_u32(ssh, fwd->listen_port)) != 0 || |
|
(r = sshpkt_send(ssh)) != 0 || |
|
(r = ssh_packet_write_wait(ssh)) != 0) |
|
fatal("%s: request tcpip-forward: %s", |
|
__func__, ssh_err(r)); |
} |
} |
packet_send(); |
|
packet_write_wait(); |
|
/* Assume that server accepts the request */ |
/* Assume that server accepts the request */ |
success = 1; |
success = 1; |
if (success) { |
if (success) { |
/* Record that connection to this host/port is permitted. */ |
/* Record that connection to this host/port is permitted. */ |
permitted_opens = xreallocarray(permitted_opens, |
host_to_connect = listen_host = listen_path = NULL; |
num_permitted_opens + 1, sizeof(*permitted_opens)); |
port_to_connect = listen_port = 0; |
idx = num_permitted_opens++; |
|
if (fwd->connect_path != NULL) { |
if (fwd->connect_path != NULL) { |
permitted_opens[idx].host_to_connect = |
host_to_connect = xstrdup(fwd->connect_path); |
xstrdup(fwd->connect_path); |
port_to_connect = PORT_STREAMLOCAL; |
permitted_opens[idx].port_to_connect = |
|
PORT_STREAMLOCAL; |
|
} else { |
} else { |
permitted_opens[idx].host_to_connect = |
host_to_connect = xstrdup(fwd->connect_host); |
xstrdup(fwd->connect_host); |
port_to_connect = fwd->connect_port; |
permitted_opens[idx].port_to_connect = |
|
fwd->connect_port; |
|
} |
} |
if (fwd->listen_path != NULL) { |
if (fwd->listen_path != NULL) { |
permitted_opens[idx].listen_host = NULL; |
listen_path = xstrdup(fwd->listen_path); |
permitted_opens[idx].listen_path = |
listen_port = PORT_STREAMLOCAL; |
xstrdup(fwd->listen_path); |
|
permitted_opens[idx].listen_port = PORT_STREAMLOCAL; |
|
} else { |
} else { |
permitted_opens[idx].listen_host = |
if (fwd->listen_host != NULL) |
fwd->listen_host ? xstrdup(fwd->listen_host) : NULL; |
listen_host = xstrdup(fwd->listen_host); |
permitted_opens[idx].listen_path = NULL; |
listen_port = fwd->listen_port; |
permitted_opens[idx].listen_port = fwd->listen_port; |
|
} |
} |
permitted_opens[idx].downstream = NULL; |
idx = fwd_perm_list_add(ssh, FWDPERM_USER, |
|
host_to_connect, port_to_connect, |
|
listen_host, listen_path, listen_port, NULL); |
} |
} |
return (idx); |
return idx; |
} |
} |
|
|
static int |
static int |
|
|
* local side. |
* local side. |
*/ |
*/ |
static int |
static int |
channel_request_rforward_cancel_tcpip(const char *host, u_short port) |
channel_request_rforward_cancel_tcpip(struct ssh *ssh, |
|
const char *host, u_short port) |
{ |
{ |
int i; |
struct ssh_channels *sc = ssh->chanctxt; |
|
int r; |
|
u_int i; |
|
ForwardPermission *fp; |
|
|
for (i = 0; i < num_permitted_opens; i++) { |
for (i = 0; i < sc->num_permitted_opens; i++) { |
if (open_listen_match_tcpip(&permitted_opens[i], host, port, 0)) |
fp = &sc->permitted_opens[i]; |
|
if (open_listen_match_tcpip(fp, host, port, 0)) |
break; |
break; |
|
fp = NULL; |
} |
} |
if (i >= num_permitted_opens) { |
if (fp == NULL) { |
debug("%s: requested forward not found", __func__); |
debug("%s: requested forward not found", __func__); |
return -1; |
return -1; |
} |
} |
packet_start(SSH2_MSG_GLOBAL_REQUEST); |
if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || |
packet_put_cstring("cancel-tcpip-forward"); |
(r = sshpkt_put_cstring(ssh, "cancel-tcpip-forward")) != 0 || |
packet_put_char(0); |
(r = sshpkt_put_u8(ssh, 0)) != 0 || /* want reply */ |
packet_put_cstring(channel_rfwd_bind_host(host)); |
(r = sshpkt_put_cstring(ssh, channel_rfwd_bind_host(host))) != 0 || |
packet_put_int(port); |
(r = sshpkt_put_u32(ssh, port)) != 0 || |
packet_send(); |
(r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: send cancel: %s", __func__, ssh_err(r)); |
|
|
permitted_opens[i].listen_port = 0; |
fwd_perm_clear(fp); /* unregister */ |
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; |
|
|
|
return 0; |
return 0; |
} |
} |
|
|
* path from local side. |
* path from local side. |
*/ |
*/ |
static int |
static int |
channel_request_rforward_cancel_streamlocal(const char *path) |
channel_request_rforward_cancel_streamlocal(struct ssh *ssh, const char *path) |
{ |
{ |
int i; |
struct ssh_channels *sc = ssh->chanctxt; |
|
int r; |
|
u_int i; |
|
ForwardPermission *fp; |
|
|
for (i = 0; i < num_permitted_opens; i++) { |
for (i = 0; i < sc->num_permitted_opens; i++) { |
if (open_listen_match_streamlocal(&permitted_opens[i], path)) |
fp = &sc->permitted_opens[i]; |
|
if (open_listen_match_streamlocal(fp, path)) |
break; |
break; |
|
fp = NULL; |
} |
} |
if (i >= num_permitted_opens) { |
if (fp == NULL) { |
debug("%s: requested forward not found", __func__); |
debug("%s: requested forward not found", __func__); |
return -1; |
return -1; |
} |
} |
packet_start(SSH2_MSG_GLOBAL_REQUEST); |
if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || |
packet_put_cstring("cancel-streamlocal-forward@openssh.com"); |
(r = sshpkt_put_cstring(ssh, |
packet_put_char(0); |
"cancel-streamlocal-forward@openssh.com")) != 0 || |
packet_put_cstring(path); |
(r = sshpkt_put_u8(ssh, 0)) != 0 || /* want reply */ |
packet_send(); |
(r = sshpkt_put_cstring(ssh, path)) != 0 || |
|
(r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: send cancel: %s", __func__, ssh_err(r)); |
|
|
permitted_opens[i].listen_port = 0; |
fwd_perm_clear(fp); /* unregister */ |
permitted_opens[i].port_to_connect = 0; |
|
free(permitted_opens[i].host_to_connect); |
|
permitted_opens[i].host_to_connect = NULL; |
|
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; |
return 0; |
} |
} |
|
|
* Request cancellation of remote forwarding of a connection from local side. |
* Request cancellation of remote forwarding of a connection from local side. |
*/ |
*/ |
int |
int |
channel_request_rforward_cancel(struct Forward *fwd) |
channel_request_rforward_cancel(struct ssh *ssh, struct Forward *fwd) |
{ |
{ |
if (fwd->listen_path != NULL) { |
if (fwd->listen_path != NULL) { |
return (channel_request_rforward_cancel_streamlocal( |
return channel_request_rforward_cancel_streamlocal(ssh, |
fwd->listen_path)); |
fwd->listen_path); |
} else { |
} else { |
return (channel_request_rforward_cancel_tcpip(fwd->listen_host, |
return channel_request_rforward_cancel_tcpip(ssh, |
fwd->listen_port ? fwd->listen_port : fwd->allocated_port)); |
fwd->listen_host, |
|
fwd->listen_port ? fwd->listen_port : fwd->allocated_port); |
} |
} |
} |
} |
|
|
|
|
* anyway, and the server has no way to know but to trust the client anyway. |
* anyway, and the server has no way to know but to trust the client anyway. |
*/ |
*/ |
void |
void |
channel_permit_all_opens(void) |
channel_permit_all_opens(struct ssh *ssh) |
{ |
{ |
if (num_permitted_opens == 0) |
if (ssh->chanctxt->num_permitted_opens == 0) |
all_opens_permitted = 1; |
ssh->chanctxt->all_opens_permitted = 1; |
} |
} |
|
|
void |
void |
channel_add_permitted_opens(char *host, int port) |
channel_add_permitted_opens(struct ssh *ssh, char *host, int port) |
{ |
{ |
debug("allow port forwarding to host %s port %d", host, port); |
struct ssh_channels *sc = ssh->chanctxt; |
|
|
permitted_opens = xreallocarray(permitted_opens, |
debug("allow port forwarding to host %s port %d", host, port); |
num_permitted_opens + 1, sizeof(*permitted_opens)); |
fwd_perm_list_add(ssh, FWDPERM_USER, host, port, NULL, NULL, 0, NULL); |
permitted_opens[num_permitted_opens].host_to_connect = xstrdup(host); |
sc->all_opens_permitted = 0; |
permitted_opens[num_permitted_opens].port_to_connect = 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; |
|
} |
} |
|
|
/* |
/* |
|
|
* passed then they entry will be invalidated. |
* passed then they entry will be invalidated. |
*/ |
*/ |
void |
void |
channel_update_permitted_opens(int idx, int newport) |
channel_update_permitted_opens(struct ssh *ssh, int idx, int newport) |
{ |
{ |
if (idx < 0 || idx >= num_permitted_opens) { |
struct ssh_channels *sc = ssh->chanctxt; |
debug("channel_update_permitted_opens: index out of range:" |
|
" %d num_permitted_opens %d", idx, num_permitted_opens); |
if (idx < 0 || (u_int)idx >= sc->num_permitted_opens) { |
|
debug("%s: index out of range: %d num_permitted_opens %d", |
|
__func__, idx, sc->num_permitted_opens); |
return; |
return; |
} |
} |
debug("%s allowed port %d for forwarding to host %s port %d", |
debug("%s allowed port %d for forwarding to host %s port %d", |
newport > 0 ? "Updating" : "Removing", |
newport > 0 ? "Updating" : "Removing", |
newport, |
newport, |
permitted_opens[idx].host_to_connect, |
sc->permitted_opens[idx].host_to_connect, |
permitted_opens[idx].port_to_connect); |
sc->permitted_opens[idx].port_to_connect); |
if (newport >= 0) { |
if (newport <= 0) |
permitted_opens[idx].listen_port = |
fwd_perm_clear(&sc->permitted_opens[idx]); |
|
else { |
|
sc->permitted_opens[idx].listen_port = |
(datafellows & SSH_BUG_DYNAMIC_RPORT) ? 0 : newport; |
(datafellows & SSH_BUG_DYNAMIC_RPORT) ? 0 : newport; |
} else { |
|
permitted_opens[idx].listen_port = 0; |
|
permitted_opens[idx].port_to_connect = 0; |
|
free(permitted_opens[idx].host_to_connect); |
|
permitted_opens[idx].host_to_connect = NULL; |
|
free(permitted_opens[idx].listen_host); |
|
permitted_opens[idx].listen_host = NULL; |
|
free(permitted_opens[idx].listen_path); |
|
permitted_opens[idx].listen_path = NULL; |
|
} |
} |
} |
} |
|
|
int |
int |
channel_add_adm_permitted_opens(char *host, int port) |
channel_add_adm_permitted_opens(struct ssh *ssh, char *host, int port) |
{ |
{ |
debug("config allows port forwarding to host %s port %d", host, port); |
debug("config allows port forwarding to host %s port %d", host, port); |
|
return fwd_perm_list_add(ssh, FWDPERM_ADMIN, host, port, |
permitted_adm_opens = xreallocarray(permitted_adm_opens, |
NULL, NULL, 0, NULL); |
num_adm_permitted_opens + 1, sizeof(*permitted_adm_opens)); |
|
permitted_adm_opens[num_adm_permitted_opens].host_to_connect |
|
= xstrdup(host); |
|
permitted_adm_opens[num_adm_permitted_opens].port_to_connect = port; |
|
permitted_adm_opens[num_adm_permitted_opens].listen_host = NULL; |
|
permitted_adm_opens[num_adm_permitted_opens].listen_path = NULL; |
|
permitted_adm_opens[num_adm_permitted_opens].listen_port = 0; |
|
return ++num_adm_permitted_opens; |
|
} |
} |
|
|
void |
void |
channel_disable_adm_local_opens(void) |
channel_disable_adm_local_opens(struct ssh *ssh) |
{ |
{ |
channel_clear_adm_permitted_opens(); |
channel_clear_adm_permitted_opens(ssh); |
permitted_adm_opens = xcalloc(sizeof(*permitted_adm_opens), 1); |
fwd_perm_list_add(ssh, FWDPERM_ADMIN, NULL, 0, NULL, NULL, 0, NULL); |
permitted_adm_opens[num_adm_permitted_opens].host_to_connect = NULL; |
|
num_adm_permitted_opens = 1; |
|
} |
} |
|
|
void |
void |
channel_clear_permitted_opens(void) |
channel_clear_permitted_opens(struct ssh *ssh) |
{ |
{ |
int i; |
struct ssh_channels *sc = ssh->chanctxt; |
|
|
for (i = 0; i < num_permitted_opens; i++) { |
sc->permitted_opens = xrecallocarray(sc->permitted_opens, |
free(permitted_opens[i].host_to_connect); |
sc->num_permitted_opens, 0, sizeof(*sc->permitted_opens)); |
free(permitted_opens[i].listen_host); |
sc->num_permitted_opens = 0; |
free(permitted_opens[i].listen_path); |
|
} |
|
free(permitted_opens); |
|
permitted_opens = NULL; |
|
num_permitted_opens = 0; |
|
} |
} |
|
|
void |
void |
channel_clear_adm_permitted_opens(void) |
channel_clear_adm_permitted_opens(struct ssh *ssh) |
{ |
{ |
int i; |
struct ssh_channels *sc = ssh->chanctxt; |
|
|
for (i = 0; i < num_adm_permitted_opens; i++) { |
sc->permitted_adm_opens = xrecallocarray(sc->permitted_adm_opens, |
free(permitted_adm_opens[i].host_to_connect); |
sc->num_adm_permitted_opens, 0, sizeof(*sc->permitted_adm_opens)); |
free(permitted_adm_opens[i].listen_host); |
sc->num_adm_permitted_opens = 0; |
free(permitted_adm_opens[i].listen_path); |
|
} |
|
free(permitted_adm_opens); |
|
permitted_adm_opens = NULL; |
|
num_adm_permitted_opens = 0; |
|
} |
} |
|
|
void |
|
channel_print_adm_permitted_opens(void) |
|
{ |
|
int i; |
|
|
|
printf("permitopen"); |
|
if (num_adm_permitted_opens == 0) { |
|
printf(" any\n"); |
|
return; |
|
} |
|
for (i = 0; i < num_adm_permitted_opens; i++) |
|
if (permitted_adm_opens[i].host_to_connect == NULL) |
|
printf(" none"); |
|
else |
|
printf(" %s:%d", permitted_adm_opens[i].host_to_connect, |
|
permitted_adm_opens[i].port_to_connect); |
|
printf("\n"); |
|
} |
|
|
|
/* returns port number, FWD_PERMIT_ANY_PORT or -1 on error */ |
/* returns port number, FWD_PERMIT_ANY_PORT or -1 on error */ |
int |
int |
permitopen_port(const char *p) |
permitopen_port(const char *p) |
|
|
{ |
{ |
int sock, saved_errno; |
int sock, saved_errno; |
struct sockaddr_un *sunaddr; |
struct sockaddr_un *sunaddr; |
char ntop[NI_MAXHOST], strport[MAXIMUM(NI_MAXSERV,sizeof(sunaddr->sun_path))]; |
char ntop[NI_MAXHOST]; |
|
char strport[MAXIMUM(NI_MAXSERV, sizeof(sunaddr->sun_path))]; |
|
|
for (; cctx->ai; cctx->ai = cctx->ai->ai_next) { |
for (; cctx->ai; cctx->ai = cctx->ai->ai_next) { |
switch (cctx->ai->ai_family) { |
switch (cctx->ai->ai_family) { |
|
|
* passing back the failure reason if appropriate. |
* passing back the failure reason if appropriate. |
*/ |
*/ |
static Channel * |
static Channel * |
connect_to_reason(const char *name, int port, char *ctype, char *rname, |
connect_to_reason(struct ssh *ssh, const char *name, int port, |
int *reason, const char **errmsg) |
char *ctype, char *rname, int *reason, const char **errmsg) |
{ |
{ |
struct addrinfo hints; |
struct addrinfo hints; |
int gaierr; |
int gaierr; |
|
|
cctx.aitop = ai; |
cctx.aitop = ai; |
} else { |
} else { |
memset(&hints, 0, sizeof(hints)); |
memset(&hints, 0, sizeof(hints)); |
hints.ai_family = IPv4or6; |
hints.ai_family = ssh->chanctxt->IPv4or6; |
hints.ai_socktype = SOCK_STREAM; |
hints.ai_socktype = SOCK_STREAM; |
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)) |
|
|
channel_connect_ctx_free(&cctx); |
channel_connect_ctx_free(&cctx); |
return NULL; |
return NULL; |
} |
} |
c = channel_new(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->connect_ctx = cctx; |
c->connect_ctx = cctx; |
return c; |
return c; |
|
|
|
|
/* Return CONNECTING channel to remote host:port or local socket path */ |
/* Return CONNECTING channel to remote host:port or local socket path */ |
static Channel * |
static Channel * |
connect_to(const char *name, int port, char *ctype, char *rname) |
connect_to(struct ssh *ssh, const char *name, int port, |
|
char *ctype, char *rname) |
{ |
{ |
return connect_to_reason(name, port, ctype, rname, NULL, NULL); |
return connect_to_reason(ssh, name, port, ctype, rname, NULL, NULL); |
} |
} |
|
|
/* |
/* |
|
|
* that needs to deal with this connection. |
* that needs to deal with this connection. |
*/ |
*/ |
Channel * |
Channel * |
channel_connect_by_listen_address(const char *listen_host, |
channel_connect_by_listen_address(struct ssh *ssh, const char *listen_host, |
u_short listen_port, char *ctype, char *rname) |
u_short listen_port, char *ctype, char *rname) |
{ |
{ |
int i; |
struct ssh_channels *sc = ssh->chanctxt; |
|
u_int i; |
|
ForwardPermission *fp; |
|
|
for (i = 0; i < num_permitted_opens; i++) { |
for (i = 0; i < sc->num_permitted_opens; i++) { |
if (open_listen_match_tcpip(&permitted_opens[i], listen_host, |
fp = &sc->permitted_opens[i]; |
listen_port, 1)) { |
if (open_listen_match_tcpip(fp, listen_host, listen_port, 1)) { |
if (permitted_opens[i].downstream) |
if (fp->downstream) |
return permitted_opens[i].downstream; |
return fp->downstream; |
return connect_to( |
return connect_to(ssh, |
permitted_opens[i].host_to_connect, |
fp->host_to_connect, fp->port_to_connect, |
permitted_opens[i].port_to_connect, ctype, rname); |
ctype, rname); |
} |
} |
} |
} |
error("WARNING: Server requests forwarding for unknown listen_port %d", |
error("WARNING: Server requests forwarding for unknown listen_port %d", |
|
|
} |
} |
|
|
Channel * |
Channel * |
channel_connect_by_listen_path(const char *path, char *ctype, char *rname) |
channel_connect_by_listen_path(struct ssh *ssh, const char *path, |
|
char *ctype, char *rname) |
{ |
{ |
int i; |
struct ssh_channels *sc = ssh->chanctxt; |
|
u_int i; |
|
ForwardPermission *fp; |
|
|
for (i = 0; i < num_permitted_opens; i++) { |
for (i = 0; i < sc->num_permitted_opens; i++) { |
if (open_listen_match_streamlocal(&permitted_opens[i], path)) { |
fp = &sc->permitted_opens[i]; |
return connect_to( |
if (open_listen_match_streamlocal(fp, path)) { |
permitted_opens[i].host_to_connect, |
return connect_to(ssh, |
permitted_opens[i].port_to_connect, ctype, rname); |
fp->host_to_connect, fp->port_to_connect, |
|
ctype, rname); |
} |
} |
} |
} |
error("WARNING: Server requests forwarding for unknown path %.100s", |
error("WARNING: Server requests forwarding for unknown path %.100s", |
|
|
|
|
/* Check if connecting to that port is permitted and connect. */ |
/* Check if connecting to that port is permitted and connect. */ |
Channel * |
Channel * |
channel_connect_to_port(const char *host, u_short port, char *ctype, |
channel_connect_to_port(struct ssh *ssh, const char *host, u_short port, |
char *rname, int *reason, const char **errmsg) |
char *ctype, char *rname, int *reason, const char **errmsg) |
{ |
{ |
int i, permit, permit_adm = 1; |
struct ssh_channels *sc = ssh->chanctxt; |
|
u_int i, permit, permit_adm = 1; |
|
ForwardPermission *fp; |
|
|
permit = all_opens_permitted; |
permit = sc->all_opens_permitted; |
if (!permit) { |
if (!permit) { |
for (i = 0; i < num_permitted_opens; i++) |
for (i = 0; i < sc->num_permitted_opens; i++) { |
if (open_match(&permitted_opens[i], host, port)) { |
fp = &sc->permitted_opens[i]; |
|
if (open_match(fp, host, port)) { |
permit = 1; |
permit = 1; |
break; |
break; |
} |
} |
|
} |
} |
} |
|
|
if (num_adm_permitted_opens > 0) { |
if (sc->num_adm_permitted_opens > 0) { |
permit_adm = 0; |
permit_adm = 0; |
for (i = 0; i < num_adm_permitted_opens; i++) |
for (i = 0; i < sc->num_adm_permitted_opens; i++) { |
if (open_match(&permitted_adm_opens[i], host, port)) { |
fp = &sc->permitted_adm_opens[i]; |
|
if (open_match(fp, host, port)) { |
permit_adm = 1; |
permit_adm = 1; |
break; |
break; |
} |
} |
|
} |
} |
} |
|
|
if (!permit || !permit_adm) { |
if (!permit || !permit_adm) { |
|
|
*reason = SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED; |
*reason = SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED; |
return NULL; |
return NULL; |
} |
} |
return connect_to_reason(host, port, ctype, rname, reason, errmsg); |
return connect_to_reason(ssh, host, port, ctype, rname, reason, errmsg); |
} |
} |
|
|
/* Check if connecting to that path is permitted and connect. */ |
/* Check if connecting to that path is permitted and connect. */ |
Channel * |
Channel * |
channel_connect_to_path(const char *path, char *ctype, char *rname) |
channel_connect_to_path(struct ssh *ssh, const char *path, |
|
char *ctype, char *rname) |
{ |
{ |
int i, permit, permit_adm = 1; |
struct ssh_channels *sc = ssh->chanctxt; |
|
u_int i, permit, permit_adm = 1; |
|
ForwardPermission *fp; |
|
|
permit = all_opens_permitted; |
permit = sc->all_opens_permitted; |
if (!permit) { |
if (!permit) { |
for (i = 0; i < num_permitted_opens; i++) |
for (i = 0; i < sc->num_permitted_opens; i++) { |
if (open_match(&permitted_opens[i], path, PORT_STREAMLOCAL)) { |
fp = &sc->permitted_opens[i]; |
|
if (open_match(fp, path, PORT_STREAMLOCAL)) { |
permit = 1; |
permit = 1; |
break; |
break; |
} |
} |
|
} |
} |
} |
|
|
if (num_adm_permitted_opens > 0) { |
if (sc->num_adm_permitted_opens > 0) { |
permit_adm = 0; |
permit_adm = 0; |
for (i = 0; i < num_adm_permitted_opens; i++) |
for (i = 0; i < sc->num_adm_permitted_opens; i++) { |
if (open_match(&permitted_adm_opens[i], path, PORT_STREAMLOCAL)) { |
fp = &sc->permitted_adm_opens[i]; |
|
if (open_match(fp, path, PORT_STREAMLOCAL)) { |
permit_adm = 1; |
permit_adm = 1; |
break; |
break; |
} |
} |
|
} |
} |
} |
|
|
if (!permit || !permit_adm) { |
if (!permit || !permit_adm) { |
|
|
"but the request was denied.", path); |
"but the request was denied.", path); |
return NULL; |
return NULL; |
} |
} |
return connect_to(path, PORT_STREAMLOCAL, ctype, rname); |
return connect_to(ssh, path, PORT_STREAMLOCAL, ctype, rname); |
} |
} |
|
|
void |
void |
channel_send_window_changes(void) |
channel_send_window_changes(struct ssh *ssh) |
{ |
{ |
u_int i; |
struct ssh_channels *sc = ssh->chanctxt; |
struct winsize ws; |
struct winsize ws; |
|
int r; |
|
u_int i; |
|
|
for (i = 0; i < channels_alloc; i++) { |
for (i = 0; i < sc->channels_alloc; i++) { |
if (channels[i] == NULL || !channels[i]->client_tty || |
if (sc->channels[i] == NULL || !sc->channels[i]->client_tty || |
channels[i]->type != SSH_CHANNEL_OPEN) |
sc->channels[i]->type != SSH_CHANNEL_OPEN) |
continue; |
continue; |
if (ioctl(channels[i]->rfd, TIOCGWINSZ, &ws) < 0) |
if (ioctl(sc->channels[i]->rfd, TIOCGWINSZ, &ws) < 0) |
continue; |
continue; |
channel_request_start(i, "window-change", 0); |
channel_request_start(ssh, i, "window-change", 0); |
packet_put_int((u_int)ws.ws_col); |
if ((r = sshpkt_put_u32(ssh, (u_int)ws.ws_col)) != 0 || |
packet_put_int((u_int)ws.ws_row); |
(r = sshpkt_put_u32(ssh, (u_int)ws.ws_row)) != 0 || |
packet_put_int((u_int)ws.ws_xpixel); |
(r = sshpkt_put_u32(ssh, (u_int)ws.ws_xpixel)) != 0 || |
packet_put_int((u_int)ws.ws_ypixel); |
(r = sshpkt_put_u32(ssh, (u_int)ws.ws_ypixel)) != 0 || |
packet_send(); |
(r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: channel %u: send window-change: %s", |
|
__func__, i, ssh_err(r)); |
} |
} |
} |
} |
|
|
|
|
* stored in display_numberp , or -1 if an error occurs. |
* stored in display_numberp , or -1 if an error occurs. |
*/ |
*/ |
int |
int |
x11_create_display_inet(int x11_display_offset, int x11_use_localhost, |
x11_create_display_inet(struct ssh *ssh, int x11_display_offset, |
int single_connection, u_int *display_numberp, int **chanids) |
int x11_use_localhost, int single_connection, |
|
u_int *display_numberp, int **chanids) |
{ |
{ |
Channel *nc = NULL; |
Channel *nc = NULL; |
int display_number, sock; |
int display_number, sock; |
|
|
display_number++) { |
display_number++) { |
port = 6000 + display_number; |
port = 6000 + display_number; |
memset(&hints, 0, sizeof(hints)); |
memset(&hints, 0, sizeof(hints)); |
hints.ai_family = IPv4or6; |
hints.ai_family = ssh->chanctxt->IPv4or6; |
hints.ai_flags = x11_use_localhost ? 0: AI_PASSIVE; |
hints.ai_flags = x11_use_localhost ? 0: AI_PASSIVE; |
hints.ai_socktype = SOCK_STREAM; |
hints.ai_socktype = SOCK_STREAM; |
snprintf(strport, sizeof strport, "%d", port); |
snprintf(strport, sizeof strport, "%d", port); |
if ((gaierr = getaddrinfo(NULL, strport, &hints, &aitop)) != 0) { |
if ((gaierr = getaddrinfo(NULL, strport, |
|
&hints, &aitop)) != 0) { |
error("getaddrinfo: %.100s", ssh_gai_strerror(gaierr)); |
error("getaddrinfo: %.100s", ssh_gai_strerror(gaierr)); |
return -1; |
return -1; |
} |
} |
for (ai = aitop; ai; ai = ai->ai_next) { |
for (ai = aitop; ai; ai = ai->ai_next) { |
if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) |
if (ai->ai_family != AF_INET && |
|
ai->ai_family != AF_INET6) |
continue; |
continue; |
sock = socket(ai->ai_family, ai->ai_socktype, |
sock = socket(ai->ai_family, ai->ai_socktype, |
ai->ai_protocol); |
ai->ai_protocol); |
|
|
} |
} |
channel_set_reuseaddr(sock); |
channel_set_reuseaddr(sock); |
if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { |
if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { |
debug2("bind port %d: %.100s", port, strerror(errno)); |
debug2("%s: bind port %d: %.100s", __func__, |
|
port, strerror(errno)); |
close(sock); |
close(sock); |
|
for (n = 0; n < num_socks; n++) |
for (n = 0; n < num_socks; n++) { |
|
close(socks[n]); |
close(socks[n]); |
} |
|
num_socks = 0; |
num_socks = 0; |
break; |
break; |
} |
} |
|
|
*chanids = xcalloc(num_socks + 1, sizeof(**chanids)); |
*chanids = xcalloc(num_socks + 1, sizeof(**chanids)); |
for (n = 0; n < num_socks; n++) { |
for (n = 0; n < num_socks; n++) { |
sock = socks[n]; |
sock = socks[n]; |
nc = channel_new("x11 listener", |
nc = channel_new(ssh, "x11 listener", |
SSH_CHANNEL_X11_LISTENER, sock, sock, -1, |
SSH_CHANNEL_X11_LISTENER, sock, sock, -1, |
CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, |
CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, |
0, "X11 inet listener", 1); |
0, "X11 inet listener", 1); |
|
|
|
|
/* Return the display number for the DISPLAY environment variable. */ |
/* Return the display number for the DISPLAY environment variable. */ |
*display_numberp = display_number; |
*display_numberp = display_number; |
return (0); |
return 0; |
} |
} |
|
|
static int |
static int |
|
|
} |
} |
|
|
int |
int |
x11_connect_display(void) |
x11_connect_display(struct ssh *ssh) |
{ |
{ |
u_int display_number; |
u_int display_number; |
const char *display; |
const char *display; |
|
|
if (strncmp(display, "unix:", 5) == 0 || |
if (strncmp(display, "unix:", 5) == 0 || |
display[0] == ':') { |
display[0] == ':') { |
/* Connect to the unix domain socket. */ |
/* Connect to the unix domain socket. */ |
if (sscanf(strrchr(display, ':') + 1, "%u", &display_number) != 1) { |
if (sscanf(strrchr(display, ':') + 1, "%u", |
error("Could not parse display number from DISPLAY: %.100s", |
&display_number) != 1) { |
display); |
error("Could not parse display number from DISPLAY: " |
|
"%.100s", display); |
return -1; |
return -1; |
} |
} |
/* Create a socket. */ |
/* Create a socket. */ |
|
|
return -1; |
return -1; |
} |
} |
*cp = 0; |
*cp = 0; |
/* buf now contains the host name. But first we parse the display number. */ |
/* |
|
* buf now contains the host name. But first we parse the |
|
* display number. |
|
*/ |
if (sscanf(cp + 1, "%u", &display_number) != 1) { |
if (sscanf(cp + 1, "%u", &display_number) != 1) { |
error("Could not parse display number from DISPLAY: %.100s", |
error("Could not parse display number from DISPLAY: %.100s", |
display); |
display); |
|
|
|
|
/* Look up the host address */ |
/* Look up the host address */ |
memset(&hints, 0, sizeof(hints)); |
memset(&hints, 0, sizeof(hints)); |
hints.ai_family = IPv4or6; |
hints.ai_family = ssh->chanctxt->IPv4or6; |
hints.ai_socktype = SOCK_STREAM; |
hints.ai_socktype = SOCK_STREAM; |
snprintf(strport, sizeof strport, "%u", 6000 + display_number); |
snprintf(strport, sizeof strport, "%u", 6000 + display_number); |
if ((gaierr = getaddrinfo(buf, strport, &hints, &aitop)) != 0) { |
if ((gaierr = getaddrinfo(buf, strport, &hints, &aitop)) != 0) { |
|
|
} |
} |
freeaddrinfo(aitop); |
freeaddrinfo(aitop); |
if (!ai) { |
if (!ai) { |
error("connect %.100s port %u: %.100s", buf, 6000 + display_number, |
error("connect %.100s port %u: %.100s", buf, |
strerror(errno)); |
6000 + display_number, strerror(errno)); |
return -1; |
return -1; |
} |
} |
set_nodelay(sock); |
set_nodelay(sock); |
|
|
* This should be called in the client only. |
* This should be called in the client only. |
*/ |
*/ |
void |
void |
x11_request_forwarding_with_spoofing(int client_session_id, const char *disp, |
x11_request_forwarding_with_spoofing(struct ssh *ssh, int client_session_id, |
const char *proto, const char *data, int want_reply) |
const char *disp, const char *proto, const char *data, int want_reply) |
{ |
{ |
|
struct ssh_channels *sc = ssh->chanctxt; |
u_int data_len = (u_int) strlen(data) / 2; |
u_int data_len = (u_int) strlen(data) / 2; |
u_int i, value; |
u_int i, value; |
char *new_data; |
|
int screen_number; |
|
const char *cp; |
const char *cp; |
|
char *new_data; |
|
int r, screen_number; |
|
|
if (x11_saved_display == NULL) |
if (sc->x11_saved_display == NULL) |
x11_saved_display = xstrdup(disp); |
sc->x11_saved_display = xstrdup(disp); |
else if (strcmp(disp, x11_saved_display) != 0) { |
else if (strcmp(disp, sc->x11_saved_display) != 0) { |
error("x11_request_forwarding_with_spoofing: different " |
error("x11_request_forwarding_with_spoofing: different " |
"$DISPLAY already forwarded"); |
"$DISPLAY already forwarded"); |
return; |
return; |
|
|
else |
else |
screen_number = 0; |
screen_number = 0; |
|
|
if (x11_saved_proto == NULL) { |
if (sc->x11_saved_proto == NULL) { |
/* Save protocol name. */ |
/* Save protocol name. */ |
x11_saved_proto = xstrdup(proto); |
sc->x11_saved_proto = xstrdup(proto); |
|
|
/* Extract real authentication data. */ |
/* Extract real authentication data. */ |
x11_saved_data = xmalloc(data_len); |
sc->x11_saved_data = xmalloc(data_len); |
for (i = 0; i < data_len; i++) { |
for (i = 0; i < data_len; i++) { |
if (sscanf(data + 2 * i, "%2x", &value) != 1) |
if (sscanf(data + 2 * i, "%2x", &value) != 1) |
fatal("x11_request_forwarding: bad " |
fatal("x11_request_forwarding: bad " |
"authentication data: %.100s", data); |
"authentication data: %.100s", data); |
x11_saved_data[i] = value; |
sc->x11_saved_data[i] = value; |
} |
} |
x11_saved_data_len = data_len; |
sc->x11_saved_data_len = data_len; |
|
|
/* Generate fake data of the same length. */ |
/* Generate fake data of the same length. */ |
x11_fake_data = xmalloc(data_len); |
sc->x11_fake_data = xmalloc(data_len); |
arc4random_buf(x11_fake_data, data_len); |
arc4random_buf(sc->x11_fake_data, data_len); |
x11_fake_data_len = data_len; |
sc->x11_fake_data_len = data_len; |
} |
} |
|
|
/* Convert the fake data into hex. */ |
/* Convert the fake data into hex. */ |
new_data = tohex(x11_fake_data, data_len); |
new_data = tohex(sc->x11_fake_data, data_len); |
|
|
/* Send the request packet. */ |
/* Send the request packet. */ |
channel_request_start(client_session_id, "x11-req", want_reply); |
channel_request_start(ssh, client_session_id, "x11-req", want_reply); |
packet_put_char(0); /* XXX bool single connection */ |
if ((r = sshpkt_put_u8(ssh, 0)) != 0 || /* bool: single connection */ |
packet_put_cstring(proto); |
(r = sshpkt_put_cstring(ssh, proto)) != 0 || |
packet_put_cstring(new_data); |
(r = sshpkt_put_cstring(ssh, new_data)) != 0 || |
packet_put_int(screen_number); |
(r = sshpkt_put_u32(ssh, screen_number)) != 0 || |
packet_send(); |
(r = sshpkt_send(ssh)) != 0 || |
packet_write_wait(); |
(r = ssh_packet_write_wait(ssh)) != 0) |
|
fatal("%s: send x11-req: %s", __func__, ssh_err(r)); |
free(new_data); |
free(new_data); |
} |
} |