diff options
author | Damien Miller <djm@mindrot.org> | 2000-04-01 11:09:21 +1000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2000-04-01 11:09:21 +1000 |
commit | b38eff8e4ff901df9cf1113a9f14d64c3565a401 (patch) | |
tree | 9a856898f15f7760ed95c5d47789a6f954b4ad2f /channels.c | |
parent | 450a7a1ff40fe7c2d84c93b83cf2df53445d807d (diff) |
- Big OpenBSD CVS update (mainly beginnings of SSH2 infrastructure)
- [auth.c session.c sshd.c auth.h]
split sshd.c -> auth.c session.c sshd.c plus cleanup and goto-removal
- [bufaux.c bufaux.h]
support ssh2 bignums
- [channels.c channels.h clientloop.c sshd.c nchan.c nchan.h packet.c]
[readconf.c ssh.c ssh.h serverloop.c]
replace big switch() with function tables (prepare for ssh2)
- [ssh2.h]
ssh2 message type codes
- [sshd.8]
reorder Xr to avoid cutting
- [serverloop.c]
close(fdin) if fdin != fdout, shutdown otherwise, ok theo@
- [channels.c]
missing close
allow bigger packets
- [cipher.c cipher.h]
support ssh2 ciphers
- [compress.c]
cleanup, less code
- [dispatch.c dispatch.h]
function tables for different message types
- [log-server.c]
do not log() if debuggin to stderr
rename a cpp symbol, to avoid param.h collision
- [mpaux.c]
KNF
- [nchan.c]
sync w/ channels.c
Diffstat (limited to 'channels.c')
-rw-r--r-- | channels.c | 1194 |
1 files changed, 682 insertions, 512 deletions
@@ -16,7 +16,7 @@ */ #include "includes.h" -RCSID("$Id: channels.c,v 1.19 2000/03/17 12:40:15 damien Exp $"); +RCSID("$Id: channels.c,v 1.20 2000/04/01 01:09:23 damien Exp $"); #include "ssh.h" #include "packet.h" @@ -37,6 +37,10 @@ RCSID("$Id: channels.c,v 1.19 2000/03/17 12:40:15 damien Exp $"); /* Max len of agent socket */ #define MAX_SOCKET_NAME 100 +/* default buffer for tcp-fwd-channel */ +#define CHAN_WINDOW_DEFAULT (8*1024) +#define CHAN_PACKET_DEFAULT (CHAN_WINDOW_DEFAULT/2) + /* * Pointer to an array containing all allocated channels. The array is * dynamically extended as needed. @@ -81,8 +85,9 @@ unsigned int x11_fake_data_len; * network (which might be behind a firewall). */ typedef struct { - char *host; /* Host name. */ - u_short port; /* Port number. */ + char *host_to_connect; /* Connect to 'host'. */ + u_short port_to_connect; /* Connect to 'port'. */ + u_short listen_port; /* Remote side should listen port number. */ } ForwardPermission; /* List of all permitted host/port pairs to connect. */ @@ -119,20 +124,43 @@ channel_permit_all_opens() all_opens_permitted = 1; } +/* lookup channel by id */ + +Channel * +channel_lookup(int id) +{ + Channel *c; + if (id < 0 && id > channels_alloc) { + log("channel_lookup: %d: bad id", id); + return NULL; + } + c = &channels[id]; + if (c->type == SSH_CHANNEL_FREE) { + log("channel_lookup: %d: bad id: channel free", id); + return NULL; + } + return c; +} + /* * Allocate a new channel object and set its type and socket. This will cause * remote_name to be freed. */ int -channel_allocate(int type, int sock, char *remote_name) +channel_new(char *ctype, int type, int rfd, int wfd, int efd, + int window, int maxpack, int extended_usage, char *remote_name) { int i, found; Channel *c; /* Update the maximum file descriptor value. */ - if (sock > channel_max_fd_value) - channel_max_fd_value = sock; + if (rfd > channel_max_fd_value) + channel_max_fd_value = rfd; + if (wfd > channel_max_fd_value) + channel_max_fd_value = wfd; + if (efd > channel_max_fd_value) + channel_max_fd_value = efd; /* XXX set close-on-exec -markus */ /* Do initial allocation if this is the first call. */ @@ -167,388 +195,514 @@ channel_allocate(int type, int sock, char *remote_name) c = &channels[found]; buffer_init(&c->input); buffer_init(&c->output); + buffer_init(&c->extended); chan_init_iostates(c); c->self = found; c->type = type; - c->sock = sock; + c->ctype = ctype; + c->local_window = window; + c->local_window_max = window; + c->local_consumed = 0; + c->local_maxpacket = maxpack; + c->remote_window = 0; + c->remote_maxpacket = 0; + c->rfd = rfd; + c->wfd = wfd; + c->sock = (rfd == wfd) ? rfd : -1; + c->efd = efd; + c->extended_usage = extended_usage; c->remote_id = -1; c->remote_name = remote_name; + c->remote_window = 0; + c->remote_maxpacket = 0; + c->cb_fn = NULL; + c->cb_arg = NULL; + c->cb_event = 0; + c->dettach_user = NULL; debug("channel %d: new [%s]", found, remote_name); return found; } +int +channel_allocate(int type, int sock, char *remote_name) +{ + return channel_new("", type, sock, sock, -1, 0, 0, 0, remote_name); +} /* Free the channel and close its socket. */ void -channel_free(int channel) +channel_free(int id) { - if (channel < 0 || channel >= channels_alloc || - channels[channel].type == SSH_CHANNEL_FREE) - packet_disconnect("channel free: bad local channel %d", channel); - - if (compat13) - shutdown(channels[channel].sock, SHUT_RDWR); - close(channels[channel].sock); - buffer_free(&channels[channel].input); - buffer_free(&channels[channel].output); - channels[channel].type = SSH_CHANNEL_FREE; - if (channels[channel].remote_name) { - xfree(channels[channel].remote_name); - channels[channel].remote_name = NULL; + Channel *c = channel_lookup(id); + if (c == NULL) + packet_disconnect("channel free: bad local channel %d", id); + debug("channel_free: channel %d: status: %s", id, channel_open_message()); + if (c->sock != -1) { + shutdown(c->sock, SHUT_RDWR); + close(c->sock); + } + buffer_free(&c->input); + buffer_free(&c->output); + buffer_free(&c->extended); + c->type = SSH_CHANNEL_FREE; + if (c->remote_name) { + xfree(c->remote_name); + c->remote_name = NULL; } } /* - * This is called just before select() to add any bits relevant to channels - * in the select bitmasks. + * '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]; -void -channel_prepare_select(fd_set * readset, fd_set * writeset) +void +channel_pre_listener(Channel *c, fd_set * readset, fd_set * writeset) { - int i; - Channel *ch; - unsigned char *ucp; - unsigned int proto_len, data_len; + FD_SET(c->sock, readset); +} - for (i = 0; i < channels_alloc; i++) { - ch = &channels[i]; -redo: - switch (ch->type) { - case SSH_CHANNEL_X11_LISTENER: - case SSH_CHANNEL_PORT_LISTENER: - case SSH_CHANNEL_AUTH_SOCKET: - FD_SET(ch->sock, readset); - break; +void +channel_pre_open_13(Channel *c, fd_set * readset, fd_set * writeset) +{ + if (buffer_len(&c->input) < packet_get_maxsize()) + FD_SET(c->sock, readset); + if (buffer_len(&c->output) > 0) + FD_SET(c->sock, writeset); +} - case SSH_CHANNEL_OPEN: - if (compat13) { - if (buffer_len(&ch->input) < packet_get_maxsize()) - FD_SET(ch->sock, readset); - if (buffer_len(&ch->output) > 0) - FD_SET(ch->sock, writeset); - break; - } - /* test whether sockets are 'alive' for read/write */ - if (ch->istate == CHAN_INPUT_OPEN) - if (buffer_len(&ch->input) < packet_get_maxsize()) - FD_SET(ch->sock, readset); - if (ch->ostate == CHAN_OUTPUT_OPEN || - ch->ostate == CHAN_OUTPUT_WAIT_DRAIN) { - if (buffer_len(&ch->output) > 0) { - FD_SET(ch->sock, writeset); - } else if (ch->ostate == CHAN_OUTPUT_WAIT_DRAIN) { - chan_obuf_empty(ch); - } - } - break; +void +channel_pre_open_15(Channel *c, fd_set * readset, fd_set * writeset) +{ + /* test whether sockets are 'alive' for read/write */ + if (c->istate == CHAN_INPUT_OPEN) + if (buffer_len(&c->input) < packet_get_maxsize()) + FD_SET(c->sock, readset); + if (c->ostate == CHAN_OUTPUT_OPEN || + c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { + if (buffer_len(&c->output) > 0) { + FD_SET(c->sock, writeset); + } else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { + chan_obuf_empty(c); + } + } +} - case SSH_CHANNEL_INPUT_DRAINING: - if (!compat13) - fatal("cannot happen: IN_DRAIN"); - if (buffer_len(&ch->input) == 0) { - packet_start(SSH_MSG_CHANNEL_CLOSE); - packet_put_int(ch->remote_id); - packet_send(); - ch->type = SSH_CHANNEL_CLOSED; - debug("Closing channel %d after input drain.", ch->self); - break; - } - break; +void +channel_pre_input_draining(Channel *c, fd_set * readset, fd_set * writeset) +{ + if (buffer_len(&c->input) == 0) { + packet_start(SSH_MSG_CHANNEL_CLOSE); + packet_put_int(c->remote_id); + packet_send(); + c->type = SSH_CHANNEL_CLOSED; + debug("Closing channel %d after input drain.", c->self); + } +} - case SSH_CHANNEL_OUTPUT_DRAINING: - if (!compat13) - fatal("cannot happen: OUT_DRAIN"); - if (buffer_len(&ch->output) == 0) { - channel_free(i); - break; - } - FD_SET(ch->sock, writeset); - break; +void +channel_pre_output_draining(Channel *c, fd_set * readset, fd_set * writeset) +{ + if (buffer_len(&c->output) == 0) + channel_free(c->self); + else + FD_SET(c->sock, writeset); +} - case SSH_CHANNEL_X11_OPEN: - /* - * This is a special state for X11 authentication - * spoofing. An opened X11 connection (when - * authentication spoofing is being done) remains in - * this state until the first packet has been - * completely read. The authentication data in that - * packet is then substituted by the real data if it - * matches the fake data, and the channel is put into - * normal mode. - */ - /* Check if the fixed size part of the packet is in buffer. */ - if (buffer_len(&ch->output) < 12) - break; +/* + * This is a special state for X11 authentication spoofing. An opened X11 + * connection (when authentication spoofing is being done) remains in this + * state until the first packet has been completely read. The authentication + * data in that packet is then substituted by the real data if it matches the + * fake data, and the channel is put into normal mode. + */ +int +x11_open_helper(Channel *c) +{ + unsigned char *ucp; + unsigned int proto_len, data_len; - /* Parse the lengths of variable-length fields. */ - ucp = (unsigned char *) buffer_ptr(&ch->output); - if (ucp[0] == 0x42) { /* Byte order MSB first. */ - proto_len = 256 * ucp[6] + ucp[7]; - data_len = 256 * ucp[8] + ucp[9]; - } else if (ucp[0] == 0x6c) { /* Byte order LSB first. */ - proto_len = ucp[6] + 256 * ucp[7]; - data_len = ucp[8] + 256 * ucp[9]; - } else { - debug("Initial X11 packet contains bad byte order byte: 0x%x", - ucp[0]); - ch->type = SSH_CHANNEL_OPEN; - goto reject; - } + /* Check if the fixed size part of the packet is in buffer. */ + if (buffer_len(&c->output) < 12) + return 0; + + /* Parse the lengths of variable-length fields. */ + ucp = (unsigned char *) buffer_ptr(&c->output); + if (ucp[0] == 0x42) { /* Byte order MSB first. */ + proto_len = 256 * ucp[6] + ucp[7]; + data_len = 256 * ucp[8] + ucp[9]; + } else if (ucp[0] == 0x6c) { /* Byte order LSB first. */ + proto_len = ucp[6] + 256 * ucp[7]; + data_len = ucp[8] + 256 * ucp[9]; + } else { + debug("Initial X11 packet contains bad byte order byte: 0x%x", + ucp[0]); + return -1; + } - /* Check if the whole packet is in buffer. */ - if (buffer_len(&ch->output) < - 12 + ((proto_len + 3) & ~3) + ((data_len + 3) & ~3)) - break; + /* Check if the whole packet is in buffer. */ + if (buffer_len(&c->output) < + 12 + ((proto_len + 3) & ~3) + ((data_len + 3) & ~3)) + return 0; - /* Check if authentication protocol matches. */ - if (proto_len != strlen(x11_saved_proto) || - memcmp(ucp + 12, x11_saved_proto, proto_len) != 0) { - debug("X11 connection uses different authentication protocol."); - ch->type = SSH_CHANNEL_OPEN; - goto reject; - } - /* Check if authentication data matches our fake data. */ - if (data_len != x11_fake_data_len || - memcmp(ucp + 12 + ((proto_len + 3) & ~3), - x11_fake_data, x11_fake_data_len) != 0) { - debug("X11 auth data does not match fake data."); - ch->type = SSH_CHANNEL_OPEN; - goto reject; - } - /* Check fake data length */ - if (x11_fake_data_len != x11_saved_data_len) { - error("X11 fake_data_len %d != saved_data_len %d", - x11_fake_data_len, x11_saved_data_len); - ch->type = SSH_CHANNEL_OPEN; - goto reject; - } - /* - * Received authentication protocol and data match - * our fake data. Substitute the fake data with real - * data. - */ - memcpy(ucp + 12 + ((proto_len + 3) & ~3), - x11_saved_data, x11_saved_data_len); + /* Check if authentication protocol matches. */ + if (proto_len != strlen(x11_saved_proto) || + memcmp(ucp + 12, x11_saved_proto, proto_len) != 0) { + debug("X11 connection uses different authentication protocol."); + return -1; + } + /* Check if authentication data matches our fake data. */ + if (data_len != x11_fake_data_len || + memcmp(ucp + 12 + ((proto_len + 3) & ~3), + x11_fake_data, x11_fake_data_len) != 0) { + debug("X11 auth data does not match fake data."); + return -1; + } + /* Check fake data length */ + if (x11_fake_data_len != x11_saved_data_len) { + error("X11 fake_data_len %d != saved_data_len %d", + x11_fake_data_len, x11_saved_data_len); + return -1; + } + /* + * Received authentication protocol and data match + * our fake data. Substitute the fake data with real + * data. + */ + memcpy(ucp + 12 + ((proto_len + 3) & ~3), + x11_saved_data, x11_saved_data_len); + return 1; +} - /* Start normal processing for the channel. */ - ch->type = SSH_CHANNEL_OPEN; - goto redo; +void +channel_pre_x11_open_13(Channel *c, fd_set * readset, fd_set * writeset) +{ + int ret = x11_open_helper(c); + if (ret == 1) { + /* Start normal processing for the channel. */ + c->type = SSH_CHANNEL_OPEN; + } else if (ret == -1) { + /* + * We have received an X11 connection that has bad + * authentication information. + */ + log("X11 connection rejected because of wrong authentication.\r\n"); + buffer_clear(&c->input); + buffer_clear(&c->output); + close(c->sock); + c->sock = -1; + c->type = SSH_CHANNEL_CLOSED; + packet_start(SSH_MSG_CHANNEL_CLOSE); + packet_put_int(c->remote_id); + packet_send(); + } +} - reject: - /* - * We have received an X11 connection that has bad - * authentication information. - */ - log("X11 connection rejected because of wrong authentication.\r\n"); - buffer_clear(&ch->input); - buffer_clear(&ch->output); - if (compat13) { - close(ch->sock); - ch->sock = -1; - ch->type = SSH_CHANNEL_CLOSED; - packet_start(SSH_MSG_CHANNEL_CLOSE); - packet_put_int(ch->remote_id); - packet_send(); - } else { - debug("X11 rejected %d i%d/o%d", ch->self, ch->istate, ch->ostate); - chan_read_failed(ch); - chan_write_failed(ch); - debug("X11 rejected %d i%d/o%d", ch->self, ch->istate, ch->ostate); - } - break; +void +channel_pre_x11_open_15(Channel *c, fd_set * readset, fd_set * writeset) +{ + int ret = x11_open_helper(c); + if (ret == 1) { + c->type = SSH_CHANNEL_OPEN; + } else if (ret == -1) { + debug("X11 rejected %d i%d/o%d", c->self, c->istate, c->ostate); + chan_read_failed(c); + chan_write_failed(c); + debug("X11 closed %d i%d/o%d", c->self, c->istate, c->ostate); + } +} - case SSH_CHANNEL_FREE: - default: - continue; +/* This is our fake X11 server socket. */ +void +channel_post_x11_listener(Channel *c, fd_set * readset, fd_set * writeset) +{ + struct sockaddr addr; + int newsock, newch; + socklen_t addrlen; + char buf[16384], *remote_hostname; + + if (FD_ISSET(c->sock, readset)) { + debug("X11 connection requested."); + addrlen = sizeof(addr); + newsock = accept(c->sock, &addr, &addrlen); + if (newsock < 0) { + error("accept: %.100s", strerror(errno)); + return; } + remote_hostname = get_remote_hostname(newsock); + snprintf(buf, sizeof buf, "X11 connection from %.200s port %d", + remote_hostname, get_peer_port(newsock)); + xfree(remote_hostname); + newch = channel_allocate(SSH_CHANNEL_OPENING, newsock, + xstrdup(buf)); + packet_start(SSH_SMSG_X11_OPEN); + packet_put_int(newch); + if (have_hostname_in_open) + packet_put_string(buf, strlen(buf)); + packet_send(); } } /* - * After select, perform any appropriate operations for channels which have - * events pending. + * This socket is listening for connections to a forwarded TCP/IP port. */ +void +channel_post_port_listener(Channel *c, fd_set * readset, fd_set * writeset) +{ + struct sockaddr addr; + int newsock, newch; + socklen_t addrlen; + char buf[1024], *remote_hostname; + int remote_port; + + if (FD_ISSET(c->sock, readset)) { + debug("Connection to port %d forwarding " + "to %.100s port %d requested.", + c->listening_port, c->path, c->host_port); + addrlen = sizeof(addr); + newsock = accept(c->sock, &addr, &addrlen); + if (newsock < 0) { + error("accept: %.100s", strerror(errno)); + return; + } + remote_hostname = get_remote_hostname(newsock); + remote_port = get_peer_port(newsock); + snprintf(buf, sizeof buf, + "listen port %d for %.100s port %d, " + "connect from %.200s port %d", + c->listening_port, c->path, c->host_port, + remote_hostname, remote_port); + newch = channel_new("direct-tcpip", + SSH_CHANNEL_OPENING, newsock, newsock, -1, + c->local_window_max, c->local_maxpacket, + 0, xstrdup(buf)); + + packet_start(SSH_MSG_PORT_OPEN); + packet_put_int(newch); + packet_put_string(c->path, strlen(c->path)); + packet_put_int(c->host_port); + if (have_hostname_in_open) { + packet_put_string(buf, strlen(buf)); + } + packet_send(); + xfree(remote_hostname); + } +} -void -channel_after_select(fd_set * readset, fd_set * writeset) +/* + * This is the authentication agent socket listening for connections from + * clients. + */ +void +channel_post_auth_listener(Channel *c, fd_set * readset, fd_set * writeset) { struct sockaddr addr; - int newsock, i, newch, len; + int newsock, newch; socklen_t addrlen; - Channel *ch; - char buf[16384], *remote_hostname; - /* Loop over all channels... */ - for (i = 0; i < channels_alloc; i++) { - ch = &channels[i]; - switch (ch->type) { - case SSH_CHANNEL_X11_LISTENER: - /* This is our fake X11 server socket. */ - if (FD_ISSET(ch->sock, readset)) { - debug("X11 connection requested."); - addrlen = sizeof(addr); - newsock = accept(ch->sock, &addr, &addrlen); - if (newsock < 0) { - error("accept: %.100s", strerror(errno)); - break; - } - remote_hostname = get_remote_hostname(newsock); - snprintf(buf, sizeof buf, "X11 connection from %.200s port %d", - remote_hostname, get_peer_port(newsock)); - xfree(remote_hostname); - newch = channel_allocate(SSH_CHANNEL_OPENING, newsock, - xstrdup(buf)); - packet_start(SSH_SMSG_X11_OPEN); - packet_put_int(newch); - if (have_hostname_in_open) - packet_put_string(buf, strlen(buf)); - packet_send(); - } - break; + if (FD_ISSET(c->sock, readset)) { + addrlen = sizeof(addr); + newsock = accept(c->sock, &addr, &addrlen); + if (newsock < 0) { + error("accept from auth socket: %.100s", strerror(errno)); + return; + } + newch = channel_allocate(SSH_CHANNEL_OPENING, newsock, + xstrdup("accepted auth socket")); + packet_start(SSH_SMSG_AGENT_OPEN); + packet_put_int(newch); + packet_send(); + } +} - case SSH_CHANNEL_PORT_LISTENER: - /* - * This socket is listening for connections to a - * forwarded TCP/IP port. - */ - if (FD_ISSET(ch->sock, readset)) { - debug("Connection to port %d forwarding to %.100s port %d requested.", - ch->listening_port, ch->path, ch->host_port); - addrlen = sizeof(addr); - newsock = accept(ch->sock, &addr, &addrlen); - if (newsock < 0) { - error("accept: %.100s", strerror(errno)); - break; - } - remote_hostname = get_remote_hostname(newsock); - snprintf(buf, sizeof buf, "listen port %d for %.100s port %d, connect from %.200s port %d", - ch->listening_port, ch->path, ch->host_port, - remote_hostname, get_peer_port(newsock)); - xfree(remote_hostname); - newch = channel_allocate(SSH_CHANNEL_OPENING, newsock, - xstrdup(buf)); - packet_start(SSH_MSG_PORT_OPEN); - packet_put_int(newch); - packet_put_string(ch->path, strlen(ch->path)); - packet_put_int(ch->host_port); - if (have_hostname_in_open) - packet_put_string(buf, strlen(buf)); - packet_send(); +int +channel_handle_rfd(Channel *c, fd_set * readset, fd_set * writeset) +{ + char buf[16*1024]; + int len; + + if (c->rfd != -1 && + FD_ISSET(c->rfd, readset)) { + len = read(c->rfd, buf, sizeof(buf)); + if (len <= 0) { + debug("channel %d: read<0 rfd %d len %d", + c->self, c->rfd, len); + if (compat13) { + buffer_consume(&c->output, buffer_len(&c->output)); + c->type = SSH_CHANNEL_INPUT_DRAINING; + debug("Channel %d status set to input draining.", c->self); + } else { + chan_read_failed(c); } - break; - - case SSH_CHANNEL_AUTH_SOCKET: - /* - * This is the authentication agent socket listening - * for connections from clients. - */ - if (FD_ISSET(ch->sock, readset)) { - addrlen = sizeof(addr); - newsock = accept(ch->sock, &addr, &addrlen); - if (newsock < 0) { - error("accept from auth socket: %.100s", strerror(errno)); - break; - } - newch = channel_allocate(SSH_CHANNEL_OPENING, newsock, - xstrdup("accepted auth socket")); - packet_start(SSH_SMSG_AGENT_OPEN); - packet_put_int(newch); - packet_send(); + return -1; + } + buffer_append(&c->input, buf, len); + } + return 1; +} +int +channel_handle_wfd(Channel *c, fd_set * readset, fd_set * writeset) +{ + int len; + + /* Send buffered output data to the socket. */ + if (c->wfd != -1 && + FD_ISSET(c->wfd, writeset) && + buffer_len(&c->output) > 0) { + len = write(c->wfd, buffer_ptr(&c->output), + buffer_len(&c->output)); + if (len <= 0) { + if (compat13) { + buffer_consume(&c->output, buffer_len(&c->output)); + debug("Channel %d status set to input draining.", c->self); + c->type = SSH_CHANNEL_INPUT_DRAINING; + } else { + chan_write_failed(c); } - break; + return -1; + } + buffer_consume(&c->output, len); + } + return 1; +} - case SSH_CHANNEL_OPEN: - /* - * This is an open two-way communication channel. It - * is not of interest to us at this point what kind - * of data is being transmitted. - */ +void +channel_post_open_1(Channel *c, fd_set * readset, fd_set * writeset) +{ + channel_handle_rfd(c, readset, writeset); + channel_handle_wfd(c, readset, writeset); +} - /* - * Read available incoming data and append it to - * buffer; shutdown socket, if read or write failes - */ - if (FD_ISSET(ch->sock, readset)) { - len = read(ch->sock, buf, sizeof(buf)); - if (len <= 0) { - if (compat13) { - buffer_consume(&ch->output, buffer_len(&ch->output)); - ch->type = SSH_CHANNEL_INPUT_DRAINING; - debug("Channel %d status set to input draining.", i); - } else { - chan_read_failed(ch); - } - break; - } - buffer_append(&ch->input, buf, len); - } - /* Send buffered output data to the socket. */ - if (FD_ISSET(ch->sock, writeset) && buffer_len(&ch->output) > 0) { - len = write(ch->sock, buffer_ptr(&ch->output), - buffer_len(&ch->output)); - if (len <= 0) { - if (compat13) { - buffer_consume(&ch->output, buffer_len(&ch->output)); - debug("Channel %d status set to input draining.", i); - ch->type = SSH_CHANNEL_INPUT_DRAINING; - } else { - chan_write_failed(ch); - } - break; - } - buffer_consume(&ch->output, len); - } - break; +void +channel_post_output_drain_13(Channel *c, fd_set * readset, fd_set * writeset) +{ + int len; + /* Send buffered output data to the socket. */ + if (FD_ISSET(c->sock, writeset) && buffer_len(&c->output) > 0) { + len = write(c->sock, buffer_ptr(&c->output), + buffer_len(&c->output)); + if (len <= 0) + buffer_consume(&c->output, buffer_len(&c->output)); + else + buffer_consume(&c->output, len); + } +} - case SSH_CHANNEL_OUTPUT_DRAINING: - if (!compat13) - fatal("cannot happen: OUT_DRAIN"); - /* Send buffered output data to the socket. */ - if (FD_ISSET(ch->sock, writeset) && buffer_len(&ch->output) > 0) { - len = write(ch->sock, buffer_ptr(&ch->output), - buffer_len(&ch->output)); - if (len <= 0) - buffer_consume(&ch->output, buffer_len(&ch->output)); - else - buffer_consume(&ch->output, len); - } - break; +void +channel_handler_init_13(void) +{ + channel_pre[SSH_CHANNEL_OPEN] = &channel_pre_open_13; + channel_pre[SSH_CHANNEL_X11_OPEN] = &channel_pre_x11_open_13; + channel_pre[SSH_CHANNEL_X11_LISTENER] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_PORT_LISTENER] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_INPUT_DRAINING] = &channel_pre_input_draining; + channel_pre[SSH_CHANNEL_OUTPUT_DRAINING] = &channel_pre_output_draining; + + channel_post[SSH_CHANNEL_OPEN] = &channel_post_open_1; + channel_post[SSH_CHANNEL_X11_LISTENER] = &channel_post_x11_listener; + channel_post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener; + channel_post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; + channel_post[SSH_CHANNEL_OUTPUT_DRAINING] = &channel_post_output_drain_13; +} - case SSH_CHANNEL_X11_OPEN: - case SSH_CHANNEL_FREE: - default: +void +channel_handler_init_15(void) +{ + channel_pre[SSH_CHANNEL_OPEN] = &channel_pre_open_15; + channel_pre[SSH_CHANNEL_X11_OPEN] = &channel_pre_x11_open_15; + channel_pre[SSH_CHANNEL_X11_LISTENER] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_PORT_LISTENER] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; + + channel_post[SSH_CHANNEL_X11_LISTENER] = &channel_post_x11_listener; + channel_post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener; + channel_post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; + channel_post[SSH_CHANNEL_OPEN] = &channel_post_open_1; +} + +void +channel_handler_init(void) +{ + int i; + for(i = 0; i < SSH_CHANNEL_MAX_TYPE; i++) { + channel_pre[i] = NULL; + channel_post[i] = NULL; + } + if (compat13) + channel_handler_init_13(); + else + channel_handler_init_15(); +} + +void +channel_handler(chan_fn *ftab[], fd_set * readset, fd_set * writeset) +{ + static int did_init = 0; + int i; + Channel *c; + + if (!did_init) { + channel_handler_init(); + did_init = 1; + } + for (i = 0; i < channels_alloc; i++) { + c = &channels[i]; + if (c->type == SSH_CHANNEL_FREE) continue; - } + if (ftab[c->type] == NULL) + continue; + (*ftab[c->type])(c, readset, writeset); + if (!compat13) + chan_delete_if_full_closed(c); } } +void +channel_prepare_select(fd_set * readset, fd_set * writeset) +{ + channel_handler(channel_pre, readset, writeset); +} + +void +channel_after_select(fd_set * readset, fd_set * writeset) +{ + channel_handler(channel_post, readset, writeset); +} + /* If there is data to send to the connection, send some of it now. */ void channel_output_poll() { int len, i; - Channel *ch; + Channel *c; for (i = 0; i < channels_alloc; i++) { - ch = &channels[i]; + c = &channels[i]; /* We are only interested in channels that can have buffered incoming data. */ if (compat13) { - if (ch->type != SSH_CHANNEL_OPEN && - ch->type != SSH_CHANNEL_INPUT_DRAINING) + if (c->type != SSH_CHANNEL_OPEN && + c->type != SSH_CHANNEL_INPUT_DRAINING) continue; } else { - if (ch->type != SSH_CHANNEL_OPEN) + if (c->type != SSH_CHANNEL_OPEN) continue; - if (ch->istate != CHAN_INPUT_OPEN && - ch->istate != CHAN_INPUT_WAIT_DRAIN) + if (c->istate != CHAN_INPUT_OPEN && + c->istate != CHAN_INPUT_WAIT_DRAIN) continue; } /* Get the amount of buffered data for this channel. */ - len = buffer_len(&ch->input); + len = buffer_len(&c->input); if (len > 0) { /* Send some data for the other side over the secure connection. */ if (packet_is_interactive()) { @@ -556,22 +710,26 @@ channel_output_poll() len = 512; } else { /* Keep the packets at reasonable size. */ - if (len > packet_get_maxsize()/2) + if (len > packet_get_maxsize()) len = packet_get_maxsize()/2; } - packet_start(SSH_MSG_CHANNEL_DATA); - packet_put_int(ch->remote_id); - packet_put_string(buffer_ptr(&ch->input), len); - packet_send(); - buffer_consume(&ch->input, len); - } else if (ch->istate == CHAN_INPUT_WAIT_DRAIN) { + if (len > 0) { + packet_start(SSH_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; +debug("channel %d: send data len %d", c->self, len); + } + } else if (c->istate == CHAN_INPUT_WAIT_DRAIN) { if (compat13) fatal("cannot happen: istate == INPUT_WAIT_DRAIN for proto 1.3"); /* * input-buffer is empty and read-socket shutdown: * tell peer, that we will not send more data: send IEOF */ - chan_ibuf_empty(ch); + chan_ibuf_empty(c); } } } @@ -583,35 +741,33 @@ channel_output_poll() */ void -channel_input_data(int payload_len) +channel_input_data(int type, int plen) { int id; char *data; unsigned int data_len; - Channel *ch; + Channel *c; /* Get the channel number and verify it. */ id = packet_get_int(); - if (id < 0 || id >= channels_alloc) + c = channel_lookup(id); + if (c == NULL) packet_disconnect("Received data for nonexistent channel %d.", id); - ch = &channels[id]; - - if (ch->type == SSH_CHANNEL_FREE) - packet_disconnect("Received data for free channel %d.", ch->self); /* Ignore any data for non-open channels (might happen on close) */ - if (ch->type != SSH_CHANNEL_OPEN && - ch->type != SSH_CHANNEL_X11_OPEN) + if (c->type != SSH_CHANNEL_OPEN && + c->type != SSH_CHANNEL_X11_OPEN) return; /* same for protocol 1.5 if output end is no longer open */ - if (!compat13 && ch->ostate != CHAN_OUTPUT_OPEN) + if (!compat13 && c->ostate != CHAN_OUTPUT_OPEN) return; /* Get the data. */ data = packet_get_string(&data_len); - packet_integrity_check(payload_len, 4 + 4 + data_len, SSH_MSG_CHANNEL_DATA); - buffer_append(&ch->output, data, data_len); + + packet_integrity_check(plen, 4 + 4 + data_len, SSH_MSG_CHANNEL_DATA); + buffer_append(&c->output, data, data_len); xfree(data); } @@ -624,45 +780,60 @@ int channel_not_very_much_buffered_data() { unsigned int i; - Channel *ch; + Channel *c; for (i = 0; i < channels_alloc; i++) { - ch = &channels[i]; - if (ch->type == SSH_CHANNEL_OPEN) { - if (buffer_len(&ch->input) > packet_get_maxsize()) + c = &channels[i]; + if (c->type == SSH_CHANNEL_OPEN) { + if (buffer_len(&c->input) > packet_get_maxsize()) { + debug("channel %d: big input buffer %d", + c->self, buffer_len(&c->input)); return 0; - if (buffer_len(&ch->output) > packet_get_maxsize()) + } + if (buffer_len(&c->output) > packet_get_maxsize()) { + debug("channel %d: big output buffer %d", + c->self, buffer_len(&c->output)); return 0; + } } } return 1; } -/* This is called after receiving CHANNEL_CLOSE/IEOF. */ +void +channel_input_ieof(int type, int plen) +{ + int id; + Channel *c; + + packet_integrity_check(plen, 4, type); + + id = packet_get_int(); + c = channel_lookup(id); + if (c == NULL) + packet_disconnect("Received ieof for nonexistent channel %d.", id); + chan_rcvd_ieof(c); +} void -channel_input_close() +channel_input_close(int type, int plen) { - int channel; + int id; + Channel *c; - /* Get the channel number and verify it. */ - channel = packet_get_int(); - if (channel < 0 || channel >= channels_alloc || |