diff options
Diffstat (limited to 'libssh2-sys/libssh2-1.5.0/src/packet.c')
-rw-r--r-- | libssh2-sys/libssh2-1.5.0/src/packet.c | 1267 |
1 files changed, 1267 insertions, 0 deletions
diff --git a/libssh2-sys/libssh2-1.5.0/src/packet.c b/libssh2-sys/libssh2-1.5.0/src/packet.c new file mode 100644 index 0000000..5f1feb8 --- /dev/null +++ b/libssh2-sys/libssh2-1.5.0/src/packet.c @@ -0,0 +1,1267 @@ +/* Copyright (c) 2004-2007, Sara Golemon <sarag@libssh2.org> + * Copyright (c) 2005,2006 Mikhail Gusarov + * Copyright (c) 2009-2014 by Daniel Stenberg + * Copyright (c) 2010 Simon Josefsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 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. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 COPYRIGHT OWNER OR + * CONTRIBUTORS 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 "libssh2_priv.h" +#include <errno.h> +#include <fcntl.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#ifdef HAVE_INTTYPES_H +#include <inttypes.h> +#endif + +/* Needed for struct iovec on some platforms */ +#ifdef HAVE_SYS_UIO_H +#include <sys/uio.h> +#endif + +#include <sys/types.h> + +#include "transport.h" +#include "channel.h" +#include "packet.h" + +/* + * libssh2_packet_queue_listener + * + * Queue a connection request for a listener + */ +static inline int +packet_queue_listener(LIBSSH2_SESSION * session, unsigned char *data, + unsigned long datalen, + packet_queue_listener_state_t *listen_state) +{ + /* + * Look for a matching listener + */ + /* 17 = packet_type(1) + channel(4) + reason(4) + descr(4) + lang(4) */ + unsigned long packet_len = 17 + (sizeof(FwdNotReq) - 1); + unsigned char *p; + LIBSSH2_LISTENER *listn = _libssh2_list_first(&session->listeners); + char failure_code = SSH_OPEN_ADMINISTRATIVELY_PROHIBITED; + int rc; + + (void) datalen; + + if (listen_state->state == libssh2_NB_state_idle) { + unsigned char *s = data + (sizeof("forwarded-tcpip") - 1) + 5; + listen_state->sender_channel = _libssh2_ntohu32(s); + s += 4; + + listen_state->initial_window_size = _libssh2_ntohu32(s); + s += 4; + listen_state->packet_size = _libssh2_ntohu32(s); + s += 4; + + listen_state->host_len = _libssh2_ntohu32(s); + s += 4; + listen_state->host = s; + s += listen_state->host_len; + listen_state->port = _libssh2_ntohu32(s); + s += 4; + + listen_state->shost_len = _libssh2_ntohu32(s); + s += 4; + listen_state->shost = s; + s += listen_state->shost_len; + listen_state->sport = _libssh2_ntohu32(s); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Remote received connection from %s:%ld to %s:%ld", + listen_state->shost, listen_state->sport, + listen_state->host, listen_state->port); + + listen_state->state = libssh2_NB_state_allocated; + } + + if (listen_state->state != libssh2_NB_state_sent) { + while (listn) { + if ((listn->port == (int) listen_state->port) && + (strlen(listn->host) == listen_state->host_len) && + (memcmp (listn->host, listen_state->host, + listen_state->host_len) == 0)) { + /* This is our listener */ + LIBSSH2_CHANNEL *channel = NULL; + listen_state->channel = NULL; + + if (listen_state->state == libssh2_NB_state_allocated) { + if (listn->queue_maxsize && + (listn->queue_maxsize <= listn->queue_size)) { + /* Queue is full */ + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Listener queue full, ignoring"); + listen_state->state = libssh2_NB_state_sent; + break; + } + + channel = LIBSSH2_CALLOC(session, sizeof(LIBSSH2_CHANNEL)); + if (!channel) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate a channel for " + "new connection"); + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + listen_state->state = libssh2_NB_state_sent; + break; + } + listen_state->channel = channel; + + channel->session = session; + channel->channel_type_len = sizeof("forwarded-tcpip") - 1; + channel->channel_type = LIBSSH2_ALLOC(session, + channel-> + channel_type_len + + 1); + if (!channel->channel_type) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate a channel for new" + " connection"); + LIBSSH2_FREE(session, channel); + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + listen_state->state = libssh2_NB_state_sent; + break; + } + memcpy(channel->channel_type, "forwarded-tcpip", + channel->channel_type_len + 1); + + channel->remote.id = listen_state->sender_channel; + channel->remote.window_size_initial = + LIBSSH2_CHANNEL_WINDOW_DEFAULT; + channel->remote.window_size = + LIBSSH2_CHANNEL_WINDOW_DEFAULT; + channel->remote.packet_size = + LIBSSH2_CHANNEL_PACKET_DEFAULT; + + channel->local.id = _libssh2_channel_nextid(session); + channel->local.window_size_initial = + listen_state->initial_window_size; + channel->local.window_size = + listen_state->initial_window_size; + channel->local.packet_size = listen_state->packet_size; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Connection queued: channel %lu/%lu " + "win %lu/%lu packet %lu/%lu", + channel->local.id, channel->remote.id, + channel->local.window_size, + channel->remote.window_size, + channel->local.packet_size, + channel->remote.packet_size); + + p = listen_state->packet; + *(p++) = SSH_MSG_CHANNEL_OPEN_CONFIRMATION; + _libssh2_store_u32(&p, channel->remote.id); + _libssh2_store_u32(&p, channel->local.id); + _libssh2_store_u32(&p, + channel->remote.window_size_initial); + _libssh2_store_u32(&p, channel->remote.packet_size); + + listen_state->state = libssh2_NB_state_created; + } + + if (listen_state->state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, listen_state->packet, + 17, NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + else if (rc) { + listen_state->state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, + "Unable to send channel " + "open confirmation"); + } + + /* Link the channel into the end of the queue list */ + if (listen_state->channel) { + _libssh2_list_add(&listn->queue, + &listen_state->channel->node); + listn->queue_size++; + } + + listen_state->state = libssh2_NB_state_idle; + return 0; + } + } + + listn = _libssh2_list_next(&listn->node); + } + + listen_state->state = libssh2_NB_state_sent; + } + + /* We're not listening to you */ + p = listen_state->packet; + *(p++) = SSH_MSG_CHANNEL_OPEN_FAILURE; + _libssh2_store_u32(&p, listen_state->sender_channel); + _libssh2_store_u32(&p, failure_code); + _libssh2_store_str(&p, FwdNotReq, sizeof(FwdNotReq) - 1); + _libssh2_htonu32(p, 0); + + rc = _libssh2_transport_send(session, listen_state->packet, + packet_len, NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + listen_state->state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, "Unable to send open failure"); + + } + listen_state->state = libssh2_NB_state_idle; + return 0; +} + +/* + * packet_x11_open + * + * Accept a forwarded X11 connection + */ +static inline int +packet_x11_open(LIBSSH2_SESSION * session, unsigned char *data, + unsigned long datalen, + packet_x11_open_state_t *x11open_state) +{ + int failure_code = SSH_OPEN_CONNECT_FAILED; + /* 17 = packet_type(1) + channel(4) + reason(4) + descr(4) + lang(4) */ + unsigned long packet_len = 17 + (sizeof(X11FwdUnAvil) - 1); + unsigned char *p; + LIBSSH2_CHANNEL *channel = x11open_state->channel; + int rc; + + (void) datalen; + + if (x11open_state->state == libssh2_NB_state_idle) { + unsigned char *s = data + (sizeof("x11") - 1) + 5; + x11open_state->sender_channel = _libssh2_ntohu32(s); + s += 4; + x11open_state->initial_window_size = _libssh2_ntohu32(s); + s += 4; + x11open_state->packet_size = _libssh2_ntohu32(s); + s += 4; + x11open_state->shost_len = _libssh2_ntohu32(s); + s += 4; + x11open_state->shost = s; + s += x11open_state->shost_len; + x11open_state->sport = _libssh2_ntohu32(s); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "X11 Connection Received from %s:%ld on channel %lu", + x11open_state->shost, x11open_state->sport, + x11open_state->sender_channel); + + x11open_state->state = libssh2_NB_state_allocated; + } + + if (session->x11) { + if (x11open_state->state == libssh2_NB_state_allocated) { + channel = LIBSSH2_CALLOC(session, sizeof(LIBSSH2_CHANNEL)); + if (!channel) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "allocate a channel for new connection"); + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + goto x11_exit; + } + + channel->session = session; + channel->channel_type_len = sizeof("x11") - 1; + channel->channel_type = LIBSSH2_ALLOC(session, + channel->channel_type_len + + 1); + if (!channel->channel_type) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "allocate a channel for new connection"); + LIBSSH2_FREE(session, channel); + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + goto x11_exit; + } + memcpy(channel->channel_type, "x11", + channel->channel_type_len + 1); + + channel->remote.id = x11open_state->sender_channel; + channel->remote.window_size_initial = + LIBSSH2_CHANNEL_WINDOW_DEFAULT; + channel->remote.window_size = LIBSSH2_CHANNEL_WINDOW_DEFAULT; + channel->remote.packet_size = LIBSSH2_CHANNEL_PACKET_DEFAULT; + + channel->local.id = _libssh2_channel_nextid(session); + channel->local.window_size_initial = + x11open_state->initial_window_size; + channel->local.window_size = x11open_state->initial_window_size; + channel->local.packet_size = x11open_state->packet_size; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "X11 Connection established: channel %lu/%lu " + "win %lu/%lu packet %lu/%lu", + channel->local.id, channel->remote.id, + channel->local.window_size, + channel->remote.window_size, + channel->local.packet_size, + channel->remote.packet_size); + p = x11open_state->packet; + *(p++) = SSH_MSG_CHANNEL_OPEN_CONFIRMATION; + _libssh2_store_u32(&p, channel->remote.id); + _libssh2_store_u32(&p, channel->local.id); + _libssh2_store_u32(&p, channel->remote.window_size_initial); + _libssh2_store_u32(&p, channel->remote.packet_size); + + x11open_state->state = libssh2_NB_state_created; + } + + if (x11open_state->state == libssh2_NB_state_created) { + rc = _libssh2_transport_send(session, x11open_state->packet, 17, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + x11open_state->state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send channel open " + "confirmation"); + } + + /* Link the channel into the session */ + _libssh2_list_add(&session->channels, &channel->node); + + /* + * Pass control to the callback, they may turn right around and + * free the channel, or actually use it + */ + LIBSSH2_X11_OPEN(channel, (char *)x11open_state->shost, + x11open_state->sport); + + x11open_state->state = libssh2_NB_state_idle; + return 0; + } + } + else + failure_code = SSH_OPEN_RESOURCE_SHORTAGE; + /* fall-trough */ + x11_exit: + p = x11open_state->packet; + *(p++) = SSH_MSG_CHANNEL_OPEN_FAILURE; + _libssh2_store_u32(&p, x11open_state->sender_channel); + _libssh2_store_u32(&p, failure_code); + _libssh2_store_str(&p, X11FwdUnAvil, sizeof(X11FwdUnAvil) - 1); + _libssh2_htonu32(p, 0); + + rc = _libssh2_transport_send(session, x11open_state->packet, packet_len, + NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + x11open_state->state = libssh2_NB_state_idle; + return _libssh2_error(session, rc, "Unable to send open failure"); + } + x11open_state->state = libssh2_NB_state_idle; + return 0; +} + +/* + * _libssh2_packet_add + * + * Create a new packet and attach it to the brigade. Called from the transport + * layer when it has received a packet. + * + * The input pointer 'data' is pointing to allocated data that this function + * is asked to deal with so on failure OR success, it must be freed fine. + * The only exception is when the return code is LIBSSH2_ERROR_EAGAIN. + * + * This function will always be called with 'datalen' greater than zero. + */ +int +_libssh2_packet_add(LIBSSH2_SESSION * session, unsigned char *data, + size_t datalen, int macstate) +{ + int rc = 0; + char *message=NULL; + char *language=NULL; + size_t message_len=0; + size_t language_len=0; + LIBSSH2_CHANNEL *channelp = NULL; + size_t data_head = 0; + unsigned char msg = data[0]; + + switch(session->packAdd_state) { + case libssh2_NB_state_idle: + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Packet type %d received, length=%d", + (int) msg, (int) datalen); + + if ((macstate == LIBSSH2_MAC_INVALID) && + (!session->macerror || + LIBSSH2_MACERROR(session, (char *) data, datalen))) { + /* Bad MAC input, but no callback set or non-zero return from the + callback */ + + LIBSSH2_FREE(session, data); + return _libssh2_error(session, LIBSSH2_ERROR_INVALID_MAC, + "Invalid MAC received"); + } + session->packAdd_state = libssh2_NB_state_allocated; + break; + case libssh2_NB_state_jump1: + goto libssh2_packet_add_jump_point1; + case libssh2_NB_state_jump2: + goto libssh2_packet_add_jump_point2; + case libssh2_NB_state_jump3: + goto libssh2_packet_add_jump_point3; + case libssh2_NB_state_jump4: + goto libssh2_packet_add_jump_point4; + case libssh2_NB_state_jump5: + goto libssh2_packet_add_jump_point5; + default: /* nothing to do */ + break; + } + + if (session->packAdd_state == libssh2_NB_state_allocated) { + /* A couple exceptions to the packet adding rule: */ + switch (msg) { + + /* + byte SSH_MSG_DISCONNECT + uint32 reason code + string description in ISO-10646 UTF-8 encoding [RFC3629] + string language tag [RFC3066] + */ + + case SSH_MSG_DISCONNECT: + if(datalen >= 5) { + size_t reason = _libssh2_ntohu32(data + 1); + + if(datalen >= 9) { + message_len = _libssh2_ntohu32(data + 5); + + if(message_len < datalen-13) { + /* 9 = packet_type(1) + reason(4) + message_len(4) */ + message = (char *) data + 9; + + language_len = _libssh2_ntohu32(data + 9 + message_len); + language = (char *) data + 9 + message_len + 4; + + if(language_len > (datalen-13-message_len)) { + /* bad input, clear info */ + language = message = NULL; + language_len = message_len = 0; + } + } + else + /* bad size, clear it */ + message_len=0; + } + if (session->ssh_msg_disconnect) { + LIBSSH2_DISCONNECT(session, reason, message, + message_len, language, language_len); + } + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Disconnect(%d): %s(%s)", reason, + message, language); + } + + LIBSSH2_FREE(session, data); + session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; + session->packAdd_state = libssh2_NB_state_idle; + return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_DISCONNECT, + "socket disconnect"); + /* + byte SSH_MSG_IGNORE + string data + */ + + case SSH_MSG_IGNORE: + if (datalen >= 2) { + if (session->ssh_msg_ignore) { + LIBSSH2_IGNORE(session, (char *) data + 1, datalen - 1); + } + } else if (session->ssh_msg_ignore) { + LIBSSH2_IGNORE(session, "", 0); + } + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + + /* + byte SSH_MSG_DEBUG + boolean always_display + string message in ISO-10646 UTF-8 encoding [RFC3629] + string language tag [RFC3066] + */ + + case SSH_MSG_DEBUG: + if(datalen >= 2) { + int always_display= data[1]; + + if(datalen >= 6) { + message_len = _libssh2_ntohu32(data + 2); + + if(message_len <= (datalen - 10)) { + /* 6 = packet_type(1) + display(1) + message_len(4) */ + message = (char *) data + 6; + language_len = _libssh2_ntohu32(data + 6 + message_len); + + if(language_len <= (datalen - 10 - message_len)) + language = (char *) data + 10 + message_len; + } + } + + if (session->ssh_msg_debug) { + LIBSSH2_DEBUG(session, always_display, message, + message_len, language, language_len); + } + } + /* + * _libssh2_debug will actually truncate this for us so + * that it's not an inordinate about of data + */ + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Debug Packet: %s", message); + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + + /* + byte SSH_MSG_GLOBAL_REQUEST + string request name in US-ASCII only + boolean want reply + .... request-specific data follows + */ + + case SSH_MSG_GLOBAL_REQUEST: + if(datalen >= 5) { + uint32_t len =0; + unsigned char want_reply=0; + len = _libssh2_ntohu32(data + 1); + if(datalen >= (6 + len)) { + want_reply = data[5 + len]; + _libssh2_debug(session, + LIBSSH2_TRACE_CONN, + "Received global request type %.*s (wr %X)", + len, data + 5, want_reply); + } + + + if (want_reply) { + static const unsigned char packet = + SSH_MSG_REQUEST_FAILURE; + libssh2_packet_add_jump_point5: + session->packAdd_state = libssh2_NB_state_jump5; + rc = _libssh2_transport_send(session, &packet, 1, NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + } + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + + /* + byte SSH_MSG_CHANNEL_EXTENDED_DATA + uint32 recipient channel + uint32 data_type_code + string data + */ + + case SSH_MSG_CHANNEL_EXTENDED_DATA: + /* streamid(4) */ + data_head += 4; + + /* fall-through */ + + /* + byte SSH_MSG_CHANNEL_DATA + uint32 recipient channel + string data + */ + + case SSH_MSG_CHANNEL_DATA: + /* packet_type(1) + channelno(4) + datalen(4) */ + data_head += 9; + + if(datalen >= data_head) + channelp = + _libssh2_channel_locate(session, + _libssh2_ntohu32(data + 1)); + + if (!channelp) { + _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_UNKNOWN, + "Packet received for unknown channel"); + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + } +#ifdef LIBSSH2DEBUG + { + uint32_t stream_id = 0; + if (msg == SSH_MSG_CHANNEL_EXTENDED_DATA) + stream_id = _libssh2_ntohu32(data + 5); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "%d bytes packet_add() for %lu/%lu/%lu", + (int) (datalen - data_head), + channelp->local.id, + channelp->remote.id, + stream_id); + } +#endif + if ((channelp->remote.extended_data_ignore_mode == + LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE) && + (msg == SSH_MSG_CHANNEL_EXTENDED_DATA)) { + /* Pretend we didn't receive this */ + LIBSSH2_FREE(session, data); + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Ignoring extended data and refunding %d bytes", + (int) (datalen - 13)); + if (channelp->read_avail + datalen - data_head >= + channelp->remote.window_size) + datalen = channelp->remote.window_size - + channelp->read_avail + data_head; + + channelp->remote.window_size -= datalen - data_head; + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "shrinking window size by %lu bytes to %lu, read_avail %lu", + datalen - data_head, + channelp->remote.window_size, + channelp->read_avail); + + session->packAdd_channelp = channelp; + + /* Adjust the window based on the block we just freed */ + libssh2_packet_add_jump_point1: + session->packAdd_state = libssh2_NB_state_jump1; + rc = _libssh2_channel_receive_window_adjust(session-> + packAdd_channelp, + datalen - 13, + 1, NULL); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + + session->packAdd_state = libssh2_NB_state_idle; + return 0; + } + + /* + * REMEMBER! remote means remote as source of data, + * NOT remote window! + */ + if (channelp->remote.packet_size < (datalen - data_head)) { + /* + * Spec says we MAY ignore bytes sent beyond + * packet_size + */ + _libssh2_error(session, + LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED, + "Packet contains more data than we offered" + " to receive, truncating"); + datalen = channelp->remote.packet_size + data_head; + } + if (channelp->remote.window_size <= channelp->read_avail) { + /* + * Spec says we MAY ignore bytes sent beyond + * window_size + */ + _libssh2_error(session, + LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, + "The current receive window is full," + " data ignored"); + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + } + /* Reset EOF status */ + channelp->remote.eof = 0; + + if (channelp->read_avail + datalen - data_head > + channelp->remote.window_size) { + _libssh2_error(session, + LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, + "Remote sent more data than current " + "window allows, truncating"); + datalen = channelp->remote.window_size - + channelp->read_avail + data_head; + } + + /* Update the read_avail counter. The window size will be + * updated once the data is actually read from the queue + * from an upper layer */ + channelp->read_avail += datalen - data_head; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "increasing read_avail by %lu bytes to %lu/%lu", + (long)(datalen - data_head), + (long)channelp->read_avail, + (long)channelp->remote.window_size); + + break; + + /* + byte SSH_MSG_CHANNEL_EOF + uint32 recipient channel + */ + + case SSH_MSG_CHANNEL_EOF: + if(datalen >= 5) + channelp = + _libssh2_channel_locate(session, + _libssh2_ntohu32(data + 1)); + if (!channelp) + /* We may have freed already, just quietly ignore this... */ + ; + else { + _libssh2_debug(session, + LIBSSH2_TRACE_CONN, + "EOF received for channel %lu/%lu", + channelp->local.id, + channelp->remote.id); + channelp->remote.eof = 1; + } + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + + /* + byte SSH_MSG_CHANNEL_REQUEST + uint32 recipient channel + string request type in US-ASCII characters only + boolean want reply + .... type-specific data follows + */ + + case SSH_MSG_CHANNEL_REQUEST: + if(datalen >= 9) { + uint32_t channel = _libssh2_ntohu32(data + 1); + uint32_t len = _libssh2_ntohu32(data + 5); + unsigned char want_reply = 1; + + if(len < (datalen - 10)) + want_reply = data[9 + len]; + + _libssh2_debug(session, + LIBSSH2_TRACE_CONN, + "Channel %d received request type %.*s (wr %X)", + channel, len, data + 9, want_reply); + + if (len == sizeof("exit-status") - 1 + && !memcmp("exit-status", data + 9, + sizeof("exit-status") - 1)) { + + /* we've got "exit-status" packet. Set the session value */ + if(datalen >= 20) + channelp = + _libssh2_channel_locate(session, channel); + + if (channelp) { + channelp->exit_status = + _libssh2_ntohu32(data + 9 + sizeof("exit-status")); + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Exit status %lu received for " + "channel %lu/%lu", + channelp->exit_status, + channelp->local.id, + channelp->remote.id); + } + + } + else if (len == sizeof("exit-signal") - 1 + && !memcmp("exit-signal", data + 9, + sizeof("exit-signal") - 1)) { + /* command terminated due to signal */ + if(datalen >= 20) + channelp = _libssh2_channel_locate(session, channel); + + if (channelp) { + /* set signal name (without SIG prefix) */ + uint32_t namelen = + _libssh2_ntohu32(data + 9 + sizeof("exit-signal")); + channelp->exit_signal = + LIBSSH2_ALLOC(session, namelen + 1); + if (!channelp->exit_signal) + rc = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "memory for signal name"); + else { + memcpy(channelp->exit_signal, + data + 13 + sizeof("exit_signal"), namelen); + channelp->exit_signal[namelen] = '\0'; + /* TODO: save error message and language tag */ + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Exit signal %s received for " + "channel %lu/%lu", + channelp->exit_signal, + channelp->local.id, + channelp->remote.id); + } + } + } + + + if (want_reply) { + unsigned char packet[5]; + libssh2_packet_add_jump_point4: + session->packAdd_state = libssh2_NB_state_jump4; + packet[0] = SSH_MSG_CHANNEL_FAILURE; + memcpy(&packet[1], data+1, 4); + rc = _libssh2_transport_send(session, packet, 5, NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + } + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return rc; + + /* + byte SSH_MSG_CHANNEL_CLOSE + uint32 recipient channel + */ + + case SSH_MSG_CHANNEL_CLOSE: + if(datalen >= 5) + channelp = + _libssh2_channel_locate(session, + _libssh2_ntohu32(data + 1)); + if (!channelp) { + /* We may have freed already, just quietly ignore this... */ + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + } + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Close received for channel %lu/%lu", + channelp->local.id, + channelp->remote.id); + + channelp->remote.close = 1; + channelp->remote.eof = 1; + + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + + /* + byte SSH_MSG_CHANNEL_OPEN + string "session" + uint32 sender channel + uint32 initial window size + uint32 maximum packet size + */ + + case SSH_MSG_CHANNEL_OPEN: + if(datalen < 17) + ; + else if ((datalen >= (sizeof("forwarded-tcpip") + 4)) && + ((sizeof("forwarded-tcpip") - 1) == + _libssh2_ntohu32(data + 1)) + && + (memcmp(data + 5, "forwarded-tcpip", + sizeof("forwarded-tcpip") - 1) == 0)) { + + /* init the state struct */ + memset(&session->packAdd_Qlstn_state, 0, + sizeof(session->packAdd_Qlstn_state)); + + libssh2_packet_add_jump_point2: + session->packAdd_state = libssh2_NB_state_jump2; + rc = packet_queue_listener(session, data, datalen, + &session->packAdd_Qlstn_state); + } + else if ((datalen >= (sizeof("x11") + 4)) && + ((sizeof("x11") - 1) == _libssh2_ntohu32(data + 1)) && + (memcmp(data + 5, "x11", sizeof("x11") - 1) == 0)) { + + /* init the state struct */ + memset(&session->packAdd_x11open_state, 0, + sizeof(session->packAdd_x11open_state)); + + libssh2_packet_add_jump_point3: + session->packAdd_state = libssh2_NB_state_jump3; + rc = packet_x11_open(session, data, datalen, + &session->packAdd_x11open_state); + } + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return rc; + + /* + byte SSH_MSG_CHANNEL_WINDOW_ADJUST + uint32 recipient channel + uint32 bytes to add + */ + case SSH_MSG_CHANNEL_WINDOW_ADJUST: + if(datalen < 9) + ; + else { + uint32_t bytestoadd = _libssh2_ntohu32(data + 5); + channelp = + _libssh2_channel_locate(session, + _libssh2_ntohu32(data + 1)); + if(channelp) { + channelp->local.window_size += bytestoadd; + + _libssh2_debug(session, LIBSSH2_TRACE_CONN, + "Window adjust for channel %lu/%lu, " + "adding %lu bytes, new window_size=%lu", + channelp->local.id, + channelp->remote.id, + bytestoadd, + channelp->local.window_size); + } + } + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return 0; + default: + break; + } + + session->packAdd_state = libssh2_NB_state_sent; + } + + if (session->packAdd_state == libssh2_NB_state_sent) { + LIBSSH2_PACKET *packetp = + LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PACKET)); + if (!packetp) { + _libssh2_debug(session, LIBSSH2_ERROR_ALLOC, + "memory for packet"); + LIBSSH2_FREE(session, data); + session->packAdd_state = libssh2_NB_state_idle; + return LIBSSH2_ERROR_ALLOC; + } + packetp->data = data; + packetp->data_len = datalen; + packetp->data_head = data_head; + + _libssh2_list_add(&session->packets, &packetp->node); + + session->packAdd_state = libssh2_NB_state_sent1; + } + + if ((msg == SSH_MSG_KEXINIT && + !(session->state & LIBSSH2_STATE_EXCHANGING_KEYS)) || + (session->packAdd_state == libssh2_NB_state_sent2)) { + if (session->packAdd_state == libssh2_NB_state_sent1) { + /* + * Remote wants new keys + * Well, it's already in the brigade, + * let's just call back into ourselves + */ + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Renegotiating Keys"); + + session->packAdd_state = libssh2_NB_state_sent2; + } + + /* + * The KEXINIT message has been added to the queue. The packAdd and + * readPack states need to be reset because _libssh2_kex_exchange + * (eventually) calls upon _libssh2_transport_read to read the rest of + * the key exchange conversation. + */ + session->readPack_state = libssh2_NB_state_idle; + session->packet.total_num = 0; + session->packAdd_state = libssh2_NB_state_idle; + session->fullpacket_state = libssh2_NB_state_idle; + + memset(&session->startup_key_state, 0, sizeof(key_exchange_state_t)); + + /* + * If there was a key reexchange failure, let's just hope we didn't + * send NEWKEYS yet, otherwise remote will drop us like a rock + */ + rc = _libssh2_kex_exchange(session, 1, &session->startup_key_state); + if (rc == LIBSSH2_ERROR_EAGAIN) + return rc; + } + + session->packAdd_state = libssh2_NB_state_idle; + return 0; +} + +/* + * _libssh2_packet_ask + * + * Scan the brigade for a matching packet type, optionally poll the socket for + * a packet first + */ +int +_libssh2_packet_ask(LIBSSH2_SESSION * session, unsigned char packet_type, + unsigned char **data, size_t *data_len, + int match_ofs, const unsigned char *match_buf, + size_t match_len) +{ + LIBSSH2_PACKET *packet = _libssh2_list_first(&session->packets); + + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Looking for packet of type: %d", (int) packet_type); + + while (packet) { + if (packet->data[0] == packet_type + && (packet->data_len >= (match_ofs + match_len)) + && (!match_buf || + (memcmp(packet->data + match_ofs, match_buf, + match_len) == 0))) { + *data = packet->data; + *data_len = packet->data_len; + + /* unlink struct from session->packets */ + _libssh2_list_remove(&packet->node); + + LIBSSH2_FREE(session, packet); + + return 0; + } + packet = _libssh2_list_next(&packet->node); + } + return -1; +} + +/* + * libssh2_packet_askv + * + * Scan for any of a list of packet types in the brigade, optionally poll the + * socket for a packet first + */ +int +_libssh2_packet_askv(LIBSSH2_SESSION * session, + const unsigned char *packet_types, + unsigned char **data, size_t *data_len, + int match_ofs, + const unsigned char *match_buf, + size_t match_len) +{ + int i, packet_types_len = strlen((char *) packet_types); + + for(i = 0; i < packet_types_len; i++) { + if (0 == _libssh2_packet_ask(session, packet_types[i], data, + data_len, match_ofs, + match_buf, match_len)) { + return 0; + } + } + + return -1; +} + +/* + * _libssh2_packet_require + * + * Loops _libssh2_transport_read() until the packet requested is available + * SSH_DISCONNECT or a SOCKET_DISCONNECTED will cause a bailout + * + * Returns negative on error + * Returns 0 when it has taken care of the requested packet. + */ +int +_libssh2_packet_require(LIBSSH2_SESSION * session, unsigned char packet_type, + unsigned char **data, size_t *data_len, + int match_ofs, + const unsigned char *match_buf, + size_t match_len, + packet_require_state_t *state) +{ + if (state->start == 0) { + if (_libssh2_packet_ask(session, packet_type, data, data_len, + match_ofs, match_buf, + match_len) == 0) { + /* A packet was available in the packet brigade */ + return 0; + } + + state->start = time(NULL); + } + + while (session->socket_state == LIBSSH2_SOCKET_CONNECTED) { + int ret = _libssh2_transport_read(session); + if (ret == LIBSSH2_ERROR_EAGAIN) + return ret; + else if (ret < 0) { + state->start = 0; + /* an error which is not just because of blocking */ + return ret; + } else if (ret == packet_type) { + /* Be lazy, let packet_ask pull it out of the brigade */ + ret = _libssh2_packet_ask(session, packet_type, data, data_len, + match_ofs, match_buf, match_len); + state->start = 0; + return ret; + } else if (ret == 0) { + /* nothing available, wait until data arrives or we time out */ + long left = LIBSSH2_READ_TIMEOUT - (long)(time(NULL) - + state->start); + + if (left <= 0) { + state->start = 0; + return LIBSSH2_ERROR_TIMEOUT; + } + return -1; /* no packet available yet */ + } + } + + /* Only reached if the socket died */ + return LIBSSH2_ERROR_SOCKET_DISCONNECT; +} + +/* + * _libssh2_packet_burn + * + * Loops _libssh2_transport_read() until any packet is available and promptly + * discards it. + * Used during KEX exchange to discard badly guessed KEX_INIT packets + */ +int +_libssh2_packet_burn(LIBSSH2_SESSION * session, + libssh2_nonblocking_states * state) +{ + unsigned char *data; + size_t data_len; + unsigned char i, all_packets[255]; + int ret; + + if (*state == libssh2_NB_state_idle) { + for(i = 1; i < 255; i++) { + all_packets[i - 1] = i; + } + all_packets[254] = 0; + + if (_libssh2_packet_askv(session, all_packets, &data, &data_len, 0, + NULL, 0) == 0) { + i = data[0]; + /* A packet was available in the packet brigade, burn it */ + LIBSSH2_FREE(session, data); + return i; + } + + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, + "Blocking until packet becomes available to burn"); + *state = libssh2_NB_state_created; + } + + while (session->socket_state == LIBSSH2_SOCKET_CONNECTED) { + ret = _libssh2_transport_read(session); + if (ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } else if (ret < 0) { + *state = libssh2_NB_state_idle; + return ret; + } else if (ret == 0) { + /* FIXME: this might busyloop */ + continue; + } + + /* Be lazy, let packet_ask pull it out of the brigade */ + if (0 == + _libssh2_packet_ask(session, (unsigned char)ret, + &data, &data_len, 0, NULL, 0)) { + /* Smoke 'em if you got 'em */ + LIBSSH2_FREE(session, data); + *state = libssh2_NB_state_idle; + return ret; + } + } + + /* Only reached if the socket died */ + return LIBSSH2_ERROR_SOCKET_DISCONNECT; +} + +/* + * _libssh2_packet_requirev + * + * Loops _libssh2_transport_read() until one of a list of packet types + * requested is available. SSH_DISCONNECT or a SOCKET_DISCONNECTED will cause + * a bailout. packet_types is a null terminated list of packet_type numbers + */ + +int +_libssh2_packet_requirev(LIBSSH2_SESSION *session, + const unsigned char *packet_types, + unsigned char **data, size_t *data_len, + int match_ofs, + const unsigned char *match_buf, size_t match_len, + packet_requirev_state_t * state) +{ + if (_libssh2_packet_askv(session, packet_types, data, data_len, match_ofs, + match_buf, match_len) == 0) { + /* One of the packets listed was available in the packet brigade */ + state->start = 0; + return 0; + } + + if (state->start == 0) { + state->start = time(NULL); + } + + while (session->socket_state != LIBSSH2_SOCKET_DISCONNECTED) { + int ret = _libssh2_transport_read(session); + if ((ret < 0) && (ret != LIBSSH2_ERROR_EAGAIN)) { + state->start = 0; + return ret; + } + if (ret <= 0) { + long left = LIBSSH2_READ_TIMEOUT - + (long)(time(NULL) - state->start); + + if (left <= 0) { + state->start = 0; + return LIBSSH2_ERROR_TIMEOUT; + } + else if (ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + } + + if (strchr((char *) packet_types, ret)) { + /* Be lazy, let packet_ask pull it out of the brigade */ + return _libssh2_packet_askv(session, packet_types, data, + data_len, match_ofs, match_buf, + match_len); + } + } + + /* Only reached if the socket died */ + state->start = 0; + return LIBSSH2_ERROR_SOCKET_DISCONNECT; +} + |