diff options
Diffstat (limited to 'src/session.c')
-rw-r--r-- | src/session.c | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/src/session.c b/src/session.c new file mode 100644 index 00000000..42f5e772 --- /dev/null +++ b/src/session.c @@ -0,0 +1,520 @@ +/* + * session.c - non-networking functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 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 <string.h> +#include <stdlib.h> +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/server.h" +#include "libssh/socket.h" +#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; + 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; + session->blocking = 1; + session->log_indent = 0; + session->maxchannel = FIRST_CHANNEL; + + /* options */ + session->StrictHostKeyChecking = 1; + session->port = 22; + session->fd = -1; + session->ssh2 = 1; +#ifdef WITH_SSH1 + session->ssh1 = 1; +#else + session->ssh1 = 0; +#endif + +#ifndef _WIN32 + session->agent = agent_new(session); + if (session->agent == NULL) { + goto err; + } +#endif /* _WIN32 */ + + session->identity = ssh_list_new(); + if (session->identity == NULL) { + goto err; + } + + id = strdup("%d/id_rsa"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->identity, id); + if (rc == SSH_ERROR) { + goto err; + } + + id = strdup("%d/id_dsa"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->identity, id); + if (rc == SSH_ERROR) { + goto err; + } + + id = strdup("%d/identity"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->identity, id); + if (rc == SSH_ERROR) { + goto err; + } + + return session; + +err: + 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; + enter_function(); + + if (session == NULL) { + return; + } + + SAFE_FREE(session->serverbanner); + SAFE_FREE(session->clientbanner); + SAFE_FREE(session->banner); +#ifdef WITH_PCAP + if(session->pcap_ctx){ + ssh_pcap_context_free(session->pcap_ctx); + session->pcap_ctx=NULL; + } +#endif + ssh_buffer_free(session->in_buffer); + ssh_buffer_free(session->out_buffer); + session->in_buffer=session->out_buffer=NULL; + crypto_free(session->current_crypto); + crypto_free(session->next_crypto); + ssh_socket_free(session->socket); + /* delete all channels */ + while (session->channels) { + ssh_channel_free(session->channels); + } +#ifndef _WIN32 + agent_free(session->agent); +#endif /* _WIN32 */ + if (session->client_kex.methods) { + for (i = 0; i < 10; i++) { + SAFE_FREE(session->client_kex.methods[i]); + } + } + + if (session->server_kex.methods) { + for (i = 0; i < 10; i++) { + SAFE_FREE(session->server_kex.methods[i]); + } + } + SAFE_FREE(session->client_kex.methods); + SAFE_FREE(session->server_kex.methods); + + privatekey_free(session->dsa_key); + privatekey_free(session->rsa_key); + if(session->ssh_message_list){ + ssh_message msg; + while((msg=ssh_list_pop_head(ssh_message ,session->ssh_message_list)) + != NULL){ + ssh_message_free(msg); + } + ssh_list_free(session->ssh_message_list); + } + + if (session->packet_callbacks) + ssh_list_free(session->packet_callbacks); + + if (session->identity) { + char *id; + + for (id = ssh_list_pop_head(char *, session->identity); + id != NULL; + id = ssh_list_pop_head(char *, session->identity)) { + SAFE_FREE(id); + } + ssh_list_free(session->identity); + } + + /* options */ + SAFE_FREE(session->username); + SAFE_FREE(session->host); + SAFE_FREE(session->sshdir); + SAFE_FREE(session->knownhosts); + SAFE_FREE(session->ProxyCommand); + + for (i = 0; i < 10; i++) { + if (session->wanted_methods[i]) { + SAFE_FREE(session->wanted_methods[i]); + } + } + + /* burn connection, it could hang sensitive datas */ + ZERO_STRUCTP(session); + SAFE_FREE(session); +} + +/** + * @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) { + enter_function(); + + if (session == NULL) { + return; + } + + ssh_socket_close(session->socket); + session->alive = 0; + ssh_disconnect(session); + leave_function(); +} + +/** + * @brief Set the session in blocking/nonblocking mode. + * + * @param[in] session The ssh session to change. + * + * @param[in] blocking Zero for nonblocking mode. + * + * \bug nonblocking code is in development and won't work as expected + */ +void ssh_set_blocking(ssh_session session, int blocking) { + if (session == NULL) { + return; + } + + session->blocking = blocking ? 1 : 0; +} + +/** + * @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_toread(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_towrite(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 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 a negative value + * means an infinite timeout. 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; + if(session==NULL || session->socket==NULL) + return SSH_ERROR; + enter_function(); + spoll_in=ssh_socket_get_poll_handle_in(session->socket); + spoll_out=ssh_socket_get_poll_handle_out(session->socket); + ssh_poll_set_events(spoll_in, POLLIN | POLLERR); + ctx=ssh_poll_get_ctx(spoll_in); + if(ctx==NULL){ + ctx=ssh_get_global_poll_ctx(session); + ssh_poll_ctx_add(ctx,spoll_in); + if(spoll_in != spoll_out) + ssh_poll_ctx_add(ctx,spoll_out); + } + ssh_poll_ctx_dopoll(ctx,timeout); + leave_function(); + if (session->session_state != SSH_SESSION_STATE_ERROR) + return SSH_OK; + else + return SSH_ERROR; +} + +/** + * @brief Get session status + * + * @param session The ssh session to use. + * + * @returns A bitmask including SSH_CLOSED, SSH_READ_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->closed) { + r |= SSH_CLOSED; + } + if (socketstate & SSH_READ_PENDING) { + r |= SSH_READ_PENDING; + } + if (session->closed && (socketstate & SSH_CLOSED_ERROR)) { + r |= SSH_CLOSED_ERROR; + } + + return r; +} + +/** + * @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->closed) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Connection not closed yet"); + } else if(session->closed_by_except) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Connection closed by socket error"); + } 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 Handle a SSH_DISCONNECT packet. + */ +SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback){ + uint32_t code; + char *error=NULL; + ssh_string error_s; + (void)user; + (void)type; + buffer_get_u32(packet, &code); + error_s = buffer_get_ssh_string(packet); + if (error_s != NULL) { + error = ssh_string_to_char(error_s); + ssh_string_free(error_s); + } + ssh_log(session, SSH_LOG_PACKET, "Received SSH_MSG_DISCONNECT %d:%s",code, + error != NULL ? error : "no error"); + ssh_set_error(session, SSH_FATAL, + "Received SSH_MSG_DISCONNECT: %d:%s",code, + error != NULL ? error : "no error"); + SAFE_FREE(error); + + ssh_socket_close(session->socket); + session->alive = 0; + /* TODO: handle a graceful disconnect */ + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_IGNORE and SSH_DEBUG packet. + */ +SSH_PACKET_CALLBACK(ssh_packet_ignore_callback){ + (void)user; + (void)type; + (void)packet; + ssh_log(session,SSH_LOG_PROTOCOL,"Received %s packet",type==SSH2_MSG_IGNORE ? "SSH_MSG_IGNORE" : "SSH_MSG_DEBUG"); + /* TODO: handle a graceful disconnect */ + return SSH_PACKET_USED; +} + +/** + * @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; + enter_function(); + ssh_log(session,SSH_LOG_RARE,"Socket exception callback: %d (%d)",code, errno_code); + session->session_state=SSH_SESSION_STATE_ERROR; + ssh_set_error(session,SSH_FATAL,"Socket error: %s",strerror(errno_code)); + session->ssh_connection_callback(session); + leave_function(); +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ |