./
Some checks failed
Dropshell Test / Build_and_Test (push) Failing after 21s

This commit is contained in:
Your Name 2025-05-10 14:17:04 +12:00
parent 39e083898f
commit bc45f60b6e
4 changed files with 162 additions and 115 deletions

View File

@ -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})

View File

@ -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>
```

View File

@ -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;

View File

@ -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);
} }