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