| /* Copyright (c) 2004-2005, Sara Golemon <sarag@libssh2.org> |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, |
| * with or without modification, are permitted provided |
| * that the following conditions are met: |
| * |
| * Redistributions of source code must retain the above |
| * copyright notice, this list of conditions and the |
| * following disclaimer. |
| * |
| * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials |
| * provided with the distribution. |
| * |
| * Neither the name of the copyright holder nor the names |
| * of any other contributors may be used to endorse or |
| * promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
| * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE |
| * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY |
| * OF SUCH DAMAGE. |
| */ |
| |
| #include "libssh2_priv.h" |
| #include <openssl/rand.h> |
| #ifndef WIN32 |
| #include <unistd.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; |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "Allocated new channel ID#%lu", id); |
| #endif |
| 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_session |
| * Establish a generic session channel |
| */ |
| LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_open_ex(LIBSSH2_SESSION *session, char *channel_type, int channel_type_len, int window_size, int packet_size, |
| char *message, int message_len) |
| { |
| unsigned char reply_codes[3] = { SSH_MSG_CHANNEL_OPEN_CONFIRMATION, SSH_MSG_CHANNEL_OPEN_FAILURE, 0 }; |
| LIBSSH2_CHANNEL *channel = NULL; |
| unsigned long local_channel = libssh2_channel_nextid(session); |
| unsigned char *s, *packet = NULL; |
| unsigned long packet_len = channel_type_len + message_len + 17; /* packet_type(1) + channel_type_len(4) + sender_channel(4) + |
| window_size(4) + packet_size(4) */ |
| unsigned char *data = NULL; |
| unsigned long data_len; |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "Opening Channel - win %d pack %d", window_size, packet_size); |
| #endif |
| channel = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_CHANNEL)); |
| if (!channel) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate space for channel data", 0); |
| return NULL; |
| } |
| memset(channel, 0, sizeof(LIBSSH2_CHANNEL)); |
| |
| channel->channel_type_len = channel_type_len; |
| channel->channel_type = LIBSSH2_ALLOC(session, channel_type_len); |
| if (!channel->channel_type) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Failed allocating memory for channel type name", 0); |
| LIBSSH2_FREE(session, channel); |
| return NULL; |
| } |
| memcpy(channel->channel_type, channel_type, channel_type_len); |
| |
| /* REMEMBER: local as in locally sourced */ |
| channel->local.id = local_channel; |
| channel->remote.window_size = window_size; |
| channel->remote.window_size_initial = window_size; |
| channel->remote.packet_size = packet_size; |
| |
| libssh2_channel_add(session, channel); |
| |
| s = packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!packet) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate temporary space for packet", 0); |
| return NULL; |
| } |
| *(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, 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; |
| } |
| |
| if (libssh2_packet_write(session, packet, packet_len)) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel-open request", 0); |
| goto channel_error; |
| } |
| |
| if (libssh2_packet_requirev_ex(session, reply_codes, &data, &data_len, 1, packet + 5 + channel_type_len, 4)) { |
| goto channel_error; |
| } |
| |
| if (data[0] == SSH_MSG_CHANNEL_OPEN_CONFIRMATION) { |
| channel->remote.id = libssh2_ntohu32(data + 5); |
| channel->local.window_size = libssh2_ntohu32(data + 9); |
| channel->local.window_size_initial = libssh2_ntohu32(data + 9); |
| channel->local.packet_size = libssh2_ntohu32(data + 13); |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "Connection Established - ID: %lu/%lu win: %lu/%lu pack: %lu/%lu", |
| channel->local.id, channel->remote.id, |
| channel->local.window_size, channel->remote.window_size, |
| channel->local.packet_size, channel->remote.packet_size); |
| #endif |
| LIBSSH2_FREE(session, packet); |
| LIBSSH2_FREE(session, data); |
| |
| return channel; |
| } |
| |
| if (data[0] == SSH_MSG_CHANNEL_OPEN_FAILURE) { |
| libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, "Channel open failure", 0); |
| } |
| |
| channel_error: |
| |
| if (data) { |
| LIBSSH2_FREE(session, data); |
| } |
| if (packet) { |
| LIBSSH2_FREE(session, packet); |
| } |
| if (channel) { |
| unsigned char channel_id[4]; |
| LIBSSH2_FREE(session, channel->channel_type); |
| |
| if (channel->next) { |
| channel->next->prev = channel->prev; |
| } |
| if (channel->prev) { |
| channel->prev->next = channel->next; |
| } |
| if (session->channels.head == channel) { |
| session->channels.head = channel->next; |
| } |
| if (session->channels.tail == channel) { |
| session->channels.tail = channel->prev; |
| } |
| |
| /* 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, 1) >= 0) || |
| (libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_EXTENDED_DATA, &data, &data_len, 1, channel_id, 4, 1) >= 0)) { |
| LIBSSH2_FREE(session, data); |
| } |
| |
| LIBSSH2_FREE(session, channel); |
| } |
| |
| 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, char *host, int port, char *shost, int sport) |
| { |
| LIBSSH2_CHANNEL *channel; |
| unsigned char *message, *s; |
| unsigned long host_len = strlen(host), shost_len = strlen(shost); |
| unsigned long message_len = host_len + shost_len + 16; /* host_len(4) + port(4) + shost_len(4) + sport(4) */ |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "Requesting direct-tcpip session to from %s:%d to %s:%d", shost, sport, host, port); |
| #endif |
| |
| s = message = LIBSSH2_ALLOC(session, message_len); |
| if (!message) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for direct-tcpip connection", 0); |
| return NULL; |
| } |
| libssh2_htonu32(s, host_len); s += 4; |
| memcpy(s, host, host_len); s += host_len; |
| libssh2_htonu32(s, port); s += 4; |
| |
| libssh2_htonu32(s, shost_len); s += 4; |
| memcpy(s, shost, shost_len); s += shost_len; |
| libssh2_htonu32(s, sport); s += 4; |
| |
| channel = libssh2_channel_open_ex(session, "direct-tcpip", sizeof("direct-tcpip") - 1, LIBSSH2_CHANNEL_WINDOW_DEFAULT, LIBSSH2_CHANNEL_PACKET_DEFAULT, message, message_len); |
| LIBSSH2_FREE(session, message); |
| |
| 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, char *host, int port, int *bound_port, int queue_maxsize) |
| { |
| unsigned char *packet, *s, *data, reply_codes[3] = { SSH_MSG_REQUEST_SUCCESS, SSH_MSG_REQUEST_FAILURE, 0 }; |
| unsigned long data_len; |
| unsigned long host_len = (host ? strlen(host) : (sizeof("0.0.0.0") - 1)); |
| unsigned long packet_len = host_len + (sizeof("tcpip-forward") - 1) + 14; |
| /* packet_type(1) + request_len(4) + want_replay(1) + host_len(4) + port(4) */ |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "Requesting tcpip-forward session for %s:%d", host, port); |
| #endif |
| |
| s = packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!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++) = 0xFF; /* want_reply */ |
| |
| libssh2_htonu32(s, host_len); s += 4; |
| memcpy(s, host ? host : "0.0.0.0", host_len); s += host_len; |
| libssh2_htonu32(s, port); s += 4; |
| |
| if (libssh2_packet_write(session, packet, packet_len)) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send global-request packet for forward listen request", 0); |
| LIBSSH2_FREE(session, packet); |
| return NULL; |
| } |
| LIBSSH2_FREE(session, packet); |
| |
| if (libssh2_packet_requirev(session, reply_codes, &data, &data_len)) { |
| 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); |
| return NULL; |
| } |
| memset(listener, 0, sizeof(LIBSSH2_LISTENER)); |
| listener->session = session; |
| listener->host = LIBSSH2_ALLOC(session, 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); |
| return NULL; |
| } |
| memcpy(listener->host, host ? host : "0.0.0.0", host_len); |
| listener->host[host_len] = 0; |
| if (data_len >= 5 && !port) { |
| listener->port = libssh2_ntohu32(data + 1); |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "Dynamic tcpip-forward port allocated: %d", listener->port); |
| #endif |
| } 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); |
| 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); |
| return NULL; |
| } |
| |
| return NULL; |
| } |
| /* }}} */ |
| |
| /* {{{ libssh2_channel_forward_cancel |
| * Stop listening on a remote port and free the listener |
| * Toss out any pending (un-accept()ed) connections |
| */ |
| 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); |
| unsigned long packet_len = host_len + 14 + sizeof("cancel-tcpip-forward") - 1; |
| /* packet_type(1) + request_len(4) + want_replay(1) + host_len(4) + port(4) */ |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "Cancelling tcpip-forward session for %s:%d", listener->host, listener->port); |
| #endif |
| |
| 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; |
| |
| if (libssh2_packet_write(session, packet, packet_len)) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send global-request packet for forward listen request", 0); |
| LIBSSH2_FREE(session, packet); |
| return -1; |
| } |
| LIBSSH2_FREE(session, packet); |
| |
| while (queued) { |
| LIBSSH2_CHANNEL *next = queued->next; |
| |
| libssh2_channel_free(queued); |
| 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); |
| |
| return 0; |
| } |
| /* }}} */ |
| |
| /* {{{ libssh2_channel_forward_accept |
| * Accept a connection |
| */ |
| LIBSSH2_API LIBSSH2_CHANNEL *libssh2_channel_forward_accept(LIBSSH2_LISTENER *listener) |
| { |
| while (libssh2_packet_read(listener->session, 0) > 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, int varname_len, char *value, int value_len) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned char *s, *packet, *data, reply_codes[3] = { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }, local_channel[4]; |
| unsigned long data_len; |
| unsigned long packet_len = varname_len + value_len + 21; /* packet_type(1) + channel_id(4) + request_len(4) + request(3)"env" + |
| want_reply(1) + varname_len(4) + value_len(4) */ |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "Setting remote environment variable: %s=%s on channel %lu/%lu", varname, value, channel->local.id, channel->remote.id); |
| #endif |
| |
| 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_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++) = 0xFF; |
| |
| 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; |
| |
| if (libssh2_packet_write(session, packet, packet_len)) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel-request packet for setenv request", 0); |
| LIBSSH2_FREE(session, packet); |
| return -1; |
| } |
| LIBSSH2_FREE(session, packet); |
| |
| libssh2_htonu32(local_channel, channel->local.id); |
| if (libssh2_packet_requirev_ex(session, reply_codes, &data, &data_len, 1, local_channel, 4)) { |
| return -1; |
| } |
| |
| if (data[0] == SSH_MSG_CHANNEL_SUCCESS) { |
| LIBSSH2_FREE(session, data); |
| return 0; |
| } |
| |
| LIBSSH2_FREE(session, data); |
| libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, "Unable to complete request for channel-setenv", 0); |
| return -1; |
| } |
| /* }}} */ |
| |
| /* {{{ libssh2_channel_request_pty_ex |
| * Duh... Request a PTY |
| */ |
| LIBSSH2_API int libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel, char *term, int term_len, |
| char *modes, int modes_len, |
| int width, int height, |
| int width_px, int height_px) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned char *s, *packet, *data, reply_codes[3] = { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }, local_channel[4]; |
| unsigned long data_len; |
| unsigned long packet_len = term_len + modes_len + 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) */ |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "Allocating tty on channel %lu/%lu", channel->local.id, channel->remote.id); |
| #endif |
| |
| s = packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!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++) = 0xFF; |
| |
| 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; |
| } |
| |
| if (libssh2_packet_write(session, packet, packet_len)) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send pty-request packet", 0); |
| LIBSSH2_FREE(session, packet); |
| return -1; |
| } |
| LIBSSH2_FREE(session, packet); |
| |
| libssh2_htonu32(local_channel, channel->local.id); |
| if (libssh2_packet_requirev_ex(session, reply_codes, &data, &data_len, 1, local_channel, 4)) { |
| return -1; |
| } |
| |
| if (data[0] == SSH_MSG_CHANNEL_SUCCESS) { |
| LIBSSH2_FREE(session, data); |
| return 0; |
| } |
| |
| LIBSSH2_FREE(session, data); |
| libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, "Unable to complete request for channel request-pty", 0); |
| 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, char *auth_proto, char *auth_cookie, int screen_number) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned char *s, *packet, *data, reply_codes[3] = { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }, local_channel[4]; |
| 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; |
| unsigned long packet_len = proto_len + cookie_len + 41; /* packet_type(1) + channel(4) + x11_req_len(4) + "x11-req"(7) + want_reply(1) + |
| single_cnx(4) + proto_len(4) + cookie_len(4) + screen_num(4) */ |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _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); |
| #endif |
| |
| s = packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!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++) = 0xFF; /* want_reply */ |
| *(s++) = single_connection ? 0xFF : 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); |
| if (auth_cookie) { |
| memcpy(s, auth_cookie, cookie_len); |
| } else { |
| int i; |
| char buffer[LIBSSH2_X11_RANDOM_COOKIE_LEN / 2]; |
| |
| RAND_bytes(buffer, LIBSSH2_X11_RANDOM_COOKIE_LEN / 2); |
| for (i = 0; i < (LIBSSH2_X11_RANDOM_COOKIE_LEN / 2); i++) { |
| snprintf(s + (i * 2), 2, "%02X", buffer[i]); |
| } |
| } |
| s += cookie_len; |
| |
| libssh2_htonu32(s, screen_number); s += 4; |
| |
| if (libssh2_packet_write(session, packet, packet_len)) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send pty-request packet", 0); |
| LIBSSH2_FREE(session, packet); |
| return -1; |
| } |
| LIBSSH2_FREE(session, packet); |
| |
| libssh2_htonu32(local_channel, channel->local.id); |
| if (libssh2_packet_requirev_ex(session, reply_codes, &data, &data_len, 1, local_channel, 4)) { |
| LIBSSH2_FREE(session, data); |
| 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, char *request, int request_len, char *message, int message_len) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned char *s, *packet, *data, reply_codes[3] = { SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, 0 }, local_channel[4]; |
| unsigned long data_len; |
| unsigned long packet_len = request_len + 10; /* packet_type(1) + channel(4) + request_len(4) + want_reply(1) */ |
| |
| if (message) { |
| packet_len += message_len + 4; |
| } |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "starting request(%s) on channel %lu/%lu, message=%s", request, channel->local.id, channel->remote.id, message); |
| #endif |
| |
| s = packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!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++) = 0xFF; |
| |
| if (message) { |
| libssh2_htonu32(s, message_len); s += 4; |
| memcpy(s, message, message_len); s += message_len; |
| } |
| |
| if (libssh2_packet_write(session, packet, packet_len)) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel request", 0); |
| LIBSSH2_FREE(session, packet); |
| return -1; |
| } |
| LIBSSH2_FREE(session, packet); |
| |
| libssh2_htonu32(local_channel, channel->local.id); |
| if (libssh2_packet_requirev_ex(session, reply_codes, &data, &data_len, 1, local_channel, 4)) { |
| return -1; |
| } |
| |
| if (data[0] == SSH_MSG_CHANNEL_SUCCESS) { |
| LIBSSH2_FREE(session, data); |
| return 0; |
| } |
| |
| LIBSSH2_FREE(session, data); |
| libssh2_error(session, LIBSSH2_ERROR_CHANNEL_REQUEST_DENIED, "Unable to complete request for channel-process-startup", 0); |
| 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) |
| { |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(channel->session, LIBSSH2_DBG_CONN, "Setting blocking mode on channel %lu/%lu to %d", channel->local.id, channel->remote.id, blocking); |
| #endif |
| channel->blocking = 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; |
| unsigned long refund_bytes = 0, 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 */ |
| unsigned 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; |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _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); |
| #endif |
| |
| /* It's one of the streams we wanted to flush */ |
| refund_bytes += packet->data_len - 13; |
| 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; |
| } |
| |
| if (refund_bytes && channel->remote.window_size_initial) { |
| libssh2_channel_receive_window_adjust(channel, refund_bytes, 0); |
| } |
| |
| return flush_bytes; |
| } |
| /* }}} */ |
| |
| /* {{{ 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) |
| { |
| unsigned char adjust[9]; /* packet_type(1) + channel(4) + adjustment(4) */ |
| |
| if (!force && (adjustment + channel->adjust_queue < LIBSSH2_CHANNEL_MINADJUST)) { |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(channel->session, LIBSSH2_DBG_CONN, "Queing %lu bytes for receive window adjustment for channel %lu/%lu", adjustment, channel->local.id, channel->remote.id); |
| #endif |
| 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 */ |
| adjust[0] = SSH_MSG_CHANNEL_WINDOW_ADJUST; |
| libssh2_htonu32(adjust + 1, channel->remote.id); |
| libssh2_htonu32(adjust + 5, adjustment); |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _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); |
| #endif |
| |
| if (libssh2_packet_write(channel->session, adjust, 9)) { |
| 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) |
| { |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _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); |
| #endif |
| channel->remote.extended_data_ignore_mode = ignore_mode; |
| |
| if (ignore_mode == LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE) { |
| libssh2_channel_flush_ex(channel, LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA); |
| } |
| } |
| /* }}} */ |
| |
| /* {{{ libssh2_channel_read_ex |
| * Read data from a channel |
| */ |
| LIBSSH2_API int libssh2_channel_read_ex(LIBSSH2_CHANNEL *channel, int stream_id, char *buf, size_t buflen) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| int bytes_read = 0, blocking_read = 0; |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _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); |
| #endif |
| do { |
| LIBSSH2_PACKET *packet; |
| |
| /* Process any waiting packets */ |
| while (libssh2_packet_read(session, blocking_read) > 0) blocking_read = 0; |
| packet = session->packets.head; |
| |
| while (packet && (bytes_read < buflen)) { |
| /* In case packet gets destroyed during this iteration */ |
| LIBSSH2_PACKET *next = packet->next; |
| |
| /* 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 && (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) && (channel->local.id == libssh2_ntohu32(packet->data + 1)) && (stream_id == libssh2_ntohu32(packet->data + 5))) || |
| (!stream_id && (packet->data[0] == SSH_MSG_CHANNEL_DATA) && (channel->local.id == libssh2_ntohu32(packet->data + 1))) || |
| (!stream_id && (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) && (channel->local.id == libssh2_ntohu32(packet->data + 1)) && (channel->remote.extended_data_ignore_mode == LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE))) { |
| int want = buflen - bytes_read; |
| int unlink_packet = 0; |
| |
| if (want >= (packet->data_len - packet->data_head)) { |
| want = packet->data_len - packet->data_head; |
| unlink_packet = 1; |
| } |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "Reading %d of buffered data from %lu/%lu/%d", want, channel->local.id, channel->remote.id, stream_id); |
| #endif |
| memcpy(buf + bytes_read, packet->data + packet->data_head, want); |
| packet->data_head += want; |
| bytes_read += want; |
| |
| if (unlink_packet) { |
| if (packet->prev) { |
| packet->prev->next = packet->next; |
| } else { |
| session->packets.head = packet->next; |
| } |
| if (packet->next) { |
| packet->next->prev = packet->prev; |
| } else { |
| session->packets.tail = packet->prev; |
| } |
| LIBSSH2_FREE(session, packet->data); |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "Unlinking empty packet buffer from channel %lu/%lu", channel->local.id, channel->remote.id); |
| #endif |
| libssh2_channel_receive_window_adjust(channel, packet->data_len - (stream_id ? 13 : 9), 0); |
| LIBSSH2_FREE(session, packet); |
| } |
| } |
| packet = next; |
| } |
| blocking_read = 1; |
| } while (channel->blocking && (bytes_read == 0) && !channel->remote.close); |
| |
| if (channel->blocking && (bytes_read == 0)) { |
| libssh2_error(session, LIBSSH2_ERROR_CHANNEL_CLOSED, "Remote end has closed this channel", 0); |
| } |
| |
| return bytes_read; |
| } |
| /* }}} */ |
| |
| /* {{{ libssh2_channel_write_ex |
| * Send data to a channel |
| */ |
| LIBSSH2_API int libssh2_channel_write_ex(LIBSSH2_CHANNEL *channel, int stream_id, const char *buf, size_t buflen) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned char *packet; |
| unsigned long packet_len, bufwrote = 0; |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _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); |
| #endif |
| 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->blocking && (channel->local.window_size <= 0)) { |
| /* Can't write anything */ |
| return 0; |
| } |
| |
| packet_len = buflen + (stream_id ? 13 : 9); /* packet_type(1) + channelno(4) [ + streamid(4) ] + buflen(4) */ |
| packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!packet) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocte space for data transmission packet", 0); |
| return -1; |
| } |
| |
| while (buflen > 0) { |
| size_t bufwrite = buflen; |
| unsigned char *s = packet; |
| |
| *(s++) = stream_id ? SSH_MSG_CHANNEL_EXTENDED_DATA : SSH_MSG_CHANNEL_DATA; |
| libssh2_htonu32(s, channel->remote.id); s += 4; |
| if (stream_id) { |
| libssh2_htonu32(s, stream_id); 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 */ |
| if (libssh2_packet_read(session, 1) < 0) { |
| /* Error occured, disconnect? */ |
| return -1; |
| } |
| } |
| |
| /* Don't exceed the remote end's limits */ |
| /* REMEMBER local means local as the SOURCE of the data */ |
| if (bufwrite > channel->local.window_size) { |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _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); |
| #endif |
| bufwrite = channel->local.window_size; |
| } |
| if (bufwrite > channel->local.packet_size) { |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _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); |
| #endif |
| bufwrite = channel->local.packet_size; |
| } |
| libssh2_htonu32(s, bufwrite); s += 4; |
| memcpy(s, buf, bufwrite); s += bufwrite; |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "Sending %d bytes on channel %lu/%lu, stream_id=%d", (int)bufwrite, channel->local.id, channel->remote.id, stream_id); |
| #endif |
| if (libssh2_packet_write(session, packet, s - packet)) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send channel data", 0); |
| LIBSSH2_FREE(session, packet); |
| return -1; |
| } |
| /* Shrink local window size */ |
| channel->local.window_size -= bufwrite; |
| |
| /* Adjust buf for next iteration */ |
| buflen -= bufwrite; |
| buf += bufwrite; |
| bufwrote += bufwrite; |
| |
| if (!channel->blocking) { |
| break; |
| } |
| } |
| |
| LIBSSH2_FREE(session, packet); |
| |
| return 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) */ |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "Sending EOF on channel %lu/%lu",channel->local.id, channel->remote.id); |
| #endif |
| packet[0] = SSH_MSG_CHANNEL_EOF; |
| libssh2_htonu32(packet + 1, channel->remote.id); |
| if (libssh2_packet_write(session, packet, 5)) { |
| 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_close |
| * Close a channel |
| */ |
| LIBSSH2_API int libssh2_channel_close(LIBSSH2_CHANNEL *channel) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned char packet[5]; |
| |
| if (channel->local.close) { |
| /* Already closed, act like we sent another close, even though we didn't... shhhhhh */ |
| return 0; |
| } |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "Closing channel %lu/%lu", channel->local.id, channel->remote.id); |
| #endif |
| |
| if (channel->close_cb) { |
| LIBSSH2_CHANNEL_CLOSE(session, channel); |
| } |
| |
| packet[0] = SSH_MSG_CHANNEL_CLOSE; |
| libssh2_htonu32(packet + 1, channel->remote.id); |
| if (libssh2_packet_write(session, packet, 5)) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send close-channel request", 0); |
| return -1; |
| } |
| channel->local.close = 1; |
| |
| /* TODO: Wait up to a timeout value for a CHANNEL_CLOSE to come back, to avoid the problem alluded to in channel_nextid */ |
| |
| return 0; |
| } |
| /* }}} */ |
| |
| /* {{{ libssh2_channel_free |
| * Make sure a channel is closed, then remove the channel from the session and free its resource(s) |
| */ |
| LIBSSH2_API int libssh2_channel_free(LIBSSH2_CHANNEL *channel) |
| { |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned char channel_id[4], *data; |
| unsigned long data_len; |
| |
| #ifdef LIBSSH2_DEBUG_CONNECTION |
| _libssh2_debug(session, LIBSSH2_DBG_CONN, "Freeing channel %lu/%lu resources", channel->local.id, channel->remote.id); |
| #endif |
| /* Allow channel freeing even when the socket has lost its connection */ |
| if (!channel->local.close && (session->socket_state == LIBSSH2_SOCKET_CONNECTED) && |
| libssh2_channel_close(channel)) { |
| return -1; |
| } |
| |
| /* 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, 1) >= 0) || |
| (libssh2_packet_ask_ex(session, SSH_MSG_CHANNEL_EXTENDED_DATA, &data, &data_len, 1, channel_id, 4, 1) >= 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; |
| } |
| |
| 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; |
| } |
| /* }}} */ |