mysqlshdk/libs/ssh/ssh_session.cc (691 lines of code) (raw):
/*
* Copyright (c) 2021, 2024, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms,
* as designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
*
* This program 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 General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "mysqlshdk/libs/ssh/ssh_session.h"
#include <fcntl.h>
#include <functional>
#include <sstream>
#include <vector>
#include "mysqlshdk/include/scripting/shexcept.h"
#include "mysqlshdk/include/shellcore/console.h"
#include "mysqlshdk/libs/utils/logger.h"
#include "mysqlshdk/libs/utils/utils_file.h"
#include "mysqlshdk/libs/utils/utils_general.h"
#include "mysqlshdk/libs/utils/utils_string.h"
#include "mysqlshdk/shellcore/credential_manager.h"
#ifdef _MSC_VER
#include "Shlobj.h"
#endif // _MSC_VER
namespace mysqlshdk {
namespace ssh {
namespace {
// lists of allowed SSH related algorithms (unaccetable ones explicitly omitted)
// according to policy
static const char k_kex_algorithm_allowed[] = {
"curve25519-sha256," // recommended
"ecdh-sha2-nistp384," // recommended
"rsa2048-sha256," // recommended
"ecdh-sha2-nistp256,"
"ecdh-sha2-nistp521,"
"curve448-sha512,"
"diffie-hellman-group16-sha512,"
"diffie-hellman-group18-sha512,"
"diffie-hellman-group14-sha256,"
"diffie-hellman-group15-sha512,"
"diffie-hellman-group17-sha512,"
"diffie-hellman-group-exchange-sha256,"
"gss-group14-sha256-,"
"gss-group16-sha512-,"
"gss-nistp256-sha256-,"
"gss-nistp384-sha384-,"
"gss-curve25519-sha256-"};
static const char k_mac_allowed[] = {
"hmac-sha2-384," // recommended
"hmac-sha384@ssh.com," // recommended (alt name)
"hmac-sha2-512-etm@openssh.com," // recommended
"hmac-sha2-256-etm@openssh.com," // recommended
"umac-128-etm@openssh.com,"
"hmac-sha2-512,"
"hmac-sha2-256,"
"umac-128@openssh.com"};
static const char k_host_key_allowed[] = {
"ssh-ed25519," // recommended
"ssh-ed25519-cert-v01@openssh.com," // recommended (alt name)
"ecdsa-sha2-nistp384," // recommended
"ecdsa-sha2-nistp384-cert-v01@openssh.com," // recommended (alt name)
"rsa-sha2-512," // recommended
"rsa-sha2-512-cert-v01@openssh.com," // recommended (alt name)
"ssh-ed448,"
"rsa-sha2-256,"
"rsa-sha2-256-cert-v01@openssh.com," // (alt name)
"ecdsa-sha2-nistp256,"
"ecdsa-sha2-nistp256-cert-v01@openssh.com," // (alt name)
"ecdsa-sha2-nistp521,"
"ecdsa-sha2-nistp521-cert-v01@openssh.com," // (alt name)
"x509v3-rsa2048-sha256,"
"x509v3-ecdsa-sha2-nistp256,"
"x509v3-ecdsa-sha2-nistp384,"
"x509v3-ecdsa-sha2-nistp521,"
"ssh-rsa" // (ssh2 version of ssh-rsa, not the one from ssh1)
};
static const char k_cipher_allowed[] = {
"chacha20-poly1305@openssh.com," // recommended
"aes256-gcm," // recommended
"AES256-Gcm@openssh.com," // recommended (alt name)
"AEAD_AES_256_GCM," // recommended (alt name)
"aes128-gcm," // recommended
"AES128-Gcm@openssh.com," // recommended (alt name)
"AEAD_AES_128_GCM," // recommended (alt name)
"aes256-ctr,"
"aes192-ctr,"
"aes128-ctr"};
static const char k_public_key_allowed[] = {
"ssh-ed25519,"
"ssh-ed25519-cert-v01@openssh.com,"
"ecdsa-sha2-nistp384,"
"ecdsa-sha2-nistp384-cert-v01@openssh.com,"
"rsa-sha2-512,"
"rsa-sha2-512-cert-v01@openssh.com,"
"ssh-ed448,"
"rsa-sha2-256,"
"rsa-sha2-256-cert-v01@openssh.com,"
"ecdsa-sha2-nistp256,"
"ecdsa-sha2-nistp256-cert-v01@openssh.com,"
"ecdsa-sha2-nistp521,"
"ecdsa-sha2-nistp521-cert-v01@openssh.com,"
"x509v3-rsa2048-sha256,"
"x509v3-ecdsa-sha2-nistp256,"
"x509v3-ecdsa-sha2-nistp384,"
"x509v3-ecdsa-sha2-nistp521,"
"ssh-rsa" // (ssh2 version of ssh-rsa, not the one from ssh1)
};
int libssh_auth_callback(const char *prompt, char *buf, size_t len, int echo,
int UNUSED(verify), void *userdata) {
std::string return_value;
if (echo == 1) {
if (mysqlsh::current_console()->prompt(prompt, &return_value) !=
shcore::Prompt_result::Ok)
return -1;
} else {
auto session = static_cast<Ssh_session *>(userdata);
auto &options = session->get_options();
if (strcmp(prompt, "Passphrase for private key:") == 0 &&
options.has_keyfile_password()) {
return_value = options.get_key_file_password();
} else if (mysqlsh::current_console()->prompt_password(
prompt, &return_value) != shcore::Prompt_result::Ok) {
return -1;
}
}
strncpy(buf, return_value.data(),
return_value.size() > len ? len : return_value.size());
shcore::clear_buffer(return_value);
return 0;
}
} // namespace
Ssh_session::Ssh_session()
: m_session{std::make_unique<::ssh::Session>()},
m_is_connected(false),
m_interactive(true) {
init_libssh();
memset(&m_ssh_callbacks, 0, sizeof(m_ssh_callbacks));
m_ssh_callbacks.userdata = this;
m_ssh_callbacks.auth_function = libssh_auth_callback;
ssh_callbacks_init(&m_ssh_callbacks);
auto rc = ssh_set_callbacks(m_session->getCSession(), &m_ssh_callbacks);
if (rc == SSH_ERROR) throw std::runtime_error(m_session->getError());
}
Ssh_session::~Ssh_session() {}
std::tuple<Ssh_return_type, std::string> Ssh_session::connect(
const Ssh_connection_options &config) {
if (is_connected()) {
throw std::logic_error(
"Unable to connect already connected SSHSession, please disconnect "
"first.");
}
// auto lock = lock_session();
m_options = config;
m_interactive = m_options.interactive();
// We need to set the host before reading the config, otherwise we will get
// error. This will be of course overridden by optionsParseconfig
try {
m_session->setOption(SSH_OPTIONS_HOST, m_options.get_host().c_str());
} catch (::ssh::SshException &exc) {
log_error(
"SSH: session: Error setting remote host in ssh session option: %s",
exc.getError().c_str());
return std::make_tuple(Ssh_return_type::INVALID_AUTH_DATA, exc.getError());
}
// The default timeout of 10 seconds is set here, if different value is
// needed, it should be set in the config file
try {
m_session->setOption(SSH_OPTIONS_TIMEOUT,
static_cast<long>(m_options.get_connection_timeout()));
} catch (::ssh::SshException &exc) {
log_error(
"SSH: session: Error setting connection timeout in ssh session option: "
"%s",
exc.getError().c_str());
return std::make_tuple(Ssh_return_type::INVALID_AUTH_DATA, exc.getError());
}
// We've already loaded the config earlier
// but it wasn't full data, here we do this again, overwriting what we
// already have from the user and leave the rest like ciphers etc
try {
m_session->optionsParseConfig(m_options.has_config_file()
? m_options.get_config_file().c_str()
: nullptr);
Ssh_config_reader reader;
reader.read(&m_config, m_options.get_host(),
m_options.has_config_file() ? m_options.get_config_file() : "");
} catch (::ssh::SshException &exc) {
if (m_options.has_config_file()) {
log_error("SSH: session: Unable to parse config file: %s\nError was: %s ",
m_options.get_config_file().c_str(), exc.getError().c_str());
} else {
log_error(
"SSH: session: Unable to parse default config file\nError was: %s ",
exc.getError().c_str());
}
}
// The option setter does't indicate what option failed to set, so we have to
// wrap each call individually to be able to report exactly what failed.
if (m_options.has_user()) {
try {
m_session->setOption(SSH_OPTIONS_USER, m_options.get_user().c_str());
} catch (::ssh::SshException &exc) {
log_error(
"SSH: session: Error setting user name in ssh session option: %s",
exc.getError().c_str());
return std::make_tuple(Ssh_return_type::INVALID_AUTH_DATA,
exc.getError());
}
}
if (m_options.has_key_file()) {
try {
m_session->setOption(SSH_OPTIONS_IDENTITY,
m_options.get_key_file().c_str());
} catch (::ssh::SshException &exc) {
log_error(
"SSH: session: Error setting identity file in ssh session option: %s",
exc.getError().c_str());
return std::make_tuple(Ssh_return_type::INVALID_AUTH_DATA,
exc.getError());
}
}
if (m_options.has_port()) {
try {
m_session->setOption(SSH_OPTIONS_PORT,
static_cast<long>(m_options.get_port()));
} catch (::ssh::SshException &exc) {
log_error(
"SSH: session: Error setting remote port in ssh session option: %s",
exc.getError().c_str());
return std::make_tuple(Ssh_return_type::INVALID_AUTH_DATA,
exc.getError());
}
}
set_algorithm_configs();
m_options.dump_config();
try {
m_session->connect();
} catch (::ssh::SshException &exc) {
log_error(
"SSH: session: Unable to establish SSH connection: %s:%d\nError was: "
"%s",
m_options.get_host().c_str(), m_options.get_port(),
exc.getError().c_str());
return std::make_tuple(Ssh_return_type::CONNECTION_FAILURE, exc.getError());
}
std::string fingerprint;
int ret_val = verify_known_host(m_options, &fingerprint);
switch (ret_val) {
case SSH_SERVER_FILE_NOT_FOUND:
case SSH_SERVER_NOT_KNOWN:
return std::make_tuple(
ret_val == SSH_SERVER_FILE_NOT_FOUND
? Ssh_return_type::FINGERPRINT_UNKNOWN_AUTH_FILE_MISSING
: Ssh_return_type::FINGERPRINT_UNKNOWN,
fingerprint);
case SSH_SERVER_KNOWN_CHANGED:
return std::make_tuple(Ssh_return_type::FINGERPRINT_CHANGED, fingerprint);
case SSH_SERVER_FOUND_OTHER:
return std::make_tuple(Ssh_return_type::FINGERPRINT_MISMATCH,
fingerprint);
default:
break;
}
try {
authenticate_user(m_options);
} catch (Ssh_auth_exception &sxc) {
log_error("SSH: session: User authentication failed.");
return std::make_tuple(Ssh_return_type::INVALID_AUTH_DATA,
std::string(sxc.what()));
}
m_is_connected = true;
m_time_created = std::chrono::system_clock::now();
return std::make_tuple(Ssh_return_type::CONNECTED, "");
}
void Ssh_session::disconnect() {
log_debug2("SSH: session: disconnect");
if (m_session != nullptr) {
if (m_is_connected) m_session->disconnect();
m_session = std::make_unique<::ssh::Session>();
}
// Now we should release the lock.
m_is_connected = false;
}
bool Ssh_session::is_connected() const {
return m_is_connected && ssh_is_connected(m_session->getCSession());
}
void Ssh_session::set_algorithm_configs() {
auto set_option = [this](ssh_options_e option, const char *what,
const char *value) {
try {
m_session->setOption(option, value);
} catch (::ssh::SshException &exc) {
// not expected to happen
log_error(
"SSH: session: Error setting allowed %s list for ssh session: %s",
what, exc.getError().c_str());
throw;
}
};
set_option(SSH_OPTIONS_CIPHERS_C_S, "ciphers", k_cipher_allowed);
set_option(SSH_OPTIONS_CIPHERS_S_C, "ciphers", k_cipher_allowed);
set_option(SSH_OPTIONS_KEY_EXCHANGE, "key exchange algorithm",
k_kex_algorithm_allowed);
set_option(SSH_OPTIONS_HMAC_C_S, "mac algorithm", k_mac_allowed);
set_option(SSH_OPTIONS_HMAC_S_C, "mac algorithm", k_mac_allowed);
set_option(SSH_OPTIONS_HOSTKEYS, "hostkey types", k_host_key_allowed);
set_option(SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, "pubkey types",
k_public_key_allowed);
}
const Ssh_connection_options &Ssh_session::config() const { return m_options; }
void Ssh_session::update_local_port(int port) {
m_options.set_local_port(port);
}
bool Ssh_session::open_channel(::ssh::Channel *chann) {
int rc = SSH_ERROR;
std::size_t i = 0;
while (i < m_options.get_connection_timeout()) {
rc = ssh_channel_open_session(chann->getCChannel());
// We can't rely on rc == 0 as there's possibility it will return 0 even
// that the channel is closed. Because of this, we have to use isOpen()
// and try few times
if (rc == SSH_AGAIN || !chann->isOpen()) {
log_debug3(
"SSH: session: Unable to open channel, wait a moment and retry.");
i++;
std::this_thread::sleep_for(std::chrono::seconds(1));
} else if (rc == SSH_ERROR) {
log_error("SSH: session: Unable to open channel: %s",
ssh_get_error(chann->getCSession()));
return false;
} else {
log_debug("SSH: session: Channel successfully opened");
return true;
}
}
return false;
}
void Ssh_session::clean_connect() {
if (!ssh_is_connected(m_session->getCSession())) {
disconnect();
connect(m_options);
}
}
const char *Ssh_session::get_ssh_error() {
if (m_session) {
return m_session->getError();
}
return nullptr;
}
ssh_session Ssh_session::get_csession() {
if (m_session) {
return m_session->getCSession();
}
throw std::logic_error(
"Trying to access SSH session which should be available but it's not");
}
std::unique_ptr<::ssh::Channel> Ssh_session::create_channel() {
return std::make_unique<::ssh::Channel>(*(m_session));
}
int Ssh_session::verify_known_host(const ssh::Ssh_connection_options &config,
std::string *fingerprint) {
std::unique_ptr<unsigned char, void (*)(unsigned char *)> hash(
nullptr, [](unsigned char *v) {
if (v != nullptr) ssh_clean_pubkey_hash(&v);
});
ssh_key srv_pub_key;
int rc = 0;
std::size_t hlen = 0;
errno = 0;
rc = ssh_get_server_publickey(m_session->getCSession(), &srv_pub_key);
if (rc < 0)
throw Ssh_tunnel_exception("Can't get server pubkey " + get_error());
unsigned char *hash_ptr = nullptr;
errno = 0;
rc = ssh_get_publickey_hash(srv_pub_key, SSH_PUBLICKEY_HASH_SHA256, &hash_ptr,
&hlen);
ssh_key_free(srv_pub_key);
if (rc < 0)
throw Ssh_tunnel_exception("Can't calculate pubkey hash " + get_error());
hash.reset(hash_ptr);
std::unique_ptr<char, void (*)(char *)> hexa(ssh_get_hexa(hash.get(), hlen),
[](char *ptr) { free(ptr); });
*fingerprint = hexa.get();
int ret_val = m_session->isServerKnown();
switch (ret_val) {
case SSH_SERVER_FILE_NOT_FOUND:
case SSH_SERVER_NOT_KNOWN: {
if (config.get_fingerprint().empty()) {
return ret_val;
} else {
if (config.get_fingerprint() == hexa.get()) {
m_session->writeKnownhost();
return SSH_SERVER_KNOWN_OK;
} else {
return ret_val;
}
}
}
case SSH_SERVER_KNOWN_OK:
case SSH_SERVER_KNOWN_CHANGED:
case SSH_SERVER_FOUND_OTHER:
return ret_val;
case SSH_SERVER_ERROR:
throw Ssh_tunnel_exception(m_session->getError());
}
return SSH_SERVER_KNOWN_OK;
}
namespace {
Ssh_auth_return try_auth(const std::function<Ssh_auth_return()> &fn) {
Ssh_auth_return ret;
int attempt = 0;
do {
ret = fn();
if (ret != Ssh_auth_return::AUTH_DENIED) break;
attempt++;
} while (attempt < 3);
return ret;
}
} // namespace
void Ssh_session::authenticate_user(const ssh::Ssh_connection_options &config) {
try {
if (m_session->userauthNone() == SSH_AUTH_SUCCESS) {
log_debug("SSH: session: Server accepts \"none\" auth method");
return;
}
log_warning(
"SSH: session: Failed authenticating with the \"none\" authentication "
"method");
} catch (::ssh::SshException &) {
throw Ssh_tunnel_exception(ssh_get_error(m_session->getCSession()));
}
auto config_copy = config;
int auth_list = m_session->getAuthList();
ssh_set_blocking(m_session->getCSession(), 1);
shcore::Scoped_callback scope(
[this] { ssh_set_blocking(m_session->getCSession(), 0); });
Ssh_auth_return ret_val = Ssh_auth_return::AUTH_NONE;
if (auth_list == 0)
throw std::runtime_error(
"Unable to authenticate, no known authentication methods.");
if (ret_val != Ssh_auth_return::AUTH_SUCCESS &&
(auth_list & SSH_AUTH_METHOD_PUBLICKEY) &&
m_config.auth_methods.is_set(Ssh_auth_methods::PUBKEY_AUTH)) {
if (m_options.has_key_file()) {
if (config_copy.get_key_encrypted()) {
ret_val =
try_auth([this, &config_copy]() { return auth_key(&config_copy); });
} else {
ret_val = auth_key(&config_copy);
}
if (ret_val != Ssh_auth_return::AUTH_SUCCESS) {
log_warning(
"SSH: session: Failed authenticating using the provided identity "
"file");
}
} else {
ret_val = auth_auto_pubkey();
if (ret_val != Ssh_auth_return::AUTH_SUCCESS) {
log_warning(
"SSH: session: Failed authenticating using the default identity "
"files");
}
}
}
if (ret_val != Ssh_auth_return::AUTH_SUCCESS &&
(auth_list & SSH_AUTH_METHOD_PASSWORD) &&
m_config.auth_methods.is_set(Ssh_auth_methods::PASSWORD_AUTH)) {
ret_val = try_auth(
[this, &config_copy]() { return auth_password(&config_copy); });
if (ret_val != Ssh_auth_return::AUTH_SUCCESS) {
log_warning("SSH: session: Failed authenticating using user/password");
}
}
if (ret_val != Ssh_auth_return::AUTH_SUCCESS &&
(auth_list & SSH_AUTH_METHOD_INTERACTIVE) &&
m_config.auth_methods.is_set(Ssh_auth_methods::KBDINT_AUTH)) {
if (!m_interactive)
throw std::runtime_error(
"Interactive auth mode is disabled when in disabled wizards mode.");
ret_val = auth_interactive();
if (ret_val != Ssh_auth_return::AUTH_SUCCESS) {
log_warning(
"SSH: session: Failed authenticating using interactive method");
}
}
if (ret_val != Ssh_auth_return::AUTH_SUCCESS) {
if (ret_val == Ssh_auth_return::AUTH_DENIED) {
throw Ssh_auth_exception("Access denied");
} else if (ret_val != Ssh_auth_return::AUTH_NONE) {
throw std::runtime_error(
"Unable to authenticate, all known authentication methods failed.");
} else {
throw std::runtime_error(
"Unable to authenticate, no compatible authentication methods "
"found.");
}
}
}
namespace {
int call_auth_in_a_loop(::ssh::Session *session,
const std::function<int()> &c) {
try {
int ret = 0;
do {
ret = c();
if (ret == SSH_AUTH_AGAIN) {
// There's a bug in libssh that it returns SSH_AUTH_AGAIN
// in blocking mode even while it should return SSH_DENIED;
// This has to be removed, once libssh bug will be fixed.
if (!ssh_is_connected(session->getCSession()))
throw std::runtime_error("Unable to authenticate, " +
std::string(session->getError()));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
} while (ret == SSH_AUTH_AGAIN);
return ret;
} catch (::ssh::SshException &sxc) {
throw Ssh_auth_exception("Authentication failed: " + sxc.getError());
}
}
} // namespace
Ssh_auth_return Ssh_session::auth_agent(
const ssh::Ssh_connection_options &config) {
log_debug3("SSH trying auth_agent");
#ifdef _WIN32
throw std::logic_error(
"Ssh-agent functionality is not supported on this OS.");
#else
return handle_auth_return(
call_auth_in_a_loop(m_session.get(), [this, config]() {
return ssh_userauth_agent(m_session->getCSession(), nullptr);
}));
#endif
}
Ssh_auth_return Ssh_session::auth_password(
ssh::Ssh_connection_options *config) {
Ssh_auth_return ret_val = ssh::Ssh_auth_return::AUTH_NONE;
log_debug3("SSH trying auth_pass");
// here we need to take a pw from the user
bool from_credential_manager = false;
if (!config->has_password()) {
bool ret = shcore::Credential_manager::get().get_password(config);
if (ret && config->has_password()) from_credential_manager = true;
if (!config->has_password()) {
std::string passphrase;
if (m_interactive) {
if (mysqlsh::current_console()->prompt_password(
"Please provide the password for " + config->as_uri() + ": ",
&passphrase) == shcore::Prompt_result::Cancel)
throw shcore::cancelled("User cancelled");
}
config->set_password(passphrase);
shcore::clear_buffer(passphrase);
}
}
if (config->has_password()) {
ret_val = handle_auth_return(
call_auth_in_a_loop(m_session.get(), [this, config]() {
return m_session->userauthPassword(config->get_password().c_str());
}));
if (ret_val == Ssh_auth_return::AUTH_DENIED) {
bool had_password = config->has_password();
config->clear_password();
std::string error_msg =
"SSH Access denied, connection to \"" +
config->as_uri(mysqlshdk::db::uri::formats::user_transport()) +
"\" could not be established.";
if (from_credential_manager) {
shcore::Credential_manager::get().remove_password(*config);
error_msg.append(" Invalid password has been erased.");
}
mysqlsh::current_console()->print_error(error_msg);
// We throw exception in case it was given as a part of uri
// so the outer loop won't try to Auth using credential store.
if (had_password) throw Ssh_auth_exception("Access denied");
} else {
if (!from_credential_manager)
shcore::Credential_manager::get().save_password(*config);
}
} else {
throw Ssh_auth_exception("Access denied");
}
return ret_val;
}
Ssh_auth_return Ssh_session::auth_auto_pubkey() {
log_debug3("SSH trying auth_auto");
return handle_auth_return(call_auth_in_a_loop(m_session.get(), [this]() {
return m_session->userauthPublickeyAuto();
}));
}
Ssh_auth_return Ssh_session::auth_key(ssh::Ssh_connection_options *config) {
log_debug3("SSH trying auth_key");
ssh_key priv_key;
if (!shcore::is_file(config->get_key_file()))
throw std::runtime_error("The key file does not exist.");
std::string keypass;
bool got_password = false;
bool from_manager = false;
if (config->get_key_encrypted()) {
if (config->has_keyfile_password()) {
keypass = config->get_key_file_password();
} else {
try {
got_password = shcore::Credential_manager::get().get_credential(
config->key_file_uri(), &keypass);
from_manager = true;
} catch (shcore::Exception &re) {
mysqlsh::current_console()->print_error(
"Unable to get credentials for " + config->key_file_uri() +
" error was " + re.what());
}
if (!got_password && m_interactive &&
(mysqlsh::current_console()->prompt_password(
"Please provide key passphrase for " + config->get_key_file() +
": ",
&keypass) == shcore::Prompt_result::Cancel)) {
throw shcore::cancelled("User cancelled");
}
}
}
auto ret = ssh_pki_import_privkey_file(config->get_key_file().c_str(),
keypass.c_str(), libssh_auth_callback,
nullptr, &priv_key);
if (ret == SSH_OK) {
shcore::Scoped_callback scope([&priv_key] { ssh_key_free(priv_key); });
auto auth_info = call_auth_in_a_loop(m_session.get(), [this, priv_key]() {
return m_session->userauthPublickey(priv_key);
});
if ((auth_info == SSH_AUTH_SUCCESS || auth_info == SSH_AUTH_PARTIAL) &&
!keypass.empty()) {
config->set_key_file_password(keypass);
try {
if (!got_password &&
shcore::Credential_manager::get().should_save_password(
config->key_file_uri()))
shcore::Credential_manager::get().store_credential(
config->key_file_uri(), keypass);
} catch (shcore::Exception &re) {
mysqlsh::current_console()->print_error(
"An error occurred while storing the passphrase for the SSH "
"identity file: " +
std::string(re.what()));
}
}
return handle_auth_return(auth_info);
} else {
log_error("SSH: session: %s", m_session->getError());
// else is SSH_EOF according to docs it means, missing keyfile or
// permission denied either way we move on
if (shcore::is_file(config->get_key_file())) {
if (from_manager && got_password) {
shcore::Credential_manager::get().delete_credential(
config->key_file_uri());
mysqlsh::current_console()->print_error(
"SSH Access denied, connection to \"" +
config->as_uri(mysqlshdk::db::uri::formats::user_transport()) +
"\" could not be established. Invalid password has been erased.");
}
return Ssh_auth_return::AUTH_DENIED;
} else {
throw std::invalid_argument("The file " + config->get_key_file() +
" doesn't exist.");
}
}
}
namespace {
shcore::Prompt_result prompt_user(const std::string &prompt, std::string *reply,
bool echo) {
return echo ? mysqlsh::current_console()->prompt(prompt, reply)
: mysqlsh::current_console()->prompt_password(prompt, reply);
}
} // namespace
Ssh_auth_return Ssh_session::auth_interactive() {
log_debug3("SSH trying auth_interactive");
Ssh_auth_return ret_val = Ssh_auth_return::AUTH_NONE;
while (m_session->userauthKbdint(nullptr, nullptr) == SSH_AUTH_INFO) {
for (auto i = 0; i < m_session->userauthKbdintGetNPrompts(); i++) {
char echo;
auto prompt =
ssh_userauth_kbdint_getprompt(m_session->getCSession(), i, &echo);
if (prompt == nullptr) {
throw Ssh_auth_exception("Unknown authentication error: " +
std::string(m_session->getError()));
}
std::string reply;
if (prompt_user(prompt, &reply, echo) == shcore::Prompt_result::Cancel) {
throw shcore::cancelled("User cancelled");
}
shcore::on_leave_scope cleanup([&reply]() {
if (!reply.empty()) shcore::clear_buffer(reply);
});
try {
if (m_session->userauthKbdintSetAnswer(i, reply.c_str()) == 0) {
ret_val = Ssh_auth_return::AUTH_SUCCESS;
} else {
throw Ssh_auth_exception("An error occurred: " +
std::string(m_session->getError()));
}
} catch (::ssh::SshException &e) {
throw Ssh_auth_exception(e.getError());
}
}
}
return ret_val;
}
Ssh_auth_return Ssh_session::handle_auth_return(int auth) {
switch (auth) {
case SSH_AUTH_DENIED:
return Ssh_auth_return::AUTH_DENIED;
case SSH_AUTH_SUCCESS:
return Ssh_auth_return::AUTH_SUCCESS;
case SSH_AUTH_PARTIAL:
return Ssh_auth_return::AUTH_PARTIAL;
case SSH_AUTH_INFO:
return Ssh_auth_return::AUTH_INFO;
default:
if (!ssh_is_connected(m_session->getCSession()))
throw std::runtime_error("Unable to authenticate, " +
std::string(m_session->getError()));
throw Ssh_auth_exception("Unknown authentication error (" +
std::to_string(auth) + ").");
}
}
} // namespace ssh
} // namespace mysqlshdk