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