1*644b4646SEd Maste /*
2*644b4646SEd Maste * Copyright (c) 2000-2002 Damien Miller. All rights reserved.
3*644b4646SEd Maste *
4*644b4646SEd Maste * Redistribution and use in source and binary forms, with or without
5*644b4646SEd Maste * modification, are permitted provided that the following conditions
6*644b4646SEd Maste * are met:
7*644b4646SEd Maste * 1. Redistributions of source code must retain the above copyright
8*644b4646SEd Maste * notice, this list of conditions and the following disclaimer.
9*644b4646SEd Maste * 2. Redistributions in binary form must reproduce the above copyright
10*644b4646SEd Maste * notice, this list of conditions and the following disclaimer in the
11*644b4646SEd Maste * documentation and/or other materials provided with the distribution.
12*644b4646SEd Maste *
13*644b4646SEd Maste * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14*644b4646SEd Maste * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15*644b4646SEd Maste * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16*644b4646SEd Maste * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17*644b4646SEd Maste * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18*644b4646SEd Maste * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19*644b4646SEd Maste * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20*644b4646SEd Maste * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21*644b4646SEd Maste * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22*644b4646SEd Maste * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23*644b4646SEd Maste */
24*644b4646SEd Maste
25*644b4646SEd Maste /* GCR support by Jan Tojnar <jtojnar@gmail.com> */
26*644b4646SEd Maste
27*644b4646SEd Maste /*
28*644b4646SEd Maste * This is a simple SSH passphrase grabber for GNOME. To use it, set the
29*644b4646SEd Maste * environment variable SSH_ASKPASS to point to the location of
30*644b4646SEd Maste * gnome-ssh-askpass before calling "ssh-add < /dev/null".
31*644b4646SEd Maste */
32*644b4646SEd Maste
33*644b4646SEd Maste /*
34*644b4646SEd Maste * Known problems:
35*644b4646SEd Maste * - This depends on unstable libgcr features
36*644b4646SEd Maste * - long key fingerprints may be truncted:
37*644b4646SEd Maste * https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/6781
38*644b4646SEd Maste */
39*644b4646SEd Maste
40*644b4646SEd Maste /*
41*644b4646SEd Maste * Compile with:
42*644b4646SEd Maste *
43*644b4646SEd Maste * cc -Wall `pkg-config --cflags gcr-4 gio-2.0` \
44*644b4646SEd Maste * gnome-ssh-askpass4.c -o gnome-ssh-askpass \
45*644b4646SEd Maste * `pkg-config --libs gcr-4 gio-2.0`
46*644b4646SEd Maste *
47*644b4646SEd Maste */
48*644b4646SEd Maste
49*644b4646SEd Maste #include <stdio.h>
50*644b4646SEd Maste #include <err.h>
51*644b4646SEd Maste
52*644b4646SEd Maste #include <gio/gio.h>
53*644b4646SEd Maste
54*644b4646SEd Maste #define GCR_API_SUBJECT_TO_CHANGE 1
55*644b4646SEd Maste #include <gcr/gcr.h>
56*644b4646SEd Maste
57*644b4646SEd Maste typedef enum _PromptType {
58*644b4646SEd Maste PROMPT_ENTRY,
59*644b4646SEd Maste PROMPT_CONFIRM,
60*644b4646SEd Maste PROMPT_NONE,
61*644b4646SEd Maste } PromptType;
62*644b4646SEd Maste
63*644b4646SEd Maste typedef struct _PromptState {
64*644b4646SEd Maste GApplication *app;
65*644b4646SEd Maste char* message;
66*644b4646SEd Maste PromptType type;
67*644b4646SEd Maste int exit_status;
68*644b4646SEd Maste } PromptState;
69*644b4646SEd Maste
70*644b4646SEd Maste static PromptState *
prompt_state_new(GApplication * app,char * message,PromptType type)71*644b4646SEd Maste prompt_state_new(GApplication *app, char* message, PromptType type)
72*644b4646SEd Maste {
73*644b4646SEd Maste PromptState *state = g_malloc(sizeof(PromptState));
74*644b4646SEd Maste state->app = g_object_ref(app);
75*644b4646SEd Maste state->message = g_strdup(message);
76*644b4646SEd Maste state->type = type;
77*644b4646SEd Maste state->exit_status = -1;
78*644b4646SEd Maste return state;
79*644b4646SEd Maste }
80*644b4646SEd Maste
81*644b4646SEd Maste static void
prompt_state_free(PromptState * state)82*644b4646SEd Maste prompt_state_free(PromptState *state)
83*644b4646SEd Maste {
84*644b4646SEd Maste g_clear_object(&state->app);
85*644b4646SEd Maste g_free(state->message);
86*644b4646SEd Maste g_free(state);
87*644b4646SEd Maste }
88*644b4646SEd Maste
G_DEFINE_AUTOPTR_CLEANUP_FUNC(PromptState,prompt_state_free)89*644b4646SEd Maste G_DEFINE_AUTOPTR_CLEANUP_FUNC(PromptState, prompt_state_free)
90*644b4646SEd Maste
91*644b4646SEd Maste static void
92*644b4646SEd Maste prompt_password_done(GObject *source_object, GAsyncResult *res,
93*644b4646SEd Maste gpointer user_data)
94*644b4646SEd Maste {
95*644b4646SEd Maste GcrPrompt *prompt = GCR_PROMPT(source_object);
96*644b4646SEd Maste PromptState *state = user_data;
97*644b4646SEd Maste g_autoptr(GError) error = NULL;
98*644b4646SEd Maste
99*644b4646SEd Maste /*
100*644b4646SEd Maste * “The returned password is valid until the next time a method
101*644b4646SEd Maste * is called to display another prompt.”
102*644b4646SEd Maste */
103*644b4646SEd Maste const char *pw = gcr_prompt_password_finish(prompt, res, &error);
104*644b4646SEd Maste
105*644b4646SEd Maste if ((!pw && !error) || (error && error->code == G_IO_ERROR_CANCELLED)) {
106*644b4646SEd Maste /* Operation was cancelled or timed out. */
107*644b4646SEd Maste state->exit_status = -1;
108*644b4646SEd Maste } else if (error) {
109*644b4646SEd Maste warnx("Failed to prompt for ssh-askpass: %s", error->message);
110*644b4646SEd Maste state->exit_status = 1;
111*644b4646SEd Maste } else {
112*644b4646SEd Maste /* Report passphrase if user selected Continue. */
113*644b4646SEd Maste g_autofree char *local = g_locale_from_utf8(pw, strlen(pw),
114*644b4646SEd Maste NULL, NULL, NULL);
115*644b4646SEd Maste
116*644b4646SEd Maste if (local != NULL) {
117*644b4646SEd Maste puts(local);
118*644b4646SEd Maste memset(local, '\0', strlen(local));
119*644b4646SEd Maste } else {
120*644b4646SEd Maste puts(pw);
121*644b4646SEd Maste }
122*644b4646SEd Maste state->exit_status = 0;
123*644b4646SEd Maste }
124*644b4646SEd Maste
125*644b4646SEd Maste g_application_release(state->app);
126*644b4646SEd Maste }
127*644b4646SEd Maste
128*644b4646SEd Maste static void
prompt_confirm_done(GObject * source_object,GAsyncResult * res,gpointer user_data)129*644b4646SEd Maste prompt_confirm_done(GObject *source_object, GAsyncResult *res,
130*644b4646SEd Maste gpointer user_data)
131*644b4646SEd Maste {
132*644b4646SEd Maste GcrPrompt *prompt = GCR_PROMPT(source_object);
133*644b4646SEd Maste PromptState *state = user_data;
134*644b4646SEd Maste g_autoptr(GError) error = NULL;
135*644b4646SEd Maste
136*644b4646SEd Maste GcrPromptReply reply = gcr_prompt_confirm_finish(prompt, res, &error);
137*644b4646SEd Maste if (error) {
138*644b4646SEd Maste if (error->code == G_IO_ERROR_CANCELLED) {
139*644b4646SEd Maste /* Operation was cancelled or timed out. */
140*644b4646SEd Maste state->exit_status = -1;
141*644b4646SEd Maste } else {
142*644b4646SEd Maste state->exit_status = 1;
143*644b4646SEd Maste warnx("Failed to prompt for ssh-askpass: %s",
144*644b4646SEd Maste error->message);
145*644b4646SEd Maste }
146*644b4646SEd Maste } else if (reply == GCR_PROMPT_REPLY_CONTINUE ||
147*644b4646SEd Maste state->type == PROMPT_NONE) {
148*644b4646SEd Maste /*
149*644b4646SEd Maste * Since Gcr doesn’t yet support one button message
150*644b4646SEd Maste * boxes treat Cancel the same as Continue.
151*644b4646SEd Maste */
152*644b4646SEd Maste state->exit_status = 0;
153*644b4646SEd Maste } else {
154*644b4646SEd Maste /* GCR_PROMPT_REPLY_CANCEL */
155*644b4646SEd Maste state->exit_status = -1;
156*644b4646SEd Maste }
157*644b4646SEd Maste
158*644b4646SEd Maste g_application_release(state->app);
159*644b4646SEd Maste }
160*644b4646SEd Maste
161*644b4646SEd Maste static int
command_line(GApplication * app,G_GNUC_UNUSED GApplicationCommandLine * cmdline,gpointer user_data)162*644b4646SEd Maste command_line(GApplication* app, G_GNUC_UNUSED GApplicationCommandLine *cmdline,
163*644b4646SEd Maste gpointer user_data)
164*644b4646SEd Maste {
165*644b4646SEd Maste PromptState *state = user_data;
166*644b4646SEd Maste
167*644b4646SEd Maste /* Prevent app from exiting while waiting for the async callback. */
168*644b4646SEd Maste g_application_hold(app);
169*644b4646SEd Maste
170*644b4646SEd Maste /* Wait indefinitely. */
171*644b4646SEd Maste int timeout_seconds = -1;
172*644b4646SEd Maste g_autoptr(GError) error = NULL;
173*644b4646SEd Maste GcrPrompt* prompt = gcr_system_prompt_open(timeout_seconds, NULL, &error);
174*644b4646SEd Maste
175*644b4646SEd Maste if (!prompt) {
176*644b4646SEd Maste if (error->code == GCR_SYSTEM_PROMPT_IN_PROGRESS) {
177*644b4646SEd Maste /*
178*644b4646SEd Maste * This means the timeout elapsed, but no prompt
179*644b4646SEd Maste * was ever shown.
180*644b4646SEd Maste */
181*644b4646SEd Maste warnx("Timeout: the Gcr system prompter was "
182*644b4646SEd Maste "already in use.");
183*644b4646SEd Maste } else {
184*644b4646SEd Maste warnx("Couldn’t create prompt for ssh-askpass: %s",
185*644b4646SEd Maste error->message);
186*644b4646SEd Maste }
187*644b4646SEd Maste
188*644b4646SEd Maste return 1;
189*644b4646SEd Maste }
190*644b4646SEd Maste
191*644b4646SEd Maste gcr_prompt_set_message(prompt, "OpenSSH");
192*644b4646SEd Maste gcr_prompt_set_description(prompt, state->message);
193*644b4646SEd Maste
194*644b4646SEd Maste /*
195*644b4646SEd Maste * XXX: Remove the Cancel button for PROMPT_NONE when GCR
196*644b4646SEd Maste * supports that.
197*644b4646SEd Maste */
198*644b4646SEd Maste if (state->type == PROMPT_ENTRY) {
199*644b4646SEd Maste gcr_prompt_password_async(prompt, NULL, prompt_password_done, state);
200*644b4646SEd Maste } else {
201*644b4646SEd Maste gcr_prompt_confirm_async(prompt, NULL, prompt_confirm_done, state);
202*644b4646SEd Maste }
203*644b4646SEd Maste
204*644b4646SEd Maste /* The exit status will be changed in the async callbacks. */
205*644b4646SEd Maste return 1;
206*644b4646SEd Maste }
207*644b4646SEd Maste
208*644b4646SEd Maste int
main(int argc,char ** argv)209*644b4646SEd Maste main(int argc, char **argv)
210*644b4646SEd Maste {
211*644b4646SEd Maste g_autoptr(GApplication) app = g_application_new(
212*644b4646SEd Maste "com.openssh.gnome-ssh-askpass4",
213*644b4646SEd Maste G_APPLICATION_HANDLES_COMMAND_LINE);
214*644b4646SEd Maste g_autofree char *message = NULL;
215*644b4646SEd Maste
216*644b4646SEd Maste if (argc > 1) {
217*644b4646SEd Maste message = g_strjoinv(" ", argv + 1);
218*644b4646SEd Maste } else {
219*644b4646SEd Maste message = g_strdup("Enter your OpenSSH passphrase:");
220*644b4646SEd Maste }
221*644b4646SEd Maste
222*644b4646SEd Maste const char *prompt_mode = getenv("SSH_ASKPASS_PROMPT");
223*644b4646SEd Maste PromptType type = PROMPT_ENTRY;
224*644b4646SEd Maste if (prompt_mode != NULL) {
225*644b4646SEd Maste if (strcasecmp(prompt_mode, "confirm") == 0) {
226*644b4646SEd Maste type = PROMPT_CONFIRM;
227*644b4646SEd Maste } else if (strcasecmp(prompt_mode, "none") == 0) {
228*644b4646SEd Maste type = PROMPT_NONE;
229*644b4646SEd Maste }
230*644b4646SEd Maste }
231*644b4646SEd Maste
232*644b4646SEd Maste g_autoptr(PromptState) state = prompt_state_new(app, message, type);
233*644b4646SEd Maste
234*644b4646SEd Maste g_signal_connect(app, "command-line", G_CALLBACK(command_line), state);
235*644b4646SEd Maste
236*644b4646SEd Maste /*
237*644b4646SEd Maste * Since we are calling g_application_hold, we cannot use
238*644b4646SEd Maste * g_application_command_line_set_exit_status.
239*644b4646SEd Maste * To change the exit status returned by g_application_run:
240*644b4646SEd Maste * “If the commandline invocation results in the mainloop running
241*644b4646SEd Maste * (ie: because the use-count of the application increased to a
242*644b4646SEd Maste * non-zero value) then the application is considered to have been
243*644b4646SEd Maste * ‘successful’ in a certain sense, and the exit status is always
244*644b4646SEd Maste * zero.”
245*644b4646SEd Maste */
246*644b4646SEd Maste (void)(g_application_run(app, argc, argv));
247*644b4646SEd Maste
248*644b4646SEd Maste return state->exit_status;
249*644b4646SEd Maste }
250