libssh2/src/channel.c

2622 lines
88 KiB
C
Raw Normal View History

/* Copyright (c) 2004-2007 Sara Golemon <sarag@libssh2.org>
* Copyright (c) 2005 Mikhail Gusarov <dottedmag@dottedmag.net>
* Copyright (c) 2008-2014 by Daniel Stenberg
*
2004-12-07 21:17:20 +00:00
* 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.
2004-12-07 21:17:20 +00:00
*
* 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.
2004-12-07 21:17:20 +00:00
*
* 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.
2004-12-07 21:17:20 +00:00
*
* 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"
#ifdef HAVE_UNISTD_H
2004-12-07 21:17:20 +00:00
#include <unistd.h>
#endif
#include <fcntl.h>
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif
2009-05-24 23:44:23 +02:00
#include <assert.h>
#include "channel.h"
#include "transport.h"
#include "packet.h"
#include "session.h"
/*
* _libssh2_channel_nextid
*
2004-12-07 21:17:20 +00:00
* Determine the next channel ID we can use at our end
*/
2010-04-17 13:27:17 +02:00
uint32_t
_libssh2_channel_nextid(LIBSSH2_SESSION * session)
2004-12-07 21:17:20 +00:00
{
2010-04-17 13:27:17 +02:00
uint32_t id = session->next_channel;
LIBSSH2_CHANNEL *channel;
channel = _libssh2_list_first(&session->channels);
while (channel) {
if (channel->local.id > id) {
id = channel->local.id;
}
channel = _libssh2_list_next(&channel->node);
}
/* This is a shortcut to avoid waiting for close packets on channels we've
* forgotten about, This *could* be a problem if we request and close 4
* billion or so channels in too rapid succession for the remote end to
* respond, but the worst case scenario is that some data meant for
* another channel Gets picked up by the new one.... Pretty unlikely all
* told...
*/
session->next_channel = id + 1;
_libssh2_debug(session, LIBSSH2_TRACE_CONN, "Allocated new channel ID#%lu",
id);
return id;
2004-12-07 21:17:20 +00:00
}
/*
* _libssh2_channel_locate
*
2004-12-07 21:17:20 +00:00
* Locate a channel pointer by number
*/
LIBSSH2_CHANNEL *
2010-04-17 13:27:17 +02:00
_libssh2_channel_locate(LIBSSH2_SESSION *session, uint32_t channel_id)
2004-12-07 21:17:20 +00:00
{
LIBSSH2_CHANNEL *channel;
LIBSSH2_LISTENER *l;
for(channel = _libssh2_list_first(&session->channels);
channel;
channel = _libssh2_list_next(&channel->node)) {
if (channel->local.id == channel_id)
return channel;
}
/* We didn't find the channel in the session, let's then check its
listeners since each listener may have its own set of pending channels
*/
for(l = _libssh2_list_first(&session->listeners); l;
l = _libssh2_list_next(&l->node)) {
for(channel = _libssh2_list_first(&l->queue);
channel;
channel = _libssh2_list_next(&channel->node)) {
if (channel->local.id == channel_id)
return channel;
}
}
return NULL;
2004-12-07 21:17:20 +00:00
}
/*
* _libssh2_channel_open
*
2004-12-07 21:17:20 +00:00
* Establish a generic session channel
*/
LIBSSH2_CHANNEL *
_libssh2_channel_open(LIBSSH2_SESSION * session, const char *channel_type,
2010-04-17 13:27:17 +02:00
uint32_t channel_type_len,
uint32_t window_size,
uint32_t packet_size,
const unsigned char *message,
size_t message_len)
2004-12-07 21:17:20 +00:00
{
static const unsigned char reply_codes[3] = {
SSH_MSG_CHANNEL_OPEN_CONFIRMATION,
SSH_MSG_CHANNEL_OPEN_FAILURE,
0
};
unsigned char *s;
int rc;
if (session->open_state == libssh2_NB_state_idle) {
session->open_channel = NULL;
session->open_packet = NULL;
session->open_data = NULL;
/* 17 = packet_type(1) + channel_type_len(4) + sender_channel(4) +
* window_size(4) + packet_size(4) */
session->open_packet_len = channel_type_len + 17;
session->open_local_channel = _libssh2_channel_nextid(session);
/* Zero the whole thing out */
memset(&session->open_packet_requirev_state, 0,
sizeof(session->open_packet_requirev_state));
_libssh2_debug(session, LIBSSH2_TRACE_CONN,
"Opening Channel - win %d pack %d", window_size,
packet_size);
session->open_channel =
LIBSSH2_CALLOC(session, sizeof(LIBSSH2_CHANNEL));
if (!session->open_channel) {
_libssh2_error(session, LIBSSH2_ERROR_ALLOC,
"Unable to allocate space for channel data");
return NULL;
}
session->open_channel->channel_type_len = channel_type_len;
session->open_channel->channel_type =
LIBSSH2_ALLOC(session, channel_type_len);
if (!session->open_channel->channel_type) {
_libssh2_error(session, LIBSSH2_ERROR_ALLOC,
"Failed allocating memory for channel type name");
LIBSSH2_FREE(session, session->open_channel);
session->open_channel = NULL;
return NULL;
}
memcpy(session->open_channel->channel_type, channel_type,
channel_type_len);
/* REMEMBER: local as in locally sourced */
session->open_channel->local.id = session->open_local_channel;
session->open_channel->remote.window_size = window_size;
session->open_channel->remote.window_size_initial = window_size;
session->open_channel->remote.packet_size = packet_size;
session->open_channel->session = session;
_libssh2_list_add(&session->channels,
&session->open_channel->node);
s = session->open_packet =
LIBSSH2_ALLOC(session, session->open_packet_len);
if (!session->open_packet) {
_libssh2_error(session, LIBSSH2_ERROR_ALLOC,
"Unable to allocate temporary space for packet");
goto channel_error;
}
*(s++) = SSH_MSG_CHANNEL_OPEN;
_libssh2_store_str(&s, channel_type, channel_type_len);
_libssh2_store_u32(&s, session->open_local_channel);
_libssh2_store_u32(&s, window_size);
_libssh2_store_u32(&s, packet_size);
/* Do not copy the message */
session->open_state = libssh2_NB_state_created;
}
if (session->open_state == libssh2_NB_state_created) {
rc = _libssh2_transport_send(session,
session->open_packet,
session->open_packet_len,
message, message_len);
if (rc == LIBSSH2_ERROR_EAGAIN) {
_libssh2_error(session, rc,
"Would block sending channel-open request");
return NULL;
}
else if (rc) {
_libssh2_error(session, rc,
"Unable to send channel-open request");
goto channel_error;
}
session->open_state = libssh2_NB_state_sent;
}
if (session->open_state == libssh2_NB_state_sent) {
rc = _libssh2_packet_requirev(session, reply_codes,
&session->open_data,
&session->open_data_len, 1,
session->open_packet + 5 +
channel_type_len, 4,
&session->open_packet_requirev_state);
if (rc == LIBSSH2_ERROR_EAGAIN) {
_libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block");
return NULL;
} else if (rc) {
goto channel_error;
}
if (session->open_data[0] == SSH_MSG_CHANNEL_OPEN_CONFIRMATION) {
session->open_channel->remote.id =
_libssh2_ntohu32(session->open_data + 5);
session->open_channel->local.window_size =
_libssh2_ntohu32(session->open_data + 9);
session->open_channel->local.window_size_initial =
_libssh2_ntohu32(session->open_data + 9);
session->open_channel->local.packet_size =
_libssh2_ntohu32(session->open_data + 13);
_libssh2_debug(session, LIBSSH2_TRACE_CONN,
"Connection Established - ID: %lu/%lu win: %lu/%lu"
" pack: %lu/%lu",
session->open_channel->local.id,
session->open_channel->remote.id,
session->open_channel->local.window_size,
session->open_channel->remote.window_size,
session->open_channel->local.packet_size,
session->open_channel->remote.packet_size);
LIBSSH2_FREE(session, session->open_packet);
session->open_packet = NULL;
LIBSSH2_FREE(session, session->open_data);
session->open_data = NULL;
session->open_state = libssh2_NB_state_idle;
return session->open_channel;
}
if (session->open_data[0] == SSH_MSG_CHANNEL_OPEN_FAILURE) {
unsigned int reason_code = _libssh2_ntohu32(session->open_data + 5);
switch (reason_code) {
case SSH_OPEN_ADMINISTRATIVELY_PROHIBITED:
_libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE,
"Channel open failure (admininstratively prohibited)");
break;
case SSH_OPEN_CONNECT_FAILED:
_libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE,
"Channel open failure (connect failed)");
break;
case SSH_OPEN_UNKNOWN_CHANNELTYPE:
_libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE,
"Channel open failure (unknown channel type)");
break;
case SSH_OPEN_RESOURCE_SHORTAGE:
_libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE,
"Channel open failure (resource shortage)");
break;
default:
_libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE,
"Channel open failure");
}
}
}
2004-12-07 21:17:20 +00:00
channel_error:
2004-12-07 21:17:20 +00:00
if (session->open_data) {
LIBSSH2_FREE(session, session->open_data);
session->open_data = NULL;
}
if (session->open_packet) {
LIBSSH2_FREE(session, session->open_packet);
session->open_packet = NULL;
}
if (session->open_channel) {
unsigned char channel_id[4];
LIBSSH2_FREE(session, session->open_channel->channel_type);
_libssh2_list_remove(&session->open_channel->node);
/* Clear out packets meant for this channel */
_libssh2_htonu32(channel_id, session->open_channel->local.id);
while ((_libssh2_packet_ask(session, SSH_MSG_CHANNEL_DATA,
&session->open_data,
&session->open_data_len, 1,
channel_id, 4) >= 0)
||
(_libssh2_packet_ask(session, SSH_MSG_CHANNEL_EXTENDED_DATA,
&session->open_data,
&session->open_data_len, 1,
channel_id, 4) >= 0)) {
LIBSSH2_FREE(session, session->open_data);
session->open_data = NULL;
}
LIBSSH2_FREE(session, session->open_channel);
session->open_channel = NULL;
}
session->open_state = libssh2_NB_state_idle;
return NULL;
2004-12-07 21:17:20 +00:00
}
/*
* libssh2_channel_open_ex
*
* Establish a generic session channel
*/
LIBSSH2_API LIBSSH2_CHANNEL *
libssh2_channel_open_ex(LIBSSH2_SESSION *session, const char *type,
unsigned int type_len,
unsigned int window_size, unsigned int packet_size,
const char *msg, unsigned int msg_len)
{
LIBSSH2_CHANNEL *ptr;
if(!session)
return NULL;
BLOCK_ADJUST_ERRNO(ptr, session,
_libssh2_channel_open(session, type, type_len,
window_size, packet_size,
(unsigned char *)msg,
msg_len));
return ptr;
}
/*
* libssh2_channel_direct_tcpip_ex
*
2004-12-07 21:17:20 +00:00
* Tunnel TCP/IP connect through the SSH session to direct host/port
*/
static LIBSSH2_CHANNEL *
channel_direct_tcpip(LIBSSH2_SESSION * session, const char *host,
int port, const char *shost, int sport)
2004-12-07 21:17:20 +00:00
{
LIBSSH2_CHANNEL *channel;
unsigned char *s;
if (session->direct_state == libssh2_NB_state_idle) {
session->direct_host_len = strlen(host);
session->direct_shost_len = strlen(shost);
/* host_len(4) + port(4) + shost_len(4) + sport(4) */
session->direct_message_len =
session->direct_host_len + session->direct_shost_len + 16;
_libssh2_debug(session, LIBSSH2_TRACE_CONN,
"Requesting direct-tcpip session to from %s:%d to %s:%d",
shost, sport, host, port);
s = session->direct_message =
LIBSSH2_ALLOC(session, session->direct_message_len);
if (!session->direct_message) {
_libssh2_error(session, LIBSSH2_ERROR_ALLOC,
"Unable to allocate memory for direct-tcpip connection");
return NULL;
}
_libssh2_store_str(&s, host, session->direct_host_len);
_libssh2_store_u32(&s, port);
_libssh2_store_str(&s, shost, session->direct_shost_len);
_libssh2_store_u32(&s, sport);
}
channel =
_libssh2_channel_open(session, "direct-tcpip",
sizeof("direct-tcpip") - 1,
LIBSSH2_CHANNEL_WINDOW_DEFAULT,
LIBSSH2_CHANNEL_PACKET_DEFAULT,
session->direct_message,
session->direct_message_len);
if (!channel &&
libssh2_session_last_errno(session) == LIBSSH2_ERROR_EAGAIN) {
/* The error code is still set to LIBSSH2_ERROR_EAGAIN, set our state
to created to avoid re-creating the package on next invoke */
session->direct_state = libssh2_NB_state_created;
return NULL;
}
/* by default we set (keep?) idle state... */
session->direct_state = libssh2_NB_state_idle;
LIBSSH2_FREE(session, session->direct_message);
session->direct_message = NULL;
return channel;
2004-12-07 21:17:20 +00:00
}
/*
* libssh2_channel_direct_tcpip_ex
*
* Tunnel TCP/IP connect through the SSH session to direct host/port
*/
LIBSSH2_API LIBSSH2_CHANNEL *
libssh2_channel_direct_tcpip_ex(LIBSSH2_SESSION *session, const char *host,
int port, const char *shost, int sport)
{
LIBSSH2_CHANNEL *ptr;
if(!session)
return NULL;
BLOCK_ADJUST_ERRNO(ptr, session,
channel_direct_tcpip(session, host, port, shost, sport));
return ptr;
}
/*
* channel_forward_listen
*
2004-12-29 19:26:28 +00:00
* Bind a port on the remote host and listen for connections
*/
static LIBSSH2_LISTENER *
channel_forward_listen(LIBSSH2_SESSION * session, const char *host,
int port, int *bound_port, int queue_maxsize)
2004-12-29 19:26:28 +00:00
{
unsigned char *s;
static const unsigned char reply_codes[3] =
{ SSH_MSG_REQUEST_SUCCESS, SSH_MSG_REQUEST_FAILURE, 0 };
int rc;
if(!host)
host = "0.0.0.0";
if (session->fwdLstn_state == libssh2_NB_state_idle) {
session->fwdLstn_host_len = strlen(host);
/* 14 = packet_type(1) + request_len(4) + want_replay(1) + host_len(4)
+ port(4) */
session->fwdLstn_packet_len =
session->fwdLstn_host_len + (sizeof("tcpip-forward") - 1) + 14;
/* Zero the whole thing out */
memset(&session->fwdLstn_packet_requirev_state, 0,
sizeof(session->fwdLstn_packet_requirev_state));
_libssh2_debug(session, LIBSSH2_TRACE_CONN,
"Requesting tcpip-forward session for %s:%d", host,
port);
s = session->fwdLstn_packet =
LIBSSH2_ALLOC(session, session->fwdLstn_packet_len);
if (!session->fwdLstn_packet) {
_libssh2_error(session, LIBSSH2_ERROR_ALLOC,
2014-02-18 23:46:25 +01:00
"Unable to allocate memory for setenv packet");
return NULL;
}
*(s++) = SSH_MSG_GLOBAL_REQUEST;
_libssh2_store_str(&s, "tcpip-forward", sizeof("tcpip-forward") - 1);
*(s++) = 0x01; /* want_reply */
_libssh2_store_str(&s, host, session->fwdLstn_host_len);
_libssh2_store_u32(&s, port);
session->fwdLstn_state = libssh2_NB_state_created;
}
if (session->fwdLstn_state == libssh2_NB_state_created) {
rc = _libssh2_transport_send(session,
session->fwdLstn_packet,
session->fwdLstn_packet_len,
NULL, 0);
if (rc == LIBSSH2_ERROR_EAGAIN) {
_libssh2_error(session, LIBSSH2_ERROR_EAGAIN,
"Would block sending global-request packet for "
"forward listen request");
return NULL;
}
else if (rc) {
_libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND,
"Unable to send global-request packet for forward "
"listen request");
LIBSSH2_FREE(session, session->fwdLstn_packet);
session->fwdLstn_packet = NULL;
session->fwdLstn_state = libssh2_NB_state_idle;
return NULL;
}
LIBSSH2_FREE(session, session->fwdLstn_packet);
session->fwdLstn_packet = NULL;
session->fwdLstn_state = libssh2_NB_state_sent;
}
if (session->fwdLstn_state == libssh2_NB_state_sent) {
unsigned char *data;
size_t data_len;
rc = _libssh2_packet_requirev(session, reply_codes, &data, &data_len,
0, NULL, 0,
&session->fwdLstn_packet_requirev_state);
if (rc == LIBSSH2_ERROR_EAGAIN) {
_libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block");
return NULL;
} else if (rc) {
_libssh2_error(session, LIBSSH2_ERROR_PROTO, "Unknown");
session->fwdLstn_state = libssh2_NB_state_idle;
return NULL;
}
if (data[0] == SSH_MSG_REQUEST_SUCCESS) {
LIBSSH2_LISTENER *listener;
listener = LIBSSH2_CALLOC(session, sizeof(LIBSSH2_LISTENER));
if (!listener)
_libssh2_error(session, LIBSSH2_ERROR_ALLOC,
"Unable to allocate memory for listener queue");
else {
listener->host =
LIBSSH2_ALLOC(session, session->fwdLstn_host_len + 1);
if (!listener->host) {
_libssh2_error(session, LIBSSH2_ERROR_ALLOC,