mirror of
https://github.com/openssh/openssh-portable.git
synced 2026-01-12 00:04:08 +08:00
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.
250 lines
6.9 KiB
C
250 lines
6.9 KiB
C
/*
|
||
* 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@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;
|
||
}
|