[openssh-commits] [openssh] branch master updated: Add gnome-ssh-askpass4 for GNOME 40+

git+noreply at mindrot.org git+noreply at mindrot.org
Tue Jul 15 08:30:04 AEST 2025


This is an automated email from the git hooks/post-receive script.

djm pushed a commit to branch master
in repository openssh.

The following commit(s) were added to refs/heads/master by this push:
     new e6805e2a6 Add gnome-ssh-askpass4 for GNOME 40+
e6805e2a6 is described below

commit e6805e2a6b33e001e1a7257b85ab779fd592a578
Author: Jan Tojnar <jtojnar at gmail.com>
AuthorDate: Thu May 18 16:30:35 2023 +0200

    Add gnome-ssh-askpass4 for GNOME 40+
    
    GTK 3 has been in maintenance mode for a while now, and it is on the road
    to being abandoned. As a result, the dialogue looks out of place on modern
    systems.
    
    We could port it to GTK 4 but without the program being registered as an
    application (i.e. having a .desktop file), GNOME Shell would ask for
    permission to grab input every time.
    
    Let’s instead use the GNOME Shell’s native prompt through the unstable
    Gcr API.
---
 contrib/Makefile             |   7 +-
 contrib/README               |   8 +-
 contrib/gnome-ssh-askpass4.c | 249 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 260 insertions(+), 4 deletions(-)

diff --git a/contrib/Makefile b/contrib/Makefile
index 45d878bdc..1482783a8 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -1,7 +1,7 @@
 PKG_CONFIG = pkg-config
 
 all:
-	@echo "Valid targets: gnome-ssh-askpass1 gnome-ssh-askpass2 gnome-ssk-askpass3"
+	@echo "Valid targets: gnome-ssh-askpass1 gnome-ssh-askpass2 gnome-ssk-askpass3 gnome-ssh-askpass4"
 
 gnome-ssh-askpass1: gnome-ssh-askpass1.c
 	$(CC) $(CFLAGS) `gnome-config --cflags gnome gnomeui` \
@@ -18,5 +18,10 @@ gnome-ssh-askpass3: gnome-ssh-askpass3.c
 		gnome-ssh-askpass3.c -o gnome-ssh-askpass3 \
 		`$(PKG_CONFIG) --libs gtk+-3.0 x11`
 
+gnome-ssh-askpass4: gnome-ssh-askpass4.c
+	$(CC) $(CFLAGS) `$(PKG_CONFIG) --cflags gcr-4 gio-2.0` \
+		gnome-ssh-askpass4.c -o gnome-ssh-askpass4 \
+		`$(PKG_CONFIG) --libs gcr-4 gio-2.0`
+
 clean:
 	rm -f *.o gnome-ssh-askpass gnome-ssh-askpass[123]
diff --git a/contrib/README b/contrib/README
index 60e19ba9f..614152a12 100644
--- a/contrib/README
+++ b/contrib/README
@@ -30,10 +30,12 @@ ssh-copy-id:
 Phil Hands' <phil at hands.com> shell script to automate the process of adding
 your public key to a remote machine's ~/.ssh/authorized_keys file.
 
-gnome-ssh-askpass[12]:
+gnome-ssh-askpass[1234]:
 
-A GNOME and Gtk2 passphrase requesters. Use "make gnome-ssh-askpass1" or
-"make gnome-ssh-askpass2" to build.
+Graphical passhrase requesters. Use "make gnome-ssh-askpass1" to build
+a variant for ancient GNOME desktop, "make gnome-ssh-askpass2"
+for a GTK 2-based one, "make gnome-ssh-askpass3" for a GTK 3-based one,
+or "make gnome-ssh-askpass4" for a version for modern GNOME.
 
 sshd.pam.generic:
 
