diff options
Diffstat (limited to 'src/channels.c')
-rw-r--r-- | src/channels.c | 2495 |
1 files changed, 2495 insertions, 0 deletions
diff --git a/src/channels.c b/src/channels.c new file mode 100644 index 00000000..6900beea --- /dev/null +++ b/src/channels.c @@ -0,0 +1,2495 @@ +/* + * channels.c - SSH channel functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * Copyright (c) 2009 by Andreas Schneider <mail@cynapses.org> + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <time.h> + +#ifndef _WIN32 +#include <arpa/inet.h> +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/socket.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/messages.h" + +#define WINDOWBASE 128000 +#define WINDOWLIMIT (WINDOWBASE/2) + +/** + * @defgroup libssh_channel The SSH channel functions + * @ingroup libssh + * + * Functions that manage a SSH channel. + * + * @{ + */ + +static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet); + +/** + * @brief Allocate a new channel. + * + * @param[in] session The ssh session to use. + * + * @return A pointer to a newly allocated channel, NULL on error. + */ +ssh_channel ssh_channel_new(ssh_session session) { + ssh_channel channel = NULL; + + channel = malloc(sizeof(struct ssh_channel_struct)); + if (channel == NULL) { + return NULL; + } + memset(channel,0,sizeof(struct ssh_channel_struct)); + + channel->stdout_buffer = ssh_buffer_new(); + if (channel->stdout_buffer == NULL) { + SAFE_FREE(channel); + return NULL; + } + + channel->stderr_buffer = ssh_buffer_new(); + if (channel->stderr_buffer == NULL) { + ssh_buffer_free(channel->stdout_buffer); + SAFE_FREE(channel); + return NULL; + } + + channel->session = session; + channel->version = session->version; + channel->exit_status = -1; + + if(session->channels == NULL) { + session->channels = channel; + channel->next = channel->prev = channel; + return channel; + } + channel->next = session->channels; + channel->prev = session->channels->prev; + channel->next->prev = channel; + channel->prev->next = channel; + + return channel; +} + +/** + * @internal + * + * @brief Create a new channel identifier. + * + * @param[in] session The SSH session to use. + * + * @return The new channel identifier. + */ +uint32_t ssh_channel_new_id(ssh_session session) { + return ++(session->maxchannel); +} + +/** + * @internal + * + * @brief Handle a SSH_PACKET_CHANNEL_OPEN_CONFIRMATION packet. + * + * Constructs the channel object. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf){ + uint32_t channelid=0; + uint32_t tmp; + ssh_channel channel; + (void)type; + (void)user; + enter_function(); + ssh_log(session,SSH_LOG_PACKET,"Received SSH2_MSG_CHANNEL_OPEN_CONFIRMATION"); + + buffer_get_u32(packet, &channelid); + channelid=ntohl(channelid); + channel=ssh_channel_from_local(session,channelid); + if(channel==NULL){ + ssh_set_error(session, SSH_FATAL, + "Unknown channel id %lu", + (long unsigned int) channelid); + /* TODO: Set error marking in channel object */ + leave_function(); + return SSH_PACKET_USED; + } + + buffer_get_u32(packet, &tmp); + channel->remote_channel = ntohl(tmp); + + buffer_get_u32(packet, &tmp); + channel->remote_window = ntohl(tmp); + + buffer_get_u32(packet,&tmp); + channel->remote_maxpacket=ntohl(tmp); + + ssh_log(session, SSH_LOG_PROTOCOL, + "Received a CHANNEL_OPEN_CONFIRMATION for channel %d:%d", + channel->local_channel, + channel->remote_channel); + ssh_log(session, SSH_LOG_PROTOCOL, + "Remote window : %lu, maxpacket : %lu", + (long unsigned int) channel->remote_window, + (long unsigned int) channel->remote_maxpacket); + + channel->state = SSH_CHANNEL_STATE_OPEN; + leave_function(); + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_CHANNEL_OPEN_FAILURE and set the state of the channel. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail){ + + ssh_channel channel; + ssh_string error_s; + char *error = NULL; + uint32_t code; + (void)user; + (void)type; + channel=channel_from_msg(session,packet); + if(channel==NULL){ + ssh_log(session,SSH_LOG_RARE,"Invalid channel in packet"); + return SSH_PACKET_USED; + } + buffer_get_u32(packet, &code); + + error_s = buffer_get_ssh_string(packet); + if(error_s != NULL) + error = ssh_string_to_char(error_s); + ssh_string_free(error_s); + if (error == NULL) { + ssh_set_error_oom(session); + return SSH_PACKET_USED; + } + + ssh_set_error(session, SSH_REQUEST_DENIED, + "Channel opening failure: channel %u error (%lu) %s", + channel->local_channel, + (long unsigned int) ntohl(code), + error); + SAFE_FREE(error); + channel->state=SSH_CHANNEL_STATE_OPEN_DENIED; + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Open a channel by sending a SSH_OPEN_CHANNEL message and + * wait for the reply. + * + * @param[in] channel The current channel. + * + * @param[in] type_c A C string describing the kind of channel (e.g. "exec"). + * + * @param[in] window The receiving window of the channel. The window is the + * maximum size of data that can stay in buffers and + * network. + * + * @param[in] maxpacket The maximum packet size allowed (like MTU). + * + * @param[in] payload The buffer containing additional payload for the query. + */ +static int channel_open(ssh_channel channel, const char *type_c, int window, + int maxpacket, ssh_buffer payload) { + ssh_session session = channel->session; + ssh_string type = NULL; + int err=SSH_ERROR; + + enter_function(); + channel->local_channel = ssh_channel_new_id(session); + channel->local_maxpacket = maxpacket; + channel->local_window = window; + + ssh_log(session, SSH_LOG_PROTOCOL, + "Creating a channel %d with %d window and %d max packet", + channel->local_channel, window, maxpacket); + + type = ssh_string_from_char(type_c); + if (type == NULL) { + leave_function(); + return err; + } + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_OPEN) < 0 || + buffer_add_ssh_string(session->out_buffer,type) < 0 || + buffer_add_u32(session->out_buffer, htonl(channel->local_channel)) < 0 || + buffer_add_u32(session->out_buffer, htonl(channel->local_window)) < 0 || + buffer_add_u32(session->out_buffer, htonl(channel->local_maxpacket)) < 0) { + ssh_string_free(type); + leave_function(); + return err; + } + + ssh_string_free(type); + + if (payload != NULL) { + if (buffer_add_buffer(session->out_buffer, payload) < 0) { + leave_function(); + return err; + } + } + + if (packet_send(session) == SSH_ERROR) { + leave_function(); + return err; + } + + ssh_log(session, SSH_LOG_PACKET, + "Sent a SSH_MSG_CHANNEL_OPEN type %s for channel %d", + type_c, channel->local_channel); + + /* Todo: fix this into a correct loop */ + /* wait until channel is opened by server */ + while(channel->state == SSH_CHANNEL_STATE_NOT_OPEN){ + ssh_handle_packets(session,-1); + } + if(channel->state == SSH_CHANNEL_STATE_OPEN) + err=SSH_OK; + leave_function(); + return err; +} + +/* get ssh channel from local session? */ +ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id) { + ssh_channel initchan = session->channels; + ssh_channel channel; + + /* We assume we are always the local */ + if (initchan == NULL) { + return NULL; + } + + for (channel = initchan; channel->local_channel != id; + channel=channel->next) { + if (channel->next == initchan) { + return NULL; + } + } + + return channel; +} + +static int grow_window(ssh_session session, ssh_channel channel, int minimumsize) { + uint32_t new_window = minimumsize > WINDOWBASE ? minimumsize : WINDOWBASE; + + enter_function(); + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_WINDOW_ADJUST) < 0 || + buffer_add_u32(session->out_buffer, htonl(channel->remote_channel)) < 0 || + buffer_add_u32(session->out_buffer, htonl(new_window)) < 0) { + goto error; + } + + if (packet_send(session) == SSH_ERROR) { + /* FIXME should we fail here or not? */ + leave_function(); + return 1; + } + + ssh_log(session, SSH_LOG_PROTOCOL, + "growing window (channel %d:%d) to %d bytes", + channel->local_channel, + channel->remote_channel, + channel->local_window + new_window); + + channel->local_window += new_window; + + leave_function(); + return 0; +error: + buffer_reinit(session->out_buffer); + + leave_function(); + return -1; +} + +/** + * @internal + * + * @brief Parse a channel-related packet to resolve it to a ssh_channel. + * + * This works on SSH1 sessions too. + * + * @param[in] session The current SSH session. + * + * @param[in] packet The buffer to parse packet from. The read pointer will + * be moved after the call. + * + * @returns The related ssh_channel, or NULL if the channel is + * unknown or the packet is invalid. + */ +static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet) { + ssh_channel channel; + uint32_t chan; +#ifdef WITH_SSH1 + /* With SSH1, the channel is always the first one */ + if(session->version==1) + return session->channels; +#endif + if (buffer_get_u32(packet, &chan) != sizeof(uint32_t)) { + ssh_set_error(session, SSH_FATAL, + "Getting channel from message: short read"); + return NULL; + } + + channel = ssh_channel_from_local(session, ntohl(chan)); + if (channel == NULL) { + ssh_set_error(session, SSH_FATAL, + "Server specified invalid channel %lu", + (long unsigned int) ntohl(chan)); + } + + return channel; +} + +SSH_PACKET_CALLBACK(channel_rcv_change_window) { + ssh_channel channel; + uint32_t bytes; + int rc; + (void)user; + (void)type; + enter_function(); + + channel = channel_from_msg(session,packet); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + } + + rc = buffer_get_u32(packet, &bytes); + if (channel == NULL || rc != sizeof(uint32_t)) { + ssh_log(session, SSH_LOG_PACKET, + "Error getting a window adjust message: invalid packet"); + leave_function(); + return SSH_PACKET_USED; + } + + bytes = ntohl(bytes); + ssh_log(session, SSH_LOG_PROTOCOL, + "Adding %d bytes to channel (%d:%d) (from %d bytes)", + bytes, + channel->local_channel, + channel->remote_channel, + channel->remote_window); + + channel->remote_window += bytes; + + leave_function(); + return SSH_PACKET_USED; +} + +/* is_stderr is set to 1 if the data are extended, ie stderr */ +SSH_PACKET_CALLBACK(channel_rcv_data){ + ssh_channel channel; + ssh_string str; + size_t len; + int is_stderr; + (void)user; + enter_function(); + if(type==SSH2_MSG_CHANNEL_DATA) + is_stderr=0; + else + is_stderr=1; + + channel = channel_from_msg(session,packet); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS, + "%s", ssh_get_error(session)); + leave_function(); + return SSH_PACKET_USED; + } + + if (is_stderr) { + uint32_t ignore; + /* uint32 data type code. we can ignore it */ + buffer_get_u32(packet, &ignore); + } + + str = buffer_get_ssh_string(packet); + if (str == NULL) { + ssh_log(session, SSH_LOG_PACKET, "Invalid data packet!"); + leave_function(); + return SSH_PACKET_USED; + } + len = ssh_string_len(str); + + ssh_log(session, SSH_LOG_PROTOCOL, + "Channel receiving %zu bytes data in %d (local win=%d remote win=%d)", + len, + is_stderr, + channel->local_window, + channel->remote_window); + + /* What shall we do in this case? Let's accept it anyway */ + if (len > channel->local_window) { + ssh_log(session, SSH_LOG_RARE, + "Data packet too big for our window(%zu vs %d)", + len, + channel->local_window); + } + + if (channel_default_bufferize(channel, ssh_string_data(str), len, + is_stderr) < 0) { + ssh_string_free(str); + leave_function(); + return SSH_PACKET_USED; + } + + if (len <= channel->local_window) { + channel->local_window -= len; + } else { + channel->local_window = 0; /* buggy remote */ + } + + ssh_log(session, SSH_LOG_PROTOCOL, + "Channel windows are now (local win=%d remote win=%d)", + channel->local_window, + channel->remote_window); + + ssh_string_free(str); + leave_function(); + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(channel_rcv_eof) { + ssh_channel channel; + (void)user; + (void)type; + enter_function(); + + channel = channel_from_msg(session,packet); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + leave_function(); + return SSH_PACKET_USED; + } + + ssh_log(session, SSH_LOG_PACKET, + "Received eof on channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + /* channel->remote_window = 0; */ + channel->remote_eof = 1; + + leave_function(); + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(channel_rcv_close) { + ssh_channel channel; + (void)user; + (void)type; + enter_function(); + + channel = channel_from_msg(session,packet); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + leave_function(); + return SSH_PACKET_USED; + } + + ssh_log(session, SSH_LOG_PACKET, + "Received close on channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + + if ((channel->stdout_buffer && + buffer_get_rest_len(channel->stdout_buffer) > 0) || + (channel->stderr_buffer && + buffer_get_rest_len(channel->stderr_buffer) > 0)) { + channel->delayed_close = 1; + } else { + channel->state = SSH_CHANNEL_STATE_CLOSED; + } + + if (channel->remote_eof == 0) { + ssh_log(session, SSH_LOG_PACKET, + "Remote host not polite enough to send an eof before close"); + } + channel->remote_eof = 1; + /* + * The remote eof doesn't break things if there was still data into read + * buffer because the eof is ignored until the buffer is empty. + */ + + leave_function(); + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(channel_rcv_request) { + ssh_channel channel; + ssh_string request_s; + char *request; + uint32_t status; + (void)user; + (void)type; + + enter_function(); + + channel = channel_from_msg(session,packet); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS,"%s", ssh_get_error(session)); + leave_function(); + return SSH_PACKET_USED; + } + + request_s = buffer_get_ssh_string(packet); + if (request_s == NULL) { + ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + leave_function(); + return SSH_PACKET_USED; + } + + request = ssh_string_to_char(request_s); + ssh_string_free(request_s); + if (request == NULL) { + leave_function(); + return SSH_PACKET_USED; + } + + buffer_get_u8(packet, (uint8_t *) &status); + + if (strcmp(request,"exit-status") == 0) { + SAFE_FREE(request); + ssh_log(session, SSH_LOG_PACKET, "received exit-status"); + buffer_get_u32(packet, &status); + channel->exit_status = ntohl(status); + + leave_function(); + return SSH_PACKET_USED; + } + + if (strcmp(request, "exit-signal") == 0) { + const char *core = "(core dumped)"; + ssh_string signal_s; + char *sig; + uint8_t i; + + SAFE_FREE(request); + + signal_s = buffer_get_ssh_string(packet); + if (signal_s == NULL) { + ssh_log(session, SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + leave_function(); + return SSH_PACKET_USED; + } + + sig = ssh_string_to_char(signal_s); + ssh_string_free(signal_s); + if (sig == NULL) { + leave_function(); + return SSH_PACKET_USED; + } + + buffer_get_u8(packet, &i); + if (i == 0) { + core = ""; + } + + ssh_log(session, SSH_LOG_PACKET, + "Remote connection closed by signal SIG %s %s", sig, core); + SAFE_FREE(sig); + + leave_function(); + return SSH_PACKET_USED; + } + if(strcmp(request,"keepalive@openssh.com")==0){ + SAFE_FREE(request); + ssh_log(session, SSH_LOG_PROTOCOL,"Responding to Openssh's keepalive"); + buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_FAILURE); + buffer_add_u32(session->out_buffer, htonl(channel->remote_channel)); + packet_send(session); + leave_function(); + return SSH_PACKET_USED; + } + + /* If we are here, that means we have a request that is not in the understood + * client requests. That means we need to create a ssh message to be passed + * to the user code handling ssh messages + */ + ssh_message_handle_channel_request(session,channel,packet,request,status); + + SAFE_FREE(request); + + leave_function(); + return SSH_PACKET_USED; +} + +/* + * When data has been received from the ssh server, it can be applied to the + * known user function, with help of the callback, or inserted here + * + * FIXME is the window changed? + */ +int channel_default_bufferize(ssh_channel channel, void *data, int len, + int is_stderr) { + ssh_session session = channel->session; + + ssh_log(session, SSH_LOG_RARE, + "placing %d bytes into channel buffer (stderr=%d)", len, is_stderr); + if (is_stderr == 0) { + /* stdout */ + if (channel->stdout_buffer == NULL) { + channel->stdout_buffer = ssh_buffer_new(); + if (channel->stdout_buffer == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + + if (buffer_add_data(channel->stdout_buffer, data, len) < 0) { + ssh_buffer_free(channel->stdout_buffer); + channel->stdout_buffer = NULL; + return -1; + } + } else { + /* stderr */ + if (channel->stderr_buffer == NULL) { + channel->stderr_buffer = ssh_buffer_new(); + if (channel->stderr_buffer == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + + if (buffer_add_data(channel->stderr_buffer, data, len) < 0) { + ssh_buffer_free(channel->stderr_buffer); + channel->stderr_buffer = NULL; + return -1; + } + } + + return 0; +} + +/** + * @brief Open a session channel (suited for a shell, not TCP forwarding). + * + * @param[in] channel An allocated channel. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + * + * @see channel_open_forward() + * @see channel_request_env() + * @see channel_request_shell() + * @see channel_request_exec() + */ +int ssh_channel_open_session(ssh_channel channel) { +#ifdef WITH_SSH1 + if (channel->session->version == 1) { + return channel_open_session1(channel); + } +#endif + + return channel_open(channel,"session",64000,32000,NULL); +} + +/** + * @brief Open a TCP/IP forwarding channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] remotehost The remote host to connected (host name or IP). + * + * @param[in] remoteport The remote port. + * + * @param[in] sourcehost The source host (your local computer). It's optional + * and for logging purpose. + * + * @param[in] localport The source port (your local computer). It's optional + * and for logging purpose. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + * + * @warning This function does not bind the local port and does not automatically + * forward the content of a socket to the channel. You still have to + * use channel_read and channel_write for this. + */ +int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport) { + ssh_session session = channel->session; + ssh_buffer payload = NULL; + ssh_string str = NULL; + int rc = SSH_ERROR; + + enter_function(); + + payload = ssh_buffer_new(); + if (payload == NULL) { + goto error; + } + str = ssh_string_from_char(remotehost); + if (str == NULL) { + goto error; + } + + if (buffer_add_ssh_string(payload, str) < 0 || + buffer_add_u32(payload,htonl(remoteport)) < 0) { + goto error; + } + + ssh_string_free(str); + str = ssh_string_from_char(sourcehost); + if (str == NULL) { + goto error; + } + + if (buffer_add_ssh_string(payload, str) < 0 || + buffer_add_u32(payload,htonl(localport)) < 0) { + goto error; + } + + rc = channel_open(channel, "direct-tcpip", 64000, 32000, payload); + +error: + ssh_buffer_free(payload); + ssh_string_free(str); + + leave_function(); + return rc; +} + +/** + * @brief Close and free a channel. + * + * @param[in] channel The channel to free. + * + * @warning Any data unread on this channel will be lost. + */ +void ssh_channel_free(ssh_channel channel) { + ssh_session session = channel->session; + enter_function(); + + if (channel == NULL) { + leave_function(); + return; + } + + if (session->alive && channel->state == SSH_CHANNEL_STATE_OPEN) { + ssh_channel_close(channel); + } + + /* handle the "my channel is first on session list" case */ + if (session->channels == channel) { + session->channels = channel->next; + } + + /* handle the "my channel is the only on session list" case */ + if (channel->next == channel) { + session->channels = NULL; + } else { + channel->prev->next = channel->next; + channel->next->prev = channel->prev; + } + + ssh_buffer_free(channel->stdout_buffer); + ssh_buffer_free(channel->stderr_buffer); + + /* debug trick to catch use after frees */ + memset(channel, 'X', sizeof(struct ssh_channel_struct)); + SAFE_FREE(channel); + + leave_function(); +} + +/** + * @brief Send an end of file on the channel. + * + * This doesn't close the channel. You may still read from it but not write. + * + * @param[in] channel The channel to send the eof to. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + * + * @see channel_close() + * @see channel_free() + */ +int ssh_channel_send_eof(ssh_channel channel){ + ssh_session session = channel->session; + int rc = SSH_ERROR; + + enter_function(); + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_EOF) < 0) { + goto error; + } + if (buffer_add_u32(session->out_buffer,htonl(channel->remote_channel)) < 0) { + goto error; + } + rc = packet_send(session); + ssh_log(session, SSH_LOG_PACKET, + "Sent a EOF on client channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + + channel->local_eof = 1; + + leave_function(); + return rc; +error: + buffer_reinit(session->out_buffer); + + leave_function(); + return rc; +} + +/** + * @brief Close a channel. + * + * This sends an end of file and then closes the channel. You won't be able + * to recover any data the server was going to send or was in buffers. + * + * @param[in] channel The channel to close. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + * + * @see channel_free() + * @see channel_eof() + */ +int ssh_channel_close(ssh_channel channel){ + ssh_session session = channel->session; + int rc = 0; + + enter_function(); + + if (channel->local_eof == 0) { + rc = ssh_channel_send_eof(channel); + } + + if (rc != SSH_OK) { + leave_function(); + return rc; + } + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_CLOSE) < 0 || + buffer_add_u32(session->out_buffer, htonl(channel->remote_channel)) < 0) { + goto error; + } + + rc = packet_send(session); + ssh_log(session, SSH_LOG_PACKET, + "Sent a close on client channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + + if(rc == SSH_OK) { + channel->state=SSH_CHANNEL_STATE_CLOSED; + } + + leave_function(); + return rc; +error: + buffer_reinit(session->out_buffer); + + leave_function(); + return rc; +} + +int channel_write_common(ssh_channel channel, const void *data, + uint32_t len, int is_stderr) { + ssh_session session = channel->session; + int origlen = len; + size_t effectivelen; + /* handle the max packet len from remote side, be nice */ + /* 10 bytes for the headers */ + size_t maxpacketlen = channel->remote_maxpacket - 10; + + enter_function(); + + if (channel->local_eof) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Can't write to channel %d:%d after EOF was sent", + channel->local_channel, + channel->remote_channel); + leave_function(); + return -1; + } + + if (channel->state != SSH_CHANNEL_STATE_OPEN || channel->delayed_close != 0) { + ssh_set_error(session, SSH_REQUEST_DENIED, "Remote channel is closed"); + leave_function(); + return -1; + } + +#ifdef WITH_SSH1 + if (channel->version == 1) { + int rc = channel_write1(channel, data, len); + leave_function(); + return rc; + } +#endif + + while (len > 0) { + if (channel->remote_window < len) { + ssh_log(session, SSH_LOG_PROTOCOL, + "Remote window is %d bytes. going to write %d bytes", + channel->remote_window, + len); + ssh_log(session, SSH_LOG_PROTOCOL, + "Waiting for a growing window message..."); + /* What happens when the channel window is zero? */ + while(channel->remote_window == 0) { + /* parse every incoming packet */ + if (ssh_handle_packets(session,-1) == SSH_ERROR) { + leave_function(); + return SSH_ERROR; + } + } + effectivelen = len > channel->remote_window ? channel->remote_window : len; + } else { + effectivelen = len; + } + effectivelen = effectivelen > maxpacketlen ? maxpacketlen : effectivelen; + if (buffer_add_u8(session->out_buffer, is_stderr ? + SSH2_MSG_CHANNEL_EXTENDED_DATA : SSH2_MSG_CHANNEL_DATA) < 0 || + buffer_add_u32(session->out_buffer, + htonl(channel->remote_channel)) < 0 || + buffer_add_u32(session->out_buffer, htonl(effectivelen)) < 0 || + buffer_add_data(session->out_buffer, data, effectivelen) < 0) { + goto error; + } + + if (packet_send(session) == SSH_ERROR) { + leave_function(); + return SSH_ERROR; + } + + ssh_log(session, SSH_LOG_RARE, + "channel_write wrote %ld bytes", effectivelen); + + channel->remote_window -= effectivelen; + len -= effectivelen; + data = ((uint8_t*)data + effectivelen); + } + + leave_function(); + return origlen; +error: + buffer_reinit(session->out_buffer); + + leave_function(); + return SSH_ERROR; +} + +/** + * @brief Blocking write on a channel. + * + * @param[in] channel The channel to write to. + * + * @param[in] data A pointer to the data to write. + * + * @param[in] len The length of the buffer to write to. + * + * @return The number of bytes written, SSH_ERROR on error. + * + * @see channel_read() + */ +int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len) { + return channel_write_common(channel, data, len, 0); +} + +/** + * @brief Check if the channel is open or not. + * + * @param[in] channel The channel to check. + * + * @return 0 if channel is closed, nonzero otherwise. + * + * @see channel_is_closed() + */ +int ssh_channel_is_open(ssh_channel channel) { + return (channel->state == SSH_CHANNEL_STATE_OPEN && channel->session->alive != 0); +} + +/** + * @brief Check if the channel is closed or not. + * + * @param[in] channel The channel to check. + * + * @return 0 if channel is opened, nonzero otherwise. + * + * @see channel_is_open() + */ +int ssh_channel_is_closed(ssh_channel channel) { + return (channel->state != SSH_CHANNEL_STATE_OPEN || channel->session->alive == 0); +} + +/** + * @brief Check if remote has sent an EOF. + * + * @param[in] channel The channel to check. + * + * @return 0 if there is no EOF, nonzero otherwise. + */ +int ssh_channel_is_eof(ssh_channel channel) { + if ((channel->stdout_buffer && + buffer_get_rest_len(channel->stdout_buffer) > 0) || + (channel->stderr_buffer && + buffer_get_rest_len(channel->stderr_buffer) > 0)) { + return 0; + } + + return (channel->remote_eof != 0); +} + +/** + * @brief Put the channel into blocking or nonblocking mode. + * + * @param[in] channel The channel to use. + * + * @param[in] blocking A boolean for blocking or nonblocking. + * + * @bug This functionality is still under development and + * doesn't work correctly. + */ +void ssh_channel_set_blocking(ssh_channel channel, int blocking) { + channel->blocking = (blocking == 0 ? 0 : 1); +} + +/** + * @internal + * + * @brief handle a SSH_CHANNEL_SUCCESS packet and set the channel state. + * + * This works on SSH1 sessions too. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_success){ + ssh_channel channel; + (void)type; + (void)user; + enter_function(); + channel=channel_from_msg(session,packet); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + leave_function(); + return SSH_PACKET_USED; + } + + ssh_log(session, SSH_LOG_PACKET, + "Received SSH_CHANNEL_SUCCESS on channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING){ + ssh_log(session, SSH_LOG_RARE, "SSH_CHANNEL_SUCCESS received in incorrect state %d", + channel->request_state); + } else { + channel->request_state=SSH_CHANNEL_REQ_STATE_ACCEPTED; + } + + leave_function(); + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_CHANNEL_FAILURE packet and set the channel state. + * + * This works on SSH1 sessions too. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_failure){ + ssh_channel channel; + (void)type; + (void)user; + enter_function(); + channel=channel_from_msg(session,packet); + if (channel == NULL) { + ssh_log(session, SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + leave_function(); + return SSH_PACKET_USED; + } + + ssh_log(session, SSH_LOG_PACKET, + "Received SSH_CHANNEL_FAILURE on channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING){ + ssh_log(session, SSH_LOG_RARE, "SSH_CHANNEL_FAILURE received in incorrect state %d", + channel->request_state); + } else { + channel->request_state=SSH_CHANNEL_REQ_STATE_DENIED; + } + leave_function(); + return SSH_PACKET_USED; +} + +static int channel_request(ssh_channel channel, const char *request, + ssh_buffer buffer, int reply) { + ssh_session session = channel->session; + ssh_string req = NULL; + int rc = SSH_ERROR; + + enter_function(); + if(channel->request_state != SSH_CHANNEL_REQ_STATE_NONE){ + ssh_set_error(session,SSH_REQUEST_DENIED,"channel_request_* used in incorrect state"); + leave_function(); + return SSH_ERROR; + } + + req = ssh_string_from_char(request); + if (req == NULL) { + goto error; + } + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_CHANNEL_REQUEST) < 0 || + buffer_add_u32(session->out_buffer, htonl(channel->remote_channel)) < 0 || + buffer_add_ssh_string(session->out_buffer, req) < 0 || + buffer_add_u8(session->out_buffer, reply == 0 ? 0 : 1) < 0) { + goto error; + } + ssh_string_free(req); + + if (buffer != NULL) { + if (buffer_add_data(session->out_buffer, ssh_buffer_get_begin(buffer), + ssh_buffer_get_len(buffer)) < 0) { + goto error; + } + } + channel->request_state = SSH_CHANNEL_REQ_STATE_PENDING; + if (packet_send(session) == SSH_ERROR) { + leave_function(); + return rc; + } + + ssh_log(session, SSH_LOG_PACKET, + "Sent a SSH_MSG_CHANNEL_REQUEST %s", request); + if (reply == 0) { + channel->request_state = SSH_CHANNEL_REQ_STATE_NONE; + leave_function(); + return SSH_OK; + } + while(channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING){ + ssh_handle_packets(session,-1); + } + /* we received something */ + switch (channel->request_state){ + case SSH_CHANNEL_REQ_STATE_ERROR: + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_DENIED: + ssh_set_error(session, SSH_REQUEST_DENIED, + "Channel request %s failed", request); + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_ACCEPTED: + ssh_log(session, SSH_LOG_PROTOCOL, + "Channel request %s success",request); + rc=SSH_OK; + break; + case SSH_CHANNEL_REQ_STATE_NONE: + case SSH_CHANNEL_REQ_STATE_PENDING: + /* Never reached */ + ssh_set_error(session, SSH_FATAL, "Invalid state in channel_request()"); + rc=SSH_ERROR; + break; + } + channel->request_state=SSH_CHANNEL_REQ_STATE_NONE; + leave_function(); + return rc; +error: + buffer_reinit(session->out_buffer); + ssh_string_free(req); + + leave_function(); + return rc; +} + +/** + * @brief Request a pty with a specific type and size. + * + * @param[in] channel The channel to sent the request. + * + * @param[in] terminal The terminal type ("vt100, xterm,..."). + * + * @param[in] col The number of columns. + * + * @param[in] row The number of rows. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + */ +int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, + int col, int row) { + ssh_session session = channel->session; + ssh_string term = NULL; + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + enter_function(); +#ifdef WITH_SSH1 + if (channel->version==1) { + channel_request_pty_size1(channel,terminal, col, row); + leave_function(); + return rc; + } +#endif + buffer = ssh_buffer_new(); + if (buffer == NULL) { + goto error; + } + + term = ssh_string_from_char(terminal); + if (term == NULL) { + goto error; + } + + if (buffer_add_ssh_string(buffer, term) < 0 || + buffer_add_u32(buffer, htonl(col)) < 0 || + buffer_add_u32(buffer, htonl(row)) < 0 || + buffer_add_u32(buffer, 0) < 0 || + buffer_add_u32(buffer, 0) < 0 || + buffer_add_u32(buffer, htonl(1)) < 0 || /* Add a 0byte string */ + buffer_add_u8(buffer, 0) < 0) { + goto error; + } + + rc = channel_request(channel, "pty-req", buffer, 1); +error: + ssh_buffer_free(buffer); + ssh_string_free(term); + + leave_function(); + return rc; +} + +/** + * @brief Request a PTY. + * + * @param[in] channel The channel to send the request. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + * + * @see channel_request_pty_size() + */ +int ssh_channel_request_pty(ssh_channel channel) { + return ssh_channel_request_pty_size(channel, "xterm", 80, 24); +} + +/** + * @brief Change the size of the terminal associated to a channel. + * + * @param[in] channel The channel to change the size. + * + * @param[in] cols The new number of columns. + * + * @param[in] rows The new number of rows. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + * + * @warning Do not call it from a signal handler if you are not sure any other + * libssh function using the same channel/session is running at same + * time (not 100% threadsafe). + */ +int ssh_channel_change_pty_size(ssh_channel channel, int cols, int rows) { + ssh_session session = channel->session; + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + enter_function(); + +#ifdef WITH_SSH1 + if (channel->version == 1) { + rc = channel_change_pty_size1(channel,cols,rows); + leave_function(); + return rc; + } +#endif + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + goto error; + } + + if (buffer_add_u32(buffer, htonl(cols)) < 0 || + buffer_add_u32(buffer, htonl(rows)) < 0 || + buffer_add_u32(buffer, 0) < 0 || + buffer_add_u32(buffer, 0) < 0) { + goto error; + } + + rc = channel_request(channel, "window-change", buffer, 0); +error: + ssh_buffer_free(buffer); + + leave_function(); + return rc; +} + +/** + * @brief Request a shell. + * + * @param[in] channel The channel to send the request. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + */ +int ssh_channel_request_shell(ssh_channel channel) { +#ifdef WITH_SSH1 + if (channel->version == 1) { + return channel_request_shell1(channel); + } +#endif + return channel_request(channel, "shell", NULL, 1); +} + +/** + * @brief Request a subsystem (for example "sftp"). + * + * @param[in] channel The channel to send the request. + * + * @param[in] subsys The subsystem to request (for example "sftp"). + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + * + * @warning You normally don't have to call it for sftp, see sftp_new(). + */ +int ssh_channel_request_subsystem(ssh_channel channel, const char *subsys) { + ssh_buffer buffer = NULL; + ssh_string subsystem = NULL; + int rc = SSH_ERROR; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + goto error; + } + + subsystem = ssh_string_from_char(subsys); + if (subsystem == NULL) { + goto error; + } + + if (buffer_add_ssh_string(buffer, subsystem) < 0) { + goto error; + } + + rc = channel_request(channel, "subsystem", buffer, 1); +error: + ssh_buffer_free(buffer); + ssh_string_free(subsystem); + + return rc; +} + +int ssh_channel_request_sftp( ssh_channel channel){ + return ssh_channel_request_subsystem(channel, "sftp"); +} + +static ssh_string generate_cookie(void) { + static const char *hex = "0123456789abcdef"; + char s[36]; + int i; + + srand ((unsigned int)time(NULL)); + for (i = 0; i < 32; i++) { + s[i] = hex[rand() % 16]; + } + s[32] = '\0'; + return ssh_string_from_char(s); +} + +/** + * @brief Sends the "x11-req" channel request over an existing session channel. + * + * This will enable redirecting the display of the remote X11 applications to + * local X server over an secure tunnel. + * + * @param[in] channel An existing session channel where the remote X11 + * applications are going to be executed. + * + * @param[in] single_connection A boolean to mark only one X11 app will be + * redirected. + * + * @param[in] protocol A x11 authentication protocol. Pass NULL to use the + * default value MIT-MAGIC-COOKIE-1. + * + * @param[in] cookie A x11 authentication cookie. Pass NULL to generate + * a random cookie. + * + * @param[in] screen_number The screen number. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + */ +int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number) { + ssh_buffer buffer = NULL; + ssh_string p = NULL; + ssh_string c = NULL; + int rc = SSH_ERROR; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + goto error; + } + + p = ssh_string_from_char(protocol ? protocol : "MIT-MAGIC-COOKIE-1"); + if (p == NULL) { + goto error; + } + + if (cookie) { + c = ssh_string_from_char(cookie); + } else { + c = generate_cookie(); + } + if (c == NULL) { + goto error; + } + + if (buffer_add_u8(buffer, single_connection == 0 ? 0 : 1) < 0 || + buffer_add_ssh_string(buffer, p) < 0 || + buffer_add_ssh_string(buffer, c) < 0 || + buffer_add_u32(buffer, htonl(screen_number)) < 0) { + goto error; + } + + rc = channel_request(channel, "x11-req", buffer, 1); + +error: + ssh_buffer_free(buffer); + ssh_string_free(p); + ssh_string_free(c); + return rc; +} + +static ssh_channel ssh_channel_accept(ssh_session session, int channeltype, + int timeout_ms) { +#ifndef _WIN32 + static const struct timespec ts = { + .tv_sec = 0, + .tv_nsec = 50000000 /* 50ms */ + }; +#endif + ssh_message msg = NULL; + ssh_channel channel = NULL; + struct ssh_iterator *iterator; + int t; + + for (t = timeout_ms; t >= 0; t -= 50) + { + ssh_handle_packets(session,50); + + if (session->ssh_message_list) { + iterator = ssh_list_get_iterator(session->ssh_message_list); + while (iterator) { + msg = (ssh_message)iterator->data; + if (ssh_message_type(msg) == SSH_REQUEST_CHANNEL_OPEN && + ssh_message_subtype(msg) == channeltype) { + ssh_list_remove(session->ssh_message_list, iterator); + channel = ssh_message_channel_request_open_reply_accept(msg); + ssh_message_free(msg); + return channel; + } + iterator = iterator->next; + } + } +#ifdef _WIN32 + Sleep(50); /* 50ms */ +#else + nanosleep(&ts, NULL); +#endif + } + + return NULL; +} + +/** + * @brief Accept an X11 forwarding channel. + * + * @param[in] channel An x11-enabled session channel. + * + * @param[in] timeout_ms Timeout in milliseconds. + * + * @return A newly created channel, or NULL if no X11 request from + * the server. + */ +ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms) { + return ssh_channel_accept(channel->session, SSH_CHANNEL_X11, timeout_ms); +} + +/** + * @internal + * + * @brief Handle a SSH_REQUEST_SUCCESS packet normally sent after a global + * request. + */ +SSH_PACKET_CALLBACK(ssh_request_success){ + (void)type; + (void)user; + (void)packet; + enter_function(); + + ssh_log(session, SSH_LOG_PACKET, + "Received SSH_REQUEST_SUCCESS"); + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){ + ssh_log(session, SSH_LOG_RARE, "SSH_REQUEST_SUCCESS received in incorrect state %d", + session->global_req_state); + } else { + session->global_req_state=SSH_CHANNEL_REQ_STATE_ACCEPTED; + } + + leave_function(); + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_REQUEST_DENIED packet normally sent after a global + * request. + */ +SSH_PACKET_CALLBACK(ssh_request_denied){ + (void)type; + (void)user; + (void)packet; + enter_function(); + + ssh_log(session, SSH_LOG_PACKET, + "Received SSH_REQUEST_FAILURE"); + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){ + ssh_log(session, SSH_LOG_RARE, "SSH_REQUEST_DENIED received in incorrect state %d", + session->global_req_state); + } else { + session->global_req_state=SSH_CHANNEL_REQ_STATE_DENIED; + } + + leave_function(); + return SSH_PACKET_USED; + +} + +/** + * @internal + * + * @brief Send a global request (needed for forward listening) and wait for the + * result. + * + * @param[in] session The SSH session handle. + * + * @param[in] request The type of request (defined in RFC). + * + * @param[in] buffer Additional data to put in packet. + * + * @param[in] reply Set if you expect a reply from server. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + */ +static int global_request(ssh_session session, const char *request, + ssh_buffer buffer, int reply) { + ssh_string req = NULL; + int rc = SSH_ERROR; + + enter_function(); + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_NONE){ + ssh_set_error(session,SSH_FATAL,"Invalid state in start of global_request()"); + leave_function(); + return rc; + } + req = ssh_string_from_char(request); + if (req == NULL) { + goto error; + } + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_GLOBAL_REQUEST) < 0 || + buffer_add_ssh_string(session->out_buffer, req) < 0 || + buffer_add_u8(session->out_buffer, reply == 0 ? 0 : 1) < 0) { + goto error; + } + ssh_string_free(req); + req=NULL; + + if (buffer != NULL) { + if (buffer_add_data(session->out_buffer, ssh_buffer_get_begin(buffer), + ssh_buffer_get_len(buffer)) < 0) { + goto error; + } + } + session->global_req_state = SSH_CHANNEL_REQ_STATE_PENDING; + if (packet_send(session) == SSH_ERROR) { + leave_function(); + return rc; + } + + ssh_log(session, SSH_LOG_PACKET, + "Sent a SSH_MSG_GLOBAL_REQUEST %s", request); + if (reply == 0) { + session->global_req_state=SSH_CHANNEL_REQ_STATE_NONE; + leave_function(); + return SSH_OK; + } + while(session->global_req_state == SSH_CHANNEL_REQ_STATE_PENDING){ + rc=ssh_handle_packets(session,-1); + if(rc==SSH_ERROR){ + session->global_req_state = SSH_CHANNEL_REQ_STATE_ERROR; + break; + } + } + switch(session->global_req_state){ + case SSH_CHANNEL_REQ_STATE_ACCEPTED: + ssh_log(session, SSH_LOG_PROTOCOL, "Global request %s success",request); + rc=SSH_OK; + break; + case SSH_CHANNEL_REQ_STATE_DENIED: + ssh_log(session, SSH_LOG_PACKET, + "Global request %s failed", request); + ssh_set_error(session, SSH_REQUEST_DENIED, + "Global request %s failed", request); + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_ERROR: + case SSH_CHANNEL_REQ_STATE_NONE: + case SSH_CHANNEL_REQ_STATE_PENDING: + rc=SSH_ERROR; + break; + + } + + leave_function(); + return rc; +error: + ssh_string_free(req); + leave_function(); + return rc; +} + +/** + * @brief Sends the "tcpip-forward" global request to ask the server to begin + * listening for inbound connections. + * + * @param[in] session The ssh session to send the request. + * + * @param[in] address The address to bind to on the server. Pass NULL to bind + * to all available addresses on all protocol families + * supported by the server. + * + * @param[in] port The port to bind to on the server. Pass 0 to ask the + * server to allocate the next available unprivileged port + * number + * + * @param[in] bound_port The pointer to get actual bound port. Pass NULL to + * ignore. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + */ +int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port) { + ssh_buffer buffer = NULL; + ssh_string addr = NULL; + int rc = SSH_ERROR; + uint32_t tmp; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + goto error; + } + + addr = ssh_string_from_char(address ? address : ""); + if (addr == NULL) { + goto error; + } + + if (buffer_add_ssh_string(buffer, addr) < 0 || + buffer_add_u32(buffer, htonl(port)) < 0) { + goto error; + } + + rc = global_request(session, "tcpip-forward", buffer, 1); + + if (rc == SSH_OK && port == 0 && bound_port) { + buffer_get_u32(session->in_buffer, &tmp); + *bound_port = ntohl(tmp); + } + +error: + ssh_buffer_free(buffer); + ssh_string_free(addr); + return rc; +} + +/** + * @brief Accept an incoming TCP/IP forwarding channel. + * + * @param[in] session The ssh session to use. + * + * @param[in] timeout_ms A timeout in milliseconds. + * + * @return Newly created channel, or NULL if no incoming channel request from + * the server + */ +ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) { + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms); +} + +/** + * @brief Sends the "cancel-tcpip-forward" global request to ask the server to + * cancel the tcpip-forward request. + * + * @param[in] session The ssh session to send the request. + * + * @param[in] address The bound address on the server. + * + * @param[in] port The bound port on the server. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + */ +int ssh_forward_cancel(ssh_session session, const char *address, int port) { + ssh_buffer buffer = NULL; + ssh_string addr = NULL; + int rc = SSH_ERROR; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + goto error; + } + + addr = ssh_string_from_char(address ? address : ""); + if (addr == NULL) { + goto error; + } + + if (buffer_add_ssh_string(buffer, addr) < 0 || + buffer_add_u32(buffer, htonl(port)) < 0) { + goto error; + } + + rc = global_request(session, "cancel-tcpip-forward", buffer, 1); + +error: + ssh_buffer_free(buffer); + ssh_string_free(addr); + return rc; +} + +/** + * @brief Set environment variables. + * + * @param[in] channel The channel to set the environment variables. + * + * @param[in] name The name of the variable. + * + * @param[in] value The value to set. + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + * + * @warning Some environment variables may be refused by security reasons. + */ +int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value) { + ssh_buffer buffer = NULL; + ssh_string str = NULL; + int rc = SSH_ERROR; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + goto error; + } + + str = ssh_string_from_char(name); + if (str == NULL) { + goto error; + } + + if (buffer_add_ssh_string(buffer, str) < 0) { + goto error; + } + + ssh_string_free(str); + str = ssh_string_from_char(value); + if (str == NULL) { + goto error; + } + + if (buffer_add_ssh_string(buffer, str) < 0) { + goto error; + } + + rc = channel_request(channel, "env", buffer,1); +error: + ssh_buffer_free(buffer); + ssh_string_free(str); + + return rc; +} + +/** + * @brief Run a shell command without an interactive shell. + * + * This is similar to 'sh -c command'. + * + * @param[in] channel The channel to execute the command. + * + * @param[in] cmd The command to execute + * (e.g. "ls ~/ -al | grep -i reports"). + * + * @return SSH_OK on success, SSH_ERROR if an error occured. + * + * @code + * rc = channel_request_exec(channel, "ps aux"); + * if (rc > 0) { + * return -1; + * } + * + * while ((rc = channel_read(channel, buffer, sizeof(buffer), 0)) > 0) { + * if (fwrite(buffer, 1, rc, stdout) != (unsigned int) rc) { + * return -1; + * } + * } + * @endcode + * + * @see channel_request_shell() + */ +int ssh_channel_request_exec(ssh_channel channel, const char *cmd) { + ssh_buffer buffer = NULL; + ssh_string command = NULL; + int rc = SSH_ERROR; + +#ifdef WITH_SSH1 + if (channel->version == 1) { + return channel_request_exec1(channel, cmd); + } +#endif + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + goto error; + } + + command = ssh_string_from_char(cmd); + if (command == NULL) { + goto error; + } + + if (buffer_add_ssh_string(buffer, command) < 0) { + goto error; + } + + rc = channel_request(channel, "exec", buffer, 1); +error: + ssh_buffer_free(buffer); + ssh_string_free(command); + return rc; +} + + +/** + * @brief Send a signal to remote process (as described in RFC 4254, section 6.9). + * + * Sends a signal 'sig' to the remote process. + * Note, that remote system may not support signals concept. + * In such a case this request will be silently ignored. + * Only SSH-v2 is supported (I'm not sure about SSH-v1). + * + * @param[in] channel The channel to send signal. + * + * @param[in] sig The signal to send (without SIG prefix) + * (e.g. "TERM" or "KILL"). + * + * @return SSH_OK on success, SSH_ERROR if an error occured + * (including attempts to send signal via SSH-v1 session). + */ +int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) { + ssh_buffer buffer = NULL; + ssh_string encoded_signal = NULL; + int rc = SSH_ERROR; + +#ifdef WITH_SSH1 + if (channel->version == 1) { + return SSH_ERROR; // TODO: Add support for SSH-v1 if possible. + } +#endif + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + goto error; + } + + encoded_signal = ssh_string_from_char(sig); + if (encoded_signal == NULL) { + goto error; + } + + if (buffer_add_ssh_string(buffer, encoded_signal) < 0) { + goto error; + } + + rc = channel_request(channel, "signal", buffer, 0); +error: + ssh_buffer_free(buffer); + ssh_string_free(encoded_signal); + return rc; +} + + +/** + * @brief Read data from a channel into a buffer. + * + * @param[in] channel The channel to read from. + * + * @param[in] buffer The buffer which will get the data. + * + * @param[in] count The count of bytes to be read. If it is bigger than 0, + * the exact size will be read, else (bytes=0) it will + * return once anything is available. + * + * @param is_stderr A boolean value to mark reading from the stderr stream. + * + * @return The number of bytes read, 0 on end of file or SSH_ERROR + * on error. + * @deprecated Please use ssh_channel_read instead + * @see ssh_channel_read + */ +int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, + int is_stderr) { + ssh_session session=channel->session; + ssh_buffer stdbuf = channel->stdout_buffer; + uint32_t maxread = count; + uint32_t len; + + buffer_reinit(buffer); + + enter_function(); + + if (count == 0) { + maxread = MAX_PACKET_LEN; + } + + if (is_stderr) { + stdbuf = channel->stderr_buffer; + } + + /* + * We may have problem if the window is too small to accept as much data + * as asked + */ + ssh_log(session, SSH_LOG_PROTOCOL, + "Read (%d) buffered: %d bytes. Window: %d", + count, + buffer_get_rest_len(stdbuf), + channel->local_window); + + if (count > buffer_get_rest_len(stdbuf) + channel->local_window) { + if (grow_window(session, channel, + count - buffer_get_rest_len(stdbuf)) < 0) { + leave_function(); + return -1; + } + } + /* block reading if asked bytes=0 */ + while (buffer_get_rest_len(stdbuf) == 0 || + buffer_get_rest_len(stdbuf) < count) { + if (channel->remote_eof && buffer_get_rest_len(stdbuf) == 0) { + leave_function(); + return 0; + } + if (channel->remote_eof) { + /* Return the resting bytes in buffer */ + break; + } + if (buffer_get_rest_len(stdbuf) >= maxread) { + /* Stop reading when buffer is full enough */ + break; + } + ssh_handle_packets(session,-1); + } + + if(channel->local_window < WINDOWLIMIT) { + if (grow_window(session, channel, 0) < 0) { + leave_function(); + return -1; + } + } + + if (count == 0) { + /* write the ful buffer information */ + if (buffer_add_data(buffer, buffer_get_rest(stdbuf), + buffer_get_rest_len(stdbuf)) < 0) { + leave_function(); + return -1; + } + buffer_reinit(stdbuf); + } else { + /* Read bytes bytes if len is greater, everything otherwise */ + len = buffer_get_rest_len(stdbuf); + len = (len > count ? count : len); + if (buffer_add_data(buffer, buffer_get_rest(stdbuf), len) < 0) { + leave_function(); + return -1; + } + buffer_pass_bytes(stdbuf,len); + } + + leave_function(); + return ssh_buffer_get_len(buffer); +} + +/* TODO FIXME Fix the delayed close thing */ +/* TODO FIXME Fix the blocking behaviours */ + +/** + * @brief Reads data from a channel. + * + * @param[in] channel The channel to read from. + * + * @param[in] dest The destination buffer which will get the data. + * + * @param[in] count The count of bytes to be read. + * + * @param[in] is_stderr A boolean value to mark reading from the stderr flow. + * + * @return The number of bytes read, 0 on end of file or SSH_ERROR + * on error. + * + * @warning This function may return less than count bytes of data, and won't + * block until count bytes have been read. + * @warning The read function using a buffer has been renamed to + * channel_read_buffer(). + */ +int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr) { + ssh_session session = channel->session; + ssh_buffer stdbuf = channel->stdout_buffer; + uint32_t len; + + enter_function(); + + if (count == 0) { + leave_function(); + return 0; + } + + if (is_stderr) { + stdbuf=channel->stderr_buffer; + } + + /* + * We may have problem if the window is too small to accept as much data + * as asked + */ + ssh_log(session, SSH_LOG_PROTOCOL, + "Read (%d) buffered : %d bytes. Window: %d", + count, + buffer_get_rest_len(stdbuf), + channel->local_window); + + if (count > buffer_get_rest_len(stdbuf) + channel->local_window) { + if (grow_window(session, channel, + count - buffer_get_rest_len(stdbuf)) < 0) { + leave_function(); + return -1; + } + } + + /* block reading until at least one byte is read + * and ignore the trivial case count=0 + */ + while (buffer_get_rest_len(stdbuf) == 0 && count > 0) { + if (channel->remote_eof && buffer_get_rest_len(stdbuf) == 0) { + leave_function(); + return 0; + } + + if (channel->remote_eof) { + /* Return the resting bytes in buffer */ + break; + } + + if (buffer_get_rest_len(stdbuf) >= count) { + /* Stop reading when buffer is full enough */ + break; + } + + ssh_handle_packets(session,-1); + } + + if (channel->local_window < WINDOWLIMIT) { + if (grow_window(session, channel, 0) < 0) { + leave_function(); + return -1; + } + } + + len = buffer_get_rest_len(stdbuf); + /* Read count bytes if len is greater, everything otherwise */ + len = (len > count ? count : len); + memcpy(dest, buffer_get_rest(stdbuf), len); + buffer_pass_bytes(stdbuf,len); + + leave_function(); + return len; +} + +/** + * @brief Do a nonblocking read on the channel. + * + * A nonblocking read on the specified channel. it will return <= count bytes of + * data read atomically. + * + * @param[in] channel The channel to read from. + * + * @param[in] dest A pointer to a destination buffer. + * + * @param[in] count The count of bytes of data to be read. + * + * @param[in] is_stderr A boolean to select the stderr stream. + * + * @return The number of bytes read, 0 if nothing is available or + * SSH_ERROR on error. + * + * @warning Don't forget to check for EOF as it would return 0 here. + * + * @see channel_is_eof() + */ +int ssh_channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr) { + ssh_session session = channel->session; + uint32_t to_read; + int rc; + + enter_function(); + + to_read = ssh_channel_poll(channel, is_stderr); + + if (to_read <= 0) { + leave_function(); + return to_read; /* may be an error code */ + } + + if (to_read > count) { + to_read = count; + } + rc = ssh_channel_read(channel, dest, to_read, is_stderr); + + leave_function(); + return rc; +} + +/** + * @brief Polls a channel for data to read. + * + * @param[in] channel The channel to poll. + * + * @param[in] is_stderr A boolean to select the stderr stream. + * + * @return The number of bytes available for reading, 0 if nothing + * is available or SSH_ERROR on error. + * + * @warning When the channel is in EOF state, the function returns SSH_EOF. + * + * @see channel_is_eof() + */ +int ssh_channel_poll(ssh_channel channel, int is_stderr){ + ssh_session session = channel->session; + ssh_buffer stdbuf = channel->stdout_buffer; + + enter_function(); + + if (is_stderr) { + stdbuf = channel->stderr_buffer; + } + + if (buffer_get_rest_len(stdbuf) == 0 && channel->remote_eof == 0) { + if (ssh_handle_packets(channel->session,0)==SSH_ERROR) { + leave_function(); + return SSH_ERROR; + } + } + + if (buffer_get_rest_len(stdbuf) > 0){ + leave_function(); + return buffer_get_rest_len(stdbuf); + } + + if (channel->remote_eof) { + leave_function(); + return SSH_EOF; + } + + leave_function(); + return buffer_get_rest_len(stdbuf); +} + +/** + * @brief Recover the session in which belongs a channel. + * + * @param[in] channel The channel to recover the session from. + * + * @return The session pointer. + */ +ssh_session ssh_channel_get_session(ssh_channel channel) { + return channel->session; +} + +/** + * @brief Get the exit status of the channel (error code from the executed + * instruction). + * + * @param[in] channel The channel to get the status from. + * + * @returns The exit status, -1 if no exit status has been returned + * or eof not sent. + */ +int ssh_channel_get_exit_status(ssh_channel channel) { + if (channel->local_eof == 0) { + return -1; + } + + while (channel->remote_eof == 0 || channel->exit_status == -1) { + /* Parse every incoming packet */ + if (ssh_handle_packets(channel->session,-1) != SSH_OK) { + return -1; + } + /* XXX We should actually wait for a close packet and not a close + * we issued ourselves + */ + if (channel->state != SSH_CHANNEL_STATE_OPEN) { + /* When a channel is closed, no exit status message can + * come anymore */ + break; + } + } + + return channel->exit_status; +} + +/* + * This function acts as a meta select. + * + * First, channels are analyzed to seek potential can-write or can-read ones, + * then if no channel has been elected, it goes in a loop with the posix + * select(2). + * This is made in two parts: protocol select and network select. The protocol + * select does not use the network functions at all + */ +static int channel_protocol_select(ssh_channel *rchans, ssh_channel *wchans, + ssh_channel *echans, ssh_channel *rout, ssh_channel *wout, ssh_channel *eout) { + ssh_channel chan; + int i; + int j = 0; + + for (i = 0; rchans[i] != NULL; i++) { + chan = rchans[i]; + + while (ssh_channel_is_open(chan) && ssh_socket_data_available(chan->session->socket)) { + ssh_handle_packets(chan->session,-1); + } + + if ((chan->stdout_buffer && ssh_buffer_get_len(chan->stdout_buffer) > 0) || + (chan->stderr_buffer && ssh_buffer_get_len(chan->stderr_buffer) > 0) || + chan->remote_eof) { + rout[j] = chan; + j++; + } + } + rout[j] = NULL; + + j = 0; + for(i = 0; wchans[i] != NULL; i++) { + chan = wchans[i]; + /* It's not our business to seek if the file descriptor is writable */ + if (ssh_socket_data_writable(chan->session->socket) && + ssh_channel_is_open(chan) && (chan->remote_window > 0)) { + wout[j] = chan; + j++; + } + } + wout[j] = NULL; + + j = 0; + for (i = 0; echans[i] != NULL; i++) { + chan = echans[i]; + + if (!ssh_socket_is_open(chan->session->socket) || ssh_channel_is_closed(chan)) { + eout[j] = chan; + j++; + } + } + eout[j] = NULL; + + return 0; +} + +/* Just count number of pointers in the array */ +static int count_ptrs(ssh_channel *ptrs) { + int c; + for (c = 0; ptrs[c] != NULL; c++) + ; + + return c; +} + +/** + * @brief Act like the standard select(2) on channels. + * + * The list of pointers are then actualized and will only contain pointers to + * channels that are respectively readable, writable or have an exception to + * trap. + * + * @param[in] readchans A NULL pointer or an array of channel pointers, + * terminated by a NULL. + * + * @param[in] writechans A NULL pointer or an array of channel pointers, + * terminated by a NULL. + * + * @param[in] exceptchans A NULL pointer or an array of channel pointers, + * terminated by a NULL. + * + * @param[in] timeout Timeout as defined by select(2). + * + * @return SSH_OK on a successful operation, SSH_EINTR if the + * select(2) syscall was interrupted, then relaunch the + * function. + */ +int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, + ssh_channel *exceptchans, struct timeval * timeout) { + ssh_channel *rchans, *wchans, *echans; + ssh_channel dummy = NULL; + fd_set rset; + fd_set wset; + fd_set eset; + socket_t max_fd = SSH_INVALID_SOCKET; + int rc; + int i; + + /* don't allow NULL pointers */ + if (readchans == NULL) { + readchans = &dummy; + } + + if (writechans == NULL) { + writechans = &dummy; + } + + if (exceptchans == NULL) { + exceptchans = &dummy; + } + + if (readchans[0] == NULL && writechans[0] == NULL && exceptchans[0] == NULL) { + /* No channel to poll?? Go away! */ + return 0; + } + + /* Prepare the outgoing temporary arrays */ + rchans = malloc(sizeof(ssh_channel ) * (count_ptrs(readchans) + 1)); + if (rchans == NULL) { + return SSH_ERROR; + } + + wchans = malloc(sizeof(ssh_channel ) * (count_ptrs(writechans) + 1)); + if (wchans == NULL) { + SAFE_FREE(rchans); + return SSH_ERROR; + } + + echans = malloc(sizeof(ssh_channel ) * (count_ptrs(exceptchans) + 1)); + if (echans == NULL) { + SAFE_FREE(rchans); + SAFE_FREE(wchans); + return SSH_ERROR; + } + + /* + * First, try without doing network stuff then, select and redo the + * networkless stuff + */ + do { + channel_protocol_select(readchans, writechans, exceptchans, + rchans, wchans, echans); + if (rchans[0] != NULL || wchans[0] != NULL || echans[0] != NULL) { + /* We've got one without doing any select overwrite the beginning arrays */ + memcpy(readchans, rchans, (count_ptrs(rchans) + 1) * sizeof(ssh_channel )); + memcpy(writechans, wchans, (count_ptrs(wchans) + 1) * sizeof(ssh_channel )); + memcpy(exceptchans, echans, (count_ptrs(echans) + 1) * sizeof(ssh_channel )); + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + return 0; + } + /* + * Since we verified the invalid fd cases into the networkless select, + * we can be sure all fd are valid ones + */ + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_ZERO(&eset); + + for (i = 0; readchans[i] != NULL; i++) { + if (!ssh_socket_fd_isset(readchans[i]->session->socket, &rset)) { + ssh_socket_fd_set(readchans[i]->session->socket, &rset, &max_fd); + } + } + + for (i = 0; writechans[i] != NULL; i++) { + if (!ssh_socket_fd_isset(writechans[i]->session->socket, &wset)) { + ssh_socket_fd_set(writechans[i]->session->socket, &wset, &max_fd); + } + } + + for (i = 0; exceptchans[i] != NULL; i++) { + if (!ssh_socket_fd_isset(exceptchans[i]->session->socket, &eset)) { + ssh_socket_fd_set(exceptchans[i]->session->socket, &eset, &max_fd); + } + } + + /* Here we go */ + rc = select(max_fd, &rset, &wset, &eset, timeout); + /* Leave if select was interrupted */ + if (rc == EINTR) { + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + return SSH_EINTR; + } + + for (i = 0; readchans[i] != NULL; i++) { + if (ssh_socket_fd_isset(readchans[i]->session->socket, &rset)) { + ssh_socket_set_toread(readchans[i]->session->socket); + } + } + + for (i = 0; writechans[i] != NULL; i++) { + if (ssh_socket_fd_isset(writechans[i]->session->socket, &wset)) { + ssh_socket_set_towrite(writechans[i]->session->socket); + } + } + + for (i = 0; exceptchans[i] != NULL; i++) { + if (ssh_socket_fd_isset(exceptchans[i]->session->socket, &eset)) { + ssh_socket_set_except(exceptchans[i]->session->socket); + } + } + } while(1); /* Return to do loop */ + + /* not reached */ + return 0; +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ |