diff options
Diffstat (limited to 'libssh/channels.c')
-rw-r--r-- | libssh/channels.c | 2495 |
1 files changed, 0 insertions, 2495 deletions
diff --git a/libssh/channels.c b/libssh/channels.c deleted file mode 100644 index 6900beea..00000000 --- a/libssh/channels.c +++ /dev/null @@ -1,2495 +0,0 @@ -/* - * 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: */ |