diff --git a/runner/runner.cpp b/runner/runner.cpp index 76c14cf..fe8af58 100644 --- a/runner/runner.cpp +++ b/runner/runner.cpp @@ -12,6 +12,217 @@ namespace runner { +namespace { + +ssh_session ssh_connect_and_auth(const sSSHInfo* sshinfo, const std::map& env, std::string* error) { + ssh_session session = ssh_new(); + if (!session) { + if (error) *error = "Failed to create SSH session."; + return nullptr; + } + ssh_options_set(session, SSH_OPTIONS_HOST, sshinfo->host.c_str()); + if (!sshinfo->port.empty()) { + int port = std::stoi(sshinfo->port); + ssh_options_set(session, SSH_OPTIONS_PORT, &port); + } + if (!sshinfo->user.empty()) { + ssh_options_set(session, SSH_OPTIONS_USER, sshinfo->user.c_str()); + } + int rc = ssh_connect(session); + if (rc != SSH_OK) { + if (error) *error = std::string("SSH connection failed: ") + ssh_get_error(session); + ssh_free(session); + return nullptr; + } + rc = ssh_userauth_publickey_auto(session, nullptr, nullptr); + if (rc != SSH_AUTH_SUCCESS) { + auto it = env.find("SSHPASS"); + if (it != env.end()) { + rc = ssh_userauth_password(session, nullptr, it->second.c_str()); + } + } + if (rc != SSH_AUTH_SUCCESS) { + if (error) *error = std::string("SSH authentication failed: ") + ssh_get_error(session); + ssh_disconnect(session); + ssh_free(session); + return nullptr; + } + return session; +} + +std::string ssh_build_remote_command(const std::string& command, const std::vector& args, const std::string& working_dir, const std::map& env) { + std::ostringstream remote_cmd; + for (const auto& kv : env) { + if (kv.first == "SSHPASS") continue; + remote_cmd << kv.first << "='" << kv.second << "' "; + } + if (!working_dir.empty()) { + remote_cmd << "cd '" << working_dir << "' && "; + } + remote_cmd << command; + for (const auto& arg : args) { + remote_cmd << " '" << arg << "'"; + } + return remote_cmd.str(); +} + +int ssh_interactive_shell_session(ssh_session session, ssh_channel channel, const std::string& remote_cmd_str, const std::string& command, std::string* output) { + int rc = ssh_channel_request_pty(channel); + if (rc != SSH_OK) { + if (output) *output = std::string("Failed to request pty: ") + ssh_get_error(session); + return -1; + } + rc = ssh_channel_request_shell(channel); + if (rc != SSH_OK) { + if (output) *output = std::string("Failed to request shell: ") + ssh_get_error(session); + return -1; + } + struct termios orig_termios, raw_termios; + tcgetattr(STDIN_FILENO, &orig_termios); + raw_termios = orig_termios; + cfmakeraw(&raw_termios); + tcsetattr(STDIN_FILENO, TCSANOW, &raw_termios); + if (!command.empty()) { + ssh_channel_write(channel, remote_cmd_str.c_str(), remote_cmd_str.size()); + ssh_channel_write(channel, "\n", 1); + } + int maxfd = STDIN_FILENO > STDOUT_FILENO ? STDIN_FILENO : STDOUT_FILENO; + maxfd = maxfd > ssh_get_fd(session) ? maxfd : ssh_get_fd(session); + char buffer[4096]; + bool done = false; + while (!done) { + fd_set fds_read; + FD_ZERO(&fds_read); + FD_SET(STDIN_FILENO, &fds_read); + FD_SET(ssh_get_fd(session), &fds_read); + int ret = select(maxfd + 1, &fds_read, nullptr, nullptr, nullptr); + if (ret < 0) break; + if (FD_ISSET(STDIN_FILENO, &fds_read)) { + ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer)); + if (n > 0) { + ssh_channel_write(channel, buffer, n); + } else { + ssh_channel_send_eof(channel); + done = true; + } + } + if (FD_ISSET(ssh_get_fd(session), &fds_read)) { + int n = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (n > 0) { + write(STDOUT_FILENO, buffer, n); + } else if (n == 0) { + done = true; + } + } + if (ssh_channel_is_closed(channel) || ssh_channel_is_eof(channel)) { + done = true; + } + } + tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); + return 0; +} + +int ssh_exec_command(ssh_session session, ssh_channel channel, const std::string& remote_cmd_str, bool silent, std::string* output) { + int rc = ssh_channel_request_exec(channel, remote_cmd_str.c_str()); + if (rc != SSH_OK) { + if (output) *output = std::string("Failed to exec remote command: ") + ssh_get_error(session); + return -1; + } + if (output) { + std::ostringstream oss; + char buffer[4096]; + int nbytes; + while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) { + oss.write(buffer, nbytes); + } + *output = oss.str(); + } else if (!silent) { + char buffer[4096]; + int nbytes; + while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) { + write(1, buffer, nbytes); + } + } + return 0; +} + +int local_execute_cmd( + const std::string& command, + const std::vector& args, + const std::string& working_dir, + const std::map& env, + bool silent, + bool interactive, + std::string* output +) { + int pipefd[2]; + bool use_pipe = output && !interactive; + if (use_pipe && pipe(pipefd) == -1) { + perror("pipe"); + return -1; + } + pid_t pid = fork(); + if (pid == -1) { + perror("fork"); + return -1; + } + if (pid == 0) { + if (!working_dir.empty()) { + if (chdir(working_dir.c_str()) != 0) { + perror("chdir"); + exit(-1); + } + } + for (const auto& kv : env) { + setenv(kv.first.c_str(), kv.second.c_str(), 1); + } + if (use_pipe) { + close(pipefd[0]); + dup2(pipefd[1], STDOUT_FILENO); + dup2(pipefd[1], STDERR_FILENO); + close(pipefd[1]); + } else if (silent && !interactive) { + int devnull = open("/dev/null", O_WRONLY); + dup2(devnull, STDOUT_FILENO); + dup2(devnull, STDERR_FILENO); + close(devnull); + } + if (!interactive) { + setsid(); + } + std::vector argv; + argv.push_back(const_cast(command.c_str())); + for (const auto& arg : args) { + argv.push_back(const_cast(arg.c_str())); + } + argv.push_back(nullptr); + execvp(command.c_str(), argv.data()); + perror("execvp"); + exit(-1); + } else { + if (use_pipe) { + close(pipefd[1]); + std::ostringstream oss; + char buf[4096]; + ssize_t n; + while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) { + oss.write(buf, n); + } + close(pipefd[0]); + *output = oss.str(); + } + int status = 0; + waitpid(pid, &status, 0); + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } else { + return -1; + } + } +} + +} // anonymous namespace + int execute_cmd( const std::string& command, const std::vector& args, @@ -23,48 +234,20 @@ int execute_cmd( std::string* output ) { if (sshinfo) { - ssh_session session = ssh_new(); - if (session == nullptr) { - if (output) *output = "Failed to create SSH session."; - return -1; - } - ssh_options_set(session, SSH_OPTIONS_HOST, sshinfo->host.c_str()); - if (!sshinfo->port.empty()) { - int port = std::stoi(sshinfo->port); - ssh_options_set(session, SSH_OPTIONS_PORT, &port); - } - if (!sshinfo->user.empty()) { - ssh_options_set(session, SSH_OPTIONS_USER, sshinfo->user.c_str()); - } - int rc = ssh_connect(session); - if (rc != SSH_OK) { - if (output) *output = std::string("SSH connection failed: ") + ssh_get_error(session); - ssh_free(session); - return -1; - } - // Try public key, then password if needed - rc = ssh_userauth_publickey_auto(session, nullptr, nullptr); - if (rc != SSH_AUTH_SUCCESS) { - // Try password from env["SSHPASS"] if present - auto it = env.find("SSHPASS"); - if (it != env.end()) { - rc = ssh_userauth_password(session, nullptr, it->second.c_str()); - } - } - if (rc != SSH_AUTH_SUCCESS) { - if (output) *output = std::string("SSH authentication failed: ") + ssh_get_error(session); - ssh_disconnect(session); - ssh_free(session); + std::string error; + ssh_session session = ssh_connect_and_auth(sshinfo, env, &error); + if (!session) { + if (output) *output = error; return -1; } ssh_channel channel = ssh_channel_new(session); - if (channel == nullptr) { + if (!channel) { if (output) *output = "Failed to create SSH channel."; ssh_disconnect(session); ssh_free(session); return -1; } - rc = ssh_channel_open_session(channel); + int rc = ssh_channel_open_session(channel); if (rc != SSH_OK) { if (output) *output = std::string("Failed to open SSH channel: ") + ssh_get_error(session); ssh_channel_free(channel); @@ -72,190 +255,22 @@ int execute_cmd( ssh_free(session); return -1; } - std::ostringstream remote_cmd; - for (const auto& kv : env) { - if (kv.first == "SSHPASS") continue; - remote_cmd << kv.first << "='" << kv.second << "' "; - } - if (!working_dir.empty()) { - remote_cmd << "cd '" << working_dir << "' && "; - } - remote_cmd << command; - for (const auto& arg : args) { - remote_cmd << " '" << arg << "'"; - } - std::string remote_cmd_str = remote_cmd.str(); + std::string remote_cmd_str = ssh_build_remote_command(command, args, working_dir, env); + int ret = 0; if (interactive) { - rc = ssh_channel_request_pty(channel); - if (rc != SSH_OK) { - if (output) *output = std::string("Failed to request pty: ") + ssh_get_error(session); - ssh_channel_close(channel); - ssh_channel_free(channel); - ssh_disconnect(session); - ssh_free(session); - return -1; - } - rc = ssh_channel_request_shell(channel); - if (rc != SSH_OK) { - if (output) *output = std::string("Failed to request shell: ") + ssh_get_error(session); - ssh_channel_close(channel); - ssh_channel_free(channel); - ssh_disconnect(session); - ssh_free(session); - return -1; - } - // Set local terminal to raw mode - struct termios orig_termios, raw_termios; - tcgetattr(STDIN_FILENO, &orig_termios); - raw_termios = orig_termios; - cfmakeraw(&raw_termios); - tcsetattr(STDIN_FILENO, TCSANOW, &raw_termios); - // If a command is provided, send it to the shell - if (!command.empty()) { - ssh_channel_write(channel, remote_cmd_str.c_str(), remote_cmd_str.size()); - ssh_channel_write(channel, "\n", 1); - } - // Forward input/output using select() - int maxfd = STDIN_FILENO > STDOUT_FILENO ? STDIN_FILENO : STDOUT_FILENO; - maxfd = maxfd > ssh_get_fd(session) ? maxfd : ssh_get_fd(session); - char buffer[4096]; - bool done = false; - while (!done) { - fd_set fds_read; - FD_ZERO(&fds_read); - FD_SET(STDIN_FILENO, &fds_read); - FD_SET(ssh_get_fd(session), &fds_read); - int ret = select(maxfd + 1, &fds_read, nullptr, nullptr, nullptr); - if (ret < 0) break; - // Read from stdin, send to channel - if (FD_ISSET(STDIN_FILENO, &fds_read)) { - ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer)); - if (n > 0) { - ssh_channel_write(channel, buffer, n); - } else { - ssh_channel_send_eof(channel); - done = true; - } - } - // Read from channel, write to stdout - if (FD_ISSET(ssh_get_fd(session), &fds_read)) { - int n = ssh_channel_read(channel, buffer, sizeof(buffer), 0); - if (n > 0) { - write(STDOUT_FILENO, buffer, n); - } else if (n == 0) { - done = true; - } - } - if (ssh_channel_is_closed(channel) || ssh_channel_is_eof(channel)) { - done = true; - } - } - // Restore terminal - tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); + ret = ssh_interactive_shell_session(session, channel, remote_cmd_str, command, output); } else { - rc = ssh_channel_request_exec(channel, remote_cmd_str.c_str()); - if (rc != SSH_OK) { - if (output) *output = std::string("Failed to exec remote command: ") + ssh_get_error(session); - ssh_channel_close(channel); - ssh_channel_free(channel); - ssh_disconnect(session); - ssh_free(session); - return -1; - } - if (output) { - std::ostringstream oss; - char buffer[4096]; - int nbytes; - while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) { - oss.write(buffer, nbytes); - } - *output = oss.str(); - } else if (!silent) { - char buffer[4096]; - int nbytes; - while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) { - write(1, buffer, nbytes); - } - } + ret = ssh_exec_command(session, channel, remote_cmd_str, silent, output); } ssh_channel_send_eof(channel); ssh_channel_close(channel); ssh_channel_free(channel); ssh_disconnect(session); ssh_free(session); - return 0; + return ret; } else { - int pipefd[2]; - bool use_pipe = output && !interactive; - if (use_pipe && pipe(pipefd) == -1) { - perror("pipe"); - return -1; - } - - pid_t pid = fork(); - if (pid == -1) { - perror("fork"); - return -1; - } - if (pid == 0) { - // Child process - if (!working_dir.empty()) { - if (chdir(working_dir.c_str()) != 0) { - perror("chdir"); - exit(-1); - } - } - // Set environment variables - for (const auto& kv : env) { - setenv(kv.first.c_str(), kv.second.c_str(), 1); - } - if (use_pipe) { - close(pipefd[0]); - dup2(pipefd[1], STDOUT_FILENO); - dup2(pipefd[1], STDERR_FILENO); - close(pipefd[1]); - } else if (silent && !interactive) { - int devnull = open("/dev/null", O_WRONLY); - dup2(devnull, STDOUT_FILENO); - dup2(devnull, STDERR_FILENO); - close(devnull); - } - if (!interactive) { - // Detach from terminal if not interactive - setsid(); - } - std::vector argv; - argv.push_back(const_cast(command.c_str())); - for (const auto& arg : args) { - argv.push_back(const_cast(arg.c_str())); - } - argv.push_back(nullptr); - execvp(command.c_str(), argv.data()); - perror("execvp"); - exit(-1); - } else { - // Parent process - if (use_pipe) { - close(pipefd[1]); - std::ostringstream oss; - char buf[4096]; - ssize_t n; - while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) { - oss.write(buf, n); - } - close(pipefd[0]); - *output = oss.str(); - } - int status = 0; - waitpid(pid, &status, 0); - if (WIFEXITED(status)) { - return WEXITSTATUS(status); - } else { - return -1; - } - } + return local_execute_cmd(command, args, working_dir, env, silent, interactive, output); } -} - +} } // namespace runner \ No newline at end of file diff --git a/runner/runner_demo.cpp b/runner/runner_demo.cpp index 5ace633..a08c91a 100644 --- a/runner/runner_demo.cpp +++ b/runner/runner_demo.cpp @@ -62,8 +62,7 @@ // return ret; // } - -int main(int argc, char* argv[]) { +void test_docker() { std::string command = "docker"; std::vector args = {"exec", "-it", "squashkiwi", "/bin/bash"}; std::string working_dir = "."; @@ -78,3 +77,24 @@ int main(int argc, char* argv[]) { runner::execute_cmd(command, args, working_dir, env, silent, interactive, &ssh); } +void test_ssh() { + std::string command = "bash"; + std::vector args = {"-c", "ls -l && echo $WHATSUP"}; + std::string working_dir = "/home"; + std::map env = {{"WHATSUP", "Waaaaattttsssuuuppppp!"}}; + bool silent = false; + bool interactive = false; + runner::sSSHInfo ssh; + ssh.host = "10.10.10.13"; + ssh.user = "katie"; + ssh.port = "22"; + + runner::execute_cmd(command, args, working_dir, env, silent, interactive, &ssh); +} + + +int main(int argc, char* argv[]) { + test_ssh(); + +} +