| /* Copyright (c) 2004-2007 Sara Golemon <[email protected]> |
| * Copyright (c) 2005 Mikhail Gusarov <[email protected]> |
| * Copyright (c) 2008-2019 by Daniel Stenberg |
| * |
| * 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 |
| #include <assert.h> |
| |
| #include "channel.h" |
| #include "transport.h" |
| #include "packet.h" |
| #include "session.h" |
| |
| /* |
| * _libssh2_channel_nextid |
| * |
| * Determine the next channel ID we can use at our end |
| */ |
| uint32_t |
| _libssh2_channel_nextid(LIBSSH2_SESSION * session) |
| { |
| 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; |
| } |
| |
| /* |
| * _libssh2_channel_locate |
| * |
| * Locate a channel pointer by number |
| */ |
| LIBSSH2_CHANNEL * |
| _libssh2_channel_locate(LIBSSH2_SESSION *session, uint32_t channel_id) |
| { |
| 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; |
| } |
| |
| /* |
| * _libssh2_channel_open |
| * |
| * Establish a generic session channel |
| */ |
| LIBSSH2_CHANNEL * |
| _libssh2_channel_open(LIBSSH2_SESSION * session, const char *channel_type, |
| uint32_t channel_type_len, |
| uint32_t window_size, |
| uint32_t packet_size, |
| const unsigned char *message, |
| size_t 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 + 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) { |
| _libssh2_error(session, rc, "Unexpected error"); |
| goto channel_error; |
| } |
| |
| if(session->open_data_len < 1) { |
| _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Unexpected packet size"); |
| goto channel_error; |
| } |
| |
| if(session->open_data[0] == SSH_MSG_CHANNEL_OPEN_CONFIRMATION) { |
| |
| if(session->open_data_len < 17) { |
| _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Unexpected packet size"); |
| goto channel_error; |
| } |
| |
| 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 " |
| "(administratively 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"); |
| } |
| } |
| } |
| |
| 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); |
| |
| _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; |
| } |
| |
| /* |
| * 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 |
| * |
| * 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) |
| { |
| 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 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; |
| } |
| |
| /* |
| * 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 |
| * |
| * 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) |
| { |
| 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, |
| "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 || (data_len < 1)) { |
| _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, |
| "Unable to allocate memory " |
| "for listener queue"); |
| LIBSSH2_FREE(session, listener); |
| listener = NULL; |
| } |
| else { |
| listener->session = session; |
| memcpy(listener->host, host, 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_TRACE_CONN, |
| "Dynamic tcpip-forward port " |
| "allocated: %d", |
| listener->port); |
| } |
| else |
| listener->port = port; |
| |
| listener->queue_size = 0; |
| listener->queue_maxsize = queue_maxsize; |
| |
| /* append this to the parent's list of listeners */ |
| _libssh2_list_add(&session->listeners, &listener->node); |
| |
| if(bound_port) { |
| *bound_port = listener->port; |
| } |
| } |
| } |
| |
| LIBSSH2_FREE(session, data); |
| session->fwdLstn_state = libssh2_NB_state_idle; |
| return listener; |
| } |
| else 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"); |
| session->fwdLstn_state = libssh2_NB_state_idle; |
| return NULL; |
| } |
| } |
| |
| session->fwdLstn_state = libssh2_NB_state_idle; |
| |
| return NULL; |
| } |
| |
| /* |
| * 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) |
| { |
| LIBSSH2_LISTENER *ptr; |
| |
| if(!session) |
| return NULL; |
| |
| BLOCK_ADJUST_ERRNO(ptr, session, |
| channel_forward_listen(session, host, port, bound_port, |
| queue_maxsize)); |
| return ptr; |
| } |
| |
| /* |
| * _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, LIBSSH2_ERROR_EAGAIN if would block, -1 on error |
| */ |
| int _libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener) |
| { |
| LIBSSH2_SESSION *session = listener->session; |
| LIBSSH2_CHANNEL *queued; |
| unsigned char *packet, *s; |
| size_t host_len = strlen(listener->host); |
| /* 14 = packet_type(1) + request_len(4) + want_replay(1) + host_len(4) + |
| port(4) */ |
| size_t packet_len = |
| host_len + 14 + sizeof("cancel-tcpip-forward") - 1; |
| int rc; |
| int retcode = 0; |
| |
| if(listener->chanFwdCncl_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_TRACE_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 memory for setenv packet"); |
| return LIBSSH2_ERROR_ALLOC; |
| } |
| |
| *(s++) = SSH_MSG_GLOBAL_REQUEST; |
| _libssh2_store_str(&s, "cancel-tcpip-forward", |
| sizeof("cancel-tcpip-forward") - 1); |
| *(s++) = 0x00; /* want_reply */ |
| |
| _libssh2_store_str(&s, listener->host, host_len); |
| _libssh2_store_u32(&s, listener->port); |
| |
| listener->chanFwdCncl_state = libssh2_NB_state_created; |
| } |
| else { |
| packet = listener->chanFwdCncl_data; |
| } |
| |
| if(listener->chanFwdCncl_state == libssh2_NB_state_created) { |
| rc = _libssh2_transport_send(session, packet, packet_len, NULL, 0); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(session, rc, |
| "Would block sending forward request"); |
| listener->chanFwdCncl_data = packet; |
| return rc; |
| } |
| else if(rc) { |
| _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send global-request packet for forward " |
| "listen request"); |
| /* set the state to something we don't check for, for the |
| unfortunate situation where we get an EAGAIN further down |
| when trying to bail out due to errors! */ |
| listener->chanFwdCncl_state = libssh2_NB_state_sent; |
| retcode = LIBSSH2_ERROR_SOCKET_SEND; |
| } |
| LIBSSH2_FREE(session, packet); |
| |
| listener->chanFwdCncl_state = libssh2_NB_state_sent; |
| } |
| |
| queued = _libssh2_list_first(&listener->queue); |
| while(queued) { |
| LIBSSH2_CHANNEL *next = _libssh2_list_next(&queued->node); |
| |
| rc = _libssh2_channel_free(queued); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| queued = next; |
| } |
| LIBSSH2_FREE(session, listener->host); |
| |
| /* remove this entry from the parent's list of listeners */ |
| _libssh2_list_remove(&listener->node); |
| |
| LIBSSH2_FREE(session, listener); |
| |
| return retcode; |
| } |
| |
| /* |
| * 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, LIBSSH2_ERROR_EAGAIN if would block, -1 on error |
| */ |
| LIBSSH2_API int |
| libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener) |
| { |
| int rc; |
| |
| if(!listener) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, listener->session, |
| _libssh2_channel_forward_cancel(listener)); |
| return rc; |
| } |
| |
| /* |
| * channel_forward_accept |
| * |
| * Accept a connection |
| */ |
| static LIBSSH2_CHANNEL * |
| channel_forward_accept(LIBSSH2_LISTENER *listener) |
| { |
| int rc; |
| |
| do { |
| rc = _libssh2_transport_read(listener->session); |
| } while(rc > 0); |
| |
| if(_libssh2_list_first(&listener->queue)) { |
| LIBSSH2_CHANNEL *channel = _libssh2_list_first(&listener->queue); |
| |
| /* detach channel from listener's queue */ |
| _libssh2_list_remove(&channel->node); |
| |
| listener->queue_size--; |
| |
| /* add channel to session's channel list */ |
| _libssh2_list_add(&channel->session->channels, &channel->node); |
| |
| return channel; |
| } |
| |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(listener->session, LIBSSH2_ERROR_EAGAIN, |
| "Would block waiting for packet"); |
| } |
| else |
| _libssh2_error(listener->session, LIBSSH2_ERROR_CHANNEL_UNKNOWN, |
| "Channel not found"); |
| return NULL; |
| } |
| |
| /* |
| * libssh2_channel_forward_accept |
| * |
| * Accept a connection |
| */ |
| LIBSSH2_API LIBSSH2_CHANNEL * |
| libssh2_channel_forward_accept(LIBSSH2_LISTENER *listener) |
| { |
| LIBSSH2_CHANNEL *ptr; |
| |
| if(!listener) |
| return NULL; |
| |
| BLOCK_ADJUST_ERRNO(ptr, listener->session, |
| channel_forward_accept(listener)); |
| return ptr; |
| |
| } |
| |
| /* |
| * channel_setenv |
| * |
| * Set an environment variable prior to requesting a shell/program/subsystem |
| */ |
| static int channel_setenv(LIBSSH2_CHANNEL *channel, |
| const 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 }; |
| size_t 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_TRACE_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) { |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory " |
| "for setenv packet"); |
| } |
| |
| *(s++) = SSH_MSG_CHANNEL_REQUEST; |
| _libssh2_store_u32(&s, channel->remote.id); |
| _libssh2_store_str(&s, "env", sizeof("env") - 1); |
| *(s++) = 0x01; |
| _libssh2_store_str(&s, varname, varname_len); |
| _libssh2_store_str(&s, value, value_len); |
| |
| channel->setenv_state = libssh2_NB_state_created; |
| } |
| |
| if(channel->setenv_state == libssh2_NB_state_created) { |
| rc = _libssh2_transport_send(session, |
| channel->setenv_packet, |
| channel->setenv_packet_len, |
| NULL, 0); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(session, rc, |
| "Would block sending setenv request"); |
| return rc; |
| } |
| else if(rc) { |
| LIBSSH2_FREE(session, channel->setenv_packet); |
| channel->setenv_packet = NULL; |
| channel->setenv_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send channel-request packet for " |
| "setenv request"); |
| } |
| 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(session, reply_codes, &data, &data_len, |
| 1, channel->setenv_local_channel, 4, |
| &channel-> |
| setenv_packet_requirev_state); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| if(rc) { |
| channel->setenv_state = libssh2_NB_state_idle; |
| return rc; |
| } |
| else if(data_len < 1) { |
| channel->setenv_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Unexpected packet size"); |
| } |
| |
| if(data[0] == SSH_MSG_CHANNEL_SUCCESS) { |
| LIBSSH2_FREE(session, data); |
| channel->setenv_state = libssh2_NB_state_idle; |
| return 0; |
| } |
| |
| LIBSSH2_FREE(session, data); |
| } |
| |
| channel->setenv_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, |
| "Unable to complete request for channel-setenv"); |
| } |
| |
| /* |
| * 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, |
| const char *varname, unsigned int varname_len, |
| const char *value, unsigned int value_len) |
| { |
| int rc; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, channel->session, |
| channel_setenv(channel, varname, varname_len, |
| value, value_len)); |
| return rc; |
| } |
| |
| /* |
| * channel_request_pty |
| * Duh... Request a PTY |
| */ |
| static int channel_request_pty(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; |
| static const unsigned char reply_codes[3] = |
| { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }; |
| 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) */ |
| if(term_len + modes_len > 256) { |
| return _libssh2_error(session, LIBSSH2_ERROR_INVAL, |
| "term + mode lengths too large"); |
| } |
| |
| 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_TRACE_CONN, |
| "Allocating tty on channel %lu/%lu", channel->local.id, |
| channel->remote.id); |
| |
| s = channel->reqPTY_packet; |
| |
| *(s++) = SSH_MSG_CHANNEL_REQUEST; |
| _libssh2_store_u32(&s, channel->remote.id); |
| _libssh2_store_str(&s, (char *)"pty-req", sizeof("pty-req") - 1); |
| |
| *(s++) = 0x01; |
| |
| _libssh2_store_str(&s, term, term_len); |
| _libssh2_store_u32(&s, width); |
| _libssh2_store_u32(&s, height); |
| _libssh2_store_u32(&s, width_px); |
| _libssh2_store_u32(&s, height_px); |
| _libssh2_store_str(&s, modes, modes_len); |
| |
| channel->reqPTY_state = libssh2_NB_state_created; |
| } |
| |
| if(channel->reqPTY_state == libssh2_NB_state_created) { |
| rc = _libssh2_transport_send(session, channel->reqPTY_packet, |
| channel->reqPTY_packet_len, |
| NULL, 0); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(session, rc, |
| "Would block sending pty request"); |
| return rc; |
| } |
| else if(rc) { |
| channel->reqPTY_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "Unable to send pty-request packet"); |
| } |
| _libssh2_htonu32(channel->reqPTY_local_channel, channel->local.id); |
| |
| channel->reqPTY_state = libssh2_NB_state_sent; |
| } |
| |
| if(channel->reqPTY_state == libssh2_NB_state_sent) { |
| unsigned char *data; |
| size_t data_len; |
| unsigned char code; |
| rc = _libssh2_packet_requirev(session, reply_codes, &data, &data_len, |
| 1, channel->reqPTY_local_channel, 4, |
| &channel->reqPTY_packet_requirev_state); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if(rc || data_len < 1) { |
| channel->reqPTY_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Failed to require the PTY package"); |
| } |
| |
| code = data[0]; |
| |
| LIBSSH2_FREE(session, data); |
| channel->reqPTY_state = libssh2_NB_state_idle; |
| |
| if(code == SSH_MSG_CHANNEL_SUCCESS) |
| return 0; |
| } |
| |
| return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, |
| "Unable to complete request for " |
| "channel request-pty"); |
| } |
| |
| /** |
| * channel_request_auth_agent |
| * The actual re-entrant method which requests an auth agent. |
| * */ |
| static int channel_request_auth_agent(LIBSSH2_CHANNEL *channel, |
| const char *request_str, |
| int request_str_len) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned char *s; |
| static const unsigned char reply_codes[3] = |
| { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }; |
| int rc; |
| |
| if(channel->req_auth_agent_state == libssh2_NB_state_idle) { |
| /* Only valid options are "auth-agent-req" and |
| * "auth-agent-req_at_openssh.com" so we make sure it is not |
| * actually longer than the longest possible. */ |
| if(request_str_len > 26) { |
| return _libssh2_error(session, LIBSSH2_ERROR_INVAL, |
| "request_str length too large"); |
| } |
| |
| /* |
| * Length: 24 or 36 = packet_type(1) + channel(4) + req_len(4) + |
| * request_str (variable) + want_reply (1) */ |
| channel->req_auth_agent_packet_len = 10 + request_str_len; |
| |
| /* Zero out the requireev state to reset */ |
| memset(&channel->req_auth_agent_requirev_state, 0, |
| sizeof(channel->req_auth_agent_requirev_state)); |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_CONN, |
| "Requesting auth agent on channel %lu/%lu", |
| channel->local.id, channel->remote.id); |
| |
| /* |
| * byte SSH_MSG_CHANNEL_REQUEST |
| * uint32 recipient channel |
| * string "auth-agent-req" |
| * boolean want reply |
| * */ |
| s = channel->req_auth_agent_packet; |
| *(s++) = SSH_MSG_CHANNEL_REQUEST; |
| _libssh2_store_u32(&s, channel->remote.id); |
| _libssh2_store_str(&s, (char *)request_str, request_str_len); |
| *(s++) = 0x01; |
| |
| channel->req_auth_agent_state = libssh2_NB_state_created; |
| } |
| |
| if(channel->req_auth_agent_state == libssh2_NB_state_created) { |
| /* Send the packet, we can use sizeof() on the packet because it |
| * is always completely filled; there are no variable length fields. */ |
| rc = _libssh2_transport_send(session, channel->req_auth_agent_packet, |
| channel->req_auth_agent_packet_len, |
| NULL, 0); |
| |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(session, rc, |
| "Would block sending auth-agent request"); |
| } |
| else if(rc) { |
| channel->req_auth_agent_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "Unable to send auth-agent request"); |
| } |
| _libssh2_htonu32(channel->req_auth_agent_local_channel, |
| channel->local.id); |
| channel->req_auth_agent_state = libssh2_NB_state_sent; |
| } |
| |
| if(channel->req_auth_agent_state == libssh2_NB_state_sent) { |
| unsigned char *data; |
| size_t data_len; |
| unsigned char code; |
| |
| rc = _libssh2_packet_requirev( |
| session, reply_codes, &data, &data_len, 1, |
| channel->req_auth_agent_local_channel, |
| 4, &channel->req_auth_agent_requirev_state); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if(rc) { |
| channel->req_auth_agent_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
| "Failed to request auth-agent"); |
| } |
| |
| code = data[0]; |
| |
| LIBSSH2_FREE(session, data); |
| channel->req_auth_agent_state = libssh2_NB_state_idle; |
| |
| if(code == SSH_MSG_CHANNEL_SUCCESS) |
| return 0; |
| } |
| |
| return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, |
| "Unable to complete request for auth-agent"); |
| } |
| |
| /** |
| * libssh2_channel_request_auth_agent |
| * Requests that agent forwarding be enabled for the session. The |
| * request must be sent over a specific channel, which starts the agent |
| * listener on the remote side. Once the channel is closed, the agent |
| * listener continues to exist. |
| * */ |
| LIBSSH2_API int |
| libssh2_channel_request_auth_agent(LIBSSH2_CHANNEL *channel) |
| { |
| int rc; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| /* The current RFC draft for agent forwarding says you're supposed to |
| * send "auth-agent-req," but most SSH servers out there right now |
| * actually expect "[email protected]", so we try that |
| * first. */ |
| if(channel->req_auth_agent_try_state == libssh2_NB_state_idle) { |
| BLOCK_ADJUST(rc, channel->session, |
| channel_request_auth_agent(channel, |
| "[email protected]", |
| 26)); |
| |
| /* If we failed (but not with EAGAIN), then we move onto |
| * the next step to try another request type. */ |
| if(rc != 0 && rc != LIBSSH2_ERROR_EAGAIN) |
| channel->req_auth_agent_try_state = libssh2_NB_state_sent; |
| } |
| |
| if(channel->req_auth_agent_try_state == libssh2_NB_state_sent) { |
| BLOCK_ADJUST(rc, channel->session, |
| channel_request_auth_agent(channel, |
| "auth-agent-req", 14)); |
| |
| /* If we failed without an EAGAIN, then move on with this |
| * state machine. */ |
| if(rc != 0 && rc != LIBSSH2_ERROR_EAGAIN) |
| channel->req_auth_agent_try_state = libssh2_NB_state_sent1; |
| } |
| |
| /* If things are good, reset the try state. */ |
| if(rc == 0) |
| channel->req_auth_agent_try_state = libssh2_NB_state_idle; |
| |
| return rc; |
| } |
| |
| /* |
| * 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) |
| { |
| int rc; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, channel->session, |
| channel_request_pty(channel, term, term_len, modes, |
| modes_len, width, height, |
| width_px, height_px)); |
| return rc; |
| } |
| |
| static int |
| channel_request_pty_size(LIBSSH2_CHANNEL * channel, int width, |
| int height, int width_px, int height_px) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned char *s; |
| int rc; |
| int retcode = LIBSSH2_ERROR_PROTO; |
| |
| if(channel->reqPTY_state == libssh2_NB_state_idle) { |
| channel->reqPTY_packet_len = 39; |
| |
| /* Zero the whole thing out */ |
| memset(&channel->reqPTY_packet_requirev_state, 0, |
| sizeof(channel->reqPTY_packet_requirev_state)); |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_CONN, |
| "changing tty size on channel %lu/%lu", |
| channel->local.id, |
| channel->remote.id); |
| |
| s = channel->reqPTY_packet; |
| |
| *(s++) = SSH_MSG_CHANNEL_REQUEST; |
| _libssh2_store_u32(&s, channel->remote.id); |
| _libssh2_store_str(&s, (char *)"window-change", |
| sizeof("window-change") - 1); |
| *(s++) = 0x00; /* Don't reply */ |
| _libssh2_store_u32(&s, width); |
| _libssh2_store_u32(&s, height); |
| _libssh2_store_u32(&s, width_px); |
| _libssh2_store_u32(&s, height_px); |
| |
| channel->reqPTY_state = libssh2_NB_state_created; |
| } |
| |
| if(channel->reqPTY_state == libssh2_NB_state_created) { |
| rc = _libssh2_transport_send(session, channel->reqPTY_packet, |
| channel->reqPTY_packet_len, |
| NULL, 0); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(session, rc, |
| "Would block sending window-change request"); |
| return rc; |
| } |
| else if(rc) { |
| channel->reqPTY_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "Unable to send window-change packet"); |
| } |
| _libssh2_htonu32(channel->reqPTY_local_channel, channel->local.id); |
| retcode = LIBSSH2_ERROR_NONE; |
| } |
| |
| channel->reqPTY_state = libssh2_NB_state_idle; |
| return retcode; |
| } |
| |
| LIBSSH2_API int |
| libssh2_channel_request_pty_size_ex(LIBSSH2_CHANNEL *channel, int width, |
| int height, int width_px, int height_px) |
| { |
| int rc; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, channel->session, |
| channel_request_pty_size(channel, width, height, width_px, |
| height_px)); |
| return rc; |
| } |
| |
| /* Keep this an even number */ |
| #define LIBSSH2_X11_RANDOM_COOKIE_LEN 32 |
| |
| /* |
| * channel_x11_req |
| * Request X11 forwarding |
| */ |
| static int |
| channel_x11_req(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; |
| static const unsigned char reply_codes[3] = |
| { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }; |
| size_t proto_len = |
| auth_proto ? strlen(auth_proto) : (sizeof("MIT-MAGIC-COOKIE-1") - 1); |
| size_t 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_TRACE_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) { |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for pty-request"); |
| } |
| |
| *(s++) = SSH_MSG_CHANNEL_REQUEST; |
| _libssh2_store_u32(&s, channel->remote.id); |
| _libssh2_store_str(&s, "x11-req", sizeof("x11-req") - 1); |
| |
| *(s++) = 0x01; /* want_reply */ |
| *(s++) = single_connection ? 0x01 : 0x00; |
| |
| _libssh2_store_str(&s, auth_proto ? auth_proto : "MIT-MAGIC-COOKIE-1", |
| proto_len); |
| |
| _libssh2_store_u32(&s, cookie_len); |
| if(auth_cookie) { |
| memcpy(s, auth_cookie, cookie_len); |
| } |
| else { |
| int i; |
| /* note: the extra +1 below is necessary since the sprintf() |
| loop will always write 3 bytes so the last one will write |
| the trailing zero at the LIBSSH2_X11_RANDOM_COOKIE_LEN/2 |
| border */ |
| unsigned char buffer[(LIBSSH2_X11_RANDOM_COOKIE_LEN / 2) + 1]; |
| |
| if(_libssh2_random(buffer, LIBSSH2_X11_RANDOM_COOKIE_LEN / 2)) { |
| return _libssh2_error(session, LIBSSH2_ERROR_RANDGEN, |
| "Unable to get random bytes " |
| "for x11-req cookie"); |
| } |
| for(i = 0; i < (LIBSSH2_X11_RANDOM_COOKIE_LEN / 2); i++) { |
| snprintf((char *)&s[i*2], 3, "%02X", buffer[i]); |
| } |
| } |
| s += cookie_len; |
| |
| _libssh2_store_u32(&s, screen_number); |
| channel->reqX11_state = libssh2_NB_state_created; |
| } |
| |
| if(channel->reqX11_state == libssh2_NB_state_created) { |
| rc = _libssh2_transport_send(session, channel->reqX11_packet, |
| channel->reqX11_packet_len, |
| NULL, 0); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(session, rc, |
| "Would block sending X11-req packet"); |
| return rc; |
| } |
| if(rc) { |
| LIBSSH2_FREE(session, channel->reqX11_packet); |
| channel->reqX11_packet = NULL; |
| channel->reqX11_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "Unable to send x11-req packet"); |
| } |
| 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) { |
| size_t data_len; |
| unsigned char *data; |
| unsigned char code; |
| |
| rc = _libssh2_packet_requirev(session, reply_codes, &data, &data_len, |
| 1, channel->reqX11_local_channel, 4, |
| &channel->reqX11_packet_requirev_state); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if(rc || data_len < 1) { |
| channel->reqX11_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "waiting for x11-req response packet"); |
| } |
| |
| code = data[0]; |
| LIBSSH2_FREE(session, data); |
| channel->reqX11_state = libssh2_NB_state_idle; |
| |
| if(code == SSH_MSG_CHANNEL_SUCCESS) |
| return 0; |
| } |
| |
| return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, |
| "Unable to complete request for channel x11-req"); |
| } |
| |
| /* |
| * 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) |
| { |
| int rc; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, channel->session, |
| channel_x11_req(channel, single_connection, auth_proto, |
| auth_cookie, screen_number)); |
| return rc; |
| } |
| |
| |
| /* |
| * _libssh2_channel_process_startup |
| * |
| * Primitive for libssh2_channel_(shell|exec|subsystem) |
| */ |
| int |
| _libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, |
| const char *request, size_t request_len, |
| const char *message, size_t message_len) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned char *s; |
| static const unsigned char reply_codes[3] = |
| { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }; |
| int rc; |
| |
| if(channel->process_state == libssh2_NB_state_end) { |
| return _libssh2_error(session, LIBSSH2_ERROR_BAD_USE, |
| "Channel can not be reused"); |
| } |
| |
| 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 += + 4; |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_CONN, |
| "starting request(%s) on channel %lu/%lu, message=%s", |
| request, channel->local.id, channel->remote.id, |
| message ? message : "<null>"); |
| s = channel->process_packet = |
| LIBSSH2_ALLOC(session, channel->process_packet_len); |
| if(!channel->process_packet) |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory " |
| "for channel-process request"); |
| |
| *(s++) = SSH_MSG_CHANNEL_REQUEST; |
| _libssh2_store_u32(&s, channel->remote.id); |
| _libssh2_store_str(&s, request, request_len); |
| *(s++) = 0x01; |
| |
| if(message) |
| _libssh2_store_u32(&s, message_len); |
| |
| channel->process_state = libssh2_NB_state_created; |
| } |
| |
| if(channel->process_state == libssh2_NB_state_created) { |
| rc = _libssh2_transport_send(session, |
| channel->process_packet, |
| channel->process_packet_len, |
| (unsigned char *)message, message_len); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(session, rc, |
| "Would block sending channel request"); |
| return rc; |
| } |
| else if(rc) { |
| LIBSSH2_FREE(session, channel->process_packet); |
| channel->process_packet = NULL; |
| channel->process_state = libssh2_NB_state_end; |
| return _libssh2_error(session, rc, |
| "Unable to send channel request"); |
| } |
| 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) { |
| unsigned char *data; |
| size_t data_len; |
| unsigned char code; |
| rc = _libssh2_packet_requirev(session, reply_codes, &data, &data_len, |
| 1, channel->process_local_channel, 4, |
| &channel->process_packet_requirev_state); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if(rc || data_len < 1) { |
| channel->process_state = libssh2_NB_state_end; |
| return _libssh2_error(session, rc, |
| "Failed waiting for channel success"); |
| } |
| |
| code = data[0]; |
| LIBSSH2_FREE(session, data); |
| channel->process_state = libssh2_NB_state_end; |
| |
| if(code == SSH_MSG_CHANNEL_SUCCESS) |
| return 0; |
| } |
| |
| return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, |
| "Unable to complete request for " |
| "channel-process-startup"); |
| } |
| |
| /* |
| * libssh2_channel_process_startup |
| * |
| * Primitive for libssh2_channel_(shell|exec|subsystem) |
| */ |
| LIBSSH2_API int |
| libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, |
| const char *req, unsigned int req_len, |
| const char *msg, unsigned int msg_len) |
| { |
| int rc; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, channel->session, |
| _libssh2_channel_process_startup(channel, req, req_len, |
| msg, msg_len)); |
| return rc; |
| } |
| |
| |
| /* |
| * libssh2_channel_set_blocking |
| * |
| * Set a channel's BEHAVIOR blocking on or off. The socket will remain non- |
| * blocking. |
| */ |
| LIBSSH2_API void |
| libssh2_channel_set_blocking(LIBSSH2_CHANNEL * channel, int blocking) |
| { |
| if(channel) |
| (void) _libssh2_session_set_blocking(channel->session, blocking); |
| } |
| |
| /* |
| * _libssh2_channel_flush |
| * |
| * Flush data from one (or all) stream |
| * Returns number of bytes flushed, or negative on failure |
| */ |
| int |
| _libssh2_channel_flush(LIBSSH2_CHANNEL *channel, int streamid) |
| { |
| if(channel->flush_state == libssh2_NB_state_idle) { |
| LIBSSH2_PACKET *packet = |
| _libssh2_list_first(&channel->session->packets); |
| channel->flush_refund_bytes = 0; |
| channel->flush_flush_bytes = 0; |
| |
| while(packet) { |
| unsigned char packet_type; |
| LIBSSH2_PACKET *next = _libssh2_list_next(&packet->node); |
| |
| if(packet->data_len < 1) { |
| packet = next; |
| _libssh2_debug(channel->session, LIBSSH2_TRACE_ERROR, |
| "Unexpected packet length"); |
| continue; |
| } |
| |
| packet_type = packet->data[0]; |
| |
| if(((packet_type == SSH_MSG_CHANNEL_DATA) |
| || (packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA)) |
| && ((packet->data_len >= 5) |
| && (_libssh2_ntohu32(packet->data + 1) |
| == channel->local.id))) { |
| /* It's our channel at least */ |
| int packet_stream_id; |
| |
| if(packet_type == SSH_MSG_CHANNEL_DATA) { |
| packet_stream_id = 0; |
| } |
| else if(packet->data_len >= 9) { |
| packet_stream_id = _libssh2_ntohu32(packet->data + 5); |
| } |
| else { |
| channel->flush_state = libssh2_NB_state_idle; |
| return _libssh2_error(channel->session, |
| LIBSSH2_ERROR_PROTO, |
| "Unexpected packet length"); |
| } |
| |
| 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))) { |
| size_t bytes_to_flush = packet->data_len - |
| packet->data_head; |
| |
| _libssh2_debug(channel->session, LIBSSH2_TRACE_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); |
| |
| /* remove this packet from the parent's list */ |
| _libssh2_list_remove(&packet->node); |
| LIBSSH2_FREE(channel->session, packet); |
| } |
| } |
| packet = next; |
| } |
| |
| channel->flush_state = libssh2_NB_state_created; |
| } |
| |
| channel->read_avail -= channel->flush_flush_bytes; |
| channel->remote.window_size -= channel->flush_flush_bytes; |
| |
| if(channel->flush_refund_bytes) { |
| int rc = |
| _libssh2_channel_receive_window_adjust(channel, |
| channel->flush_refund_bytes, |
| 1, NULL); |
| if(rc == LIBSSH2_ERROR_EAGAIN) |
| return rc; |
| } |
| |
| channel->flush_state = libssh2_NB_state_idle; |
| |
| return channel->flush_flush_bytes; |
| } |
| |
| /* |
| * libssh2_channel_flush_ex |
| * |
| * Flush data from one (or all) stream |
| * Returns number of bytes flushed, or negative on failure |
| */ |
| LIBSSH2_API int |
| libssh2_channel_flush_ex(LIBSSH2_CHANNEL *channel, int stream) |
| { |
| int rc; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, channel->session, |
| _libssh2_channel_flush(channel, stream)); |
| return rc; |
| } |
| |
| /* |
| * libssh2_channel_get_exit_status |
| * |
| * Return the channel's program exit status. Note that the actual protocol |
| * provides the full 32bit this function returns. We cannot abuse it to |
| * return error values in case of errors so we return a zero if channel is |
| * NULL. |
| */ |
| LIBSSH2_API int |
| libssh2_channel_get_exit_status(LIBSSH2_CHANNEL *channel) |
| { |
| if(!channel) |
| return 0; |
| |
| return channel->exit_status; |
| } |
| |
| /* |
| * libssh2_channel_get_exit_signal |
| * |
| * Get exit signal (without leading "SIG"), error message, and language |
| * tag into newly allocated buffers of indicated length. Caller can |
| * use NULL pointers to indicate that the value should not be set. The |
| * *_len variables are set if they are non-NULL even if the |
| * corresponding string parameter is NULL. Returns LIBSSH2_ERROR_NONE |
| * on success, or an API error code. |
| */ |
| LIBSSH2_API int |
| libssh2_channel_get_exit_signal(LIBSSH2_CHANNEL *channel, |
| char **exitsignal, |
| size_t *exitsignal_len, |
| char **errmsg, |
| size_t *errmsg_len, |
| char **langtag, |
| size_t *langtag_len) |
| { |
| size_t namelen = 0; |
| |
| if(channel) { |
| LIBSSH2_SESSION *session = channel->session; |
| |
| if(channel->exit_signal) { |
| namelen = strlen(channel->exit_signal); |
| if(exitsignal) { |
| *exitsignal = LIBSSH2_ALLOC(session, namelen + 1); |
| if(!*exitsignal) { |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for signal name"); |
| } |
| memcpy(*exitsignal, channel->exit_signal, namelen); |
| (*exitsignal)[namelen] = '\0'; |
| } |
| if(exitsignal_len) |
| *exitsignal_len = namelen; |
| } |
| else { |
| if(exitsignal) |
| *exitsignal = NULL; |
| if(exitsignal_len) |
| *exitsignal_len = 0; |
| } |
| |
| /* TODO: set error message and language tag */ |
| |
| if(errmsg) |
| *errmsg = NULL; |
| |
| if(errmsg_len) |
| *errmsg_len = 0; |
| |
| if(langtag) |
| *langtag = NULL; |
| |
| if(langtag_len) |
| *langtag_len = 0; |
| } |
| |
| return LIBSSH2_ERROR_NONE; |
| } |
| |
| /* |
| * _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. |
| * |
| * Calls _libssh2_error() ! |
| */ |
| int |
| _libssh2_channel_receive_window_adjust(LIBSSH2_CHANNEL * channel, |
| uint32_t adjustment, |
| unsigned char force, |
| unsigned int *store) |
| { |
| int rc; |
| |
| if(store) |
| *store = channel->remote.window_size; |
| |
| if(channel->adjust_state == libssh2_NB_state_idle) { |
| if(!force |
| && (adjustment + channel->adjust_queue < |
| LIBSSH2_CHANNEL_MINADJUST)) { |
| _libssh2_debug(channel->session, LIBSSH2_TRACE_CONN, |
| "Queueing %lu bytes for receive window adjustment " |
| "for channel %lu/%lu", |
| adjustment, channel->local.id, channel->remote.id); |
| channel->adjust_queue += adjustment; |
| return 0; |
| } |
| |
| if(!adjustment && !channel->adjust_queue) { |
| return 0; |
| } |
| |
| 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_TRACE_CONN, |
| "Adjusting window %lu bytes for data on " |
| "channel %lu/%lu", |
| adjustment, channel->local.id, channel->remote.id); |
| |
| channel->adjust_state = libssh2_NB_state_created; |
| } |
| |
| rc = _libssh2_transport_send(channel->session, channel->adjust_adjust, 9, |
| NULL, 0); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(channel->session, rc, |
| "Would block sending window adjust"); |
| return rc; |
| } |
| else if(rc) { |
| channel->adjust_queue = adjustment; |
| return _libssh2_error(channel->session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send transfer-window adjustment " |
| "packet, deferring"); |
| } |
| else { |
| channel->remote.window_size += adjustment; |
| } |
| |
| channel->adjust_state = libssh2_NB_state_idle; |
| |
| return 0; |
| } |
| |
| /* |
| * libssh2_channel_receive_window_adjust |
| * |
| * DEPRECATED |
| * |
| * 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). |
| * Note that it might return EAGAIN too which is highly stupid. |
| * |
| */ |
| LIBSSH2_API unsigned long |
| libssh2_channel_receive_window_adjust(LIBSSH2_CHANNEL *channel, |
| unsigned long adj, |
| unsigned char force) |
| { |
| unsigned int window; |
| int rc; |
| |
| if(!channel) |
| return (unsigned long)LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, channel->session, |
| _libssh2_channel_receive_window_adjust(channel, adj, |
| force, &window)); |
| |
| /* stupid - but this is how it was made to work before and this is just |
| kept for backwards compatibility */ |
| return rc ? (unsigned long)rc : window; |
| } |
| |
| /* |
| * libssh2_channel_receive_window_adjust2 |
| * |
| * 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. |
| * |
| * Stores the new size of the receive window in the data 'window' points to. |
| * |
| * Returns the "normal" error code: 0 for success, negative for failure. |
| */ |
| LIBSSH2_API int |
| libssh2_channel_receive_window_adjust2(LIBSSH2_CHANNEL *channel, |
| unsigned long adj, |
| unsigned char force, |
| unsigned int *window) |
| { |
| int rc; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, channel->session, |
| _libssh2_channel_receive_window_adjust(channel, adj, force, |
| window)); |
| return rc; |
| } |
| |
| int |
| _libssh2_channel_extended_data(LIBSSH2_CHANNEL *channel, int ignore_mode) |
| { |
| if(channel->extData2_state == libssh2_NB_state_idle) { |
| _libssh2_debug(channel->session, LIBSSH2_TRACE_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 = (char)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) { |
| int rc = |
| _libssh2_channel_flush(channel, |
| LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA); |
| if(LIBSSH2_ERROR_EAGAIN == rc) |
| return rc; |
| } |
| } |
| |
| channel->extData2_state = libssh2_NB_state_idle; |
| return 0; |
| } |
| |
| /* |
| * libssh2_channel_handle_extended_data2() |
| * |
| */ |
| LIBSSH2_API int |
| libssh2_channel_handle_extended_data2(LIBSSH2_CHANNEL *channel, |
| int mode) |
| { |
| int rc; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, channel->session, _libssh2_channel_extended_data(channel, |
| mode)); |
| return rc; |
| } |
| |
| /* |
| * libssh2_channel_handle_extended_data |
| * |
| * DEPRECATED DO NOTE USE! |
| * |
| * 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) |
| { |
| (void)libssh2_channel_handle_extended_data2(channel, ignore_mode); |
| } |
| |
| |
| |
| /* |
| * _libssh2_channel_read |
| * |
| * Read data from a channel |
| * |
| * 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 LIBSSH2_ERROR_EAGAIN. |
| * |
| * The receive window must be maintained (enlarged) by the user of this |
| * function. |
| */ |
| ssize_t _libssh2_channel_read(LIBSSH2_CHANNEL *channel, int stream_id, |
| char *buf, size_t buflen) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| int rc; |
| size_t bytes_read = 0; |
| size_t bytes_want; |
| int unlink_packet; |
| LIBSSH2_PACKET *read_packet; |
| LIBSSH2_PACKET *read_next; |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_CONN, |
| "channel_read() wants %d bytes from channel %lu/%lu " |
| "stream #%d", |
| (int) buflen, channel->local.id, channel->remote.id, |
| stream_id); |
| |
| /* expand the receiving window first if it has become too narrow */ |
| if((channel->read_state == libssh2_NB_state_jump1) || |
| (channel->remote.window_size < |
| channel->remote.window_size_initial / 4 * 3 + buflen) ) { |
| |
| uint32_t adjustment = channel->remote.window_size_initial + buflen - |
| channel->remote.window_size; |
| if(adjustment < LIBSSH2_CHANNEL_MINADJUST) |
| adjustment = LIBSSH2_CHANNEL_MINADJUST; |
| |
| /* the actual window adjusting may not finish so we need to deal with |
| this special state here */ |
| channel->read_state = libssh2_NB_state_jump1; |
| rc = _libssh2_channel_receive_window_adjust(channel, adjustment, |
| 0, NULL); |
| if(rc) |
| return rc; |
| |
| channel->read_state = libssh2_NB_state_idle; |
| } |
| |
| /* Process all pending incoming packets. Tests prove that this way |
| produces faster transfers. */ |
| do { |
| rc = _libssh2_transport_read(session); |
| } while(rc > 0); |
| |
| if((rc < 0) && (rc != LIBSSH2_ERROR_EAGAIN)) |
| return _libssh2_error(session, rc, "transport read"); |
| |
| read_packet = _libssh2_list_first(&session->packets); |
| while(read_packet && (bytes_read < buflen)) { |
| /* previously this loop condition also checked for |
| !channel->remote.close but we cannot let it do this: |
| |
| We may have a series of packets to read that are still pending even |
| if a close has been received. Acknowledging the close too early |
| makes us flush buffers prematurely and loose data. |
| */ |
| |
| LIBSSH2_PACKET *readpkt = read_packet; |
| |
| /* In case packet gets destroyed during this iteration */ |
| read_next = _libssh2_list_next(&readpkt->node); |
| |
| if(readpkt->data_len < 5) { |
| read_packet = read_next; |
| _libssh2_debug(channel->session, LIBSSH2_TRACE_ERROR, |
| "Unexpected packet length"); |
| continue; |
| } |
| |
| channel->read_local_id = |
| _libssh2_ntohu32(readpkt->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 |
| && (readpkt->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) |
| && (channel->local.id == channel->read_local_id) |
| && (readpkt->data_len >= 9) |
| && (stream_id == (int) _libssh2_ntohu32(readpkt->data + 5))) |
| || (!stream_id && (readpkt->data[0] == SSH_MSG_CHANNEL_DATA) |
| && (channel->local.id == channel->read_local_id)) |
| || (!stream_id |
| && (readpkt->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))) { |
| |
| /* figure out much more data we want to read */ |
| bytes_want = buflen - bytes_read; |
| unlink_packet = FALSE; |
| |
| if(bytes_want >= (readpkt->data_len - readpkt->data_head)) { |
| /* we want more than this node keeps, so adjust the number and |
| delete this node after the copy */ |
| bytes_want = readpkt->data_len - readpkt->data_head; |
| unlink_packet = TRUE; |
| } |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_CONN, |
| "channel_read() got %d of data from %lu/%lu/%d%s", |
| bytes_want, channel->local.id, |
| channel->remote.id, stream_id, |
| unlink_packet?" [ul]":""); |
| |
| /* copy data from this struct to the target buffer */ |
| memcpy(&buf[bytes_read], |
| &readpkt->data[readpkt->data_head], bytes_want); |
| |
| /* advance pointer and counter */ |
| readpkt->data_head += bytes_want; |
| bytes_read += bytes_want; |
| |
| /* if drained, remove from list */ |
| if(unlink_packet) { |
| /* detach readpkt from session->packets list */ |
| _libssh2_list_remove(&readpkt->node); |
| |
| LIBSSH2_FREE(session, readpkt->data); |
| LIBSSH2_FREE(session, readpkt); |
| } |
| } |
| |
| /* check the next struct in the chain */ |
| read_packet = read_next; |
| } |
| |
| if(!bytes_read) { |
| /* If the channel is already at EOF or even closed, we need to signal |
| that back. We may have gotten that info while draining the incoming |
| transport layer until EAGAIN so we must not be fooled by that |
| return code. */ |
| if(channel->remote.eof || channel->remote.close) |
| return 0; |
| else if(rc != LIBSSH2_ERROR_EAGAIN) |
| return 0; |
| |
| /* if the transport layer said EAGAIN then we say so as well */ |
| return _libssh2_error(session, rc, "would block"); |
| } |
| |
| channel->read_avail -= bytes_read; |
| channel->remote.window_size -= bytes_read; |
| |
| return bytes_read; |
| } |
| |
| /* |
| * 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 |
| * LIBSSH2_ERROR_EAGAIN. |
| * |
| * This function will first make sure there's a receive window enough to |
| * receive a full buffer's wort of contents. An application may choose to |
| * adjust the receive window more to increase transfer performance. |
| */ |
| LIBSSH2_API ssize_t |
| libssh2_channel_read_ex(LIBSSH2_CHANNEL *channel, int stream_id, char *buf, |
| size_t buflen) |
| { |
| int rc; |
| unsigned long recv_window; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| recv_window = libssh2_channel_window_read_ex(channel, NULL, NULL); |
| |
| if(buflen > recv_window) { |
| BLOCK_ADJUST(rc, channel->session, |
| _libssh2_channel_receive_window_adjust(channel, buflen, |
| 1, NULL)); |
| } |
| |
| BLOCK_ADJUST(rc, channel->session, |
| _libssh2_channel_read(channel, stream_id, buf, buflen)); |
| return rc; |
| } |
| |
| /* |
| * _libssh2_channel_packet_data_len |
| * |
| * Return the size of the data block of the current packet, or 0 if there |
| * isn't a packet. |
| */ |
| size_t |
| _libssh2_channel_packet_data_len(LIBSSH2_CHANNEL * channel, int stream_id) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| LIBSSH2_PACKET *read_packet; |
| LIBSSH2_PACKET *next_packet; |
| uint32_t read_local_id; |
| |
| read_packet = _libssh2_list_first(&session->packets); |
| if(read_packet == NULL) |
| return 0; |
| |
| while(read_packet) { |
| |
| next_packet = _libssh2_list_next(&read_packet->node); |
| |
| if(read_packet->data_len < 5) { |
| read_packet = next_packet; |
| _libssh2_debug(channel->session, LIBSSH2_TRACE_ERROR, |
| "Unexpected packet length"); |
| continue; |
| } |
| |
| read_local_id = _libssh2_ntohu32(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 |
| && (read_packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) |
| && (channel->local.id == read_local_id) |
| && (read_packet->data_len >= 9) |
| && (stream_id == (int) _libssh2_ntohu32(read_packet->data + 5))) |
| || |
| (!stream_id |
| && (read_packet->data[0] == SSH_MSG_CHANNEL_DATA) |
| && (channel->local.id == read_local_id)) |
| || |
| (!stream_id |
| && (read_packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) |
| && (channel->local.id == read_local_id) |
| && (channel->remote.extended_data_ignore_mode |
| == LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE))) { |
| return (read_packet->data_len - read_packet->data_head); |
| } |
| |
| read_packet = next_packet; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * _libssh2_channel_write |
| * |
| * Send data to a channel. Note that if this returns EAGAIN, the caller must |
| * call this function again with the SAME input arguments. |
| * |
| * Returns: number of bytes sent, or if it returns a negative number, that is |
| * the error code! |
| */ |
| ssize_t |
| _libssh2_channel_write(LIBSSH2_CHANNEL *channel, int stream_id, |
| const unsigned char *buf, size_t buflen) |
| { |
| int rc = 0; |
| LIBSSH2_SESSION *session = channel->session; |
| ssize_t wrote = 0; /* counter for this specific this call */ |
| |
| /* In theory we could split larger buffers into several smaller packets |
| * but it turns out to be really hard and nasty to do while still offering |
| * the API/prototype. |
| * |
| * Instead we only deal with the first 32K in this call and for the parent |
| * function to call it again with the remainder! 32K is a conservative |
| * limit based on the text in RFC4253 section 6.1. |
| */ |
| if(buflen > 32700) |
| buflen = 32700; |
| |
| if(channel->write_state == libssh2_NB_state_idle) { |
| unsigned char *s = channel->write_packet; |
| |
| _libssh2_debug(channel->session, LIBSSH2_TRACE_CONN, |
| "Writing %d bytes on channel %lu/%lu, stream #%d", |
| (int) buflen, channel->local.id, channel->remote.id, |
| stream_id); |
| |
| if(channel->local.close) |
| return _libssh2_error(channel->session, |
| LIBSSH2_ERROR_CHANNEL_CLOSED, |
| "We've already closed this channel"); |
| else if(channel->local.eof) |
| return _libssh2_error(channel->session, |
| LIBSSH2_ERROR_CHANNEL_EOF_SENT, |
| "EOF has already been received, " |
| "data might be ignored"); |
| |
| /* drain the incoming flow first, mostly to make sure we get all |
| * pending window adjust packets */ |
| do |
| rc = _libssh2_transport_read(session); |
| while(rc > 0); |
| |
| if((rc < 0) && (rc != LIBSSH2_ERROR_EAGAIN)) { |
| return _libssh2_error(channel->session, rc, |
| "Failure while draining incoming flow"); |
| } |
| |
| if(channel->local.window_size <= 0) { |
| /* there's no room for data so we stop */ |
| |
| /* Waiting on the socket to be writable would be wrong because we |
| * would be back here immediately, but a readable socket might |
| * herald an incoming window adjustment. |
| */ |
| session->socket_block_directions = LIBSSH2_SESSION_BLOCK_INBOUND; |
| |
| return (rc == LIBSSH2_ERROR_EAGAIN?rc:0); |
| } |
| |
| channel->write_bufwrite = buflen; |
| |
| *(s++) = stream_id ? SSH_MSG_CHANNEL_EXTENDED_DATA : |
| SSH_MSG_CHANNEL_DATA; |
| _libssh2_store_u32(&s, channel->remote.id); |
| if(stream_id) |
| _libssh2_store_u32(&s, stream_id); |
| |
| /* 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_TRACE_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_TRACE_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; |
| } |
| /* store the size here only, the buffer is passed in as-is to |
| _libssh2_transport_send() */ |
| _libssh2_store_u32(&s, channel->write_bufwrite); |
| channel->write_packet_len = s - channel->write_packet; |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_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_transport_send(session, channel->write_packet, |
| channel->write_packet_len, |
| buf, channel->write_bufwrite); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return _libssh2_error(session, rc, |
| "Unable to send channel data"); |
| } |
| else if(rc) { |
| channel->write_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "Unable to send channel data"); |
| } |
| /* Shrink local window size */ |
| channel->local.window_size -= channel->write_bufwrite; |
| |
| wrote += channel->write_bufwrite; |
| |
| /* Since _libssh2_transport_write() succeeded, we must return |
| now to allow the caller to provide the next chunk of data. |
| |
| We cannot move on to send the next piece of data that may |
| already have been provided in this same function call, as we |
| risk getting EAGAIN for that and we can't return information |
| both about sent data as well as EAGAIN. So, by returning short |
| now, the caller will call this function again with new data to |
| send */ |
| |
| channel->write_state = libssh2_NB_state_idle; |
| |
| return wrote; |
| } |
| |
| return LIBSSH2_ERROR_INVAL; /* reaching this point is really bad */ |
| } |
| |
| /* |
| * 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) |
| { |
| ssize_t rc; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, channel->session, |
| _libssh2_channel_write(channel, stream_id, |
| (unsigned char *)buf, buflen)); |
| return rc; |
| } |
| |
| /* |
| * channel_send_eof |
| * |
| * Send EOF on channel |
| */ |
| static int 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_TRACE_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_transport_send(session, packet, 5, NULL, 0); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(session, rc, |
| "Would block sending EOF"); |
| return rc; |
| } |
| else if(rc) { |
| return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send EOF on channel"); |
| } |
| channel->local.eof = 1; |
| |
| return 0; |
| } |
| |
| /* |
| * libssh2_channel_send_eof |
| * |
| * Send EOF on channel |
| */ |
| LIBSSH2_API int |
| libssh2_channel_send_eof(LIBSSH2_CHANNEL *channel) |
| { |
| int rc; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, channel->session, channel_send_eof(channel)); |
| return rc; |
| } |
| |
| /* |
| * libssh2_channel_eof |
| * |
| * Read channel's eof status |
| */ |
| LIBSSH2_API int |
| libssh2_channel_eof(LIBSSH2_CHANNEL * channel) |
| { |
| LIBSSH2_SESSION *session; |
| LIBSSH2_PACKET *packet; |
| LIBSSH2_PACKET *next_packet; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| session = channel->session; |
| packet = _libssh2_list_first(&session->packets); |
| |
| while(packet) { |
| |
| next_packet = _libssh2_list_next(&packet->node); |
| |
| if(packet->data_len < 1) { |
| packet = next_packet; |
| _libssh2_debug(channel->session, LIBSSH2_TRACE_ERROR, |
| "Unexpected packet length"); |
| continue; |
| } |
| |
| if(((packet->data[0] == SSH_MSG_CHANNEL_DATA) |
| || (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA)) |
| && ((packet->data_len >= 5) |
| && (channel->local.id == _libssh2_ntohu32(packet->data + 1)))) { |
| /* There's data waiting to be read yet, mask the EOF status */ |
| return 0; |
| } |
| packet = next_packet; |
| } |
| |
| return channel->remote.eof; |
| } |
| |
| /* |
| * channel_wait_eof |
| * |
| * Awaiting channel EOF |
| */ |
| static int 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_TRACE_CONN, |
| "Awaiting EOF for 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; |
| } |
| |
| if((channel->remote.window_size == channel->read_avail) && |
| session->api_block_mode) |
| return _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_WINDOW_FULL, |
| "Receiving channel window " |
| "has been exhausted"); |
| |
| rc = _libssh2_transport_read(session); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if(rc < 0) { |
| channel->wait_eof_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "_libssh2_transport_read() bailed out!"); |
| } |
| } while(1); |
| |
| channel->wait_eof_state = libssh2_NB_state_idle; |
| |
| return 0; |
| } |
| |
| /* |
| * libssh2_channel_wait_eof |
| * |
| * Awaiting channel EOF |
| */ |
| LIBSSH2_API int |
| libssh2_channel_wait_eof(LIBSSH2_CHANNEL *channel) |
| { |
| int rc; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, channel->session, channel_wait_eof(channel)); |
| return rc; |
| } |
| |
| int _libssh2_channel_close(LIBSSH2_CHANNEL * channel) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| int rc = 0; |
| |
| 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->local.eof) { |
| rc = channel_send_eof(channel); |
| if(rc) { |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| _libssh2_error(session, rc, |
| "Unable to send EOF, but closing channel anyway"); |
| } |
| } |
| |
| /* ignore if we have received a remote eof or not, as it is now too |
| late for us to wait for it. Continue closing! */ |
| |
| if(channel->close_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_TRACE_CONN, "Closing channel %lu/%lu", |
| channel->local.id, channel->remote.id); |
| |
| 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) { |
| rc = _libssh2_transport_send(session, channel->close_packet, 5, |
| NULL, 0); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(session, rc, |
| "Would block sending close-channel"); |
| return rc; |
| |
| } |
| else if(rc) { |
| _libssh2_error(session, rc, |
| "Unable to send close-channel request, " |
| "but closing anyway"); |
| /* skip waiting for the response and fall through to |
| LIBSSH2_CHANNEL_CLOSE below */ |
| |
| } |
| else |
| 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 */ |
| |
| while(!channel->remote.close && !rc && |
| (session->socket_state != LIBSSH2_SOCKET_DISCONNECTED)) |
| rc = _libssh2_transport_read(session); |
| } |
| |
| if(rc != LIBSSH2_ERROR_EAGAIN) { |
| /* set the local close state first when we're perfectly confirmed to |
| not do any more EAGAINs */ |
| channel->local.close = 1; |
| |
| /* We call the callback last in this function to make it keep the local |
| data as long as EAGAIN is returned. */ |
| if(channel->close_cb) { |
| LIBSSH2_CHANNEL_CLOSE(session, channel); |
| } |
| |
| channel->close_state = libssh2_NB_state_idle; |
| } |
| |
| /* return 0 or an error */ |
| return rc >= 0 ? 0 : rc; |
| } |
| |
| /* |
| * libssh2_channel_close |
| * |
| * Close a channel |
| */ |
| LIBSSH2_API int |
| libssh2_channel_close(LIBSSH2_CHANNEL *channel) |
| { |
| int rc; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, channel->session, _libssh2_channel_close(channel) ); |
| return rc; |
| } |
| |
| /* |
| * channel_wait_closed |
| * |
| * Awaiting channel close after EOF |
| */ |
| static int channel_wait_closed(LIBSSH2_CHANNEL *channel) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| int rc; |
| |
| if(!channel->remote.eof) { |
| return _libssh2_error(session, LIBSSH2_ERROR_INVAL, |
| "libssh2_channel_wait_closed() invoked when " |
| "channel is not in EOF state"); |
| } |
| |
| if(channel->wait_closed_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_TRACE_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. |
| */ |
| if(!channel->remote.close) { |
| do { |
| rc = _libssh2_transport_read(session); |
| if(channel->remote.close) |
| /* it is now closed, move on! */ |
| break; |
| } while(rc > 0); |
| if(rc < 0) |
| return rc; |
| } |
| |
| channel->wait_closed_state = libssh2_NB_state_idle; |
| |
| return 0; |
| } |
| |
| /* |
| * libssh2_channel_wait_closed |
| * |
| * Awaiting channel close after EOF |
| */ |
| LIBSSH2_API int |
| libssh2_channel_wait_closed(LIBSSH2_CHANNEL *channel) |
| { |
| int rc; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, channel->session, channel_wait_closed(channel)); |
| return rc; |
| } |
| |
| /* |
| * _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, negative on failure |
| */ |
| int _libssh2_channel_free(LIBSSH2_CHANNEL *channel) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned char channel_id[4]; |
| unsigned char *data; |
| size_t data_len; |
| int rc; |
| |
| assert(session); |
| |
| if(channel->free_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_TRACE_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)) { |
| rc = _libssh2_channel_close(channel); |
| |
| if(rc == LIBSSH2_ERROR_EAGAIN) |
| return rc; |
| |
| /* ignore all other errors as they otherwise risk blocking the channel |
| free from happening */ |
| } |
| |
| channel->free_state = libssh2_NB_state_idle; |
| |
| if(channel->exit_signal) { |
| LIBSSH2_FREE(session, channel->exit_signal); |
| } |
| |
| /* |
| * 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(session, SSH_MSG_CHANNEL_DATA, &data, |
| &data_len, 1, channel_id, 4) >= 0) |
| || |
| (_libssh2_packet_ask(session, SSH_MSG_CHANNEL_EXTENDED_DATA, &data, |
| &data_len, 1, channel_id, 4) >= 0)) { |
| LIBSSH2_FREE(session, data); |
| } |
| |
| /* free "channel_type" */ |
| if(channel->channel_type) { |
| LIBSSH2_FREE(session, channel->channel_type); |
| } |
| |
| /* Unlink from channel list */ |
| _libssh2_list_remove(&channel->node); |
| |
| /* |
| * Make sure all memory used in the state variables are free |
| */ |
| if(channel->setenv_packet) { |
| LIBSSH2_FREE(session, channel->setenv_packet); |
| } |
| if(channel->reqX11_packet) { |
| LIBSSH2_FREE(session, channel->reqX11_packet); |
| } |
| if(channel->process_packet) { |
| LIBSSH2_FREE(session, channel->process_packet); |
| } |
| |
| LIBSSH2_FREE(session, channel); |
| |
| return 0; |
| } |
| |
| /* |
| * 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, negative on failure |
| */ |
| LIBSSH2_API int |
| libssh2_channel_free(LIBSSH2_CHANNEL *channel) |
| { |
| int rc; |
| |
| if(!channel) |
| return LIBSSH2_ERROR_BAD_USE; |
| |
| BLOCK_ADJUST(rc, channel->session, _libssh2_channel_free(channel)); |
| return rc; |
| } |
| /* |
| * 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(!channel) |
| return 0; /* no channel, no window! */ |
| |
| if(window_size_initial) { |
| *window_size_initial = channel->remote.window_size_initial; |
| } |
| |
| if(read_avail) { |
| size_t bytes_queued = 0; |
| LIBSSH2_PACKET *next_packet; |
| LIBSSH2_PACKET *packet = |
| _libssh2_list_first(&channel->session->packets); |
| |
| while(packet) { |
| unsigned char packet_type; |
| next_packet = _libssh2_list_next(&packet->node); |
| |
| if(packet->data_len < 1) { |
| packet = next_packet; |
| _libssh2_debug(channel->session, LIBSSH2_TRACE_ERROR, |
| "Unexpected packet length"); |
| continue; |
| } |
| |
| packet_type = packet->data[0]; |
| |
| if(((packet_type == SSH_MSG_CHANNEL_DATA) |
| || (packet_type == SSH_MSG_CHANNEL_EXTENDED_DATA)) |
| && ((packet->data_len >= 5) |
| && (_libssh2_ntohu32(packet->data + 1) == |
| channel->local.id))) { |
| bytes_queued += packet->data_len - packet->data_head; |
| } |
| |
| packet = next_packet; |
| } |
| |
| *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 written 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(!channel) |
| return 0; /* no channel, no window! */ |
| |
| 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; |
| } |