libssh2 ssh2_exec with boost::asio



// AVOID ERROR BECAUSE OF swprintf
#undef __STRICT_ANSI__
// #define FOR WIN7. CAN'T FIND sdkddkver.h ON MINGW
#define _WIN32_WINNT 0x0601


#include "libssh2_config.h"
#include <libssh2.h>
 

#include <boost/asio.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/bind/protect.hpp>
 

#include <utility>

 
using boost::asio::ip::tcp;
using std::string;
using std::cout;
using std::endl;
using boost::asio::deadline_timer;
using boost::asio::io_service;
using boost::protect;

 
// Please, note: The class isn't called TestSSH because I lack imagination.
// The name comes from the fact that this hasn't yet seen anything 
// beyond basic testing.
/*!
  * \todo Create exceptions, instead of using logic_error 
 */ 
class TestSSH
{
public:
    TestSSH();
    ~TestSSH();
    void Connect(string const& hostName, string const& user, 
        string const& password);
    void ExecuteCommand(string const& command);
private:
    static const int PORT = 22;
    enum state { WORK_DONE, STILL_WORKING };
 

    void LoopAsync(boost::function<state()> DoWork, bool isRead);
    void LoopTimer(boost::function<state()> DoWork);
 

    void ConnectSocket(string const& hostName);
    void CreateSSHSession();
    state DoHandshake();
    state DoAuthenticate();
    state DoCreateChannel();
    state DoExecute();
    state DoGetResult();
    state DoCloseChannel();
    void Cleanup();
 

    string userName;
    string password;
    string command;

    io_service ios;
    tcp::socket sock;
    deadline_timer dtimer;

    LIBSSH2_SESSION* session;
    LIBSSH2_CHANNEL *channel;
};
 

const int TestSSH::PORT;

 
TestSSH::TestSSH() : userName("None"), password("None"), command("None"),  
    sock(ios), dtimer(ios), session(nullptr), channel(nullptr)
{
}
 

TestSSH::~TestSSH()
{
    if (channel != nullptr)
    {
        libssh2_channel_free(channel);
        channel = nullptr;
    }
 

    if (session != nullptr)
    {
        libssh2_session_set_blocking(session, 1);
        libssh2_session_disconnect(session, "Disconnecting");
        libssh2_session_free(session);
        session = nullptr;
    }
 

    if (sock.is_open())
    {
        try
        {
        boost::system::error_code ec;
        sock.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
        sock.close(ec);
        } 
        catch (std::exception& ex)
        {
            // NO EXCEPTIONS IN DTOR, LOG IT SOMEWHERE
        }
    }
}
 

void TestSSH::Connect(string const& hostName, string const& user,  
    string const& password)
{
    // THESE ARE BLOCKING
    ConnectSocket(hostName);
    CreateSSHSession();
 

    userName = user;
    this->password = std::move(password);
 

    // NOW WE ENTER THE IO SERVICE LOOP
    // FOR THE HANDSHAKE, READ EVENT IS ENOUGH
    sock.async_read_some(boost::asio::null_buffers(),
        boost::bind(&TestSSH::LoopAsync, this,
        protect(boost::bind(&TestSSH::DoHandshake, this)), true));
    ios.run();
 

    // THEN, THE AUTHENTICATION. THIS ONE GOES WITH A TIMER, FOR NOW.
    // NEED MORE TESTING TO SEE IF I CAN GET ASYNC READ/WRITE TO WORK
    ios.reset(); // RESET THE EVENT LOOP, SO IT MAY BE REUSED
    dtimer.expires_from_now(boost::posix_time::milliseconds(500));
    dtimer.async_wait(boost::bind(&TestSSH::LoopTimer, this,
        protect(boost::bind(&TestSSH::DoAuthenticate, this))));
    ios.run();
}
 

/*!
 * \todo Turn this into a loop
 */
