856 строки
40 KiB
C
856 строки
40 KiB
C
/* Copyright (c) 2004-2007, Sara Golemon <sarag@libssh2.org>
|
|
* 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>
|
|
#ifndef WIN32
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
/* Needed for struct iovec on some platforms */
|
|
#ifdef HAVE_SYS_UIO_H
|
|
#include <sys/uio.h>
|
|
#endif
|
|
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <inttypes.h>
|
|
|
|
/* {{{ libssh2_packet_queue_listener
|
|
* Queue a connection request for a listener
|
|
*/
|
|
static inline int libssh2_packet_queue_listener(LIBSSH2_SESSION *session,
|
|
unsigned char *data,
|
|
unsigned long datalen)
|
|
{
|
|
/* Look for a matching listener */
|
|
unsigned char *s = data + (sizeof("forwarded-tcpip") - 1) + 5;
|
|
/* 17 = packet_type(1) + channel(4) + reason(4) + descr(4) + lang(4) */
|
|
unsigned long packet_len = 17 + (sizeof("Forward not requested") - 1);
|
|
unsigned char *p, packet[17 + (sizeof("Forward not requested") - 1)];
|
|
LIBSSH2_LISTENER *l = session->listeners;
|
|
char failure_code = 1; /* SSH_OPEN_ADMINISTRATIVELY_PROHIBITED */
|
|
uint32_t sender_channel, initial_window_size, packet_size;
|
|
unsigned char *host, *shost;
|
|
uint32_t port, sport, host_len, shost_len;
|
|
(void)datalen;
|
|
|
|
sender_channel = libssh2_ntohu32(s); s += 4;
|
|
|
|
initial_window_size = libssh2_ntohu32(s); s += 4;
|
|
packet_size = libssh2_ntohu32(s); s += 4;
|
|
|
|
host_len = libssh2_ntohu32(s); s += 4;
|
|
host = s; s += host_len;
|
|
port = libssh2_ntohu32(s); s += 4;
|
|
|
|
shost_len = libssh2_ntohu32(s); s += 4;
|
|
shost = s; s += shost_len;
|
|
sport = libssh2_ntohu32(s); s += 4;
|
|
|
|
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Remote received connection from %s:%ld to %s:%ld", shost, sport, host, port);
|
|
|
|
while (l) {
|
|
if ((l->port == (int)port) &&
|
|
(strlen(l->host) == host_len) &&
|
|
(memcmp(l->host, host, host_len) == 0)) {
|
|
/* This is our listener */
|
|
LIBSSH2_CHANNEL *channel, *last_queued = l->queue;
|
|
|
|
if (l->queue_maxsize &&
|
|
(l->queue_maxsize <= l->queue_size)) {
|
|
/* Queue is full */
|
|
failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */
|
|
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Listener queue full, ignoring");
|
|
break;
|
|
}
|
|
|
|
channel = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_CHANNEL));
|
|
if (!channel) {
|
|
libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a channel for new connection", 0);
|
|
failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */
|
|
break;
|
|
}
|
|
memset(channel, 0, sizeof(LIBSSH2_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", 0);
|
|
LIBSSH2_FREE(session, channel);
|
|
failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */
|
|
break;
|
|
}
|
|
memcpy(channel->channel_type, "forwarded-tcpip", channel->channel_type_len + 1);
|
|
|
|
channel->remote.id = 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 = initial_window_size;
|
|
channel->local.window_size = initial_window_size;
|
|
channel->local.packet_size = packet_size;
|
|
|
|
_libssh2_debug(session, LIBSSH2_DBG_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 = packet;
|
|
*(p++) = SSH_MSG_CHANNEL_OPEN_CONFIRMATION;
|
|
libssh2_htonu32(p, channel->remote.id); p += 4;
|
|
libssh2_htonu32(p, channel->local.id); p += 4;
|
|
libssh2_htonu32(p, channel->remote.window_size_initial); p += 4;
|
|
libssh2_htonu32(p, channel->remote.packet_size); p += 4;
|
|
|
|
if (libssh2_packet_write(session, packet, 17)) {
|
|
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel open confirmation", 0);
|
|
return -1;
|
|
}
|
|
|
|
/* Link the channel into the end of the queue list */
|
|
|
|
if (!last_queued) {
|
|
l->queue = channel;
|
|
return 0;
|
|
}
|
|
|
|
while (last_queued->next) last_queued = last_queued->next;
|
|
|
|
last_queued->next = channel;
|
|
channel->prev = last_queued;
|
|
|
|
l->queue_size++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
l = l->next;
|
|
}
|
|
|
|
/* We're not listening to you */
|
|
{
|
|
|
|
p = packet;
|
|
*(p++) = SSH_MSG_CHANNEL_OPEN_FAILURE;
|
|
libssh2_htonu32(p, sender_channel); p += 4;
|
|
libssh2_htonu32(p, failure_code); p += 4;
|
|
libssh2_htonu32(p, sizeof("Forward not requested") - 1); p += 4;
|
|
memcpy(s, "Forward not requested", sizeof("Forward not requested") - 1); p += sizeof("Forward not requested") - 1;
|
|
libssh2_htonu32(p, 0);
|
|
|
|
if (libssh2_packet_write(session, packet, packet_len)) {
|
|
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send open failure", 0);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ libssh2_packet_x11_open
|
|
* Accept a forwarded X11 connection
|
|
*/
|
|
static inline int libssh2_packet_x11_open(LIBSSH2_SESSION *session,
|
|
unsigned char *data, unsigned long datalen)
|
|
{
|
|
int failure_code = 2; /* SSH_OPEN_CONNECT_FAILED */
|
|
unsigned char *s = data + (sizeof("x11") - 1) + 5;
|
|
unsigned long packet_len = 17 + (sizeof("X11 Forward Unavailable") - 1);
|
|
unsigned char *p, packet[17 + (sizeof("X11 Forward Unavailable") - 1)];
|
|
/* packet_type(1) + channel(4) + reason(4) + descr(4) + lang(4) */
|
|
LIBSSH2_CHANNEL *channel;
|
|
unsigned long sender_channel, initial_window_size, packet_size;
|
|
unsigned char *shost;
|
|
unsigned long sport, shost_len;
|
|
(void)datalen;
|
|
|
|
sender_channel = libssh2_ntohu32(s); s += 4;
|
|
initial_window_size = libssh2_ntohu32(s); s += 4;
|
|
packet_size = libssh2_ntohu32(s); s += 4;
|
|
shost_len = libssh2_ntohu32(s); s += 4;
|
|
shost = s; s += shost_len;
|
|
sport = libssh2_ntohu32(s); s += 4;
|
|
|
|
_libssh2_debug(session, LIBSSH2_DBG_CONN, "X11 Connection Received from %s:%ld on channel %lu", shost, sport, sender_channel);
|
|
if (session->x11) {
|
|
channel = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_CHANNEL));
|
|
if (!channel) {
|
|
libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a channel for new connection", 0);
|
|
failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */
|
|
goto x11_exit;
|
|
}
|
|
memset(channel, 0, sizeof(LIBSSH2_CHANNEL));
|
|
|
|
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, "Unable to allocate a channel for new connection", 0);
|
|
LIBSSH2_FREE(session, channel);
|
|
failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */
|
|
goto x11_exit;
|
|
}
|
|
memcpy(channel->channel_type, "x11", channel->channel_type_len + 1);
|
|
|
|
channel->remote.id = 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 = initial_window_size;
|
|
channel->local.window_size = initial_window_size;
|
|
channel->local.packet_size = packet_size;
|
|
|
|
_libssh2_debug(session, LIBSSH2_DBG_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 = packet;
|
|
*(p++) = SSH_MSG_CHANNEL_OPEN_CONFIRMATION;
|
|
libssh2_htonu32(p, channel->remote.id); p += 4;
|
|
libssh2_htonu32(p, channel->local.id); p += 4;
|
|
libssh2_htonu32(p, channel->remote.window_size_initial); p += 4;
|
|
libssh2_htonu32(p, channel->remote.packet_size); p += 4;
|
|
|
|
if (libssh2_packet_write(session, packet, 17)) {
|
|
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel open confirmation", 0);
|
|
return -1;
|
|
}
|
|
|
|
/* Link the channel into the session */
|
|
if (session->channels.tail) {
|
|
session->channels.tail->next = channel;
|
|
channel->prev = session->channels.tail;
|
|
} else {
|
|
session->channels.head = channel;
|
|
channel->prev = NULL;
|
|
}
|
|
channel->next = NULL;
|
|
session->channels.tail = channel;
|
|
|
|
/*
|
|
* Pass control to the callback, they may turn right around and
|
|
* free the channel, or actually use it
|
|
*/
|
|
LIBSSH2_X11_OPEN(channel, (char *)shost, sport);
|
|
|
|
return 0;
|
|
} else {
|
|
failure_code = 4; /* SSH_OPEN_RESOURCE_SHORTAGE */
|
|
}
|
|
|
|
x11_exit:
|
|
p = packet;
|
|
*(p++) = SSH_MSG_CHANNEL_OPEN_FAILURE;
|
|
libssh2_htonu32(p, sender_channel); p += 4;
|
|
libssh2_htonu32(p, failure_code); p += 4;
|
|
libssh2_htonu32(p, sizeof("X11 Forward Unavailable") - 1); p += 4;
|
|
memcpy(s, "X11 Forward Unavailable", sizeof("X11 Forward Unavailable") - 1); p += sizeof("X11 Forward Unavailable") - 1;
|
|
libssh2_htonu32(p, 0);
|
|
|
|
if (libssh2_packet_write(session, packet, packet_len)) {
|
|
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send open failure", 0);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ libssh2_packet_new
|
|
* Create a new packet and attach it to the brigade
|
|
*/
|
|
int libssh2_packet_add(LIBSSH2_SESSION *session, unsigned char *data, size_t datalen, int macstate)
|
|
{
|
|
LIBSSH2_PACKET *packet;
|
|
unsigned long data_head = 0;
|
|
|
|
_libssh2_debug(session, LIBSSH2_DBG_TRANS, "Packet type %d received, length=%d", (int)data[0], (int)datalen);
|
|
if (macstate == LIBSSH2_MAC_INVALID) {
|
|
if (session->macerror) {
|
|
if (LIBSSH2_MACERROR(session, (char *)data, datalen) == 0) {
|
|
/* Calling app has given the OK, Process it anyway */
|
|
macstate = LIBSSH2_MAC_CONFIRMED;
|
|
} else {
|
|
libssh2_error(session, LIBSSH2_ERROR_INVALID_MAC, "Invalid Message Authentication Code received", 0);
|
|
if (session->ssh_msg_disconnect) {
|
|
LIBSSH2_DISCONNECT(session, SSH_DISCONNECT_MAC_ERROR, "Invalid MAC received", sizeof("Invalid MAC received") - 1, "", 0);
|
|
}
|
|
LIBSSH2_FREE(session, data);
|
|
return -1;
|
|
}
|
|
} else {
|
|
libssh2_error(session, LIBSSH2_ERROR_INVALID_MAC, "Invalid Message Authentication Code received", 0);
|
|
if (session->ssh_msg_disconnect) {
|
|
LIBSSH2_DISCONNECT(session, SSH_DISCONNECT_MAC_ERROR, "Invalid MAC received", sizeof("Invalid MAC received") - 1, "", 0);
|
|
}
|
|
LIBSSH2_FREE(session, data);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* A couple exceptions to the packet adding rule: */
|
|
switch (data[0]) {
|
|
case SSH_MSG_DISCONNECT:
|
|
{
|
|
char *message, *language;
|
|
int reason, message_len, language_len;
|
|
|
|
reason = libssh2_ntohu32(data + 1);
|
|
message_len = libssh2_ntohu32(data + 5);
|
|
message = (char *)data + 9; /* packet_type(1) + reason(4) + message_len(4) */
|
|
language_len = libssh2_ntohu32(data + 9 + message_len);
|
|
/* This is where we hack on the data a little,
|
|
* Use the MSB of language_len to to a terminating NULL (In all liklihood it is already)
|
|
* Shift the language tag back a byte (In all likelihood it's zero length anyway
|
|
* Store a NULL in the last byte of the packet to terminate the language string
|
|
* With the lengths passed this isn't *REALLY* necessary, but it's "kind"
|
|
*/
|
|
message[message_len] = '\0';
|
|
language = (char *)data + 9 + message_len + 3;
|
|
if (language_len) {
|
|
memcpy(language, language + 1, language_len);
|
|
}
|
|
language[language_len] = '\0';
|
|
|
|
if (session->ssh_msg_disconnect) {
|
|
LIBSSH2_DISCONNECT(session, reason, message, message_len, language, language_len);
|
|
}
|
|
_libssh2_debug(session, LIBSSH2_DBG_TRANS, "Disconnect(%d): %s(%s)", reason, message, language);
|
|
LIBSSH2_FREE(session, data);
|
|
session->socket_state = LIBSSH2_SOCKET_DISCONNECTED;
|
|
return -1;
|
|
}
|
|
break;
|
|
case SSH_MSG_IGNORE:
|
|
/* As with disconnect, back it up one and add a trailing NULL */
|
|
memcpy(data + 4, data + 5, datalen - 5);
|
|
data[datalen] = '\0';
|
|
if (session->ssh_msg_ignore) {
|
|
LIBSSH2_IGNORE(session, (char *)data + 4, datalen - 5);
|
|
}
|
|
LIBSSH2_FREE(session, data);
|
|
return 0;
|
|
break;
|
|
case SSH_MSG_DEBUG:
|
|
{
|
|
int always_display = data[0];
|
|
char *message, *language;
|
|
int message_len, language_len;
|
|
|
|
message_len = libssh2_ntohu32(data + 2);
|
|
message = (char *)data + 6; /* packet_type(1) + display(1) + message_len(4) */
|
|
language_len = libssh2_ntohu32(data + 6 + message_len);
|
|
/* This is where we hack on the data a little,
|
|
* Use the MSB of language_len to to a terminating NULL (In all liklihood it is already)
|
|
* Shift the language tag back a byte (In all likelihood it's zero length anyway
|
|
* Store a NULL in the last byte of the packet to terminate the language string
|
|
* With the lengths passed this isn't *REALLY* necessary, but it's "kind"
|
|
*/
|
|
message[message_len] = '\0';
|
|
language = (char *)data + 6 + message_len + 3;
|
|
if (language_len) {
|
|
memcpy(language, language + 1, language_len);
|
|
}
|
|
language[language_len] = '\0';
|
|
|
|
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_DBG_TRANS, "Debug Packet: %s", message);
|
|
LIBSSH2_FREE(session, data);
|
|
return 0;
|
|
}
|
|
break;
|
|
case SSH_MSG_CHANNEL_EXTENDED_DATA:
|
|
data_head += 4; /* streamid(4) */
|
|
case SSH_MSG_CHANNEL_DATA:
|
|
data_head += 9; /* packet_type(1) + channelno(4) + datalen(4) */
|
|
{
|
|
LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data + 1));
|
|
|
|
if (!channel) {
|
|
libssh2_error(session, LIBSSH2_ERROR_CHANNEL_UNKNOWN, "Packet received for unknown channel, ignoring", 0);
|
|
LIBSSH2_FREE(session, data);
|
|
return 0;
|
|
}
|
|
#ifdef LIBSSH2DEBUG
|
|
{
|
|
unsigned long stream_id = 0;
|
|
|
|
if (data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) {
|
|
stream_id = libssh2_ntohu32(data + 5);
|
|
}
|
|
|
|
_libssh2_debug(session, LIBSSH2_DBG_CONN, "%d bytes received for channel %lu/%lu stream #%lu", (int)(datalen - data_head), channel->local.id, channel->remote.id, stream_id);
|
|
}
|
|
#endif
|
|
if ((channel->remote.extended_data_ignore_mode == LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE) && (data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA)) {
|
|
/* Pretend we didn't receive this */
|
|
LIBSSH2_FREE(session, data);
|
|
|
|
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Ignoring extended data and refunding %d bytes", (int)(datalen - 13));
|
|
/* Adjust the window based on the block we just freed */
|
|
libssh2_channel_receive_window_adjust(channel, datalen - 13, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* REMEMBER! remote means remote as source of data, NOT remote window! */
|
|
if (channel->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", 0);
|
|
datalen = channel->remote.packet_size + data_head;
|
|
}
|
|
if (channel->remote.window_size <= 0) {
|
|
/* 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", 0);
|
|
LIBSSH2_FREE(session, data);
|
|
return 0;
|
|
}
|
|
/* Reset EOF status */
|
|
channel->remote.eof = 0;
|
|
|
|
if ((datalen - data_head) > channel->remote.window_size) {
|
|
libssh2_error(session, LIBSSH2_ERROR_CHANNEL_WINDOW_EXCEEDED, "Remote sent more data than current window allows, truncating", 0);
|
|
datalen = channel->remote.window_size + data_head;
|
|
} else {
|
|
/* Now that we've received it, shrink our window */
|
|
channel->remote.window_size -= datalen - data_head;
|
|
}
|
|
}
|
|
break;
|
|
case SSH_MSG_CHANNEL_EOF:
|
|
{
|
|
LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data + 1));
|
|
|
|
if (!channel) {
|
|
/* We may have freed already, just quietly ignore this... */
|
|
LIBSSH2_FREE(session, data);
|
|
return 0;
|
|
}
|
|
|
|
_libssh2_debug(session, LIBSSH2_DBG_CONN, "EOF received for channel %lu/%lu", channel->local.id, channel->remote.id);
|
|
channel->remote.eof = 1;
|
|
|
|
LIBSSH2_FREE(session, data);
|
|
return 0;
|
|
}
|
|
break;
|
|
case SSH_MSG_CHANNEL_REQUEST:
|
|
{
|
|
if (libssh2_ntohu32(data+5) == sizeof("exit-status") - 1
|
|
&& !memcmp("exit-status", data + 9, sizeof("exit-status") - 1)) {
|
|
|
|
/* we've got "exit-status" packet. Set the session value */
|
|
LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data+1));
|
|
|
|
if (channel) {
|
|
channel->exit_status = libssh2_ntohu32(data + 9 + sizeof("exit-status"));
|
|
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Exit status %lu received for channel %lu/%lu", channel->exit_status, channel->local.id, channel->remote.id);
|
|
}
|
|
|
|
LIBSSH2_FREE(session, data);
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
case SSH_MSG_CHANNEL_CLOSE:
|
|
{
|
|
LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data + 1));
|
|
|
|
if (!channel) {
|
|
/* We may have freed already, just quietly ignore this... */
|
|
LIBSSH2_FREE(session, data);
|
|
return 0;
|
|
}
|
|
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Close received for channel %lu/%lu", channel->local.id, channel->remote.id);
|
|
|
|
channel->remote.close = 1;
|
|
channel->remote.eof = 1;
|
|
/* TODO: Add a callback for this */
|
|
|
|
LIBSSH2_FREE(session, data);
|
|
return 0;
|
|
}
|
|
break;
|
|
case SSH_MSG_CHANNEL_OPEN:
|
|
if ((datalen >= (sizeof("forwarded-tcpip") + 4)) &&
|
|
((sizeof("forwarded-tcpip")-1) == libssh2_ntohu32(data + 1)) &&
|
|
(memcmp(data + 5, "forwarded-tcpip", sizeof("forwarded-tcpip") - 1) == 0)) {
|
|
int retval = libssh2_packet_queue_listener(session, data, datalen);
|
|
|
|
LIBSSH2_FREE(session, data);
|
|
return retval;
|
|
}
|
|
if ((datalen >= (sizeof("x11") + 4)) &&
|
|
((sizeof("x11")-1) == libssh2_ntohu32(data + 1)) &&
|
|
(memcmp(data + 5, "x11", sizeof("x11") - 1) == 0)) {
|
|
int retval = libssh2_packet_x11_open(session, data, datalen);
|
|
|
|
LIBSSH2_FREE(session, data);
|
|
return retval;
|
|
}
|
|
break;
|
|
case SSH_MSG_CHANNEL_WINDOW_ADJUST:
|
|
{
|
|
LIBSSH2_CHANNEL *channel = libssh2_channel_locate(session, libssh2_ntohu32(data + 1));
|
|
unsigned long bytestoadd = libssh2_ntohu32(data + 5);
|
|
|
|
if (channel && bytestoadd) {
|
|
channel->local.window_size += bytestoadd;
|
|
}
|
|
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Window adjust received for channel %lu/%lu, adding %lu bytes, new window_size=%lu", channel->local.id, channel->remote.id, bytestoadd, channel->local.window_size);
|
|
|
|
LIBSSH2_FREE(session, data);
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
packet = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PACKET));
|
|
if (!packet) {
|
|
_libssh2_debug(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for LIBSSH2_PACKET");
|
|
LIBSSH2_FREE(session, data);
|
|
return -1;
|
|
}
|
|
memset(packet, 0, sizeof(LIBSSH2_PACKET));
|
|
|
|
packet->data = data;
|
|
packet->data_len = datalen;
|
|
packet->data_head = data_head;
|
|
packet->mac = macstate;
|
|
packet->brigade = &session->packets;
|
|
packet->next = NULL;
|
|
|
|
if (session->packets.tail) {
|
|
packet->prev = session->packets.tail;
|
|
packet->prev->next = packet;
|
|
session->packets.tail = packet;
|
|
} else {
|
|
session->packets.head = packet;
|
|
session->packets.tail = packet;
|
|
packet->prev = NULL;
|
|
}
|
|
|
|
if (data[0] == SSH_MSG_KEXINIT && !(session->state & LIBSSH2_STATE_EXCHANGING_KEYS)) {
|
|
/* Remote wants new keys
|
|
* Well, it's already in the brigade,
|
|
* let's just call back into ourselves
|
|
*/
|
|
_libssh2_debug(session, LIBSSH2_DBG_TRANS,
|
|
"Renegotiating Keys");
|
|
libssh2_kex_exchange(session, 1);
|
|
/* 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 */
|
|
}
|
|
|
|
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_ex(LIBSSH2_SESSION *session, unsigned char packet_type,
|
|
unsigned char **data, unsigned long *data_len,
|
|
unsigned long match_ofs,
|
|
const unsigned char *match_buf,
|
|
unsigned long match_len, int poll_socket)
|
|
{
|
|
LIBSSH2_PACKET *packet = session->packets.head;
|
|
|
|
if (poll_socket) {
|
|
libssh2pack_t rc = libssh2_packet_read(session);
|
|
if ((rc < 0) && !packet) {
|
|
return rc;
|
|
}
|
|
}
|
|
_libssh2_debug(session, LIBSSH2_DBG_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;
|
|
|
|
if (packet->prev) {
|
|
packet->prev->next = packet->next;
|
|
} else {
|
|
session->packets.head = packet->next;
|
|
}
|
|
|
|
if (packet->next) {
|
|
packet->next->prev = packet->prev;
|
|
} else {
|
|
session->packets.tail = packet->prev;
|
|
}
|
|
|
|
LIBSSH2_FREE(session, packet);
|
|
|
|
return 0;
|
|
}
|
|
packet = packet->next;
|
|
}
|
|
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_ex(LIBSSH2_SESSION *session,
|
|
unsigned char *packet_types,
|
|
unsigned char **data,
|
|
unsigned long *data_len,
|
|
unsigned long match_ofs,
|
|
const unsigned char *match_buf,
|
|
unsigned long match_len, int poll_socket)
|
|
{
|
|
int i, packet_types_len = strlen((char *)packet_types);
|
|
|
|
for(i = 0; i < packet_types_len; i++) {
|
|
if (0 == libssh2_packet_ask_ex(session, packet_types[i],
|
|
data, data_len, match_ofs,
|
|
match_buf, match_len,
|
|
i ? 0 : poll_socket)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ waitsocket
|
|
* Returns
|
|
* negative on error
|
|
* >0 on incoming data
|
|
* 0 on timeout
|
|
*
|
|
* FIXME: convert to use poll on systems that have it.
|
|
*/
|
|
int libssh2_waitsocket(LIBSSH2_SESSION *session,
|
|
long seconds)
|
|
{
|
|
struct timeval timeout;
|
|
int rc;
|
|
fd_set fd;
|
|
|
|
timeout.tv_sec = seconds;
|
|
timeout.tv_usec = 0;
|
|
|
|
FD_ZERO(&fd);
|
|
|
|
FD_SET(session->socket_fd, &fd);
|
|
|
|
rc = select(session->socket_fd+1, &fd, NULL, NULL, &timeout);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* {{{ libssh2_packet_require
|
|
* [BLOCKING] for LIBSSH2_READ_TIMEOUT seconds
|
|
* Loops libssh2_packet_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_ex(LIBSSH2_SESSION *session,
|
|
unsigned char packet_type,
|
|
unsigned char **data, unsigned long *data_len,
|
|
unsigned long match_ofs,
|
|
const unsigned char *match_buf,
|
|
unsigned long match_len)
|
|
{
|
|
time_t start = time(NULL);
|
|
|
|
if (libssh2_packet_ask_ex(session, packet_type, data, data_len,
|
|
match_ofs, match_buf, match_len, 0) == 0) {
|
|
/* A packet was available in the packet brigade */
|
|
return 0;
|
|
}
|
|
|
|
_libssh2_debug(session, LIBSSH2_DBG_TRANS,
|
|
"Blocking until packet of type %d becomes available",
|
|
(int)packet_type);
|
|
|
|
while (session->socket_state == LIBSSH2_SOCKET_CONNECTED) {
|
|
libssh2pack_t ret = libssh2_packet_read(session);
|
|
if ((ret < 0) && (ret != PACKET_EAGAIN)) {
|
|
/* an error which is not just because of blocking */
|
|
return ret;
|
|
}
|
|
if (packet_type == ret) {
|
|
/* Be lazy, let packet_ask pull it out of the
|
|
brigade */
|
|
ret = libssh2_packet_ask_ex(session, packet_type,
|
|
data, data_len, match_ofs,
|
|
match_buf, match_len, 0);
|
|
return ret;
|
|
}
|
|
else if (ret <= 0) {
|
|
/* nothing available, wait until data arrives or
|
|
we time out */
|
|
long left = LIBSSH2_READ_TIMEOUT -
|
|
(time(NULL) - start);
|
|
|
|
if((left <= 0) ||
|
|
(libssh2_waitsocket(session, left) <= 0)) {
|
|
return PACKET_TIMEOUT;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Only reached if the socket died */
|
|
return -1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ libssh2_packet_burn
|
|
* Loops libssh2_packet_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)
|
|
{
|
|
unsigned char *data;
|
|
unsigned long data_len;
|
|
unsigned char all_packets[255];
|
|
int i;
|
|
for(i = 1; i < 256; i++) all_packets[i - 1] = i;
|
|
|
|
if (libssh2_packet_askv_ex(session, all_packets, &data, &data_len, 0,
|
|
NULL, 0, 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_DBG_TRANS,
|
|
"Blocking until packet becomes available to burn");
|
|
|
|
while (session->socket_state == LIBSSH2_SOCKET_CONNECTED) {
|
|
int ret = libssh2_packet_read(session);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
if (ret == 0) {
|
|
/* FIXME: this might busyloop */
|
|
continue;
|
|
}
|
|
|
|
/* Be lazy, let packet_ask pull it out of the brigade */
|
|
if (0 == libssh2_packet_ask_ex(session, ret, &data, &data_len,
|
|
0, NULL, 0, 0)) {
|
|
/* Smoke 'em if you got 'em */
|
|
LIBSSH2_FREE(session, data);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Only reached if the socket died */
|
|
return -1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ libssh2_packet_requirev
|
|
* [BLOCKING]
|
|
* Loops libssh2_packet_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_ex(LIBSSH2_SESSION *session,
|
|
unsigned char *packet_types,
|
|
unsigned char **data, unsigned long *data_len,
|
|
unsigned long match_ofs,
|
|
const unsigned char *match_buf,
|
|
unsigned long match_len)
|
|
{
|
|
time_t start = time(NULL);
|
|
|
|
if (libssh2_packet_askv_ex(session, packet_types, data, data_len,
|
|
match_ofs, match_buf, match_len, 0) == 0) {
|
|
/* One of the packets listed was available in the packet
|
|
brigade */
|
|
return 0;
|
|
}
|
|
|
|
while (session->socket_state != LIBSSH2_SOCKET_DISCONNECTED) {
|
|
int ret = libssh2_packet_read(session);
|
|
if ((ret < 0) && (ret != PACKET_EAGAIN)) {
|
|
return ret;
|
|
}
|
|
if (ret <= 0) {
|
|
long left = LIBSSH2_READ_TIMEOUT -
|
|
(time(NULL) - start);
|
|
|
|
if((left <= 0) ||
|
|
(libssh2_waitsocket(session, left) <= 0 )) {
|
|
return PACKET_TIMEOUT;
|
|
}
|
|
}
|
|
|
|
if (strchr((char *)packet_types, ret)) {
|
|
/* Be lazy, let packet_ask pull it out of the
|
|
brigade */
|
|
return libssh2_packet_askv_ex(session, packet_types,
|
|
data, data_len,
|
|
match_ofs, match_buf,
|
|
match_len, 0);
|
|
}
|
|
}
|
|
|
|
/* Only reached if the socket died */
|
|
return -1;
|
|
}
|
|
/* }}} */
|