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_); }