X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/2ea438c29555f60b63b8f90028b1ace4fd372af1..d03186af8de9b660564ccc1804d2a9c2e93b2e58:/channels.c diff --git a/channels.c b/channels.c index 4af7df5d..94939239 100644 --- a/channels.c +++ b/channels.c @@ -1,4 +1,4 @@ -/* $OpenBSD: channels.c,v 1.287 2008/11/01 06:43:33 stevesk Exp $ */ +/* $OpenBSD: channels.c,v 1.299 2009/11/11 21:37:03 markus Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -53,6 +53,7 @@ #include #include +#include #include #include #include @@ -162,6 +163,9 @@ static u_int x11_fake_data_len; /* AF_UNSPEC or AF_INET or AF_INET6 */ static int IPv4or6 = AF_UNSPEC; +/* Set the routing domain a.k.a. VRF */ +static int channel_rdomain = -1; + /* helper */ static void port_open_helper(Channel *c, char *rtype); @@ -228,7 +232,12 @@ channel_register_fds(Channel *c, int rfd, int wfd, int efd, channel_max_fd = MAX(channel_max_fd, wfd); channel_max_fd = MAX(channel_max_fd, efd); - /* XXX set close-on-exec -markus */ + if (rfd != -1) + fcntl(rfd, F_SETFD, FD_CLOEXEC); + if (wfd != -1 && wfd != rfd) + fcntl(wfd, F_SETFD, FD_CLOEXEC); + if (efd != -1 && efd != rfd && efd != wfd) + fcntl(efd, F_SETFD, FD_CLOEXEC); c->rfd = rfd; c->wfd = wfd; @@ -296,6 +305,7 @@ channel_new(char *ctype, int type, int rfd, int wfd, int efd, buffer_init(&c->input); buffer_init(&c->output); buffer_init(&c->extended); + c->path = NULL; c->ostate = CHAN_OUTPUT_OPEN; c->istate = CHAN_INPUT_OPEN; c->flags = 0; @@ -321,6 +331,7 @@ channel_new(char *ctype, int type, int rfd, int wfd, int efd, c->output_filter = NULL; c->filter_ctx = NULL; c->filter_cleanup = NULL; + c->delayed = 1; /* prevent call to channel_post handler */ TAILQ_INIT(&c->status_confirms); debug("channel %d: new [%s]", found, remote_name); return c; @@ -402,6 +413,10 @@ channel_free(Channel *c) xfree(c->remote_name); c->remote_name = NULL; } + if (c->path) { + xfree(c->path); + c->path = NULL; + } while ((cc = TAILQ_FIRST(&c->status_confirms)) != NULL) { if (cc->abandon_cb != NULL) cc->abandon_cb(c, cc->ctx); @@ -980,7 +995,7 @@ static int channel_decode_socks4(Channel *c, fd_set *readset, fd_set *writeset) { char *p, *host; - u_int len, have, i, found; + u_int len, have, i, found, need; char username[256]; struct { u_int8_t version; @@ -996,10 +1011,20 @@ channel_decode_socks4(Channel *c, fd_set *readset, fd_set *writeset) if (have < len) return 0; p = buffer_ptr(&c->input); + + need = 1; + /* SOCKS4A uses an invalid IP address 0.0.0.x */ + if (p[4] == 0 && p[5] == 0 && p[6] == 0 && p[7] != 0) { + debug2("channel %d: socks4a request", c->self); + /* ... and needs an extra string (the hostname) */ + need = 2; + } + /* Check for terminating NUL on the string(s) */ for (found = 0, i = len; i < have; i++) { if (p[i] == '\0') { - found = 1; - break; + found++; + if (found == need) + break; } if (i > 1024) { /* the peer is probably sending garbage */ @@ -1008,7 +1033,7 @@ channel_decode_socks4(Channel *c, fd_set *readset, fd_set *writeset) return -1; } } - if (!found) + if (found < need) return 0; buffer_get(&c->input, (char *)&s4_req.version, 1); buffer_get(&c->input, (char *)&s4_req.command, 1); @@ -1018,23 +1043,46 @@ channel_decode_socks4(Channel *c, fd_set *readset, fd_set *writeset) p = buffer_ptr(&c->input); len = strlen(p); debug2("channel %d: decode socks4: user %s/%d", c->self, p, len); + len++; /* trailing '\0' */ if (len > have) fatal("channel %d: decode socks4: len %d > have %d", c->self, len, have); strlcpy(username, p, sizeof(username)); buffer_consume(&c->input, len); - buffer_consume(&c->input, 1); /* trailing '\0' */ - host = inet_ntoa(s4_req.dest_addr); - strlcpy(c->path, host, sizeof(c->path)); + if (c->path != NULL) { + xfree(c->path); + c->path = NULL; + } + if (need == 1) { /* SOCKS4: one string */ + host = inet_ntoa(s4_req.dest_addr); + c->path = xstrdup(host); + } else { /* SOCKS4A: two strings */ + have = buffer_len(&c->input); + p = buffer_ptr(&c->input); + len = strlen(p); + debug2("channel %d: decode socks4a: host %s/%d", + c->self, p, len); + len++; /* trailing '\0' */ + if (len > have) + fatal("channel %d: decode socks4a: len %d > have %d", + c->self, len, have); + if (len > NI_MAXHOST) { + error("channel %d: hostname \"%.100s\" too long", + c->self, p); + return -1; + } + c->path = xstrdup(p); + buffer_consume(&c->input, len); + } c->host_port = ntohs(s4_req.dest_port); debug2("channel %d: dynamic request: socks4 host %s port %u command %u", - c->self, host, c->host_port, s4_req.command); + c->self, c->path, c->host_port, s4_req.command); if (s4_req.command != 1) { - debug("channel %d: cannot handle: socks4 cn %d", - c->self, s4_req.command); + debug("channel %d: cannot handle: %s cn %d", + c->self, need == 1 ? "SOCKS4" : "SOCKS4A", s4_req.command); return -1; } s4_rsp.version = 0; /* vn: 0 for reply */ @@ -1065,7 +1113,7 @@ channel_decode_socks5(Channel *c, fd_set *readset, fd_set *writeset) u_int8_t atyp; } s5_req, s5_rsp; u_int16_t dest_port; - u_char *p, dest_addr[255+1]; + u_char *p, dest_addr[255+1], ntop[INET6_ADDRSTRLEN]; u_int have, need, i, found, nmethods, addrlen, af; debug2("channel %d: decode socks5", c->self); @@ -1138,10 +1186,22 @@ channel_decode_socks5(Channel *c, fd_set *readset, fd_set *writeset) buffer_get(&c->input, (char *)&dest_addr, addrlen); buffer_get(&c->input, (char *)&dest_port, 2); dest_addr[addrlen] = '\0'; - if (s5_req.atyp == SSH_SOCKS5_DOMAIN) - strlcpy(c->path, (char *)dest_addr, sizeof(c->path)); - else if (inet_ntop(af, dest_addr, c->path, sizeof(c->path)) == NULL) - return -1; + if (c->path != NULL) { + xfree(c->path); + c->path = NULL; + } + if (s5_req.atyp == SSH_SOCKS5_DOMAIN) { + if (addrlen >= NI_MAXHOST) { + error("channel %d: dynamic request: socks5 hostname " + "\"%.100s\" too long", c->self, dest_addr); + return -1; + } + c->path = xstrdup(dest_addr); + } else { + if (inet_ntop(af, dest_addr, ntop, sizeof(ntop)) == NULL) + return -1; + c->path = xstrdup(ntop); + } c->host_port = ntohs(dest_port); debug2("channel %d: dynamic request: socks5 host %s port %u command %u", @@ -1169,7 +1229,6 @@ channel_pre_dynamic(Channel *c, fd_set *readset, fd_set *writeset) int ret; have = buffer_len(&c->input); - c->delayed = 0; debug2("channel %d: pre_dynamic: have %d", c->self, have); /* buffer_dump(&c->input); */ /* check if the fixed size part of the packet is in buffer. */ @@ -1370,18 +1429,11 @@ channel_post_port_listener(Channel *c, fd_set *readset, fd_set *writeset) c->local_window_max, c->local_maxpacket, 0, rtype, 1); nc->listening_port = c->listening_port; nc->host_port = c->host_port; - strlcpy(nc->path, c->path, sizeof(nc->path)); + if (c->path != NULL) + nc->path = xstrdup(c->path); - if (nextstate == SSH_CHANNEL_DYNAMIC) { - /* - * do not call the channel_post handler until - * this flag has been reset by a pre-handler. - * otherwise the FD_ISSET calls might overflow - */ - nc->delayed = 1; - } else { + if (nextstate != SSH_CHANNEL_DYNAMIC) port_open_helper(nc, rtype); - } } } @@ -1602,6 +1654,7 @@ channel_handle_wfd(Channel *c, fd_set *readset, fd_set *writeset) } return -1; } +#ifndef BROKEN_TCGETATTR_ICANON if (compat20 && c->isatty && dlen >= 1 && buf[0] != '\r') { if (tcgetattr(c->wfd, &tio) == 0 && !(tio.c_lflag & ECHO) && (tio.c_lflag & ICANON)) { @@ -1615,6 +1668,7 @@ channel_handle_wfd(Channel *c, fd_set *readset, fd_set *writeset) packet_send(); } } +#endif buffer_consume(&c->output, len); if (compat20 && len > 0) { c->local_consumed += len; @@ -1724,8 +1778,6 @@ channel_check_window(Channel *c) static void channel_post_open(Channel *c, fd_set *readset, fd_set *writeset) { - if (c->delayed) - return; channel_handle_rfd(c, readset, writeset); channel_handle_wfd(c, readset, writeset); if (!compat20) @@ -1857,17 +1909,23 @@ static void channel_handler(chan_fn *ftab[], fd_set *readset, fd_set *writeset) { static int did_init = 0; - u_int i; + u_int i, oalloc; Channel *c; if (!did_init) { channel_handler_init(); did_init = 1; } - for (i = 0; i < channels_alloc; i++) { + for (i = 0, oalloc = channels_alloc; i < oalloc; i++) { c = channels[i]; if (c == NULL) continue; + if (c->delayed) { + if (ftab == channel_pre) + c->delayed = 0; + else + continue; + } if (ftab[c->type] != NULL) (*ftab[c->type])(c, readset, writeset); channel_garbage_collect(c); @@ -2311,8 +2369,8 @@ channel_input_open_failure(int type, u_int32_t seq, void *ctxt) xfree(lang); } packet_check_eom(); - /* Free the channel. This will also close the socket. */ - channel_free(c); + /* Schedule the channel for cleanup/deletion. */ + chan_mark_dead(c); } /* ARGSUSED */ @@ -2377,18 +2435,18 @@ channel_input_status_confirm(int type, u_int32_t seq, void *ctxt) { Channel *c; struct channel_confirm *cc; - int remote_id; + int id; /* Reset keepalive timeout */ - keep_alive_timeouts = 0; + packet_set_alive_timeouts(0); - remote_id = packet_get_int(); + id = packet_get_int(); packet_check_eom(); - debug2("channel_input_status_confirm: type %d id %d", type, remote_id); + debug2("channel_input_status_confirm: type %d id %d", type, id); - if ((c = channel_lookup(remote_id)) == NULL) { - logit("channel_input_status_confirm: %d: unknown", remote_id); + if ((c = channel_lookup(id)) == NULL) { + logit("channel_input_status_confirm: %d: unknown", id); return; } ; @@ -2408,8 +2466,15 @@ channel_set_af(int af) IPv4or6 = af; } +void +channel_set_rdomain(int rdomain) +{ + channel_rdomain = rdomain; +} + static int -channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_port, +channel_setup_fwd_listener(int type, const char *listen_addr, + u_short listen_port, int *allocated_listen_port, const char *host_to_connect, u_short port_to_connect, int gateway_ports) { Channel *c; @@ -2417,6 +2482,7 @@ channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_por struct addrinfo hints, *ai, *aitop; const char *host, *addr; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; + in_port_t *lport_p; host = (type == SSH_CHANNEL_RPORT_LISTENER) ? listen_addr : host_to_connect; @@ -2426,7 +2492,7 @@ channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_por error("No forward host name."); return 0; } - if (strlen(host) > SSH_CHANNEL_PATH_LEN - 1) { + if (strlen(host) >= NI_MAXHOST) { error("Forward host name too long."); return 0; } @@ -2485,17 +2551,37 @@ channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_por } return 0; } - + if (allocated_listen_port != NULL) + *allocated_listen_port = 0; for (ai = aitop; ai; ai = ai->ai_next) { - if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + switch (ai->ai_family) { + case AF_INET: + lport_p = &((struct sockaddr_in *)ai->ai_addr)-> + sin_port; + break; + case AF_INET6: + lport_p = &((struct sockaddr_in6 *)ai->ai_addr)-> + sin6_port; + break; + default: continue; + } + /* + * If allocating a port for -R forwards, then use the + * same port for all address families. + */ + if (type == SSH_CHANNEL_RPORT_LISTENER && listen_port == 0 && + allocated_listen_port != NULL && *allocated_listen_port > 0) + *lport_p = htons(*allocated_listen_port); + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { error("channel_setup_fwd_listener: getnameinfo failed"); continue; } /* Create a port to listen for the host. */ - sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + sock = socket_rdomain(ai->ai_family, ai->ai_socktype, + ai->ai_protocol, channel_rdomain); if (sock < 0) { /* this is no error since kernel may not support ipv6 */ verbose("socket: %.100s", strerror(errno)); @@ -2503,8 +2589,11 @@ channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_por } channel_set_reuseaddr(sock); + if (ai->ai_family == AF_INET6) + sock_set_v6only(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. */ if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { @@ -2523,11 +2612,24 @@ channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_por close(sock); continue; } + + /* + * listen_port == 0 requests a dynamically allocated port - + * record what we got. + */ + if (type == SSH_CHANNEL_RPORT_LISTENER && listen_port == 0 && + allocated_listen_port != NULL && + *allocated_listen_port == 0) { + *allocated_listen_port = get_sock_port(sock, 1); + debug("Allocated listen port %d", + *allocated_listen_port); + } + /* Allocate a channel number for the socket. */ c = channel_new("port listener", type, sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "port listener", 1); - strlcpy(c->path, host, sizeof(c->path)); + c->path = xstrdup(host); c->host_port = port_to_connect; c->listening_port = listen_port; success = 1; @@ -2549,8 +2651,7 @@ channel_cancel_rport_listener(const char *host, u_short port) Channel *c = channels[i]; if (c != NULL && c->type == SSH_CHANNEL_RPORT_LISTENER && - strncmp(c->path, host, sizeof(c->path)) == 0 && - c->listening_port == port) { + strcmp(c->path, host) == 0 && c->listening_port == port) { debug2("%s: close channel %d", __func__, i); channel_free(c); found = 1; @@ -2566,17 +2667,18 @@ channel_setup_local_fwd_listener(const char *listen_host, u_short listen_port, const char *host_to_connect, u_short port_to_connect, int gateway_ports) { return channel_setup_fwd_listener(SSH_CHANNEL_PORT_LISTENER, - listen_host, listen_port, host_to_connect, port_to_connect, + listen_host, listen_port, NULL, host_to_connect, port_to_connect, gateway_ports); } /* protocol v2 remote port fwd, used by sshd */ int channel_setup_remote_fwd_listener(const char *listen_address, - u_short listen_port, int gateway_ports) + u_short listen_port, int *allocated_listen_port, int gateway_ports) { return channel_setup_fwd_listener(SSH_CHANNEL_RPORT_LISTENER, - listen_address, listen_port, NULL, 0, gateway_ports); + listen_address, listen_port, allocated_listen_port, + NULL, 0, gateway_ports); } /* @@ -2791,10 +2893,16 @@ 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(" %s:%d", permitted_adm_opens[i].host_to_connect, permitted_adm_opens[i].port_to_connect); + printf("\n"); } /* Try to start non-blocking connect to next host in cctx list */ @@ -2814,8 +2922,9 @@ connect_next(struct channel_connect *cctx) error("connect_next: getnameinfo failed"); continue; } - if ((sock = socket(cctx->ai->ai_family, cctx->ai->ai_socktype, - cctx->ai->ai_protocol)) == -1) { + if ((sock = socket_rdomain(cctx->ai->ai_family, + cctx->ai->ai_socktype, cctx->ai->ai_protocol, + channel_rdomain)) == -1) { if (cctx->ai->ai_next == NULL) error("socket: %.100s", strerror(errno)); else @@ -3001,8 +3110,8 @@ x11_create_display_inet(int x11_display_offset, int x11_use_localhost, for (ai = aitop; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) continue; - sock = socket(ai->ai_family, ai->ai_socktype, - ai->ai_protocol); + sock = socket_rdomain(ai->ai_family, ai->ai_socktype, + ai->ai_protocol, channel_rdomain); if (sock < 0) { if ((errno != EINVAL) && (errno != EAFNOSUPPORT)) { error("socket: %.100s", strerror(errno)); @@ -3014,13 +3123,8 @@ x11_create_display_inet(int x11_display_offset, int x11_use_localhost, continue; } } -#ifdef IPV6_V6ONLY - if (ai->ai_family == AF_INET6) { - int on = 1; - if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0) - error("setsockopt IPV6_V6ONLY: %.100s", strerror(errno)); - } -#endif + if (ai->ai_family == AF_INET6) + sock_set_v6only(sock); if (x11_use_localhost) channel_set_reuseaddr(sock); if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { @@ -3074,7 +3178,7 @@ x11_create_display_inet(int x11_display_offset, int x11_use_localhost, } static int -connect_local_xsocket(u_int dnr) +connect_local_xsocket_path(const char *pathname) { int sock; struct sockaddr_un addr; @@ -3084,7 +3188,7 @@ connect_local_xsocket(u_int dnr) error("socket: %.100s", strerror(errno)); memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; - snprintf(addr.sun_path, sizeof addr.sun_path, _PATH_UNIX_X, dnr); + strlcpy(addr.sun_path, pathname, sizeof addr.sun_path); if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0) return sock; close(sock); @@ -3092,6 +3196,14 @@ connect_local_xsocket(u_int dnr) return -1; } +static int +connect_local_xsocket(u_int dnr) +{ + char buf[1024]; + snprintf(buf, sizeof buf, _PATH_UNIX_X, dnr); + return connect_local_xsocket_path(buf); +} + int x11_connect_display(void) { @@ -3113,6 +3225,17 @@ x11_connect_display(void) * connection to the real X server. */ + /* Check if the display is from launchd. */ +#ifdef __APPLE__ + if (strncmp(display, "/tmp/launch", 11) == 0) { + sock = connect_local_xsocket_path(display); + if (sock < 0) + return -1; + + /* OK, we now have a connection to the display. */ + return sock; + } +#endif /* * Check if it is a unix domain socket. Unix domain displays are in * one of the following formats: unix:d[.s], :d[.s], ::d[.s] @@ -3163,7 +3286,8 @@ x11_connect_display(void) } for (ai = aitop; ai; ai = ai->ai_next) { /* Create a socket. */ - sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + sock = socket_rdomain(ai->ai_family, ai->ai_socktype, + ai->ai_protocol, channel_rdomain); if (sock < 0) { debug2("socket: %.100s", strerror(errno)); continue;