void TestSSH::ExecuteCommand(string const& command)
{
    this->command = command;
 

    ios.reset();
    sock.async_read_some(boost::asio::null_buffers(),
        boost::bind(&TestSSH::LoopAsync, this,
        protect(boost::bind(&TestSSH::DoCreateChannel, this)), true));
    sock.async_write_some(boost::asio::null_buffers(),
        boost::bind(&TestSSH::LoopAsync, this,
        protect(boost::bind(&TestSSH::DoCreateChannel, this)), false));
    ios.run();
 

    ios.reset();
    sock.async_read_some(boost::asio::null_buffers(),
        boost::bind(&TestSSH::LoopAsync, this,
        protect(boost::bind(&TestSSH::DoExecute, this)), true));
    sock.async_write_some(boost::asio::null_buffers(),
        boost::bind(&TestSSH::LoopAsync, this,
        protect(boost::bind(&TestSSH::DoExecute, this)), false));
    ios.run();
 

    ios.reset();
    sock.async_read_some(boost::asio::null_buffers(),
        boost::bind(&TestSSH::LoopAsync, this,
        protect(boost::bind(&TestSSH::DoGetResult, this)), true));
    sock.async_write_some(boost::asio::null_buffers(),
        boost::bind(&TestSSH::LoopAsync, this,
        protect(boost::bind(&TestSSH::DoGetResult, this)), false));
    ios.run();
 

    ios.reset();
    sock.async_read_some(boost::asio::null_buffers(),
        boost::bind(&TestSSH::LoopAsync, this,
        protect(boost::bind(&TestSSH::DoCloseChannel, this)), true));
    sock.async_write_some(boost::asio::null_buffers(),
        boost::bind(&TestSSH::LoopAsync, this,
        protect(boost::bind(&TestSSH::DoCloseChannel, this)), false));
    ios.run();
 

    // THIS IS BLOCKING
    Cleanup();
}
 

void TestSSH::LoopAsync(boost::function<state()> DoWork, bool isRead)
{
    if (sock.get_io_service().stopped())
    {
        return;
    }
 

    state st = DoWork();
 

    if (st == STILL_WORKING)
    {
        if (isRead)
        {
            sock.async_read_some(boost::asio::null_buffers(), 
                boost::bind(&TestSSH::LoopAsync, this, DoWork, isRead));
        }
        else
        {
            sock.async_write_some(boost::asio::null_buffers(),  
                boost::bind(&TestSSH::LoopAsync, this, DoWork, isRead));
        }
        return;
    }
 

    // OTHERWISE, WORK IS OVER
    sock.get_io_service().stop();
}
 

void TestSSH::LoopTimer(boost::function<state()> DoWork)
{
    state st = DoWork();
 

    if (st == STILL_WORKING)
    {
        dtimer.expires_from_now(boost::posix_time::milliseconds(500));
        dtimer.async_wait(boost::bind(&TestSSH::LoopTimer, this, DoWork));
    }

    // OTHERWISE, WORK IS OVER; NOTHING TO DO, AS THE TIMER IS NOT REARMED
}
 

void TestSSH::ConnectSocket(string const& hostName)
{
    tcp::resolver rsv(ios);
    tcp::resolver::query query(hostName,  
        boost::lexical_cast< std::string >(PORT));
    tcp::resolver::iterator iter = rsv.resolve(query);
    boost::asio::connect(sock, iter);
}
 

void TestSSH::CreateSSHSession()
{
    session = libssh2_session_init();
    if (!session)
    {
        throw std::logic_error("Error creating session");
    }
 

    libssh2_session_set_blocking(session, 0);
}
 

TestSSH::state TestSSH::DoHandshake()
{
    int rc = libssh2_session_handshake(session, sock.native_handle());
 

    if (rc == LIBSSH2_ERROR_EAGAIN)
    {
        return STILL_WORKING;
    }
 

    if (rc)
    {
        throw std::logic_error("Error in SSH handshake");
    }
 

    return WORK_DONE;
}
 

