/* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * This file contains functions for generic socket connection forwarding. * There is also code for initiating connection forwarding for X11 connections, * arbitrary tcp/ip connections, and the authentication agent connection. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * * SSH2 support added by Markus Friedl. * Copyright (c) 1999,2000 Markus Friedl. All rights reserved. * Copyright (c) 1999 Dug Song. All rights reserved. * Copyright (c) 1999 Theo de Raadt. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" RCSID("$OpenBSD: channel.c,v 1.2 2001/05/30 15:20:09 markus Exp $"); #include "ssh2.h" #include "packet.h" #include "xmalloc.h" #include "log.h" #include "misc.h" #include "channel.h" #include "compat.h" /* * Pointer to an array containing all allocated channels. The array is * dynamically extended as needed. */ Channel **channels = NULL; /* * Size of the channel array. All slots of the array must always be * initialized (at least the type field); unused slots set to NULL */ int channels_alloc = 0; /* * Maximum file descriptor value used in any of the channels. This is * updated in channel_new. */ int channel_max_fd = 0; 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 == NULL) { log("channel_lookup: %d: bad id: channel free", id); return NULL; } return c; } /* * Register filedescriptors for a channel, used when allocating a channel or * when the channel consumer/producer is ready, e.g. shell exec'd */ void channel_register_fds(Channel *c, int rfd, int wfd, int efd, int extusage, int nonblock) { /* 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 */ c->rfd = rfd; c->wfd = wfd; c->sock = (rfd == wfd) ? rfd : -1; c->efd = efd; c->extended_usage = extusage; /* XXX ugly hack: nonblock is only set by the server */ if (nonblock && isatty(c->rfd)) { debug("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; } /* enable nonblocking mode */ if (nonblock) { if (rfd != -1) set_nonblock(rfd); if (wfd != -1) set_nonblock(wfd); if (efd != -1) set_nonblock(efd); } } /* * 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, int window, int maxpack, int extusage, char *remote_name, int nonblock) { int i, found; Channel *c; /* Do initial allocation if this is the first call. */ if (channels_alloc == 0) { chan_init(); channels_alloc = 10; channels = xmalloc(channels_alloc * sizeof(Channel *)); for (i = 0; i < channels_alloc; i++) channels[i] = NULL; /* * Kludge: arrange a call to channel_stop_listening if we * terminate with fatal(). */ fatal_add_cleanup((void (*) (void *)) channel_stop_listening, NULL); } /* Try to find a free slot where to put the new channel. */ for (found = -1, i = 0; i < channels_alloc; i++) if (channels[i] == NULL) { /* Found a free slot. */ found = i; break; } if (found == -1) { /* There are no free slots. Take last+1 slot and expand the array. */ found = channels_alloc; channels_alloc += 10; debug2("channel: expanding %d", channels_alloc); channels = xrealloc(channels, channels_alloc * sizeof(Channel *)); for (i = found; i < channels_alloc; i++) channels[i] = NULL; } /* Initialize and return new channel. */ c = channels[found] = xmalloc(sizeof(Channel)); buffer_init(&c->input); buffer_init(&c->output); buffer_init(&c->extended); chan_init_iostates(c); channel_register_fds(c, rfd, wfd, efd, extusage, nonblock); c->self = found; c->type = type; c->ctype = ctype; c->local_window = window; c->local_window_max = window; c->local_consumed = 0; c->local_maxpacket = maxpack; 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; c->input_filter = NULL; debug("channel %d: new [%s]", found, remote_name); return c; } /* Close all channel fd/socket. */ void channel_close_fds(Channel *c) { debug3("channel_close_fds: channel %d: r %d w %d e %d", c->self, c->rfd, c->wfd, c->efd); if (c->sock != -1) { close(c->sock); c->sock = -1; } if (c->rfd != -1) { close(c->rfd); c->rfd = -1; } if (c->wfd != -1) { close(c->wfd); c->wfd = -1; } if (c->efd != -1) { close(c->efd); c->efd = -1; } } /* Free the channel and close its fd/socket. */ void channel_free(Channel *c) { char *s; int i, n; for (n = 0, i = 0; i < channels_alloc; i++) if (channels[i]) n++; debug("channel_free: channel %d: %s, nchannels %d", c->self, c->remote_name ? c->remote_name : "???", n); s = channel_open_message(); debug3("channel_free: status: %s", s); xfree(s); if (c->dettach_user != NULL) { debug("channel_free: channel %d: dettaching channel user", c->self); c->dettach_user(c->self, NULL); } if (c->sock != -1) shutdown(c->sock, SHUT_RDWR); channel_close_fds(c); buffer_free(&c->input); buffer_free(&c->output); buffer_free(&c->extended); if (c->remote_name) { xfree(c->remote_name); c->remote_name = NULL; } channels[c->self] = NULL; xfree(c); } /* * Stops listening for channels, and removes any unix domain sockets that we * might have. */ void channel_stop_listening() { int i; Channel *c; for (i = 0; i < channels_alloc; i++) { c = channels[i]; if (c != NULL) { switch (c->type) { case SSH_CHANNEL_AUTH_SOCKET: close(c->sock); unlink(c->path); channel_free(c); break; case SSH_CHANNEL_PORT_LISTENER: case SSH_CHANNEL_RPORT_LISTENER: case SSH_CHANNEL_X11_LISTENER: close(c->sock); channel_free(c); break; default: break; } } } } /* * Closes the sockets/fds of all channels. This is used to close extra file * descriptors after a fork. */ void channel_close_all() { int i; for (i = 0; i < channels_alloc; i++) if (channels[i] != NULL) channel_close_fds(channels[i]); } /* * 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() { u_int i; Channel *c; for (i = 0; i < channels_alloc; i++) { c = channels[i]; if (c != NULL && c->type == SSH_CHANNEL_OPEN) { if (!compat20 && 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(&c->output) > packet_get_maxsize()) { debug("channel %d: big output buffer %d", c->self, buffer_len(&c->output)); return 0; } } } return 1; } /* Returns true if any channel is still open. */ int channel_still_open() { int i; Channel *c; for (i = 0; i < channels_alloc; i++) { c = channels[i]; if (c == NULL) continue; switch (c->type) { case SSH_CHANNEL_X11_LISTENER: case SSH_CHANNEL_PORT_LISTENER: case SSH_CHANNEL_RPORT_LISTENER: case SSH_CHANNEL_CLOSED: case SSH_CHANNEL_AUTH_SOCKET: case SSH_CHANNEL_DYNAMIC: case SSH_CHANNEL_CONNECTING: case SSH_CHANNEL_ZOMBIE: continue; case SSH_CHANNEL_LARVAL: if (!compat20) fatal("cannot happen: SSH_CHANNEL_LARVAL"); continue; case SSH_CHANNEL_OPENING: case SSH_CHANNEL_OPEN: case SSH_CHANNEL_X11_OPEN: return 1; case SSH_CHANNEL_INPUT_DRAINING: case SSH_CHANNEL_OUTPUT_DRAINING: if (!compat13) fatal("cannot happen: OUT_DRAIN"); return 1; default: fatal("channel_still_open: bad channel type %d", c->type); /* NOTREACHED */ } } return 0; } /* Returns the id of an open channel suitable for keepaliving */ int channel_find_open() { int i; Channel *c; for (i = 0; i < channels_alloc; i++) { c = channels[i]; if (c == NULL) continue; switch (c->type) { case SSH_CHANNEL_CLOSED: case SSH_CHANNEL_DYNAMIC: case SSH_CHANNEL_X11_LISTENER: case SSH_CHANNEL_PORT_LISTENER: case SSH_CHANNEL_RPORT_LISTENER: case SSH_CHANNEL_OPENING: case SSH_CHANNEL_CONNECTING: case SSH_CHANNEL_ZOMBIE: continue; case SSH_CHANNEL_LARVAL: case SSH_CHANNEL_AUTH_SOCKET: case SSH_CHANNEL_OPEN: case SSH_CHANNEL_X11_OPEN: return i; case SSH_CHANNEL_INPUT_DRAINING: case SSH_CHANNEL_OUTPUT_DRAINING: if (!compat13) fatal("cannot happen: OUT_DRAIN"); return i; default: fatal("channel_find_open: bad channel type %d", c->type); /* NOTREACHED */ } } return -1; } /* * Returns a message describing the currently open forwarded connections, * suitable for sending to the client. The message contains crlf pairs for * newlines. */ char * channel_open_message() { Buffer buffer; Channel *c; char buf[1024], *cp; int i; buffer_init(&buffer); snprintf(buf, sizeof buf, "The following connections are open:\r\n"); buffer_append(&buffer, buf, strlen(buf)); for (i = 0; i < channels_alloc; i++) { c = channels[i]; if (c == NULL) continue; switch (c->type) { case SSH_CHANNEL_X11_LISTENER: case SSH_CHANNEL_PORT_LISTENER: case SSH_CHANNEL_RPORT_LISTENER: case SSH_CHANNEL_CLOSED: case SSH_CHANNEL_AUTH_SOCKET: case SSH_CHANNEL_ZOMBIE: continue; case SSH_CHANNEL_LARVAL: case SSH_CHANNEL_OPENING: case SSH_CHANNEL_CONNECTING: case SSH_CHANNEL_DYNAMIC: case SSH_CHANNEL_OPEN: case SSH_CHANNEL_X11_OPEN: case SSH_CHANNEL_INPUT_DRAINING: case SSH_CHANNEL_OUTPUT_DRAINING: snprintf(buf, sizeof buf, " #%d %.300s (t%d r%d i%d/%d o%d/%d fd %d/%d)\r\n", c->self, c->remote_name, c->type, c->remote_id, c->istate, buffer_len(&c->input), c->ostate, buffer_len(&c->output), c->rfd, c->wfd); buffer_append(&buffer, buf, strlen(buf)); continue; default: fatal("channel_open_message: bad channel type %d", c->type); /* NOTREACHED */ } } buffer_append(&buffer, "\0", 1); cp = xstrdup(buffer_ptr(&buffer)); buffer_free(&buffer); return cp; } void channel_send_open(int id) { Channel *c = channel_lookup(id); if (c == NULL) { log("channel_send_open: %d: bad id", id); return; } debug("send channel open %d", id); packet_start(SSH2_MSG_CHANNEL_OPEN); packet_put_cstring(c->ctype); packet_put_int(c->self); packet_put_int(c->local_window); packet_put_int(c->local_maxpacket); packet_send(); } void channel_request(int id, char *service, int wantconfirm) { channel_request_start(id, service, wantconfirm); packet_send(); debug("channel request %d: %s", id, service) ; } void channel_request_start(int id, char *service, int wantconfirm) { Channel *c = channel_lookup(id); if (c == NULL) { log("channel_request: %d: bad id", id); return; } packet_start(SSH2_MSG_CHANNEL_REQUEST); packet_put_int(c->remote_id); packet_put_cstring(service); packet_put_char(wantconfirm); } void channel_register_callback(int id, int mtype, channel_callback_fn *fn, void *arg) { Channel *c = channel_lookup(id); if (c == NULL) { log("channel_register_callback: %d: bad id", id); return; } c->cb_event = mtype; c->cb_fn = fn; c->cb_arg = arg; } void channel_register_cleanup(int id, channel_callback_fn *fn) { Channel *c = channel_lookup(id); if (c == NULL) { log("channel_register_cleanup: %d: bad id", id); return; } c->dettach_user = fn; } void channel_cancel_cleanup(int id) { Channel *c = channel_lookup(id); if (c == NULL) { log("channel_cancel_cleanup: %d: bad id", id); return; } c->dettach_user = NULL; } void channel_register_filter(int id, channel_filter_fn *fn) { Channel *c = channel_lookup(id); if (c == NULL) { log("channel_register_filter: %d: bad id", id); return; } c->input_filter = fn; } void channel_set_fds(int id, int rfd, int wfd, int efd, int extusage, int nonblock) { 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); c->type = SSH_CHANNEL_OPEN; /* XXX window size? */ c->local_window = c->local_window_max = c->local_maxpacket * 2; packet_start(SSH2_MSG_CHANNEL_WINDOW_ADJUST); packet_put_int(c->remote_id); packet_put_int(c->local_window); packet_send(); }