+/* $OpenBSD: channels.c,v 1.299 2009/11/11 21:37:03 markus Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
*/
#include "includes.h"
-RCSID("$OpenBSD: channels.c,v 1.227 2005/10/14 02:29:37 stevesk Exp $");
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+#include "openbsd-compat/sys-queue.h"
+#include "xmalloc.h"
#include "ssh.h"
#include "ssh1.h"
#include "ssh2.h"
#include "packet.h"
-#include "xmalloc.h"
#include "log.h"
#include "misc.h"
+#include "buffer.h"
#include "channels.h"
#include "compat.h"
#include "canohost.h"
#include "key.h"
#include "authfd.h"
#include "pathnames.h"
-#include "bufaux.h"
/* -- channel core */
-#define CHAN_RBUF 16*1024
-
/*
* Pointer to an array containing all allocated channels. The array is
* dynamically extended as needed.
u_short listen_port; /* Remote side should listen port number. */
} ForwardPermission;
-/* List of all permitted host/port pairs to connect. */
+/* List of all permitted host/port pairs to connect by the user. */
static ForwardPermission permitted_opens[SSH_MAX_FORWARDS_PER_DIRECTION];
-/* Number of permitted host/port pairs in the array. */
+/* List of all permitted host/port pairs to connect by the admin. */
+static ForwardPermission permitted_adm_opens[SSH_MAX_FORWARDS_PER_DIRECTION];
+
+/* Number of permitted host/port pairs in the array permitted by the user. */
static int num_permitted_opens = 0;
+
+/* Number of permitted host/port pair in the array permitted by the admin. */
+static int num_adm_permitted_opens = 0;
+
/*
* 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
* Fake X11 authentication data. This is what the server will be sending us;
* we should replace any occurrences of this by the real data.
*/
-static char *x11_fake_data = NULL;
+static u_char *x11_fake_data = NULL;
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);
+/* non-blocking connect helpers */
+static int connect_next(struct channel_connect *);
+static void channel_connect_ctx_free(struct channel_connect *);
+
/* -- 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);
+ }
+ 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
*/
-
static void
channel_register_fds(Channel *c, int rfd, int wfd, int efd,
- int extusage, int nonblock)
+ int extusage, int nonblock, int is_tty)
{
/* Update the maximum file descriptor value. */
channel_max_fd = MAX(channel_max_fd, rfd);
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;
c->efd = efd;
c->extended_usage = extusage;
- /* XXX ugly hack: nonblock is only set by the server */
- if (nonblock && isatty(c->rfd)) {
+ if ((c->isatty = is_tty) != 0)
debug2("channel %d: rfd %d isatty", c->self, c->rfd);
- c->isatty = 1;
- if (!isatty(c->wfd)) {
- error("channel %d: wfd %d is not a tty?",
- c->self, c->wfd);
- }
- } else {
- c->isatty = 0;
- }
- c->wfd_isatty = isatty(c->wfd);
+ c->wfd_isatty = is_tty || isatty(c->wfd);
/* enable nonblocking mode */
if (nonblock) {
* Allocate a new channel object and set its type and socket. This will cause
* remote_name to be freed.
*/
-
Channel *
channel_new(char *ctype, int type, int rfd, int wfd, int efd,
u_int window, u_int maxpack, int extusage, char *remote_name, int nonblock)
/* Do initial allocation if this is the first call. */
if (channels_alloc == 0) {
channels_alloc = 10;
- channels = xmalloc(channels_alloc * sizeof(Channel *));
+ channels = xcalloc(channels_alloc, sizeof(Channel *));
for (i = 0; i < channels_alloc; i++)
channels[i] = NULL;
}
if (channels_alloc > 10000)
fatal("channel_new: internal error: channels_alloc %d "
"too big.", channels_alloc);
- channels = xrealloc(channels,
- (channels_alloc + 10) * sizeof(Channel *));
+ channels = xrealloc(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;
}
/* Initialize and return new channel. */
- c = channels[found] = xmalloc(sizeof(Channel));
- memset(c, 0, sizeof(Channel));
+ c = channels[found] = xcalloc(1, sizeof(Channel));
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;
- channel_register_fds(c, rfd, wfd, efd, extusage, nonblock);
+ channel_register_fds(c, rfd, wfd, efd, extusage, nonblock, 0);
c->self = found;
c->type = type;
c->ctype = ctype;
c->single_connection = 0;
c->detach_user = NULL;
c->detach_close = 0;
- c->confirm = NULL;
- c->confirm_ctx = NULL;
+ 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->delayed = 1; /* prevent call to channel_post handler */
+ TAILQ_INIT(&c->status_confirms);
debug("channel %d: new [%s]", found, remote_name);
return c;
}
}
/* Close all channel fd/socket. */
-
static void
channel_close_fds(Channel *c)
{
}
/* Free the channel and close its fd/socket. */
-
void
channel_free(Channel *c)
{
char *s;
u_int i, n;
+ struct channel_confirm *cc;
for (n = 0, i = 0; i < channels_alloc; i++)
if (channels[i])
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);
+ TAILQ_REMOVE(&c->status_confirms, cc, entry);
+ bzero(cc, sizeof(*cc));
+ xfree(cc);
+ }
+ if (c->filter_cleanup != NULL && c->filter_ctx != NULL)
+ c->filter_cleanup(c->self, c->filter_ctx);
channels[c->self] = NULL;
xfree(c);
}
* Closes the sockets/fds of all channels. This is used to close extra file
* descriptors after a fork.
*/
-
void
channel_close_all(void)
{
/*
* Stop listening to channels.
*/
-
void
channel_stop_listening(void)
{
* Returns true if no channel has too much buffered data, and false if one or
* more channel is overfull.
*/
-
int
channel_not_very_much_buffered_data(void)
{
}
/* Returns true if any channel is still open. */
-
int
channel_still_open(void)
{
}
/* Returns the id of an open channel suitable for keepaliving */
-
int
channel_find_open(void)
{
* suitable for sending to the client. The message contains crlf pairs for
* newlines.
*/
-
char *
channel_open_message(void)
{
packet_put_cstring(service);
packet_put_char(wantconfirm);
}
+
+void
+channel_register_status_confirm(int id, channel_confirm_cb *cb,
+ channel_confirm_abandon_cb *abandon_cb, void *ctx)
+{
+ struct channel_confirm *cc;
+ Channel *c;
+
+ if ((c = channel_lookup(id)) == NULL)
+ fatal("channel_register_expect: %d: bad id", id);
+
+ cc = xmalloc(sizeof(*cc));
+ cc->cb = cb;
+ cc->abandon_cb = abandon_cb;
+ cc->ctx = ctx;
+ TAILQ_INSERT_TAIL(&c->status_confirms, cc, entry);
+}
+
void
-channel_register_confirm(int id, channel_callback_fn *fn, void *ctx)
+channel_register_open_confirm(int id, channel_callback_fn *fn, void *ctx)
{
Channel *c = channel_lookup(id);
if (c == NULL) {
- logit("channel_register_comfirm: %d: bad id", id);
+ logit("channel_register_open_confirm: %d: bad id", id);
return;
}
- c->confirm = fn;
- c->confirm_ctx = ctx;
+ c->open_confirm = fn;
+ c->open_confirm_ctx = ctx;
}
+
void
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);
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);
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_filter_cleanup_fn *cfn, void *ctx)
{
Channel *c = channel_lookup(id);
logit("channel_register_filter: %d: bad id", id);
return;
}
- c->input_filter = fn;
+ c->input_filter = ifn;
+ c->output_filter = ofn;
+ c->filter_ctx = ctx;
+ c->filter_cleanup = cfn;
}
void
channel_set_fds(int id, int rfd, int wfd, int efd,
- int extusage, int nonblock, u_int window_max)
+ int extusage, int nonblock, int is_tty, u_int window_max)
{
Channel *c = channel_lookup(id);
if (c == NULL || c->type != SSH_CHANNEL_LARVAL)
fatal("channel_activate for non-larval channel %d.", id);
- channel_register_fds(c, rfd, wfd, efd, extusage, nonblock);
+ channel_register_fds(c, rfd, wfd, efd, extusage, nonblock, is_tty);
c->type = SSH_CHANNEL_OPEN;
c->local_window = c->local_window_max = window_max;
packet_start(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
* 'channel_post*': perform any appropriate operations for channels which
* have events pending.
*/
-typedef void chan_fn(Channel *c, fd_set * readset, fd_set * writeset);
+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
-channel_pre_listener(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_listener(Channel *c, fd_set *readset, fd_set *writeset)
{
FD_SET(c->sock, readset);
}
+/* ARGSUSED */
static void
-channel_pre_connecting(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_connecting(Channel *c, fd_set *readset, fd_set *writeset)
{
debug3("channel %d: waiting for connection", c->self);
FD_SET(c->sock, writeset);
}
static void
-channel_pre_open_13(Channel *c, fd_set * readset, fd_set * writeset)
+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);
}
static void
-channel_pre_open(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_open(Channel *c, fd_set *readset, fd_set *writeset)
{
u_int limit = compat20 ? c->remote_window : packet_get_maxsize();
- /* check buffer limits */
- limit = MIN(limit, (BUFFER_MAX_LEN - BUFFER_MAX_CHUNK - CHAN_RBUF));
-
if (c->istate == CHAN_INPUT_OPEN &&
limit > 0 &&
- buffer_len(&c->input) < limit)
+ buffer_len(&c->input) < limit &&
+ buffer_check_alloc(&c->input, CHAN_RBUF))
FD_SET(c->rfd, readset);
if (c->ostate == CHAN_OUTPUT_OPEN ||
c->ostate == CHAN_OUTPUT_WAIT_DRAIN) {
}
}
/** XXX check close conditions, too */
- if (compat20 && c->efd != -1) {
+ if (compat20 && c->efd != -1 &&
+ !(c->istate == CHAN_INPUT_CLOSED && c->ostate == CHAN_OUTPUT_CLOSED)) {
if (c->extended_usage == CHAN_EXTENDED_WRITE &&
buffer_len(&c->extended) > 0)
FD_SET(c->efd, writeset);
FD_SET(c->ctl_fd, readset);
}
+/* ARGSUSED */
static void
-channel_pre_input_draining(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_input_draining(Channel *c, fd_set *readset, fd_set *writeset)
{
if (buffer_len(&c->input) == 0) {
packet_start(SSH_MSG_CHANNEL_CLOSE);
}
}
+/* ARGSUSED */
static void
-channel_pre_output_draining(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_output_draining(Channel *c, fd_set *readset, fd_set *writeset)
{
if (buffer_len(&c->output) == 0)
chan_mark_dead(c);
}
static void
-channel_pre_x11_open_13(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_x11_open_13(Channel *c, fd_set *readset, fd_set *writeset)
{
int ret = x11_open_helper(&c->output);
}
static void
-channel_pre_x11_open(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_x11_open(Channel *c, fd_set *readset, fd_set *writeset)
{
int ret = x11_open_helper(&c->output);
}
/* try to decode a socks4 header */
+/* ARGSUSED */
static int
-channel_decode_socks4(Channel *c, fd_set * readset, fd_set * writeset)
+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;
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 */
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);
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 */
s4_rsp.command = 90; /* cd: req granted */
s4_rsp.dest_port = 0; /* ignored */
s4_rsp.dest_addr.s_addr = INADDR_ANY; /* ignored */
- buffer_append(&c->output, (char *)&s4_rsp, sizeof(s4_rsp));
+ buffer_append(&c->output, &s4_rsp, sizeof(s4_rsp));
return 1;
}
#define SSH_SOCKS5_CONNECT 0x01
#define SSH_SOCKS5_SUCCESS 0x00
+/* ARGSUSED */
static int
-channel_decode_socks5(Channel *c, fd_set * readset, fd_set * writeset)
+channel_decode_socks5(Channel *c, fd_set *readset, fd_set *writeset)
{
struct {
u_int8_t version;
u_int8_t atyp;
} s5_req, s5_rsp;
u_int16_t dest_port;
- u_char *p, dest_addr[255+1];
- u_int have, i, found, nmethods, addrlen, af;
+ 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);
p = buffer_ptr(&c->input);
if (have < nmethods + 2)
return 0;
/* look for method: "NO AUTHENTICATION REQUIRED" */
- for (found = 0, i = 2 ; i < nmethods + 2; i++) {
- if (p[i] == SSH_SOCKS5_NOAUTH ) {
+ for (found = 0, i = 2; i < nmethods + 2; i++) {
+ if (p[i] == SSH_SOCKS5_NOAUTH) {
found = 1;
break;
}
debug2("channel %d: socks5 post auth", c->self);
if (have < sizeof(s5_req)+1)
return 0; /* need more */
- memcpy((char *)&s5_req, p, sizeof(s5_req));
+ memcpy(&s5_req, p, sizeof(s5_req));
if (s5_req.version != 0x05 ||
s5_req.command != SSH_SOCKS5_CONNECT ||
s5_req.reserved != 0x00) {
debug2("channel %d: bad socks5 atyp %d", c->self, s5_req.atyp);
return -1;
}
- if (have < 4 + addrlen + 2)
+ need = sizeof(s5_req) + addrlen + 2;
+ if (s5_req.atyp == SSH_SOCKS5_DOMAIN)
+ need++;
+ if (have < need)
return 0;
buffer_consume(&c->input, sizeof(s5_req));
if (s5_req.atyp == SSH_SOCKS5_DOMAIN)
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",
((struct in_addr *)&dest_addr)->s_addr = INADDR_ANY;
dest_port = 0; /* ignored */
- buffer_append(&c->output, (char *)&s5_rsp, sizeof(s5_rsp));
- buffer_append(&c->output, (char *)&dest_addr, sizeof(struct in_addr));
- buffer_append(&c->output, (char *)&dest_port, sizeof(dest_port));
+ buffer_append(&c->output, &s5_rsp, sizeof(s5_rsp));
+ buffer_append(&c->output, &dest_addr, sizeof(struct in_addr));
+ buffer_append(&c->output, &dest_port, sizeof(dest_port));
return 1;
}
/* dynamic port forwarding */
static void
-channel_pre_dynamic(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_dynamic(Channel *c, fd_set *readset, fd_set *writeset)
{
u_char *p;
u_int have;
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. */
}
/* This is our fake X11 server socket. */
+/* ARGSUSED */
static void
-channel_post_x11_listener(Channel *c, fd_set * readset, fd_set * writeset)
+channel_post_x11_listener(Channel *c, fd_set *readset, fd_set *writeset)
{
Channel *nc;
- struct sockaddr addr;
+ struct sockaddr_storage addr;
int newsock;
socklen_t addrlen;
char buf[16384], *remote_ipaddr;
if (FD_ISSET(c->sock, readset)) {
debug("X11 connection requested.");
addrlen = sizeof(addr);
- newsock = accept(c->sock, &addr, &addrlen);
+ newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen);
if (c->single_connection) {
debug2("single_connection: closing X11 listener.");
channel_close_fd(&c->sock);
/*
* This socket is listening for connections to a forwarded TCP/IP port.
*/
+/* ARGSUSED */
static void
-channel_post_port_listener(Channel *c, fd_set * readset, fd_set * writeset)
+channel_post_port_listener(Channel *c, fd_set *readset, fd_set *writeset)
{
Channel *nc;
- struct sockaddr addr;
+ struct sockaddr_storage addr;
int newsock, nextstate;
socklen_t addrlen;
char *rtype;
}
addrlen = sizeof(addr);
- newsock = accept(c->sock, &addr, &addrlen);
+ newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen);
if (newsock < 0) {
error("accept: %.100s", strerror(errno));
return;
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);
- }
}
}
* This is the authentication agent socket listening for connections from
* clients.
*/
+/* ARGSUSED */
static void
-channel_post_auth_listener(Channel *c, fd_set * readset, fd_set * writeset)
+channel_post_auth_listener(Channel *c, fd_set *readset, fd_set *writeset)
{
Channel *nc;
int newsock;
- struct sockaddr addr;
+ struct sockaddr_storage addr;
socklen_t addrlen;
if (FD_ISSET(c->sock, readset)) {
addrlen = sizeof(addr);
- newsock = accept(c->sock, &addr, &addrlen);
+ newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen);
if (newsock < 0) {
error("accept from auth socket: %.100s", strerror(errno));
return;
}
}
+/* ARGSUSED */
static void
-channel_post_connecting(Channel *c, fd_set * readset, fd_set * writeset)
+channel_post_connecting(Channel *c, fd_set *readset, fd_set *writeset)
{
- int err = 0;
+ int err = 0, sock;
socklen_t sz = sizeof(err);
if (FD_ISSET(c->sock, writeset)) {
error("getsockopt SO_ERROR failed");
}
if (err == 0) {
- debug("channel %d: connected", c->self);
+ 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 (compat20) {
packet_start(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
packet_put_int(c->self);
}
} else {
- debug("channel %d: not connected: %s",
+ 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);
if (compat20) {
packet_start(SSH2_MSG_CHANNEL_OPEN_FAILURE);
packet_put_int(c->remote_id);
}
}
+/* ARGSUSED */
static int
-channel_handle_rfd(Channel *c, fd_set * readset, fd_set * writeset)
+channel_handle_rfd(Channel *c, fd_set *readset, fd_set *writeset)
{
char buf[CHAN_RBUF];
- int len;
+ int len, force;
- if (c->rfd != -1 &&
- FD_ISSET(c->rfd, readset)) {
+ force = c->isatty && c->detach_close && c->istate != CHAN_INPUT_CLOSED;
+ if (c->rfd != -1 && (force || FD_ISSET(c->rfd, readset))) {
+ errno = 0;
len = read(c->rfd, buf, sizeof(buf));
- if (len < 0 && (errno == EINTR || errno == EAGAIN))
+ if (len < 0 && (errno == EINTR ||
+ ((errno == EAGAIN || errno == EWOULDBLOCK) && !force)))
return 1;
+#ifndef PTY_ZEROREAD
if (len <= 0) {
+#else
+ if ((!c->isatty && len <= 0) ||
+ (c->isatty && (len < 0 || (len == 0 && errno != 0)))) {
+#endif
debug2("channel %d: read<=0 rfd %d len %d",
c->self, c->rfd, len);
if (c->type != SSH_CHANNEL_OPEN) {
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);
}
}
return 1;
}
+
+/* ARGSUSED */
static int
-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;
- u_char *data;
+ u_char *data = NULL, *buf;
u_int dlen;
int len;
if (c->wfd != -1 &&
FD_ISSET(c->wfd, writeset) &&
buffer_len(&c->output) > 0) {
- data = buffer_ptr(&c->output);
- dlen = buffer_len(&c->output);
+ 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 ||
+ errno == EWOULDBLOCK))
+ return 1;
+ if (len <= 0) {
+ if (c->type != SSH_CHANNEL_OPEN)
+ chan_mark_dead(c);
+ else
+ chan_write_failed(c);
+ return -1;
+ }
+ return 1;
+ }
#ifdef _AIX
/* XXX: Later AIX versions can't push as much data to tty */
if (compat20 && c->wfd_isatty)
dlen = MIN(dlen, 8*1024);
#endif
- len = write(c->wfd, data, dlen);
- if (len < 0 && (errno == EINTR || errno == EAGAIN))
+
+ len = write(c->wfd, buf, dlen);
+ if (len < 0 &&
+ (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK))
return 1;
if (len <= 0) {
if (c->type != SSH_CHANNEL_OPEN) {
}
return -1;
}
- if (compat20 && c->isatty && dlen >= 1 && data[0] != '\r') {
+#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)) {
/*
* 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();
}
}
+#endif
buffer_consume(&c->output, len);
if (compat20 && len > 0) {
c->local_consumed += len;
}
return 1;
}
+
static int
-channel_handle_efd(Channel *c, fd_set * readset, fd_set * writeset)
+channel_handle_efd(Channel *c, fd_set *readset, fd_set *writeset)
{
char buf[CHAN_RBUF];
int len;
buffer_len(&c->extended));
debug2("channel %d: written %d to efd %d",
c->self, len, c->efd);
- if (len < 0 && (errno == EINTR || errno == EAGAIN))
+ if (len < 0 && (errno == EINTR || errno == EAGAIN ||
+ errno == EWOULDBLOCK))
return 1;
if (len <= 0) {
debug2("channel %d: closing write-efd %d",
c->local_consumed += len;
}
} else if (c->extended_usage == CHAN_EXTENDED_READ &&
- FD_ISSET(c->efd, readset)) {
+ (c->detach_close || 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))
+ if (len < 0 && (errno == EINTR || ((errno == EAGAIN ||
+ errno == EWOULDBLOCK) && !c->detach_close)))
return 1;
if (len <= 0) {
debug2("channel %d: closing read-efd %d",
}
return 1;
}
+
+/* ARGSUSED */
static int
-channel_handle_ctl(Channel *c, fd_set * readset, fd_set * writeset)
+channel_handle_ctl(Channel *c, fd_set *readset, fd_set *writeset)
{
char buf[16];
int len;
/* Monitor control fd to detect if the slave client exits */
if (c->ctl_fd != -1 && FD_ISSET(c->ctl_fd, readset)) {
len = read(c->ctl_fd, buf, sizeof(buf));
- if (len < 0 && (errno == EINTR || errno == EAGAIN))
+ if (len < 0 &&
+ (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK))
return 1;
if (len <= 0) {
debug2("channel %d: ctl read<=0", c->self);
}
return 1;
}
+
static int
channel_check_window(Channel *c)
{
if (c->type == SSH_CHANNEL_OPEN &&
!(c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD)) &&
- c->local_window < c->local_window_max/2 &&
+ ((c->local_window_max - c->local_window >
+ c->local_maxpacket*3) ||
+ c->local_window < c->local_window_max/2) &&
c->local_consumed > 0) {
packet_start(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
packet_put_int(c->remote_id);
}
static void
-channel_post_open(Channel *c, fd_set * readset, fd_set * writeset)
+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)
channel_check_window(c);
}
+/* ARGSUSED */
static void
-channel_post_output_drain_13(Channel *c, fd_set * readset, fd_set * writeset)
+channel_post_output_drain_13(Channel *c, fd_set *readset, fd_set *writeset)
{
int len;
}
static void
-channel_handler(chan_fn *ftab[], fd_set * readset, fd_set * writeset)
+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);
channel_prepare_select(fd_set **readsetp, fd_set **writesetp, int *maxfdp,
u_int *nallocp, int rekeying)
{
- u_int n, sz;
+ u_int n, sz, nfdset;
n = MAX(*maxfdp, channel_max_fd);
- sz = howmany(n+1, NFDBITS) * sizeof(fd_mask);
+ nfdset = howmany(n+1, NFDBITS);
+ /* Explicitly test here, because xrealloc isn't always called */
+ if (nfdset && SIZE_T_MAX / nfdset < sizeof(fd_mask))
+ fatal("channel_prepare_select: max_fd (%d) is too large", n);
+ sz = nfdset * sizeof(fd_mask);
+
/* perhaps check sz < nalloc/2 and shrink? */
if (*readsetp == NULL || sz > *nallocp) {
- *readsetp = xrealloc(*readsetp, sz);
- *writesetp = xrealloc(*writesetp, sz);
+ *readsetp = xrealloc(*readsetp, nfdset, sizeof(fd_mask));
+ *writesetp = xrealloc(*writesetp, nfdset, sizeof(fd_mask));
*nallocp = sz;
}
*maxfdp = n;
* events pending.
*/
void
-channel_after_select(fd_set * readset, fd_set * writeset)
+channel_after_select(fd_set *readset, fd_set *writeset)
{
channel_handler(channel_post, readset, writeset);
}
/* If there is data to send to the connection, enqueue some of it now. */
-
void
channel_output_poll(void)
{
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.
/* -- protocol input */
+/* ARGSUSED */
void
channel_input_data(int type, u_int32_t seq, void *ctxt)
{
return;
/* Get the data. */
- data = packet_get_string(&data_len);
+ data = packet_get_string_ptr(&data_len);
/*
* Ignore data for protocol > 1.3 if output end is no longer open.
c->local_window -= data_len;
c->local_consumed += data_len;
}
- xfree(data);
return;
}
if (data_len > c->local_window) {
logit("channel %d: rcvd too much data %d, win %d",
c->self, data_len, c->local_window);
- xfree(data);
return;
}
c->local_window -= data_len;
}
+ if (c->datagram)
+ buffer_put_string(&c->output, data, data_len);
+ else
+ buffer_append(&c->output, data, data_len);
packet_check_eom();
- buffer_append(&c->output, data, data_len);
- xfree(data);
}
+/* ARGSUSED */
void
channel_input_extended_data(int type, u_int32_t seq, void *ctxt)
{
xfree(data);
}
+/* ARGSUSED */
void
channel_input_ieof(int type, u_int32_t seq, void *ctxt)
{
}
+/* ARGSUSED */
void
channel_input_close(int type, u_int32_t seq, void *ctxt)
{
}
/* proto version 1.5 overloads CLOSE_CONFIRMATION with OCLOSE */
+/* ARGSUSED */
void
channel_input_oclose(int type, u_int32_t seq, void *ctxt)
{
chan_rcvd_oclose(c);
}
+/* ARGSUSED */
void
channel_input_close_confirmation(int type, u_int32_t seq, void *ctxt)
{
channel_free(c);
}
+/* ARGSUSED */
void
channel_input_open_confirmation(int type, u_int32_t seq, void *ctxt)
{
if (compat20) {
c->remote_window = packet_get_int();
c->remote_maxpacket = packet_get_int();
- if (c->confirm) {
+ if (c->open_confirm) {
debug2("callback start");
- c->confirm(c->self, c->confirm_ctx);
+ c->open_confirm(c->self, c->open_confirm_ctx);
debug2("callback done");
}
debug2("channel %d: open confirm rwindow %u rmax %u", c->self,
return "unknown reason";
}
+/* ARGSUSED */
void
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 */
void
channel_input_window_adjust(int type, u_int32_t seq, void *ctxt)
{
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();
c->remote_window += adjust;
}
+/* ARGSUSED */
void
channel_input_port_open(int type, u_int32_t seq, void *ctxt)
{
Channel *c = NULL;
u_short host_port;
char *host, *originator_string;
- int remote_id, sock = -1;
+ int remote_id;
remote_id = packet_get_int();
host = packet_get_string(NULL);
originator_string = xstrdup("unknown (remote did not supply name)");
}
packet_check_eom();
- sock = channel_connect_to(host, host_port);
- if (sock != -1) {
- c = channel_new("connected socket",
- SSH_CHANNEL_CONNECTING, sock, sock, -1, 0, 0, 0,
- originator_string, 1);
- c->remote_id = remote_id;
- }
+ c = channel_connect_to(host, host_port,
+ "connected socket", originator_string);
xfree(originator_string);
+ xfree(host);
if (c == NULL) {
packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE);
packet_put_int(remote_id);
packet_send();
- }
- xfree(host);
+ } else
+ c->remote_id = remote_id;
}
+/* ARGSUSED */
+void
+channel_input_status_confirm(int type, u_int32_t seq, void *ctxt)
+{
+ Channel *c;
+ struct channel_confirm *cc;
+ int id;
+
+ /* Reset keepalive timeout */
+ packet_set_alive_timeouts(0);
+
+ id = packet_get_int();
+ packet_check_eom();
+
+ debug2("channel_input_status_confirm: type %d id %d", type, id);
+
+ if ((c = channel_lookup(id)) == NULL) {
+ logit("channel_input_status_confirm: %d: unknown", id);
+ return;
+ }
+ ;
+ if ((cc = TAILQ_FIRST(&c->status_confirms)) == NULL)
+ return;
+ cc->cb(type, c, cc->ctx);
+ TAILQ_REMOVE(&c->status_confirms, cc, entry);
+ bzero(cc, sizeof(*cc));
+ xfree(cc);
+}
/* -- tcp forwarding */
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;
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;
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;
}
wildcard = 1;
} else if (gateway_ports || is_client) {
if (((datafellows & SSH_OLD_FORWARD_ADDR) &&
- strcmp(listen_addr, "0.0.0.0") == 0) ||
+ strcmp(listen_addr, "0.0.0.0") == 0 && is_client == 0) ||
*listen_addr == '\0' || strcmp(listen_addr, "*") == 0 ||
(!is_client && gateway_ports == 1))
wildcard = 1;
if (addr == NULL) {
/* This really shouldn't happen */
packet_disconnect("getaddrinfo: fatal error: %s",
- gai_strerror(r));
+ ssh_gai_strerror(r));
} else {
error("channel_setup_fwd_listener: "
- "getaddrinfo(%.64s): %s", addr, gai_strerror(r));
+ "getaddrinfo(%.64s): %s", addr,
+ ssh_gai_strerror(r));
}
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));
}
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) {
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;
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;
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);
}
/*
* the secure channel to host:port from local side.
*/
-void
+int
channel_request_remote_forwarding(const char *listen_host, u_short listen_port,
const char *host_to_connect, u_short port_to_connect)
{
/* Send the forward request to the remote side. */
if (compat20) {
const char *address_to_bind;
- if (listen_host == NULL)
- address_to_bind = "localhost";
- else if (*listen_host == '\0' || strcmp(listen_host, "*") == 0)
- address_to_bind = "";
- else
+ if (listen_host == NULL) {
+ if (datafellows & SSH_BUG_RFWD_ADDR)
+ address_to_bind = "127.0.0.1";
+ else
+ address_to_bind = "localhost";
+ } else if (*listen_host == '\0' ||
+ strcmp(listen_host, "*") == 0) {
+ if (datafellows & SSH_BUG_RFWD_ADDR)
+ address_to_bind = "0.0.0.0";
+ else
+ address_to_bind = "";
+ } else
address_to_bind = listen_host;
packet_start(SSH2_MSG_GLOBAL_REQUEST);
success = 1;
break;
case SSH_SMSG_FAILURE:
- logit("Warning: Server denied remote port forwarding.");
break;
default:
/* Unknown packet */
permitted_opens[num_permitted_opens].listen_port = listen_port;
num_permitted_opens++;
}
+ return (success ? 0 : -1);
}
/*
/*
* This is called after receiving CHANNEL_FORWARDING_REQUEST. This initates
* listening for the port, and sends back a success reply (or disconnect
- * message if there was an error). This never returns if there was an error.
+ * message if there was an error).
*/
-
-void
+int
channel_input_port_forward_request(int is_root, int gateway_ports)
{
u_short port, host_port;
+ int success = 0;
char *hostname;
/* Get arguments from the packet. */
#endif
/* Initiate forwarding */
- channel_setup_local_fwd_listener(NULL, port, hostname,
+ success = channel_setup_local_fwd_listener(NULL, port, hostname,
host_port, gateway_ports);
/* Free the argument string. */
xfree(hostname);
+
+ return (success ? 0 : -1);
}
/*
channel_add_permitted_opens(char *host, int port)
{
if (num_permitted_opens >= SSH_MAX_FORWARDS_PER_DIRECTION)
- fatal("channel_request_remote_forwarding: too many forwards");
+ fatal("channel_add_permitted_opens: too many forwards");
debug("allow port forwarding to host %s port %d", host, port);
permitted_opens[num_permitted_opens].host_to_connect = xstrdup(host);
all_opens_permitted = 0;
}
+int
+channel_add_adm_permitted_opens(char *host, int port)
+{
+ if (num_adm_permitted_opens >= SSH_MAX_FORWARDS_PER_DIRECTION)
+ fatal("channel_add_adm_permitted_opens: too many forwards");
+ debug("config allows port forwarding to host %s port %d", host, port);
+
+ permitted_adm_opens[num_adm_permitted_opens].host_to_connect
+ = xstrdup(host);
+ permitted_adm_opens[num_adm_permitted_opens].port_to_connect = port;
+ return ++num_adm_permitted_opens;
+}
+
void
channel_clear_permitted_opens(void)
{
if (permitted_opens[i].host_to_connect != NULL)
xfree(permitted_opens[i].host_to_connect);
num_permitted_opens = 0;
+}
+
+void
+channel_clear_adm_permitted_opens(void)
+{
+ int i;
+ for (i = 0; i < num_adm_permitted_opens; i++)
+ if (permitted_adm_opens[i].host_to_connect != NULL)
+ xfree(permitted_adm_opens[i].host_to_connect);
+ 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(" %s:%d", permitted_adm_opens[i].host_to_connect,
+ permitted_adm_opens[i].port_to_connect);
+ printf("\n");
+}
-/* return socket to remote host, port */
+/* Try to start non-blocking connect to next host in cctx list */
static int
-connect_to(const char *host, u_short port)
+connect_next(struct channel_connect *cctx)
{
- struct addrinfo hints, *ai, *aitop;
+ int sock, saved_errno;
char ntop[NI_MAXHOST], strport[NI_MAXSERV];
- int gaierr;
- int sock = -1;
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = IPv4or6;
- hints.ai_socktype = SOCK_STREAM;
- snprintf(strport, sizeof strport, "%d", port);
- if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) {
- error("connect_to %.100s: unknown host (%s)", host,
- gai_strerror(gaierr));
- return -1;
- }
- for (ai = aitop; ai; ai = ai->ai_next) {
- if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
+ for (; cctx->ai; cctx->ai = cctx->ai->ai_next) {
+ if (cctx->ai->ai_family != AF_INET &&
+ cctx->ai->ai_family != AF_INET6)
continue;
- if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop),
- strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
- error("connect_to: getnameinfo failed");
+ if (getnameinfo(cctx->ai->ai_addr, cctx->ai->ai_addrlen,
+ ntop, sizeof(ntop), strport, sizeof(strport),
+ NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
+ error("connect_next: getnameinfo failed");
continue;
}
- sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
- if (sock < 0) {
- if (ai->ai_next == NULL)
+ 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
verbose("socket: %.100s", strerror(errno));
}
if (set_nonblock(sock) == -1)
fatal("%s: set_nonblock(%d)", __func__, sock);
- if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0 &&
- errno != EINPROGRESS) {
- error("connect_to %.100s port %s: %.100s", ntop, strport,
+ if (connect(sock, cctx->ai->ai_addr,
+ cctx->ai->ai_addrlen) == -1 && errno != EINPROGRESS) {
+ debug("connect_next: host %.100s ([%.100s]:%s): "
+ "%.100s", cctx->host, ntop, strport,
strerror(errno));
+ saved_errno = errno;
close(sock);
+ errno = saved_errno;
continue; /* fail -- try next */
}
- break; /* success */
+ debug("connect_next: host %.100s ([%.100s]:%s) "
+ "in progress, fd=%d", cctx->host, ntop, strport, sock);
+ cctx->ai = cctx->ai->ai_next;
+ set_nodelay(sock);
+ return sock;
+ }
+ return -1;
+}
+static void
+channel_connect_ctx_free(struct channel_connect *cctx)
+{
+ xfree(cctx->host);
+ if (cctx->aitop)
+ freeaddrinfo(cctx->aitop);
+ bzero(cctx, sizeof(*cctx));
+ cctx->host = NULL;
+ cctx->ai = cctx->aitop = NULL;
+}
+
+/* Return CONNECTING channel to remote host, port */
+static Channel *
+connect_to(const char *host, u_short port, char *ctype, char *rname)
+{
+ struct addrinfo hints;
+ int gaierr;
+ int sock = -1;
+ char strport[NI_MAXSERV];
+ struct channel_connect cctx;
+ Channel *c;
+
+ memset(&cctx, 0, sizeof(cctx));
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = IPv4or6;
+ hints.ai_socktype = SOCK_STREAM;
+ snprintf(strport, sizeof strport, "%d", port);
+ if ((gaierr = getaddrinfo(host, strport, &hints, &cctx.aitop)) != 0) {
+ error("connect_to %.100s: unknown host (%s)", host,
+ ssh_gai_strerror(gaierr));
+ return NULL;
}
- freeaddrinfo(aitop);
- if (!ai) {
- error("connect_to %.100s port %d: failed.", host, port);
- return -1;
+
+ cctx.host = xstrdup(host);
+ cctx.port = port;
+ cctx.ai = cctx.aitop;
+
+ if ((sock = connect_next(&cctx)) == -1) {
+ error("connect to %.100s port %d failed: %s",
+ host, port, strerror(errno));
+ channel_connect_ctx_free(&cctx);
+ return NULL;
}
- /* success */
- set_nodelay(sock);
- return sock;
+ c = channel_new(ctype, SSH_CHANNEL_CONNECTING, sock, sock, -1,
+ CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1);
+ c->connect_ctx = cctx;
+ return c;
}
-int
-channel_connect_by_listen_address(u_short listen_port)
+Channel *
+channel_connect_by_listen_address(u_short listen_port, char *ctype, char *rname)
{
int i;
- for (i = 0; i < num_permitted_opens; i++)
+ for (i = 0; i < num_permitted_opens; i++) {
if (permitted_opens[i].host_to_connect != NULL &&
- permitted_opens[i].listen_port == listen_port)
+ permitted_opens[i].listen_port == listen_port) {
return connect_to(
permitted_opens[i].host_to_connect,
- permitted_opens[i].port_to_connect);
+ permitted_opens[i].port_to_connect, ctype, rname);
+ }
+ }
error("WARNING: Server requests forwarding for unknown listen_port %d",
listen_port);
- return -1;
+ return NULL;
}
/* Check if connecting to that port is permitted and connect. */
-int
-channel_connect_to(const char *host, u_short port)
+Channel *
+channel_connect_to(const char *host, u_short port, char *ctype, char *rname)
{
- int i, permit;
+ int i, permit, permit_adm = 1;
permit = all_opens_permitted;
if (!permit) {
permitted_opens[i].port_to_connect == port &&
strcmp(permitted_opens[i].host_to_connect, host) == 0)
permit = 1;
+ }
+ if (num_adm_permitted_opens > 0) {
+ permit_adm = 0;
+ for (i = 0; i < num_adm_permitted_opens; i++)
+ if (permitted_adm_opens[i].host_to_connect != NULL &&
+ permitted_adm_opens[i].port_to_connect == port &&
+ strcmp(permitted_adm_opens[i].host_to_connect, host)
+ == 0)
+ permit_adm = 1;
}
- if (!permit) {
+
+ if (!permit || !permit_adm) {
logit("Received request to connect to host %.100s port %d, "
"but the request was denied.", host, port);
- return -1;
+ return NULL;
}
- return connect_to(host, port);
+ return connect_to(host, port, ctype, rname);
}
void
if (ioctl(channels[i]->rfd, TIOCGWINSZ, &ws) < 0)
continue;
channel_request_start(i, "window-change", 0);
- packet_put_int(ws.ws_col);
- packet_put_int(ws.ws_row);
- packet_put_int(ws.ws_xpixel);
- packet_put_int(ws.ws_ypixel);
+ packet_put_int((u_int)ws.ws_col);
+ packet_put_int((u_int)ws.ws_row);
+ packet_put_int((u_int)ws.ws_xpixel);
+ packet_put_int((u_int)ws.ws_ypixel);
packet_send();
}
}
hints.ai_socktype = SOCK_STREAM;
snprintf(strport, sizeof strport, "%d", port);
if ((gaierr = getaddrinfo(NULL, strport, &hints, &aitop)) != 0) {
- error("getaddrinfo: %.100s", gai_strerror(gaierr));
+ error("getaddrinfo: %.100s", ssh_gai_strerror(gaierr));
return -1;
}
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));
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
- channel_set_reuseaddr(sock);
+ 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) {
debug2("bind port %d: %.100s", port, strerror(errno));
close(sock);
- if (ai->ai_next)
- continue;
-
for (n = 0; n < num_socks; n++) {
close(socks[n]);
}
break;
}
socks[num_socks++] = sock;
-#ifndef DONT_TRY_OTHER_AF
if (num_socks == NUM_SOCKS)
break;
-#else
- if (x11_use_localhost) {
- if (num_socks == NUM_SOCKS)
- break;
- } else {
- break;
- }
-#endif
}
freeaddrinfo(aitop);
if (num_socks > 0)
}
/* Allocate a channel for each socket. */
- *chanids = xmalloc(sizeof(**chanids) * (num_socks + 1));
+ *chanids = xcalloc(num_socks + 1, sizeof(**chanids));
for (n = 0; n < num_socks; n++) {
sock = socks[n];
nc = channel_new("x11 listener",
}
static int
-connect_local_xsocket(u_int dnr)
+connect_local_xsocket_path(const char *pathname)
{
int sock;
struct sockaddr_un addr;
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);
- if (connect(sock, (struct sockaddr *) & addr, sizeof(addr)) == 0)
+ strlcpy(addr.sun_path, pathname, sizeof addr.sun_path);
+ if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0)
return sock;
close(sock);
error("connect %.100s: %.100s", addr.sun_path, strerror(errno));
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)
{
- int display_number, sock = 0;
+ u_int display_number;
const char *display;
char buf[1024], *cp;
struct addrinfo hints, *ai, *aitop;
char strport[NI_MAXSERV];
- int gaierr;
+ int gaierr, sock = 0;
/* Try to open a socket for the local X server. */
display = getenv("DISPLAY");
* 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]
if (strncmp(display, "unix:", 5) == 0 ||
display[0] == ':') {
/* Connect to the unix domain socket. */
- if (sscanf(strrchr(display, ':') + 1, "%d", &display_number) != 1) {
+ if (sscanf(strrchr(display, ':') + 1, "%u", &display_number) != 1) {
error("Could not parse display number from DISPLAY: %.100s",
display);
return -1;
}
*cp = 0;
/* buf now contains the host name. But first we parse the display number. */
- if (sscanf(cp + 1, "%d", &display_number) != 1) {
+ if (sscanf(cp + 1, "%u", &display_number) != 1) {
error("Could not parse display number from DISPLAY: %.100s",
display);
return -1;
memset(&hints, 0, sizeof(hints));
hints.ai_family = IPv4or6;
hints.ai_socktype = SOCK_STREAM;
- snprintf(strport, sizeof strport, "%d", 6000 + display_number);
+ snprintf(strport, sizeof strport, "%u", 6000 + display_number);
if ((gaierr = getaddrinfo(buf, strport, &hints, &aitop)) != 0) {
- error("%.100s: unknown host. (%s)", buf, gai_strerror(gaierr));
+ error("%.100s: unknown host. (%s)", buf,
+ ssh_gai_strerror(gaierr));
return -1;
}
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;
}
/* Connect it to the display. */
if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0) {
- debug2("connect %.100s port %d: %.100s", buf,
+ debug2("connect %.100s port %u: %.100s", buf,
6000 + display_number, strerror(errno));
close(sock);
continue;
}
freeaddrinfo(aitop);
if (!ai) {
- error("connect %.100s port %d: %.100s", buf, 6000 + display_number,
+ error("connect %.100s port %u: %.100s", buf, 6000 + display_number,
strerror(errno));
return -1;
}
* with either SSH_MSG_OPEN_CONFIRMATION or SSH_MSG_OPEN_FAILURE.
*/
+/* ARGSUSED */
void
x11_input_open(int type, u_int32_t seq, void *ctxt)
{
}
/* dummy protocol handler that denies SSH-1 requests (agent/x11) */
+/* ARGSUSED */
void
deny_input_open(int type, u_int32_t seq, void *ctxt)
{
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();
return;
}
- cp = disp;
- if (disp)
- cp = strchr(disp, ':');
+ cp = strchr(disp, ':');
if (cp)
cp = strchr(cp, '.');
if (cp)
- screen_number = atoi(cp + 1);
+ screen_number = (u_int)strtonum(cp + 1, 0, 400, NULL);
else
screen_number = 0;