This commit is contained in:
parent
2bcf6c530d
commit
b5bc7b611d
28
runner/.gitignore
vendored
Normal file
28
runner/.gitignore
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
# Build directory
|
||||
build/
|
||||
cmake-build-*/
|
||||
|
||||
# IDE files
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*~
|
||||
|
||||
# Compiled object files
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Compiled dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
runner
|
||||
|
||||
# CMake generated files
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
Makefile
|
90
runner/CMakeLists.txt
Normal file
90
runner/CMakeLists.txt
Normal file
@ -0,0 +1,90 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(runner VERSION 1.0)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Include directories
|
||||
include_directories(include)
|
||||
|
||||
# Find required packages
|
||||
find_package(nlohmann_json QUIET)
|
||||
if(NOT nlohmann_json_FOUND)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
nlohmann_json
|
||||
GIT_REPOSITORY https://github.com/nlohmann/json.git
|
||||
GIT_TAG v3.11.2
|
||||
)
|
||||
FetchContent_MakeAvailable(nlohmann_json)
|
||||
endif()
|
||||
|
||||
# Try to find libssh using different methods
|
||||
find_package(libssh QUIET)
|
||||
if(NOT libssh_FOUND)
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(LIBSSH libssh QUIET)
|
||||
endif()
|
||||
|
||||
if(NOT LIBSSH_FOUND)
|
||||
# Try to find manually if pkg-config failed too
|
||||
find_path(LIBSSH_INCLUDE_DIR
|
||||
NAMES libssh/libssh.h
|
||||
PATHS /usr/include /usr/local/include
|
||||
)
|
||||
|
||||
find_library(LIBSSH_LIBRARY
|
||||
NAMES ssh libssh
|
||||
PATHS /usr/lib /usr/local/lib /usr/lib/x86_64-linux-gnu
|
||||
)
|
||||
|
||||
if(LIBSSH_INCLUDE_DIR AND LIBSSH_LIBRARY)
|
||||
set(LIBSSH_FOUND TRUE)
|
||||
set(LIBSSH_LIBRARIES ${LIBSSH_LIBRARY})
|
||||
set(LIBSSH_INCLUDE_DIRS ${LIBSSH_INCLUDE_DIR})
|
||||
message(STATUS "Found libssh: ${LIBSSH_LIBRARY}")
|
||||
else()
|
||||
message(FATAL_ERROR "libssh not found. Please install libssh-dev package.\nOn Ubuntu/Debian: sudo apt install libssh-dev\nOn CentOS/RHEL: sudo yum install libssh-devel\nOn macOS: brew install libssh")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Print libssh information for debugging
|
||||
message(STATUS "LIBSSH_FOUND: ${LIBSSH_FOUND}")
|
||||
message(STATUS "LIBSSH_LIBRARIES: ${LIBSSH_LIBRARIES}")
|
||||
message(STATUS "LIBSSH_INCLUDE_DIRS: ${LIBSSH_INCLUDE_DIRS}")
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
# Library target
|
||||
add_library(dropshell_runner STATIC
|
||||
src/runner.cpp
|
||||
src/base64.cpp
|
||||
)
|
||||
|
||||
target_include_directories(dropshell_runner PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${LIBSSH_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
# Link with libssh
|
||||
if(libssh_FOUND)
|
||||
# libssh was found using the find_package method
|
||||
target_link_libraries(dropshell_runner PUBLIC
|
||||
nlohmann_json::nlohmann_json
|
||||
ssh
|
||||
Threads::Threads
|
||||
)
|
||||
else()
|
||||
# libssh was found using pkg-config or manual search
|
||||
target_link_libraries(dropshell_runner PUBLIC
|
||||
nlohmann_json::nlohmann_json
|
||||
${LIBSSH_LIBRARIES}
|
||||
Threads::Threads
|
||||
)
|
||||
endif()
|
||||
|
||||
# Executable target
|
||||
add_executable(runner src/main.cpp)
|
||||
target_link_libraries(runner PRIVATE dropshell_runner)
|
212
runner/README.md
Normal file
212
runner/README.md
Normal file
@ -0,0 +1,212 @@
|
||||
# Runner
|
||||
|
||||
Simple c++ demonstration program of the dropshell runner library.
|
||||
|
||||
use:
|
||||
runner BASE64COMMAND
|
||||
|
||||
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 c++ library used, which is contained in this codebase, has two simple functions:
|
||||
bool runner(nlohmann::json run_json); // no output capture
|
||||
bool runner(nlohmann::json run_json, std::string & output); // with output capture.
|
||||
|
||||
## JSON Specification
|
||||
|
||||
```json
|
||||
{
|
||||
"ssh": { // Optional: SSH connection information
|
||||
"host": "hostname", // Remote host to connect to
|
||||
"port": 22, // Port number (default: 22)
|
||||
"user": "username", // Username for SSH connection
|
||||
"key": "path/to/key" // Path to SSH key file or "auto" to use current user's key
|
||||
},
|
||||
"env": { // Optional: Environment variables
|
||||
"VAR1": "value1",
|
||||
"VAR2": "value2"
|
||||
},
|
||||
"command": "command_name", // Required: Command to execute
|
||||
"args": ["arg1", "arg2"], // Optional: Command arguments
|
||||
"options": { // Optional: Execution options
|
||||
"silent": false, // Suppress all terminal output (default: false)
|
||||
"interactive": false // Hook up TTY for interactive sessions (default: false)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If SSH information is provided, the command will be executed on the remote server.
|
||||
|
||||
## Build Instructions
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- CMake 3.10 or newer
|
||||
- C++17 compatible compiler
|
||||
- libssh development libraries
|
||||
- jq (for the helper scripts)
|
||||
|
||||
#### Installing Dependencies
|
||||
|
||||
##### Quick Installation (Ubuntu/Debian)
|
||||
|
||||
For Ubuntu/Debian systems, you can use the provided installation script:
|
||||
|
||||
```bash
|
||||
sudo ./install_deps.sh
|
||||
```
|
||||
|
||||
This will install all required dependencies (cmake, g++, libssh-dev, jq).
|
||||
|
||||
##### Manual Installation
|
||||
|
||||
###### Ubuntu/Debian
|
||||
|
||||
```bash
|
||||
sudo apt-get install cmake g++ libssh-dev jq
|
||||
```
|
||||
|
||||
###### CentOS/RHEL
|
||||
|
||||
```bash
|
||||
sudo yum install cmake gcc-c++ libssh-devel jq
|
||||
```
|
||||
|
||||
###### macOS
|
||||
|
||||
```bash
|
||||
brew install cmake libssh jq
|
||||
```
|
||||
|
||||
###### Windows
|
||||
|
||||
Using vcpkg:
|
||||
|
||||
```bash
|
||||
vcpkg install libssh nlohmann-json
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
To build the project, you can use the provided build script:
|
||||
|
||||
```bash
|
||||
./build.sh
|
||||
```
|
||||
|
||||
Or manually:
|
||||
|
||||
```bash
|
||||
mkdir -p build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
The executable will be created at `build/runner`.
|
||||
|
||||
### Testing
|
||||
|
||||
A simple test script is included to verify the functionality:
|
||||
|
||||
```bash
|
||||
./test.sh
|
||||
```
|
||||
|
||||
This will run basic tests for command execution, environment variables, silent mode, and return codes.
|
||||
|
||||
If you have SSH configured on your local machine and want to test the SSH functionality:
|
||||
|
||||
```bash
|
||||
ENABLE_SSH_TEST=1 ./test.sh
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If CMake cannot find libssh, you can:
|
||||
|
||||
1. Run the libssh finder script to locate your installation:
|
||||
```bash
|
||||
./examples/find_libssh.sh
|
||||
```
|
||||
|
||||
2. Specify its location manually:
|
||||
```bash
|
||||
cmake -DCMAKE_PREFIX_PATH=/path/to/libssh/installation ..
|
||||
```
|
||||
|
||||
3. Or set the libssh_DIR environment variable:
|
||||
```bash
|
||||
export libssh_DIR=/path/to/libssh/installation
|
||||
cmake ..
|
||||
```
|
||||
|
||||
4. If the problem persists, specify the library and include paths directly:
|
||||
```bash
|
||||
cmake -DLIBSSH_LIBRARY=/path/to/libssh.so -DLIBSSH_INCLUDE_DIR=/path/to/include ..
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Running a local command
|
||||
|
||||
```bash
|
||||
# Create a JSON configuration for the 'ls -l' command
|
||||
JSON='{"command":"ls","args":["-l"]}'
|
||||
|
||||
# Base64 encode the JSON
|
||||
BASE64=$(echo -n "$JSON" | base64)
|
||||
|
||||
# Run the command
|
||||
./build/runner $BASE64
|
||||
```
|
||||
|
||||
### Running a command with environment variables
|
||||
|
||||
```bash
|
||||
# Create a JSON configuration with environment variables
|
||||
JSON='{"command":"echo","args":["$GREETING"],"env":{"GREETING":"Hello, World!"}}'
|
||||
|
||||
# Base64 encode the JSON
|
||||
BASE64=$(echo -n "$JSON" | base64)
|
||||
|
||||
# Run the command
|
||||
./build/runner $BASE64
|
||||
```
|
||||
|
||||
### Running a command on a remote server via SSH
|
||||
|
||||
```bash
|
||||
# Create a JSON configuration for a remote command
|
||||
JSON='{"ssh":{"host":"example.com","user":"username","key":"auto"},"command":"hostname"}'
|
||||
|
||||
# Base64 encode the JSON
|
||||
BASE64=$(echo -n "$JSON" | base64)
|
||||
|
||||
# Run the command
|
||||
./build/runner $BASE64
|
||||
```
|
||||
|
||||
### Running an interactive command
|
||||
|
||||
```bash
|
||||
# Create a JSON configuration for an interactive command
|
||||
JSON='{"command":"vim","options":{"interactive":true}}'
|
||||
|
||||
# Base64 encode the JSON
|
||||
BASE64=$(echo -n "$JSON" | base64)
|
||||
|
||||
# Run the command
|
||||
./build/runner $BASE64
|
||||
```
|
||||
|
||||
### Using the helper script
|
||||
|
||||
The `run.sh` script simplifies testing by handling the JSON validation and Base64 encoding:
|
||||
|
||||
```bash
|
||||
# Run with a JSON file
|
||||
./run.sh examples/local_command.json
|
||||
|
||||
# Run with a JSON string
|
||||
./run.sh '{"command":"echo","args":["Hello World"]}'
|
||||
```
|
78
runner/build.sh
Executable file
78
runner/build.sh
Executable file
@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Don't use set -e because we want to handle errors ourselves
|
||||
# set -e
|
||||
|
||||
echo "Building Runner - Dropshell Command Execution Library"
|
||||
echo "===================================================="
|
||||
|
||||
# Create build directory if it doesn't exist
|
||||
mkdir -p build || {
|
||||
echo "ERROR: Failed to create build directory"
|
||||
exit 1
|
||||
}
|
||||
cd build
|
||||
|
||||
# Check if pkg-config is available and can find libssh
|
||||
if command -v pkg-config &> /dev/null && pkg-config --exists libssh; then
|
||||
# If libssh is found through pkg-config, use it
|
||||
LIBSSH_PREFIX=$(pkg-config --variable=prefix libssh)
|
||||
echo "Found libssh through pkg-config at: $LIBSSH_PREFIX"
|
||||
CMAKE_ARGS="-DCMAKE_PREFIX_PATH=$LIBSSH_PREFIX"
|
||||
else
|
||||
# Otherwise, let CMake try to find it
|
||||
CMAKE_ARGS=""
|
||||
|
||||
# Check if libssh-dev/libssh-devel is installed
|
||||
if [ ! -f "/usr/include/libssh/libssh.h" ] && [ ! -f "/usr/local/include/libssh/libssh.h" ]; then
|
||||
echo "WARNING: libssh development headers not found in standard locations."
|
||||
echo "You may need to install the libssh development package:"
|
||||
echo " - Ubuntu/Debian: sudo apt install libssh-dev"
|
||||
echo " - CentOS/RHEL: sudo yum install libssh-devel"
|
||||
echo " - macOS: brew install libssh"
|
||||
echo ""
|
||||
echo "Continuing build anyway, but it may fail..."
|
||||
echo ""
|
||||
|
||||
# Offer to run the find_libssh.sh helper script
|
||||
if [ -x "../examples/find_libssh.sh" ]; then
|
||||
echo "Would you like to run the libssh finder helper script? (y/n)"
|
||||
read -r answer
|
||||
if [[ "$answer" =~ ^[Yy] ]]; then
|
||||
cd ..
|
||||
./examples/find_libssh.sh
|
||||
cd build
|
||||
echo ""
|
||||
echo "Continuing with build..."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Configure and build with error handling
|
||||
echo "Running cmake with args: $CMAKE_ARGS"
|
||||
if ! cmake $CMAKE_ARGS ..; then
|
||||
echo ""
|
||||
echo "ERROR: CMake configuration failed."
|
||||
echo "This is likely due to missing dependencies. Please check the error message above."
|
||||
echo "For libssh issues, try:"
|
||||
echo " 1. Install libssh development package: sudo apt install libssh-dev"
|
||||
echo " 2. Run our helper script: ./examples/find_libssh.sh"
|
||||
echo " 3. If you know where libssh is installed, specify it with:"
|
||||
echo " cmake -DLIBSSH_LIBRARY=/path/to/libssh.so -DLIBSSH_INCLUDE_DIR=/path/to/include .."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! make -j$(nproc); then
|
||||
echo ""
|
||||
echo "ERROR: Build failed."
|
||||
echo "Please check the error messages above for details."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Build complete. Binary is at build/runner"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " ./run.sh examples/local_command.json # Run a local command"
|
||||
echo " ./run.sh examples/env_vars.json # Run with environment variables"
|
1
runner/examples/env_vars.json
Normal file
1
runner/examples/env_vars.json
Normal file
@ -0,0 +1 @@
|
||||
{"command":"bash","args":["-c","echo Hello $NAME, the current directory is $PWD"],"env":{"NAME":"Runner","CUSTOM_VAR":"This is a custom environment variable"}}
|
64
runner/examples/find_libssh.sh
Executable file
64
runner/examples/find_libssh.sh
Executable file
@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
# Helper script to find libssh installation on your system
|
||||
|
||||
echo "Looking for libssh installation..."
|
||||
echo
|
||||
|
||||
# Check pkg-config path
|
||||
if command -v pkg-config &> /dev/null; then
|
||||
echo "Checking pkg-config for libssh:"
|
||||
if pkg-config --exists libssh; then
|
||||
echo " - Found libssh with pkg-config"
|
||||
echo " - Version: $(pkg-config --modversion libssh)"
|
||||
echo " - Include path: $(pkg-config --variable=includedir libssh)"
|
||||
echo " - Library path: $(pkg-config --variable=libdir libssh)"
|
||||
echo
|
||||
echo "To use this in cmake:"
|
||||
echo " cmake -DCMAKE_PREFIX_PATH=$(pkg-config --variable=prefix libssh) .."
|
||||
echo
|
||||
else
|
||||
echo " - libssh not found with pkg-config"
|
||||
echo
|
||||
fi
|
||||
else
|
||||
echo "pkg-config not found on your system"
|
||||
echo
|
||||
fi
|
||||
|
||||
# Check common include paths
|
||||
for DIR in /usr/include /usr/local/include /opt/local/include; do
|
||||
if [ -d "$DIR" ] && [ -f "$DIR/libssh/libssh.h" ]; then
|
||||
echo "Found libssh headers at: $DIR/libssh/libssh.h"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check common library paths
|
||||
LIB_PATHS=()
|
||||
for DIR in /usr/lib /usr/local/lib /usr/lib/x86_64-linux-gnu /usr/local/lib64 /opt/local/lib; do
|
||||
if [ -d "$DIR" ]; then
|
||||
LIBS=$(find "$DIR" -name "libssh.so*" -o -name "libssh.dylib" -o -name "libssh.a" 2>/dev/null)
|
||||
if [ -n "$LIBS" ]; then
|
||||
echo "Found libssh libraries in $DIR:"
|
||||
echo "$LIBS" | sed 's/^/ - /'
|
||||
LIB_DIR="$DIR"
|
||||
LIB_PATHS+=("$DIR")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
if [ ${#LIB_PATHS[@]} -gt 0 ]; then
|
||||
echo "To build with the detected libssh installation, you can use:"
|
||||
echo " cmake -DLIBSSH_LIBRARY=${LIB_PATHS[0]}/libssh.so -DLIBSSH_INCLUDE_DIR=/usr/include .."
|
||||
echo
|
||||
echo "Or set environment variables:"
|
||||
echo " export LIBSSH_LIBRARY=${LIB_PATHS[0]}/libssh.so"
|
||||
echo " export LIBSSH_INCLUDE_DIR=/usr/include"
|
||||
echo " cmake .."
|
||||
else
|
||||
echo "Could not find libssh on your system."
|
||||
echo "Please install it with your package manager:"
|
||||
echo " - Ubuntu/Debian: sudo apt install libssh-dev"
|
||||
echo " - CentOS/RHEL: sudo yum install libssh-devel"
|
||||
echo " - macOS: brew install libssh"
|
||||
fi
|
1
runner/examples/interactive_mode.json
Normal file
1
runner/examples/interactive_mode.json
Normal file
@ -0,0 +1 @@
|
||||
{"command":"bash","options":{"interactive":true}}
|
1
runner/examples/local_command.json
Normal file
1
runner/examples/local_command.json
Normal file
@ -0,0 +1 @@
|
||||
{"command":"ls","args":["-la"],"options":{"silent":false}}
|
1
runner/examples/local_ssh.json
Normal file
1
runner/examples/local_ssh.json
Normal file
@ -0,0 +1 @@
|
||||
{"ssh":{"host":"localhost","key":"auto"},"command":"echo","args":["Hello from SSH on localhost!"]}
|
1
runner/examples/silent_mode.json
Normal file
1
runner/examples/silent_mode.json
Normal file
@ -0,0 +1 @@
|
||||
{"command":"ls","args":["-la"],"options":{"silent":true}}
|
1
runner/examples/ssh_command.json
Normal file
1
runner/examples/ssh_command.json
Normal file
@ -0,0 +1 @@
|
||||
{"ssh":{"host":"localhost","port":22,"user":"USERNAME","key":"auto"},"command":"hostname","options":{"interactive":false}}
|
13
runner/include/base64.h
Normal file
13
runner/include/base64.h
Normal file
@ -0,0 +1,13 @@
|
||||
#ifndef BASE64_H
|
||||
#define BASE64_H
|
||||
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Decode a Base64 encoded string to its original form
|
||||
* @param encoded The Base64 encoded string
|
||||
* @return The decoded string
|
||||
*/
|
||||
std::string base64_decode(const std::string& encoded);
|
||||
|
||||
#endif // BASE64_H
|
98
runner/include/runner.h
Normal file
98
runner/include/runner.h
Normal file
@ -0,0 +1,98 @@
|
||||
#ifndef DROPSHELL_RUNNER_H
|
||||
#define DROPSHELL_RUNNER_H
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
/**
|
||||
* Runner library for executing commands locally or remotely via SSH
|
||||
*/
|
||||
class Runner {
|
||||
public:
|
||||
/**
|
||||
* Execute a command according to the specification in the JSON
|
||||
* @param run_json JSON specification for the command
|
||||
* @return true if command executed successfully, false otherwise
|
||||
*/
|
||||
static bool run(const nlohmann::json& run_json);
|
||||
|
||||
/**
|
||||
* Execute a command and capture its output
|
||||
* @param run_json JSON specification for the command
|
||||
* @param output Reference to string where output will be stored
|
||||
* @return true if command executed successfully, false otherwise
|
||||
*/
|
||||
static bool run(const nlohmann::json& run_json, std::string& output);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Execute a command locally
|
||||
* @param command Command to execute
|
||||
* @param args Command arguments
|
||||
* @param env Environment variables
|
||||
* @param silent Whether to suppress output
|
||||
* @param interactive Whether to enable interactive mode
|
||||
* @param output Reference to string where output will be stored (if capturing)
|
||||
* @param capture_output Whether to capture output
|
||||
* @return exit code of the command, or -1 if execution failed
|
||||
*/
|
||||
static int execute_local(
|
||||
const std::string& command,
|
||||
const std::vector<std::string>& args,
|
||||
const std::map<std::string, std::string>& env,
|
||||
bool silent,
|
||||
bool interactive,
|
||||
std::string* output = nullptr,
|
||||
bool capture_output = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Execute a command remotely via SSH
|
||||
* @param ssh_config SSH configuration
|
||||
* @param command Command to execute
|
||||
* @param args Command arguments
|
||||
* @param env Environment variables
|
||||
* @param silent Whether to suppress output
|
||||
* @param interactive Whether to enable interactive mode
|
||||
* @param output Reference to string where output will be stored (if capturing)
|
||||
* @param capture_output Whether to capture output
|
||||
* @return exit code of the command, or -1 if execution failed
|
||||
*/
|
||||
static int execute_ssh(
|
||||
const nlohmann::json& ssh_config,
|
||||
const std::string& command,
|
||||
const std::vector<std::string>& args,
|
||||
const std::map<std::string, std::string>& env,
|
||||
bool silent,
|
||||
bool interactive,
|
||||
std::string* output = nullptr,
|
||||
bool capture_output = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Parse command specification from JSON
|
||||
* @param run_json JSON specification
|
||||
* @param command Output parameter for command
|
||||
* @param args Output parameter for command arguments
|
||||
* @param env Output parameter for environment variables
|
||||
* @param silent Output parameter for silent option
|
||||
* @param interactive Output parameter for interactive option
|
||||
* @return true if parsing was successful, false otherwise
|
||||
*/
|
||||
static bool parse_json(
|
||||
const nlohmann::json& run_json,
|
||||
std::string& command,
|
||||
std::vector<std::string>& args,
|
||||
std::map<std::string, std::string>& env,
|
||||
bool& silent,
|
||||
bool& interactive
|
||||
);
|
||||
};
|
||||
|
||||
} // namespace dropshell
|
||||
|
||||
#endif // DROPSHELL_RUNNER_H
|
64
runner/install_deps.sh
Executable file
64
runner/install_deps.sh
Executable file
@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Install dependencies script for Runner
|
||||
# This script is for Ubuntu/Debian systems
|
||||
|
||||
echo "Installing dependencies for Runner - Dropshell Command Execution Library"
|
||||
echo "======================================================================"
|
||||
echo
|
||||
|
||||
# Check if running as root or with sudo
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run this script with sudo:"
|
||||
echo " sudo $0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Updating package lists..."
|
||||
apt-get update
|
||||
|
||||
echo "Installing required packages:"
|
||||
echo " - build-essential (C++ compiler and build tools)"
|
||||
echo " - cmake (build system)"
|
||||
echo " - libssh-dev (SSH client library)"
|
||||
echo " - jq (JSON parsing for helper scripts)"
|
||||
echo
|
||||
|
||||
apt-get install -y build-essential cmake libssh-dev jq
|
||||
|
||||
# Check if installation was successful
|
||||
if [ $? -eq 0 ]; then
|
||||
echo
|
||||
echo "Dependencies installed successfully!"
|
||||
echo
|
||||
echo "You can now build the project with:"
|
||||
echo " ./build.sh"
|
||||
echo
|
||||
else
|
||||
echo
|
||||
echo "Error: Failed to install dependencies."
|
||||
echo "Please check the error messages above."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify libssh installation
|
||||
if [ -f "/usr/include/libssh/libssh.h" ]; then
|
||||
echo "Verified: libssh development headers are installed."
|
||||
|
||||
# Find libssh shared library
|
||||
LIB=$(find /usr/lib -name "libssh.so*" | head -1)
|
||||
if [ -n "$LIB" ]; then
|
||||
echo "Verified: libssh shared library is installed at $LIB"
|
||||
else
|
||||
echo "Warning: Could not find libssh shared library."
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "You're all set! Build the project with ./build.sh"
|
||||
else
|
||||
echo "Warning: Could not verify libssh installation."
|
||||
echo "The package might have been installed in a non-standard location."
|
||||
echo "You may need to manually specify the location when building:"
|
||||
echo
|
||||
echo " cmake -DLIBSSH_LIBRARY=/path/to/libssh.so -DLIBSSH_INCLUDE_DIR=/path/to/include .."
|
||||
fi
|
96
runner/minimal_test.sh
Executable file
96
runner/minimal_test.sh
Executable file
@ -0,0 +1,96 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Running minimal libssh test to diagnose linking issues"
|
||||
echo "===================================================="
|
||||
|
||||
# Create test directory
|
||||
TESTDIR="ssh_test_tmp"
|
||||
mkdir -p $TESTDIR
|
||||
cd $TESTDIR
|
||||
|
||||
# Create minimal C program that uses libssh
|
||||
cat > test.c << 'EOF'
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <libssh/libssh.h>
|
||||
|
||||
int main() {
|
||||
ssh_session session = ssh_new();
|
||||
if (session == NULL) {
|
||||
fprintf(stderr, "Failed to create SSH session\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Successfully created SSH session\n");
|
||||
|
||||
ssh_free(session);
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Created minimal test program. Attempting to compile..."
|
||||
|
||||
# Check pkg-config first
|
||||
if command -v pkg-config &> /dev/null && pkg-config --exists libssh; then
|
||||
CFLAGS=$(pkg-config --cflags libssh)
|
||||
LIBS=$(pkg-config --libs libssh)
|
||||
|
||||
echo "Found libssh with pkg-config:"
|
||||
echo " CFLAGS: $CFLAGS"
|
||||
echo " LIBS: $LIBS"
|
||||
echo
|
||||
|
||||
echo "Compiling with pkg-config flags..."
|
||||
gcc -o test_pkg test.c $CFLAGS $LIBS
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Compilation successful!"
|
||||
echo "Running the test program:"
|
||||
./test_pkg
|
||||
else
|
||||
echo "Compilation failed with pkg-config flags."
|
||||
fi
|
||||
echo
|
||||
fi
|
||||
|
||||
# Try with simple -lssh flag
|
||||
echo "Compiling with simple -lssh flag..."
|
||||
gcc -o test_simple test.c -lssh
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Compilation successful with -lssh!"
|
||||
echo "Running the test program:"
|
||||
./test_simple
|
||||
else
|
||||
echo "Compilation failed with simple -lssh flag."
|
||||
echo "This indicates your libssh installation might not be in the standard location."
|
||||
fi
|
||||
echo
|
||||
|
||||
# Try to find libssh manually
|
||||
echo "Searching for libssh.so in common locations..."
|
||||
LIBSSH_PATHS=$(find /usr/lib /usr/local/lib /usr/lib/x86_64-linux-gnu -name "libssh.so*" 2>/dev/null)
|
||||
|
||||
if [ -n "$LIBSSH_PATHS" ]; then
|
||||
echo "Found libssh in the following locations:"
|
||||
echo "$LIBSSH_PATHS" | sed 's/^/ - /'
|
||||
|
||||
# Extract directory of first result
|
||||
LIBSSH_DIR=$(dirname $(echo "$LIBSSH_PATHS" | head -1))
|
||||
echo
|
||||
echo "For CMake, you can try:"
|
||||
echo " cmake -DLIBSSH_LIBRARY=$LIBSSH_DIR/libssh.so -DLIBSSH_INCLUDE_DIR=/usr/include .."
|
||||
else
|
||||
echo "Could not find libssh.so."
|
||||
echo "Consider installing libssh-dev package:"
|
||||
echo " sudo apt install libssh-dev"
|
||||
fi
|
||||
|
||||
# Clean up
|
||||
cd ..
|
||||
echo
|
||||
echo "Cleaning up..."
|
||||
rm -rf $TESTDIR
|
||||
|
||||
echo
|
||||
echo "Test completed. Use this information to troubleshoot your libssh configuration."
|
83
runner/run.sh
Executable file
83
runner/run.sh
Executable file
@ -0,0 +1,83 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Check if jq is installed
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "Error: jq is required but not installed."
|
||||
echo "Install it with: sudo apt-get install jq"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to print usage
|
||||
usage() {
|
||||
echo "Usage: $0 <json_file>"
|
||||
echo " $0 'json_string'"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if argument is provided
|
||||
if [ $# -ne 1 ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
JSON=""
|
||||
|
||||
# Process and clean up JSON input to handle any whitespace or formatting issues
|
||||
process_json() {
|
||||
local input="$1"
|
||||
# Use 'tr' to remove any trailing whitespace and control characters
|
||||
# Then use jq to validate and normalize the JSON structure
|
||||
echo "$input" | tr -d '\r' | sed 's/[[:space:]]*$//' | jq -c '.'
|
||||
}
|
||||
|
||||
# Check if the argument is a file or a JSON string
|
||||
if [ -f "$1" ]; then
|
||||
# Validate JSON file
|
||||
if ! jq . "$1" > /dev/null 2>&1; then
|
||||
echo "Error: Invalid JSON in file $1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Read file content and process it
|
||||
FILE_CONTENT=$(cat "$1")
|
||||
JSON=$(process_json "$FILE_CONTENT")
|
||||
else
|
||||
# Check if it's a valid JSON string
|
||||
if ! echo "$1" | jq . > /dev/null 2>&1; then
|
||||
echo "Error: Invalid JSON string"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Process the JSON string
|
||||
JSON=$(process_json "$1")
|
||||
fi
|
||||
|
||||
# Double-check that we have valid JSON (defensive programming)
|
||||
if ! echo "$JSON" | jq . > /dev/null 2>&1; then
|
||||
echo "Error: Something went wrong processing the JSON"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Base64 encode the JSON, ensuring no trailing newlines
|
||||
BASE64=$(echo -n "$JSON" | base64 | tr -d '\n')
|
||||
|
||||
# Ensure the binary exists
|
||||
if [ ! -f "build/runner" ]; then
|
||||
echo "Building the project first..."
|
||||
./build.sh
|
||||
fi
|
||||
|
||||
# Run the command
|
||||
echo "Running command with JSON:"
|
||||
echo "$JSON" | jq .
|
||||
echo
|
||||
echo "Base64 encoded: $BASE64"
|
||||
echo
|
||||
|
||||
# Execute the runner with the processed base64 input
|
||||
build/runner "$BASE64"
|
||||
EXIT_CODE=$?
|
||||
|
||||
echo
|
||||
echo "Command exited with code: $EXIT_CODE"
|
||||
exit $EXIT_CODE
|
62
runner/src/base64.cpp
Normal file
62
runner/src/base64.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
#include "base64.h"
|
||||
#include <iostream>
|
||||
#include <cctype>
|
||||
|
||||
std::string base64_decode(const std::string& encoded) {
|
||||
const std::string base64_chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
size_t in_len = encoded.size();
|
||||
size_t i = 0;
|
||||
size_t in_ = 0;
|
||||
unsigned char char_array_4[4], char_array_3[3];
|
||||
std::string decoded;
|
||||
|
||||
while (in_ < in_len && encoded[in_] != '=' &&
|
||||
(std::isalnum(encoded[in_]) || encoded[in_] == '+' || encoded[in_] == '/')) {
|
||||
char_array_4[i++] = encoded[in_++];
|
||||
if (i == 4) {
|
||||
// Translate values in char_array_4 from base64 alphabet to indices
|
||||
for (i = 0; i < 4; i++) {
|
||||
char_array_4[i] = base64_chars.find(char_array_4[i]);
|
||||
}
|
||||
|
||||
// Decode to original bytes
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
decoded += char_array_3[i];
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle any remaining bytes
|
||||
if (i) {
|
||||
// Fill remaining positions with zeros
|
||||
for (size_t j = i; j < 4; j++) {
|
||||
char_array_4[j] = 0;
|
||||
}
|
||||
|
||||
// Convert to indices
|
||||
for (size_t j = 0; j < 4; j++) {
|
||||
char_array_4[j] = base64_chars.find(char_array_4[j]);
|
||||
}
|
||||
|
||||
// Decode remaining bytes
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
// Only add valid bytes based on how many characters we had
|
||||
for (size_t j = 0; j < i - 1; j++) {
|
||||
decoded += char_array_3[j];
|
||||
}
|
||||
}
|
||||
|
||||
return decoded;
|
||||
}
|
42
runner/src/main.cpp
Normal file
42
runner/src/main.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include "runner.h"
|
||||
#include "base64.h"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
void print_usage() {
|
||||
std::cerr << "Usage: runner BASE64COMMAND" << std::endl;
|
||||
std::cerr << " where BASE64COMMAND is a Base64 encoded JSON string" << std::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc != 2) {
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string base64_command = argv[1];
|
||||
std::string json_string;
|
||||
|
||||
try {
|
||||
// Decode Base64
|
||||
json_string = base64_decode(base64_command);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error decoding Base64: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Parse JSON
|
||||
nlohmann::json run_json;
|
||||
try {
|
||||
run_json = nlohmann::json::parse(json_string);
|
||||
} catch (const nlohmann::json::parse_error& e) {
|
||||
std::cerr << "Error parsing JSON: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Execute command
|
||||
bool success = dropshell::Runner::run(run_json);
|
||||
|
||||
// Return the exit code
|
||||
return success ? 0 : 1;
|
||||
}
|
466
runner/src/runner.cpp
Normal file
466
runner/src/runner.cpp
Normal file
@ -0,0 +1,466 @@
|
||||
#include "runner.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <cstdlib>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <fcntl.h>
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/sftp.h>
|
||||
#include <pwd.h>
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
bool Runner::run(const nlohmann::json& run_json) {
|
||||
std::string command;
|
||||
std::vector<std::string> args;
|
||||
std::map<std::string, std::string> env;
|
||||
bool silent, interactive;
|
||||
|
||||
if (!parse_json(run_json, command, args, env, silent, interactive)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int exit_code;
|
||||
if (run_json.contains("ssh")) {
|
||||
exit_code = execute_ssh(run_json["ssh"], command, args, env, silent, interactive);
|
||||
} else {
|
||||
exit_code = execute_local(command, args, env, silent, interactive);
|
||||
}
|
||||
|
||||
return exit_code == 0;
|
||||
}
|
||||
|
||||
bool Runner::run(const nlohmann::json& run_json, std::string& output) {
|
||||
std::string command;
|
||||
std::vector<std::string> args;
|
||||
std::map<std::string, std::string> env;
|
||||
bool silent, interactive;
|
||||
|
||||
if (!parse_json(run_json, command, args, env, silent, interactive)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int exit_code;
|
||||
if (run_json.contains("ssh")) {
|
||||
exit_code = execute_ssh(run_json["ssh"], command, args, env, silent, interactive, &output, true);
|
||||
} else {
|
||||
exit_code = execute_local(command, args, env, silent, interactive, &output, true);
|
||||
}
|
||||
|
||||
return exit_code == 0;
|
||||
}
|
||||
|
||||
bool Runner::parse_json(
|
||||
const nlohmann::json& run_json,
|
||||
std::string& command,
|
||||
std::vector<std::string>& args,
|
||||
std::map<std::string, std::string>& env,
|
||||
bool& silent,
|
||||
bool& interactive
|
||||
) {
|
||||
try {
|
||||
// Command is required
|
||||
if (!run_json.contains("command") || !run_json["command"].is_string()) {
|
||||
std::cerr << "Error: 'command' field is required and must be a string" << std::endl;
|
||||
return false;
|
||||
}
|
||||
command = run_json["command"];
|
||||
|
||||
// Args are optional
|
||||
args.clear();
|
||||
if (run_json.contains("args")) {
|
||||
if (!run_json["args"].is_array()) {
|
||||
std::cerr << "Error: 'args' field must be an array" << std::endl;
|
||||
return false;
|
||||
}
|
||||
for (const auto& arg : run_json["args"]) {
|
||||
if (!arg.is_string()) {
|
||||
std::cerr << "Error: All arguments must be strings" << std::endl;
|
||||
return false;
|
||||
}
|
||||
args.push_back(arg);
|
||||
}
|
||||
}
|
||||
|
||||
// Environment variables are optional
|
||||
env.clear();
|
||||
if (run_json.contains("env")) {
|
||||
if (!run_json["env"].is_object()) {
|
||||
std::cerr << "Error: 'env' field must be an object" << std::endl;
|
||||
return false;
|
||||
}
|
||||
for (auto it = run_json["env"].begin(); it != run_json["env"].end(); ++it) {
|
||||
if (!it.value().is_string()) {
|
||||
std::cerr << "Error: All environment variable values must be strings" << std::endl;
|
||||
return false;
|
||||
}
|
||||
env[it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
|
||||
// Options are optional
|
||||
silent = false;
|
||||
interactive = false;
|
||||
if (run_json.contains("options")) {
|
||||
if (!run_json["options"].is_object()) {
|
||||
std::cerr << "Error: 'options' field must be an object" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (run_json["options"].contains("silent")) {
|
||||
if (!run_json["options"]["silent"].is_boolean()) {
|
||||
std::cerr << "Error: 'silent' option must be a boolean" << std::endl;
|
||||
return false;
|
||||
}
|
||||
silent = run_json["options"]["silent"];
|
||||
}
|
||||
|
||||
if (run_json["options"].contains("interactive")) {
|
||||
if (!run_json["options"]["interactive"].is_boolean()) {
|
||||
std::cerr << "Error: 'interactive' option must be a boolean" << std::endl;
|
||||
return false;
|
||||
}
|
||||
interactive = run_json["options"]["interactive"];
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error parsing JSON: " << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int Runner::execute_local(
|
||||
const std::string& command,
|
||||
const std::vector<std::string>& args,
|
||||
const std::map<std::string, std::string>& env,
|
||||
bool silent,
|
||||
bool interactive,
|
||||
std::string* output,
|
||||
bool capture_output
|
||||
) {
|
||||
int pipefd[2];
|
||||
if (capture_output && pipe(pipefd) == -1) {
|
||||
std::cerr << "Error creating pipe: " << strerror(errno) << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
std::cerr << "Fork failed: " << strerror(errno) << std::endl;
|
||||
if (capture_output) {
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// Child process
|
||||
|
||||
// Set up output redirection if needed
|
||||
if (capture_output) {
|
||||
close(pipefd[0]); // Close read end
|
||||
dup2(pipefd[1], STDOUT_FILENO);
|
||||
dup2(pipefd[1], STDERR_FILENO);
|
||||
close(pipefd[1]);
|
||||
} else if (silent) {
|
||||
int devnull = open("/dev/null", O_WRONLY);
|
||||
if (devnull != -1) {
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
dup2(devnull, STDERR_FILENO);
|
||||
close(devnull);
|
||||
}
|
||||
}
|
||||
|
||||
// Set environment variables
|
||||
for (const auto& [key, value] : env) {
|
||||
setenv(key.c_str(), value.c_str(), 1);
|
||||
}
|
||||
|
||||
// Prepare arguments
|
||||
std::vector<char*> c_args;
|
||||
c_args.push_back(const_cast<char*>(command.c_str()));
|
||||
for (const auto& arg : args) {
|
||||
c_args.push_back(const_cast<char*>(arg.c_str()));
|
||||
}
|
||||
c_args.push_back(nullptr);
|
||||
|
||||
// Execute command
|
||||
execvp(command.c_str(), c_args.data());
|
||||
|
||||
// If exec fails
|
||||
std::cerr << "exec failed: " << strerror(errno) << std::endl;
|
||||
exit(1);
|
||||
} else {
|
||||
// Parent process
|
||||
if (capture_output) {
|
||||
close(pipefd[1]); // Close write end
|
||||
|
||||
std::array<char, 4096> buffer;
|
||||
std::ostringstream oss;
|
||||
|
||||
ssize_t bytes_read;
|
||||
while ((bytes_read = read(pipefd[0], buffer.data(), buffer.size() - 1)) > 0) {
|
||||
buffer[bytes_read] = '\0';
|
||||
oss << buffer.data();
|
||||
|
||||
if (!silent) {
|
||||
std::cout << buffer.data();
|
||||
}
|
||||
}
|
||||
|
||||
close(pipefd[0]);
|
||||
|
||||
if (output) {
|
||||
*output = oss.str();
|
||||
}
|
||||
}
|
||||
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
if (WIFEXITED(status)) {
|
||||
return WEXITSTATUS(status);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string find_ssh_key_for_user() {
|
||||
const char* home_dir = getenv("HOME");
|
||||
if (!home_dir) {
|
||||
struct passwd* pw = getpwuid(getuid());
|
||||
if (pw) {
|
||||
home_dir = pw->pw_dir;
|
||||
}
|
||||
}
|
||||
|
||||
if (!home_dir) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Common SSH key locations
|
||||
std::vector<std::string> key_paths = {
|
||||
std::string(home_dir) + "/.ssh/id_rsa",
|
||||
std::string(home_dir) + "/.ssh/id_ed25519",
|
||||
std::string(home_dir) + "/.ssh/id_ecdsa",
|
||||
std::string(home_dir) + "/.ssh/id_dsa"
|
||||
};
|
||||
|
||||
for (const auto& path : key_paths) {
|
||||
if (access(path.c_str(), F_OK) == 0) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
int Runner::execute_ssh(
|
||||
const nlohmann::json& ssh_config,
|
||||
const std::string& command,
|
||||
const std::vector<std::string>& args,
|
||||
const std::map<std::string, std::string>& env,
|
||||
bool silent,
|
||||
bool interactive,
|
||||
std::string* output,
|
||||
bool capture_output
|
||||
) {
|
||||
if (!ssh_config.contains("host") || !ssh_config["host"].is_string()) {
|
||||
std::cerr << "Error: SSH configuration requires 'host' field" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
std::string host = ssh_config["host"];
|
||||
|
||||
int port = 22;
|
||||
if (ssh_config.contains("port")) {
|
||||
if (ssh_config["port"].is_number_integer()) {
|
||||
port = ssh_config["port"];
|
||||
} else {
|
||||
std::cerr << "Error: SSH 'port' must be an integer" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::string user = "";
|
||||
if (ssh_config.contains("user") && ssh_config["user"].is_string()) {
|
||||
user = ssh_config["user"];
|
||||
} else {
|
||||
// Get current username as default
|
||||
char username[256];
|
||||
if (getlogin_r(username, sizeof(username)) == 0) {
|
||||
user = username;
|
||||
} else {
|
||||
struct passwd* pw = getpwuid(getuid());
|
||||
if (pw) {
|
||||
user = pw->pw_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string key_path = "";
|
||||
if (ssh_config.contains("key") && ssh_config["key"].is_string()) {
|
||||
std::string key = ssh_config["key"];
|
||||
if (key == "auto") {
|
||||
key_path = find_ssh_key_for_user();
|
||||
if (key_path.empty()) {
|
||||
std::cerr << "Error: Could not find SSH key automatically" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
key_path = key;
|
||||
}
|
||||
} else {
|
||||
key_path = find_ssh_key_for_user();
|
||||
}
|
||||
|
||||
// Initialize SSH session
|
||||
ssh_session session = ssh_new();
|
||||
if (session == nullptr) {
|
||||
std::cerr << "Error: Failed to create SSH session" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set SSH options
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, host.c_str());
|
||||
ssh_options_set(session, SSH_OPTIONS_PORT, &port);
|
||||
ssh_options_set(session, SSH_OPTIONS_USER, user.c_str());
|
||||
|
||||
// Connect to server
|
||||
int rc = ssh_connect(session);
|
||||
if (rc != SSH_OK) {
|
||||
std::cerr << "Error connecting to " << host << ": " << ssh_get_error(session) << std::endl;
|
||||
ssh_free(session);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Authenticate with key
|
||||
if (!key_path.empty()) {
|
||||
rc = ssh_userauth_publickey_auto(session, nullptr, key_path.empty() ? nullptr : key_path.c_str());
|
||||
if (rc != SSH_AUTH_SUCCESS) {
|
||||
std::cerr << "Error authenticating with key: " << ssh_get_error(session) << std::endl;
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
// Try default authentication methods
|
||||
rc = ssh_userauth_publickey_auto(session, nullptr, nullptr);
|
||||
if (rc != SSH_AUTH_SUCCESS) {
|
||||
std::cerr << "Error authenticating: " << ssh_get_error(session) << std::endl;
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare command
|
||||
std::ostringstream cmd_stream;
|
||||
|
||||
// Add environment variables
|
||||
for (const auto& [key, value] : env) {
|
||||
cmd_stream << "export " << key << "=\"" << value << "\"; ";
|
||||
}
|
||||
|
||||
// Add command and args
|
||||
cmd_stream << command;
|
||||
for (const auto& arg : args) {
|
||||
cmd_stream << " " << arg;
|
||||
}
|
||||
|
||||
std::string full_command = cmd_stream.str();
|
||||
|
||||
int exit_status = -1;
|
||||
ssh_channel channel = ssh_channel_new(session);
|
||||
if (channel == nullptr) {
|
||||
std::cerr << "Error creating SSH channel: " << ssh_get_error(session) << std::endl;
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = ssh_channel_open_session(channel);
|
||||
if (rc != SSH_OK) {
|
||||
std::cerr << "Error opening SSH session: " << ssh_get_error(session) << std::endl;
|
||||
ssh_channel_free(channel);
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (interactive) {
|
||||
// Request a pseudo-terminal for interactive commands
|
||||
rc = ssh_channel_request_pty(channel);
|
||||
if (rc != SSH_OK) {
|
||||
std::cerr << "Error requesting PTY: " << ssh_get_error(session) << std::endl;
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the command
|
||||
rc = ssh_channel_request_exec(channel, full_command.c_str());
|
||||
if (rc != SSH_OK) {
|
||||
std::cerr << "Error executing command: " << ssh_get_error(session) << std::endl;
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Read command output
|
||||
char buffer[4096];
|
||||
int nbytes;
|
||||
std::ostringstream oss;
|
||||
|
||||
// Read from stdout
|
||||
while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) {
|
||||
if (capture_output) {
|
||||
oss.write(buffer, nbytes);
|
||||
}
|
||||
|
||||
if (!silent) {
|
||||
std::cout.write(buffer, nbytes);
|
||||
}
|
||||
}
|
||||
|
||||
// Read from stderr if needed
|
||||
while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 1)) > 0) {
|
||||
if (capture_output) {
|
||||
oss.write(buffer, nbytes);
|
||||
}
|
||||
|
||||
if (!silent) {
|
||||
std::cerr.write(buffer, nbytes);
|
||||
}
|
||||
}
|
||||
|
||||
if (capture_output && output) {
|
||||
*output = oss.str();
|
||||
}
|
||||
|
||||
// Get exit status
|
||||
ssh_channel_send_eof(channel);
|
||||
exit_status = ssh_channel_get_exit_status(channel);
|
||||
|
||||
// Clean up
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
|
||||
return exit_status;
|
||||
}
|
||||
|
||||
} // namespace dropshell
|
100
runner/test.sh
Executable file
100
runner/test.sh
Executable file
@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Simple test script to verify that the runner works properly
|
||||
|
||||
echo "Testing Runner - Dropshell Command Execution Library"
|
||||
echo "=================================================="
|
||||
echo
|
||||
|
||||
# Ensure the binary is built
|
||||
if [ ! -f "build/runner" ]; then
|
||||
echo "Building the project first..."
|
||||
./build.sh
|
||||
fi
|
||||
|
||||
# Run a simple echo command
|
||||
echo "Test 1: Basic command execution"
|
||||
JSON='{"command":"echo","args":["Hello from runner test!"]}'
|
||||
BASE64=$(echo -n "$JSON" | base64)
|
||||
echo "JSON: $JSON"
|
||||
echo "Running command..."
|
||||
build/runner "$BASE64"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Test 1 passed"
|
||||
else
|
||||
echo "❌ Test 1 failed"
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
|
||||
# Test with environment variables
|
||||
echo "Test 2: Environment variables"
|
||||
JSON='{"command":"bash","args":["-c","echo Value of TEST_VAR: $TEST_VAR"],"env":{"TEST_VAR":"This is a test environment variable"}}'
|
||||
BASE64=$(echo -n "$JSON" | base64)
|
||||
echo "JSON: $JSON"
|
||||
echo "Running command..."
|
||||
build/runner "$BASE64"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Test 2 passed"
|
||||
else
|
||||
echo "❌ Test 2 failed"
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
|
||||
# Test silent mode
|
||||
echo "Test 3: Silent mode"
|
||||
JSON='{"command":"echo","args":["This should not be displayed"],"options":{"silent":true}}'
|
||||
BASE64=$(echo -n "$JSON" | base64)
|
||||
echo "JSON: $JSON"
|
||||
echo "Running command (no output expected)..."
|
||||
OUTPUT=$(build/runner "$BASE64")
|
||||
if [ $? -eq 0 ] && [ -z "$OUTPUT" ]; then
|
||||
echo "✅ Test 3 passed"
|
||||
else
|
||||
echo "❌ Test 3 failed, unexpected output: '$OUTPUT'"
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
|
||||
# Test return code handling
|
||||
echo "Test 4: Error return code"
|
||||
JSON='{"command":"false"}'
|
||||
BASE64=$(echo -n "$JSON" | base64)
|
||||
echo "JSON: $JSON"
|
||||
echo "Running command (should fail)..."
|
||||
build/runner "$BASE64" || true
|
||||
RC=$?
|
||||
if [ $RC -ne 0 ]; then
|
||||
echo "✅ Test 4 passed (command correctly reported failure)"
|
||||
else
|
||||
echo "❌ Test 4 failed (command should have returned non-zero)"
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
|
||||
# Optional SSH test (disabled by default)
|
||||
# Set ENABLE_SSH_TEST=1 to enable this test
|
||||
if [ "${ENABLE_SSH_TEST}" = "1" ]; then
|
||||
echo "Test 5: SSH to localhost (make sure SSH server is running and you can connect to localhost)"
|
||||
if [ -f "examples/local_ssh.json" ]; then
|
||||
echo "Using examples/local_ssh.json for the test..."
|
||||
./run.sh examples/local_ssh.json || {
|
||||
echo "❌ Test 5 failed"
|
||||
echo "Note: This test requires SSH server running on localhost and proper key-based authentication."
|
||||
echo "Check your SSH configuration or disable this test."
|
||||
exit 1
|
||||
}
|
||||
echo "✅ Test 5 passed"
|
||||
else
|
||||
echo "❌ Test 5 failed: examples/local_ssh.json not found"
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
else
|
||||
echo "Test 5: SSH test disabled (set ENABLE_SSH_TEST=1 to enable)"
|
||||
echo
|
||||
fi
|
||||
|
||||
echo "All tests passed! 🎉"
|
Loading…
x
Reference in New Issue
Block a user