diff --git a/Makefile b/Makefile index 5808f74..dd2f9cc 100644 --- a/Makefile +++ b/Makefile @@ -267,8 +267,6 @@ INSTALL_STRIP_PROGRAM = $(install_sh) -c -s LDFLAGS = LIBOBJS = LIBS = -LIBSSH_CFLAGS = -LIBSSH_LIBS = -lssh LTLIBOBJS = MAKEINFO = ${SHELL} '/home/administrator/Programms/ssh/x3-ssh/build-aux/missing' makeinfo MKDIR_P = /usr/bin/mkdir -p diff --git a/Makefile.in b/Makefile.in index 815719b..6941188 100644 --- a/Makefile.in +++ b/Makefile.in @@ -267,8 +267,6 @@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ LDFLAGS = @LDFLAGS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ -LIBSSH_CFLAGS = @LIBSSH_CFLAGS@ -LIBSSH_LIBS = @LIBSSH_LIBS@ LTLIBOBJS = @LTLIBOBJS@ MAKEINFO = @MAKEINFO@ MKDIR_P = @MKDIR_P@ diff --git a/configure b/configure index 39fd865..82e02a9 100755 --- a/configure +++ b/configure @@ -613,8 +613,6 @@ ac_subst_vars='am__EXEEXT_FALSE am__EXEEXT_TRUE LTLIBOBJS LIBOBJS -LIBSSH_LIBS -LIBSSH_CFLAGS VTE_LIBS VTE_CFLAGS GTK_LIBS @@ -732,9 +730,7 @@ PKG_CONFIG_LIBDIR GTK_CFLAGS GTK_LIBS VTE_CFLAGS -VTE_LIBS -LIBSSH_CFLAGS -LIBSSH_LIBS' +VTE_LIBS' # Initialize some variables set by options. @@ -1382,9 +1378,6 @@ Some influential environment variables: GTK_LIBS linker flags for GTK, overriding pkg-config VTE_CFLAGS C compiler flags for VTE, overriding pkg-config VTE_LIBS linker flags for VTE, overriding pkg-config - LIBSSH_CFLAGS - C compiler flags for LIBSSH, overriding pkg-config - LIBSSH_LIBS linker flags for LIBSSH, overriding pkg-config Use these variables to override the choices made by 'configure' or to help it to find libraries and programs with nonstandard names/locations. @@ -5120,97 +5113,6 @@ printf "%s\n" "yes" >&6; } fi -pkg_failed=no -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libssh >= 0.9" >&5 -printf %s "checking for libssh >= 0.9... " >&6; } - -if test -n "$LIBSSH_CFLAGS"; then - pkg_cv_LIBSSH_CFLAGS="$LIBSSH_CFLAGS" - elif test -n "$PKG_CONFIG"; then - if test -n "$PKG_CONFIG" && \ - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libssh >= 0.9\""; } >&5 - ($PKG_CONFIG --exists --print-errors "libssh >= 0.9") 2>&5 - ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then - pkg_cv_LIBSSH_CFLAGS=`$PKG_CONFIG --cflags "libssh >= 0.9" 2>/dev/null` - test "x$?" != "x0" && pkg_failed=yes -else - pkg_failed=yes -fi - else - pkg_failed=untried -fi -if test -n "$LIBSSH_LIBS"; then - pkg_cv_LIBSSH_LIBS="$LIBSSH_LIBS" - elif test -n "$PKG_CONFIG"; then - if test -n "$PKG_CONFIG" && \ - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libssh >= 0.9\""; } >&5 - ($PKG_CONFIG --exists --print-errors "libssh >= 0.9") 2>&5 - ac_status=$? - printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then - pkg_cv_LIBSSH_LIBS=`$PKG_CONFIG --libs "libssh >= 0.9" 2>/dev/null` - test "x$?" != "x0" && pkg_failed=yes -else - pkg_failed=yes -fi - else - pkg_failed=untried -fi - - - -if test $pkg_failed = yes; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } - -if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then - _pkg_short_errors_supported=yes -else - _pkg_short_errors_supported=no -fi - if test $_pkg_short_errors_supported = yes; then - LIBSSH_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libssh >= 0.9" 2>&1` - else - LIBSSH_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libssh >= 0.9" 2>&1` - fi - # Put the nasty error message in config.log where it belongs - echo "$LIBSSH_PKG_ERRORS" >&5 - - as_fn_error $? "Package requirements (libssh >= 0.9) were not met: - -$LIBSSH_PKG_ERRORS - -Consider adjusting the PKG_CONFIG_PATH environment variable if you -installed software in a non-standard prefix. - -Alternatively, you may set the environment variables LIBSSH_CFLAGS -and LIBSSH_LIBS to avoid the need to call pkg-config. -See the pkg-config man page for more details." "$LINENO" 5 -elif test $pkg_failed = untried; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} -as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it -is in your PATH or set the PKG_CONFIG environment variable to the full -path to pkg-config. - -Alternatively, you may set the environment variables LIBSSH_CFLAGS -and LIBSSH_LIBS to avoid the need to call pkg-config. -See the pkg-config man page for more details. - -To get pkg-config, see . -See 'config.log' for more details" "$LINENO" 5; } -else - LIBSSH_CFLAGS=$pkg_cv_LIBSSH_CFLAGS - LIBSSH_LIBS=$pkg_cv_LIBSSH_LIBS - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -printf "%s\n" "yes" >&6; } - -fi - ac_config_files="$ac_config_files Makefile src/Makefile" cat >confcache <<\_ACEOF diff --git a/configure.ac b/configure.ac index d56dc4e..49b9dbe 100644 --- a/configure.ac +++ b/configure.ac @@ -8,7 +8,6 @@ AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory]) PKG_CHECK_MODULES([GTK], [gtk+-3.0 >= 3.24]) PKG_CHECK_MODULES([VTE], [vte-2.91 >= 0.50]) -PKG_CHECK_MODULES([LIBSSH], [libssh >= 0.9]) AC_CONFIG_FILES([Makefile src/Makefile]) AC_OUTPUT diff --git a/src/Makefile b/src/Makefile index 79feffe..6c095ae 100644 --- a/src/Makefile +++ b/src/Makefile @@ -107,8 +107,7 @@ am_x3_ssh_OBJECTS = x3_ssh-main.$(OBJEXT) \ x3_ssh-ssh_client.$(OBJEXT) x3_ssh-key_store.$(OBJEXT) x3_ssh_OBJECTS = $(am_x3_ssh_OBJECTS) am__DEPENDENCIES_1 = -x3_ssh_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ - $(am__DEPENDENCIES_1) +x3_ssh_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) x3_ssh_LINK = $(CXXLD) $(x3_ssh_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ AM_V_P = $(am__v_P_$(V)) @@ -234,8 +233,6 @@ INSTALL_STRIP_PROGRAM = $(install_sh) -c -s LDFLAGS = LIBOBJS = LIBS = -LIBSSH_CFLAGS = -LIBSSH_LIBS = -lssh LTLIBOBJS = MAKEINFO = ${SHELL} '/home/administrator/Programms/ssh/x3-ssh/build-aux/missing' makeinfo MKDIR_P = /usr/bin/mkdir -p @@ -313,14 +310,12 @@ x3_ssh_CXXFLAGS = \ -std=c++17 \ $(GTK_CFLAGS) \ $(VTE_CFLAGS) \ - $(LIBSSH_CFLAGS) \ -DLOCALEDIR=\"$(localedir)\" \ -DICONDIR=\"$(datadir)/icons\" x3_ssh_LDADD = \ $(GTK_LIBS) \ - $(VTE_LIBS) \ - $(LIBSSH_LIBS) + $(VTE_LIBS) iconsdir = $(datadir)/icons dist_icons_DATA = ../icons/x3-ssh.png diff --git a/src/Makefile.am b/src/Makefile.am index 9e03d25..fdc6bba 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,14 +11,12 @@ x3_ssh_CXXFLAGS = \ -std=c++17 \ $(GTK_CFLAGS) \ $(VTE_CFLAGS) \ - $(LIBSSH_CFLAGS) \ -DLOCALEDIR=\"$(localedir)\" \ -DICONDIR=\"$(datadir)/icons\" x3_ssh_LDADD = \ $(GTK_LIBS) \ - $(VTE_LIBS) \ - $(LIBSSH_LIBS) + $(VTE_LIBS) iconsdir = $(datadir)/icons dist_icons_DATA = ../icons/x3-ssh.png diff --git a/src/Makefile.in b/src/Makefile.in index bbb553a..4641ffe 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -107,8 +107,7 @@ am_x3_ssh_OBJECTS = x3_ssh-main.$(OBJEXT) \ x3_ssh-ssh_client.$(OBJEXT) x3_ssh-key_store.$(OBJEXT) x3_ssh_OBJECTS = $(am_x3_ssh_OBJECTS) am__DEPENDENCIES_1 = -x3_ssh_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ - $(am__DEPENDENCIES_1) +x3_ssh_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) x3_ssh_LINK = $(CXXLD) $(x3_ssh_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ AM_V_P = $(am__v_P_@AM_V@) @@ -234,8 +233,6 @@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ LDFLAGS = @LDFLAGS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ -LIBSSH_CFLAGS = @LIBSSH_CFLAGS@ -LIBSSH_LIBS = @LIBSSH_LIBS@ LTLIBOBJS = @LTLIBOBJS@ MAKEINFO = @MAKEINFO@ MKDIR_P = @MKDIR_P@ @@ -313,14 +310,12 @@ x3_ssh_CXXFLAGS = \ -std=c++17 \ $(GTK_CFLAGS) \ $(VTE_CFLAGS) \ - $(LIBSSH_CFLAGS) \ -DLOCALEDIR=\"$(localedir)\" \ -DICONDIR=\"$(datadir)/icons\" x3_ssh_LDADD = \ $(GTK_LIBS) \ - $(VTE_LIBS) \ - $(LIBSSH_LIBS) + $(VTE_LIBS) iconsdir = $(datadir)/icons dist_icons_DATA = ../icons/x3-ssh.png diff --git a/src/key_store.cpp b/src/key_store.cpp index 9635feb..296c9d8 100644 --- a/src/key_store.cpp +++ b/src/key_store.cpp @@ -4,7 +4,6 @@ #include #include #include -#include static std::string trim(const std::string& s) { size_t a = s.find_first_not_of(" \t\r\n"); @@ -128,15 +127,14 @@ bool KeyStore::generate_keypair(const std::string& name, int bits) { } bool KeyStore::import_key(const std::string& name, const std::string& privkey_path) { - ssh_key key = nullptr; - if (ssh_pki_import_privkey_file(privkey_path.c_str(), nullptr, nullptr, nullptr, &key) != SSH_OK) { - return false; - } - ssh_key_free(key); + /* Validate key by running ssh-keygen -l */ + std::string check_cmd = "ssh-keygen -l -f \"" + privkey_path + "\" >/dev/null 2>&1"; + int rc = system(check_cmd.c_str()); + if (rc != 0) return false; std::string dest = keys_dir_ + "/" + name; std::string cp_cmd = "cp \"" + privkey_path + "\" \"" + dest + "\" && chmod 600 \"" + dest + "\""; - int rc = system(cp_cmd.c_str()); + rc = system(cp_cmd.c_str()); if (rc != 0) return false; std::string pub_src = privkey_path + ".pub"; diff --git a/src/ssh_client.cpp b/src/ssh_client.cpp index 43f6268..317196f 100644 --- a/src/ssh_client.cpp +++ b/src/ssh_client.cpp @@ -315,8 +315,7 @@ void SshClientWindow::disconnect() { void SshClientWindow::connect_to_server(const std::string& host, int port, const std::string& user, const std::string& keyfile, - const std::string& password, const std::string& name, - const std::string& error_msg) { + const std::string& password, const std::string& name) { g_message("connect_to_server: %s@%s:%d (key=%s, saved_pw=%s)", user.c_str(), host.c_str(), port, @@ -346,14 +345,7 @@ void SshClientWindow::connect_to_server(const std::string& host, int port, /* Remove page FIRST, then delete terminal */ gtk_notebook_remove_page(GTK_NOTEBOOK(notebook_), idx); delete term; - - /* If password auth (no keyfile), re-show password dialog with error */ - if (keyfile.empty()) { - std::string err = ssh_error.empty() ? "Authentication failed" : ssh_error; - show_password_dialog(host, port, user, keyfile, name, err); - } else { - show_notification("Failed to connect to " + host + ": " + ssh_error, true); - } + show_notification("Failed to connect to " + host + ": " + ssh_error, true); } else { update_status("Connected to " + user + "@" + host); show_notification("Server connected: " + user + "@" + host + ":" + std::to_string(port)); @@ -377,20 +369,13 @@ void SshClientWindow::on_quick_connect(GtkWidget*, gpointer d) { int sel = gtk_combo_box_get_active(GTK_COMBO_BOX(self->server_combo_)); if (sel > 0 && sel <= (int)self->server_store_.servers().size()) { const auto& s = self->server_store_.servers()[sel - 1]; - if (s.use_key && !s.keyfile.empty()) { - self->connect_to_server(s.host, s.port, s.user, s.keyfile, "", s.name); - return; - } - std::string pw = s.save_password ? s.password : ""; - if (pw.empty()) { - self->show_password_dialog(s.host, s.port, s.user, s.keyfile, s.name); - } else { - self->connect_to_server(s.host, s.port, s.user, s.keyfile, pw, s.name); - } + /* Pass saved password if available, otherwise ssh prompts interactively */ + std::string pw = (s.save_password) ? s.password : ""; + self->connect_to_server(s.host, s.port, s.user, s.keyfile, pw, s.name); return; } - self->show_password_dialog(host, port, u, "", host); + self->connect_to_server(host, port, u, "", "", host); } /* ─── server row activated ────────────────────────────────── */ @@ -401,16 +386,9 @@ void SshClientWindow::on_server_row_activated(GtkListBox*, GtkListBoxRow* row, g if (idx < 0 || idx >= (int)self->server_store_.servers().size()) return; const auto& s = self->server_store_.servers()[idx]; - if (s.use_key && !s.keyfile.empty()) { - self->connect_to_server(s.host, s.port, s.user, s.keyfile, "", s.name); - } else { - std::string pw = s.save_password ? s.password : ""; - if (pw.empty()) { - self->show_password_dialog(s.host, s.port, s.user, s.keyfile, s.name); - } else { - self->connect_to_server(s.host, s.port, s.user, s.keyfile, pw, s.name); - } - } + /* Pass saved password if available, otherwise ssh prompts interactively */ + std::string pw = (s.save_password) ? s.password : ""; + self->connect_to_server(s.host, s.port, s.user, s.keyfile, pw, s.name); } /* ─── server right-click context menu ─────────────────────── */ @@ -432,18 +410,14 @@ gboolean SshClientWindow::on_server_row_button(GtkWidget*, GdkEventButton* event /* Connect item */ GtkWidget* connect_item = gtk_menu_item_new_with_label("Connect"); - struct ConnectCtx { SshClientWindow* w; int i; bool uk; bool sp; }; - auto* connect_ctx = new ConnectCtx{self, idx, use_key, has_saved_pw}; + struct ConnectCtx { SshClientWindow* w; int i; }; + auto* connect_ctx = new ConnectCtx{self, idx}; g_signal_connect_data(connect_item, "activate", G_CALLBACK(+[](GtkWidget*, gpointer d) { auto* c = static_cast(d); const auto& sv = c->w->server_store_.servers()[c->i]; - if (c->uk) { - c->w->connect_to_server(sv.host, sv.port, sv.user, sv.keyfile, "", sv.name); - } else if (c->sp) { - c->w->connect_to_server(sv.host, sv.port, sv.user, sv.keyfile, sv.password, sv.name); - } else { - c->w->show_password_dialog(sv.host, sv.port, sv.user, sv.keyfile, sv.name); - } + /* Pass saved password if available, otherwise ssh prompts interactively */ + std::string pw = (sv.save_password) ? sv.password : ""; + c->w->connect_to_server(sv.host, sv.port, sv.user, sv.keyfile, pw, sv.name); delete c; }), connect_ctx, nullptr, (GConnectFlags)0); gtk_menu_shell_append(GTK_MENU_SHELL(menu), connect_item); @@ -486,81 +460,7 @@ void SshClientWindow::on_add_server(GtkWidget*, gpointer d) { self->show_server_dialog(-1); } -/* ─── password dialog ─────────────────────────────────────── */ -void SshClientWindow::show_password_dialog(const std::string& host, int port, - const std::string& user, const std::string& keyfile, const std::string& name, - const std::string& error_msg) { - - GtkWidget* dialog = gtk_dialog_new_with_buttons( - "SSH Authentication", GTK_WINDOW(window_), - (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), - "_Cancel", GTK_RESPONSE_CANCEL, - "_Connect", GTK_RESPONSE_ACCEPT, - nullptr); - - GtkWidget* content = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - GtkWidget* grid = gtk_grid_new(); - gtk_grid_set_column_spacing(GTK_GRID(grid), 8); - gtk_grid_set_row_spacing(GTK_GRID(grid), 6); - gtk_widget_set_margin_start(grid, 12); - gtk_widget_set_margin_end(grid, 12); - gtk_widget_set_margin_top(grid, 12); - gtk_widget_set_margin_bottom(grid, 12); - - int row = 0; - - if (!error_msg.empty()) { - GtkWidget* err_label = gtk_label_new(nullptr); - char err_markup[1024]; - snprintf(err_markup, sizeof(err_markup), - "Error: %s", error_msg.c_str()); - gtk_label_set_markup(GTK_LABEL(err_label), err_markup); - gtk_label_set_line_wrap(GTK_LABEL(err_label), TRUE); - gtk_widget_set_hexpand(err_label, TRUE); - gtk_grid_attach(GTK_GRID(grid), err_label, 0, row, 2, 1); - row++; - } - - GtkWidget* info_label = gtk_label_new(nullptr); - char markup[512]; - snprintf(markup, sizeof(markup), "Connect to %s@%s:%d", user.c_str(), host.c_str(), port); - gtk_label_set_markup(GTK_LABEL(info_label), markup); - gtk_grid_attach(GTK_GRID(grid), info_label, 0, row, 2, 1); - row++; - - GtkWidget* pass_label = gtk_label_new("Password:"); - gtk_widget_set_halign(pass_label, GTK_ALIGN_START); - gtk_grid_attach(GTK_GRID(grid), pass_label, 0, row, 1, 1); - - GtkWidget* pass_entry = gtk_entry_new(); - gtk_entry_set_visibility(GTK_ENTRY(pass_entry), FALSE); - gtk_entry_set_placeholder_text(GTK_ENTRY(pass_entry), "Enter password"); - gtk_widget_set_hexpand(pass_entry, TRUE); - gtk_grid_attach(GTK_GRID(grid), pass_entry, 1, row, 1, 1); - row++; - - if (!error_msg.empty()) { - gtk_widget_grab_focus(pass_entry); - } - - gtk_container_add(GTK_CONTAINER(content), grid); - gtk_widget_show_all(dialog); - - while (true) { - gint result = gtk_dialog_run(GTK_DIALOG(dialog)); - if (result == GTK_RESPONSE_ACCEPT) { - const char* password = gtk_entry_get_text(GTK_ENTRY(pass_entry)); - if (password && *password) { - gtk_widget_destroy(dialog); - connect_to_server(host, port, user, keyfile, password, name); - return; - } - } - break; - } - gtk_widget_destroy(dialog); -} /* ─── server dialog ───────────────────────────────────────── */ @@ -841,7 +741,8 @@ void SshClientWindow::on_close_tab(GtkWidget*, gpointer d) { void SshClientWindow::on_connect(GtkWidget*, gpointer d) { auto* self = static_cast(d); - self->show_password_dialog("localhost", 22, "root", "", "Local"); + /* For local SSH, just use connect with empty password — ssh will prompt */ + self->connect_to_server("localhost", 22, "root", "", "", "Local"); } void SshClientWindow::on_disconnect(GtkWidget*, gpointer d) { diff --git a/src/ssh_client.h b/src/ssh_client.h index 8f82cab..9063498 100644 --- a/src/ssh_client.h +++ b/src/ssh_client.h @@ -22,14 +22,10 @@ private: void disconnect(); void connect_to_server(const std::string& host, int port, const std::string& user, const std::string& keyfile, - const std::string& password, const std::string& name, - const std::string& error_msg = ""); + const std::string& password, const std::string& name); void show_server_dialog(int edit_index = -1); void show_key_dialog(); - void show_password_dialog(const std::string& host, int port, const std::string& user, - const std::string& keyfile, const std::string& name, - const std::string& error_msg = ""); void update_server_combo(); void update_server_list(); void update_status(const std::string& msg); diff --git a/src/ssh_terminal.cpp b/src/ssh_terminal.cpp index 7fe208f..5d35555 100644 --- a/src/ssh_terminal.cpp +++ b/src/ssh_terminal.cpp @@ -1,24 +1,18 @@ #include "ssh_terminal.h" -#include -#include -#include -#include #include -#include #include +#include SshTerminal::SshTerminal() - : terminal_(nullptr), vte_box_(nullptr), vte_pty_(nullptr), - session_(nullptr), channel_(nullptr), - pty_master_(-1), pty_slave_(-1), - running_(false), connected_(false), cleanup_done_(true), thread_(nullptr) { + : 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_, "size-allocate", G_CALLBACK(on_size_changed), this); + g_signal_connect(terminal_, "child-exited", G_CALLBACK(on_child_exited), this); } SshTerminal::~SshTerminal() { @@ -29,108 +23,17 @@ SshTerminal::~SshTerminal() { } } -void SshTerminal::on_size_changed(GtkWidget* widget, GtkAllocation* alloc, gpointer data) { - (void)alloc; +void SshTerminal::on_child_exited(VteTerminal* terminal, int status, gpointer data) { + (void)terminal; auto* self = static_cast(data); - if (self->channel_ && self->connected_) { - VteTerminal* term = VTE_TERMINAL(widget); - guint cols = vte_terminal_get_column_count(term); - guint rows = vte_terminal_get_row_count(term); - ssh_channel_change_pty_size(self->channel_, cols, rows); - } -} - -void SshTerminal::cleanup() { - std::lock_guard lock(mutex_); - if (cleanup_done_) return; - cleanup_done_ = true; - - if (channel_) { - ssh_channel_close(channel_); - ssh_channel_free(channel_); - channel_ = nullptr; - } - if (session_) { - ssh_disconnect(session_); - ssh_free(session_); - session_ = nullptr; - } - if (pty_slave_ >= 0) { close(pty_slave_); pty_slave_ = -1; } - if (pty_master_ >= 0) { close(pty_master_); pty_master_ = -1; } - connected_ = false; -} - -gpointer SshTerminal::io_thread_func(gpointer data) { - auto* self = static_cast(data); - - ssh_session sess = self->session_; - ssh_channel chan = self->channel_; - int ssh_fd = ssh_get_fd(sess); - int slave_fd = self->pty_slave_; - - if (ssh_fd < 0 || slave_fd < 0) { - g_idle_add([](gpointer d) -> gboolean { - auto* s = static_cast(d); - if (s->on_status_change) s->on_status_change(false, "SSH fd unavailable"); - return G_SOURCE_REMOVE; - }, self); - return nullptr; - } - - char buf[4096]; - fd_set rfds; - struct timeval tv; - - while (self->running_) { - FD_ZERO(&rfds); - FD_SET(slave_fd, &rfds); - FD_SET(ssh_fd, &rfds); - int maxfd = (slave_fd > ssh_fd ? slave_fd : ssh_fd) + 1; - - tv.tv_sec = 0; - tv.tv_usec = 50000; - int ret = select(maxfd, &rfds, nullptr, nullptr, &tv); - - if (ret < 0) { - if (errno == EINTR) continue; - break; - } - if (ret == 0) continue; - - if (FD_ISSET(slave_fd, &rfds)) { - ssize_t n = read(slave_fd, buf, sizeof(buf)); - if (n > 0) { - if (ssh_channel_write(chan, buf, n) < 0) break; - } else if (n <= 0) { - break; - } - } - - if (FD_ISSET(ssh_fd, &rfds)) { - int poll_result = ssh_channel_poll_timeout(chan, 50, 0); - if (poll_result > 0) { - ssize_t n = ssh_channel_read_timeout(chan, buf, sizeof(buf), 0, 0); - if (n > 0) { - if (write(slave_fd, buf, n) < 0 && errno != EINTR) break; - } else if (n == SSH_EOF || n == SSH_ERROR) { - break; - } - } else if (poll_result < 0) { - break; - } - } - - if (ssh_channel_is_eof(chan)) break; - } - self->connected_ = false; - g_idle_add([](gpointer d) -> gboolean { - auto* s = static_cast(d); - if (s->on_status_change) s->on_status_change(false, "Disconnected"); - return G_SOURCE_REMOVE; - }, self); + self->child_pid_ = 0; - return nullptr; + 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, @@ -138,232 +41,128 @@ bool SshTerminal::connect(const std::string& host, int port, const std::string& std::string& out_error) { if (connected_) disconnect(); - /* Reset state for a fresh connect attempt */ - cleanup_done_ = true; - session_ = nullptr; - channel_ = nullptr; - pty_master_ = -1; - pty_slave_ = -1; - vte_pty_ = nullptr; + g_message("Connecting to %s@%s:%d ...", user.c_str(), host.c_str(), port); - g_message("Connecting to %s:%d as %s ...", host.c_str(), port, user.c_str()); + /* Build ssh command arguments */ + std::vector args; - g_message("ssh_new..."); - session_ = ssh_new(); - if (!session_) { out_error = "Failed to create SSH session"; g_warning("ssh_new() failed"); return false; } - - g_message("ssh_options_set host..."); - ssh_options_set(session_, SSH_OPTIONS_HOST, host.c_str()); - unsigned int p = port; - g_message("ssh_options_set port..."); - ssh_options_set(session_, SSH_OPTIONS_PORT, &p); - g_message("ssh_options_set user..."); - ssh_options_set(session_, SSH_OPTIONS_USER, user.c_str()); - int strict = 0; - g_message("ssh_options_set stricthostkeycheck..."); - ssh_options_set(session_, SSH_OPTIONS_STRICTHOSTKEYCHECK, &strict); - - g_message("ssh_connect..."); - if (ssh_connect(session_) != SSH_OK) { - std::string err = ssh_get_error(session_); - g_warning("ssh_connect failed: %s", err.c_str()); - out_error = "Connection failed: " + err; - ssh_free(session_); - session_ = nullptr; - if (on_status_change) on_status_change(false, out_error); - return false; + /* 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"); + } } - g_message("ssh_connect OK"); - g_message("ssh_session_update_known_hosts..."); - ssh_session_update_known_hosts(session_); - g_message("known_hosts OK"); + 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()) { - g_message("Trying keyfile auth..."); - ssh_key key = nullptr; - if (ssh_pki_import_privkey_file(keyfile.c_str(), nullptr, nullptr, nullptr, &key) == SSH_OK) { - if (ssh_userauth_publickey(session_, nullptr, key) == SSH_AUTH_SUCCESS) { - ssh_key_free(key); - goto auth_done; + 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])); } - ssh_key_free(key); + g_strfreev(parts); } - } - { - g_message("ssh_userauth_password..."); - int rc = ssh_userauth_password(session_, nullptr, password.c_str()); - g_message("auth result: %d", rc); - if (rc != SSH_AUTH_SUCCESS) { - std::string err = ssh_get_error(session_); - g_warning("Auth failed: %s", err.c_str()); - out_error = "Authentication failed: " + err; - ssh_disconnect(session_); - ssh_free(session_); - session_ = nullptr; - if (on_status_change) on_status_change(false, out_error); - return false; + 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); } -auth_done: - g_message("ssh_channel_new..."); - channel_ = ssh_channel_new(session_); - if (!channel_) { out_error = "Channel creation failed"; g_warning("ssh_channel_new() failed"); return false; } + /* Spawn ssh process */ + GError* error = nullptr; + GPid child_pid = 0; - g_message("ssh_channel_open_session..."); - if (ssh_channel_open_session(channel_) != SSH_OK) { - out_error = "Channel open failed"; - g_warning("ssh_channel_open_session failed"); - ssh_channel_free(channel_); - channel_ = nullptr; - ssh_disconnect(session_); - ssh_free(session_); - session_ = nullptr; - if (on_status_change) on_status_change(false, "Session open failed"); - return false; + 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); } - g_message("channel open OK"); - guint cols = vte_terminal_get_column_count(terminal_); - guint rows = vte_terminal_get_row_count(terminal_); - g_message("requesting PTY %dx%d...", cols, rows); - if (ssh_channel_request_pty_size(channel_, "xterm-256color", cols, rows) != SSH_OK) { - out_error = "PTY request failed"; - g_warning("PTY request failed"); - ssh_channel_free(channel_); - channel_ = nullptr; - ssh_disconnect(session_); - ssh_free(session_); - session_ = nullptr; - if (on_status_change) on_status_change(false, "PTY request failed"); + 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; } - g_message("requesting shell..."); - if (ssh_channel_request_shell(channel_) != SSH_OK) { - out_error = "Shell request failed"; - g_warning("Shell request failed"); - ssh_channel_free(channel_); - channel_ = nullptr; - ssh_disconnect(session_); - ssh_free(session_); - session_ = nullptr; - if (on_status_change) on_status_change(false, "Shell request failed"); - return false; - } - g_message("shell OK"); - - g_message("openpty (posix_openpt)..."); - pty_master_ = posix_openpt(O_RDWR | O_NOCTTY); - if (pty_master_ < 0) { - g_warning("posix_openpt failed: %s", strerror(errno)); - ssh_channel_free(channel_); - channel_ = nullptr; - ssh_disconnect(session_); - ssh_free(session_); - session_ = nullptr; - if (on_status_change) on_status_change(false, "PTY master open failed"); - return false; - } - g_message("grantpt..."); - if (grantpt(pty_master_) < 0) { - g_warning("grantpt failed: %s", strerror(errno)); - close(pty_master_); pty_master_ = -1; - ssh_channel_free(channel_); - channel_ = nullptr; - ssh_disconnect(session_); - ssh_free(session_); - session_ = nullptr; - if (on_status_change) on_status_change(false, "PTY grantpt failed"); - return false; - } - g_message("unlockpt..."); - if (unlockpt(pty_master_) < 0) { - g_warning("unlockpt failed: %s", strerror(errno)); - close(pty_master_); pty_master_ = -1; - ssh_channel_free(channel_); - channel_ = nullptr; - ssh_disconnect(session_); - ssh_free(session_); - session_ = nullptr; - if (on_status_change) on_status_change(false, "PTY unlockpt failed"); - return false; - } - const char* slave_name = ptsname(pty_master_); - g_message("slave name: %s", slave_name ? slave_name : "(null)"); - if (!slave_name) { - g_warning("ptsname failed"); - close(pty_master_); pty_master_ = -1; - ssh_channel_free(channel_); - channel_ = nullptr; - ssh_disconnect(session_); - ssh_free(session_); - session_ = nullptr; - if (on_status_change) on_status_change(false, "PTY ptsname failed"); - return false; - } - g_message("opening slave %s...", slave_name); - pty_slave_ = open(slave_name, O_RDWR | O_NOCTTY); - if (pty_slave_ < 0) { - g_warning("slave open failed: %s", strerror(errno)); - close(pty_master_); pty_master_ = -1; - ssh_channel_free(channel_); - channel_ = nullptr; - ssh_disconnect(session_); - ssh_free(session_); - session_ = nullptr; - if (on_status_change) on_status_change(false, "PTY slave open failed"); - return false; - } - g_message("PTY ready (master=%d, slave=%d)", pty_master_, pty_slave_); - - g_message("vte_pty_new_foreign_sync..."); - vte_pty_ = vte_pty_new_foreign_sync(pty_master_, nullptr, nullptr); - if (vte_pty_) { - g_message("setting VTE pty..."); - vte_terminal_set_pty(terminal_, vte_pty_); - } else { - close(pty_slave_); pty_slave_ = -1; - close(pty_master_); pty_master_ = -1; - ssh_channel_free(channel_); - channel_ = nullptr; - ssh_disconnect(session_); - ssh_free(session_); - session_ = nullptr; - if (on_status_change) on_status_change(false, "VTE pty setup failed"); - return false; - } - - cleanup_done_ = false; + child_pid_ = child_pid; connected_ = true; - running_ = true; - g_message("SSH connection established, starting IO thread"); - thread_ = g_thread_new("ssh_io", io_thread_func, this); - if (on_status_change) on_status_change(true, "Connected to " + user + "@" + host); + 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() { - running_ = false; - - if (thread_) { - g_thread_join(thread_); - thread_ = nullptr; + 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; } - - /* Flush any pending idle callbacks from the IO thread - that might still reference this object */ - while (g_main_context_pending(NULL)) - g_main_context_iteration(NULL, FALSE); - - if (vte_pty_) { - g_object_unref(vte_pty_); - vte_pty_ = nullptr; - if (terminal_) vte_terminal_set_pty(terminal_, nullptr); - } - - cleanup(); + connected_ = false; } diff --git a/src/ssh_terminal.h b/src/ssh_terminal.h index 0ea88ef..0c65cf6 100644 --- a/src/ssh_terminal.h +++ b/src/ssh_terminal.h @@ -1,10 +1,7 @@ #pragma once #include #include -#include -#include #include -#include #include class SshTerminal { @@ -24,21 +21,11 @@ public: std::function on_status_change; private: - void cleanup(); - static gpointer io_thread_func(gpointer data); - static void on_size_changed(GtkWidget* widget, GtkAllocation* alloc, gpointer data); + static void on_child_exited(VteTerminal* terminal, int status, gpointer data); GtkWidget* vte_box_; VteTerminal* terminal_; - VtePty* vte_pty_; - ssh_session session_; - ssh_channel channel_; - int pty_master_; - int pty_slave_; - std::atomic running_; - std::atomic connected_; - std::atomic cleanup_done_; - GThread* thread_; - std::mutex mutex_; + GPid child_pid_; + bool connected_; std::string server_name_; };