I wrote some code to execute remote commands over ssh, using expect, from c++. The code works but is a bit of a mess of c / c++ since there doesn't seem to be an idiomatic way to achieve much of what I wanted in pure c++. I appreciate that there are nominally some security concerns with the way I'm using passwords, but these aren't a concern for my use cases.
remote_commmand.h
#pragma once
#include <stdexcept>
struct RemoteException : public std::runtime_error {
using std::runtime_error::runtime_error;
};
void remote_command(const std::string &host, const std::string &user, const std::string &password, const std::string &cmd);
remote_command.cpp
#include "remote_command.h"
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <fstream>
#include <vector>
using std::string;
namespace {
const char* expect_template =
"set timeout 60\n"
"spawn ssh %s@%s %s\n"
"expect {\n"
" \"*yes/no*\"\n"
" {\n"
" send \"yes\\r\"\n"
" exp_continue\n"
" }\n"
" \"*?assword:*\"\n"
" {\n"
" send -- \"%s\\r\"\n"
" send -- \"\\r\"\n"
" expect eof\n"
" }\n"
"}\n";
class TempFile {
public:
explicit TempFile(const char* contents){
file_name = std::tmpnam(nullptr);
std::ofstream f(file_name.c_str(), std::ios::out);
if(!f.write(contents, std::strlen(contents))){
throw std::runtime_error("ofstream");
}
}
~TempFile(){
std::remove(file_name.c_str());
}
TempFile(TempFile &other) = delete;
TempFile& operator=(TempFile &other) = delete;
std::string file_name;
};
}
void remote_command(const string &host, const string &user, const string &password, const string &cmd){
std::vector<char> formatted_cmd;
const std::size_t formatted_size = std::strlen(expect_template) + host.size() + user.size() + password.size() + cmd.size();
formatted_cmd.resize(formatted_size);
const std::size_t res = std::snprintf(&formatted_cmd[0], formatted_size, expect_template, user.c_str(), host.c_str(), cmd.c_str(), password.c_str());
if (res <= 0 || res >= formatted_size ){
throw RemoteException("infsufficient size for snprintf");
}
TempFile tempfile(&formatted_cmd[0]);
int r = execl("/usr/bin/expect", "-f", tempfile.file_name.c_str(), (char *)0);
if (r == -1){
throw RemoteException(string("execl") + std::strerror(errno) );
}
}
example usage
remote_command("my-host", "user", "pw", "/path/to/my/script.sh");