TestSSH::state TestSSH::DoAuthenticate()
{
    int rc = libssh2_userauth_password(session, userName.c_str(),  
        password.c_str());
 

    if (rc == LIBSSH2_ERROR_EAGAIN)
    {
        return STILL_WORKING;
    }
 

    if (rc)
    {
        throw std::logic_error("Error in authentication");
    }
 

    return WORK_DONE;
}
 

TestSSH::state TestSSH::DoCreateChannel()
{
    channel = libssh2_channel_open_session(session);
 
    if (channel == nullptr)
    {
        int rc = libssh2_session_last_error(session, nullptr, nullptr, 0);
 
        if (rc == LIBSSH2_ERROR_EAGAIN)
        {
            return STILL_WORKING;
        }
        else
        {
            throw std::logic_error("Error opening channel");
        }
    }
 

    return WORK_DONE;
}
 

TestSSH::state TestSSH::DoExecute()
{
    int rc = libssh2_channel_exec(channel, command.c_str());
 
    if (rc == LIBSSH2_ERROR_EAGAIN)
    {
        return STILL_WORKING;
    }
 

    if (rc)
    {
        throw std::logic_error("Error executing command");
    }
 

    return WORK_DONE;
}
 

TestSSH::state TestSSH::DoGetResult()
{
    char buffer[0x4001];
    int rc = libssh2_channel_read(channel, buffer, sizeof(buffer)-1);
 

    if (rc > 0)
    {
        // BUFFER IS NOT AN SZ-STRING, SO WE TURN IT INTO ONE
        buffer[rc] = '\0';
        string str = buffer;
        cout << str << endl;
 

        // WE'RE NOT FINISHED UNTIL WE READ 0
        return STILL_WORKING;
    }
 

    if (rc == LIBSSH2_ERROR_EAGAIN)
    {
        return STILL_WORKING;
    }
 

    if (rc)
    {
        throw std::logic_error("Error getting execution result");
    }
 

    return WORK_DONE;
}
 

TestSSH::state TestSSH::DoCloseChannel()
{
    int rc = libssh2_channel_close(channel);
 

    if (rc == LIBSSH2_ERROR_EAGAIN)
    {
        return STILL_WORKING;
    }
 

    if (rc)
    {
        throw std::logic_error("Error closing channel");
    }
 

    return WORK_DONE;
}
 

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
#pragma GCC diagnostic ignored "-Wunused-variable"
void TestSSH::Cleanup()
{
    int exitcode = libssh2_channel_get_exit_status( channel );
    char *buf = 
        static_cast<char*>("A buffer for getting the exit signal, if any");
    libssh2_channel_get_exit_signal(channel, &buf, nullptr, nullptr, 
        nullptr, nullptr, nullptr);
 

    if (buf == nullptr)
    {
        cout << "No signal" << endl;
    }
    else
    {
        string str = buf;
        cout << (str.length() == 0 ? "No signal description" : str) << endl;
    }
 

    libssh2_channel_free(channel);
    channel = nullptr;
}
#pragma GCC diagnostic pop
 

int main(int argc, char *argv[])
{
    string hostname("127.0.0.1");
    string commandline("pwd");
    string username("user");
    string password("********"); // STRONG PASSWORD :)
 

    if (argc > 1)        
    {
        hostname = argv[1];
    } 


    if (argc > 2) 
    {
        username = argv[2];
    }
 
    if (argc > 3) {
        password = argv[3];
    }
 
    if (argc > 4) {
        commandline = argv[4];
    }
 

    int rc = libssh2_init (0);
    if (rc != 0) {
        fprintf (stderr, "libssh2 initialization failed (%d)\n", rc);
        return 1;
    }
 

    try
    {
    TestSSH tssh;
    tssh.Connect(hostname, username, password);
    tssh.ExecuteCommand(commandline);
    }
    catch (std::exception& ex)
    {
        cout << ex.what() << endl;
    }
 

    libssh2_exit();

    return 0;
}



No comments:

Post a Comment