This commit is contained in:
parent
39e083898f
commit
bc45f60b6e
@ -4,9 +4,14 @@ project(runner_demo)
|
|||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(LIBSSH REQUIRED libssh)
|
||||||
|
include_directories(${LIBSSH_INCLUDE_DIRS})
|
||||||
|
link_directories(${LIBSSH_LIBRARY_DIRS})
|
||||||
|
|
||||||
add_library(runner_lib runner.cpp)
|
add_library(runner_lib runner.cpp)
|
||||||
target_include_directories(runner_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(runner_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
target_link_libraries(runner_lib ${LIBSSH_LIBRARIES})
|
||||||
|
|
||||||
add_executable(runner runner_demo.cpp)
|
add_executable(runner runner_demo.cpp)
|
||||||
target_link_libraries(runner runner_lib OpenSSL::SSL OpenSSL::Crypto)
|
target_link_libraries(runner runner_lib OpenSSL::SSL OpenSSL::Crypto ${LIBSSH_LIBRARIES})
|
@ -3,9 +3,10 @@
|
|||||||
Simple c++ demonstration program of the dropshell runner library.
|
Simple c++ demonstration program of the dropshell runner library.
|
||||||
|
|
||||||
use:
|
use:
|
||||||
runner BASE64COMMAND
|
runner
|
||||||
|
|
||||||
BASE64COMMAND is Base64 encoded json. The json format is as described below. The exit code is that of the command run, or -1 if the command couldn't be run.
|
|
||||||
|
The exit code is that of the command run, or -1 if the command couldn't be run.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -39,45 +40,3 @@ If interactive is true, then an interactive session is created - e.g. for runnin
|
|||||||
If silent is true, then all output is suppressed.
|
If silent is true, then all output is suppressed.
|
||||||
|
|
||||||
Before the command is run, the current directory is changed to working_dir on the remote host (or local host if no ssh info provided).
|
Before the command is run, the current directory is changed to working_dir on the remote host (or local host if no ssh info provided).
|
||||||
|
|
||||||
|
|
||||||
## BASE64COMMAND JSON Format
|
|
||||||
|
|
||||||
The BASE64COMMAND argument should be a Base64-encoded JSON object with the following fields:
|
|
||||||
|
|
||||||
- `command` (string, required): The command to execute (e.g., "ls").
|
|
||||||
- `args` (array of strings, optional): Arguments to pass to the command (e.g., ["-l", "/tmp"]).
|
|
||||||
- `working_dir` (string, optional): Directory to change to before running the command.
|
|
||||||
- `env` (object, optional): Environment variables to set (e.g., {"FOO": "bar"}).
|
|
||||||
- `silent` (boolean, optional): If true, suppress all output. Default: false.
|
|
||||||
- `interactive` (boolean, optional): If true, run the command interactively. Default: false.
|
|
||||||
- `sshinfo` (object, optional): If present, run the command on a remote host (not implemented in demo):
|
|
||||||
- `host` (string): Hostname or IP.
|
|
||||||
- `user` (string): Username.
|
|
||||||
- `port` (string): SSH port.
|
|
||||||
|
|
||||||
### Example JSON
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"command": "ls",
|
|
||||||
"args": ["-l", "/tmp"],
|
|
||||||
"working_dir": "/",
|
|
||||||
"env": {"FOO": "bar"},
|
|
||||||
"silent": false,
|
|
||||||
"interactive": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
To use, encode the JSON as a single line, then base64 encode it:
|
|
||||||
|
|
||||||
```
|
|
||||||
echo -n '{"command":"ls","args":["-l","/tmp"],"working_dir":"/","env":{"FOO":"bar"},"silent":false,"interactive":false}' | base64
|
|
||||||
```
|
|
||||||
|
|
||||||
Then run:
|
|
||||||
|
|
||||||
```
|
|
||||||
./build/runner <BASE64COMMAND>
|
|
||||||
```
|
|
||||||
|
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <libssh/libssh.h>
|
||||||
|
#include <libssh/callbacks.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
|
||||||
namespace runner {
|
namespace runner {
|
||||||
|
|
||||||
@ -19,89 +23,167 @@ int execute_cmd(
|
|||||||
std::string* output
|
std::string* output
|
||||||
) {
|
) {
|
||||||
if (sshinfo) {
|
if (sshinfo) {
|
||||||
// Build remote command string
|
ssh_session session = ssh_new();
|
||||||
|
if (session == nullptr) {
|
||||||
|
if (output) *output = "Failed to create SSH session.";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ssh_options_set(session, SSH_OPTIONS_HOST, sshinfo->host.c_str());
|
||||||
|
if (!sshinfo->port.empty()) {
|
||||||
|
int port = std::stoi(sshinfo->port);
|
||||||
|
ssh_options_set(session, SSH_OPTIONS_PORT, &port);
|
||||||
|
}
|
||||||
|
if (!sshinfo->user.empty()) {
|
||||||
|
ssh_options_set(session, SSH_OPTIONS_USER, sshinfo->user.c_str());
|
||||||
|
}
|
||||||
|
int rc = ssh_connect(session);
|
||||||
|
if (rc != SSH_OK) {
|
||||||
|
if (output) *output = std::string("SSH connection failed: ") + ssh_get_error(session);
|
||||||
|
ssh_free(session);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// Try public key, then password if needed
|
||||||
|
rc = ssh_userauth_publickey_auto(session, nullptr, nullptr);
|
||||||
|
if (rc != SSH_AUTH_SUCCESS) {
|
||||||
|
// Try password from env["SSHPASS"] if present
|
||||||
|
auto it = env.find("SSHPASS");
|
||||||
|
if (it != env.end()) {
|
||||||
|
rc = ssh_userauth_password(session, nullptr, it->second.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rc != SSH_AUTH_SUCCESS) {
|
||||||
|
if (output) *output = std::string("SSH authentication failed: ") + ssh_get_error(session);
|
||||||
|
ssh_disconnect(session);
|
||||||
|
ssh_free(session);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ssh_channel channel = ssh_channel_new(session);
|
||||||
|
if (channel == nullptr) {
|
||||||
|
if (output) *output = "Failed to create SSH channel.";
|
||||||
|
ssh_disconnect(session);
|
||||||
|
ssh_free(session);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
rc = ssh_channel_open_session(channel);
|
||||||
|
if (rc != SSH_OK) {
|
||||||
|
if (output) *output = std::string("Failed to open SSH channel: ") + ssh_get_error(session);
|
||||||
|
ssh_channel_free(channel);
|
||||||
|
ssh_disconnect(session);
|
||||||
|
ssh_free(session);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
std::ostringstream remote_cmd;
|
std::ostringstream remote_cmd;
|
||||||
// Set environment variables
|
|
||||||
for (const auto& kv : env) {
|
for (const auto& kv : env) {
|
||||||
|
if (kv.first == "SSHPASS") continue;
|
||||||
remote_cmd << kv.first << "='" << kv.second << "' ";
|
remote_cmd << kv.first << "='" << kv.second << "' ";
|
||||||
}
|
}
|
||||||
// Change working directory
|
|
||||||
if (!working_dir.empty()) {
|
if (!working_dir.empty()) {
|
||||||
remote_cmd << "cd '" << working_dir << "' && ";
|
remote_cmd << "cd '" << working_dir << "' && ";
|
||||||
}
|
}
|
||||||
// Command and args
|
|
||||||
remote_cmd << command;
|
remote_cmd << command;
|
||||||
for (const auto& arg : args) {
|
for (const auto& arg : args) {
|
||||||
remote_cmd << " '" << arg << "'";
|
remote_cmd << " '" << arg << "'";
|
||||||
}
|
}
|
||||||
std::string remote_cmd_str = remote_cmd.str();
|
std::string remote_cmd_str = remote_cmd.str();
|
||||||
|
if (interactive) {
|
||||||
// Build ssh command
|
rc = ssh_channel_request_pty(channel);
|
||||||
std::vector<std::string> ssh_argv = {"ssh"};
|
if (rc != SSH_OK) {
|
||||||
if (!sshinfo->port.empty()) {
|
if (output) *output = std::string("Failed to request pty: ") + ssh_get_error(session);
|
||||||
ssh_argv.push_back("-p");
|
ssh_channel_close(channel);
|
||||||
ssh_argv.push_back(sshinfo->port);
|
ssh_channel_free(channel);
|
||||||
}
|
ssh_disconnect(session);
|
||||||
std::string userhost = sshinfo->user.empty() ? sshinfo->host : (sshinfo->user + "@" + sshinfo->host);
|
ssh_free(session);
|
||||||
ssh_argv.push_back(userhost);
|
|
||||||
ssh_argv.push_back(remote_cmd_str);
|
|
||||||
|
|
||||||
// Prepare for exec
|
|
||||||
std::vector<char*> argv;
|
|
||||||
for (auto& s : ssh_argv) argv.push_back(const_cast<char*>(s.c_str()));
|
|
||||||
argv.push_back(nullptr);
|
|
||||||
|
|
||||||
int pipefd[2];
|
|
||||||
bool use_pipe = output && !interactive;
|
|
||||||
if (use_pipe && pipe(pipefd) == -1) {
|
|
||||||
perror("pipe");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid == -1) {
|
|
||||||
perror("fork");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (pid == 0) {
|
|
||||||
// Child process
|
|
||||||
if (use_pipe) {
|
|
||||||
close(pipefd[0]);
|
|
||||||
dup2(pipefd[1], STDOUT_FILENO);
|
|
||||||
dup2(pipefd[1], STDERR_FILENO);
|
|
||||||
close(pipefd[1]);
|
|
||||||
} else if (silent && !interactive) {
|
|
||||||
int devnull = open("/dev/null", O_WRONLY);
|
|
||||||
dup2(devnull, STDOUT_FILENO);
|
|
||||||
dup2(devnull, STDERR_FILENO);
|
|
||||||
close(devnull);
|
|
||||||
}
|
|
||||||
if (!interactive) {
|
|
||||||
setsid();
|
|
||||||
}
|
|
||||||
execvp("ssh", argv.data());
|
|
||||||
perror("execvp ssh");
|
|
||||||
exit(-1);
|
|
||||||
} else {
|
|
||||||
// Parent process
|
|
||||||
if (use_pipe) {
|
|
||||||
close(pipefd[1]);
|
|
||||||
std::ostringstream oss;
|
|
||||||
char buf[4096];
|
|
||||||
ssize_t n;
|
|
||||||
while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) {
|
|
||||||
oss.write(buf, n);
|
|
||||||
}
|
|
||||||
close(pipefd[0]);
|
|
||||||
*output = oss.str();
|
|
||||||
}
|
|
||||||
int status = 0;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
if (WIFEXITED(status)) {
|
|
||||||
return WEXITSTATUS(status);
|
|
||||||
} else {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
rc = ssh_channel_request_shell(channel);
|
||||||
|
if (rc != SSH_OK) {
|
||||||
|
if (output) *output = std::string("Failed to request shell: ") + ssh_get_error(session);
|
||||||
|
ssh_channel_close(channel);
|
||||||
|
ssh_channel_free(channel);
|
||||||
|
ssh_disconnect(session);
|
||||||
|
ssh_free(session);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// Set local terminal to raw mode
|
||||||
|
struct termios orig_termios, raw_termios;
|
||||||
|
tcgetattr(STDIN_FILENO, &orig_termios);
|
||||||
|
raw_termios = orig_termios;
|
||||||
|
cfmakeraw(&raw_termios);
|
||||||
|
tcsetattr(STDIN_FILENO, TCSANOW, &raw_termios);
|
||||||
|
// If a command is provided, send it to the shell
|
||||||
|
if (!command.empty()) {
|
||||||
|
ssh_channel_write(channel, remote_cmd_str.c_str(), remote_cmd_str.size());
|
||||||
|
ssh_channel_write(channel, "\n", 1);
|
||||||
|
}
|
||||||
|
// Forward input/output using select()
|
||||||
|
int maxfd = STDIN_FILENO > STDOUT_FILENO ? STDIN_FILENO : STDOUT_FILENO;
|
||||||
|
maxfd = maxfd > ssh_get_fd(session) ? maxfd : ssh_get_fd(session);
|
||||||
|
char buffer[4096];
|
||||||
|
bool done = false;
|
||||||
|
while (!done) {
|
||||||
|
fd_set fds_read;
|
||||||
|
FD_ZERO(&fds_read);
|
||||||
|
FD_SET(STDIN_FILENO, &fds_read);
|
||||||
|
FD_SET(ssh_get_fd(session), &fds_read);
|
||||||
|
int ret = select(maxfd + 1, &fds_read, nullptr, nullptr, nullptr);
|
||||||
|
if (ret < 0) break;
|
||||||
|
// Read from stdin, send to channel
|
||||||
|
if (FD_ISSET(STDIN_FILENO, &fds_read)) {
|
||||||
|
ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer));
|
||||||
|
if (n > 0) {
|
||||||
|
ssh_channel_write(channel, buffer, n);
|
||||||
|
} else {
|
||||||
|
ssh_channel_send_eof(channel);
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Read from channel, write to stdout
|
||||||
|
if (FD_ISSET(ssh_get_fd(session), &fds_read)) {
|
||||||
|
int n = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
|
||||||
|
if (n > 0) {
|
||||||
|
write(STDOUT_FILENO, buffer, n);
|
||||||
|
} else if (n == 0) {
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ssh_channel_is_closed(channel) || ssh_channel_is_eof(channel)) {
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Restore terminal
|
||||||
|
tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
|
||||||
|
} else {
|
||||||
|
rc = ssh_channel_request_exec(channel, remote_cmd_str.c_str());
|
||||||
|
if (rc != SSH_OK) {
|
||||||
|
if (output) *output = std::string("Failed to exec remote command: ") + ssh_get_error(session);
|
||||||
|
ssh_channel_close(channel);
|
||||||
|
ssh_channel_free(channel);
|
||||||
|
ssh_disconnect(session);
|
||||||
|
ssh_free(session);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (output) {
|
||||||
|
std::ostringstream oss;
|
||||||
|
char buffer[4096];
|
||||||
|
int nbytes;
|
||||||
|
while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) {
|
||||||
|
oss.write(buffer, nbytes);
|
||||||
|
}
|
||||||
|
*output = oss.str();
|
||||||
|
} else if (!silent) {
|
||||||
|
char buffer[4096];
|
||||||
|
int nbytes;
|
||||||
|
while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) {
|
||||||
|
write(1, buffer, nbytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
ssh_channel_send_eof(channel);
|
||||||
|
ssh_channel_close(channel);
|
||||||
|
ssh_channel_free(channel);
|
||||||
|
ssh_disconnect(session);
|
||||||
|
ssh_free(session);
|
||||||
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
int pipefd[2];
|
int pipefd[2];
|
||||||
bool use_pipe = output && !interactive;
|
bool use_pipe = output && !interactive;
|
||||||
|
@ -76,4 +76,5 @@ int main(int argc, char* argv[]) {
|
|||||||
ssh.port = "22";
|
ssh.port = "22";
|
||||||
|
|
||||||
runner::execute_cmd(command, args, working_dir, env, silent, interactive, &ssh);
|
runner::execute_cmd(command, args, working_dir, env, silent, interactive, &ssh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user