/* * 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 * * 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 #include #include #include #ifndef _WIN32 #include #include #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 = malloc(sizeof(ssh_channel ) * (count_ptrs(readchans) + 1)); if (rchans == NULL) { return SSH_ERROR; } wchans = malloc(sizeof(ssh_channel ) * (count_ptrs(writechans) + 1)); if (wchans == NULL) { SAFE_FREE(rchans); return SSH_ERROR; } echans = malloc(sizeof(ssh_channel ) * (count_ptrs(exceptchans) + 1)); if (echans == NULL) { SAFE_FREE(rchans); SAFE_FREE(wchans); return SSH_ERROR; } /* * First, try without doing network stuff then, 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: */