xref: /freebsd/crypto/openssh/contrib/gnome-ssh-askpass4.c (revision 644b4646c7acab87dc20d4e5dd53d2d9da152989)
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