Commit dbcdb221 authored by Matthieu Kermagoret's avatar Matthieu Kermagoret
Browse files

Connector SSH: on its way to work !

parent 4434d1af
......@@ -118,6 +118,12 @@ else ()
endif ()
endif ()
# Should the connector perform check against known_hosts file ?
option(WITH_KNOWN_HOSTS_CHECK "Check hosts against user's known_hosts file." OFF)
if (WITH_KNOWN_HOSTS_CHECK)
add_definitions(-DWITH_KNOWN_HOSTS_CHECK)
endif ()
# SSH connector library.
set(CONNECTORLIB "centreonconnectorssh")
add_library("${CONNECTORLIB}"
......@@ -352,5 +358,10 @@ message(STATUS "")
message(STATUS " Project ${PROJECT_NAME}")
message(STATUS " - Version ${CONNECTOR_SSH_VERSION}")
message(STATUS " - Extra compilation flags ${CMAKE_CXX_FLAGS}")
if (WITH_KNOWN_HOSTS_CHECK)
message(STATUS " - Known hosts check enabled")
else ()
message(STATUS " - Known hosts check disabled")
endif ()
message(STATUS " - Build unit tests ${BUILD_UNIT_TEST}")
message(STATUS "")
......@@ -59,8 +59,7 @@ namespace sessions {
private:
enum e_step {
session_connect = 0,
session_startup,
session_startup = 0,
session_password,
session_key,
session_keepalive
......@@ -68,7 +67,6 @@ namespace sessions {
session(session const& s);
session& operator=(session const& s);
void _connect();
void _key();
void _nop();
void _passwd();
......
......@@ -48,6 +48,11 @@ check::check()
* Destructor.
*/
check::~check() throw () {
// Send result if we haven't already done so.
result r;
r.set_command_id(_cmd_id);
_send_result_and_unregister(r);
if (_channel) {
// Close channel.
while (libssh2_channel_close(_channel) == LIBSSH2_ERROR_EAGAIN)
......@@ -146,6 +151,9 @@ void check::on_connected(sessions::session& sess) {
multiplexer::instance().handle_manager::add(
sess.get_socket_handle(),
this);
logging::debug(logging::low) << "manually starting check "
<< _cmd_id;
read(*sess.get_socket_handle());
return ;
}
......@@ -158,24 +166,38 @@ void check::read(handle& h) {
try {
switch (_step) {
case chan_open:
logging::info(logging::low)
<< "attempting to open channel for check " << _cmd_id;
if (!_open()) {
logging::info(logging::low) << "check " << _cmd_id
<< " channel was successfully opened";
_step = chan_exec;
read(h);
}
break ;
case chan_exec:
logging::info(logging::low)
<< "attempting to execute check " << _cmd_id;
if (!_exec()) {
logging::info(logging::low)
<< "check " << _cmd_id << " was successfully executed";
_step = chan_read;
read(h);
}
break ;
case chan_read:
logging::info(logging::low)
<< "reading check " << _cmd_id << " result from channel";
if (!_read()) {
logging::info(logging::low) << "result of check "
<< _cmd_id << " was successfully fetched";
_step = chan_close;
read(h);
}
break ;
case chan_close:
logging::info(logging::low) << "attempting to close check "
<< _cmd_id << " channel";
_close();
break ;
default:
......@@ -434,6 +456,8 @@ bool check::_read() {
*/
void check::_send_result_and_unregister(result const& r) {
// Unregister from multiplexer.
logging::debug(logging::low) << "check " << this
<< " is unregistering from multiplexer";
multiplexer::instance().handle_manager::remove(this);
// Unregister from session.
......
......@@ -19,6 +19,7 @@
*/
#include <errno.h>
#include <libssh2.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
......@@ -65,12 +66,19 @@ int main() {
multiplexer::load();
logging::file log_file(stdout);
logging::engine::instance().add(&log_file, -1, 63);
#if LIBSSH2_VERSION_NUM >= 0x010205
// Initialize libssh2.
logging::info(logging::medium) << "initializing libssh2";
if (libssh2_init(0))
throw (basic_error() << "libssh2 initialization failed");
{
char const* version(libssh2_version(LIBSSH2_VERSION_NUM));
if (!version)
throw (basic_error() << "libssh2 version is too old (>= "
<< LIBSSH2_VERSION << " required)");
logging::debug(logging::high) << "libssh2 version "
<< version << " successfully loaded";
}
#endif /* libssh2 version >= 1.2.5 */
// Set termination handler.
......
......@@ -92,8 +92,8 @@ policy::~policy() throw () {
* Called if stdin is closed.
*/
void policy::on_eof() {
logging::info(logging::high) << "stdin is closed, exiting";
should_exit = true;
logging::info(logging::high) << "stdin is closed";
on_quit();
return ;
}
......@@ -102,8 +102,8 @@ void policy::on_eof() {
*/
void policy::on_error() {
logging::info(logging::high)
<< "error occurred while parsing stdin, exiting";
should_exit = true;
<< "error occurred while parsing stdin";
on_quit();
return ;
}
......@@ -124,31 +124,42 @@ void policy::on_execute(
std::string const& user,
std::string const& password,
std::string const& cmd) {
// Credentials.
sessions::credentials creds;
creds.set_host(host);
creds.set_user(user);
creds.set_password(password);
// Find session.
std::map<sessions::credentials, sessions::session*>::iterator it;
it = _sessions.find(creds);
if (it == _sessions.end()) {
logging::info(logging::low) << "creating session for "
<< user << "@" << host;
std::auto_ptr<sessions::session> sess(new sessions::session(creds));
sess->connect();
_sessions[creds] = sess.get();
sess.release();
try {
// Credentials.
sessions::credentials creds;
creds.set_host(host);
creds.set_user(user);
creds.set_password(password);
// Find session.
std::map<sessions::credentials, sessions::session*>::iterator it;
it = _sessions.find(creds);
}
if (it == _sessions.end()) {
logging::info(logging::low) << "creating session for "
<< user << "@" << host;
std::auto_ptr<sessions::session> sess(new sessions::session(creds));
sess->connect();
_sessions[creds] = sess.get();
sess.release();
it = _sessions.find(creds);
}
// Launch check.
std::auto_ptr<checks::check> chk(new checks::check);
chk->listen(this);
chk->execute(*it->second, cmd_id, cmd, timeout);
_checks[cmd_id] = chk.get();
chk.release();
// Launch check.
std::auto_ptr<checks::check> chk(new checks::check);
chk->listen(this);
chk->execute(*it->second, cmd_id, cmd, timeout);
_checks[cmd_id] = chk.get();
chk.release();
}
catch (std::exception const& e) {
logging::error(logging::high) << "could not launch check ID "
<< cmd_id << " on host " << host << " because an error occurred: "
<< e.what();
}
catch (...) {
logging::error(logging::high) << "could not launch check ID "
<< cmd_id << " on host " << host << " because an error occurred";
}
return ;
}
......@@ -208,6 +219,14 @@ void policy::run() {
while (!should_exit)
multiplexer::instance().multiplex();
// Run as long as a check remains.
logging::info(logging::high) << "waiting for checks to terminate";
while (!_checks.empty())
multiplexer::instance().multiplex();
// Run as long as some data remains.
// XXX
return ;
}
......
......@@ -18,7 +18,6 @@
** <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
......@@ -54,7 +53,13 @@ using namespace com::centreon::connector::ssh::sessions;
session::session(credentials const& creds)
: _creds(creds),
_session(NULL),
_step(session_connect) {}
_step(session_startup) {
// Create session instance.
_session = libssh2_session_init();
if (!_session)
throw (basic_error()
<< "SSH session creation failed (out of memory ?)");
}
/**
* Destructor.
......@@ -64,6 +69,13 @@ session::~session() throw () {
this->close();
}
catch (...) {}
// Delete session.
libssh2_session_set_blocking(_session, 1);
libssh2_session_disconnect(
_session,
"Centreon Connector SSH shutdown");
libssh2_session_free(_session);
}
/**
......@@ -82,14 +94,6 @@ void session::close() {
++it)
(*it)->on_close(*this);
// Delete session.
libssh2_session_set_blocking(_session, 1);
libssh2_session_disconnect(
_session,
"Centreon Connector SSH shutdown");
libssh2_session_free(_session);
_session = NULL;
// Close socket.
_socket.close();
......@@ -121,19 +125,22 @@ void session::connect() {
}
// Step.
_step = session_connect;
_step = session_startup;
// Host pointer.
char const* host_ptr(_creds.get_host().c_str());
// Host lookup.
logging::info(logging::low) << "looking up address " << host_ptr;
sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
{
// Try to avoid DNS lookup.
in_addr_t addr(inet_addr(host_ptr));
if (addr != (in_addr_t)-1)
if (addr != (in_addr_t)-1) {
logging::debug(logging::low) << "host_ptr is an IP address";
sin.sin_addr.s_addr = addr;
}
// DNS lookup.
else {
// IPv4 address lookup only.
......@@ -142,10 +149,11 @@ void session::connect() {
hint.ai_family = AF_INET;
hint.ai_socktype = SOCK_STREAM;
addrinfo* res;
int retval(getaddrinfo(host_ptr,
NULL,
&hint,
&res));
int retval(getaddrinfo(
host_ptr,
NULL,
&hint,
&res));
if (retval)
throw (basic_error() << "lookup of host '" << host_ptr
<< "' failed: " << gai_strerror(retval));
......@@ -153,8 +161,13 @@ void session::connect() {
throw (basic_error() << "no IPv4 address found for host '"
<< host_ptr << "'");
// Log message.
logging::debug(logging::low) << "found host " << host_ptr
<< " address through name resolution";
// Get address.
sin.sin_addr.s_addr = ((sockaddr_in*)(res->ai_addr))->sin_addr.s_addr;
sin.sin_addr.s_addr
= ((sockaddr_in*)(res->ai_addr))->sin_addr.s_addr;
// Free result.
freeaddrinfo(res);
......@@ -203,6 +216,12 @@ void session::connect() {
// Register with multiplexer.
multiplexer::instance().handle_manager::add(&_socket, this);
// Launch the connection process.
logging::debug(logging::medium)
<< "manually launching the connection process of session "
<< _creds.get_user() << "@" << _creds.get_host();
_startup();
return ;
}
......@@ -264,7 +283,6 @@ void session::listen(listener* listnr) {
void session::read(handle& h) {
(void)h;
static void (session::* const redirector[])() = {
&session::_connect,
&session::_startup,
&session::_passwd,
&session::_key,
......@@ -280,7 +298,11 @@ void session::read(handle& h) {
* @param[in] listnr Listener to remove.
*/
void session::unlisten(listener* listnr) {
std::remove(_listnrs.begin(), _listnrs.end(), listnr);
unsigned int size(_listnrs.size());
_listnrs.remove(listnr);
logging::debug(logging::low) << "session " << this
<< " is removing listener " << listnr << " (there was "
<< size << ", there is " << _listnrs.size() << ")";
return ;
}
......@@ -351,30 +373,15 @@ session& session::operator=(session const& s) {
return (*this);
}
/**
* @brief Perform connection operations.
*
* Once this method is called, this means that socket was successfully
* connected to remote host.
*/
void session::_connect() {
// Create session instance.
_session = libssh2_session_init();
if (!_session)
throw (basic_error()
<< "SSH session creation failed (out of memory ?)");
// Launch session startup.
_step = session_startup;
_startup();
return ;
}
/**
* Attempt public key authentication.
*/
void session::_key() {
// Log message.
logging::info(logging::low)
<< "launching key-based authentication on session"
<< _creds.get_user() << "@" << _creds.get_host();
// Get home directory.
passwd* pw(getpwuid(getuid()));
......@@ -391,7 +398,8 @@ void session::_key() {
pub.append(".ssh/id_rsa.pub");
// Try public key authentication.
int retval(libssh2_userauth_publickey_fromfile(_session,
int retval(libssh2_userauth_publickey_fromfile(
_session,
_creds.get_user().c_str(),
pub.c_str(),
priv.c_str(),
......@@ -401,6 +409,11 @@ void session::_key() {
throw (basic_error() << "user authentication failed");
}
else {
// Log message.
logging::info(logging::medium)
<< "successful key-based authentication on session "
<< _creds.get_user() << "@" << _creds.get_host();
// Enable non-blocking mode.
libssh2_session_set_blocking(_session, 0);
......@@ -428,8 +441,14 @@ void session::_nop() {
* Try password authentication.
*/
void session::_passwd() {
// Log message.
logging::info(logging::low)
<< "launching password-based authentication on session "
<< _creds.get_user() << "@" << _creds.get_host();
// Try password.
int retval(libssh2_userauth_password(_session,
int retval(libssh2_userauth_password(
_session,
_creds.get_user().c_str(),
_creds.get_password().c_str()));
if (retval != 0) {
......@@ -440,6 +459,9 @@ void session::_passwd() {
&& (retval != LIBSSH2_ERROR_ALLOC)
&& (retval != LIBSSH2_ERROR_SOCKET_SEND)) {
#endif /* libssh2 version >= 1.2.3 */
logging::info(logging::low)
<< "could not authenticate with password on session "
<< _creds.get_user() << "@" << _creds.get_host();
_step = session_key;
_key();
}
......@@ -451,6 +473,11 @@ void session::_passwd() {
}
}
else {
// Log message.
logging::info(logging::medium)
<< "successful password authentication on session "
<< _creds.get_user() << "@" << _creds.get_host();
// Enable non-blocking mode.
libssh2_session_set_blocking(_session, 0);
......@@ -470,6 +497,10 @@ void session::_passwd() {
* Perform SSH connection startup.
*/
void session::_startup() {
// Log message.
logging::info(logging::low) << "attempting to initialize SSH session "
<< _creds.get_user() << "@" << _creds.get_host();
// Exchange banners, keys, setup crypto, compression, ...
int retval(libssh2_session_startup(
_session,
......@@ -483,6 +514,12 @@ void session::_startup() {
}
}
else { // Successful startup.
// Log message.
logging::info(logging::medium) << "SSH session "
<< _creds.get_user() << "@" << _creds.get_host()
<< " successfully initialized";
#ifdef WITH_KNOWN_HOSTS_CHECK
// Initialize known hosts list.
LIBSSH2_KNOWNHOSTS* known_hosts(libssh2_knownhost_init(_session));
if (!known_hosts) {
......@@ -502,68 +539,86 @@ void session::_startup() {
known_hosts_file.append("/.ssh/");
}
known_hosts_file.append("known_hosts");
libssh2_knownhost_readfile(known_hosts,
known_hosts_file.c_str(),
LIBSSH2_KNOWNHOST_FILE_OPENSSH);
int rh(libssh2_knownhost_readfile(
known_hosts,
known_hosts_file.c_str(),
LIBSSH2_KNOWNHOST_FILE_OPENSSH));
if (rh < 0)
throw (basic_error() << "parsing of known_hosts file "
<< known_hosts_file << " failed: error " << -rh);
else
logging::info(logging::low) << rh
<< " hosts found in known_hosts file " << known_hosts_file;
// Check host fingerprint against known hosts.
{
// Get peer fingerprint.
size_t len;
int type;
char const* fingerprint(libssh2_session_hostkey(_session,
&len,
&type));
if (!fingerprint) {
char* msg;
libssh2_session_last_error(_session, &msg, NULL, 0);
libssh2_knownhost_free(known_hosts);
throw (basic_error()
<< "failed to get remote host fingerprint: " << msg);
}
// Check fingerprint.
libssh2_knownhost* kh;
logging::info(logging::low) << "checking fingerprint on session "
<< _creds.get_user() << "@" << _creds.get_host();
// Get peer fingerprint.
size_t len;
int type;
char const* fingerprint(libssh2_session_hostkey(
_session,
&len,
&type));
if (!fingerprint) {
char* msg;
libssh2_session_last_error(_session, &msg, NULL, 0);
libssh2_knownhost_free(known_hosts);
throw (basic_error()
<< "failed to get remote host fingerprint: " << msg);
}
// Check fingerprint.
libssh2_knownhost* kh;
#if LIBSSH2_VERSION_NUM >= 0x010206
// Introduced in 1.2.6.
int check(libssh2_knownhost_checkp(known_hosts,
_creds.get_host().c_str(),
22,
fingerprint,
len,
LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW,
&kh));
// Introduced in 1.2.6.
int check(libssh2_knownhost_checkp(
known_hosts,
_creds.get_host().c_str(),
-1,
fingerprint,
len,
LIBSSH2_KNOWNHOST_TYPE_PLAIN
| LIBSSH2_KNOWNHOST_KEYENC_RAW,
&kh));
#else
// 1.2.5 or older.
int check(libssh2_knownhost_check(known_hosts,
creds.get_host().c_str(),
fingerprint,
len,
LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW,
&kh));
int check(libssh2_knownhost_check(
known_hosts,
creds.get_host().c_str(),
fingerprint,
len,
LIBSSH2_KNOWNHOST_TYPE_PLAIN
| LIBSSH2_KNOWNHOST_KEYENC_RAW,
&kh));
#endif // LIBSSH2_VERSION_NUM
// Free known hosts list.
libssh2_knownhost_free(known_hosts);
// Check fingerprint.
if (check != LIBSSH2_KNOWNHOST_CHECK_MATCH) {
exceptions::basic e(basic_error());
e << "host '" << _creds.get_host()
<< "' is not known or could not be validated: ";
if (LIBSSH2_KNOWNHOST_CHECK_NOTFOUND == check)
e << "host was not found in known_hosts file";
else if (LIBSSH2_KNOWNHOST_CHECK_MISMATCH == check)
e << "host fingerprint mismatch with known_hosts file";
else
e << "unknown error";
throw (e);
}
// Successful peer authentication.
_step = session_password;
_passwd();
// Free known hosts list.
libssh2_knownhost_free(known_hosts);
// Check fingerprint.
if (check != LIBSSH2_KNOWNHOST_CHECK_MATCH) {
exceptions::basic e(basic_error());
e << "host '" << _creds.get_host()
<< "' is not known or could not be validated: ";
if (LIBSSH2_KNOWNHOST_CHECK_NOTFOUND == check)
e << "host was not found in known_hosts file "
<< known_hosts_file;
else if (LIBSSH2_KNOWNHOST_CHECK_MISMATCH == check)
e << "host fingerprint mismatch with known_hosts file "
<< known_hosts_file;
else
e << "unknown error";
throw (e);
}
logging::info(logging::medium) << "fingerprint on session "
<< _creds.get_user() << "@" << _creds.get_host()
<< " matches a known host";