/* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * * 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". * * * Copyright (c) 2001 Markus Friedl. 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-input.c,v 1.1 2001/05/30 12:55:07 markus Exp $"); #include "ssh1.h" #include "ssh2.h" #include "packet.h" #include "xmalloc.h" #include "log.h" #include "channel.h" #include "compat.h" void channel_input_data(int type, int plen, void *ctxt) { int id; char *data; u_int data_len; Channel *c; /* Get the channel number and verify it. */ id = packet_get_int(); c = channel_lookup(id); if (c == NULL) packet_disconnect("Received data for nonexistent channel %d.", id); /* Ignore any data for non-open channels (might happen on close) */ if (c->type != SSH_CHANNEL_OPEN && c->type != SSH_CHANNEL_X11_OPEN) return; /* same for protocol 1.5 if output end is no longer open */ if (!compat13 && c->ostate != CHAN_OUTPUT_OPEN) return; /* Get the data. */ data = packet_get_string(&data_len); packet_done(); if (compat20){ if (data_len > c->local_maxpacket) { log("channel %d: rcvd big packet %d, maxpack %d", c->self, data_len, c->local_maxpacket); } if (data_len > c->local_window) { log("channel %d: rcvd too much data %d, win %d", c->self, data_len, c->local_window); xfree(data); return; } c->local_window -= data_len; }else{ packet_integrity_check(plen, 4 + 4 + data_len, type); } buffer_append(&c->output, data, data_len); xfree(data); } void channel_input_extended_data(int type, int plen, void *ctxt) { int id; int tcode; char *data; u_int data_len; Channel *c; /* Get the channel number and verify it. */ id = packet_get_int(); c = channel_lookup(id); if (c == NULL) packet_disconnect("Received extended_data for bad channel %d.", id); if (c->type != SSH_CHANNEL_OPEN) { log("channel %d: ext data for non open", id); return; } tcode = packet_get_int(); if (c->efd == -1 || c->extended_usage != CHAN_EXTENDED_WRITE || tcode != SSH2_EXTENDED_DATA_STDERR) { log("channel %d: bad ext data", c->self); return; } data = packet_get_string(&data_len); packet_done(); if (data_len > c->local_window) { log("channel %d: rcvd too much extended_data %d, win %d", c->self, data_len, c->local_window); xfree(data); return; } debug2("channel %d: rcvd ext data %d", c->self, data_len); c->local_window -= data_len; buffer_append(&c->extended, data, data_len); xfree(data); } void channel_input_ieof(int type, int plen, void *ctxt) { int id; Channel *c; packet_integrity_check(plen, 4, type); id = packet_get_int(); c = channel_lookup(id); if (c == NULL) packet_disconnect("Received ieof for nonexistent channel %d.", id); chan_rcvd_ieof(c); } void channel_input_close(int type, int plen, void *ctxt) { int id; Channel *c; packet_integrity_check(plen, 4, type); id = packet_get_int(); c = channel_lookup(id); if (c == NULL) packet_disconnect("Received close for nonexistent channel %d.", id); /* * Send a confirmation that we have closed the channel and no more * data is coming for it. */ packet_start(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION); packet_put_int(c->remote_id); packet_send(); /* * If the channel is in closed state, we have sent a close request, * and the other side will eventually respond with a confirmation. * Thus, we cannot free the channel here, because then there would be * no-one to receive the confirmation. The channel gets freed when * the confirmation arrives. */ if (c->type != SSH_CHANNEL_CLOSED) { /* * Not a closed channel - mark it as draining, which will * cause it to be freed later. */ buffer_consume(&c->input, buffer_len(&c->input)); c->type = SSH_CHANNEL_OUTPUT_DRAINING; } } /* proto version 1.5 overloads CLOSE_CONFIRMATION with OCLOSE */ void channel_input_oclose(int type, int plen, void *ctxt) { int id = packet_get_int(); Channel *c = channel_lookup(id); packet_integrity_check(plen, 4, type); if (c == NULL) packet_disconnect("Received oclose for nonexistent channel %d.", id); chan_rcvd_oclose(c); } void channel_input_close_confirmation(int type, int plen, void *ctxt) { int id = packet_get_int(); Channel *c = channel_lookup(id); packet_done(); if (c == NULL) packet_disconnect("Received close confirmation for " "out-of-range channel %d.", id); if (c->type != SSH_CHANNEL_CLOSED) packet_disconnect("Received close confirmation for " "non-closed channel %d (type %d).", id, c->type); channel_free(c); } void channel_input_open_confirmation(int type, int plen, void *ctxt) { int id, remote_id; Channel *c; if (!compat20) packet_integrity_check(plen, 4 + 4, type); id = packet_get_int(); c = channel_lookup(id); if (c==NULL || c->type != SSH_CHANNEL_OPENING) packet_disconnect("Received open confirmation for " "non-opening channel %d.", id); remote_id = packet_get_int(); /* Record the remote channel number and mark that the channel is now open. */ c->remote_id = remote_id; c->type = SSH_CHANNEL_OPEN; if (compat20) { c->remote_window = packet_get_int(); c->remote_maxpacket = packet_get_int(); packet_done(); if (c->cb_fn != NULL && c->cb_event == type) { debug2("callback start"); c->cb_fn(c->self, c->cb_arg); debug2("callback done"); } debug("channel %d: open confirm rwindow %d rmax %d", c->self, c->remote_window, c->remote_maxpacket); } } char * reason2txt(int reason) { switch(reason) { case SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED: return "administratively prohibited"; case SSH2_OPEN_CONNECT_FAILED: return "connect failed"; case SSH2_OPEN_UNKNOWN_CHANNEL_TYPE: return "unknown channel type"; case SSH2_OPEN_RESOURCE_SHORTAGE: return "resource shortage"; } return "unknown reason"; } void channel_input_open_failure(int type, int plen, void *ctxt) { int id, reason; char *msg = NULL, *lang = NULL; Channel *c; if (!compat20) packet_integrity_check(plen, 4, type); id = packet_get_int(); c = channel_lookup(id); if (c==NULL || c->type != SSH_CHANNEL_OPENING) packet_disconnect("Received open failure for " "non-opening channel %d.", id); if (compat20) { reason = packet_get_int(); if (!(datafellows & SSH_BUG_OPENFAILURE)) { msg = packet_get_string(NULL); lang = packet_get_string(NULL); } packet_done(); log("channel %d: open failed: %s%s%s", id, reason2txt(reason), msg ? ": ": "", msg ? msg : ""); if (msg != NULL) xfree(msg); if (lang != NULL) xfree(lang); } /* Free the channel. This will also close the socket. */ channel_free(c); } void channel_input_channel_request(int type, int plen, void *ctxt) { int id; Channel *c; id = packet_get_int(); c = channel_lookup(id); if (c == NULL || (c->type != SSH_CHANNEL_OPEN && c->type != SSH_CHANNEL_LARVAL)) packet_disconnect("Received request for " "non-open channel %d.", id); if (c->cb_fn != NULL && c->cb_event == type) { debug2("callback start"); c->cb_fn(c->self, c->cb_arg); debug2("callback done"); } else { char *service = packet_get_string(NULL); debug("channel %d: rcvd request for %s", c->self, service); debug("cb_fn %p cb_event %d", c->cb_fn , c->cb_event); xfree(service); } } void channel_input_window_adjust(int type, int plen, void *ctxt) { Channel *c; int id, adjust; if (!compat20) return; /* Get the channel number and verify it. */ id = packet_get_int(); c = channel_lookup(id); if (c == NULL || c->type != SSH_CHANNEL_OPEN) { log("Received window adjust for " "non-open channel %d.", id); return; } adjust = packet_get_int(); packet_done(); debug2("channel %d: rcvd adjust %d", id, adjust); c->remote_window += adjust; } void channel_input_port_open(int type, int plen, void *ctxt) { Channel *c = NULL; u_short host_port; char *host, *originator_string; int remote_id, sock = -1; remote_id = packet_get_int(); host = packet_get_string(NULL); host_port = packet_get_int(); if (packet_get_protocol_flags() & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) { originator_string = packet_get_string(NULL); } else { originator_string = xstrdup("unknown (remote did not supply name)"); } packet_done(); 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); if (c == NULL) { error("channel_input_port_open: channel_new failed"); close(sock); } else { c->remote_id = remote_id; } } if (c == NULL) { packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); packet_put_int(remote_id); packet_send(); } xfree(host); }