#include "ssh_terminal.h" #include #include #include SshTerminal::SshTerminal() : vte_box_(nullptr), terminal_(nullptr), child_pid_(0), connected_(false) { vte_box_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); terminal_ = VTE_TERMINAL(vte_terminal_new()); gtk_box_pack_start(GTK_BOX(vte_box_), GTK_WIDGET(terminal_), TRUE, TRUE, 0); gtk_widget_show_all(vte_box_); g_signal_connect(terminal_, "child-exited", G_CALLBACK(on_child_exited), this); } SshTerminal::~SshTerminal() { disconnect(); if (vte_box_) { gtk_widget_destroy(vte_box_); vte_box_ = nullptr; } } void SshTerminal::on_child_exited(VteTerminal* terminal, int status, gpointer data) { (void)terminal; auto* self = static_cast(data); self->connected_ = false; self->child_pid_ = 0; if (self->on_status_change) { char msg[128]; snprintf(msg, sizeof(msg), "Process exited with status %d", status); self->on_status_change(false, msg); } } bool SshTerminal::connect(const std::string& host, int port, const std::string& user, const std::string& keyfile, const std::string& password, std::string& out_error) { if (connected_) disconnect(); g_message("Connecting to %s@%s:%d ...", user.c_str(), host.c_str(), port); /* Build ssh command arguments */ std::vector args; /* Use sshpass if password is provided and sshpass is available */ bool use_sshpass = false; if (!password.empty()) { if (g_find_program_in_path("sshpass")) { use_sshpass = true; args.push_back("sshpass"); args.push_back("-e"); g_message("Using sshpass for auto-login"); } else { g_message("sshpass not found, ssh will prompt for password"); } } args.push_back("ssh"); args.push_back("-p"); args.push_back(std::to_string(port)); args.push_back("-o"); args.push_back("StrictHostKeyChecking=no"); args.push_back("-o"); args.push_back("UserKnownHostsFile=/dev/null"); args.push_back("-o"); args.push_back("LogLevel=QUIET"); if (!keyfile.empty()) { args.push_back("-i"); args.push_back(keyfile); g_message("Using keyfile: %s", keyfile.c_str()); } args.push_back(user + "@" + host); /* Convert to char** for spawn */ std::vector c_args; for (auto& arg : args) { c_args.push_back(const_cast(arg.c_str())); } c_args.push_back(nullptr); /* Set up environment with password if using sshpass */ gchar** envp = nullptr; if (use_sshpass) { GHashTable* env_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); /* Copy current environment */ for (char** ep = environ; *ep; ep++) { gchar** parts = g_strsplit(*ep, "=", 2); if (parts[0] && parts[1]) { g_hash_table_insert(env_table, g_strdup(parts[0]), g_strdup(parts[1])); } g_strfreev(parts); } g_hash_table_insert(env_table, g_strdup("SSHPASS"), g_strdup(password.c_str())); /* Convert hash table to envp array */ GHashTableIter iter; gpointer key, value; GArray* env_arr = g_array_new(TRUE, TRUE, sizeof(gchar*)); g_hash_table_iter_init(&iter, env_table); while (g_hash_table_iter_next(&iter, &key, &value)) { gchar* entry = g_strdup_printf("%s=%s", (const char*)key, (const char*)value); g_array_append_val(env_arr, entry); } envp = (gchar**)g_array_free(env_arr, FALSE); g_hash_table_destroy(env_table); } /* Spawn ssh process */ GError* error = nullptr; GPid child_pid = 0; g_message("Spawning: %s", args[0].c_str()); gboolean ok = vte_terminal_spawn_sync( terminal_, VTE_PTY_DEFAULT, nullptr, /* working directory */ c_args.data(), envp, /* environment */ (GSpawnFlags)(G_SPAWN_CHILD_INHERITS_STDIN), nullptr, /* child_setup */ nullptr, /* child_setup_data */ &child_pid, nullptr, /* cancellable */ &error ); if (envp) { for (gchar** p = envp; *p; p++) g_free(*p); g_free(envp); } if (!ok) { out_error = error ? error->message : "Failed to spawn SSH process"; g_warning("spawn failed: %s", out_error.c_str()); if (error) g_error_free(error); return false; } child_pid_ = child_pid; connected_ = true; g_message("SSH process spawned (PID %d)", (int)child_pid); if (on_status_change) on_status_change(true, "Connected to " + user + "@" + host + ":" + std::to_string(port)); return true; } void SshTerminal::disconnect() { if (child_pid_ > 0) { g_message("Killing SSH process (PID %d)", (int)child_pid_); kill(child_pid_, SIGHUP); g_spawn_close_pid(child_pid_); child_pid_ = 0; } connected_ = false; }