diff --git a/contrib/gnome-ssh-askpass4.c b/contrib/gnome-ssh-askpass4.c
new file mode 100644
index 000000000..182bce0cc
--- /dev/null
+++ b/contrib/gnome-ssh-askpass4.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2000-2002 Damien Miller.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* GCR support by Jan Tojnar <jtojnar at gmail.com> */
+
+/*
+ * This is a simple SSH passphrase grabber for GNOME. To use it, set the
+ * environment variable SSH_ASKPASS to point to the location of
+ * gnome-ssh-askpass before calling "ssh-add < /dev/null".
+ */
+
+/*
+ * Known problems:
+ *   - This depends on unstable libgcr features
+ *   - long key fingerprints may be truncted:
+ *     https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/6781
+ */
+
+/*
+ * Compile with:
+ *
+ * cc -Wall `pkg-config --cflags gcr-4 gio-2.0` \
+ *    gnome-ssh-askpass4.c -o gnome-ssh-askpass \
+ *    `pkg-config --libs gcr-4 gio-2.0`
+ *
+ */
+
+#include <stdio.h>
+#include <err.h>
+
+#include <gio/gio.h>
+
+#define GCR_API_SUBJECT_TO_CHANGE 1
+#include <gcr/gcr.h>
+
+typedef enum _PromptType {
+	PROMPT_ENTRY,
+	PROMPT_CONFIRM,
+	PROMPT_NONE,
+} PromptType;
+
+typedef struct _PromptState {
+	GApplication *app;
+	char* message;
+	PromptType type;
+	int exit_status;
+} PromptState;
+
+static PromptState *
+prompt_state_new(GApplication *app, char* message, PromptType type)
+{
+	PromptState *state = g_malloc(sizeof(PromptState));
+	state->app = g_object_ref(app);
+	state->message = g_strdup(message);
+	state->type = type;
+	state->exit_status = -1;
+	return state;
+}
+
+static void
+prompt_state_free(PromptState *state)
+{
+	g_clear_object(&state->app);
+	g_free(state->message);
+	g_free(state);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(PromptState, prompt_state_free)
+
+static void
+prompt_password_done(GObject *source_object, GAsyncResult *res,
+    gpointer user_data)
+{
+	GcrPrompt *prompt = GCR_PROMPT(source_object);
+	PromptState *state = user_data;
+	g_autoptr(GError) error = NULL;
+
+	/*
+	 * “The returned password is valid until the next time a method
+	 * is called to display another prompt.”
+	 */
+	const char *pw = gcr_prompt_password_finish(prompt, res, &error);
+
+	if ((!pw && !error) || (error && error->code == G_IO_ERROR_CANCELLED)) {
+		/* Operation was cancelled or timed out. */
+		state->exit_status = -1;
+	} else if (error) {
+		warnx("Failed to prompt for ssh-askpass: %s", error->message);
+		state->exit_status = 1;
+	} else {
+		/* Report passphrase if user selected Continue. */
+		g_autofree char *local = g_locale_from_utf8(pw, strlen(pw),
+		    NULL, NULL, NULL);
+
+		if (local != NULL) {
+			puts(local);
+			memset(local, '\0', strlen(local));
+		} else {
+			puts(pw);
+		}
+		state->exit_status = 0;
+	}
+
+	g_application_release(state->app);
+}
+
+static void
+prompt_confirm_done(GObject *source_object, GAsyncResult *res,
+    gpointer user_data)
+{
+	GcrPrompt *prompt = GCR_PROMPT(source_object);
+	PromptState *state = user_data;
+	g_autoptr(GError) error = NULL;
+
+	GcrPromptReply reply = gcr_prompt_confirm_finish(prompt, res, &error);
+	if (error) {
+		if (error->code == G_IO_ERROR_CANCELLED) {
+			/* Operation was cancelled or timed out. */
+			state->exit_status = -1;
+		} else {
+			state->exit_status = 1;
+			warnx("Failed to prompt for ssh-askpass: %s",
+			    error->message);
+		}
+	} else if (reply == GCR_PROMPT_REPLY_CONTINUE ||
+	    state->type == PROMPT_NONE) {
+		/*
+		 * Since Gcr doesn’t yet support one button message
+		 * boxes treat Cancel the same as Continue.
+		 */
+		state->exit_status = 0;
+	} else {
+		/* GCR_PROMPT_REPLY_CANCEL */
+		state->exit_status = -1;
+	}
+
+	g_application_release(state->app);
+}
+
+static int
+command_line(GApplication* app, G_GNUC_UNUSED GApplicationCommandLine *cmdline,
+    gpointer user_data)
+{
+	PromptState *state = user_data;
+
+	/* Prevent app from exiting while waiting for the async callback. */
+	g_application_hold(app);
+
+	/* Wait indefinitely. */
+	int timeout_seconds = -1;
+	g_autoptr(GError) error = NULL;
+	GcrPrompt* prompt = gcr_system_prompt_open(timeout_seconds, NULL, &error);
+
+	if (!prompt) {
+		if (error->code == GCR_SYSTEM_PROMPT_IN_PROGRESS) {
+			/*
+			 * This means the timeout elapsed, but no prompt
+			 * was ever shown.
+			 */
+			warnx("Timeout: the Gcr system prompter was "
+			    "already in use.");
+		} else {
+			warnx("Couldn’t create prompt for ssh-askpass: %s",
+			    error->message);
+		}
+
+		return 1;
+	}
+
+	gcr_prompt_set_message(prompt, "OpenSSH");
+	gcr_prompt_set_description(prompt, state->message);
+
+	/*
+	 * XXX: Remove the Cancel button for PROMPT_NONE when GCR
+	 * supports that.
+	 */
+	if (state->type == PROMPT_ENTRY) {
+		gcr_prompt_password_async(prompt, NULL, prompt_password_done, state);
+	} else {
+		gcr_prompt_confirm_async(prompt, NULL, prompt_confirm_done, state);
+	}
+
+	/* The exit status will be changed in the async callbacks. */
+	return 1;
+}
+
+int
+main(int argc, char **argv)
+{
+	g_autoptr(GApplication) app = g_application_new(
+	    "com.openssh.gnome-ssh-askpass4",
+	    G_APPLICATION_HANDLES_COMMAND_LINE);
+	g_autofree char *message = NULL;
+
+	if (argc > 1) {
+		message = g_strjoinv(" ", argv + 1);
+	} else {
+		message = g_strdup("Enter your OpenSSH passphrase:");
+	}
+
+	const char *prompt_mode = getenv("SSH_ASKPASS_PROMPT");
+	PromptType type = PROMPT_ENTRY;
+	if (prompt_mode != NULL) {
+		if (strcasecmp(prompt_mode, "confirm") == 0) {
+			type = PROMPT_CONFIRM;
+		} else if (strcasecmp(prompt_mode, "none") == 0) {
+			type = PROMPT_NONE;
+		}
+	}
+
+	g_autoptr(PromptState) state = prompt_state_new(app, message, type);
+
+	g_signal_connect(app, "command-line", G_CALLBACK(command_line), state);
+
+	/*
+	 * Since we are calling g_application_hold, we cannot use
+	 * g_application_command_line_set_exit_status.
+	 * To change the exit status returned by g_application_run:
+	 *   “If the commandline invocation results in the mainloop running
+	 *   (ie: because the use-count of the application increased to a
+	 *   non-zero value) then the application is considered to have been
+	 *   ‘successful’ in a certain sense, and the exit status is always
+	 *   zero.”
+	 */
+	(void)(g_application_run(app, argc, argv));
+
+	return state->exit_status;
+}

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list