2006-08-21 [colin] 2.4.0cvs67
[claws.git] / src / plugins / pgpcore / passphrase.c
1 /* passphrase.c - GTK+ based passphrase callback
2  *      Copyright (C) 2001-2006 Werner Koch (dd9jn) and the Sylpheed-Claws team
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22
23 #if USE_GPGME
24
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <gdk/gdktypes.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <gdk/gdkdisplay.h>
30 #ifdef GDK_WINDOWING_X11
31 #  include <gdk/gdkx.h>
32 #endif /* GDK_WINDOWING_X11 */
33 #include <gtk/gtkmain.h>
34 #include <gtk/gtkwidget.h>
35 #include <gtk/gtkwindow.h>
36 #include <gtk/gtkvbox.h>
37 #include <gtk/gtktable.h>
38 #include <gtk/gtklabel.h>
39 #include <gtk/gtkentry.h>
40 #include <gtk/gtkhbbox.h>
41 #include <gtk/gtkbutton.h>
42 #include <gtk/gtkfilesel.h>
43 #include <gtk/gtksignal.h>
44 #include <string.h>
45 #include <sys/types.h>
46 #ifdef G_OS_WIN32
47 #include <windows.h>
48 #else
49 #include <sys/mman.h>
50 #endif
51
52 #include "passphrase.h"
53 #include "prefs_common.h"
54 #include "prefs_gpg.h"
55 #include "manage_window.h"
56 #include "utils.h"
57 #include "mainwindow.h"
58 #include "summaryview.h"
59
60 static gboolean grab_all = FALSE;
61
62 static gboolean pass_ack;
63 static gchar *last_pass = NULL;
64
65 static void passphrase_ok_cb(GtkWidget *widget, gpointer data);
66 static void passphrase_cancel_cb(GtkWidget *widget, gpointer data);
67 static gint passphrase_deleted(GtkWidget *widget, GdkEventAny *event,
68                                gpointer data);
69 static gboolean passphrase_key_pressed(GtkWidget *widget, GdkEventKey *event,
70                                        gpointer data);
71
72 static GtkWidget *create_description(const gchar *uid_hint,
73                                      const gchar *pass_hint, gint prev_bad, gint new_key);
74
75 void
76 gpgmegtk_set_passphrase_grab(gint yes)
77 {
78     grab_all = yes;
79 }
80
81 gchar*
82 passphrase_mbox(const gchar *uid_hint, const gchar *pass_hint, gint prev_bad, gint new_key)
83 {
84     gchar *the_passphrase = NULL;
85     GtkWidget *vbox, *hbox;
86     GtkWidget *confirm_box;
87     GtkWidget *window;
88     GtkWidget *pass_entry;
89     GtkWidget *ok_button;
90     GtkWidget *cancel_button;
91
92     SummaryView *summaryview = mainwindow_get_mainwindow()->summaryview;
93     
94     gtk_menu_popdown(GTK_MENU(summaryview->popupmenu));
95
96     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
97     gtk_window_set_title(GTK_WINDOW(window), _("Passphrase"));
98     gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
99     gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
100     gtk_window_set_modal(GTK_WINDOW(window), TRUE);
101     gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, FALSE);
102     g_signal_connect(G_OBJECT(window), "delete_event",
103                      G_CALLBACK(passphrase_deleted), NULL);
104     g_signal_connect(G_OBJECT(window), "key_press_event",
105                      G_CALLBACK(passphrase_key_pressed), NULL);
106     MANAGE_WINDOW_SIGNALS_CONNECT(window);
107     manage_window_set_transient(GTK_WINDOW(window));
108
109     vbox = gtk_vbox_new(FALSE, 8);
110     gtk_container_add(GTK_CONTAINER(window), vbox);
111     gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
112
113     if (uid_hint || pass_hint) {
114         GtkWidget *label, *icon;
115         label = create_description (uid_hint, pass_hint, prev_bad, new_key);
116         icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_AUTHENTICATION,
117                                 GTK_ICON_SIZE_DIALOG); 
118
119         hbox = gtk_hbox_new (FALSE, 12);
120         gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
121         gtk_widget_show (hbox);
122         gtk_box_pack_start (GTK_BOX(hbox), icon, FALSE, FALSE, 0);
123         gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0);
124         gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
125     }
126
127     pass_entry = gtk_entry_new();
128     gtk_box_pack_start(GTK_BOX(vbox), pass_entry, FALSE, FALSE, 0);
129     gtk_entry_set_visibility(GTK_ENTRY(pass_entry), FALSE);
130     gtk_widget_grab_focus(pass_entry);
131
132     gtkut_stock_button_set_create(&confirm_box, 
133                                   &cancel_button, GTK_STOCK_CANCEL,
134                                   &ok_button, GTK_STOCK_OK,
135                                   NULL, NULL);
136
137     gtk_widget_set_size_request(confirm_box, 375, -1);
138
139     gtk_box_pack_end(GTK_BOX(vbox), confirm_box, FALSE, FALSE, 0);
140     gtk_widget_grab_default(ok_button);
141
142     g_signal_connect(G_OBJECT(ok_button), "clicked",
143                      G_CALLBACK(passphrase_ok_cb), NULL);
144     g_signal_connect(G_OBJECT(pass_entry), "activate",
145                      G_CALLBACK(passphrase_ok_cb), NULL);
146     g_signal_connect(G_OBJECT(cancel_button), "clicked",
147                      G_CALLBACK(passphrase_cancel_cb), NULL);
148
149     gtk_window_set_position (GTK_WINDOW(window), GTK_WIN_POS_CENTER);
150     if (grab_all)   
151         gtk_window_set_policy (GTK_WINDOW(window), FALSE, FALSE, TRUE);
152     
153     gtk_widget_show_all(window);
154
155     if (grab_all) {
156         int err = 0, cnt = 0;
157         /* make sure that window is viewable */
158         gtk_widget_show_now(window);
159         gdk_window_process_updates(window->window, TRUE);
160         gdk_flush();
161         while(gtk_events_pending()) {
162                 gtk_main_iteration();
163         }
164 try_again:
165         if ((err = gdk_pointer_grab(window->window, TRUE, 0,
166                              window->window, NULL, GDK_CURRENT_TIME))) {
167             if (err == GDK_GRAB_NOT_VIEWABLE && cnt < 10) {
168                 cnt++;
169                 g_warning("trying to grab mouse again\n");
170                 gtk_main_iteration();
171                 goto try_again;
172             } else {
173                 g_warning("OOPS: Could not grab mouse\n");
174                 gtk_widget_destroy(window);
175                 return NULL;
176             }
177         }
178         if (gdk_keyboard_grab(window->window, FALSE, GDK_CURRENT_TIME)) {
179             gdk_display_pointer_ungrab(gdk_display_get_default(),
180                                        GDK_CURRENT_TIME);
181             g_warning("OOPS: Could not grab keyboard\n");
182             gtk_widget_destroy(window);
183             return NULL;
184         }
185     }
186
187     gtk_main();
188
189     if (grab_all) {
190         gdk_display_keyboard_ungrab(gdk_display_get_default(),
191                                     GDK_CURRENT_TIME);
192         gdk_display_pointer_ungrab(gdk_display_get_default(), GDK_CURRENT_TIME);
193         gdk_flush();
194     }
195
196     manage_window_focus_out(window, NULL, NULL);
197
198     if (pass_ack) {
199         const gchar *entry_text;
200         entry_text = gtk_entry_get_text(GTK_ENTRY(pass_entry));
201         the_passphrase = g_locale_from_utf8(entry_text, -1, NULL, NULL, NULL);
202         if (the_passphrase == NULL) 
203             the_passphrase = g_strdup (entry_text);
204     }
205     gtk_widget_destroy (window);
206
207     return the_passphrase;
208 }
209
210
211 static void 
212 passphrase_ok_cb(GtkWidget *widget, gpointer data)
213 {
214     pass_ack = TRUE;
215     gtk_main_quit();
216 }
217
218 static void 
219 passphrase_cancel_cb(GtkWidget *widget, gpointer data)
220 {
221     pass_ack = FALSE;
222     gtk_main_quit();
223 }
224
225
226 static gint
227 passphrase_deleted(GtkWidget *widget, GdkEventAny *event, gpointer data)
228 {
229     passphrase_cancel_cb(NULL, NULL);
230     return TRUE;
231 }
232
233
234 static gboolean
235 passphrase_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
236 {
237     if (event && event->keyval == GDK_Escape)
238         passphrase_cancel_cb(NULL, NULL);
239     return FALSE;
240 }
241
242 static gint 
243 linelen (const gchar *s)
244 {
245     gint i;
246
247     for (i = 0; *s && *s != '\n'; s++, i++)
248         ;
249
250     return i;
251 }
252
253 static GtkWidget *
254 create_description(const gchar *uid_hint, const gchar *pass_hint, gint prev_bad, gint new_key)
255 {
256     const gchar *uid = NULL, *info = NULL;
257     gchar *buf;
258     GtkWidget *label;
259     gchar *my_uid = NULL;
260     if (!uid_hint)
261         uid = _("[no user id]");
262     else
263         uid = uid_hint;
264     if (!pass_hint)
265         info = "";
266     else
267         info = pass_hint;
268
269     my_uid = g_strdup(uid);
270     while (strchr(my_uid, '<')) 
271         *(strchr(my_uid, '<')) = '(';
272     while (strchr(my_uid, '>')) 
273         *(strchr(my_uid, '>')) = ')';
274
275     if (new_key == 1) {
276             buf = g_strdup_printf (_("<span weight=\"bold\" size=\"larger\">%sPlease enter the passphrase for the new key:</span>\n\n"
277                            "%.*s\n"),
278                            prev_bad ?
279                            _("Passphrases did not match.\n") : "",
280                            linelen (my_uid), my_uid);
281     } else if (new_key == 2) {
282             buf = g_strdup_printf (_("<span weight=\"bold\" size=\"larger\">Please re-enter the passphrase for the new key:</span>\n\n"
283                            "%.*s\n"),
284                            linelen (my_uid), my_uid);
285     } else {
286             buf = g_strdup_printf (_("<span weight=\"bold\" size=\"larger\">%sPlease enter the passphrase for:</span>\n\n"
287                            "%.*s\n"),
288                            prev_bad ?
289                            _("Bad passphrase.\n") : "",
290                            linelen (my_uid), my_uid);
291     }
292     g_free(my_uid);
293     label = gtk_label_new (buf);
294     gtk_label_set_use_markup(GTK_LABEL (label), TRUE);
295     gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
296     gtk_label_set_line_wrap(GTK_LABEL (label), TRUE);
297     g_free (buf);
298
299     return label;
300 }
301
302 static int free_passphrase(gpointer _unused)
303 {
304     if (last_pass != NULL) {
305 #ifndef G_PLATFORM_WIN32
306         munlock(last_pass, strlen(last_pass));
307 #endif
308         g_free(last_pass);
309         last_pass = NULL;
310         debug_print("%% passphrase removed\n");
311     }
312     
313     return FALSE;
314 }
315
316 gpgme_error_t
317 gpgmegtk_passphrase_cb(void *opaque, const char *uid_hint,
318         const char *passphrase_hint, int prev_bad, int fd)
319 {
320     const char *pass;
321
322     if (prefs_gpg_get_config()->store_passphrase && last_pass && !prev_bad)
323         pass = last_pass;
324     else {
325     gpgmegtk_set_passphrase_grab (prefs_gpg_get_config()->passphrase_grab);
326     debug_print ("%% requesting passphrase for '%s'\n ", uid_hint);
327     pass = passphrase_mbox (uid_hint, passphrase_hint, prev_bad, FALSE);
328     gpgmegtk_free_passphrase();
329     if (!pass) {
330         debug_print ("%% cancel passphrase entry\n");
331         write(fd, "\n", 1);
332         return GPG_ERR_CANCELED;
333     }
334     else {
335         if (prefs_gpg_get_config()->store_passphrase) {
336             last_pass = g_strdup(pass);
337 #ifndef G_PLATFORM_WIN32
338             if (mlock(last_pass, strlen(last_pass)) == -1)
339                 debug_print("%% locking passphrase failed\n");
340 #endif
341             if (prefs_gpg_get_config()->store_passphrase_timeout > 0) {
342                     gtk_timeout_add(prefs_gpg_get_config()
343                                     ->store_passphrase_timeout*60*1000,
344                                 free_passphrase, NULL);
345             }
346         }
347         debug_print ("%% sending passphrase\n");
348     }
349     }
350
351 #ifdef G_OS_WIN32
352     {
353         /* Under Windows FD is actually a System handle. */
354         DWORD nwritten;
355         WriteFile ((HANDLE)fd, pass, strlen (pass), &nwritten, NULL);
356         WriteFile ((HANDLE)fd, "\n", 1, &nwritten, NULL);
357     }
358 #else
359     write(fd, pass, strlen(pass));
360     write(fd, "\n", 1);
361 #endif
362     return GPG_ERR_NO_ERROR;
363 }
364
365 void gpgmegtk_free_passphrase()
366 {
367     (void)free_passphrase(NULL); /* could be inline */
368 }
369
370 #endif /* USE_GPGME */