Commit 997d1cc0 authored by Lefebvre, Jordan's avatar Lefebvre, Jordan
Browse files

#1. Initial implementation of Session and Channel classes for separation of...

#1. Initial implementation of Session and Channel classes for separation of session and execution channels to support multi-threaded environment.
parent b217162d
Pipeline #98383 failed with stages
in 81 minutes and 18 seconds
...@@ -9,6 +9,7 @@ queuesge.hh ...@@ -9,6 +9,7 @@ queuesge.hh
) )
SET(SOURCES SET(SOURCES
hostconfig.cc hostconfig.cc
session.cc
sessionworker.cc sessionworker.cc
sessioncontroller.cc sessioncontroller.cc
queuebase.cc queuebase.cc
...@@ -23,6 +24,7 @@ SET(HEADERS ...@@ -23,6 +24,7 @@ SET(HEADERS
${HEADERS} ${HEADERS}
declspec.hh declspec.hh
hostconfig.hh hostconfig.hh
session.hh
sessionlog.hh sessionlog.hh
) )
TRIBITS_ADD_LIBRARY(rsmcore TRIBITS_ADD_LIBRARY(rsmcore
......
#include "rsmcore/session.hh"
#include "radixbug/bug.hh"
#include <libssh/libssh.h>
#include <cassert>
#include <sstream>
#include <QByteArray>
#include <QString>
namespace rsm
{
void assert_ssh_session(ssh_session session, const std::string& error_message)
{
if (session == nullptr)
{
throw std::runtime_error(error_message.c_str());
}
}
void assert_ssh_channel(ssh_channel channel, const std::string& error_message)
{
if (channel == nullptr)
{
throw std::runtime_error(error_message.c_str());
}
}
class ChannelImpl
{
public:
ssh_channel channel = nullptr;
explicit ChannelImpl(ssh_session session)
{
channel = ssh_channel_new(session);
}
~ChannelImpl()
{
if (channel != nullptr)
{
ssh_channel_close(channel);
ssh_channel_free(channel);
}
}
}; // class ChannelImpl
Channel::Channel()
{
radix_tagged_line("Channel::Channel()");
p = nullptr;
}
Channel::~Channel()
{
radix_tagged_line("Channel::~Channel()");
if (p != nullptr) delete p;
}
bool Channel::open()
{
assert_ssh_channel(p->channel, "open() -- Channel is not allocated.");
int rc = ssh_channel_open_session(p->channel);
return (rc > -1);
}
bool Channel::isOpen() const
{
// Perhaps we should just return false
assert_ssh_channel(p->channel, "isOpen() -- Channel is not allocated.");
int rc = ssh_channel_is_open(p->channel);
return (rc != 0);
}
void Channel::close()
{
if (p->channel != nullptr)
{
ssh_channel_close(p->channel);
ssh_channel_free(p->channel);
p->channel = nullptr;
}
}
bool Channel::exec(QString command)
{
assert_ssh_channel(p->channel, "exec() -- Channel is not allocated.");
int rc = ssh_channel_request_exec(p->channel, command.toStdString().c_str());
return !(rc < 0);
}
QString Channel::readExecOut()
{
assert_ssh_channel(p->channel, "readExecOut() -- Channel is not allocated.");
char buffer[256];
int nbytes = ssh_channel_read(p->channel, buffer, sizeof(buffer), 0);
QByteArray output_buffer;
while (nbytes > 0)
{
output_buffer.append(buffer, nbytes);
nbytes = ssh_channel_read(p->channel, buffer, sizeof(buffer), 0);
}
return output_buffer;
}
QString Channel::readExecErr()
{
assert_ssh_channel(p->channel, "readExecErr() -- Channel is not allocated.");
char buffer[256];
int nbytes = ssh_channel_read(p->channel, buffer, sizeof(buffer), 1);
QByteArray output_buffer;
while (nbytes > 0)
{
output_buffer.append(buffer, nbytes);
nbytes = ssh_channel_read(p->channel, buffer, sizeof(buffer), 1);
}
return output_buffer;
}
class SessionImpl
{
public:
ssh_session session = nullptr;
SessionImpl()
{
session = ssh_new();
assert_ssh_session(session, "Failed to allocate new ssh session.");
}
~SessionImpl()
{
if (session != nullptr)
{
ssh_free(session);
ssh_finalize();
}
}
}; // class SessionImpl
Session::Session() { p = new SessionImpl(); }
Session::Session(QString host)
{
p = new SessionImpl();
setHost(host);
}
Session::~Session()
{
if (p != nullptr) delete p;
}
void Session::setHost(QString host)
{
assert_ssh_session(p->session, "setHost() -- Session is not allocated.");
ssh_options_set(p->session, SSH_OPTIONS_HOST, host.toStdString().c_str());
}
std::string Session::host() const
{
// This may not be necessary
assert_ssh_session(p->session, "host() -- Session is not allocated.");
char* host_name;
int rv = ssh_options_get(p->session, SSH_OPTIONS_HOST, &host_name);
if (rv == SSH_OK)
{
std::string host = host_name;
delete host_name;
return host;
}
else
{
return std::string("");
}
}
void Session::setLogVerbosity(SessionVerbosity level)
{
assert_ssh_session(p->session,
"setLogVerbosity() -- Session is not allocated.");
int verbosity = static_cast<int>(level);
ssh_options_set(p->session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
}
void Session::setPort(int port)
{
assert_ssh_session(p->session, "setPort() -- Session is not allocated.");
ssh_options_set(p->session, SSH_OPTIONS_PORT, &port);
}
void Session::setUser(QString name)
{
assert_ssh_session(p->session, "setName() -- Session is not allocated.");
ssh_options_set(p->session, SSH_OPTIONS_USER, name.toStdString().c_str());
}
std::string Session::user() const
{
// This may not be necessary
assert_ssh_session(p->session, "user() -- Session is not allocated.");
char* user_name;
int rv = ssh_options_get(p->session, SSH_OPTIONS_USER, &user_name);
if (rv == SSH_OK)
{
std::string user = user_name;
delete user_name;
return user;
}
else
{
return std::string("");
}
}
void Session::setProxyCommand(QString command)
{
assert_ssh_session(p->session,
"setProxyCommand() -- Session is not allocated.");
ssh_options_set(p->session, SSH_OPTIONS_PROXYCOMMAND,
command.toStdString().c_str());
}
bool Session::connect()
{
assert_ssh_session(p->session, "connect() -- Session is not allocated.");
radix_tagged_line("connect()");
if (ssh_is_connected(p->session) == 1)
{
radix_tagged_line("session already connected. disconnectinng.");
ssh_disconnect(p->session);
}
{
radix_tagged_line("Attempting connection.");
// attempt a connection
int rc = ssh_connect(p->session);
return (rc == SSH_OK);
}
}
bool Session::isConnected() const
{
assert_ssh_session(p->session, "isConnected() -- Session is not allocated.");
return (ssh_is_connected(p->session) != 0);
}
std::string Session::getError() const
{
assert_ssh_session(p->session, "getError() -- Session is not allocated.");
return ssh_get_error(p->session);
}
bool Session::disconnect()
{
assert_ssh_session(p->session, "disconnect() -- Session is not allocated.");
radix_tagged_line("disconnect()");
if (ssh_is_connected(p->session) != 0)
{
radix_tagged_line("Disconnecting session.");
ssh_disconnect(p->session);
// copy options in preparation for deletion
ssh_session new_session = ssh_new();
ssh_options_copy(p->session, &new_session);
ssh_free(p->session);
p->session = new_session;
}
return true;
}
QString Session::getHexa() const
{
assert_ssh_session(p->session, "getHexa() -- Session is not allocated.");
unsigned char* hash = nullptr;
ssh_key server_public_key = nullptr;
char* hexa;
size_t hlen;
QString qhexa;
int rc = ssh_get_server_publickey(p->session, &server_public_key);
if (rc < 0)
{
return QString();
}
// Probably should remove hardwired HASH_SHA1
rc = ssh_get_publickey_hash(server_public_key, SSH_PUBLICKEY_HASH_SHA1, &hash,
&hlen);
ssh_key_free(server_public_key);
if (rc < 0)
{
return QString();
}
hexa = ssh_get_hexa(hash, hlen);
qhexa = hexa;
ssh_clean_pubkey_hash(&hash);
ssh_string_free_char(hexa);
return qhexa;
}
SessionHostState Session::verifyKnownHost() const
{
assert_ssh_session(p->session,
"verifyKnownHost() -- Session is not allocated.");
enum ssh_known_hosts_e state;
unsigned char* hash = nullptr;
ssh_key server_public_key = nullptr;
size_t hlen;
QString qhexa;
radix_tagged_line("verifyKnownHost()");
int rc = ssh_get_server_publickey(p->session, &server_public_key);
if (rc < 0)
{
return SessionHostState::ERROR;
}
rc = ssh_get_publickey_hash(server_public_key, SSH_PUBLICKEY_HASH_SHA1, &hash,
&hlen);
ssh_clean_pubkey_hash(&hash);
ssh_key_free(server_public_key);
if (rc < 0)
{
return SessionHostState::ERROR;
}
state = ssh_session_is_known_server(p->session);
return static_cast<SessionHostState>(state);
}
bool Session::updateKnownHosts()
{
assert_ssh_session(p->session,
"updateKnownHosts() -- Session is not allocated.");
radix_tagged_line("updateKnownHosts()");
int rc = ssh_session_update_known_hosts(p->session);
return (rc == SSH_OK);
}
int Session::authenticationMethodList() const
{
assert_ssh_session(p->session,
"authenticationMethodList() -- Session is not allocated.");
// get acceptable methods
int method = ssh_userauth_list(p->session, nullptr);
return method;
}
SessionAuthState Session::authenticateWithPublicKey()
{
int rc = ssh_userauth_publickey_auto(p->session, nullptr, nullptr);
return static_cast<SessionAuthState>(rc);
}
QString Session::keyboardInteractiveName() const
{
assert_ssh_session(p->session,
"keyboardInteractiveName() -- Session is not allocated.");
QString name = ssh_userauth_kbdint_getname(p->session);
return name;
}
QString Session::keyboardInteractiveInstruction() const
{
assert_ssh_session(
p->session,
"keyboardInteractiveInstruction() -- Session is not allocated.");
QString instruction = ssh_userauth_kbdint_getinstruction(p->session);
return instruction;
}
QStringList Session::keyboardInteractivePrompts() const
{
int num_prompts = ssh_userauth_kbdint_getnprompts(p->session);
// build list of prompts
QStringList prompts;
char echo;
for (int i = 0; i < num_prompts; ++i)
{
QString prompt = ssh_userauth_kbdint_getprompt(
p->session, static_cast<unsigned int>(i), &echo);
radix_tagged_line(i << ". " << prompt.toStdString());
if (prompt.isEmpty()) break;
prompts << prompt;
}
return prompts;
}
SessionAuthState Session::authenticateWithPassword(QString pswd)
{
assert_ssh_session(p->session,
"authenticateWithPassword() -- Session is not allocated.");
radix_tagged_line("Authenticate with password.");
int rc =
ssh_userauth_password(p->session, nullptr, pswd.toStdString().c_str());
return static_cast<SessionAuthState>(rc);
}
QString Session::loginIssueBanner() const
{
char* banner = ssh_get_issue_banner(p->session);
if (banner)
{
QString qbanner = banner;
delete banner;
return qbanner;
}
return QString("");
}
SessionAuthState Session::authenticatePrompts(QStringList responses)
{
int err;
radix_tagged_line("authenticatePrompts()");
for (int i = 0; i < responses.size(); ++i)
{
radix_tagged_line("Setting response " << i);
const char* answer = responses.at(i).toStdString().c_str();
err = ssh_userauth_kbdint_setanswer(p->session,
static_cast<unsigned int>(i), answer);
if (err < 0)
{
ssh_disconnect(p->session);
return SessionAuthState::ERROR;
}
}
// check status
err = ssh_userauth_kbdint(p->session, nullptr, nullptr);
SessionAuthState state = static_cast<SessionAuthState>(err);
if (state == SessionAuthState::DENIED)
{
ssh_disconnect(p->session);
}
return state;
}
Channel Session::newChannel()
{
assert_ssh_session(p->session, "newChannel() -- Session is not allocated.");
Channel channel;
channel.p = new ChannelImpl(p->session);
return channel;
}
} // namespace rsm
#ifndef RSM_RSMCORE_SESSION_HH_
#define RSM_RSMCORE_SESSION_HH_
#include <QObject>
#include "rsmcore/declspec.hh"
#include "rsmcore/sessionlog.hh"
namespace rsm
{
enum class SessionAuthState
{
SUCCESS = 0,
DENIED,
PARTIAL,
INFO,
AGAIN,
ERROR = -1
};
enum class SessionAuthMethod
{
UNKNOWN = 0x0000u,
NONE = 0x0001u,
PASSWORD = 0x0002u,
PUBLICKEY = 0x0004u,
HOSTBASED = 0x0008u,
INTERACTIVE = 0x0010u,
GSSAPI_MIC = 0x0020u,
};
/**
* @brief The SessionHostState enum
*/
enum class SessionHostState
{ /**
* There had been an error checking the host.
*/
ERROR = -2,
/**
* The known host file does not exist. The host is thus unknown. File will
* be created if host key is accepted.
*/
NOT_FOUND = -1,
/**
* The server is unknown. User should confirm the public key hash is
* correct.
*/
UNKNOWN = 0,
/**
* The server is known and has not changed.
*/
OK,
/**
* The server key has changed. Either you are under attack or the
* administrator changed the key. You HAVE to warn the user about a
* possible attack.
*/
CHANGED,
/**
* The server gave use a key of a type while we had another type recorded.
* It is a possible attack.
*/
OTHER,
};
/** Forward declaration of private implementation */
class ChannelImpl;
class SessionImpl;
class Session;
class RSM_PUBLIC Channel
{
protected:
// Private implementation
ChannelImpl* p;
// constructor is protected to only be used by "friend Session"
Channel();
public:
~Channel();
/**
* @brief open Opens the channel for execution
* @return
*/
bool open();
/**
* @brief isOpen Checks if the channel is open for execution
* @return
*/
bool isOpen() const;
/**
* @brief close Close channel of execution
* Channel is no longer usable
* @return
*/
void close();
/**
* @brief exec Execute command on remote system
* @param command
* @return true on success, false on failure
*/
bool exec(QString command);
/**
* @brief readExecOut Read execution's standard output
* @return
*/
QString readExecOut();
/**
* @brief readExecErr Read execution's standard error
* @return
*/
QString readExecErr();
friend Session;
}; // class Channel
class RSM_PUBLIC Session
{
private:
// Private implementation
SessionImpl* p;
public:
/**
* Basic Constructor
*/
Session();
/**
* Contructs with host name
*/
Session(QString host);
~Session();
/**
* Set the remote host to connect to
*/
void setHost(QString host);
/**
* @brief host Get the host name of the session
*/
std::string host() const;
/**
* Set the Log verbosity
*/
void setLogVerbosity(SessionVerbosity level);
/**
* Set the ssh port number
*/
void setPort(int port);
/**
* Set the user
* Defaults to system user
*/
void setUser(QString name);
/**
* @brief user get the username
* @return
*/
std::string user() const;
/**
* Set the proxy command
* @param command
*/
void setProxyCommand(QString command);
/**
* Perform hand-shake to connect to host.
*/