blob: eb9f7a7a2aa5ad8785d5ae0692f5eb5116186dfe [file] [log] [blame]
/* 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"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif
/* {{{ libssh2_channel_nextid
* Determine the next channel ID we can use at our end
*/
unsigned long libssh2_channel_nextid(LIBSSH2_SESSION *session)
{
unsigned long id = session->next_channel;
LIBSSH2_CHANNEL *channel;
channel = session->channels.head;
while (channel) {
if (channel->local.id > id) {
id = channel->local.id;
}
channel = channel->next;
}
/* 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_DBG_CONN, "Allocated new channel ID#%lu", id);
return id;
}
/* }}} */
/* {{{ libssh2_channel_locate
* Locate a channel pointer by number
*/
LIBSSH2_CHANNEL *libssh2_channel_locate(LIBSSH2_SESSION *session, unsigned long channel_id)
{
LIBSSH2_CHANNEL *channel = session->channels.head;
while (channel) {
if (channel->local.id == channel_id) {
return channel;
}
channel = channel->next;
}
return NULL;
}
/* }}} */
#define libssh2_channel_add(session, channel) \
{ \
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); \
(channel)->session = (session); \
}
/* {{{ libssh2_channel_open_ex
* Establish a generic session channel
*/
LIBSSH2_API LIBSSH2_CHANNEL *
libssh2_channel_open_ex(LIBSSH2_SESSION *session, const char *channel_type, unsigned int channel_type_len,
unsigned int window_size, unsigned int packet_size, const char *message, unsigned int message_len)
{
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 + message_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_DBG_CONN, "Opening Channel - win %d pack %d", window_size, packet_size);
session->open_channel = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_CHANNEL));
if (!session->open_channel) {
libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate space for channel data", 0);
return NULL;
}
memset(session->open_channel, 0, sizeof(LIBSSH2_CHANNEL));
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", 0);
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;
libssh2_channel_add(session, session->open_channel);
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", 0);
goto channel_error;
}
*(s++) = SSH_MSG_CHANNEL_OPEN;
libssh2_htonu32(s, channel_type_len); s += 4;
memcpy(s, channel_type, channel_type_len); s += channel_type_len;
libssh2_htonu32(s, session->open_local_channel); s += 4;
libssh2_htonu32(s, window_size); s += 4;
libssh2_htonu32(s, packet_size); s += 4;
if (message && message_len) {
memcpy(s, message, message_len); s += message_len;
}
session->open_state = libssh2_NB_state_created;
}
if (session->open_state == libssh2_NB_state_created) {
rc = libssh2_packet_write(session, session->open_packet, session->open_packet_len);
if (rc == PACKET_EAGAIN) {
libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block sending channel-open request", 0);
return NULL;
}
else if (rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel-open request", 0);
goto channel_error;
}
session->open_state = libssh2_NB_state_sent;
}
if (session->open_state == libssh2_NB_state_sent) {
rc = libssh2_packet_requirev_ex(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 == PACKET_EAGAIN) {
libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block", 0);
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_DBG_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) {
libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, "Channel open failure", 0);
}
}
channel_error:
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);
if (session->open_channel->next) {
session->open_channel->next->prev = session->open_channel->prev;
}
if (session->open_channel->prev) {
session->open_channel->prev->next = session->open_channel->next;
}
if (session->channels.head == session->open_channel) {
session->channels.head = session->open_channel->next;
}
if (session->channels.tail == session->open_channel) {
session->channels.tail = session->open_channel->prev;
}
/* Clear out packets meant for this channel */
libssh2_htonu32(channel_id, session->open_channel->local.id);
while ((libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_DATA, &session->open_data, &session->open_data_len,
1, channel_id, 4, 0) >= 0) ||
(libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_EXTENDED_DATA, &session->open_data, &session->open_data_len,
1, channel_id, 4, 0) >= 0)) {
LIBSSH2_FREE(session, session->open_data);
session->open_data = NULL;
}
/* Free any state variables still holding data */
if (session->open_channel->write_packet) {
LIBSSH2_FREE(session, session->open_channel->write_packet);
session->open_channel->write_packet = NULL;
}
LIBSSH2_FREE(session, session->open_channel);
session->open_channel = NULL;
}
session->open_state = libssh2_NB_state_idle;
return NULL;
}
/* }}} */
/* {{{ 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 *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_DBG_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", 0);
return NULL;
}
libssh2_htonu32(s, session->direct_host_len); s += 4;
memcpy(s, host, session->direct_host_len); s += session->direct_host_len;
libssh2_htonu32(s, port); s += 4;
libssh2_htonu32(s, session->direct_shost_len); s += 4;
memcpy(s, shost, session->direct_shost_len); s += session->direct_shost_len;
libssh2_htonu32(s, sport); s += 4;
session->direct_state = libssh2_NB_state_created;
}
channel = libssh2_channel_open_ex(session, "direct-tcpip", sizeof("direct-tcpip") - 1, LIBSSH2_CHANNEL_WINDOW_DEFAULT,
LIBSSH2_CHANNEL_PACKET_DEFAULT, (char *)session->direct_message, session->direct_message_len);
if (!channel) {
if (libssh2_session_last_errno(session) == LIBSSH2_ERROR_EAGAIN) {
/* The error code is still set to LIBSSH2_ERROR_EAGAIN */
return NULL;
} else {
LIBSSH2_FREE(session, session->direct_message);
session->direct_message = NULL;
return NULL;
}
}
LIBSSH2_FREE(session, session->direct_message);
session->direct_message = NULL;
return channel;
}
/* }}} */
/* {{{ libssh2_channel_forward_listen_ex
* Bind a port on the remote host and listen for connections
*/
LIBSSH2_API LIBSSH2_LISTENER *
libssh2_channel_forward_listen_ex(LIBSSH2_SESSION *session, const char *host, int port, int *bound_port, int queue_maxsize)
{
unsigned char *s, *data;
static const unsigned char reply_codes[3] = { SSH_MSG_REQUEST_SUCCESS, SSH_MSG_REQUEST_FAILURE, 0 };
unsigned long data_len;
int rc;
if (session->fwdLstn_state == libssh2_NB_state_idle) {
session->fwdLstn_host_len = (host ? strlen(host) : (sizeof("0.0.0.0") - 1));
/* 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_DBG_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, "Unable to allocate memeory for setenv packet", 0);
return NULL;
}
*(s++) = SSH_MSG_GLOBAL_REQUEST;
libssh2_htonu32(s, sizeof("tcpip-forward") - 1); s += 4;
memcpy(s, "tcpip-forward", sizeof("tcpip-forward") - 1); s += sizeof("tcpip-forward") - 1;
*(s++) = 0x01; /* want_reply */
libssh2_htonu32(s, session->fwdLstn_host_len); s += 4;
memcpy(s, host ? host : "0.0.0.0", session->fwdLstn_host_len); s += session->fwdLstn_host_len;
libssh2_htonu32(s, port); s += 4;
session->fwdLstn_state = libssh2_NB_state_created;
}
if (session->fwdLstn_state == libssh2_NB_state_created) {
rc = libssh2_packet_write(session, session->fwdLstn_packet, session->fwdLstn_packet_len);
if (rc == PACKET_EAGAIN) {
libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block sending global-request packet for forward listen request", 0);
return NULL;
}
else if (rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send global-request packet for forward listen request", 0);
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) {
rc = libssh2_packet_requirev_ex(session, reply_codes, &data, &data_len, 0, NULL, 0,
&session->fwdLstn_packet_requirev_state);
if (rc == PACKET_EAGAIN) {
libssh2_error(session, LIBSSH2_ERROR_EAGAIN, "Would block", 0);
return NULL;
}
else if (rc) {
libssh2_error(session, LIBSSH2_ERROR_PROTO, "Unknown", 0);
session->fwdLstn_state = libssh2_NB_state_idle;
return NULL;
}
if (data[0] == SSH_MSG_REQUEST_SUCCESS) {
LIBSSH2_LISTENER *listener;
listener = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_LISTENER));
if (!listener) {
libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for listener queue", 0);
LIBSSH2_FREE(session, data);
session->fwdLstn_state = libssh2_NB_state_idle;
return NULL;
}
memset(listener, 0, sizeof(LIBSSH2_LISTENER));
listener->session = session;
listener->host = LIBSSH2_ALLOC(session, session->fwdLstn_host_len + 1);
if (!listener->host) {
libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for listener queue", 0);
LIBSSH2_FREE(session, listener);
LIBSSH2_FREE(session, data);
session->fwdLstn_state = libssh2_NB_state_idle;
return NULL;
}
memcpy(listener->host, host ? host : "0.0.0.0", session->fwdLstn_host_len);
listener->host[session->fwdLstn_host_len] = 0;
if (data_len >= 5 && !port) {
listener->port = libssh2_ntohu32(data + 1);
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Dynamic tcpip-forward port allocated: %d", listener->port);
} else {
listener->port = port;
}
listener->queue_size = 0;
listener->queue_maxsize = queue_maxsize;
listener->next = session->listeners;
listener->prev = NULL;
if (session->listeners) {
session->listeners->prev = listener;
}
session->listeners = listener;
if (bound_port) {
*bound_port = listener->port;
}
LIBSSH2_FREE(session, data);
session->fwdLstn_state = libssh2_NB_state_idle;
return listener;
}
if (data[0] == SSH_MSG_REQUEST_FAILURE) {
LIBSSH2_FREE(session, data);
libssh2_error(session, LIBSSH2_ERROR_REQUEST_DENIED, "Unable to complete request for forward-listen", 0);
session->fwdLstn_state = libssh2_NB_state_idle;
return NULL;
}
}
session->fwdLstn_state = libssh2_NB_state_idle;
return NULL;
}
/* }}} */
/* {{{ libssh2_channel_forward_cancel
* Stop listening on a remote port and free the listener
* Toss out any pending (un-accept()ed) connections
*
* Return 0 on success, PACKET_EAGAIN if would block, -1 on error
*/
LIBSSH2_API int libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener)
{
LIBSSH2_SESSION *session = listener->session;
LIBSSH2_CHANNEL *queued = listener->queue;
unsigned char *packet, *s;
unsigned long host_len = strlen(listener->host);
/* 14 = packet_type(1) + request_len(4) + want_replay(1) + host_len(4) + port(4) */
unsigned long packet_len = host_len + 14 + sizeof("cancel-tcpip-forward") - 1;
int rc;
if (listener->chanFwdCncl_state == libssh2_NB_state_idle) {
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Cancelling tcpip-forward session for %s:%d", listener->host, listener->port);
s = packet = LIBSSH2_ALLOC(session, packet_len);
if (!packet) {
libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memeory for setenv packet", 0);
return -1;
}
*(s++) = SSH_MSG_GLOBAL_REQUEST;
libssh2_htonu32(s, sizeof("cancel-tcpip-forward") - 1); s += 4;
memcpy(s, "cancel-tcpip-forward", sizeof("cancel-tcpip-forward") - 1); s += sizeof("cancel-tcpip-forward") - 1;
*(s++) = 0x00; /* want_reply */
libssh2_htonu32(s, host_len); s += 4;
memcpy(s, listener->host, host_len); s += host_len;
libssh2_htonu32(s, listener->port); s += 4;
listener->chanFwdCncl_state = libssh2_NB_state_created;
} else {
packet = listener->chanFwdCncl_data;
}
if (listener->chanFwdCncl_state == libssh2_NB_state_created) {
rc = libssh2_packet_write(session, packet, packet_len);
if (rc == PACKET_EAGAIN) {
listener->chanFwdCncl_data = packet;
}
else if (rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send global-request packet for forward listen request", 0);
LIBSSH2_FREE(session, packet);
listener->chanFwdCncl_state = libssh2_NB_state_idle;
return -1;
}
LIBSSH2_FREE(session, packet);
listener->chanFwdCncl_state = libssh2_NB_state_sent;
}
while (queued) {
LIBSSH2_CHANNEL *next = queued->next;
rc = libssh2_channel_free(queued);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
queued = next;
}
LIBSSH2_FREE(session, listener->host);
if (listener->next) {
listener->next->prev = listener->prev;
}
if (listener->prev) {
listener->prev->next = listener->next;
} else {
session->listeners = listener->next;
}
LIBSSH2_FREE(session, listener);
listener->chanFwdCncl_state = libssh2_NB_state_idle;
return 0;
}
/* }}} */
/* {{{ libssh2_channel_forward_accept
* Accept a connection
*/
LIBSSH2_API LIBSSH2_CHANNEL *
libssh2_channel_forward_accept(LIBSSH2_LISTENER *listener)
{
libssh2pack_t rc;
do {
rc = libssh2_packet_read(listener->session);
if (rc == PACKET_EAGAIN) {
libssh2_error(listener->session, LIBSSH2_ERROR_EAGAIN, "Would block waiting for packet", 0);
return NULL;
}
} while (rc > 0);
if (listener->queue) {
LIBSSH2_SESSION *session = listener->session;
LIBSSH2_CHANNEL *channel;
channel = listener->queue;
listener->queue = listener->queue->next;
if (listener->queue) {
listener->queue->prev = NULL;
}
channel->prev = NULL;
channel->next = session->channels.head;
session->channels.head = channel;
if (channel->next) {
channel->next->prev = channel;
} else {
session->channels.tail = channel;
}
listener->queue_size--;
return channel;
}
return NULL;
}
/* }}} */
/* {{{ libssh2_channel_setenv_ex
* Set an environment variable prior to requesting a shell/program/subsystem
*/
LIBSSH2_API int
libssh2_channel_setenv_ex(LIBSSH2_CHANNEL *channel, char *varname, unsigned int varname_len, const char *value,
unsigned int value_len)
{
LIBSSH2_SESSION *session = channel->session;
unsigned char *s, *data;
static const unsigned char reply_codes[3] = { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 };
unsigned long data_len;
int rc;
if (channel->setenv_state == libssh2_NB_state_idle) {
/* 21 = packet_type(1) + channel_id(4) + request_len(4) +
* request(3)"env" + want_reply(1) + varname_len(4) + value_len(4) */
channel->setenv_packet_len = varname_len + value_len + 21;
/* Zero the whole thing out */
memset(&channel->setenv_packet_requirev_state, 0, sizeof(channel->setenv_packet_requirev_state));
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Setting remote environment variable: %s=%s on channel %lu/%lu",
varname, value, channel->local.id, channel->remote.id);
s = channel->setenv_packet = LIBSSH2_ALLOC(session, channel->setenv_packet_len);
if (!channel->setenv_packet) {
libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memeory for setenv packet", 0);
return -1;
}
*(s++) = SSH_MSG_CHANNEL_REQUEST;
libssh2_htonu32(s, channel->remote.id); s += 4;
libssh2_htonu32(s, sizeof("env") - 1); s += 4;
memcpy(s, "env", sizeof("env") - 1); s += sizeof("env") - 1;
*(s++) = 0x01;
libssh2_htonu32(s, varname_len); s += 4;
memcpy(s, varname, varname_len); s += varname_len;
libssh2_htonu32(s, value_len); s += 4;
memcpy(s, value, value_len); s += value_len;
channel->setenv_state = libssh2_NB_state_created;
}
if (channel->setenv_state == libssh2_NB_state_created) {
rc = libssh2_packet_write(session, channel->setenv_packet, channel->setenv_packet_len);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
else if (rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel-request packet for setenv request", 0);
LIBSSH2_FREE(session, channel->setenv_packet);
channel->setenv_packet = NULL;
channel->setenv_state = libssh2_NB_state_idle;
return -1;
}
LIBSSH2_FREE(session, channel->setenv_packet);
channel->setenv_packet = NULL;
libssh2_htonu32(channel->setenv_local_channel, channel->local.id);
channel->setenv_state = libssh2_NB_state_sent;
}
if (channel->setenv_state == libssh2_NB_state_sent) {
rc = libssh2_packet_requirev_ex(session, reply_codes, &data, &data_len, 1, channel->setenv_local_channel, 4,
&channel->setenv_packet_requirev_state);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
if (rc) {
channel->setenv_state = libssh2_NB_state_idle;
return -1;
}
if (data[0] == SSH_MSG_CHANNEL_SUCCESS) {
LIBSSH2_FREE(session, data);
channel->setenv_state = libssh2_NB_state_idle;
return 0;
}
LIBSSH2_FREE(session, data);
}
libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, "Unable to complete request for channel-setenv", 0);
channel->setenv_state = libssh2_NB_state_idle;
return -1;
}
/* }}} */
/* {{{ libssh2_channel_request_pty_ex
* Duh... Request a PTY
*/
LIBSSH2_API int
libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel, const char *term, unsigned int term_len, const char *modes,
unsigned int modes_len, int width, int height, int width_px, int height_px)
{
LIBSSH2_SESSION *session = channel->session;
unsigned char *s, *data;
static const unsigned char reply_codes[3] = { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 };
unsigned long data_len;
int rc;
if (channel->reqPTY_state == libssh2_NB_state_idle) {
/* 41 = packet_type(1) + channel(4) + pty_req_len(4) + "pty_req"(7) +
* want_reply(1) + term_len(4) + width(4) + height(4) + width_px(4) +
* height_px(4) + modes_len(4) */
channel->reqPTY_packet_len = term_len + modes_len + 41;
/* Zero the whole thing out */
memset(&channel->reqPTY_packet_requirev_state, 0, sizeof(channel->reqPTY_packet_requirev_state));
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Allocating tty on channel %lu/%lu", channel->local.id, channel->remote.id);
s = channel->reqPTY_packet = LIBSSH2_ALLOC(session, channel->reqPTY_packet_len);
if (!channel->reqPTY_packet) {
libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for pty-request", 0);
return -1;
}
*(s++) = SSH_MSG_CHANNEL_REQUEST;
libssh2_htonu32(s, channel->remote.id); s += 4;
libssh2_htonu32(s, sizeof("pty-req") - 1); s += 4;
memcpy(s, "pty-req", sizeof("pty-req") - 1); s += sizeof("pty-req") - 1;
*(s++) = 0x01;
libssh2_htonu32(s, term_len); s += 4;
if (term) {
memcpy(s, term, term_len); s += term_len;
}
libssh2_htonu32(s, width); s += 4;
libssh2_htonu32(s, height); s += 4;
libssh2_htonu32(s, width_px); s += 4;
libssh2_htonu32(s, height_px); s += 4;
libssh2_htonu32(s, modes_len); s += 4;
if (modes) {
memcpy(s, modes, modes_len); s += modes_len;
}
channel->reqPTY_state = libssh2_NB_state_created;
}
if (channel->reqPTY_state == libssh2_NB_state_created) {
rc = libssh2_packet_write(session, channel->reqPTY_packet, channel->reqPTY_packet_len);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
else if (rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send pty-request packet", 0);
LIBSSH2_FREE(session, channel->reqPTY_packet);
channel->reqPTY_packet = NULL;
channel->reqPTY_state = libssh2_NB_state_idle;
return -1;
}
LIBSSH2_FREE(session, channel->reqPTY_packet);
channel->reqPTY_packet = NULL;
libssh2_htonu32(channel->reqPTY_local_channel, channel->local.id);
channel->reqPTY_state = libssh2_NB_state_sent;
}
if (channel->reqPTY_state == libssh2_NB_state_sent) {
rc = libssh2_packet_requirev_ex(session, reply_codes, &data, &data_len, 1, channel->reqPTY_local_channel, 4,
&channel->reqPTY_packet_requirev_state);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
else if (rc) {
channel->reqPTY_state = libssh2_NB_state_idle;
return -1;
}
if (data[0] == SSH_MSG_CHANNEL_SUCCESS) {
LIBSSH2_FREE(session, data);
channel->reqPTY_state = libssh2_NB_state_idle;
return 0;
}
}
LIBSSH2_FREE(session, data);
libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, "Unable to complete request for channel request-pty", 0);
channel->reqPTY_state = libssh2_NB_state_idle;
return -1;
}
/* }}} */
/* Keep this an even number */
#define LIBSSH2_X11_RANDOM_COOKIE_LEN 32
/* {{{ libssh2_channel_x11_req_ex
* Request X11 forwarding
*/
LIBSSH2_API int
libssh2_channel_x11_req_ex(LIBSSH2_CHANNEL *channel, int single_connection, const char *auth_proto, const char *auth_cookie,
int screen_number)
{
LIBSSH2_SESSION *session = channel->session;
unsigned char *s, *data;
static const unsigned char reply_codes[3] = { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 };
unsigned long data_len;
unsigned long proto_len = auth_proto ? strlen(auth_proto) : (sizeof("MIT-MAGIC-COOKIE-1") - 1);
unsigned long cookie_len = auth_cookie ? strlen(auth_cookie) : LIBSSH2_X11_RANDOM_COOKIE_LEN;
int rc;
if (channel->reqX11_state == libssh2_NB_state_idle) {
/* 30 = packet_type(1) + channel(4) + x11_req_len(4) + "x11-req"(7) +
* want_reply(1) + single_cnx(1) + proto_len(4) + cookie_len(4) +
* screen_num(4) */
channel->reqX11_packet_len = proto_len + cookie_len + 30;
/* Zero the whole thing out */
memset(&channel->reqX11_packet_requirev_state, 0,
sizeof(channel->reqX11_packet_requirev_state));
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Requesting x11-req for channel %lu/%lu: single=%d proto=%s cookie=%s screen=%d",
channel->local.id, channel->remote.id, single_connection, auth_proto ? auth_proto : "MIT-MAGIC-COOKIE-1",
auth_cookie ? auth_cookie : "<random>", screen_number);
s = channel->reqX11_packet = LIBSSH2_ALLOC(session, channel->reqX11_packet_len);
if (!channel->reqX11_packet) {
libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for pty-request", 0);
return -1;
}
*(s++) = SSH_MSG_CHANNEL_REQUEST;
libssh2_htonu32(s, channel->remote.id); s += 4;
libssh2_htonu32(s, sizeof("x11-req") - 1); s += 4;
memcpy(s, "x11-req", sizeof("x11-req") - 1); s += sizeof("x11-req") - 1;
*(s++) = 0x01; /* want_reply */
*(s++) = single_connection ? 0x01 : 0x00;
libssh2_htonu32(s, proto_len); s += 4;
memcpy(s, auth_proto ? auth_proto : "MIT-MAGIC-COOKIE-1", proto_len);
s += proto_len;
libssh2_htonu32(s, cookie_len); s += 4;
if (auth_cookie) {
memcpy(s, auth_cookie, cookie_len);
} else {
int i;
unsigned char buffer[LIBSSH2_X11_RANDOM_COOKIE_LEN / 2];
libssh2_random(buffer, LIBSSH2_X11_RANDOM_COOKIE_LEN / 2);
for (i = 0; i < (LIBSSH2_X11_RANDOM_COOKIE_LEN / 2); i++) {
snprintf((char *)s + (i * 2), 2, "%02X", buffer[i]);
}
}
s += cookie_len;
libssh2_htonu32(s, screen_number); s += 4;
channel->reqX11_state = libssh2_NB_state_created;
}
if (channel->reqX11_state == libssh2_NB_state_created) {
rc = libssh2_packet_write(session, channel->reqX11_packet, channel->reqX11_packet_len);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
if (rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send x11-req packet", 0);
LIBSSH2_FREE(session, channel->reqX11_packet);
channel->reqX11_packet = NULL;
channel->reqX11_state = libssh2_NB_state_idle;
return -1;
}
LIBSSH2_FREE(session, channel->reqX11_packet);
channel->reqX11_packet = NULL;
libssh2_htonu32(channel->reqX11_local_channel, channel->local.id);
channel->reqX11_state = libssh2_NB_state_sent;
}
if (channel->reqX11_state == libssh2_NB_state_sent) {
rc = libssh2_packet_requirev_ex(session, reply_codes, &data, &data_len, 1, channel->reqX11_local_channel, 4,
&channel->reqX11_packet_requirev_state);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
else if (rc) {
channel->reqX11_state = libssh2_NB_state_idle;
return -1;
}
if (data[0] == SSH_MSG_CHANNEL_SUCCESS) {
LIBSSH2_FREE(session, data);
channel->reqX11_state = libssh2_NB_state_idle;
return 0;
}
}
LIBSSH2_FREE(session, data);
libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, "Unable to complete request for channel x11-req", 0);
return -1;
}
/* }}} */
/* {{{ libssh2_channel_process_startup
* Primitive for libssh2_channel_(shell|exec|subsystem)
*/
LIBSSH2_API int
libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, const char *request, unsigned int request_len, const char *message,
unsigned int message_len)
{
LIBSSH2_SESSION *session = channel->session;
unsigned char *s, *data;
static const unsigned char reply_codes[3] = { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 };
unsigned long data_len;
libssh2pack_t rc;
if (channel->process_state == libssh2_NB_state_idle) {
/* 10 = packet_type(1) + channel(4) + request_len(4) + want_reply(1) */
channel->process_packet_len = request_len + 10;
/* Zero the whole thing out */
memset(&channel->process_packet_requirev_state, 0, sizeof(channel->process_packet_requirev_state));
if (message) {
channel->process_packet_len += message_len + 4;
}
_libssh2_debug(session, LIBSSH2_DBG_CONN, "starting request(%s) on channel %lu/%lu, message=%s",
request, channel->local.id, channel->remote.id, message);
s = channel->process_packet = LIBSSH2_ALLOC(session, channel->process_packet_len);
if (!channel->process_packet) {
libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for channel-process request", 0);
return -1;
}
*(s++) = SSH_MSG_CHANNEL_REQUEST;
libssh2_htonu32(s, channel->remote.id); s += 4;
libssh2_htonu32(s, request_len); s += 4;
memcpy(s, request, request_len); s += request_len;
*(s++) = 0x01;
if (message) {
libssh2_htonu32(s, message_len); s += 4;
memcpy(s, message, message_len); s += message_len;
}
channel->process_state = libssh2_NB_state_created;
}
if (channel->process_state == libssh2_NB_state_created) {
rc = libssh2_packet_write(session, channel->process_packet, channel->process_packet_len);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
else if (rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel request", 0);
LIBSSH2_FREE(session, channel->process_packet);
channel->process_packet = NULL;
channel->process_state = libssh2_NB_state_idle;
return -1;
}
LIBSSH2_FREE(session, channel->process_packet);
channel->process_packet = NULL;
libssh2_htonu32(channel->process_local_channel, channel->local.id);
channel->process_state = libssh2_NB_state_sent;
}
if (channel->process_state == libssh2_NB_state_sent) {
rc = libssh2_packet_requirev_ex(session, reply_codes, &data, &data_len, 1, channel->process_local_channel, 4,
&channel->process_packet_requirev_state);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
else if (rc) {
channel->process_state = libssh2_NB_state_idle;
return -1;
}
if (data[0] == SSH_MSG_CHANNEL_SUCCESS) {
LIBSSH2_FREE(session, data);
channel->process_state = libssh2_NB_state_idle;
return 0;
}
}
LIBSSH2_FREE(session, data);
libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, "Unable to complete request for channel-process-startup", 0);
channel->process_state = libssh2_NB_state_idle;
return -1;
}
/* }}} */
/* {{{ libssh2_channel_set_blocking
* Set a channel's blocking mode on or off, similar to a socket's
* fcntl(fd, F_SETFL, O_NONBLOCK); type command
*/
LIBSSH2_API void
libssh2_channel_set_blocking(LIBSSH2_CHANNEL *channel, int blocking)
{
(void)_libssh2_session_set_blocking(channel->session, blocking);
}
/* }}} */
/* {{{ libssh2_channel_flush_ex
* Flush data from one (or all) stream
* Returns number of bytes flushed, or -1 on failure
*/
LIBSSH2_API int libssh2_channel_flush_ex(LIBSSH2_CHANNEL *channel, int streamid)
{
LIBSSH2_PACKET *packet = channel->session->packets.head;
if (channel->flush_state == libssh2_NB_state_idle) {
channel->flush_refund_bytes = 0;
channel->flush_flush_bytes = 0;
while (packet) {
LIBSSH2_PACKET *next = packet->next;
unsigned char packet_type = packet->data[0];
if (((packet_type == SSH_MSG_CHANNEL_DATA) || (packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA)) &&
(libssh2_ntohu32(packet->data + 1) == channel->local.id)) {
/* It's our channel at least */
long packet_stream_id = (packet_type == SSH_MSG_CHANNEL_DATA) ? 0 : libssh2_ntohu32(packet->data + 5);
if ((streamid == LIBSSH2_CHANNEL_FLUSH_ALL) || ((packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA) &&
((streamid == LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA) || (streamid == packet_stream_id))) ||
((packet_type == SSH_MSG_CHANNEL_DATA) && (streamid == 0))) {
int bytes_to_flush = packet->data_len - packet->data_head;
_libssh2_debug(channel->session, LIBSSH2_DBG_CONN,
"Flushing %d bytes of data from stream %lu on channel %lu/%lu", bytes_to_flush,
packet_stream_id, channel->local.id, channel->remote.id);
/* It's one of the streams we wanted to flush */
channel->flush_refund_bytes += packet->data_len - 13;
channel->flush_flush_bytes += bytes_to_flush;
LIBSSH2_FREE(channel->session, packet->data);
if (packet->prev) {
packet->prev->next = packet->next;
} else {
channel->session->packets.head = packet->next;
}
if (packet->next) {
packet->next->prev = packet->prev;
} else {
channel->session->packets.tail = packet->prev;
}
LIBSSH2_FREE(channel->session, packet);
}
}
packet = next;
}
channel->flush_state = libssh2_NB_state_created;
}
if (channel->flush_refund_bytes) {
int rc;
rc = libssh2_channel_receive_window_adjust(channel, channel->flush_refund_bytes, 0);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
}
channel->flush_state = libssh2_NB_state_idle;
return channel->flush_flush_bytes;
}
/* }}} */
/* {{{ libssh2_channel_get_exit_status
* Return the channel's program exit status
*/
LIBSSH2_API int libssh2_channel_get_exit_status(LIBSSH2_CHANNEL* channel)
{
return channel->exit_status;
}
/* }}} */
/* {{{ libssh2_channel_receive_window_adjust
* Adjust the receive window for a channel by adjustment bytes
* If the amount to be adjusted is less than LIBSSH2_CHANNEL_MINADJUST and
* force is 0 the adjustment amount will be queued for a later packet
*
* Returns the new size of the receive window (as understood by remote end)
*/
LIBSSH2_API unsigned long
libssh2_channel_receive_window_adjust(LIBSSH2_CHANNEL *channel, unsigned long adjustment, unsigned char force)
{
int rc;
if (channel->adjust_state == libssh2_NB_state_idle) {
if (!force && (adjustment + channel->adjust_queue < LIBSSH2_CHANNEL_MINADJUST)) {
_libssh2_debug(channel->session, LIBSSH2_DBG_CONN,
"Queueing %lu bytes for receive window adjustment for channel %lu/%lu",
adjustment, channel->local.id, channel->remote.id);
channel->adjust_queue += adjustment;
return channel->remote.window_size;
}
if (!adjustment && !channel->adjust_queue) {
return channel->remote.window_size;
}
adjustment += channel->adjust_queue;
channel->adjust_queue = 0;
/* Adjust the window based on the block we just freed */
channel->adjust_adjust[0] = SSH_MSG_CHANNEL_WINDOW_ADJUST;
libssh2_htonu32(channel->adjust_adjust + 1, channel->remote.id);
libssh2_htonu32(channel->adjust_adjust + 5, adjustment);
_libssh2_debug(channel->session, LIBSSH2_DBG_CONN, "Adjusting window %lu bytes for data flushed from channel %lu/%lu",
adjustment, channel->local.id, channel->remote.id);
channel->adjust_state = libssh2_NB_state_created;
}
rc = libssh2_packet_write(channel->session, channel->adjust_adjust, 9);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
else if (rc) {
libssh2_error(channel->session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send transfer-window adjustment packet, deferring",
0);
channel->adjust_queue = adjustment;
} else {
channel->remote.window_size += adjustment;
}
return channel->remote.window_size;
}
/* }}} */
/* {{{ libssh2_channel_handle_extended_data
* How should extended data look to the calling app?
* Keep it in separate channels[_read() _read_stdder()]? (NORMAL)
* Merge the extended data to the standard data? [everything via _read()]? (MERGE)
* Ignore it entirely [toss out packets as they come in]? (IGNORE)
*/
LIBSSH2_API void
libssh2_channel_handle_extended_data(LIBSSH2_CHANNEL *channel, int ignore_mode)
{
while (libssh2_channel_handle_extended_data2(channel, ignore_mode) == PACKET_EAGAIN);
}
LIBSSH2_API int
libssh2_channel_handle_extended_data2(LIBSSH2_CHANNEL *channel, int ignore_mode)
{
if (channel->extData2_state == libssh2_NB_state_idle) {
_libssh2_debug(channel->session, LIBSSH2_DBG_CONN, "Setting channel %lu/%lu handle_extended_data mode to %d",
channel->local.id, channel->remote.id, ignore_mode);
channel->remote.extended_data_ignore_mode = ignore_mode;
channel->extData2_state = libssh2_NB_state_created;
}
if (channel->extData2_state == libssh2_NB_state_idle) {
if (ignore_mode == LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE) {
if (libssh2_channel_flush_ex(channel, LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA) == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
}
}
channel->extData2_state = libssh2_NB_state_idle;
return 0;
}
/* }}} */
/*
* {{{ _libssh2_channel_read_ex
* Read data from a channel blocking or non-blocking depending on set state
*
* When this is done non-blocking, it is important to not return 0 until the
* currently read channel is complete. If we read stuff from the wire but it
* was no payload data to fill in the buffer with, we MUST make sure to return
* PACKET_EAGAIN.
*/
LIBSSH2_API ssize_t
libssh2_channel_read_ex(LIBSSH2_CHANNEL *channel, int stream_id, char *buf, size_t buflen)
{
LIBSSH2_SESSION *session = channel->session;
libssh2pack_t rc = 0;
if (channel->read_state == libssh2_NB_state_idle) {
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Attempting to read %d bytes from channel %lu/%lu stream #%d",
(int)buflen, channel->local.id, channel->remote.id, stream_id);
/* process all incoming packets */
do {
if (libssh2_waitsocket(session, 0) > 0) {
rc = libssh2_packet_read(session);
} else {
/* Set for PACKET_EAGAIN so we continue */
rc = PACKET_EAGAIN;
}
} while (rc > 0);
if ((rc < 0) && (rc != PACKET_EAGAIN)) {
fprintf(stderr, "return rc = %d\n", rc);
return rc;
}
channel->read_bytes_read = 0;
channel->read_block = 0;
channel->read_packet = session->packets.head;
channel->read_state = libssh2_NB_state_created;
}
/*
* =============================== NOTE ===============================
* I know this is very ugly and not a really good use of "goto", but
* this case statement would be even uglier to do it any other way
*/
if (channel->read_state == libssh2_NB_state_jump1) {
goto channel_read_ex_point1;
}
rc = 0;
do {
if (channel->read_block) {
/* in the second lap and onwards, do this */
rc = libssh2_packet_read(session);
channel->read_packet = session->packets.head;
}
if (rc < 0) {
if (rc != PACKET_EAGAIN) {
channel->read_state = libssh2_NB_state_idle;
}
/* no packets available */
return rc;
}
while (channel->read_packet && (channel->read_bytes_read < (int)buflen)) {
/* In case packet gets destroyed during this iteration */
channel->read_next = channel->read_packet->next;
channel->read_local_id = libssh2_ntohu32(channel->read_packet->data + 1);
/*
* Either we asked for a specific extended data stream
* (and data was available),
* or the standard stream (and data was available),
* or the standard stream with extended_data_merge
* enabled and data was available
*/
if ((stream_id && (channel->read_packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) &&
(channel->local.id == channel->read_local_id) &&
(stream_id == (int)libssh2_ntohu32(channel->read_packet->data + 5))) ||
(!stream_id && (channel->read_packet->data[0] == SSH_MSG_CHANNEL_DATA) &&
(channel->local.id == channel->read_local_id)) ||
(!stream_id && (channel->read_packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) &&
(channel->local.id == channel->read_local_id) &&
(channel->remote.extended_data_ignore_mode == LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE))) {
channel->read_want = buflen - channel->read_bytes_read;
channel->read_unlink_packet = 0;
if (channel->read_want >= (int)(channel->read_packet->data_len - channel->read_packet->data_head)) {
channel->read_want = channel->read_packet->data_len - channel->read_packet->data_head;
channel->read_unlink_packet = 1;
}
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Reading %d of buffered data from %lu/%lu/%d",
channel->read_want, channel->local.id, channel->remote.id, stream_id);
memcpy(buf + channel->read_bytes_read, channel->read_packet->data + channel->read_packet->data_head,
channel->read_want);
channel->read_packet->data_head += channel->read_want;
channel->read_bytes_read += channel->read_want;
if (channel->read_unlink_packet) {
if (channel->read_packet->prev) {
channel->read_packet->prev->next = channel->read_packet->next;
} else {
session->packets.head = channel->read_packet->next;
}
if (channel->read_packet->next) {
channel->read_packet->next->prev = channel->read_packet->prev;
} else {
session->packets.tail = channel->read_packet->prev;
}
LIBSSH2_FREE(session, channel->read_packet->data);
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Unlinking empty packet buffer from channel %lu/%lu",
channel->local.id, channel->remote.id);
channel_read_ex_point1:
channel->read_state = libssh2_NB_state_jump1;
rc = libssh2_channel_receive_window_adjust(channel, channel->read_packet->data_len - (stream_id ? 13 : 9), 0);
if (rc == PACKET_EAGAIN){
return PACKET_EAGAIN;
}
channel->read_state = libssh2_NB_state_created;
LIBSSH2_FREE(session, channel->read_packet);
channel->read_packet = NULL;
}
}
channel->read_packet = channel->read_next;
}
channel->read_block = 1;
} while ((channel->read_bytes_read == 0) && !channel->remote.close);
channel->read_state = libssh2_NB_state_idle;
if (channel->read_bytes_read == 0) {
if (channel->session->socket_block) {
libssh2_error(session, LIBSSH2_ERROR_CHANNEL_CLOSED, "Remote end has closed this channel", 0);
} else {
/*
* when non-blocking, we must return PACKET_EAGAIN if we haven't
* completed reading the channel
*/
if (!libssh2_channel_eof(channel)) {
return PACKET_EAGAIN;
}
}
}
channel->read_state = libssh2_NB_state_idle;
return channel->read_bytes_read;
}
/* }}} */
/* {{{ libssh2_channel_write_ex
* Send data to a channel
*/
LIBSSH2_API ssize_t libssh2_channel_write_ex(LIBSSH2_CHANNEL *channel, int stream_id, const char *buf, size_t buflen)
{
LIBSSH2_SESSION *session = channel->session;
libssh2pack_t rc;
if (channel->write_state == libssh2_NB_state_idle) {
channel->write_bufwrote = 0;
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Writing %d bytes on channel %lu/%lu, stream #%d",
(int)buflen, channel->local.id, channel->remote.id, stream_id);
if (channel->local.close) {
libssh2_error(session, LIBSSH2_ERROR_CHANNEL_CLOSED, "We've already closed this channel", 0);
return -1;
}
if (channel->local.eof) {
libssh2_error(session, LIBSSH2_ERROR_CHANNEL_EOF_SENT, "EOF has already been sight, data might be ignored", 0);
}
if (!channel->session->socket_block &&
(channel->local.window_size <= 0)) {
/* Can't write anything */
return 0;
}
/* [13] 9 = packet_type(1) + channelno(4) [ + streamid(4) ] + buflen(4) */
channel->write_packet_len = buflen + (stream_id ? 13 : 9);
channel->write_packet = LIBSSH2_ALLOC(session, channel->write_packet_len);
if (!channel->write_packet) {
libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocte space for data transmission packet", 0);
return -1;
}
channel->write_state = libssh2_NB_state_allocated;
}
while (buflen > 0) {
if (channel->write_state == libssh2_NB_state_allocated) {
channel->write_bufwrite = buflen;
channel->write_s = channel->write_packet;
*(channel->write_s++) = stream_id ? SSH_MSG_CHANNEL_EXTENDED_DATA : SSH_MSG_CHANNEL_DATA;
libssh2_htonu32(channel->write_s, channel->remote.id); channel->write_s += 4;
if (stream_id) {
libssh2_htonu32(channel->write_s, stream_id); channel->write_s += 4;
}
/* twiddle our thumbs until there's window space available */
while (channel->local.window_size <= 0) {
/* Don't worry -- This is never hit unless it's a
blocking channel anyway */
rc = libssh2_packet_read(session);
if (rc < 0) {
/* Error or EAGAIN occurred, disconnect? */
if (rc != PACKET_EAGAIN) {
channel->write_state = libssh2_NB_state_idle;
}
return rc;
}
if ((rc == 0) && (session->socket_block == 0)) {
/*
* if rc == 0 and in non-blocking, then fake EAGAIN
* to prevent busyloops until data arriaves on the network
* which seemed like a very bad idea
*/
return PACKET_EAGAIN;
}
}
/* Don't exceed the remote end's limits */
/* REMEMBER local means local as the SOURCE of the data */
if (channel->write_bufwrite > channel->local.window_size) {
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Splitting write block due to %lu byte window_size on %lu/%lu/%d",
channel->local.window_size, channel->local.id, channel->remote.id, stream_id);
channel->write_bufwrite = channel->local.window_size;
}
if (channel->write_bufwrite > channel->local.packet_size) {
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Splitting write block due to %lu byte packet_size on %lu/%lu/%d",
channel->local.packet_size, channel->local.id, channel->remote.id, stream_id);
channel->write_bufwrite = channel->local.packet_size;
}
libssh2_htonu32(channel->write_s, channel->write_bufwrite); channel->write_s += 4;
memcpy(channel->write_s, buf, channel->write_bufwrite); channel->write_s += channel->write_bufwrite;
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Sending %d bytes on channel %lu/%lu, stream_id=%d",
(int)channel->write_bufwrite, channel->local.id, channel->remote.id, stream_id);
channel->write_state = libssh2_NB_state_created;
}
if (channel->write_state == libssh2_NB_state_created) {
rc = libssh2_packet_write(session, channel->write_packet, channel->write_s - channel->write_packet);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
else if (rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel data", 0);
LIBSSH2_FREE(session, channel->write_packet);
channel->write_packet = NULL;
channel->write_state = libssh2_NB_state_idle;
return -1;
}
/* Shrink local window size */
channel->local.window_size -= channel->write_bufwrite;
/* Adjust buf for next iteration */
buflen -= channel->write_bufwrite;
buf += channel->write_bufwrite;
channel->write_bufwrote += channel->write_bufwrite;
channel->write_state = libssh2_NB_state_allocated;
/*
* Not sure this is still wanted
if (!channel->session->socket_block) {
break;
}
*/
}
}
LIBSSH2_FREE(session, channel->write_packet);
channel->write_packet = NULL;
channel->write_state = libssh2_NB_state_idle;
return channel->write_bufwrote;
}
/* }}} */
/* {{{ libssh2_channel_send_eof
* Send EOF on channel
*/
LIBSSH2_API int libssh2_channel_send_eof(LIBSSH2_CHANNEL *channel)
{
LIBSSH2_SESSION *session = channel->session;
unsigned char packet[5]; /* packet_type(1) + channelno(4) */
int rc;
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Sending EOF on channel %lu/%lu", channel->local.id, channel->remote.id);
packet[0] = SSH_MSG_CHANNEL_EOF;
libssh2_htonu32(packet + 1, channel->remote.id);
rc = libssh2_packet_write(session, packet, 5);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
else if (rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send EOF on channel", 0);
return -1;
}
channel->local.eof = 1;
return 0;
}
/* }}} */
/* {{{ libssh2_channel_eof
* Read channel's eof status
*/
LIBSSH2_API int libssh2_channel_eof(LIBSSH2_CHANNEL *channel)
{
LIBSSH2_SESSION *session = channel->session;
LIBSSH2_PACKET *packet = session->packets.head;
while (packet) {
if (((packet->data[0] == SSH_MSG_CHANNEL_DATA) || (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA)) &&
(channel->local.id == libssh2_ntohu32(packet->data + 1))) {
/* There's data waiting to be read yet, mask the EOF status */
return 0;
}
packet = packet->next;
}
return channel->remote.eof;
}
/* }}} */
/* {{{ libssh2_channel_wait_closed
* Awaiting channel EOF
*/
LIBSSH2_API int libssh2_channel_wait_eof(LIBSSH2_CHANNEL *channel)
{
LIBSSH2_SESSION* session = channel->session;
int rc;
if (channel->wait_eof_state == libssh2_NB_state_idle) {
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Awaiting close of channel %lu/%lu", channel->local.id, channel->remote.id);
channel->wait_eof_state = libssh2_NB_state_created;
}
/*
* While channel is not eof, read more packets from the network.
* Either the EOF will be set or network timeout will occur.
*/
do {
if (channel->remote.eof) {
break;
}
rc = libssh2_packet_read(session);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
else if (rc < 0) {
channel->wait_eof_state = libssh2_NB_state_idle;
return -1;
}
} while (1);
channel->wait_eof_state = libssh2_NB_state_idle;
return 0;
}
/* }}} */
/* {{{ libssh2_channel_close
* Close a channel
*/
LIBSSH2_API int libssh2_channel_close(LIBSSH2_CHANNEL *channel)
{
LIBSSH2_SESSION *session = channel->session;
int rc = 0;
int retcode;
if (channel->local.close) {
/* Already closed, act like we sent another close,
* even though we didn't... shhhhhh */
channel->close_state = libssh2_NB_state_idle;
return 0;
}
if (channel->close_state == libssh2_NB_state_idle) {
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Closing channel %lu/%lu", channel->local.id, channel->remote.id);
if (channel->close_cb) {
LIBSSH2_CHANNEL_CLOSE(session, channel);
}
channel->local.close = 1;
channel->close_packet[0] = SSH_MSG_CHANNEL_CLOSE;
libssh2_htonu32(channel->close_packet + 1, channel->remote.id);
channel->close_state = libssh2_NB_state_created;
}
if (channel->close_state == libssh2_NB_state_created) {
retcode = libssh2_packet_write(session, channel->close_packet, 5);
if (retcode == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
else if (retcode) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send close-channel request", 0);
channel->close_state = libssh2_NB_state_idle;
return -1;
}
channel->close_state = libssh2_NB_state_sent;
}
if (channel->close_state == libssh2_NB_state_sent) {
/* We must wait for the remote SSH_MSG_CHANNEL_CLOSE message */
if (!channel->remote.close) {
libssh2pack_t ret;
do {
ret = libssh2_packet_read(session);
if (ret == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
else if (ret < 0) {
rc = -1;
}
} while ((ret != SSH_MSG_CHANNEL_CLOSE) && (rc == 0));
}
}
channel->close_state = libssh2_NB_state_idle;
return rc;
}
/* }}} */
/* {{{ libssh2_channel_wait_closed
* Awaiting channel close after EOF
*/
LIBSSH2_API int libssh2_channel_wait_closed(LIBSSH2_CHANNEL *channel)
{
LIBSSH2_SESSION* session = channel->session;
int rc;
if (!libssh2_channel_eof(channel)) {
libssh2_error(session, LIBSSH2_ERROR_INVAL, "libssh2_channel_wait_closed() invoked when channel is not in EOF state", 0);
return -1;
}
if (channel->wait_closed_state == libssh2_NB_state_idle) {
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Awaiting close of channel %lu/%lu", channel->local.id, channel->remote.id);
channel->wait_closed_state = libssh2_NB_state_created;
}
/*
* While channel is not closed, read more packets from the network.
* Either the channel will be closed or network timeout will occur.
*/
do {
if (!channel->remote.close) {
break;
}
rc = libssh2_packet_read(session);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
else if (rc <= 0) {
break;
}
} while (1);
channel->wait_closed_state = libssh2_NB_state_idle;
return 1;
}
/* }}} */
/* {{{ libssh2_channel_free
* Make sure a channel is closed, then remove the channel from the session
* and free its resource(s)
*
* Returns 0 on success, -1 on failure
*/
LIBSSH2_API int libssh2_channel_free(LIBSSH2_CHANNEL *channel)
{
LIBSSH2_SESSION *session = channel->session;
unsigned char channel_id[4];
unsigned char *data;
unsigned long data_len;
int rc;
if (channel->free_state == libssh2_NB_state_idle) {
_libssh2_debug(session, LIBSSH2_DBG_CONN, "Freeing channel %lu/%lu resources", channel->local.id, channel->remote.id);
channel->free_state = libssh2_NB_state_created;
}
/* Allow channel freeing even when the socket has lost its connection */
if (!channel->local.close && (session->socket_state == LIBSSH2_SOCKET_CONNECTED)) {
while ((rc = libssh2_channel_close(channel)) == PACKET_EAGAIN);
if (rc == PACKET_EAGAIN) {
return PACKET_EAGAIN;
}
else if (rc) {
channel->free_state = libssh2_NB_state_idle;
return -1;
}
}
channel->free_state = libssh2_NB_state_idle;
/*
* channel->remote.close *might* not be set yet, Well...
* We've sent the close packet, what more do you want?
* Just let packet_add ignore it when it finally arrives
*/
/* Clear out packets meant for this channel */
libssh2_htonu32(channel_id, channel->local.id);
while ((libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_DATA, &data, &data_len, 1, channel_id, 4, 0) >= 0) ||
(libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_EXTENDED_DATA, &data, &data_len, 1, channel_id, 4, 0) >= 0)) {
LIBSSH2_FREE(session, data);
}
/* free "channel_type" */
if (channel->channel_type) {
LIBSSH2_FREE(session, channel->channel_type);
}
/* Unlink from channel brigade */
if (channel->prev) {
channel->prev->next = channel->next;
} else {
session->channels.head = channel->next;
}
if (channel->next) {
channel->next->prev = channel->prev;
} else {
session->channels.tail = channel->prev;
}
/*
* Make sure all memory used in the state variables are free
*/
if (channel->setenv_packet) {
LIBSSH2_FREE(session, channel->setenv_packet);
}
if (channel->reqPTY_packet) {
LIBSSH2_FREE(session, channel->reqPTY_packet);
}
if (channel->reqX11_packet) {
LIBSSH2_FREE(session, channel->reqX11_packet);
}
if (channel->process_packet) {
LIBSSH2_FREE(session, channel->process_packet);
}
if (channel->write_packet) {
LIBSSH2_FREE(session, channel->write_packet);
}
LIBSSH2_FREE(session, channel);
return 0;
}
/* }}} */
/* {{{ libssh2_channel_window_read_ex
* Check the status of the read window
* Returns the number of bytes which the remote end may send without overflowing the window limit
* read_avail (if passed) will be populated with the number of bytes actually available to be read
* window_size_initial (if passed) will be populated with the window_size_initial as defined by the channel_open request
*/
LIBSSH2_API unsigned long libssh2_channel_window_read_ex(LIBSSH2_CHANNEL *channel, unsigned long *read_avail, unsigned long *window_size_initial)
{
if (window_size_initial) {
*window_size_initial = channel->remote.window_size_initial;
}
if (read_avail) {
unsigned long bytes_queued = 0;
LIBSSH2_PACKET *packet = channel->session->packets.head;
while (packet) {
unsigned char packet_type = packet->data[0];
if (((packet_type == SSH_MSG_CHANNEL_DATA) || (packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA)) &&
(libssh2_ntohu32(packet->data + 1) == channel->local.id)) {
bytes_queued += packet->data_len - packet->data_head;
}
packet = packet->next;
}
*read_avail = bytes_queued;
}
return channel->remote.window_size;
}
/* }}} */
/* {{{ libssh2_channel_window_write_ex
* Check the status of the write window
* Returns the number of bytes which may be safely writen on the channel without blocking
* window_size_initial (if passed) will be populated with the size of the initial window as defined by the channel_open request
*/
LIBSSH2_API unsigned long libssh2_channel_window_write_ex(LIBSSH2_CHANNEL *channel, unsigned long *window_size_initial)
{
if (window_size_initial) {
/* For locally initiated channels this is very often 0, so it's not *that* useful as information goes */
*window_size_initial = channel->local.window_size_initial;
}
return channel->local.window_size;
}
/* }}} */