#include "config.h"
#include <limits.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <stdbool.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#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)
#define CHANNEL_MAX_PACKET 32768
#define CHANNEL_INITIAL_WINDOW 64000
static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet);
ssh_channel ssh_channel_new(ssh_session session)
{
ssh_channel channel = NULL;
if (session == NULL) {
return NULL;
}
if (!(session->flags & SSH_SESSION_FLAG_AUTHENTICATED)) {
return NULL;
}
channel = calloc(1, sizeof(struct ssh_channel_struct));
if (channel == NULL) {
ssh_set_error_oom(session);
return NULL;
}
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->exit_status = -1;
channel->flags = SSH_CHANNEL_FLAG_NOT_BOUND;
if (session->channels == NULL) {
session->channels = ssh_list_new();
if (session->channels == NULL) {
ssh_set_error_oom(session);
SSH_BUFFER_FREE(channel->stdout_buffer);
SSH_BUFFER_FREE(channel->stderr_buffer);
SAFE_FREE(channel);
return NULL;
}
}
ssh_list_prepend(session->channels, channel);
channel->state = SSH_CHANNEL_STATE_NOT_OPEN;
channel->request_state = SSH_CHANNEL_REQ_STATE_NONE;
return channel;
}
uint32_t ssh_channel_new_id(ssh_session session) {
return ++(session->maxchannel);
}
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 %"PRIu32,
(uint32_t) channelid);
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);
if (channel->state != SSH_CHANNEL_STATE_OPENING) {
SSH_LOG(SSH_LOG_RARE,
"SSH2_MSG_CHANNEL_OPEN_CONFIRMATION received in incorrect "
"channel state %d",
channel->state);
goto error;
}
SSH_LOG(SSH_LOG_PROTOCOL,
"Remote window : %"PRIu32", maxpacket : %"PRIu32,
(uint32_t) channel->remote_window,
(uint32_t) 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;
}
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;
}
if (channel->state != SSH_CHANNEL_STATE_OPENING) {
SSH_LOG(SSH_LOG_RARE,
"SSH2_MSG_CHANNEL_OPEN_FAILURE received in incorrect channel "
"state %d",
channel->state);
SAFE_FREE(error);
goto error;
}
ssh_set_error(session, SSH_REQUEST_DENIED,
"Channel opening failure: channel %u error (%"PRIu32") %s",
channel->local_channel,
(uint32_t) code,
error);
SAFE_FREE(error);
channel->state=SSH_CHANNEL_STATE_OPEN_DENIED;
return SSH_PACKET_USED;
error:
ssh_set_error(session, SSH_FATAL, "Invalid packet");
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;
}
static int
channel_open(ssh_channel channel,
const char *type,
uint32_t window,
uint32_t 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:
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;
} else if (err != SSH_AGAIN) {
err = SSH_ERROR;
}
return err;
}
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;
}
static int grow_window(ssh_session session,
ssh_channel channel,
uint32_t minimumsize)
{
uint32_t new_window = minimumsize > WINDOWBASE ? minimumsize : WINDOWBASE;
int rc;
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;
}
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;
}
static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet) {
ssh_channel channel;
uint32_t chan;
int rc;
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 %"PRIu32,
(uint32_t) 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;
}
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;
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 %zu bytes data in %d (local win=%d remote win=%d)",
len,
is_stderr,
channel->local_window,
channel->remote_window);
if (len > channel->local_window) {
SSH_LOG(SSH_LOG_RARE,
"Data packet too big for our window(%zu vs %d)",
len,
channel->local_window);
}
if (channel_default_bufferize(channel, ssh_string_data(str), len,
is_stderr) < 0) {
SSH_STRING_FREE(str);
return SSH_PACKET_USED;
}
if (len <= channel->local_window) {
channel->local_window -= len;
} else {
channel->local_window = 0;
}
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) == NULL) {
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_eof = 1;
ssh_callbacks_execute_list(channel->callbacks,
ssh_channel_callbacks,
channel_eof_function,
channel->session,
channel);
return SSH_PACKET_USED;
}
static bool ssh_channel_has_unread_data(ssh_channel channel)
{
if (channel == NULL) {
return false;
}
if ((channel->stdout_buffer &&
ssh_buffer_get_len(channel->stdout_buffer) > 0) ||
(channel->stderr_buffer &&
ssh_buffer_get_len(channel->stderr_buffer) > 0))
{
return true;
}
return false;
}
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 (!ssh_channel_has_unread_data(channel)) {
channel->state = SSH_CHANNEL_STATE_CLOSED;
} else {
channel->delayed_close = 1;
}
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;
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 want_reply;
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,
&want_reply);
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);
if (rc != SSH_OK) {
SSH_LOG(SSH_LOG_PACKET, "Invalid exit-status packet");
return SSH_PACKET_USED;
}
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,
&core_dumped,
&errmsg,
&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) {
int status;
SAFE_FREE(request);
SSH_LOG(SSH_LOG_PROTOCOL, "Received an auth-agent-req request");
status = SSH2_MSG_CHANNEL_FAILURE;
ssh_callbacks_iterate(channel->callbacks,
ssh_channel_callbacks,
channel_auth_agent_req_function) {
ssh_callbacks_iterate_exec(channel_auth_agent_req_function,
channel->session,
channel);
status = SSH2_MSG_CHANNEL_SUCCESS;
break;
}
ssh_callbacks_iterate_end();
if (want_reply) {
rc = ssh_buffer_pack(session->out_buffer,
"bd",
status,
channel->remote_channel);
if (rc != SSH_OK) {
return SSH_PACKET_USED;
}
ssh_packet_send(session);
}
return SSH_PACKET_USED;
}
#ifdef WITH_SERVER
ssh_message_handle_channel_request(session,channel,packet,request,want_reply);
#else
SSH_LOG(SSH_LOG_WARNING, "Unhandled channel request %s", request);
#endif
SAFE_FREE(request);
return SSH_PACKET_USED;
}
int channel_default_bufferize(ssh_channel channel,
void *data, size_t len,
bool 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 %zu bytes into channel buffer (%s)",
len,
is_stderr ? "stderr" : "stdout");
if (!is_stderr) {
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 {
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;
}
int ssh_channel_open_session(ssh_channel channel) {
if(channel == NULL) {
return SSH_ERROR;
}
return channel_open(channel,
"session",
CHANNEL_INITIAL_WINDOW,
CHANNEL_MAX_PACKET,
NULL);
}
int ssh_channel_open_auth_agent(ssh_channel channel){
if(channel == NULL) {
return SSH_ERROR;
}
return channel_open(channel,
"auth-agent@openssh.com",
CHANNEL_INITIAL_WINDOW,
CHANNEL_MAX_PACKET,
NULL);
}
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;
}
int ssh_channel_open_forward_unix(ssh_channel channel,
const char *remotepath,
const char *sourcehost,
int localport)
{
ssh_session session = NULL;
ssh_buffer payload = NULL;
ssh_string str = NULL;
int rc = SSH_ERROR;
int version;
if (channel == NULL) {
return rc;
}
session = channel->session;
version = ssh_get_openssh_version(session);
if (version == 0) {
ssh_set_error(session,
SSH_REQUEST_DENIED,
"We're not connected to an OpenSSH server!");
return SSH_ERROR;
}
if (remotepath == 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,
"ssd",
remotepath,
sourcehost,
localport);
if (rc != SSH_OK) {
ssh_set_error_oom(session);
goto error;
}
rc = channel_open(channel,
"direct-streamlocal@openssh.com",
CHANNEL_INITIAL_WINDOW,
CHANNEL_MAX_PACKET,
payload);
error:
SSH_BUFFER_FREE(payload);
SSH_STRING_FREE(str);
return rc;
}
void ssh_channel_free(ssh_channel channel)
{
ssh_session session;
if (channel == NULL) {
return;
}
session = channel->session;
if (session->alive) {
bool send_close = false;
switch (channel->state) {
case SSH_CHANNEL_STATE_OPEN:
send_close = true;
break;
case SSH_CHANNEL_STATE_CLOSED:
if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) {
send_close = true;
}
if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_LOCAL) {
send_close = false;
}
break;
default:
send_close = false;
break;
}
if (send_close) {
ssh_channel_close(channel);
}
}
channel->flags |= SSH_CHANNEL_FLAG_FREED_LOCAL;
if ((channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) ||
(channel->flags & SSH_CHANNEL_FLAG_NOT_BOUND)) {
ssh_channel_do_free(channel);
}
}
void ssh_channel_do_free(ssh_channel channel)
{
struct ssh_iterator *it = NULL;
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);
channel->callbacks = NULL;
}
channel->session = NULL;
SAFE_FREE(channel);
}
int ssh_channel_send_eof(ssh_channel channel)
{
ssh_session session;
int rc = SSH_ERROR;
int err;
if (channel == NULL || channel->session == NULL) {
return rc;
}
if (channel->local_eof != 0) {
return SSH_OK;
}
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);
if (rc != SSH_OK) {
goto error;
}
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;
}
int ssh_channel_close(ssh_channel channel)
{
ssh_session session;
int rc = 0;
if(channel == NULL) {
return SSH_ERROR;
}
if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_LOCAL) {
return SSH_OK;
}
session = channel->session;
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;
channel->flags |= SSH_CHANNEL_FLAG_CLOSED_LOCAL;
}
rc = ssh_channel_flush(channel);
if(rc == SSH_ERROR) {
goto error;
}
return rc;
error:
ssh_buffer_reinit(session->out_buffer);
return rc;
}
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;
}
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;
}
}
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;
}
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;
}
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);
if(channel->remote_window == 0) {
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;
}
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;
}
}
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;
}
}
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;
}
uint32_t ssh_channel_window_size(ssh_channel channel) {
return channel->remote_window;
}
int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len) {
return channel_write_common(channel, data, len, 0);
}
int ssh_channel_is_open(ssh_channel channel) {
if(channel == NULL) {
return 0;
}
return (channel->state == SSH_CHANNEL_STATE_OPEN && channel->session->alive != 0);
}
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);
}
int ssh_channel_is_eof(ssh_channel channel) {
if(channel == NULL) {
return SSH_ERROR;
}
if (ssh_channel_has_unread_data(channel)) {
return 0;
}
return (channel->remote_eof != 0);
}
void ssh_channel_set_blocking(ssh_channel channel, int blocking) {
if(channel == NULL) {
return;
}
ssh_set_blocking(channel->session,blocking);
}
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;
}
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;
}
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:
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;
}
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;
}
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,
0,
1,
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;
}
int ssh_channel_request_pty(ssh_channel channel) {
return ssh_channel_request_pty_size(channel, "xterm", 80, 24);
}
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;
buffer = ssh_buffer_new();
if (buffer == NULL) {
ssh_set_error_oom(session);
goto error;
}
rc = ssh_buffer_pack(buffer,
"dddd",
cols,
rows,
0,
0 );
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;
}
int ssh_channel_request_shell(ssh_channel channel) {
if(channel == NULL) {
return SSH_ERROR;
}
return channel_request(channel, "shell", NULL, 1);
}
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 ok;
int i;
ok = ssh_get_random(rnd, sizeof(rnd), 0);
if (!ok) {
return NULL;
}
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);
}
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
};
#endif
ssh_message msg = NULL;
ssh_channel channel = NULL;
struct ssh_iterator *iterator;
int t;
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);
#else
nanosleep(&ts, NULL);
#endif
}
}
ssh_set_error(session, SSH_NO_ERROR, "No channel request of this type from server");
return NULL;
}
ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms) {
return ssh_channel_accept(channel->session, SSH_CHANNEL_X11, timeout_ms, NULL);
}
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);
}
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;
}
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;
}
int ssh_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;
}
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 = ssh_global_request(session, "tcpip-forward", buffer, 1);
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;
}
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);
}
ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) {
return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, NULL);
}
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);
}
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 = ssh_global_request(session, "cancel-tcpip-forward", buffer, 1);
error:
SSH_BUFFER_FREE(buffer);
return rc;
}
int ssh_forward_cancel(ssh_session session, const char *address, int port) {
return ssh_channel_cancel_forward(session, address, port);
}
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;
}
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;
}
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;
}
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;
}
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;
}
int ssh_channel_request_send_break(ssh_channel channel, uint32_t length) {
ssh_buffer buffer = NULL;
int rc = SSH_ERROR;
if (channel == NULL) {
return SSH_ERROR;
}
buffer = ssh_buffer_new();
if (buffer == NULL) {
ssh_set_error_oom(channel->session);
goto error;
}
rc = ssh_buffer_pack(buffer, "d", length);
if (rc != SSH_OK) {
ssh_set_error_oom(channel->session);
goto error;
}
rc = channel_request(channel, "break", buffer, 0);
error:
SSH_BUFFER_FREE(buffer);
return rc;
}
int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count,
int is_stderr) {
ssh_session session;
char *buffer_tmp = NULL;
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){
count = r;
buffer_tmp = ssh_buffer_allocate(buffer, count);
if (buffer_tmp == NULL) {
ssh_set_error_oom(session);
return SSH_ERROR;
}
r=ssh_channel_read(channel, buffer_tmp, r, is_stderr);
if(r < 0){
ssh_buffer_pass_bytes_end(buffer, count);
return r;
}
ssh_buffer_pass_bytes_end(buffer, count - r);
return r;
}
if(ssh_channel_is_eof(channel)){
return 0;
}
ssh_handle_packets(channel->session, SSH_TIMEOUT_INFINITE);
} while (r == 0);
}
buffer_tmp = ssh_buffer_allocate(buffer, count);
if (buffer_tmp == NULL) {
ssh_set_error_oom(session);
return SSH_ERROR;
}
while(total < count){
r=ssh_channel_read(channel, buffer_tmp, count - total, is_stderr);
if(r<0){
ssh_buffer_pass_bytes_end(buffer, count);
return r;
}
if(r==0){
ssh_buffer_pass_bytes_end(buffer, count - total);
return total;
}
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;
}
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,
SSH_TIMEOUT_DEFAULT);
}
int ssh_channel_read_timeout(ssh_channel channel,
void *dest,
uint32_t count,
int is_stderr,
int timeout_ms)
{
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;
}
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;
}
}
ctx.channel = channel;
ctx.buffer = stdbuf;
ctx.count = 1;
if (timeout_ms < SSH_TIMEOUT_DEFAULT) {
timeout_ms = SSH_TIMEOUT_INFINITE;
}
rc = ssh_handle_packets_termination(session,
timeout_ms,
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;
}
if (channel->state == SSH_CHANNEL_STATE_CLOSED) {
ssh_set_error(session,
SSH_FATAL,
"Remote channel is closed.");
return SSH_ERROR;
}
len = ssh_buffer_get_len(stdbuf);
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;
}
if (channel->delayed_close && !ssh_channel_has_unread_data(channel)) {
channel->state = SSH_CHANNEL_STATE_CLOSED;
}
if (channel->local_window < WINDOWLIMIT) {
if (grow_window(session, channel, 0) < 0) {
return -1;
}
}
return len;
}
int ssh_channel_read_nonblocking(ssh_channel channel,
void *dest,
uint32_t count,
int is_stderr)
{
ssh_session session;
ssize_t 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;
}
if ((size_t)to_read > count) {
to_read = (ssize_t)count;
}
blocking = ssh_is_blocking(session);
ssh_set_blocking(session, 0);
rc = ssh_channel_read(channel, dest, (uint32_t)to_read, is_stderr);
ssh_set_blocking(session,blocking);
return rc;
}
int ssh_channel_poll(ssh_channel channel, int is_stderr){
ssh_buffer stdbuf;
if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) {
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);
}
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;
size_t len;
int rc;
if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) {
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 out;
} else if (rc == SSH_AGAIN) {
rc = SSH_OK;
goto out;
}
len = ssh_buffer_get_len(stdbuf);
if (len > 0) {
if (len > INT_MAX) {
rc = SSH_ERROR;
} else {
rc = (int)len;
}
goto out;
}
if (channel->remote_eof) {
rc = SSH_EOF;
}
out:
return rc;
}
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 ||
(channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) ||
channel->session->session_state == SSH_SESSION_STATE_ERROR)
return 1;
else
return 0;
}
int ssh_channel_get_exit_status(ssh_channel channel) {
int rc;
if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) {
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;
}
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];
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;
}
static size_t count_ptrs(ssh_channel *ptrs)
{
size_t c;
for (c = 0; ptrs[c] != NULL; c++)
;
return c;
}
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;
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) {
return 0;
}
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;
}
do {
channel_protocol_select(readchans, writechans, exceptchans,
rchans, wchans, echans);
if (rchans[0] != NULL || wchans[0] != NULL || echans[0] != NULL) {
break;
}
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);
}
}
if (!firstround && ssh_timeout_elapsed(&ts, tm_base)){
break;
}
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;
}
void ssh_channel_set_counter(ssh_channel channel,
ssh_counter counter) {
if (channel != NULL) {
channel->counter = counter;
}
}
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
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;
}
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;
}
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;
}
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;
}
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;
}
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