3475 строки
92 KiB
C
3475 строки
92 KiB
C
/*
|
|
* channels.c - SSH channel functions
|
|
*
|
|
* This file is part of the SSH Library
|
|
*
|
|
* Copyright (c) 2003-2013 by Aris Adamantiadis
|
|
* Copyright (c) 2009-2013 by Andreas Schneider <asn@cryptomilk.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 "config.h"
|
|
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
|
|
#ifndef _WIN32
|
|
#include <netinet/in.h>
|
|
#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"
|
|
#if WITH_SERVER
|
|
#include "libssh/server.h"
|
|
#endif
|
|
|
|
#define WINDOWBASE 1280000
|
|
#define WINDOWLIMIT (WINDOWBASE/2)
|
|
|
|
/*
|
|
* All implementations MUST be able to process packets with an
|
|
* uncompressed payload length of 32768 bytes or less and a total packet
|
|
* size of 35000 bytes or less.
|
|
*/
|
|
#define CHANNEL_MAX_PACKET 32768
|
|
#define CHANNEL_INITIAL_WINDOW 64000
|
|
|
|
/**
|
|
* @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;
|
|
|
|
if(session == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
channel = malloc(sizeof(struct ssh_channel_struct));
|
|
if (channel == NULL) {
|
|
ssh_set_error_oom(session);
|
|
return NULL;
|
|
}
|
|
memset(channel,0,sizeof(struct ssh_channel_struct));
|
|
|
|
channel->stdout_buffer = ssh_buffer_new();
|
|
if (channel->stdout_buffer == NULL) {
|
|
ssh_set_error_oom(session);
|
|
SAFE_FREE(channel);
|
|
return NULL;
|
|
}
|
|
|
|
channel->stderr_buffer = ssh_buffer_new();
|
|
if (channel->stderr_buffer == NULL) {
|
|
ssh_set_error_oom(session);
|
|
ssh_buffer_free(channel->stdout_buffer);
|
|
SAFE_FREE(channel);
|
|
return NULL;
|
|
}
|
|
|
|
channel->session = session;
|
|
channel->version = session->version;
|
|
channel->exit_status = -1;
|
|
channel->flags = SSH_CHANNEL_FLAG_NOT_BOUND;
|
|
|
|
if(session->channels == NULL) {
|
|
session->channels = ssh_list_new();
|
|
}
|
|
ssh_list_prepend(session->channels, 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;
|
|
ssh_channel channel;
|
|
int rc;
|
|
(void)type;
|
|
(void)user;
|
|
|
|
SSH_LOG(SSH_LOG_PACKET,"Received SSH2_MSG_CHANNEL_OPEN_CONFIRMATION");
|
|
|
|
rc = ssh_buffer_unpack(packet, "d", &channelid);
|
|
if (rc != SSH_OK)
|
|
goto error;
|
|
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 */
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
rc = ssh_buffer_unpack(packet, "ddd",
|
|
&channel->remote_channel,
|
|
&channel->remote_window,
|
|
&channel->remote_maxpacket);
|
|
if (rc != SSH_OK)
|
|
goto error;
|
|
|
|
SSH_LOG(SSH_LOG_PROTOCOL,
|
|
"Received a CHANNEL_OPEN_CONFIRMATION for channel %d:%d",
|
|
channel->local_channel,
|
|
channel->remote_channel);
|
|
SSH_LOG(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;
|
|
channel->flags &= ~SSH_CHANNEL_FLAG_NOT_BOUND;
|
|
return SSH_PACKET_USED;
|
|
|
|
error:
|
|
ssh_set_error(session, SSH_FATAL, "Invalid packet");
|
|
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;
|
|
char *error = NULL;
|
|
uint32_t code;
|
|
int rc;
|
|
(void)user;
|
|
(void)type;
|
|
|
|
channel=channel_from_msg(session,packet);
|
|
if(channel==NULL){
|
|
SSH_LOG(SSH_LOG_RARE,"Invalid channel in packet");
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
rc = ssh_buffer_unpack(packet, "ds", &code, &error);
|
|
if (rc != SSH_OK){
|
|
ssh_set_error(session, SSH_FATAL, "Invalid packet");
|
|
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) code,
|
|
error);
|
|
SAFE_FREE(error);
|
|
channel->state=SSH_CHANNEL_STATE_OPEN_DENIED;
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
static int ssh_channel_open_termination(void *c){
|
|
ssh_channel channel = (ssh_channel) c;
|
|
if (channel->state != SSH_CHANNEL_STATE_OPENING ||
|
|
channel->session->session_state == SSH_SESSION_STATE_ERROR)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @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 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, int window,
|
|
int maxpacket, ssh_buffer payload) {
|
|
ssh_session session = channel->session;
|
|
int err=SSH_ERROR;
|
|
int rc;
|
|
|
|
switch(channel->state){
|
|
case SSH_CHANNEL_STATE_NOT_OPEN:
|
|
break;
|
|
case SSH_CHANNEL_STATE_OPENING:
|
|
goto pending;
|
|
case SSH_CHANNEL_STATE_OPEN:
|
|
case SSH_CHANNEL_STATE_CLOSED:
|
|
case SSH_CHANNEL_STATE_OPEN_DENIED:
|
|
goto end;
|
|
default:
|
|
ssh_set_error(session,SSH_FATAL,"Bad state in channel_open: %d",channel->state);
|
|
}
|
|
channel->local_channel = ssh_channel_new_id(session);
|
|
channel->local_maxpacket = maxpacket;
|
|
channel->local_window = window;
|
|
|
|
SSH_LOG(SSH_LOG_PROTOCOL,
|
|
"Creating a channel %d with %d window and %d max packet",
|
|
channel->local_channel, window, maxpacket);
|
|
|
|
rc = ssh_buffer_pack(session->out_buffer,
|
|
"bsddd",
|
|
SSH2_MSG_CHANNEL_OPEN,
|
|
type,
|
|
channel->local_channel,
|
|
channel->local_window,
|
|
channel->local_maxpacket);
|
|
if (rc != SSH_OK){
|
|
ssh_set_error_oom(session);
|
|
return err;
|
|
}
|
|
|
|
if (payload != NULL) {
|
|
if (ssh_buffer_add_buffer(session->out_buffer, payload) < 0) {
|
|
ssh_set_error_oom(session);
|
|
|
|
return err;
|
|
}
|
|
}
|
|
channel->state = SSH_CHANNEL_STATE_OPENING;
|
|
if (ssh_packet_send(session) == SSH_ERROR) {
|
|
|
|
return err;
|
|
}
|
|
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"Sent a SSH_MSG_CHANNEL_OPEN type %s for channel %d",
|
|
type, channel->local_channel);
|
|
pending:
|
|
/* wait until channel is opened by server */
|
|
err = ssh_handle_packets_termination(session,
|
|
SSH_TIMEOUT_DEFAULT,
|
|
ssh_channel_open_termination,
|
|
channel);
|
|
|
|
if (session->session_state == SSH_SESSION_STATE_ERROR)
|
|
err = SSH_ERROR;
|
|
end:
|
|
if(channel->state == SSH_CHANNEL_STATE_OPEN)
|
|
err=SSH_OK;
|
|
|
|
return err;
|
|
}
|
|
|
|
/* return channel with corresponding local id, or NULL if not found */
|
|
ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id) {
|
|
struct ssh_iterator *it;
|
|
ssh_channel channel;
|
|
|
|
for (it = ssh_list_get_iterator(session->channels); it != NULL ; it=it->next) {
|
|
channel = ssh_iterator_value(ssh_channel, it);
|
|
if (channel == NULL) {
|
|
continue;
|
|
}
|
|
if (channel->local_channel == id) {
|
|
return channel;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
* @brief grows the local window and send a packet to the other party
|
|
* @param session SSH session
|
|
* @param channel SSH channel
|
|
* @param minimumsize The minimum acceptable size for the new window.
|
|
*/
|
|
static int grow_window(ssh_session session, ssh_channel channel, int minimumsize) {
|
|
uint32_t new_window = minimumsize > WINDOWBASE ? minimumsize : WINDOWBASE;
|
|
int rc;
|
|
|
|
#ifdef WITH_SSH1
|
|
if (session->version == 1){
|
|
channel->remote_window = new_window;
|
|
|
|
return SSH_OK;
|
|
}
|
|
#endif
|
|
if(new_window <= channel->local_window){
|
|
SSH_LOG(SSH_LOG_PROTOCOL,
|
|
"growing window (channel %d:%d) to %d bytes : not needed (%d bytes)",
|
|
channel->local_channel, channel->remote_channel, new_window,
|
|
channel->local_window);
|
|
|
|
return SSH_OK;
|
|
}
|
|
/* WINDOW_ADJUST packet needs a relative increment rather than an absolute
|
|
* value, so we give here the missing bytes needed to reach new_window
|
|
*/
|
|
rc = ssh_buffer_pack(session->out_buffer,
|
|
"bdd",
|
|
SSH2_MSG_CHANNEL_WINDOW_ADJUST,
|
|
channel->remote_channel,
|
|
new_window - channel->local_window);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
|
|
if (ssh_packet_send(session) == SSH_ERROR) {
|
|
goto error;
|
|
}
|
|
|
|
SSH_LOG(SSH_LOG_PROTOCOL,
|
|
"growing window (channel %d:%d) to %d bytes",
|
|
channel->local_channel,
|
|
channel->remote_channel,
|
|
new_window);
|
|
|
|
channel->local_window = new_window;
|
|
|
|
return SSH_OK;
|
|
error:
|
|
ssh_buffer_reinit(session->out_buffer);
|
|
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
int rc;
|
|
#ifdef WITH_SSH1
|
|
/* With SSH1, the channel is always the first one */
|
|
if(session->version==1)
|
|
return ssh_get_channel1(session);
|
|
#endif
|
|
rc = ssh_buffer_unpack(packet,"d",&chan);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error(session, SSH_FATAL,
|
|
"Getting channel from message: short read");
|
|
return NULL;
|
|
}
|
|
|
|
channel = ssh_channel_from_local(session, chan);
|
|
if (channel == NULL) {
|
|
ssh_set_error(session, SSH_FATAL,
|
|
"Server specified invalid channel %lu",
|
|
(long unsigned int) chan);
|
|
}
|
|
|
|
return channel;
|
|
}
|
|
|
|
SSH_PACKET_CALLBACK(channel_rcv_change_window) {
|
|
ssh_channel channel;
|
|
uint32_t bytes;
|
|
int rc;
|
|
(void)user;
|
|
(void)type;
|
|
|
|
channel = channel_from_msg(session,packet);
|
|
if (channel == NULL) {
|
|
SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
|
|
}
|
|
|
|
rc = ssh_buffer_unpack(packet, "d", &bytes);
|
|
if (channel == NULL || rc != SSH_OK) {
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"Error getting a window adjust message: invalid packet");
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
SSH_LOG(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;
|
|
|
|
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;
|
|
ssh_buffer buf;
|
|
size_t len;
|
|
int is_stderr;
|
|
int rest;
|
|
(void)user;
|
|
|
|
if(type==SSH2_MSG_CHANNEL_DATA)
|
|
is_stderr=0;
|
|
else
|
|
is_stderr=1;
|
|
|
|
channel = channel_from_msg(session,packet);
|
|
if (channel == NULL) {
|
|
SSH_LOG(SSH_LOG_FUNCTIONS,
|
|
"%s", ssh_get_error(session));
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
if (is_stderr) {
|
|
uint32_t ignore;
|
|
/* uint32 data type code. we can ignore it */
|
|
ssh_buffer_get_u32(packet, &ignore);
|
|
}
|
|
|
|
str = ssh_buffer_get_ssh_string(packet);
|
|
if (str == NULL) {
|
|
SSH_LOG(SSH_LOG_PACKET, "Invalid data packet!");
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
len = ssh_string_len(str);
|
|
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"Channel receiving %" PRIdS " 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(SSH_LOG_RARE,
|
|
"Data packet too big for our window(%" PRIdS " vs %d)",
|
|
len,
|
|
channel->local_window);
|
|
}
|
|
|
|
if (channel_default_bufferize(channel, ssh_string_data(str), len,
|
|
is_stderr) < 0) {
|
|
ssh_string_free(str);
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
if (len <= channel->local_window) {
|
|
channel->local_window -= len;
|
|
} else {
|
|
channel->local_window = 0; /* buggy remote */
|
|
}
|
|
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"Channel windows are now (local win=%d remote win=%d)",
|
|
channel->local_window,
|
|
channel->remote_window);
|
|
|
|
ssh_string_free(str);
|
|
|
|
if (is_stderr) {
|
|
buf = channel->stderr_buffer;
|
|
} else {
|
|
buf = channel->stdout_buffer;
|
|
}
|
|
|
|
ssh_callbacks_iterate(channel->callbacks,
|
|
ssh_channel_callbacks,
|
|
channel_data_function) {
|
|
if (ssh_buffer_get(buf) == 0) {
|
|
break;
|
|
}
|
|
rest = ssh_callbacks_iterate_exec(channel_data_function,
|
|
channel->session,
|
|
channel,
|
|
ssh_buffer_get(buf),
|
|
ssh_buffer_get_len(buf),
|
|
is_stderr);
|
|
if (rest > 0) {
|
|
if (channel->counter != NULL) {
|
|
channel->counter->in_bytes += rest;
|
|
}
|
|
ssh_buffer_pass_bytes(buf, rest);
|
|
}
|
|
}
|
|
ssh_callbacks_iterate_end();
|
|
|
|
if (channel->local_window + ssh_buffer_get_len(buf) < WINDOWLIMIT) {
|
|
if (grow_window(session, channel, 0) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
SSH_PACKET_CALLBACK(channel_rcv_eof) {
|
|
ssh_channel channel;
|
|
(void)user;
|
|
(void)type;
|
|
|
|
channel = channel_from_msg(session,packet);
|
|
if (channel == NULL) {
|
|
SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"Received eof on channel (%d:%d)",
|
|
channel->local_channel,
|
|
channel->remote_channel);
|
|
/* channel->remote_window = 0; */
|
|
channel->remote_eof = 1;
|
|
|
|
ssh_callbacks_execute_list(channel->callbacks,
|
|
ssh_channel_callbacks,
|
|
channel_eof_function,
|
|
channel->session,
|
|
channel);
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
SSH_PACKET_CALLBACK(channel_rcv_close) {
|
|
ssh_channel channel;
|
|
(void)user;
|
|
(void)type;
|
|
|
|
channel = channel_from_msg(session,packet);
|
|
if (channel == NULL) {
|
|
SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"Received close on channel (%d:%d)",
|
|
channel->local_channel,
|
|
channel->remote_channel);
|
|
|
|
if ((channel->stdout_buffer &&
|
|
ssh_buffer_get_len(channel->stdout_buffer) > 0) ||
|
|
(channel->stderr_buffer &&
|
|
ssh_buffer_get_len(channel->stderr_buffer) > 0)) {
|
|
channel->delayed_close = 1;
|
|
} else {
|
|
channel->state = SSH_CHANNEL_STATE_CLOSED;
|
|
}
|
|
if (channel->remote_eof == 0) {
|
|
SSH_LOG(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.
|
|
*/
|
|
|
|
ssh_callbacks_execute_list(channel->callbacks,
|
|
ssh_channel_callbacks,
|
|
channel_close_function,
|
|
channel->session,
|
|
channel);
|
|
|
|
channel->flags |= SSH_CHANNEL_FLAG_CLOSED_REMOTE;
|
|
if(channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)
|
|
ssh_channel_do_free(channel);
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
SSH_PACKET_CALLBACK(channel_rcv_request) {
|
|
ssh_channel channel;
|
|
char *request=NULL;
|
|
uint8_t status;
|
|
int rc;
|
|
(void)user;
|
|
(void)type;
|
|
|
|
channel = channel_from_msg(session,packet);
|
|
if (channel == NULL) {
|
|
SSH_LOG(SSH_LOG_FUNCTIONS,"%s", ssh_get_error(session));
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
rc = ssh_buffer_unpack(packet, "sb",
|
|
&request,
|
|
&status);
|
|
if (rc != SSH_OK) {
|
|
SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST");
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
if (strcmp(request,"exit-status") == 0) {
|
|
SAFE_FREE(request);
|
|
rc = ssh_buffer_unpack(packet, "d", &channel->exit_status);
|
|
SSH_LOG(SSH_LOG_PACKET, "received exit-status %d", channel->exit_status);
|
|
|
|
ssh_callbacks_execute_list(channel->callbacks,
|
|
ssh_channel_callbacks,
|
|
channel_exit_status_function,
|
|
channel->session,
|
|
channel,
|
|
channel->exit_status);
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
if (strcmp(request,"signal") == 0) {
|
|
char *sig = NULL;
|
|
|
|
SAFE_FREE(request);
|
|
SSH_LOG(SSH_LOG_PACKET, "received signal");
|
|
|
|
rc = ssh_buffer_unpack(packet, "s", &sig);
|
|
if (rc != SSH_OK) {
|
|
SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST");
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"Remote connection sent a signal SIG %s", sig);
|
|
ssh_callbacks_execute_list(channel->callbacks,
|
|
ssh_channel_callbacks,
|
|
channel_signal_function,
|
|
channel->session,
|
|
channel,
|
|
sig);
|
|
SAFE_FREE(sig);
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
if (strcmp(request, "exit-signal") == 0) {
|
|
const char *core = "(core dumped)";
|
|
char *sig = NULL;
|
|
char *errmsg = NULL;
|
|
char *lang = NULL;
|
|
uint8_t core_dumped;
|
|
|
|
SAFE_FREE(request);
|
|
|
|
rc = ssh_buffer_unpack(packet, "sbss",
|
|
&sig, /* signal name */
|
|
&core_dumped, /* core dumped */
|
|
&errmsg, /* error message */
|
|
&lang);
|
|
if (rc != SSH_OK) {
|
|
SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST");
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
if (core_dumped == 0) {
|
|
core = "";
|
|
}
|
|
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"Remote connection closed by signal SIG %s %s", sig, core);
|
|
ssh_callbacks_execute_list(channel->callbacks,
|
|
ssh_channel_callbacks,
|
|
channel_exit_signal_function,
|
|
channel->session,
|
|
channel,
|
|
sig,
|
|
core_dumped,
|
|
errmsg,
|
|
lang);
|
|
|
|
SAFE_FREE(lang);
|
|
SAFE_FREE(errmsg);
|
|
SAFE_FREE(sig);
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
if(strcmp(request,"keepalive@openssh.com")==0){
|
|
SAFE_FREE(request);
|
|
SSH_LOG(SSH_LOG_PROTOCOL,"Responding to Openssh's keepalive");
|
|
|
|
rc = ssh_buffer_pack(session->out_buffer,
|
|
"bd",
|
|
SSH2_MSG_CHANNEL_FAILURE,
|
|
channel->remote_channel);
|
|
if (rc != SSH_OK) {
|
|
return SSH_PACKET_USED;
|
|
}
|
|
ssh_packet_send(session);
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
if (strcmp(request, "auth-agent-req@openssh.com") == 0) {
|
|
SAFE_FREE(request);
|
|
SSH_LOG(SSH_LOG_PROTOCOL, "Received an auth-agent-req request");
|
|
ssh_callbacks_execute_list(channel->callbacks,
|
|
ssh_channel_callbacks,
|
|
channel_auth_agent_req_function,
|
|
channel->session,
|
|
channel);
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
#ifdef WITH_SERVER
|
|
/* 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);
|
|
#else
|
|
SSH_LOG(SSH_LOG_WARNING, "Unhandled channel request %s", request);
|
|
#endif
|
|
|
|
SAFE_FREE(request);
|
|
|
|
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;
|
|
|
|
if(channel == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
session = channel->session;
|
|
|
|
if(data == NULL) {
|
|
ssh_set_error_invalid(session);
|
|
return -1;
|
|
}
|
|
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"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 (ssh_buffer_add_data(channel->stdout_buffer, data, len) < 0) {
|
|
ssh_set_error_oom(session);
|
|
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 (ssh_buffer_add_data(channel->stderr_buffer, data, len) < 0) {
|
|
ssh_set_error_oom(session);
|
|
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 occurred,
|
|
* SSH_AGAIN if in nonblocking mode and call has
|
|
* to be done again.
|
|
*
|
|
* @see ssh_channel_open_forward()
|
|
* @see ssh_channel_request_env()
|
|
* @see ssh_channel_request_shell()
|
|
* @see ssh_channel_request_exec()
|
|
*/
|
|
int ssh_channel_open_session(ssh_channel channel) {
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
#ifdef WITH_SSH1
|
|
if (channel->session->version == 1) {
|
|
return ssh_channel_open_session1(channel);
|
|
}
|
|
#endif
|
|
|
|
return channel_open(channel,
|
|
"session",
|
|
CHANNEL_INITIAL_WINDOW,
|
|
CHANNEL_MAX_PACKET,
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* @brief Open an agent authentication forwarding channel. This type of channel
|
|
* can be opened by a server towards a client in order to provide SSH-Agent services
|
|
* to the server-side process. This channel can only be opened if the client
|
|
* claimed support by sending a channel request beforehand.
|
|
*
|
|
* @param[in] channel An allocated channel.
|
|
*
|
|
* @return SSH_OK on success,
|
|
* SSH_ERROR if an error occurred,
|
|
* SSH_AGAIN if in nonblocking mode and call has
|
|
* to be done again.
|
|
*
|
|
* @see ssh_channel_open_forward()
|
|
*/
|
|
int ssh_channel_open_auth_agent(ssh_channel channel){
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
#ifdef WITH_SSH1
|
|
if (channel->session->version == 1) {
|
|
return SSH_ERROR;
|
|
}
|
|
#endif
|
|
|
|
return channel_open(channel,
|
|
"auth-agent@openssh.com",
|
|
CHANNEL_INITIAL_WINDOW,
|
|
CHANNEL_MAX_PACKET,
|
|
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 numeric IP address of the machine from where the
|
|
* connection request originates. This is mostly for
|
|
* logging purposes.
|
|
*
|
|
* @param[in] localport The port on the host from where the connection
|
|
* originated. This is mostly for logging purposes.
|
|
*
|
|
* @return SSH_OK on success,
|
|
* SSH_ERROR if an error occurred,
|
|
* SSH_AGAIN if in nonblocking mode and call has
|
|
* to be done again.
|
|
*
|
|
* @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;
|
|
ssh_buffer payload = NULL;
|
|
ssh_string str = NULL;
|
|
int rc = SSH_ERROR;
|
|
|
|
if(channel == NULL) {
|
|
return rc;
|
|
}
|
|
|
|
session = channel->session;
|
|
|
|
if(remotehost == NULL || sourcehost == NULL) {
|
|
ssh_set_error_invalid(session);
|
|
return rc;
|
|
}
|
|
|
|
payload = ssh_buffer_new();
|
|
if (payload == NULL) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
|
|
rc = ssh_buffer_pack(payload,
|
|
"sdsd",
|
|
remotehost,
|
|
remoteport,
|
|
sourcehost,
|
|
localport);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
|
|
rc = channel_open(channel,
|
|
"direct-tcpip",
|
|
CHANNEL_INITIAL_WINDOW,
|
|
CHANNEL_MAX_PACKET,
|
|
payload);
|
|
|
|
error:
|
|
ssh_buffer_free(payload);
|
|
ssh_string_free(str);
|
|
|
|
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;
|
|
|
|
if (channel == NULL) {
|
|
return;
|
|
}
|
|
|
|
session = channel->session;
|
|
if (session->alive && channel->state == SSH_CHANNEL_STATE_OPEN) {
|
|
ssh_channel_close(channel);
|
|
}
|
|
channel->flags |= SSH_CHANNEL_FLAG_FREED_LOCAL;
|
|
|
|
/* The idea behind the flags is the following : it is well possible
|
|
* that a client closes a channel that stills exists on the server side.
|
|
* We definitively close the channel when we receive a close message *and*
|
|
* the user closed it.
|
|
*/
|
|
if((channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE)
|
|
|| (channel->flags & SSH_CHANNEL_FLAG_NOT_BOUND)){
|
|
ssh_channel_do_free(channel);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
* @brief Effectively free a channel, without caring about flags
|
|
*/
|
|
|
|
void ssh_channel_do_free(ssh_channel channel){
|
|
struct ssh_iterator *it;
|
|
ssh_session session = channel->session;
|
|
it = ssh_list_find(session->channels, channel);
|
|
if(it != NULL){
|
|
ssh_list_remove(session->channels, it);
|
|
}
|
|
ssh_buffer_free(channel->stdout_buffer);
|
|
ssh_buffer_free(channel->stderr_buffer);
|
|
if (channel->callbacks != NULL){
|
|
ssh_list_free(channel->callbacks);
|
|
}
|
|
|
|
/* debug trick to catch use after frees */
|
|
memset(channel, 'X', sizeof(struct ssh_channel_struct));
|
|
SAFE_FREE(channel);
|
|
}
|
|
|
|
/**
|
|
* @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 occurred.
|
|
*
|
|
* Example:
|
|
@code
|
|
rc = ssh_channel_send_eof(channel);
|
|
if (rc == SSH_ERROR) {
|
|
return -1;
|
|
}
|
|
while(!ssh_channel_is_eof(channel)) {
|
|
rc = ssh_channel_read(channel, buf, sizeof(buf), 0);
|
|
if (rc == SSH_ERROR) {
|
|
return -1;
|
|
}
|
|
}
|
|
ssh_channel_close(channel);
|
|
@endcode
|
|
*
|
|
* @see ssh_channel_close()
|
|
* @see ssh_channel_free()
|
|
* @see ssh_channel_is_eof()
|
|
*/
|
|
int ssh_channel_send_eof(ssh_channel channel){
|
|
ssh_session session;
|
|
int rc = SSH_ERROR;
|
|
int err;
|
|
|
|
if(channel == NULL) {
|
|
return rc;
|
|
}
|
|
|
|
session = channel->session;
|
|
|
|
err = ssh_buffer_pack(session->out_buffer,
|
|
"bd",
|
|
SSH2_MSG_CHANNEL_EOF,
|
|
channel->remote_channel);
|
|
if (err != SSH_OK) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
|
|
rc = ssh_packet_send(session);
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"Sent a EOF on client channel (%d:%d)",
|
|
channel->local_channel,
|
|
channel->remote_channel);
|
|
|
|
rc = ssh_channel_flush(channel);
|
|
if(rc == SSH_ERROR)
|
|
goto error;
|
|
|
|
channel->local_eof = 1;
|
|
|
|
return rc;
|
|
error:
|
|
ssh_buffer_reinit(session->out_buffer);
|
|
|
|
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 occurred.
|
|
*
|
|
* @see ssh_channel_free()
|
|
* @see ssh_channel_is_eof()
|
|
*/
|
|
int ssh_channel_close(ssh_channel channel){
|
|
ssh_session session;
|
|
int rc = 0;
|
|
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
session = channel->session;
|
|
|
|
if (channel->local_eof == 0) {
|
|
rc = ssh_channel_send_eof(channel);
|
|
}
|
|
|
|
if (rc != SSH_OK) {
|
|
return rc;
|
|
}
|
|
|
|
rc = ssh_buffer_pack(session->out_buffer,
|
|
"bd",
|
|
SSH2_MSG_CHANNEL_CLOSE,
|
|
channel->remote_channel);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
|
|
rc = ssh_packet_send(session);
|
|
SSH_LOG(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;
|
|
}
|
|
|
|
rc = ssh_channel_flush(channel);
|
|
if(rc == SSH_ERROR)
|
|
goto error;
|
|
|
|
return rc;
|
|
error:
|
|
ssh_buffer_reinit(session->out_buffer);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* this termination function waits for a window growing condition */
|
|
static int ssh_channel_waitwindow_termination(void *c){
|
|
ssh_channel channel = (ssh_channel) c;
|
|
if (channel->remote_window > 0 ||
|
|
channel->session->session_state == SSH_SESSION_STATE_ERROR ||
|
|
channel->state == SSH_CHANNEL_STATE_CLOSED)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/* This termination function waits until the session is not in blocked status
|
|
* anymore, e.g. because of a key re-exchange.
|
|
*/
|
|
static int ssh_waitsession_unblocked(void *s){
|
|
ssh_session session = (ssh_session)s;
|
|
switch (session->session_state){
|
|
case SSH_SESSION_STATE_DH:
|
|
case SSH_SESSION_STATE_INITIAL_KEX:
|
|
case SSH_SESSION_STATE_KEXINIT_RECEIVED:
|
|
return 0;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
/**
|
|
* @internal
|
|
* @brief Flushes a channel (and its session) until the output buffer
|
|
* is empty, or timeout elapsed.
|
|
* @param channel SSH channel
|
|
* @returns SSH_OK On success,
|
|
* SSH_ERROR on error
|
|
* SSH_AGAIN Timeout elapsed (or in nonblocking mode)
|
|
*/
|
|
int ssh_channel_flush(ssh_channel channel){
|
|
return ssh_blocking_flush(channel->session, SSH_TIMEOUT_DEFAULT);
|
|
}
|
|
|
|
static int channel_write_common(ssh_channel channel,
|
|
const void *data,
|
|
uint32_t len, int is_stderr)
|
|
{
|
|
ssh_session session;
|
|
uint32_t origlen = len;
|
|
size_t effectivelen;
|
|
size_t maxpacketlen;
|
|
int rc;
|
|
|
|
if(channel == NULL) {
|
|
return -1;
|
|
}
|
|
session = channel->session;
|
|
if(data == NULL) {
|
|
ssh_set_error_invalid(session);
|
|
return -1;
|
|
}
|
|
|
|
if (len > INT_MAX) {
|
|
SSH_LOG(SSH_LOG_PROTOCOL,
|
|
"Length (%u) is bigger than INT_MAX", len);
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Handle the max packet len from remote side, be nice
|
|
* 10 bytes for the headers
|
|
*/
|
|
maxpacketlen = channel->remote_maxpacket - 10;
|
|
|
|
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);
|
|
return -1;
|
|
}
|
|
|
|
if (channel->state != SSH_CHANNEL_STATE_OPEN || channel->delayed_close != 0) {
|
|
ssh_set_error(session, SSH_REQUEST_DENIED, "Remote channel is closed");
|
|
|
|
return -1;
|
|
}
|
|
|
|
if (session->session_state == SSH_SESSION_STATE_ERROR) {
|
|
return SSH_ERROR;
|
|
}
|
|
#ifdef WITH_SSH1
|
|
if (channel->version == 1) {
|
|
rc = ssh_channel_write1(channel, data, len);
|
|
|
|
return rc;
|
|
}
|
|
#endif
|
|
if (ssh_waitsession_unblocked(session) == 0){
|
|
rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_DEFAULT,
|
|
ssh_waitsession_unblocked, session);
|
|
if (rc == SSH_ERROR || !ssh_waitsession_unblocked(session))
|
|
goto out;
|
|
}
|
|
while (len > 0) {
|
|
if (channel->remote_window < len) {
|
|
SSH_LOG(SSH_LOG_PROTOCOL,
|
|
"Remote window is %d bytes. going to write %d bytes",
|
|
channel->remote_window,
|
|
len);
|
|
/* What happens when the channel window is zero? */
|
|
if(channel->remote_window == 0) {
|
|
/* nothing can be written */
|
|
SSH_LOG(SSH_LOG_PROTOCOL,
|
|
"Wait for a growing window message...");
|
|
rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_DEFAULT,
|
|
ssh_channel_waitwindow_termination,channel);
|
|
if (rc == SSH_ERROR ||
|
|
!ssh_channel_waitwindow_termination(channel) ||
|
|
session->session_state == SSH_SESSION_STATE_ERROR ||
|
|
channel->state == SSH_CHANNEL_STATE_CLOSED)
|
|
goto out;
|
|
continue;
|
|
}
|
|
effectivelen = MIN(len, channel->remote_window);
|
|
} else {
|
|
effectivelen = len;
|
|
}
|
|
|
|
effectivelen = MIN(effectivelen, maxpacketlen);;
|
|
|
|
rc = ssh_buffer_pack(session->out_buffer,
|
|
"bd",
|
|
is_stderr ? SSH2_MSG_CHANNEL_EXTENDED_DATA : SSH2_MSG_CHANNEL_DATA,
|
|
channel->remote_channel);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
|
|
/* stderr message has an extra field */
|
|
if (is_stderr) {
|
|
rc = ssh_buffer_pack(session->out_buffer,
|
|
"d",
|
|
SSH2_EXTENDED_DATA_STDERR);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* append payload data */
|
|
rc = ssh_buffer_pack(session->out_buffer,
|
|
"dP",
|
|
effectivelen,
|
|
(size_t)effectivelen, data);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
|
|
rc = ssh_packet_send(session);
|
|
if (rc == SSH_ERROR) {
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"channel_write wrote %ld bytes", (long int) effectivelen);
|
|
|
|
channel->remote_window -= effectivelen;
|
|
len -= effectivelen;
|
|
data = ((uint8_t*)data + effectivelen);
|
|
if (channel->counter != NULL) {
|
|
channel->counter->out_bytes += effectivelen;
|
|
}
|
|
}
|
|
|
|
/* it's a good idea to flush the socket now */
|
|
rc = ssh_channel_flush(channel);
|
|
if (rc == SSH_ERROR) {
|
|
goto error;
|
|
}
|
|
|
|
out:
|
|
return (int)(origlen - len);
|
|
|
|
error:
|
|
ssh_buffer_reinit(session->out_buffer);
|
|
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the remote window size.
|
|
*
|
|
* This is the maximum amounts of bytes the remote side expects us to send
|
|
* before growing the window again.
|
|
*
|
|
* @param[in] channel The channel to query.
|
|
*
|
|
* @return The remote window size
|
|
*
|
|
* @warning A nonzero return value does not guarantee the socket is ready
|
|
* to send that much data. Buffering may happen in the local SSH
|
|
* packet buffer, so beware of really big window sizes.
|
|
*
|
|
* @warning A zero return value means ssh_channel_write (default settings)
|
|
* will block until the window grows back.
|
|
*/
|
|
uint32_t ssh_channel_window_size(ssh_channel channel) {
|
|
return channel->remote_window;
|
|
}
|
|
|
|
/**
|
|
* @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 ssh_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 ssh_channel_is_closed()
|
|
*/
|
|
int ssh_channel_is_open(ssh_channel channel) {
|
|
if(channel == NULL) {
|
|
return 0;
|
|
}
|
|
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 ssh_channel_is_open()
|
|
*/
|
|
int ssh_channel_is_closed(ssh_channel channel) {
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
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 == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
if ((channel->stdout_buffer &&
|
|
ssh_buffer_get_len(channel->stdout_buffer) > 0) ||
|
|
(channel->stderr_buffer &&
|
|
ssh_buffer_get_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.
|
|
*
|
|
* @warning A side-effect of this is to put the whole session
|
|
* in non-blocking mode.
|
|
* @see ssh_set_blocking()
|
|
*/
|
|
void ssh_channel_set_blocking(ssh_channel channel, int blocking) {
|
|
if(channel == NULL) {
|
|
return;
|
|
}
|
|
ssh_set_blocking(channel->session,blocking);
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
|
|
channel=channel_from_msg(session,packet);
|
|
if (channel == NULL) {
|
|
SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
SSH_LOG(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(SSH_LOG_RARE, "SSH_CHANNEL_SUCCESS received in incorrect state %d",
|
|
channel->request_state);
|
|
} else {
|
|
channel->request_state=SSH_CHANNEL_REQ_STATE_ACCEPTED;
|
|
}
|
|
|
|
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;
|
|
|
|
channel=channel_from_msg(session,packet);
|
|
if (channel == NULL) {
|
|
SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
SSH_LOG(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(SSH_LOG_RARE, "SSH_CHANNEL_FAILURE received in incorrect state %d",
|
|
channel->request_state);
|
|
} else {
|
|
channel->request_state=SSH_CHANNEL_REQ_STATE_DENIED;
|
|
}
|
|
|
|
return SSH_PACKET_USED;
|
|
}
|
|
|
|
static int ssh_channel_request_termination(void *c){
|
|
ssh_channel channel = (ssh_channel)c;
|
|
if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING ||
|
|
channel->session->session_state == SSH_SESSION_STATE_ERROR)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int channel_request(ssh_channel channel, const char *request,
|
|
ssh_buffer buffer, int reply) {
|
|
ssh_session session = channel->session;
|
|
int rc = SSH_ERROR;
|
|
int ret;
|
|
|
|
switch(channel->request_state){
|
|
case SSH_CHANNEL_REQ_STATE_NONE:
|
|
break;
|
|
default:
|
|
goto pending;
|
|
}
|
|
|
|
ret = ssh_buffer_pack(session->out_buffer,
|
|
"bdsb",
|
|
SSH2_MSG_CHANNEL_REQUEST,
|
|
channel->remote_channel,
|
|
request,
|
|
reply == 0 ? 0 : 1);
|
|
if (ret != SSH_OK) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
|
|
if (buffer != NULL) {
|
|
if (ssh_buffer_add_data(session->out_buffer, ssh_buffer_get(buffer),
|
|
ssh_buffer_get_len(buffer)) < 0) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
}
|
|
channel->request_state = SSH_CHANNEL_REQ_STATE_PENDING;
|
|
if (ssh_packet_send(session) == SSH_ERROR) {
|
|
return rc;
|
|
}
|
|
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"Sent a SSH_MSG_CHANNEL_REQUEST %s", request);
|
|
if (reply == 0) {
|
|
channel->request_state = SSH_CHANNEL_REQ_STATE_NONE;
|
|
return SSH_OK;
|
|
}
|
|
pending:
|
|
rc = ssh_handle_packets_termination(session,
|
|
SSH_TIMEOUT_DEFAULT,
|
|
ssh_channel_request_termination,
|
|
channel);
|
|
|
|
if(session->session_state == SSH_SESSION_STATE_ERROR || rc == SSH_ERROR) {
|
|
channel->request_state = SSH_CHANNEL_REQ_STATE_ERROR;
|
|
}
|
|
/* 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(SSH_LOG_PROTOCOL,
|
|
"Channel request %s success",request);
|
|
rc=SSH_OK;
|
|
break;
|
|
case SSH_CHANNEL_REQ_STATE_PENDING:
|
|
rc = SSH_AGAIN;
|
|
return rc;
|
|
case SSH_CHANNEL_REQ_STATE_NONE:
|
|
/* 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;
|
|
|
|
return rc;
|
|
error:
|
|
ssh_buffer_reinit(session->out_buffer);
|
|
|
|
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 occurred,
|
|
* SSH_AGAIN if in nonblocking mode and call has
|
|
* to be done again.
|
|
*/
|
|
int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal,
|
|
int col, int row) {
|
|
ssh_session session;
|
|
ssh_buffer buffer = NULL;
|
|
int rc = SSH_ERROR;
|
|
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
session = channel->session;
|
|
|
|
if(terminal == NULL) {
|
|
ssh_set_error_invalid(channel->session);
|
|
return rc;
|
|
}
|
|
|
|
#ifdef WITH_SSH1
|
|
if (channel->version==1) {
|
|
rc = ssh_channel_request_pty_size1(channel,terminal, col, row);
|
|
|
|
return rc;
|
|
}
|
|
#endif
|
|
switch(channel->request_state){
|
|
case SSH_CHANNEL_REQ_STATE_NONE:
|
|
break;
|
|
default:
|
|
goto pending;
|
|
}
|
|
|
|
buffer = ssh_buffer_new();
|
|
if (buffer == NULL) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
|
|
rc = ssh_buffer_pack(buffer,
|
|
"sdddddb",
|
|
terminal,
|
|
col,
|
|
row,
|
|
0, /* pix */
|
|
0, /* pix */
|
|
1, /* add a 0byte string */
|
|
0);
|
|
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
pending:
|
|
rc = channel_request(channel, "pty-req", buffer, 1);
|
|
error:
|
|
ssh_buffer_free(buffer);
|
|
|
|
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 occurred,
|
|
* SSH_AGAIN if in nonblocking mode and call has
|
|
* to be done again.
|
|
*
|
|
* @see ssh_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 occurred.
|
|
*
|
|
* @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;
|
|
|
|
#ifdef WITH_SSH1
|
|
if (channel->version == 1) {
|
|
rc = ssh_channel_change_pty_size1(channel,cols,rows);
|
|
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
buffer = ssh_buffer_new();
|
|
if (buffer == NULL) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
|
|
rc = ssh_buffer_pack(buffer,
|
|
"dddd",
|
|
cols,
|
|
rows,
|
|
0, /* pix */
|
|
0 /* pix */);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
|
|
rc = channel_request(channel, "window-change", buffer, 0);
|
|
error:
|
|
ssh_buffer_free(buffer);
|
|
|
|
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 occurred,
|
|
* SSH_AGAIN if in nonblocking mode and call has
|
|
* to be done again.
|
|
*/
|
|
int ssh_channel_request_shell(ssh_channel channel) {
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
#ifdef WITH_SSH1
|
|
if (channel->version == 1) {
|
|
return ssh_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 occurred,
|
|
* SSH_AGAIN if in nonblocking mode and call has
|
|
* to be done again.
|
|
*
|
|
* @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;
|
|
int rc = SSH_ERROR;
|
|
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
if(subsys == NULL) {
|
|
ssh_set_error_invalid(channel->session);
|
|
return rc;
|
|
}
|
|
switch(channel->request_state){
|
|
case SSH_CHANNEL_REQ_STATE_NONE:
|
|
break;
|
|
default:
|
|
goto pending;
|
|
}
|
|
|
|
buffer = ssh_buffer_new();
|
|
if (buffer == NULL) {
|
|
ssh_set_error_oom(channel->session);
|
|
goto error;
|
|
}
|
|
|
|
rc = ssh_buffer_pack(buffer, "s", subsys);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(channel->session);
|
|
goto error;
|
|
}
|
|
pending:
|
|
rc = channel_request(channel, "subsystem", buffer, 1);
|
|
error:
|
|
ssh_buffer_free(buffer);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int ssh_channel_request_sftp( ssh_channel channel){
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
return ssh_channel_request_subsystem(channel, "sftp");
|
|
}
|
|
|
|
static char *generate_cookie(void) {
|
|
static const char *hex = "0123456789abcdef";
|
|
char s[36];
|
|
unsigned char rnd[16];
|
|
int i;
|
|
|
|
ssh_get_random(rnd,sizeof(rnd),0);
|
|
for (i = 0; i < 16; i++) {
|
|
s[i*2] = hex[rnd[i] & 0x0f];
|
|
s[i*2+1] = hex[rnd[i] >> 4];
|
|
}
|
|
s[32] = '\0';
|
|
return strdup(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 occurred,
|
|
* SSH_AGAIN if in nonblocking mode and call has
|
|
* to be done again.
|
|
*/
|
|
int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol,
|
|
const char *cookie, int screen_number) {
|
|
ssh_buffer buffer = NULL;
|
|
char *c = NULL;
|
|
int rc = SSH_ERROR;
|
|
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
switch(channel->request_state){
|
|
case SSH_CHANNEL_REQ_STATE_NONE:
|
|
break;
|
|
default:
|
|
goto pending;
|
|
}
|
|
|
|
buffer = ssh_buffer_new();
|
|
if (buffer == NULL) {
|
|
ssh_set_error_oom(channel->session);
|
|
goto error;
|
|
}
|
|
|
|
if (cookie == NULL) {
|
|
c = generate_cookie();
|
|
if (c == NULL) {
|
|
ssh_set_error_oom(channel->session);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
rc = ssh_buffer_pack(buffer,
|
|
"bssd",
|
|
single_connection == 0 ? 0 : 1,
|
|
protocol ? protocol : "MIT-MAGIC-COOKIE-1",
|
|
cookie ? cookie : c,
|
|
screen_number);
|
|
if (c != NULL){
|
|
SAFE_FREE(c);
|
|
}
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(channel->session);
|
|
goto error;
|
|
}
|
|
pending:
|
|
rc = channel_request(channel, "x11-req", buffer, 1);
|
|
|
|
error:
|
|
ssh_buffer_free(buffer);
|
|
return rc;
|
|
}
|
|
|
|
static ssh_channel ssh_channel_accept(ssh_session session, int channeltype,
|
|
int timeout_ms, int *destination_port) {
|
|
#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;
|
|
|
|
/*
|
|
* We sleep for 50 ms in ssh_handle_packets() and later sleep for
|
|
* 50 ms. So we need to decrement by 100 ms.
|
|
*/
|
|
for (t = timeout_ms; t >= 0; t -= 100) {
|
|
if (timeout_ms == 0) {
|
|
ssh_handle_packets(session, 0);
|
|
} else {
|
|
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);
|
|
if(destination_port) {
|
|
*destination_port=msg->channel_request_open.destination_port;
|
|
}
|
|
|
|
ssh_message_free(msg);
|
|
return channel;
|
|
}
|
|
iterator = iterator->next;
|
|
}
|
|
}
|
|
if(t>0){
|
|
#ifdef _WIN32
|
|
Sleep(50); /* 50ms */
|
|
#else
|
|
nanosleep(&ts, NULL);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
ssh_set_error(session, SSH_NO_ERROR, "No channel request of this type from server");
|
|
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, NULL);
|
|
}
|
|
|
|
/**
|
|
* @brief Send an "auth-agent-req" channel request over an existing session channel.
|
|
*
|
|
* This client-side request will enable forwarding the agent over an secure tunnel.
|
|
* When the server is ready to open one authentication agent channel, an
|
|
* ssh_channel_open_request_auth_agent_callback event will be generated.
|
|
*
|
|
* @param[in] channel The channel to send signal.
|
|
*
|
|
* @return SSH_OK on success, SSH_ERROR if an error occurred
|
|
*/
|
|
int ssh_channel_request_auth_agent(ssh_channel channel) {
|
|
if (channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
return channel_request(channel, "auth-agent-req@openssh.com", NULL, 0);
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"Received SSH_REQUEST_SUCCESS");
|
|
if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){
|
|
SSH_LOG(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;
|
|
}
|
|
|
|
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;
|
|
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"Received SSH_REQUEST_FAILURE");
|
|
if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){
|
|
SSH_LOG(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;
|
|
}
|
|
|
|
return SSH_PACKET_USED;
|
|
|
|
}
|
|
|
|
static int ssh_global_request_termination(void *s){
|
|
ssh_session session = (ssh_session) s;
|
|
if (session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING ||
|
|
session->session_state == SSH_SESSION_STATE_ERROR)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @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 occurred,
|
|
* SSH_AGAIN if in nonblocking mode and call has
|
|
* to be done again.
|
|
*/
|
|
static int global_request(ssh_session session, const char *request,
|
|
ssh_buffer buffer, int reply) {
|
|
int rc;
|
|
|
|
switch (session->global_req_state) {
|
|
case SSH_CHANNEL_REQ_STATE_NONE:
|
|
break;
|
|
default:
|
|
goto pending;
|
|
}
|
|
|
|
rc = ssh_buffer_pack(session->out_buffer,
|
|
"bsb",
|
|
SSH2_MSG_GLOBAL_REQUEST,
|
|
request,
|
|
reply == 0 ? 0 : 1);
|
|
if (rc != SSH_OK){
|
|
ssh_set_error_oom(session);
|
|
rc = SSH_ERROR;
|
|
goto error;
|
|
}
|
|
|
|
if (buffer != NULL) {
|
|
rc = ssh_buffer_add_data(session->out_buffer,
|
|
ssh_buffer_get(buffer),
|
|
ssh_buffer_get_len(buffer));
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(session);
|
|
rc = SSH_ERROR;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
session->global_req_state = SSH_CHANNEL_REQ_STATE_PENDING;
|
|
rc = ssh_packet_send(session);
|
|
if (rc == SSH_ERROR) {
|
|
return rc;
|
|
}
|
|
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"Sent a SSH_MSG_GLOBAL_REQUEST %s", request);
|
|
|
|
if (reply == 0) {
|
|
session->global_req_state = SSH_CHANNEL_REQ_STATE_NONE;
|
|
|
|
return SSH_OK;
|
|
}
|
|
pending:
|
|
rc = ssh_handle_packets_termination(session,
|
|
SSH_TIMEOUT_DEFAULT,
|
|
ssh_global_request_termination,
|
|
session);
|
|
|
|
if(rc==SSH_ERROR || session->session_state == SSH_SESSION_STATE_ERROR){
|
|
session->global_req_state = SSH_CHANNEL_REQ_STATE_ERROR;
|
|
}
|
|
switch(session->global_req_state){
|
|
case SSH_CHANNEL_REQ_STATE_ACCEPTED:
|
|
SSH_LOG(SSH_LOG_PROTOCOL, "Global request %s success",request);
|
|
rc=SSH_OK;
|
|
break;
|
|
case SSH_CHANNEL_REQ_STATE_DENIED:
|
|
SSH_LOG(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:
|
|
rc = SSH_ERROR;
|
|
break;
|
|
case SSH_CHANNEL_REQ_STATE_PENDING:
|
|
return SSH_AGAIN;
|
|
}
|
|
session->global_req_state = SSH_CHANNEL_REQ_STATE_NONE;
|
|
|
|
return rc;
|
|
error:
|
|
ssh_buffer_reinit(session->out_buffer);
|
|
|
|
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 occurred,
|
|
* SSH_AGAIN if in nonblocking mode and call has
|
|
* to be done again.
|
|
**/
|
|
int ssh_channel_listen_forward(ssh_session session,
|
|
const char *address,
|
|
int port,
|
|
int *bound_port)
|
|
{
|
|
ssh_buffer buffer = NULL;
|
|
int rc = SSH_ERROR;
|
|
|
|
if(session->global_req_state != SSH_CHANNEL_REQ_STATE_NONE)
|
|
goto pending;
|
|
|
|
buffer = ssh_buffer_new();
|
|
if (buffer == NULL) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
|
|
rc = ssh_buffer_pack(buffer,
|
|
"sd",
|
|
address ? address : "",
|
|
port);
|
|
if (rc != SSH_OK){
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
pending:
|
|
rc = global_request(session, "tcpip-forward", buffer, 1);
|
|
|
|
/* TODO: FIXME no guarantee the last packet we received contains
|
|
* that info */
|
|
if (rc == SSH_OK && port == 0 && bound_port != NULL) {
|
|
rc = ssh_buffer_unpack(session->in_buffer, "d", bound_port);
|
|
if (rc != SSH_OK)
|
|
*bound_port = 0;
|
|
}
|
|
|
|
error:
|
|
ssh_buffer_free(buffer);
|
|
return rc;
|
|
}
|
|
|
|
/* DEPRECATED */
|
|
int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port) {
|
|
return ssh_channel_listen_forward(session, address, port, bound_port);
|
|
}
|
|
|
|
/* DEPRECATED */
|
|
ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) {
|
|
return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, NULL);
|
|
}
|
|
|
|
/**
|
|
* @brief Accept an incoming TCP/IP forwarding channel and get information
|
|
* about incomming connection
|
|
* @param[in] session The ssh session to use.
|
|
*
|
|
* @param[in] timeout_ms A timeout in milliseconds.
|
|
*
|
|
* @param[in] destination_port A pointer to destination port or NULL.
|
|
*
|
|
* @return Newly created channel, or NULL if no incoming channel request from
|
|
* the server
|
|
*/
|
|
ssh_channel ssh_channel_accept_forward(ssh_session session, int timeout_ms, int* destination_port) {
|
|
return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port);
|
|
}
|
|
|
|
/**
|
|
* @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 occurred,
|
|
* SSH_AGAIN if in nonblocking mode and call has
|
|
* to be done again.
|
|
*/
|
|
int ssh_channel_cancel_forward(ssh_session session,
|
|
const char *address,
|
|
int port)
|
|
{
|
|
ssh_buffer buffer = NULL;
|
|
int rc = SSH_ERROR;
|
|
|
|
if(session->global_req_state != SSH_CHANNEL_REQ_STATE_NONE)
|
|
goto pending;
|
|
|
|
buffer = ssh_buffer_new();
|
|
if (buffer == NULL) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
|
|
rc = ssh_buffer_pack(buffer, "sd",
|
|
address ? address : "",
|
|
port);
|
|
if (rc != SSH_OK){
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
pending:
|
|
rc = global_request(session, "cancel-tcpip-forward", buffer, 1);
|
|
|
|
error:
|
|
ssh_buffer_free(buffer);
|
|
return rc;
|
|
}
|
|
|
|
/* DEPRECATED */
|
|
int ssh_forward_cancel(ssh_session session, const char *address, int port) {
|
|
return ssh_channel_cancel_forward(session, address, port);
|
|
}
|
|
|
|
/**
|
|
* @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 occurred,
|
|
* SSH_AGAIN if in nonblocking mode and call has
|
|
* to be done again.
|
|
* @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;
|
|
int rc = SSH_ERROR;
|
|
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
if(name == NULL || value == NULL) {
|
|
ssh_set_error_invalid(channel->session);
|
|
return rc;
|
|
}
|
|
switch(channel->request_state){
|
|
case SSH_CHANNEL_REQ_STATE_NONE:
|
|
break;
|
|
default:
|
|
goto pending;
|
|
}
|
|
buffer = ssh_buffer_new();
|
|
if (buffer == NULL) {
|
|
ssh_set_error_oom(channel->session);
|
|
goto error;
|
|
}
|
|
|
|
rc = ssh_buffer_pack(buffer,
|
|
"ss",
|
|
name,
|
|
value);
|
|
if (rc != SSH_OK){
|
|
ssh_set_error_oom(channel->session);
|
|
goto error;
|
|
}
|
|
pending:
|
|
rc = channel_request(channel, "env", buffer,1);
|
|
error:
|
|
ssh_buffer_free(buffer);
|
|
|
|
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 occurred,
|
|
* SSH_AGAIN if in nonblocking mode and call has
|
|
* to be done again.
|
|
*
|
|
* Example:
|
|
@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 ssh_channel_request_shell()
|
|
*/
|
|
int ssh_channel_request_exec(ssh_channel channel, const char *cmd) {
|
|
ssh_buffer buffer = NULL;
|
|
int rc = SSH_ERROR;
|
|
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
if(cmd == NULL) {
|
|
ssh_set_error_invalid(channel->session);
|
|
return rc;
|
|
}
|
|
|
|
#ifdef WITH_SSH1
|
|
if (channel->version == 1) {
|
|
return ssh_channel_request_exec1(channel, cmd);
|
|
}
|
|
#endif
|
|
switch(channel->request_state){
|
|
case SSH_CHANNEL_REQ_STATE_NONE:
|
|
break;
|
|
default:
|
|
goto pending;
|
|
}
|
|
buffer = ssh_buffer_new();
|
|
if (buffer == NULL) {
|
|
ssh_set_error_oom(channel->session);
|
|
goto error;
|
|
}
|
|
|
|
rc = ssh_buffer_pack(buffer, "s", cmd);
|
|
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(channel->session);
|
|
goto error;
|
|
}
|
|
pending:
|
|
rc = channel_request(channel, "exec", buffer, 1);
|
|
error:
|
|
ssh_buffer_free(buffer);
|
|
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).
|
|
*
|
|
* OpenSSH doesn't support signals yet, see:
|
|
* https://bugzilla.mindrot.org/show_bug.cgi?id=1424
|
|
*
|
|
* @param[in] channel The channel to send signal.
|
|
*
|
|
* @param[in] sig The signal to send (without SIG prefix)
|
|
* \n\n
|
|
* SIGABRT -> ABRT \n
|
|
* SIGALRM -> ALRM \n
|
|
* SIGFPE -> FPE \n
|
|
* SIGHUP -> HUP \n
|
|
* SIGILL -> ILL \n
|
|
* SIGINT -> INT \n
|
|
* SIGKILL -> KILL \n
|
|
* SIGPIPE -> PIPE \n
|
|
* SIGQUIT -> QUIT \n
|
|
* SIGSEGV -> SEGV \n
|
|
* SIGTERM -> TERM \n
|
|
* SIGUSR1 -> USR1 \n
|
|
* SIGUSR2 -> USR2 \n
|
|
*
|
|
* @return SSH_OK on success, SSH_ERROR if an error occurred
|
|
* (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;
|
|
int rc = SSH_ERROR;
|
|
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
if(sig == NULL) {
|
|
ssh_set_error_invalid(channel->session);
|
|
return rc;
|
|
}
|
|
|
|
#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) {
|
|
ssh_set_error_oom(channel->session);
|
|
goto error;
|
|
}
|
|
|
|
rc = ssh_buffer_pack(buffer, "s", sig);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(channel->session);
|
|
goto error;
|
|
}
|
|
|
|
rc = channel_request(channel, "signal", buffer, 0);
|
|
error:
|
|
ssh_buffer_free(buffer);
|
|
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
|
|
* @warning This function doesn't work in nonblocking/timeout mode
|
|
* @see ssh_channel_read
|
|
*/
|
|
int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count,
|
|
int is_stderr) {
|
|
ssh_session session;
|
|
char buffer_tmp[8192];
|
|
int r;
|
|
uint32_t total=0;
|
|
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
session = channel->session;
|
|
|
|
if(buffer == NULL) {
|
|
ssh_set_error_invalid(channel->session);
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
ssh_buffer_reinit(buffer);
|
|
if(count==0){
|
|
do {
|
|
r=ssh_channel_poll(channel, is_stderr);
|
|
if(r < 0){
|
|
return r;
|
|
}
|
|
if(r > 0){
|
|
r=ssh_channel_read(channel, buffer_tmp, r, is_stderr);
|
|
if(r < 0){
|
|
return r;
|
|
}
|
|
if(ssh_buffer_add_data(buffer,buffer_tmp,r) < 0){
|
|
ssh_set_error_oom(session);
|
|
r = SSH_ERROR;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
if(ssh_channel_is_eof(channel)){
|
|
return 0;
|
|
}
|
|
ssh_handle_packets(channel->session, SSH_TIMEOUT_INFINITE);
|
|
} while (r == 0);
|
|
}
|
|
while(total < count){
|
|
r=ssh_channel_read(channel, buffer_tmp, sizeof(buffer_tmp), is_stderr);
|
|
if(r<0){
|
|
return r;
|
|
}
|
|
if(r==0){
|
|
return total;
|
|
}
|
|
if (ssh_buffer_add_data(buffer,buffer_tmp,r) < 0) {
|
|
ssh_set_error_oom(session);
|
|
|
|
return SSH_ERROR;
|
|
}
|
|
total += r;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
struct ssh_channel_read_termination_struct {
|
|
ssh_channel channel;
|
|
uint32_t count;
|
|
ssh_buffer buffer;
|
|
};
|
|
|
|
static int ssh_channel_read_termination(void *s){
|
|
struct ssh_channel_read_termination_struct *ctx = s;
|
|
if (ssh_buffer_get_len(ctx->buffer) >= ctx->count ||
|
|
ctx->channel->remote_eof ||
|
|
ctx->channel->session->session_state == SSH_SESSION_STATE_ERROR)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/* 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. In nonblocking mode it Can return 0 if no data
|
|
* is available or SSH_AGAIN.
|
|
*
|
|
* @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)
|
|
{
|
|
return ssh_channel_read_timeout(channel, dest, count, is_stderr, -1);
|
|
}
|
|
|
|
/**
|
|
* @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.
|
|
*
|
|
* @param[in] timeout_ms A timeout in milliseconds. A value of -1 means
|
|
* infinite timeout.
|
|
*
|
|
* @return The number of bytes read, 0 on end of file or SSH_ERROR
|
|
* on error. In nonblocking mode it Can return 0 if no data
|
|
* is available or SSH_AGAIN.
|
|
*
|
|
* @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_timeout(ssh_channel channel,
|
|
void *dest,
|
|
uint32_t count,
|
|
int is_stderr,
|
|
int timeout)
|
|
{
|
|
ssh_session session;
|
|
ssh_buffer stdbuf;
|
|
uint32_t len;
|
|
struct ssh_channel_read_termination_struct ctx;
|
|
int rc;
|
|
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
if(dest == NULL) {
|
|
ssh_set_error_invalid(channel->session);
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
session = channel->session;
|
|
stdbuf = channel->stdout_buffer;
|
|
|
|
if (count == 0) {
|
|
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(SSH_LOG_PACKET,
|
|
"Read (%d) buffered : %d bytes. Window: %d",
|
|
count,
|
|
ssh_buffer_get_len(stdbuf),
|
|
channel->local_window);
|
|
|
|
if (count > ssh_buffer_get_len(stdbuf) + channel->local_window) {
|
|
if (grow_window(session, channel, count - ssh_buffer_get_len(stdbuf)) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* block reading until at least one byte has been read
|
|
* and ignore the trivial case count=0
|
|
*/
|
|
ctx.channel = channel;
|
|
ctx.buffer = stdbuf;
|
|
ctx.count = 1;
|
|
|
|
if (timeout < 0) {
|
|
timeout = SSH_TIMEOUT_DEFAULT;
|
|
}
|
|
|
|
rc = ssh_handle_packets_termination(session,
|
|
timeout,
|
|
ssh_channel_read_termination,
|
|
&ctx);
|
|
if (rc == SSH_ERROR){
|
|
return rc;
|
|
}
|
|
if (session->session_state == SSH_SESSION_STATE_ERROR){
|
|
return SSH_ERROR;
|
|
}
|
|
if (channel->remote_eof && ssh_buffer_get_len(stdbuf) == 0) {
|
|
return 0;
|
|
}
|
|
len = ssh_buffer_get_len(stdbuf);
|
|
/* Read count bytes if len is greater, everything otherwise */
|
|
len = (len > count ? count : len);
|
|
memcpy(dest, ssh_buffer_get(stdbuf), len);
|
|
ssh_buffer_pass_bytes(stdbuf,len);
|
|
if (channel->counter != NULL) {
|
|
channel->counter->in_bytes += len;
|
|
}
|
|
/* Authorize some buffering while userapp is busy */
|
|
if (channel->local_window < WINDOWLIMIT) {
|
|
if (grow_window(session, channel, 0) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
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 ssh_channel_is_eof()
|
|
*/
|
|
int ssh_channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count,
|
|
int is_stderr) {
|
|
ssh_session session;
|
|
int to_read;
|
|
int rc;
|
|
int blocking;
|
|
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
if(dest == NULL) {
|
|
ssh_set_error_invalid(channel->session);
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
session = channel->session;
|
|
|
|
to_read = ssh_channel_poll(channel, is_stderr);
|
|
|
|
if (to_read <= 0) {
|
|
if (session->session_state == SSH_SESSION_STATE_ERROR){
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
return to_read; /* may be an error code */
|
|
}
|
|
|
|
if (to_read > (int)count) {
|
|
to_read = (int)count;
|
|
}
|
|
blocking = ssh_is_blocking(session);
|
|
ssh_set_blocking(session, 0);
|
|
rc = ssh_channel_read(channel, dest, to_read, is_stderr);
|
|
ssh_set_blocking(session,blocking);
|
|
|
|
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 ssh_channel_is_eof()
|
|
*/
|
|
int ssh_channel_poll(ssh_channel channel, int is_stderr){
|
|
ssh_buffer stdbuf;
|
|
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
stdbuf = channel->stdout_buffer;
|
|
|
|
if (is_stderr) {
|
|
stdbuf = channel->stderr_buffer;
|
|
}
|
|
|
|
if (ssh_buffer_get_len(stdbuf) == 0 && channel->remote_eof == 0) {
|
|
if (channel->session->session_state == SSH_SESSION_STATE_ERROR){
|
|
return SSH_ERROR;
|
|
}
|
|
if (ssh_handle_packets(channel->session, SSH_TIMEOUT_NONBLOCKING)==SSH_ERROR) {
|
|
return SSH_ERROR;
|
|
}
|
|
}
|
|
|
|
if (ssh_buffer_get_len(stdbuf) > 0){
|
|
return ssh_buffer_get_len(stdbuf);
|
|
}
|
|
|
|
if (channel->remote_eof) {
|
|
return SSH_EOF;
|
|
}
|
|
|
|
return ssh_buffer_get_len(stdbuf);
|
|
}
|
|
|
|
/**
|
|
* @brief Polls a channel for data to read, waiting for a certain timeout.
|
|
*
|
|
* @param[in] channel The channel to poll.
|
|
* @param[in] timeout Set an upper limit on the time for which this function
|
|
* will block, in milliseconds. Specifying a negative value
|
|
* means an infinite timeout. This parameter is passed to
|
|
* the poll() function.
|
|
* @param[in] is_stderr A boolean to select the stderr stream.
|
|
*
|
|
* @return The number of bytes available for reading,
|
|
* 0 if nothing is available (timeout elapsed),
|
|
* SSH_EOF on end of file,
|
|
* SSH_ERROR on error.
|
|
*
|
|
* @warning When the channel is in EOF state, the function returns SSH_EOF.
|
|
*
|
|
* @see ssh_channel_is_eof()
|
|
*/
|
|
int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr){
|
|
ssh_session session;
|
|
ssh_buffer stdbuf;
|
|
struct ssh_channel_read_termination_struct ctx;
|
|
int rc;
|
|
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
session = channel->session;
|
|
stdbuf = channel->stdout_buffer;
|
|
|
|
if (is_stderr) {
|
|
stdbuf = channel->stderr_buffer;
|
|
}
|
|
ctx.buffer = stdbuf;
|
|
ctx.channel = channel;
|
|
ctx.count = 1;
|
|
rc = ssh_handle_packets_termination(channel->session, timeout,
|
|
ssh_channel_read_termination, &ctx);
|
|
if(rc ==SSH_ERROR || session->session_state == SSH_SESSION_STATE_ERROR){
|
|
rc = SSH_ERROR;
|
|
goto end;
|
|
}
|
|
rc = ssh_buffer_get_len(stdbuf);
|
|
if(rc > 0)
|
|
goto end;
|
|
if (channel->remote_eof)
|
|
rc = SSH_EOF;
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @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) {
|
|
if(channel == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return channel->session;
|
|
}
|
|
|
|
static int ssh_channel_exit_status_termination(void *c){
|
|
ssh_channel channel = c;
|
|
if(channel->exit_status != -1 ||
|
|
/* When a channel is closed, no exit status message can
|
|
* come anymore */
|
|
(channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) ||
|
|
channel->session->session_state == SSH_SESSION_STATE_ERROR)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @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
|
|
* (yet).
|
|
* @warning This function may block until a timeout (or never)
|
|
* if the other side is not willing to close the channel.
|
|
*
|
|
* If you're looking for an async handling of this register a callback for the
|
|
* exit status.
|
|
*
|
|
* @see ssh_channel_exit_status_callback
|
|
*/
|
|
int ssh_channel_get_exit_status(ssh_channel channel) {
|
|
int rc;
|
|
if(channel == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
rc = ssh_handle_packets_termination(channel->session,
|
|
SSH_TIMEOUT_DEFAULT,
|
|
ssh_channel_exit_status_termination,
|
|
channel);
|
|
if (rc == SSH_ERROR || channel->session->session_state ==
|
|
SSH_SESSION_STATE_ERROR)
|
|
return SSH_ERROR;
|
|
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, SSH_TIMEOUT_NONBLOCKING);
|
|
}
|
|
|
|
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;
|
|
ssh_event event = NULL;
|
|
int rc;
|
|
int i;
|
|
int tm, tm_base;
|
|
int firstround=1;
|
|
struct ssh_timestamp ts;
|
|
|
|
if (timeout != NULL)
|
|
tm_base = timeout->tv_sec * 1000 + timeout->tv_usec/1000;
|
|
else
|
|
tm_base = SSH_TIMEOUT_INFINITE;
|
|
ssh_timestamp_init(&ts);
|
|
tm = tm_base;
|
|
/* 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 = calloc(count_ptrs(readchans) + 1, sizeof(ssh_channel));
|
|
if (rchans == NULL) {
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
wchans = calloc(count_ptrs(writechans) + 1, sizeof(ssh_channel));
|
|
if (wchans == NULL) {
|
|
SAFE_FREE(rchans);
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
echans = calloc(count_ptrs(exceptchans) + 1, sizeof(ssh_channel));
|
|
if (echans == NULL) {
|
|
SAFE_FREE(rchans);
|
|
SAFE_FREE(wchans);
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
/*
|
|
* First, try without doing network stuff then, use the ssh_poll
|
|
* infrastructure to poll on all sessions.
|
|
*/
|
|
do {
|
|
channel_protocol_select(readchans, writechans, exceptchans,
|
|
rchans, wchans, echans);
|
|
if (rchans[0] != NULL || wchans[0] != NULL || echans[0] != NULL) {
|
|
/* At least one channel has an event */
|
|
break;
|
|
}
|
|
/* Add all channels' sessions right into an event object */
|
|
if (event == NULL) {
|
|
event = ssh_event_new();
|
|
if (event == NULL) {
|
|
SAFE_FREE(rchans);
|
|
SAFE_FREE(wchans);
|
|
SAFE_FREE(echans);
|
|
|
|
return SSH_ERROR;
|
|
}
|
|
for (i = 0; readchans[i] != NULL; i++) {
|
|
ssh_poll_get_default_ctx(readchans[i]->session);
|
|
ssh_event_add_session(event, readchans[i]->session);
|
|
}
|
|
for (i = 0; writechans[i] != NULL; i++) {
|
|
ssh_poll_get_default_ctx(writechans[i]->session);
|
|
ssh_event_add_session(event, writechans[i]->session);
|
|
}
|
|
for (i = 0; exceptchans[i] != NULL; i++) {
|
|
ssh_poll_get_default_ctx(exceptchans[i]->session);
|
|
ssh_event_add_session(event, exceptchans[i]->session);
|
|
}
|
|
}
|
|
/* Get out if the timeout has elapsed */
|
|
if (!firstround && ssh_timeout_elapsed(&ts, tm_base)){
|
|
break;
|
|
}
|
|
/* Here we go */
|
|
rc = ssh_event_dopoll(event,tm);
|
|
if (rc != SSH_OK){
|
|
SAFE_FREE(rchans);
|
|
SAFE_FREE(wchans);
|
|
SAFE_FREE(echans);
|
|
ssh_event_free(event);
|
|
return rc;
|
|
}
|
|
tm = ssh_timeout_update(&ts, tm_base);
|
|
firstround=0;
|
|
} while(1);
|
|
|
|
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);
|
|
if(event)
|
|
ssh_event_free(event);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Set the channel data counter.
|
|
*
|
|
* @code
|
|
* struct ssh_counter_struct counter = {
|
|
* .in_bytes = 0,
|
|
* .out_bytes = 0,
|
|
* .in_packets = 0,
|
|
* .out_packets = 0
|
|
* };
|
|
*
|
|
* ssh_channel_set_counter(channel, &counter);
|
|
* @endcode
|
|
*
|
|
* @param[in] channel The SSH channel.
|
|
*
|
|
* @param[in] counter Counter for bytes handled by the channel.
|
|
*/
|
|
void ssh_channel_set_counter(ssh_channel channel,
|
|
ssh_counter counter) {
|
|
if (channel != NULL) {
|
|
channel->counter = counter;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Blocking write on a channel stderr.
|
|
*
|
|
* @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 ssh_channel_read()
|
|
*/
|
|
int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) {
|
|
return channel_write_common(channel, data, len, 1);
|
|
}
|
|
|
|
#if WITH_SERVER
|
|
|
|
/**
|
|
* @brief Open a TCP/IP reverse 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 occurred,
|
|
* SSH_AGAIN if in nonblocking mode and call has
|
|
* to be done again.
|
|
*
|
|
* @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_reverse_forward(ssh_channel channel, const char *remotehost,
|
|
int remoteport, const char *sourcehost, int localport) {
|
|
ssh_session session;
|
|
ssh_buffer payload = NULL;
|
|
int rc = SSH_ERROR;
|
|
|
|
if(channel == NULL) {
|
|
return rc;
|
|
}
|
|
if(remotehost == NULL || sourcehost == NULL) {
|
|
ssh_set_error_invalid(channel->session);
|
|
return rc;
|
|
}
|
|
|
|
session = channel->session;
|
|
|
|
if(channel->state != SSH_CHANNEL_STATE_NOT_OPEN)
|
|
goto pending;
|
|
payload = ssh_buffer_new();
|
|
if (payload == NULL) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
rc = ssh_buffer_pack(payload,
|
|
"sdsd",
|
|
remotehost,
|
|
remoteport,
|
|
sourcehost,
|
|
localport);
|
|
if (rc != SSH_OK){
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
pending:
|
|
rc = channel_open(channel,
|
|
"forwarded-tcpip",
|
|
CHANNEL_INITIAL_WINDOW,
|
|
CHANNEL_MAX_PACKET,
|
|
payload);
|
|
|
|
error:
|
|
ssh_buffer_free(payload);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @brief Open a X11 channel.
|
|
*
|
|
* @param[in] channel An allocated channel.
|
|
*
|
|
* @param[in] orig_addr The source host (the local server).
|
|
*
|
|
* @param[in] orig_port The source port (the local server).
|
|
*
|
|
* @return SSH_OK on success,
|
|
* SSH_ERROR if an error occurred,
|
|
* SSH_AGAIN if in nonblocking mode and call has
|
|
* to be done again.
|
|
* @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_x11(ssh_channel channel,
|
|
const char *orig_addr, int orig_port) {
|
|
ssh_session session;
|
|
ssh_buffer payload = NULL;
|
|
int rc = SSH_ERROR;
|
|
|
|
if(channel == NULL) {
|
|
return rc;
|
|
}
|
|
if(orig_addr == NULL) {
|
|
ssh_set_error_invalid(channel->session);
|
|
return rc;
|
|
}
|
|
session = channel->session;
|
|
|
|
if(channel->state != SSH_CHANNEL_STATE_NOT_OPEN)
|
|
goto pending;
|
|
|
|
payload = ssh_buffer_new();
|
|
if (payload == NULL) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
|
|
rc = ssh_buffer_pack(payload,
|
|
"sd",
|
|
orig_addr,
|
|
orig_port);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(session);
|
|
goto error;
|
|
}
|
|
pending:
|
|
rc = channel_open(channel,
|
|
"x11",
|
|
CHANNEL_INITIAL_WINDOW,
|
|
CHANNEL_MAX_PACKET,
|
|
payload);
|
|
|
|
error:
|
|
ssh_buffer_free(payload);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @brief Send the exit status to the remote process
|
|
*
|
|
* Sends the exit status to the remote process (as described in RFC 4254,
|
|
* section 6.10).
|
|
* Only SSH-v2 is supported (I'm not sure about SSH-v1).
|
|
*
|
|
* @param[in] channel The channel to send exit status.
|
|
*
|
|
* @param[in] exit_status The exit status to send
|
|
*
|
|
* @return SSH_OK on success, SSH_ERROR if an error occurred.
|
|
* (including attempts to send exit status via SSH-v1 session).
|
|
*/
|
|
int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status) {
|
|
ssh_buffer buffer = NULL;
|
|
int rc = SSH_ERROR;
|
|
|
|
if(channel == NULL) {
|
|
return 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) {
|
|
ssh_set_error_oom(channel->session);
|
|
goto error;
|
|
}
|
|
|
|
rc = ssh_buffer_pack(buffer, "d", exit_status);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(channel->session);
|
|
goto error;
|
|
}
|
|
|
|
rc = channel_request(channel, "exit-status", buffer, 0);
|
|
error:
|
|
ssh_buffer_free(buffer);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @brief Send an exit signal to remote process (RFC 4254, section 6.10).
|
|
*
|
|
* This sends the exit status of 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").
|
|
* @param[in] core A boolean to tell if a core was dumped
|
|
* @param[in] errmsg A CRLF explanation text about the error condition
|
|
* @param[in] lang The language used in the message (format: RFC 3066)
|
|
*
|
|
* @return SSH_OK on success, SSH_ERROR if an error occurred
|
|
* (including attempts to send signal via SSH-v1 session).
|
|
*/
|
|
int ssh_channel_request_send_exit_signal(ssh_channel channel, const char *sig,
|
|
int core, const char *errmsg, const char *lang) {
|
|
ssh_buffer buffer = NULL;
|
|
int rc = SSH_ERROR;
|
|
|
|
if(channel == NULL) {
|
|
return rc;
|
|
}
|
|
if(sig == NULL || errmsg == NULL || lang == NULL) {
|
|
ssh_set_error_invalid(channel->session);
|
|
return rc;
|
|
}
|
|
#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) {
|
|
ssh_set_error_oom(channel->session);
|
|
goto error;
|
|
}
|
|
|
|
rc = ssh_buffer_pack(buffer,
|
|
"sbss",
|
|
sig,
|
|
core ? 1 : 0,
|
|
errmsg,
|
|
lang);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(channel->session);
|
|
goto error;
|
|
}
|
|
|
|
rc = channel_request(channel, "exit-signal", buffer, 0);
|
|
error:
|
|
ssh_buffer_free(buffer);
|
|
return rc;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* @} */
|
|
|
|
/* vim: set ts=4 sw=4 et cindent: */
|