From 69369aedb8d1fc53dbeb1eda383316985318b508 Mon Sep 17 00:00:00 2001 From: x3 Date: Mon, 7 Sep 2026 16:29:36 +0000 Subject: [PATCH] Fix: replace openpty with posix_openpt, add password retry dialog - Replace openpty() with posix_openpt()+grantpt()+unlockpt()+ptsname() to fix segfault on PTY creation - Add out_error parameter to SshTerminal::connect() to propagate error - Password dialog now shows red error message on auth failure - Password dialog re-opens automatically on failed password auth so user can retry without restarting --- src/ssh_client.cpp | 58 ++++++++++++++++++++++++++------ src/ssh_client.h | 6 ++-- src/ssh_terminal.cpp | 79 ++++++++++++++++++++++++++++++++++++++------ src/ssh_terminal.h | 3 +- 4 files changed, 122 insertions(+), 24 deletions(-) diff --git a/src/ssh_client.cpp b/src/ssh_client.cpp index 22695c8..43f6268 100644 --- a/src/ssh_client.cpp +++ b/src/ssh_client.cpp @@ -315,7 +315,8 @@ 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& password, const std::string& name, + const std::string& error_msg) { g_message("connect_to_server: %s@%s:%d (key=%s, saved_pw=%s)", user.c_str(), host.c_str(), port, @@ -335,16 +336,24 @@ void SshClientWindow::connect_to_server(const std::string& host, int port, gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook_), idx); update_status("Connecting to " + host + ":" + std::to_string(port) + "..."); - bool ok = term->connect(host, port, user, keyfile, password); + std::string ssh_error; + bool ok = term->connect(host, port, user, keyfile, password, ssh_error); if (!ok) { update_status("Failed to connect to " + host); - show_notification("Failed to connect to " + host, true); for (auto it = terminals_.begin(); it != terminals_.end(); ++it) { if (*it == term) { terminals_.erase(it); break; } } /* 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); + } } else { update_status("Connected to " + user + "@" + host); show_notification("Server connected: " + user + "@" + host + ":" + std::to_string(port)); @@ -480,7 +489,8 @@ void SshClientWindow::on_add_server(GtkWidget*, gpointer d) { /* ─── 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& 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_), @@ -498,28 +508,56 @@ void SshClientWindow::show_password_dialog(const std::string& host, int port, 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, 0, 2, 1); + 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, 1, 1, 1); + 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, 1, 1, 1); + 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); - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - const char* password = gtk_entry_get_text(GTK_ENTRY(pass_entry)); - connect_to_server(host, port, user, keyfile, password ? password : "", name); + 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); } diff --git a/src/ssh_client.h b/src/ssh_client.h index 92546e9..8f82cab 100644 --- a/src/ssh_client.h +++ b/src/ssh_client.h @@ -22,12 +22,14 @@ 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& password, const std::string& name, + const std::string& error_msg = ""); 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& 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 a3c317a..7fe208f 100644 --- a/src/ssh_terminal.cpp +++ b/src/ssh_terminal.cpp @@ -1,10 +1,11 @@ #include "ssh_terminal.h" #include -#include +#include #include #include #include #include +#include SshTerminal::SshTerminal() : terminal_(nullptr), vte_box_(nullptr), vte_pty_(nullptr), @@ -133,7 +134,8 @@ gpointer SshTerminal::io_thread_func(gpointer data) { } bool SshTerminal::connect(const std::string& host, int port, const std::string& user, - const std::string& keyfile, const std::string& password) { + const std::string& keyfile, const std::string& password, + std::string& out_error) { if (connected_) disconnect(); /* Reset state for a fresh connect attempt */ @@ -148,7 +150,7 @@ bool SshTerminal::connect(const std::string& host, int port, const std::string& g_message("ssh_new..."); session_ = ssh_new(); - if (!session_) { g_warning("ssh_new() failed"); return false; } + 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()); @@ -165,9 +167,10 @@ bool SshTerminal::connect(const std::string& host, int port, const std::string& 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, "Connection failed: " + err); + if (on_status_change) on_status_change(false, out_error); return false; } g_message("ssh_connect OK"); @@ -195,10 +198,11 @@ bool SshTerminal::connect(const std::string& host, int port, const std::string& 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, "Auth failed: " + err); + if (on_status_change) on_status_change(false, out_error); return false; } } @@ -206,10 +210,11 @@ bool SshTerminal::connect(const std::string& host, int port, const std::string& auth_done: g_message("ssh_channel_new..."); channel_ = ssh_channel_new(session_); - if (!channel_) { g_warning("ssh_channel_new() failed"); return false; } + if (!channel_) { out_error = "Channel creation failed"; g_warning("ssh_channel_new() failed"); return false; } 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; @@ -225,6 +230,7 @@ auth_done: 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; @@ -237,6 +243,7 @@ auth_done: 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; @@ -248,19 +255,69 @@ auth_done: } g_message("shell OK"); - g_message("openpty..."); - pty_master_ = openpty(&pty_slave_, nullptr, nullptr, nullptr, nullptr); + g_message("openpty (posix_openpt)..."); + pty_master_ = posix_openpt(O_RDWR | O_NOCTTY); if (pty_master_ < 0) { - g_warning("openpty failed"); + 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 creation failed"); + if (on_status_change) on_status_change(false, "PTY master open failed"); return false; } - g_message("openpty OK (master=%d, slave=%d)", pty_master_, pty_slave_); + 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); diff --git a/src/ssh_terminal.h b/src/ssh_terminal.h index da26213..0ea88ef 100644 --- a/src/ssh_terminal.h +++ b/src/ssh_terminal.h @@ -13,7 +13,8 @@ public: ~SshTerminal(); bool connect(const std::string& host, int port, const std::string& user, - const std::string& keyfile, const std::string& password); + const std::string& keyfile, const std::string& password, + std::string& out_error); void disconnect(); bool is_connected() const { return connected_; } GtkWidget* widget() const { return GTK_WIDGET(vte_box_); }