=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/ssh/channels.c,v retrieving revision 1.214 retrieving revision 1.214.2.2 diff -u -r1.214 -r1.214.2.2 --- src/usr.bin/ssh/channels.c 2005/03/14 11:46:56 1.214 +++ src/usr.bin/ssh/channels.c 2006/02/03 02:53:44 1.214.2.2 @@ -39,7 +39,7 @@ */ #include "includes.h" -RCSID("$OpenBSD: channels.c,v 1.214 2005/03/14 11:46:56 markus Exp $"); +RCSID("$OpenBSD: channels.c,v 1.214.2.2 2006/02/03 02:53:44 brad Exp $"); #include "ssh.h" #include "ssh1.h" @@ -58,8 +58,6 @@ /* -- channel core */ -#define CHAN_RBUF 16*1024 - /* * Pointer to an array containing all allocated channels. The array is * dynamically extended as needed. @@ -111,6 +109,9 @@ /* Maximum number of fake X11 displays to try. */ #define MAX_DISPLAYS 1000 +/* Saved X11 local (client) display. */ +static char *x11_saved_display = NULL; + /* Saved X11 authentication protocol name. */ static char *x11_saved_proto = NULL; @@ -139,23 +140,51 @@ /* -- channel core */ Channel * -channel_lookup(int id) +channel_by_id(int id) { Channel *c; 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; } c = channels[id]; 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 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 * when the channel consumer/producer is ready, e.g. shell exec'd */ @@ -265,9 +294,11 @@ c->force_drain = 0; c->single_connection = 0; c->detach_user = NULL; + c->detach_close = 0; c->confirm = NULL; c->confirm_ctx = NULL; c->input_filter = NULL; + c->output_filter = NULL; debug("channel %d: new [%s]", found, remote_name); return c; } @@ -624,29 +655,32 @@ c->confirm_ctx = ctx; } 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) { logit("channel_register_cleanup: %d: bad id", id); return; } c->detach_user = fn; + c->detach_close = do_close; } void channel_cancel_cleanup(int id) { - Channel *c = channel_lookup(id); + Channel *c = channel_by_id(id); if (c == NULL) { logit("channel_cancel_cleanup: %d: bad id", id); return; } c->detach_user = NULL; + c->detach_close = 0; } 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); @@ -654,7 +688,8 @@ logit("channel_register_filter: %d: bad id", id); return; } - c->input_filter = fn; + c->input_filter = ifn; + c->output_filter = ofn; } void @@ -726,8 +761,8 @@ FD_SET(c->wfd, writeset); } else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { if (CHANNEL_EFD_OUTPUT_ACTIVE(c)) - debug2("channel %d: obuf_empty delayed efd %d/(%d)", - c->self, c->efd, buffer_len(&c->extended)); + debug2("channel %d: obuf_empty delayed efd %d/(%d)", + c->self, c->efd, buffer_len(&c->extended)); else chan_obuf_empty(c); } @@ -893,7 +928,7 @@ channel_decode_socks4(Channel *c, fd_set * readset, fd_set * writeset) { char *p, *host; - int len, have, i, found; + u_int len, have, i, found; char username[256]; struct { u_int8_t version; @@ -978,7 +1013,7 @@ } s5_req, s5_rsp; u_int16_t dest_port; u_char *p, dest_addr[255+1]; - int i, have, found, nmethods, addrlen, af; + u_int have, i, found, nmethods, addrlen, af; debug2("channel %d: decode socks5", c->self); p = buffer_ptr(&c->input); @@ -1074,7 +1109,8 @@ channel_pre_dynamic(Channel *c, fd_set * readset, fd_set * writeset) { u_char *p; - int have, ret; + u_int have; + int ret; have = buffer_len(&c->input); c->delayed = 0; @@ -1177,7 +1213,7 @@ int direct; char buf[1024]; char *remote_ipaddr = get_peer_ipaddr(c->sock); - u_short remote_port = get_peer_port(c->sock); + int remote_port = get_peer_port(c->sock); direct = (strcmp(rtype, "direct-tcpip") == 0); @@ -1207,7 +1243,7 @@ } /* originator host and port */ packet_put_cstring(remote_ipaddr); - packet_put_int(remote_port); + packet_put_int((u_int)remote_port); packet_send(); } else { packet_start(SSH_MSG_PORT_OPEN); @@ -1222,6 +1258,19 @@ 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. */ @@ -1393,6 +1442,8 @@ debug2("channel %d: filter stops", c->self); chan_read_failed(c); } + } else if (c->datagram) { + buffer_put_string(&c->input, buf, len); } else { buffer_append(&c->input, buf, len); } @@ -1403,7 +1454,7 @@ channel_handle_wfd(Channel *c, fd_set * readset, fd_set * writeset) { struct termios tio; - u_char *data; + u_char *data = NULL, *buf; u_int dlen; int len; @@ -1411,9 +1462,40 @@ if (c->wfd != -1 && FD_ISSET(c->wfd, writeset) && buffer_len(&c->output) > 0) { - data = buffer_ptr(&c->output); - dlen = buffer_len(&c->output); - len = write(c->wfd, data, dlen); + if (c->output_filter != NULL) { + if ((buf = c->output_filter(c, &data, &dlen)) == NULL) { + 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)) return 1; if (len <= 0) { @@ -1430,14 +1512,14 @@ } 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 && !(tio.c_lflag & ECHO) && (tio.c_lflag & ICANON)) { /* * Simulate echo to reduce the impact of * traffic analysis. We need to match the * 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(); @@ -1656,7 +1738,7 @@ if (c == NULL) return; if (c->detach_user != NULL) { - if (!chan_is_dead(c, 0)) + if (!chan_is_dead(c, c->detach_close)) return; debug2("channel %d: gc: notify user", c->self); c->detach_user(c->self, NULL); @@ -1766,6 +1848,22 @@ if ((c->istate == CHAN_INPUT_OPEN || c->istate == CHAN_INPUT_WAIT_DRAIN) && (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 * connection. @@ -1803,8 +1901,8 @@ * 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)); + debug2("channel %d: ibuf_empty delayed efd %d/(%d)", + c->self, c->efd, buffer_len(&c->extended)); else chan_ibuf_empty(c); } @@ -1888,7 +1986,10 @@ c->local_window -= data_len; } 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); } @@ -2119,9 +2220,8 @@ id = packet_get_int(); c = channel_lookup(id); - if (c == NULL || c->type != SSH_CHANNEL_OPEN) { - logit("Received window adjust for " - "non-open channel %d.", id); + if (c == NULL) { + logit("Received window adjust for non-open channel %d.", id); return; } adjust = packet_get_int(); @@ -2178,7 +2278,7 @@ const char *host_to_connect, u_short port_to_connect, int gateway_ports) { 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; const char *host, *addr; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; @@ -2189,11 +2289,11 @@ if (host == NULL) { error("No forward host name."); - return success; + return 0; } if (strlen(host) > SSH_CHANNEL_PATH_LEN - 1) { error("Forward host name too long."); - return success; + return 0; } /* @@ -2244,12 +2344,10 @@ packet_disconnect("getaddrinfo: fatal error: %s", gai_strerror(r)); } else { - verbose("channel_setup_fwd_listener: " + error("channel_setup_fwd_listener: " "getaddrinfo(%.64s): %s", addr, gai_strerror(r)); - packet_send_debug("channel_setup_fwd_listener: " - "getaddrinfo(%.64s): %s", addr, gai_strerror(r)); } - aitop = NULL; + return 0; } for (ai = aitop; ai; ai = ai->ai_next) { @@ -2267,14 +2365,9 @@ verbose("socket: %.100s", strerror(errno)); 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); /* Bind the socket to the address. */ @@ -2441,7 +2534,7 @@ permitted_opens[i].listen_port = 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; } @@ -2645,7 +2738,7 @@ */ int x11_create_display_inet(int x11_display_offset, int x11_use_localhost, - int single_connection, u_int *display_numberp) + int single_connection, u_int *display_numberp, int **chanids) { Channel *nc = NULL; int display_number, sock; @@ -2654,6 +2747,9 @@ char strport[NI_MAXSERV]; int gaierr, n, num_socks = 0, socks[NUM_SOCKS]; + if (chanids == NULL) + return -1; + for (display_number = x11_display_offset; display_number < MAX_DISPLAYS; display_number++) { @@ -2677,6 +2773,7 @@ freeaddrinfo(aitop); return -1; } + channel_set_reuseaddr(sock); if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { debug2("bind port %d: %.100s", port, strerror(errno)); close(sock); @@ -2713,6 +2810,7 @@ } /* Allocate a channel for each socket. */ + *chanids = xmalloc(sizeof(**chanids) * (num_socks + 1)); for (n = 0; n < num_socks; n++) { sock = socks[n]; nc = channel_new("x11 listener", @@ -2720,7 +2818,9 @@ CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, 0, "X11 inet listener", 1); nc->single_connection = single_connection; + (*chanids)[n] = nc->self; } + (*chanids)[n] = -1; /* Return the display number for the DISPLAY environment variable. */ *display_numberp = display_number; @@ -2906,7 +3006,7 @@ error("deny_input_open: type %d", type); 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_put_int(rchan); packet_send(); @@ -2918,53 +3018,59 @@ * This should be called in the client only. */ void -x11_request_forwarding_with_spoofing(int client_session_id, +x11_request_forwarding_with_spoofing(int client_session_id, const char *disp, const char *proto, const char *data) { u_int data_len = (u_int) strlen(data) / 2; - u_int i, value, len; + u_int i, value; char *new_data; int screen_number; const char *cp; u_int32_t rnd = 0; - cp = getenv("DISPLAY"); + if (x11_saved_display == NULL) + x11_saved_display = xstrdup(disp); + else if (strcmp(disp, x11_saved_display) != 0) { + error("x11_request_forwarding_with_spoofing: different " + "$DISPLAY already forwarded"); + return; + } + + cp = disp; + if (disp) + cp = strchr(disp, ':'); if (cp) - cp = strchr(cp, ':'); - if (cp) cp = strchr(cp, '.'); if (cp) screen_number = atoi(cp + 1); else screen_number = 0; - /* Save protocol name. */ - x11_saved_proto = xstrdup(proto); - - /* - * Extract real authentication data and generate fake data of the - * same length. - */ - x11_saved_data = xmalloc(data_len); - x11_fake_data = xmalloc(data_len); - for (i = 0; i < data_len; i++) { - if (sscanf(data + 2 * i, "%2x", &value) != 1) - fatal("x11_request_forwarding: bad authentication data: %.100s", data); - if (i % 4 == 0) - rnd = arc4random(); - x11_saved_data[i] = value; - x11_fake_data[i] = rnd & 0xff; - rnd >>= 8; + if (x11_saved_proto == NULL) { + /* Save protocol name. */ + x11_saved_proto = xstrdup(proto); + /* + * Extract real authentication data and generate fake data + * of the same length. + */ + x11_saved_data = xmalloc(data_len); + x11_fake_data = xmalloc(data_len); + for (i = 0; i < data_len; i++) { + if (sscanf(data + 2 * i, "%2x", &value) != 1) + fatal("x11_request_forwarding: bad " + "authentication data: %.100s", data); + if (i % 4 == 0) + rnd = arc4random(); + x11_saved_data[i] = value; + x11_fake_data[i] = rnd & 0xff; + rnd >>= 8; + } + x11_saved_data_len = data_len; + x11_fake_data_len = data_len; } - x11_saved_data_len = data_len; - x11_fake_data_len = data_len; /* Convert the fake data into hex. */ - len = 2 * data_len + 1; - new_data = xmalloc(len); - for (i = 0; i < data_len; i++) - snprintf(new_data + 2 * i, len - 2 * i, - "%02x", (u_char) x11_fake_data[i]); + new_data = tohex(x11_fake_data, data_len); /* Send the request packet. */ if (compat20) {