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