diff --git a/external/fix-external/libssh/src/session.c b/external/fix-external/libssh/src/session.c new file mode 100644 index 0000000..f046408 --- /dev/null +++ b/external/fix-external/libssh/src/session.c @@ -0,0 +1,953 @@ +/* + * session.c - non-networking functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2013 by Aris Adamantiadis + * + * 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 "libssh/priv.h" +#include "libssh/libssh.h" +#include "libssh/crypto.h" +#include "libssh/server.h" +#include "libssh/socket.h" +#ifdef WITH_SSH1 +#include "libssh/ssh1.h" +#endif /* WITH_SSH1 */ +#include "libssh/ssh2.h" +#include "libssh/agent.h" +#include "libssh/packet.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/buffer.h" +#include "libssh/poll.h" + +#define FIRST_CHANNEL 42 // why not ? it helps to find bugs. + +/** + * @defgroup libssh_session The SSH session functions. + * @ingroup libssh + * + * Functions that manage a session. + * + * @{ + */ + +/** + * @brief Create a new ssh session. + * + * @returns A new ssh_session pointer, NULL on error. + */ +ssh_session ssh_new(void) { + ssh_session session; + char *id = NULL; + int rc; + + session = malloc(sizeof (struct ssh_session_struct)); + if (session == NULL) { + return NULL; + } + ZERO_STRUCTP(session); + + session->next_crypto = crypto_new(); + if (session->next_crypto == NULL) { + goto err; + } + + session->socket = ssh_socket_new(session); + if (session->socket == NULL) { + goto err; + } + + session->out_buffer = ssh_buffer_new(); + if (session->out_buffer == NULL) { + goto err; + } + + session->in_buffer=ssh_buffer_new(); + if (session->in_buffer == NULL) { + goto err; + } + + session->alive = 0; + session->auth_methods = 0; + ssh_set_blocking(session, 1); + session->maxchannel = FIRST_CHANNEL; + +#ifndef _WIN32 + session->agent = agent_new(session); + if (session->agent == NULL) { + goto err; + } +#endif /* _WIN32 */ + + /* OPTIONS */ + session->opts.StrictHostKeyChecking = 1; + session->opts.port = 0; + session->opts.fd = -1; + session->opts.ssh2 = 1; + session->opts.compressionlevel=7; +#ifdef WITH_SSH1 + session->opts.ssh1 = 1; +#else + session->opts.ssh1 = 0; +#endif + + session->opts.identity = ssh_list_new(); + if (session->opts.identity == NULL) { + goto err; + } + + id = strdup("%d/id_ed25519"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity, id); + if (rc == SSH_ERROR) { + goto err; + } + +#ifdef HAVE_ECC + id = strdup("%d/id_ecdsa"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity, id); + if (rc == SSH_ERROR) { + goto err; + } +#endif + + id = strdup("%d/id_rsa"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity, id); + if (rc == SSH_ERROR) { + goto err; + } + + id = strdup("%d/id_dsa"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity, id); + if (rc == SSH_ERROR) { + goto err; + } + + id = strdup("%d/identity"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity, id); + if (rc == SSH_ERROR) { + goto err; + } + + return session; + +err: + free(id); + ssh_free(session); + return NULL; +} + +/** + * @brief Deallocate a SSH session handle. + * + * @param[in] session The SSH session to free. + * + * @see ssh_disconnect() + * @see ssh_new() + */ +void ssh_free(ssh_session session) { + int i; + struct ssh_iterator *it; + + if (session == NULL) { + return; + } + + /* + * Delete all channels + * + * This needs the first thing we clean up cause if there is still an open + * channel we call ssh_channel_close() first. So we need a working socket + * and poll context for it. + */ + for (it = ssh_list_get_iterator(session->channels); + it != NULL; + it = ssh_list_get_iterator(session->channels)) { + ssh_channel_do_free(ssh_iterator_value(ssh_channel,it)); + ssh_list_remove(session->channels, it); + } + ssh_list_free(session->channels); + session->channels = NULL; + +#ifdef WITH_PCAP + if (session->pcap_ctx) { + ssh_pcap_context_free(session->pcap_ctx); + session->pcap_ctx = NULL; + } +#endif + + ssh_socket_free(session->socket); + session->socket = NULL; + + if (session->default_poll_ctx) { + ssh_poll_ctx_free(session->default_poll_ctx); + } + + ssh_buffer_free(session->in_buffer); + ssh_buffer_free(session->out_buffer); + session->in_buffer = session->out_buffer = NULL; + + if (session->in_hashbuf != NULL) { + ssh_buffer_free(session->in_hashbuf); + } + if (session->out_hashbuf != NULL) { + ssh_buffer_free(session->out_hashbuf); + } + + crypto_free(session->current_crypto); + crypto_free(session->next_crypto); + +#ifndef _WIN32 + agent_free(session->agent); +#endif /* _WIN32 */ + + ssh_key_free(session->srv.dsa_key); + session->srv.dsa_key = NULL; + ssh_key_free(session->srv.rsa_key); + session->srv.rsa_key = NULL; + ssh_key_free(session->srv.ecdsa_key); + session->srv.ecdsa_key = NULL; + ssh_key_free(session->srv.ed25519_key); + session->srv.ed25519_key = NULL; + + if (session->ssh_message_list) { + ssh_message msg; + + for (msg = ssh_list_pop_head(ssh_message, session->ssh_message_list); + msg != NULL; + msg = ssh_list_pop_head(ssh_message, session->ssh_message_list)) { + ssh_message_free(msg); + } + ssh_list_free(session->ssh_message_list); + } + + if (session->packet_callbacks) { + ssh_list_free(session->packet_callbacks); + } + + /* options */ + if (session->opts.identity) { + char *id; + + for (id = ssh_list_pop_head(char *, session->opts.identity); + id != NULL; + id = ssh_list_pop_head(char *, session->opts.identity)) { + SAFE_FREE(id); + } + ssh_list_free(session->opts.identity); + } + +#ifndef _WIN32 + ssh_agent_state_free (session->agent_state); +#endif + session->agent_state = NULL; + + SAFE_FREE(session->auth_auto_state); + SAFE_FREE(session->serverbanner); + SAFE_FREE(session->clientbanner); + SAFE_FREE(session->banner); + + SAFE_FREE(session->opts.bindaddr); + SAFE_FREE(session->opts.custombanner); + SAFE_FREE(session->opts.username); + SAFE_FREE(session->opts.host); + SAFE_FREE(session->opts.sshdir); + SAFE_FREE(session->opts.knownhosts); + SAFE_FREE(session->opts.ProxyCommand); + SAFE_FREE(session->opts.gss_server_identity); + SAFE_FREE(session->opts.gss_client_identity); + + for (i = 0; i < 10; i++) { + if (session->opts.wanted_methods[i]) { + SAFE_FREE(session->opts.wanted_methods[i]); + } + } + + /* burn connection, it could contain sensitive data */ + BURN_BUFFER(session, sizeof(struct ssh_session_struct)); + SAFE_FREE(session); +} + +/** + * @brief get the client banner + * + * @param[in] session The SSH session + * + * @return Returns the client banner string or NULL. + */ +const char* ssh_get_clientbanner(ssh_session session) { + if (session == NULL) { + return NULL; + } + + return session->clientbanner; +} + +/** + * @brief get the server banner + * + * @param[in] session The SSH session + * + * @return Returns the server banner string or NULL. + */ +const char* ssh_get_serverbanner(ssh_session session) { + if(!session) { + return NULL; + } + return session->serverbanner; +} + +/** + * @brief get the name of the current key exchange algorithm. + * + * @param[in] session The SSH session + * + * @return Returns the key exchange algorithm string or NULL. + */ +const char* ssh_get_kex_algo(ssh_session session) { + if ((session == NULL) || + (session->current_crypto == NULL)) { + return NULL; + } + + switch (session->current_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + return "diffie-hellman-group1-sha1"; + case SSH_KEX_DH_GROUP14_SHA1: + return "diffie-hellman-group14-sha1"; + case SSH_KEX_ECDH_SHA2_NISTP256: + return "ecdh-sha2-nistp256"; + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + return "curve25519-sha256@libssh.org"; + default: + break; + } + + return NULL; +} + +/** + * @brief get the name of the input cipher for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns cipher name or NULL. + */ +const char* ssh_get_cipher_in(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL) && + (session->current_crypto->in_cipher != NULL)) { + return session->current_crypto->in_cipher->name; + } + return NULL; +} + +/** + * @brief get the name of the output cipher for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns cipher name or NULL. + */ +const char* ssh_get_cipher_out(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL) && + (session->current_crypto->out_cipher != NULL)) { + return session->current_crypto->out_cipher->name; + } + return NULL; +} + +/** + * @brief get the name of the input HMAC algorithm for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns HMAC algorithm name or NULL if unknown. + */ +const char* ssh_get_hmac_in(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL)) { + return ssh_hmac_type_to_string(session->current_crypto->in_hmac); + } + return NULL; +} + +/** + * @brief get the name of the output HMAC algorithm for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns HMAC algorithm name or NULL if unknown. + */ +const char* ssh_get_hmac_out(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL)) { + return ssh_hmac_type_to_string(session->current_crypto->out_hmac); + } + return NULL; +} + +/** + * @brief Disconnect impolitely from a remote host by closing the socket. + * + * Suitable if you forked and want to destroy this session. + * + * @param[in] session The SSH session to disconnect. + */ +void ssh_silent_disconnect(ssh_session session) { + if (session == NULL) { + return; + } + + ssh_socket_close(session->socket); + session->alive = 0; + ssh_disconnect(session); +} + +/** + * @brief Set the session in blocking/nonblocking mode. + * + * @param[in] session The ssh session to change. + * + * @param[in] blocking Zero for nonblocking mode. + */ +void ssh_set_blocking(ssh_session session, int blocking) { + if (session == NULL) { + return; + } + session->flags &= ~SSH_SESSION_FLAG_BLOCKING; + session->flags |= blocking ? SSH_SESSION_FLAG_BLOCKING : 0; +} + +/** + * @brief Return the blocking mode of libssh + * @param[in] session The SSH session + * @returns 0 if the session is nonblocking, + * @returns 1 if the functions may block. + */ +int ssh_is_blocking(ssh_session session){ + return (session->flags&SSH_SESSION_FLAG_BLOCKING) ? 1 : 0; +} + +/* Waits until the output socket is empty */ +static int ssh_flush_termination(void *c){ + ssh_session session = c; + if (ssh_socket_buffered_write_bytes(session->socket) == 0 || + session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/** + * @brief Blocking flush of the outgoing buffer + * @param[in] session The SSH session + * @param[in] timeout Set an upper limit on the time for which this function + * will block, in milliseconds. Specifying -1 + * means an infinite timeout. This parameter is passed to + * the poll() function. + * @returns SSH_OK on success, SSH_AGAIN if timeout occurred, + * SSH_ERROR otherwise. + */ + +int ssh_blocking_flush(ssh_session session, int timeout){ + int rc; + if (session == NULL) { + return SSH_ERROR; + } + + rc = ssh_handle_packets_termination(session, timeout, + ssh_flush_termination, session); + if (rc == SSH_ERROR) { + return rc; + } + if (!ssh_flush_termination(session)) { + rc = SSH_AGAIN; + } + + return rc; +} + +/** + * @brief Check if we are connected. + * + * @param[in] session The session to check if it is connected. + * + * @return 1 if we are connected, 0 if not. + */ +int ssh_is_connected(ssh_session session) { + if (session == NULL) { + return 0; + } + + return session->alive; +} + +/** + * @brief Get the fd of a connection. + * + * In case you'd need the file descriptor of the connection to the server/client. + * + * @param[in] session The ssh session to use. + * + * @return The file descriptor of the connection, or -1 if it is + * not connected + */ +socket_t ssh_get_fd(ssh_session session) { + if (session == NULL) { + return -1; + } + + return ssh_socket_get_fd_in(session->socket); +} + +/** + * @brief Tell the session it has data to read on the file descriptor without + * blocking. + * + * @param[in] session The ssh session to use. + */ +void ssh_set_fd_toread(ssh_session session) { + if (session == NULL) { + return; + } + + ssh_socket_set_read_wontblock(session->socket); +} + +/** + * @brief Tell the session it may write to the file descriptor without blocking. + * + * @param[in] session The ssh session to use. + */ +void ssh_set_fd_towrite(ssh_session session) { + if (session == NULL) { + return; + } + + ssh_socket_set_write_wontblock(session->socket); +} + +/** + * @brief Tell the session it has an exception to catch on the file descriptor. + * + * \param[in] session The ssh session to use. + */ +void ssh_set_fd_except(ssh_session session) { + if (session == NULL) { + return; + } + + ssh_socket_set_except(session->socket); +} + +/** + * @internal + * + * @brief Poll the current session for an event and call the appropriate + * callbacks. This function will not loop until the timeout is expired. + * + * This will block until one event happens. + * + * @param[in] session The session handle to use. + * + * @param[in] timeout Set an upper limit on the time for which this function + * will block, in milliseconds. Specifying SSH_TIMEOUT_INFINITE + * (-1) means an infinite timeout. + * Specifying SSH_TIMEOUT_USER means to use the timeout + * specified in options. 0 means poll will return immediately. + * This parameter is passed to the poll() function. + * + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_handle_packets(ssh_session session, int timeout) { + ssh_poll_handle spoll_in,spoll_out; + ssh_poll_ctx ctx; + int tm = timeout; + int rc; + + if (session == NULL || session->socket == NULL) { + return SSH_ERROR; + } + + spoll_in = ssh_socket_get_poll_handle_in(session->socket); + spoll_out = ssh_socket_get_poll_handle_out(session->socket); + ssh_poll_add_events(spoll_in, POLLIN); + ctx = ssh_poll_get_ctx(spoll_in); + + if (!ctx) { + ctx = ssh_poll_get_default_ctx(session); + ssh_poll_ctx_add(ctx, spoll_in); + if (spoll_in != spoll_out) { + ssh_poll_ctx_add(ctx, spoll_out); + } + } + + if (timeout == SSH_TIMEOUT_USER) { + if (ssh_is_blocking(session)) + tm = ssh_make_milliseconds(session->opts.timeout, + session->opts.timeout_usec); + else + tm = 0; + } + rc = ssh_poll_ctx_dopoll(ctx, tm); + if (rc == SSH_ERROR) { + session->session_state = SSH_SESSION_STATE_ERROR; + } + + return rc; +} + +/** + * @internal + * + * @brief Poll the current session for an event and call the appropriate + * callbacks. + * + * This will block until termination function returns true, or timeout expired. + * + * @param[in] session The session handle to use. + * + * @param[in] timeout Set an upper limit on the time for which this function + * will block, in milliseconds. Specifying SSH_TIMEOUT_INFINITE + * (-1) means an infinite timeout. + * Specifying SSH_TIMEOUT_USER means to use the timeout + * specified in options. 0 means poll will return immediately. + * SSH_TIMEOUT_DEFAULT uses blocking parameters of the session. + * This parameter is passed to the poll() function. + * + * @param[in] fct Termination function to be used to determine if it is + * possible to stop polling. + * @param[in] user User parameter to be passed to fct termination function. + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_handle_packets_termination(ssh_session session, + int timeout, + ssh_termination_function fct, + void *user) +{ + struct ssh_timestamp ts; + int ret = SSH_OK; + int tm; + + if (timeout == SSH_TIMEOUT_USER) { + if (ssh_is_blocking(session)) { + timeout = ssh_make_milliseconds(session->opts.timeout, + session->opts.timeout_usec); + } else { + timeout = SSH_TIMEOUT_NONBLOCKING; + } + } else if (timeout == SSH_TIMEOUT_DEFAULT) { + if (ssh_is_blocking(session)) { + // Apex. + // timeout = SSH_TIMEOUT_INFINITE; + timeout = ssh_make_milliseconds(session->opts.timeout, + session->opts.timeout_usec); + } else { + timeout = SSH_TIMEOUT_NONBLOCKING; + } + } + + /* avoid unnecessary syscall for the SSH_TIMEOUT_NONBLOCKING case */ + if (timeout != SSH_TIMEOUT_NONBLOCKING) { + ssh_timestamp_init(&ts); + } + + tm = timeout; + while(!fct(user)) { + ret = ssh_handle_packets(session, tm); + if (ret == SSH_ERROR) { + break; + } + if (ssh_timeout_elapsed(&ts,timeout)) { + ret = fct(user) ? SSH_OK : SSH_AGAIN; + break; + } + + tm = ssh_timeout_update(&ts, timeout); + } + + return ret; +} + +/** + * @brief Get session status + * + * @param session The ssh session to use. + * + * @returns A bitmask including SSH_CLOSED, SSH_READ_PENDING, SSH_WRITE_PENDING + * or SSH_CLOSED_ERROR which respectively means the session is closed, + * has data to read on the connection socket and session was closed + * due to an error. + */ +int ssh_get_status(ssh_session session) { + int socketstate; + int r = 0; + + if (session == NULL) { + return 0; + } + + socketstate = ssh_socket_get_status(session->socket); + + if (session->session_state == SSH_SESSION_STATE_DISCONNECTED) { + r |= SSH_CLOSED; + } + if (socketstate & SSH_READ_PENDING) { + r |= SSH_READ_PENDING; + } + if (socketstate & SSH_WRITE_PENDING) { + r |= SSH_WRITE_PENDING; + } + if ((session->session_state == SSH_SESSION_STATE_DISCONNECTED && + (socketstate & SSH_CLOSED_ERROR)) || + session->session_state == SSH_SESSION_STATE_ERROR) { + r |= SSH_CLOSED_ERROR; + } + + return r; +} + +/** + * @brief Get poll flags for an external mainloop + * + * @param session The ssh session to use. + * + * @returns A bitmask including SSH_READ_PENDING or SSH_WRITE_PENDING. + * For SSH_READ_PENDING, your invocation of poll() should include + * POLLIN. For SSH_WRITE_PENDING, your invocation of poll() should + * include POLLOUT. + */ +int ssh_get_poll_flags(ssh_session session) +{ + if (session == NULL) { + return 0; + } + + return ssh_socket_get_poll_flags (session->socket); +} + +/** + * @brief Get the disconnect message from the server. + * + * @param[in] session The ssh session to use. + * + * @return The message sent by the server along with the + * disconnect, or NULL in which case the reason of the + * disconnect may be found with ssh_get_error. + * + * @see ssh_get_error() + */ +const char *ssh_get_disconnect_message(ssh_session session) { + if (session == NULL) { + return NULL; + } + + if (session->session_state != SSH_SESSION_STATE_DISCONNECTED) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Connection not closed yet"); + } else if(!session->discon_msg) { + ssh_set_error(session, SSH_FATAL, + "Connection correctly closed but no disconnect message"); + } else { + return session->discon_msg; + } + + return NULL; +} + +/** + * @brief Get the protocol version of the session. + * + * @param session The ssh session to use. + * + * @return 1 or 2, for ssh1 or ssh2, < 0 on error. + */ +int ssh_get_version(ssh_session session) { + if (session == NULL) { + return -1; + } + + return session->version; +} + +/** + * @internal + * @brief Callback to be called when the socket received an exception code. + * @param user is a pointer to session + */ +void ssh_socket_exception_callback(int code, int errno_code, void *user){ + ssh_session session=(ssh_session)user; + + SSH_LOG(SSH_LOG_RARE,"Socket exception callback: %d (%d)",code, errno_code); + session->session_state = SSH_SESSION_STATE_ERROR; + if (errno_code == 0 && code == SSH_SOCKET_EXCEPTION_EOF) { + ssh_set_error(session, SSH_FATAL, "Socket error: disconnected"); + } else { + ssh_set_error(session, SSH_FATAL, "Socket error: %s", strerror(errno_code)); + } + + session->ssh_connection_callback(session); +} + +/** + * @brief Send a message that should be ignored + * + * @param[in] session The SSH session + * @param[in] data Data to be sent + * + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_send_ignore (ssh_session session, const char *data) { +#ifdef WITH_SSH1 + const int type = session->version == 1 ? SSH_MSG_IGNORE : SSH2_MSG_IGNORE; +#else /* WITH_SSH1 */ + const int type = SSH2_MSG_IGNORE; +#endif /* WITH_SSH1 */ + int rc; + + if (ssh_socket_is_open(session->socket)) { + rc = ssh_buffer_pack(session->out_buffer, + "bs", + type, + data); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + goto error; + } + packet_send(session); + ssh_handle_packets(session, 0); + } + + return SSH_OK; + +error: + ssh_buffer_reinit(session->out_buffer); + return SSH_ERROR; +} + +/** + * @brief Send a debug message + * + * @param[in] session The SSH session + * @param[in] message Data to be sent + * @param[in] always_display Message SHOULD be displayed by the server. It + * SHOULD NOT be displayed unless debugging + * information has been explicitly requested. + * + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_send_debug (ssh_session session, const char *message, int always_display) { + int rc; + + if (ssh_socket_is_open(session->socket)) { +#ifdef WITH_SSH1 + if (session->version == 1) { + rc = ssh_buffer_pack(session->out_buffer, + "bs", + SSH_MSG_DEBUG, + message); + } else +#endif /* WITH_SSH1 */ + { + rc = ssh_buffer_pack(session->out_buffer, + "bbsd", + SSH2_MSG_DEBUG, + always_display != 0 ? 1 : 0, + message, + 0); /* empty language tag */ + } + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + packet_send(session); + ssh_handle_packets(session, 0); + } + + return SSH_OK; + +error: + ssh_buffer_reinit(session->out_buffer); + return SSH_ERROR; +} + + /** + * @brief Set the session data counters. + * + * This functions sets the counter structures to be used to calculate data + * which comes in and goes out through the session at different levels. + * + * @code + * struct ssh_counter_struct scounter = { + * .in_bytes = 0, + * .out_bytes = 0, + * .in_packets = 0, + * .out_packets = 0 + * }; + * + * struct ssh_counter_struct rcounter = { + * .in_bytes = 0, + * .out_bytes = 0, + * .in_packets = 0, + * .out_packets = 0 + * }; + * + * ssh_set_counters(session, &scounter, &rcounter); + * @endcode + * + * @param[in] session The SSH session. + * + * @param[in] scounter Counter for byte data handled by the session sockets. + * + * @param[in] rcounter Counter for byte and packet data handled by the session, + * prior compression and SSH overhead. + */ +void ssh_set_counters(ssh_session session, ssh_counter scounter, + ssh_counter rcounter) { + if (session != NULL) { + session->socket_counter = scounter; + session->raw_counter = rcounter; + } +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/server/tp_core/protocol/ssh/ssh_proxy.cpp b/server/tp_core/protocol/ssh/ssh_proxy.cpp index 8dbbbb5..6d5f72e 100644 --- a/server/tp_core/protocol/ssh/ssh_proxy.cpp +++ b/server/tp_core/protocol/ssh/ssh_proxy.cpp @@ -86,6 +86,7 @@ void SshProxy::_thread_loop() { // 注意,ssh_new()出来的指针,如果遇到停止标志,本函数内部就释放了,否则这个指针交给了SshSession类实例管理,其析构时会释放。 ssh_session sess_to_client = ssh_new(); + ssh_set_blocking(sess_to_client, 1); struct sockaddr_storage sock_client; char ip[32] = { 0 }; diff --git a/server/tp_core/protocol/ssh/ssh_session.cpp b/server/tp_core/protocol/ssh/ssh_session.cpp index 10a16aa..3344787 100644 --- a/server/tp_core/protocol/ssh/ssh_session.cpp +++ b/server/tp_core/protocol/ssh/ssh_session.cpp @@ -117,7 +117,6 @@ bool SshSession::_on_session_begin(TP_SSH_CHANNEL_PAIR* cp) void SshSession::_on_session_end(TP_SSH_CHANNEL_PAIR* cp) { - EXLOGD("[ssh] inside call end(). db-id: %d\n", cp->db_id); if (cp->db_id > 0) { EXLOGD("[ssh] session db-id: %d, ret-code: %d\n", cp->db_id, cp->retcode); @@ -143,8 +142,8 @@ void SshSession::_close_channels(void) { ssh_channel ch = (*it)->srv_channel; if (ch != NULL) { if (!ssh_channel_is_closed(ch)) { - if (!ssh_channel_is_eof(ch)) - ssh_channel_send_eof(ch); +// if (!ssh_channel_is_eof(ch)) +// ssh_channel_send_eof(ch); ssh_channel_close(ch); } ssh_channel_free(ch); @@ -153,13 +152,14 @@ void SshSession::_close_channels(void) { ch = (*it)->cli_channel; if (ch != NULL) { if (!ssh_channel_is_closed(ch)) { - if (!ssh_channel_is_eof(ch)) - ssh_channel_send_eof(ch); +// if (!ssh_channel_is_eof(ch)) +// ssh_channel_send_eof(ch); ssh_channel_close(ch); } ssh_channel_free(ch); } + EXLOGD(" --- end by close all channel: %d\n", (*it)->db_id); _on_session_end(*it); delete (*it); @@ -168,6 +168,53 @@ void SshSession::_close_channels(void) { m_channels.clear(); } +void SshSession::_check_channels() { + ExThreadSmartLock locker(m_lock); + + EXLOGD("-- check channels, have %d\n", m_channels.size()); + tp_channels::iterator it = m_channels.begin(); + for (; it != m_channels.end(); ) { + EXLOGD("-- channel db-id: %d\n", (*it)->db_id); + bool closed = false; + ssh_channel cli = (*it)->cli_channel; + ssh_channel srv = (*it)->srv_channel; + if (cli != NULL) { + if (ssh_channel_is_closed(cli)) { + EXLOGD(" -- check server channel, already closed: %d\n", (*it)->db_id); + closed = true; + } + } + if (srv != NULL) { + if (ssh_channel_is_closed(srv)) { + EXLOGD(" -- check client channel, already closed: %d\n", (*it)->db_id); + closed = true; + } + } + + if (closed) { + EXLOGD(" --- end by check channel: %d\n", (*it)->db_id); + _on_session_end((*it)); + + if (!ssh_channel_is_closed(cli)) { + ssh_channel_close(cli); + } + ssh_channel_free(cli); + + if (!ssh_channel_is_closed(srv)) { + ssh_channel_close(srv); + } + ssh_channel_free(srv); + + delete (*it); + + m_channels.erase(it++); + } + else { + ++it; + } + } +} + void SshSession::_run(void) { m_srv_cb.auth_password_function = _on_auth_password_request; m_srv_cb.channel_open_request_session_function = _on_new_channel_request; @@ -227,7 +274,8 @@ void SshSession::_run(void) { // 现在双方的连接已经建立好了,开始转发 ssh_event_add_session(event_loop, m_srv_session); do { - r = ssh_event_dopoll(event_loop, -1); + r = ssh_event_dopoll(event_loop, 5000); + //EXLOGD("ssh_event_dopoll() return %d.\n", r); if (r == SSH_ERROR) { if (0 != ssh_get_error_code(m_cli_session)) { @@ -240,6 +288,10 @@ void SshSession::_run(void) { _close_channels(); } + else if (r == SSH_AGAIN) { + // timeout. + _check_channels(); + } } while (m_channels.size() > 0); EXLOGV("[ssh] [%s:%d] all channel in this session are closed.\n", m_client_ip.c_str(), m_client_port); @@ -258,7 +310,6 @@ void SshSession::save_record() { } } - int SshSession::_on_auth_password_request(ssh_session session, const char *user, const char *password, void *userdata) { // 这里拿到的user就是我们要的session-id。 SshSession *_this = (SshSession *)userdata; @@ -294,6 +345,7 @@ int SshSession::_on_auth_password_request(ssh_session session, const char *user, // 现在尝试根据session-id获取得到的信息,连接并登录真正的SSH服务器 EXLOGV("[ssh] try to connect to real SSH server %s:%d\n", _this->m_conn_ip.c_str(), _this->m_conn_port); _this->m_srv_session = ssh_new(); + ssh_set_blocking(_this->m_srv_session, 1); ssh_options_set(_this->m_srv_session, SSH_OPTIONS_HOST, _this->m_conn_ip.c_str()); int port = (int)_this->m_conn_port; ssh_options_set(_this->m_srv_session, SSH_OPTIONS_PORT, &port); @@ -318,6 +370,9 @@ int SshSession::_on_auth_password_request(ssh_session session, const char *user, return SSH_AUTH_ERROR; } + _timeout = 10; // 10 sec. + ssh_options_set(_this->m_srv_session, SSH_OPTIONS_TIMEOUT, &_timeout); + // // 检查服务端支持的认证协议 ssh_userauth_none(_this->m_srv_session, NULL); // rc = ssh_userauth_none(_this->m_srv_session, NULL); @@ -454,9 +509,10 @@ ssh_channel SshSession::_on_new_channel_request(ssh_session session, void *userd EXLOGE("[ssh] can not create channel for server.\n"); return NULL; } - if (ssh_channel_open_session(srv_channel)) { - EXLOGE("[ssh] error opening channel to real server: %s\n", ssh_get_error(session)); + if (SSH_OK != ssh_channel_open_session(srv_channel)) { + EXLOGE("[ssh] error opening channel to real server: %s\n", ssh_get_error(_this->m_srv_session)); ssh_channel_free(cli_channel); + ssh_channel_free(srv_channel); return NULL; } ssh_set_channel_callbacks(srv_channel, &_this->m_srv_channel_cb); @@ -871,11 +927,10 @@ void SshSession::_process_sftp_command(TppSshRec* rec, const ex_u8* data, int le rec->record_command(msg); } - int SshSession::_on_client_pty_request(ssh_session session, ssh_channel channel, const char *term, int x, int y, int px, int py, void *userdata) { SshSession *_this = (SshSession *)userdata; - EXLOGD("[ssh] client request terminal: %s, (%d, %d) / (%d, %d)\n", term, x, y, px, py); + EXLOGD("[ssh] client request pty: %s, (%d, %d) / (%d, %d)\n", term, x, y, px, py); TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_CLIENT_SIDE, channel); if (NULL == cp) { @@ -885,7 +940,10 @@ int SshSession::_on_client_pty_request(ssh_session session, ssh_channel channel, cp->rec.record_win_size_startup(x, y); - return ssh_channel_request_pty_size(cp->srv_channel, term, x, y); + int err = ssh_channel_request_pty_size(cp->srv_channel, term, x, y); + if(err != SSH_OK) + EXLOGD("[ssh] pty request from server got %d\n", err); + return err; } int SshSession::_on_client_shell_request(ssh_session session, ssh_channel channel, void *userdata) { @@ -905,11 +963,13 @@ int SshSession::_on_client_shell_request(ssh_session session, ssh_channel channe // FIXME: if client is putty, it will block here. the following function will never return. // at this time, can not write data to this channel. read from this channel with timeout, got 0 byte. // I have no idea how to fix it... :( - return ssh_channel_request_shell(cp->srv_channel); + int err = ssh_channel_request_shell(cp->srv_channel); + if (err != SSH_OK) + EXLOGD("[ssh] shell request from server got %d\n", err); + return err; } void SshSession::_on_client_channel_close(ssh_session session, ssh_channel channel, void *userdata) { - EXLOGD("[ssh] on_client_channel_close().\n"); SshSession *_this = (SshSession *)userdata; @@ -920,57 +980,43 @@ void SshSession::_on_client_channel_close(ssh_session session, ssh_channel chann } EXLOGD("[ssh] on_client_channel_close(). db-id: %d\n", cp->db_id); + int db_id = cp->db_id; + + EXLOGD(" --- end by client channel close: %d\n", db_id); + _this->_on_session_end(cp); if (cp->srv_channel == NULL) { EXLOGW("[ssh] when client channel close, server-channel not exists.\n"); } else { if (!ssh_channel_is_closed(cp->srv_channel)) { - // if (!ssh_channel_is_eof(cp->srv_channel)) { - // //EXLOGD("[ssh] when client channel close, send eof to server-channel.\n"); - // ssh_channel_send_eof(cp->cli_channel); - // } - - //EXLOGD("[ssh] when client channel close, close server-channel.\n"); ssh_channel_close(cp->srv_channel); - - // ssh_channel_free(cp->srv_channel); - // cp->srv_channel = NULL; } } - if (!ssh_channel_is_closed(cp->cli_channel)) { - // if (!ssh_channel_is_eof(cp->cli_channel)) { - // //EXLOGD("[ssh] when client channel close, send eof to client-channel.\n"); - // ssh_channel_send_eof(cp->cli_channel); - // } - - //EXLOGD("[ssh] when client channel close, close client-channel.\n"); - ssh_channel_close(cp->cli_channel); - - // ssh_channel_free(cp->cli_channel); - // cp->cli_channel = NULL; - } +// if (!ssh_channel_is_closed(cp->cli_channel)) { +// ssh_channel_close(cp->cli_channel); +// } if (ssh_channel_is_closed(cp->cli_channel) && ssh_channel_is_closed(cp->srv_channel)) { ssh_channel_free(cp->cli_channel); + cp->cli_channel = NULL; ssh_channel_free(cp->srv_channel); + cp->srv_channel = NULL; ExThreadSmartLock locker(_this->m_lock); - EXLOGD("[ssh] on_client_channel_close() before call end(). db-id: %d\n", cp->db_id); - _this->_on_session_end(cp); - tp_channels::iterator it = _this->m_channels.begin(); for (; it != _this->m_channels.end(); ++it) { if ((*it) == cp) { - EXLOGD("--- client_channel_close(), erase: %d\n", cp->db_id); + EXLOGD("--- client_channel_close(), erase: %d\n", db_id); delete (*it); _this->m_channels.erase(it); break; } } + //_this->_check_channels(); } } @@ -992,12 +1038,6 @@ int SshSession::_on_client_channel_data(ssh_session session, ssh_channel channel return SSH_ERROR; } - // TS_SSH_CHANNEL_INFO *info = _this->_get_srv_channel(channel); - // if (NULL == info || NULL == info->channel) { - // EXLOGE("[ssh] when receive client channel data, not found server channel.\n"); - // return SSH_ERROR; - // } - _this->m_recving_from_cli = true; if (cp->type == TS_SSH_CHANNEL_TYPE_SHELL) @@ -1023,6 +1063,8 @@ int SshSession::_on_client_channel_data(ssh_session session, ssh_channel channel if (ret == SSH_ERROR) { EXLOGE("[ssh] send data(%dB) to server failed. [%d][cli:%s][srv:%s]\n", len, ret, ssh_get_error(_this->m_cli_session), ssh_get_error(_this->m_srv_session)); + + ssh_channel_close(channel); } _this->m_recving_from_cli = false; @@ -1040,12 +1082,6 @@ int SshSession::_on_client_pty_win_change(ssh_session session, ssh_channel chann return SSH_ERROR; } - // TS_SSH_CHANNEL_INFO *info = _this->_get_srv_channel(channel); - // if (NULL == info || NULL == info->channel) { - // EXLOGE("[ssh] when client pty win change, not found server channel.\n"); - // return SSH_ERROR; - // } - cp->rec.record_win_size_change(width, height); return ssh_channel_change_pty_size(cp->srv_channel, width, height); @@ -1068,28 +1104,15 @@ int SshSession::_on_client_channel_subsystem_request(ssh_session session, ssh_ch return SSH_ERROR; } - // TS_SSH_CHANNEL_INFO *srv_info = _this->_get_srv_channel(channel); - // if (NULL == srv_info || NULL == srv_info->channel) { - // EXLOGE("[ssh] when receive client channel subsystem request, not found server channel.\n"); - // return SSH_ERROR; - // } - // srv_info->type = TS_SSH_CHANNEL_TYPE_SFTP; - // - // TS_SSH_CHANNEL_INFO *cli_info = _this->_get_cli_channel(srv_info->channel); - // if (NULL == cli_info || NULL == cli_info->channel) { - // EXLOGE("[ssh] when client request shell, not found client channel.\n"); - // return SSH_ERROR; - // } - // cli_info->type = TS_SSH_CHANNEL_TYPE_SFTP; cp->type = TS_SSH_CHANNEL_TYPE_SFTP; - g_ssh_env.session_update(cp->db_id, TP_PROTOCOL_TYPE_SSH_SFTP, TP_SESS_STAT_STARTED); - - // 一个ssh会话打开了sftp通道,就将连接信息记录下来备用,随后这个session-id再次尝试连接时,我们允许其连接。 - //_this->_enter_sftp_mode(); - - return ssh_channel_request_subsystem(cp->srv_channel, subsystem); + //EXLOGD("[ssh] ---> request channel subsystem from server\n"); + int err = ssh_channel_request_subsystem(cp->srv_channel, subsystem); + //EXLOGD("[ssh] <--- request channel subsystem from server\n"); + if (err != SSH_OK) + EXLOGD("[ssh] request channel subsystem from server got %d\n", err); + return err; } int SshSession::_on_client_channel_exec_request(ssh_session session, ssh_channel channel, const char *command, void *userdata) { @@ -1201,6 +1224,7 @@ int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel if (ret == SSH_ERROR) { EXLOGE("[ssh] send data(%dB) to client failed. [%d][cli:%s][srv:%s]\n", len, ret, ssh_get_error(_this->m_cli_session), ssh_get_error(_this->m_srv_session)); + ssh_channel_close(channel); } _this->m_recving_from_srv = false; @@ -1208,66 +1232,44 @@ int SshSession::_on_server_channel_data(ssh_session session, ssh_channel channel } void SshSession::_on_server_channel_close(ssh_session session, ssh_channel channel, void *userdata) { - EXLOGD("[ssh] on_server_channel_close().\n"); - SshSession *_this = (SshSession *)userdata; - TP_SSH_CHANNEL_PAIR* cp = _this->_get_channel_pair(TP_SSH_SERVER_SIDE, channel); if (NULL == cp) { EXLOGE("[ssh] when server channel close, not found channel pair.\n"); return; } + int db_id = cp->db_id; + EXLOGD(" --- end by server channel close: %d\n", db_id); + _this->_on_session_end(cp); + // will the server-channel exist, the client-channel must exist too. if (cp->cli_channel == NULL) { EXLOGE("[ssh] when server channel close, client-channel not exists.\n"); } else { if (!ssh_channel_is_closed(cp->cli_channel)) { - // if (!ssh_channel_is_eof(cp->cli_channel)) { - // //EXLOGD("[ssh] when server channel close, send eof to client-channel.\n"); - // ssh_channel_send_eof(cp->cli_channel); - // } - // - //EXLOGD("[ssh] when server channel close, close client-channel.\n"); ssh_channel_close(cp->cli_channel); - - // ssh_channel_free(cp->cli_channel); - // cp->cli_channel = NULL; } } - if (!ssh_channel_is_closed(cp->srv_channel)) { - // if (!ssh_channel_is_eof(cp->srv_channel)) { - // //EXLOGD("[ssh] when server channel close, send eof to server-channel.\n"); - // ssh_channel_send_eof(cp->srv_channel); - // } - - //EXLOGD("[ssh] when server channel close, close server-channel.\n"); - ssh_channel_close(cp->srv_channel); - - // ssh_channel_free(cp->srv_channel); - // cp->srv_channel = NULL; - } +// if (!ssh_channel_is_closed(cp->srv_channel)) { +// ssh_channel_close(cp->srv_channel); +// } if (ssh_channel_is_closed(cp->cli_channel) && ssh_channel_is_closed(cp->srv_channel)) { - // ssh_channel_close(cp->cli_channel); ssh_channel_free(cp->cli_channel); cp->cli_channel = NULL; - // ssh_channel_close(cp->srv_channel); ssh_channel_free(cp->srv_channel); cp->srv_channel = NULL; ExThreadSmartLock locker(_this->m_lock); - EXLOGD("[ssh] on_server_channel_close() before call end(). db-id: %d\n", cp->db_id); - _this->_on_session_end(cp); - tp_channels::iterator it = _this->m_channels.begin(); for (; it != _this->m_channels.end(); ++it) { if ((*it) == cp) { - EXLOGD("--- server_channel_close(), erase: %d\n", cp->db_id); + EXLOGD("--- server_channel_close(), erase: %d\n", db_id); delete (*it); _this->m_channels.erase(it); break; diff --git a/server/tp_core/protocol/ssh/ssh_session.h b/server/tp_core/protocol/ssh/ssh_session.h index 876ca26..5f15ff5 100644 --- a/server/tp_core/protocol/ssh/ssh_session.h +++ b/server/tp_core/protocol/ssh/ssh_session.h @@ -78,6 +78,10 @@ private: void _run(void); void _close_channels(void); + void _check_channels(void); + +// void _client_channel_closed(TP_SSH_CHANNEL_PAIR* cp, bool& need_removed); +// void _server_channel_closed(TP_SSH_CHANNEL_PAIR* cp, bool& need_removed); static int _on_auth_password_request(ssh_session session, const char *user, const char *password, void *userdata); static ssh_channel _on_new_channel_request(ssh_session session, void *userdata);