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 /* GTK2 support by Nalin Dahyabhai <nalin@redhat.com> */
26 
27 /*
28  * This is a simple GNOME SSH passphrase grabber. 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  * There is only two run-time options: if you set the environment variable
33  * "GNOME_SSH_ASKPASS_GRAB_SERVER=true" then gnome-ssh-askpass will grab
34  * the X server. If you set "GNOME_SSH_ASKPASS_GRAB_POINTER=true", then the
35  * pointer will be grabbed too. These may have some benefit to security if
36  * you don't trust your X server. We grab the keyboard always.
37  */
38 
39 #define GRAB_TRIES	16
40 #define GRAB_WAIT	250 /* milliseconds */
41 
42 #define PROMPT_ENTRY	0
43 #define PROMPT_CONFIRM	1
44 #define PROMPT_NONE	2
45 
46 /*
47  * Compile with:
48  *
49  * cc -Wall `pkg-config --cflags gtk+-2.0` \
50  *    gnome-ssh-askpass2.c -o gnome-ssh-askpass \
51  *    `pkg-config --libs gtk+-2.0`
52  *
53  */
54 
55 #include <stdlib.h>
56 #include <stdio.h>
57 #include <string.h>
58 #include <unistd.h>
59 #include <X11/Xlib.h>
60 #include <gtk/gtk.h>
61 #include <gdk/gdkx.h>
62 
63 static void
report_failed_grab(GtkWidget * parent_window,const char * what)64 report_failed_grab (GtkWidget *parent_window, const char *what)
65 {
66 	GtkWidget *err;
67 
68 	err = gtk_message_dialog_new(GTK_WINDOW(parent_window), 0,
69 				     GTK_MESSAGE_ERROR,
70 				     GTK_BUTTONS_CLOSE,
71 				     "Could not grab %s. "
72 				     "A malicious client may be eavesdropping "
73 				     "on your session.", what);
74 	gtk_window_set_position(GTK_WINDOW(err), GTK_WIN_POS_CENTER);
75 
76 	gtk_dialog_run(GTK_DIALOG(err));
77 
78 	gtk_widget_destroy(err);
79 }
80 
81 static void
ok_dialog(GtkWidget * entry,gpointer dialog)82 ok_dialog(GtkWidget *entry, gpointer dialog)
83 {
84 	g_return_if_fail(GTK_IS_DIALOG(dialog));
85 	gtk_dialog_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
86 }
87 
88 static int
passphrase_dialog(char * message,int prompt_type)89 passphrase_dialog(char *message, int prompt_type)
90 {
91 	const char *failed;
92 	char *passphrase, *local;
93 	int result, grab_tries, grab_server, grab_pointer;
94 	int buttons, default_response;
95 	GtkWidget *parent_window, *dialog, *entry;
96 	GdkGrabStatus status;
97 
98 	grab_server = (getenv("GNOME_SSH_ASKPASS_GRAB_SERVER") != NULL);
99 	grab_pointer = (getenv("GNOME_SSH_ASKPASS_GRAB_POINTER") != NULL);
100 	grab_tries = 0;
101 
102 	/* Create an invisible parent window so that GtkDialog doesn't
103 	 * complain.  */
104 	parent_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
105 
106 	switch (prompt_type) {
107 	case PROMPT_CONFIRM:
108 		buttons = GTK_BUTTONS_YES_NO;
109 		default_response = GTK_RESPONSE_YES;
110 		break;
111 	case PROMPT_NONE:
112 		buttons = GTK_BUTTONS_CLOSE;
113 		default_response = GTK_RESPONSE_CLOSE;
114 		break;
115 	default:
116 		buttons = GTK_BUTTONS_OK_CANCEL;
117 		default_response = GTK_RESPONSE_OK;
118 		break;
119 	}
120 
121 	dialog = gtk_message_dialog_new(GTK_WINDOW(parent_window), 0,
122 	    GTK_MESSAGE_QUESTION, buttons, "%s", message);
123 
124 	gtk_window_set_title(GTK_WINDOW(dialog), "OpenSSH");
125 	gtk_window_set_position (GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
126 	gtk_window_set_keep_above(GTK_WINDOW(dialog), TRUE);
127 	gtk_dialog_set_default_response(GTK_DIALOG(dialog), default_response);
128 	gtk_window_set_keep_above(GTK_WINDOW(dialog), TRUE);
129 
130 	if (prompt_type == PROMPT_ENTRY) {
131 		entry = gtk_entry_new();
132 		gtk_box_pack_start(
133 		    GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
134 		    entry, FALSE, FALSE, 0);
135 		gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
136 		gtk_widget_grab_focus(entry);
137 		gtk_widget_show(entry);
138 		/* Make <enter> close dialog */
139 		g_signal_connect(G_OBJECT(entry), "activate",
140 				 G_CALLBACK(ok_dialog), dialog);
141 	}
142 
143 	/* Grab focus */
144 	gtk_widget_show_now(dialog);
145 	if (grab_pointer) {
146 		for(;;) {
147 			status = gdk_pointer_grab(
148 			    (gtk_widget_get_window(GTK_WIDGET(dialog))), TRUE,
149 			    0, NULL, NULL, GDK_CURRENT_TIME);
150 			if (status == GDK_GRAB_SUCCESS)
151 				break;
152 			usleep(GRAB_WAIT * 1000);
153 			if (++grab_tries > GRAB_TRIES) {
154 				failed = "mouse";
155 				goto nograb;
156 			}
157 		}
158 	}
159 	for(;;) {
160 		status = gdk_keyboard_grab(
161 		    gtk_widget_get_window(GTK_WIDGET(dialog)), FALSE,
162 		    GDK_CURRENT_TIME);
163 		if (status == GDK_GRAB_SUCCESS)
164 			break;
165 		usleep(GRAB_WAIT * 1000);
166 		if (++grab_tries > GRAB_TRIES) {
167 			failed = "keyboard";
168 			goto nograbkb;
169 		}
170 	}
171 	if (grab_server) {
172 		gdk_x11_grab_server();
173 	}
174 
175 	result = gtk_dialog_run(GTK_DIALOG(dialog));
176 
177 	/* Ungrab */
178 	if (grab_server)
179 		XUngrabServer(gdk_x11_get_default_xdisplay());
180 	if (grab_pointer)
181 		gdk_pointer_ungrab(GDK_CURRENT_TIME);
182 	gdk_keyboard_ungrab(GDK_CURRENT_TIME);
183 	gdk_flush();
184 
185 	/* Report passphrase if user selected OK */
186 	if (prompt_type == PROMPT_ENTRY) {
187 		passphrase = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
188 		if (result == GTK_RESPONSE_OK) {
189 			local = g_locale_from_utf8(passphrase,
190 			    strlen(passphrase), NULL, NULL, NULL);
191 			if (local != NULL) {
192 				puts(local);
193 				memset(local, '\0', strlen(local));
194 				g_free(local);
195 			} else {
196 				puts(passphrase);
197 			}
198 		}
199 		/* Zero passphrase in memory */
200 		memset(passphrase, '\b', strlen(passphrase));
201 		gtk_entry_set_text(GTK_ENTRY(entry), passphrase);
202 		memset(passphrase, '\0', strlen(passphrase));
203 		g_free(passphrase);
204 	}
205 
206 	gtk_widget_destroy(dialog);
207 	if (result != GTK_RESPONSE_OK && result != GTK_RESPONSE_YES)
208 		return -1;
209 	return 0;
210 
211  nograbkb:
212 	/*
213 	 * At least one grab failed - ungrab what we got, and report
214 	 * the failure to the user.  Note that XGrabServer() cannot
215 	 * fail.
216 	 */
217 	gdk_pointer_ungrab(GDK_CURRENT_TIME);
218  nograb:
219 	if (grab_server)
220 		XUngrabServer(gdk_x11_get_default_xdisplay());
221 	gtk_widget_destroy(dialog);
222 
223 	report_failed_grab(parent_window, failed);
224 
225 	return (-1);
226 }
227 
228 int
main(int argc,char ** argv)229 main(int argc, char **argv)
230 {
231 	char *message, *prompt_mode;
232 	int result, prompt_type = PROMPT_ENTRY;
233 
234 	gtk_init(&argc, &argv);
235 
236 	if (argc > 1) {
237 		message = g_strjoinv(" ", argv + 1);
238 	} else {
239 		message = g_strdup("Enter your OpenSSH passphrase:");
240 	}
241 
242 	if ((prompt_mode = getenv("SSH_ASKPASS_PROMPT")) != NULL) {
243 		if (strcasecmp(prompt_mode, "confirm") == 0)
244 			prompt_type = PROMPT_CONFIRM;
245 		else if (strcasecmp(prompt_mode, "none") == 0)
246 			prompt_type = PROMPT_NONE;
247 	}
248 
249 	setvbuf(stdout, 0, _IONBF, 0);
250 	result = passphrase_dialog(message, prompt_type);
251 	g_free(message);
252 
253 	return (result);
254 }
255