version 1.214.2.1, 2005/09/04 18:40:01 |
version 1.214.2.2, 2006/02/03 02:53:44 |
|
|
|
|
/* -- channel core */ |
/* -- channel core */ |
|
|
#define CHAN_RBUF 16*1024 |
|
|
|
/* |
/* |
* Pointer to an array containing all allocated channels. The array is |
* Pointer to an array containing all allocated channels. The array is |
* dynamically extended as needed. |
* dynamically extended as needed. |
|
|
/* -- channel core */ |
/* -- channel core */ |
|
|
Channel * |
Channel * |
channel_lookup(int id) |
channel_by_id(int id) |
{ |
{ |
Channel *c; |
Channel *c; |
|
|
if (id < 0 || (u_int)id >= channels_alloc) { |
if (id < 0 || (u_int)id >= channels_alloc) { |
logit("channel_lookup: %d: bad id", id); |
logit("channel_by_id: %d: bad id", id); |
return NULL; |
return NULL; |
} |
} |
c = channels[id]; |
c = channels[id]; |
if (c == NULL) { |
if (c == NULL) { |
logit("channel_lookup: %d: bad id: channel free", id); |
logit("channel_by_id: %d: bad id: channel free", id); |
return NULL; |
return NULL; |
} |
} |
return c; |
return c; |
} |
} |
|
|
/* |
/* |
|
* Returns the channel if it is allowed to receive protocol messages. |
|
* Private channels, like listening sockets, may not receive messages. |
|
*/ |
|
Channel * |
|
channel_lookup(int id) |
|
{ |
|
Channel *c; |
|
|
|
if ((c = channel_by_id(id)) == NULL) |
|
return (NULL); |
|
|
|
switch(c->type) { |
|
case SSH_CHANNEL_X11_OPEN: |
|
case SSH_CHANNEL_LARVAL: |
|
case SSH_CHANNEL_CONNECTING: |
|
case SSH_CHANNEL_DYNAMIC: |
|
case SSH_CHANNEL_OPENING: |
|
case SSH_CHANNEL_OPEN: |
|
case SSH_CHANNEL_INPUT_DRAINING: |
|
case SSH_CHANNEL_OUTPUT_DRAINING: |
|
return (c); |
|
break; |
|
} |
|
logit("Non-public channel %d, type %d.", id, c->type); |
|
return (NULL); |
|
} |
|
|
|
/* |
* Register filedescriptors for a channel, used when allocating a channel or |
* Register filedescriptors for a channel, used when allocating a channel or |
* when the channel consumer/producer is ready, e.g. shell exec'd |
* when the channel consumer/producer is ready, e.g. shell exec'd |
*/ |
*/ |
|
|
c->force_drain = 0; |
c->force_drain = 0; |
c->single_connection = 0; |
c->single_connection = 0; |
c->detach_user = NULL; |
c->detach_user = NULL; |
|
c->detach_close = 0; |
c->confirm = NULL; |
c->confirm = NULL; |
c->confirm_ctx = NULL; |
c->confirm_ctx = NULL; |
c->input_filter = NULL; |
c->input_filter = NULL; |
|
c->output_filter = NULL; |
debug("channel %d: new [%s]", found, remote_name); |
debug("channel %d: new [%s]", found, remote_name); |
return c; |
return c; |
} |
} |
|
|
c->confirm_ctx = ctx; |
c->confirm_ctx = ctx; |
} |
} |
void |
void |
channel_register_cleanup(int id, channel_callback_fn *fn) |
channel_register_cleanup(int id, channel_callback_fn *fn, int do_close) |
{ |
{ |
Channel *c = channel_lookup(id); |
Channel *c = channel_by_id(id); |
|
|
if (c == NULL) { |
if (c == NULL) { |
logit("channel_register_cleanup: %d: bad id", id); |
logit("channel_register_cleanup: %d: bad id", id); |
return; |
return; |
} |
} |
c->detach_user = fn; |
c->detach_user = fn; |
|
c->detach_close = do_close; |
} |
} |
void |
void |
channel_cancel_cleanup(int id) |
channel_cancel_cleanup(int id) |
{ |
{ |
Channel *c = channel_lookup(id); |
Channel *c = channel_by_id(id); |
|
|
if (c == NULL) { |
if (c == NULL) { |
logit("channel_cancel_cleanup: %d: bad id", id); |
logit("channel_cancel_cleanup: %d: bad id", id); |
return; |
return; |
} |
} |
c->detach_user = NULL; |
c->detach_user = NULL; |
|
c->detach_close = 0; |
} |
} |
void |
void |
channel_register_filter(int id, channel_filter_fn *fn) |
channel_register_filter(int id, channel_infilter_fn *ifn, |
|
channel_outfilter_fn *ofn) |
{ |
{ |
Channel *c = channel_lookup(id); |
Channel *c = channel_lookup(id); |
|
|
|
|
logit("channel_register_filter: %d: bad id", id); |
logit("channel_register_filter: %d: bad id", id); |
return; |
return; |
} |
} |
c->input_filter = fn; |
c->input_filter = ifn; |
|
c->output_filter = ofn; |
} |
} |
|
|
void |
void |
|
|
xfree(remote_ipaddr); |
xfree(remote_ipaddr); |
} |
} |
|
|
|
static void |
|
channel_set_reuseaddr(int fd) |
|
{ |
|
int on = 1; |
|
|
|
/* |
|
* Set socket options. |
|
* Allow local port reuse in TIME_WAIT. |
|
*/ |
|
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) |
|
error("setsockopt SO_REUSEADDR fd %d: %s", fd, strerror(errno)); |
|
} |
|
|
/* |
/* |
* This socket is listening for connections to a forwarded TCP/IP port. |
* This socket is listening for connections to a forwarded TCP/IP port. |
*/ |
*/ |
|
|
debug2("channel %d: filter stops", c->self); |
debug2("channel %d: filter stops", c->self); |
chan_read_failed(c); |
chan_read_failed(c); |
} |
} |
|
} else if (c->datagram) { |
|
buffer_put_string(&c->input, buf, len); |
} else { |
} else { |
buffer_append(&c->input, buf, len); |
buffer_append(&c->input, buf, len); |
} |
} |
|
|
channel_handle_wfd(Channel *c, fd_set * readset, fd_set * writeset) |
channel_handle_wfd(Channel *c, fd_set * readset, fd_set * writeset) |
{ |
{ |
struct termios tio; |
struct termios tio; |
u_char *data; |
u_char *data = NULL, *buf; |
u_int dlen; |
u_int dlen; |
int len; |
int len; |
|
|
|
|
if (c->wfd != -1 && |
if (c->wfd != -1 && |
FD_ISSET(c->wfd, writeset) && |
FD_ISSET(c->wfd, writeset) && |
buffer_len(&c->output) > 0) { |
buffer_len(&c->output) > 0) { |
data = buffer_ptr(&c->output); |
if (c->output_filter != NULL) { |
dlen = buffer_len(&c->output); |
if ((buf = c->output_filter(c, &data, &dlen)) == NULL) { |
len = write(c->wfd, data, dlen); |
debug2("channel %d: filter stops", c->self); |
|
if (c->type != SSH_CHANNEL_OPEN) |
|
chan_mark_dead(c); |
|
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); |
|
} |
|
|
|
if (c->datagram) { |
|
/* ignore truncated writes, datagrams might get lost */ |
|
c->local_consumed += dlen + 4; |
|
len = write(c->wfd, buf, dlen); |
|
xfree(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; |
|
} |
|
return 1; |
|
} |
|
|
|
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) { |
|
|
} |
} |
return -1; |
return -1; |
} |
} |
if (compat20 && c->isatty && dlen >= 1 && data[0] != '\r') { |
if (compat20 && c->isatty && dlen >= 1 && buf[0] != '\r') { |
if (tcgetattr(c->wfd, &tio) == 0 && |
if (tcgetattr(c->wfd, &tio) == 0 && |
!(tio.c_lflag & ECHO) && (tio.c_lflag & ICANON)) { |
!(tio.c_lflag & ECHO) && (tio.c_lflag & ICANON)) { |
/* |
/* |
* Simulate echo to reduce the impact of |
* Simulate echo to reduce the impact of |
* traffic analysis. We need to match the |
* traffic analysis. We need to match the |
* size of a SSH2_MSG_CHANNEL_DATA message |
* size of a SSH2_MSG_CHANNEL_DATA message |
* (4 byte channel id + data) |
* (4 byte channel id + buf) |
*/ |
*/ |
packet_send_ignore(4 + len); |
packet_send_ignore(4 + len); |
packet_send(); |
packet_send(); |
|
|
if (c == NULL) |
if (c == NULL) |
return; |
return; |
if (c->detach_user != NULL) { |
if (c->detach_user != NULL) { |
if (!chan_is_dead(c, 0)) |
if (!chan_is_dead(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(c->self, NULL); |
|
|
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) { |
(len = buffer_len(&c->input)) > 0) { |
|
if (c->datagram) { |
|
if (len > 0) { |
|
u_char *data; |
|
u_int dlen; |
|
|
|
data = buffer_get_string(&c->input, |
|
&dlen); |
|
packet_start(SSH2_MSG_CHANNEL_DATA); |
|
packet_put_int(c->remote_id); |
|
packet_put_string(data, dlen); |
|
packet_send(); |
|
c->remote_window -= dlen + 4; |
|
xfree(data); |
|
} |
|
continue; |
|
} |
/* |
/* |
* Send some data for the other side over the secure |
* Send some data for the other side over the secure |
* connection. |
* connection. |
|
|
c->local_window -= data_len; |
c->local_window -= data_len; |
} |
} |
packet_check_eom(); |
packet_check_eom(); |
buffer_append(&c->output, data, data_len); |
if (c->datagram) |
|
buffer_put_string(&c->output, data, data_len); |
|
else |
|
buffer_append(&c->output, data, data_len); |
xfree(data); |
xfree(data); |
} |
} |
|
|
|
|
id = packet_get_int(); |
id = packet_get_int(); |
c = channel_lookup(id); |
c = channel_lookup(id); |
|
|
if (c == NULL || c->type != SSH_CHANNEL_OPEN) { |
if (c == NULL) { |
logit("Received window adjust for " |
logit("Received window adjust for non-open channel %d.", id); |
"non-open channel %d.", id); |
|
return; |
return; |
} |
} |
adjust = packet_get_int(); |
adjust = packet_get_int(); |
|
|
const char *host_to_connect, u_short port_to_connect, int gateway_ports) |
const char *host_to_connect, u_short port_to_connect, int gateway_ports) |
{ |
{ |
Channel *c; |
Channel *c; |
int sock, r, success = 0, on = 1, wildcard = 0, is_client; |
int sock, r, success = 0, wildcard = 0, is_client; |
struct addrinfo hints, *ai, *aitop; |
struct addrinfo hints, *ai, *aitop; |
const char *host, *addr; |
const char *host, *addr; |
char ntop[NI_MAXHOST], strport[NI_MAXSERV]; |
char ntop[NI_MAXHOST], strport[NI_MAXSERV]; |
|
|
verbose("socket: %.100s", strerror(errno)); |
verbose("socket: %.100s", strerror(errno)); |
continue; |
continue; |
} |
} |
/* |
|
* Set socket options. |
|
* Allow local port reuse in TIME_WAIT. |
|
*/ |
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, |
|
sizeof(on)) == -1) |
|
error("setsockopt SO_REUSEADDR: %s", strerror(errno)); |
|
|
|
|
channel_set_reuseaddr(sock); |
|
|
debug("Local forwarding listening on %s port %s.", ntop, strport); |
debug("Local forwarding listening on %s port %s.", ntop, strport); |
|
|
/* Bind the socket to the address. */ |
/* Bind the socket to the address. */ |
|
|
|
|
permitted_opens[i].listen_port = 0; |
permitted_opens[i].listen_port = 0; |
permitted_opens[i].port_to_connect = 0; |
permitted_opens[i].port_to_connect = 0; |
free(permitted_opens[i].host_to_connect); |
xfree(permitted_opens[i].host_to_connect); |
permitted_opens[i].host_to_connect = NULL; |
permitted_opens[i].host_to_connect = NULL; |
} |
} |
|
|
|
|
char strport[NI_MAXSERV]; |
char strport[NI_MAXSERV]; |
int gaierr, n, num_socks = 0, socks[NUM_SOCKS]; |
int gaierr, n, num_socks = 0, socks[NUM_SOCKS]; |
|
|
|
if (chanids == NULL) |
|
return -1; |
|
|
for (display_number = x11_display_offset; |
for (display_number = x11_display_offset; |
display_number < MAX_DISPLAYS; |
display_number < MAX_DISPLAYS; |
display_number++) { |
display_number++) { |
|
|
freeaddrinfo(aitop); |
freeaddrinfo(aitop); |
return -1; |
return -1; |
} |
} |
|
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("bind port %d: %.100s", port, strerror(errno)); |
close(sock); |
close(sock); |
|
|
} |
} |
|
|
/* Allocate a channel for each socket. */ |
/* Allocate a channel for each socket. */ |
if (chanids != NULL) |
*chanids = xmalloc(sizeof(**chanids) * (num_socks + 1)); |
*chanids = xmalloc(sizeof(**chanids) * (num_socks + 1)); |
|
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("x11 listener", |
|
|
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); |
nc->single_connection = single_connection; |
nc->single_connection = single_connection; |
if (*chanids != NULL) |
(*chanids)[n] = nc->self; |
(*chanids)[n] = nc->self; |
|
} |
} |
if (*chanids != NULL) |
(*chanids)[n] = -1; |
(*chanids)[n] = -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; |
|
|
error("deny_input_open: type %d", type); |
error("deny_input_open: type %d", type); |
break; |
break; |
} |
} |
error("Warning: this is probably a break in attempt by a malicious server."); |
error("Warning: this is probably a break-in attempt by a malicious server."); |
packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); |
packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); |
packet_put_int(rchan); |
packet_put_int(rchan); |
packet_send(); |
packet_send(); |