ed344686aa83b6f58bc43cf96a77fe9098a78460
[claws.git] / src / plugins / pgpcore / select-keys.c
1 /* select-keys.c - GTK+ based key selection
2  *      Copyright (C) 2001-2007 Werner Koch (dd9jn) and the Claws Mail 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 #ifdef USE_GPGME
24 #include <stdio.h>
25 #include <stdlib.h>
26
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <gtk/gtkmain.h>
31 #include <gtk/gtkwidget.h>
32 #include <gtk/gtkwindow.h>
33 #include <gtk/gtkscrolledwindow.h>
34 #include <gtk/gtkvbox.h>
35 #include <gtk/gtkhbox.h>
36 #include <gtk/gtkclist.h>
37 #include <gtk/gtklabel.h>
38 #include <gtk/gtkentry.h>
39 #include <gtk/gtkhbbox.h>
40 #include <gtk/gtkbutton.h>
41 #include <gtk/gtkstock.h>
42
43 #include "select-keys.h"
44 #include "utils.h"
45 #include "gtkutils.h"
46 #include "inputdialog.h"
47 #include "manage_window.h"
48 #include "alertpanel.h"
49
50 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
51 #define DIMof(type,member)   DIM(((type *)0)->member)
52
53
54 enum col_titles { 
55     COL_ALGO,
56     COL_KEYID,
57     COL_NAME,
58     COL_EMAIL,
59     COL_VALIDITY,
60
61     N_COL_TITLES
62 };
63
64 struct select_keys_s {
65     int okay;
66     GtkWidget *window;
67     GtkLabel *toplabel;
68     GtkCList *clist;
69     const char *pattern;
70     unsigned int num_keys;
71     gpgme_key_t *kset;
72     gpgme_ctx_t select_ctx;
73     gpgme_protocol_t proto;
74     GtkSortType sort_type;
75     enum col_titles sort_column;
76     SelectionResult result;
77 };
78
79
80 static void set_row (GtkCList *clist, gpgme_key_t key);
81 static gpgme_key_t fill_clist (struct select_keys_s *sk, const char *pattern,
82                         gpgme_protocol_t proto);
83 static void create_dialog (struct select_keys_s *sk);
84 static void open_dialog (struct select_keys_s *sk);
85 static void close_dialog (struct select_keys_s *sk);
86 static gint delete_event_cb (GtkWidget *widget,
87                              GdkEventAny *event, gpointer data);
88 static gboolean key_pressed_cb (GtkWidget *widget,
89                                 GdkEventKey *event, gpointer data);
90 static void select_btn_cb (GtkWidget *widget, gpointer data);
91 static void cancel_btn_cb (GtkWidget *widget, gpointer data);
92 static void dont_encrypt_btn_cb (GtkWidget *widget, gpointer data);
93 static void other_btn_cb (GtkWidget *widget, gpointer data);
94 static void sort_keys (struct select_keys_s *sk, enum col_titles column);
95 static void sort_keys_name (GtkWidget *widget, gpointer data);
96 static void sort_keys_email (GtkWidget *widget, gpointer data);
97
98 static gboolean use_untrusted (gpgme_key_t);
99
100 static void
101 update_progress (struct select_keys_s *sk, int running, const char *pattern)
102 {
103     static int windmill[] = { '-', '\\', '|', '/' };
104     char *buf;
105
106     if (!running)
107         buf = g_strdup_printf (_("No exact match for '%s'; please select the key."),
108                                pattern);
109     else 
110         buf = g_strdup_printf (_("Collecting info for '%s' ... %c"),
111                                pattern,
112                                windmill[running%DIM(windmill)]);
113     gtk_label_set_text (sk->toplabel, buf);
114     g_free (buf);
115 }
116
117
118 /**
119  * gpgmegtk_recipient_selection:
120  * @recp_names: A list of email addresses
121  * 
122  * Select a list of recipients from a given list of email addresses.
123  * This may pop up a window to present the user a choice, it will also
124  * check that the recipients key are all valid.
125  * 
126  * Return value: NULL on error or a list of list of recipients.
127  **/
128 gpgme_key_t *
129 gpgmegtk_recipient_selection (GSList *recp_names, SelectionResult *result,
130                                 gpgme_protocol_t proto)
131 {
132     struct select_keys_s sk;
133     gpgme_key_t key = NULL;
134     memset (&sk, 0, sizeof sk);
135
136     open_dialog (&sk);
137
138     do {
139         sk.pattern = recp_names? recp_names->data:NULL;
140         sk.proto = proto;
141         gtk_clist_clear (sk.clist);
142         key = fill_clist (&sk, sk.pattern, proto);
143         update_progress (&sk, 0, sk.pattern);
144         if (!key) {
145                 gtk_widget_show_all (sk.window);
146                 gtk_main ();
147         } else {
148                 gtk_widget_hide (sk.window);
149                 sk.kset = g_realloc(sk.kset,
150                         sizeof(gpgme_key_t) * (sk.num_keys + 1));
151                 gpgme_key_ref(key);
152                 sk.kset[sk.num_keys] = key;
153                 sk.num_keys++;
154                 sk.okay = 1;
155                 sk.result = KEY_SELECTION_OK;
156                 gpgme_release (sk.select_ctx);
157                 sk.select_ctx = NULL;
158                 debug_print("used %s\n", key->uids->email);
159         }
160         key = NULL;
161         if (recp_names)
162             recp_names = recp_names->next;
163     } while (sk.okay && recp_names);
164
165     close_dialog (&sk);
166
167     if (!sk.okay) {
168         g_free(sk.kset);
169         sk.kset = NULL;
170     } else {
171         sk.kset = g_realloc(sk.kset, sizeof(gpgme_key_t) * (sk.num_keys + 1));
172         sk.kset[sk.num_keys] = NULL;
173     }
174     if (result)
175             *result = sk.result;
176     return sk.kset;
177
178
179 static void
180 destroy_key (gpointer data)
181 {
182     gpgme_key_t key = data;
183     gpgme_key_release (key);
184 }
185
186 static void
187 set_row (GtkCList *clist, gpgme_key_t key)
188 {
189     const char *s;
190     const char *text[N_COL_TITLES];
191     char *algo_buf;
192     int row;
193     gsize by_read = 0, by_written = 0;
194     gchar *ret_str;
195
196     /* first check whether the key is capable of encryption which is not
197      * the case for revoked, expired or sign-only keys */
198     if (!key->can_encrypt)
199         return;
200     algo_buf = g_strdup_printf ("%du/%s", 
201          key->subkeys->length,
202          gpgme_pubkey_algo_name(key->subkeys->pubkey_algo) );
203     text[COL_ALGO] = algo_buf;
204
205     s = key->subkeys->keyid;
206     if (strlen (s) == 16)
207         s += 8; /* show only the short keyID */
208     text[COL_KEYID] = s;
209
210     s = key->uids->name;
211     if (!s || !strlen(s))
212         s = key->uids->uid;
213     ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
214     if (ret_str && by_written) {
215         s = ret_str;
216     }
217     text[COL_NAME] = s;
218
219     s = key->uids->email;
220     ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
221     if (ret_str && by_written) {
222         s = ret_str;
223     }
224     text[COL_EMAIL] = s;
225
226     switch (key->uids->validity)
227       {
228       case GPGME_VALIDITY_UNDEFINED:
229         s = "q";
230         break;
231       case GPGME_VALIDITY_NEVER:
232         s = "n";
233         break;
234       case GPGME_VALIDITY_MARGINAL:
235         s = "m";
236         break;
237       case GPGME_VALIDITY_FULL:
238         s = "f";
239         break;
240       case GPGME_VALIDITY_ULTIMATE:
241         s = "u";
242         break;
243       case GPGME_VALIDITY_UNKNOWN:
244       default:
245         s = "?";
246         break;
247       }
248     text[COL_VALIDITY] = s;
249
250     row = gtk_clist_append (clist, (gchar**)text);
251     g_free (algo_buf);
252
253     gtk_clist_set_row_data_full (clist, row, key, destroy_key);
254 }
255
256 static gpgme_key_t 
257 fill_clist (struct select_keys_s *sk, const char *pattern, gpgme_protocol_t proto)
258 {
259     GtkCList *clist;
260     gpgme_ctx_t ctx;
261     gpgme_error_t err;
262     gpgme_key_t key;
263     int running=0;
264     int num_results = 0;
265     gboolean exact_match = FALSE;
266     gpgme_key_t last_key = NULL;
267     g_return_val_if_fail (sk, NULL);
268     clist = sk->clist;
269     g_return_val_if_fail (clist, NULL);
270
271     debug_print ("select_keys:fill_clist:  pattern '%s' proto %d\n", pattern, proto);
272
273     /*gtk_clist_freeze (select_keys.clist);*/
274     err = gpgme_new (&ctx);
275     g_assert (!err);
276
277     gpgme_set_protocol(ctx, proto);
278     sk->select_ctx = ctx;
279
280     update_progress (sk, ++running, pattern);
281     while (gtk_events_pending ())
282         gtk_main_iteration ();
283
284     err = gpgme_op_keylist_start (ctx, pattern, 0);
285     if (err) {
286         debug_print ("** gpgme_op_keylist_start(%s) failed: %s",
287                      pattern, gpgme_strerror (err));
288         sk->select_ctx = NULL;
289         gpgme_release(ctx);
290         return NULL;
291     }
292     update_progress (sk, ++running, pattern);
293     while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
294         gpgme_user_id_t uid = key->uids;
295         debug_print ("%% %s:%d:  insert\n", __FILE__ ,__LINE__ );
296         set_row (clist, key ); 
297         for (; uid; uid = uid->next) {
298                 if (!strcmp(pattern, uid->email)) {
299                         exact_match = TRUE;
300                         break;
301                 }
302         }
303         num_results++;
304         last_key = key;
305         key = NULL;
306         update_progress (sk, ++running, pattern);
307         while (gtk_events_pending ())
308             gtk_main_iteration ();
309     }
310  
311     if (exact_match == TRUE && num_results == 1) {
312             if (last_key->uids->validity < GPGME_VALIDITY_FULL && 
313                 !use_untrusted(last_key))
314                     exact_match = FALSE;
315     }
316
317     debug_print ("%% %s:%d:  ready\n", __FILE__ ,__LINE__ );
318     if (gpgme_err_code(err) != GPG_ERR_EOF) {
319         debug_print ("** gpgme_op_keylist_next failed: %s",
320                      gpgme_strerror (err));
321         gpgme_op_keylist_end(ctx);
322     }
323     if (!exact_match || num_results != 1) {
324             sk->select_ctx = NULL;
325             gpgme_release (ctx);
326     }
327     /*gtk_clist_thaw (select_keys.clist);*/
328     return (exact_match == TRUE && num_results == 1 ? last_key:NULL);
329 }
330
331
332 static void 
333 create_dialog (struct select_keys_s *sk)
334 {
335     GtkWidget *window;
336     GtkWidget *vbox, *vbox2, *hbox;
337     GtkWidget *bbox;
338     GtkWidget *scrolledwin;
339     GtkWidget *clist;
340     GtkWidget *label;
341     GtkWidget *select_btn, *cancel_btn, *dont_encrypt_btn, *other_btn;
342     const char *titles[N_COL_TITLES];
343
344     g_assert (!sk->window);
345     window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
346     gtk_widget_set_size_request (window, 520, 280);
347     gtk_container_set_border_width (GTK_CONTAINER (window), 8);
348     gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
349     gtk_window_set_modal (GTK_WINDOW (window), TRUE);
350     g_signal_connect (G_OBJECT (window), "delete_event",
351                       G_CALLBACK (delete_event_cb), sk);
352     g_signal_connect (G_OBJECT (window), "key_press_event",
353                       G_CALLBACK (key_pressed_cb), sk);
354     MANAGE_WINDOW_SIGNALS_CONNECT (window);
355
356     vbox = gtk_vbox_new (FALSE, 8);
357     gtk_container_add (GTK_CONTAINER (window), vbox);
358
359     hbox  = gtk_hbox_new(FALSE, 4);
360     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
361     label = gtk_label_new ( "" );
362     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
363
364     hbox = gtk_hbox_new (FALSE, 8);
365     gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
366     gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
367
368     scrolledwin = gtk_scrolled_window_new (NULL, NULL);
369     gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
370     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
371                                     GTK_POLICY_AUTOMATIC,
372                                     GTK_POLICY_AUTOMATIC);
373
374     titles[COL_ALGO]     = _("Size");
375     titles[COL_KEYID]    = _("Key ID");
376     titles[COL_NAME]     = _("Name");
377     titles[COL_EMAIL]    = _("Address");
378     titles[COL_VALIDITY] = _("Val");
379
380     clist = gtk_clist_new_with_titles (N_COL_TITLES, (char**)titles);
381     gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
382     gtk_clist_set_column_width (GTK_CLIST(clist), COL_ALGO,      72);
383     gtk_clist_set_column_width (GTK_CLIST(clist), COL_KEYID,     76);
384     gtk_clist_set_column_width (GTK_CLIST(clist), COL_NAME,     130);
385     gtk_clist_set_column_width (GTK_CLIST(clist), COL_EMAIL,    130);
386     gtk_clist_set_column_width (GTK_CLIST(clist), COL_VALIDITY,  20);
387     gtk_clist_set_selection_mode (GTK_CLIST(clist), GTK_SELECTION_BROWSE);
388     g_signal_connect (G_OBJECT(GTK_CLIST(clist)->column[COL_NAME].button),
389                       "clicked",
390                       G_CALLBACK(sort_keys_name), sk);
391     g_signal_connect (G_OBJECT(GTK_CLIST(clist)->column[COL_EMAIL].button),
392                       "clicked",
393                       G_CALLBACK(sort_keys_email), sk);
394
395     hbox = gtk_hbox_new (FALSE, 8);
396     gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
397
398     gtkut_stock_button_set_create (&bbox, 
399                                    &select_btn, _("Select"),
400                                    &other_btn, _("Other"),
401                                    &dont_encrypt_btn, _("Don't encrypt"));
402     
403     cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
404     GTK_WIDGET_SET_FLAGS(cancel_btn, GTK_CAN_DEFAULT);
405     gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
406     gtk_widget_show(cancel_btn);
407     gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
408     gtk_widget_grab_default (select_btn);
409
410     g_signal_connect (G_OBJECT (select_btn), "clicked",
411                       G_CALLBACK (select_btn_cb), sk);
412     g_signal_connect (G_OBJECT(cancel_btn), "clicked",
413                       G_CALLBACK (cancel_btn_cb), sk);
414     g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
415                       G_CALLBACK (dont_encrypt_btn_cb), sk);
416     g_signal_connect (G_OBJECT (other_btn), "clicked",
417                       G_CALLBACK (other_btn_cb), sk);
418
419     vbox2 = gtk_vbox_new (FALSE, 4);
420     gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
421
422     sk->window = window;
423     sk->toplabel = GTK_LABEL (label);
424     sk->clist  = GTK_CLIST (clist);
425 }
426
427
428 static void
429 open_dialog (struct select_keys_s *sk)
430 {
431     if (!sk->window)
432         create_dialog (sk);
433     manage_window_set_transient (GTK_WINDOW (sk->window));
434     sk->okay = 0;
435     sk->sort_column = N_COL_TITLES; /* use an invalid value */
436     sk->sort_type = GTK_SORT_ASCENDING;
437 }
438
439
440 static void
441 close_dialog (struct select_keys_s *sk)
442 {
443     g_return_if_fail (sk);
444     gtk_widget_destroy (sk->window);
445     sk->window = NULL;
446 }
447
448
449 static gint
450 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
451 {
452     struct select_keys_s *sk = data;
453
454     sk->okay = 0;
455     gtk_main_quit ();
456
457     return TRUE;
458 }
459
460
461 static gboolean
462 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
463 {
464     struct select_keys_s *sk = data;
465
466     g_return_val_if_fail (sk, FALSE);
467     if (event && event->keyval == GDK_Escape) {
468         sk->okay = 0;
469         gtk_main_quit ();
470     }
471     return FALSE;
472 }
473
474
475 static void 
476 select_btn_cb (GtkWidget *widget, gpointer data)
477 {
478     struct select_keys_s *sk = data;
479     int row;
480     gboolean use_key;
481     gpgme_key_t key;
482
483     g_return_if_fail (sk);
484     if (!sk->clist->selection) {
485         debug_print ("** nothing selected");
486         return;
487     }
488     row = GPOINTER_TO_INT(sk->clist->selection->data);
489     key = gtk_clist_get_row_data(sk->clist, row);
490     if (key) {
491         if ( key->uids->validity < GPGME_VALIDITY_FULL ) {
492             use_key = use_untrusted(key);
493             if (!use_key) {
494                 debug_print ("** Key untrusted, will not encrypt");
495                 return;
496             }
497         }
498         sk->kset = g_realloc(sk->kset,
499                 sizeof(gpgme_key_t) * (sk->num_keys + 1));
500         gpgme_key_ref(key);
501         sk->kset[sk->num_keys] = key;
502         sk->num_keys++;
503         sk->okay = 1;
504         sk->result = KEY_SELECTION_OK;
505         gtk_main_quit ();
506     }
507 }
508
509
510 static void 
511 cancel_btn_cb (GtkWidget *widget, gpointer data)
512 {
513     struct select_keys_s *sk = data;
514
515     g_return_if_fail (sk);
516     sk->okay = 0;
517     sk->result = KEY_SELECTION_CANCEL;
518     if (sk->select_ctx)
519         gpgme_cancel (sk->select_ctx);
520     gtk_main_quit ();
521 }
522
523 static void 
524 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
525 {
526     struct select_keys_s *sk = data;
527
528     g_return_if_fail (sk);
529     sk->okay = 0;
530     sk->result = KEY_SELECTION_DONT;
531     if (sk->select_ctx)
532         gpgme_cancel (sk->select_ctx);
533     gtk_main_quit ();
534 }
535
536 static void
537 other_btn_cb (GtkWidget *widget, gpointer data)
538 {
539     struct select_keys_s *sk = data;
540     char *uid;
541
542     g_return_if_fail (sk);
543     uid = input_dialog ( _("Add key"),
544                          _("Enter another user or key ID:"),
545                          NULL );
546     if (!uid)
547         return;
548     if (fill_clist (sk, uid, sk->proto) != NULL) {
549             gpgme_release(sk->select_ctx);
550             sk->select_ctx = NULL;
551     }
552     update_progress (sk, 0, sk->pattern);
553     g_free (uid);
554 }
555
556
557 static gboolean
558 use_untrusted (gpgme_key_t key)
559 {
560     AlertValue aval;
561     gchar *buf = g_strdup_printf(_("The key of '%s' is not fully trusted.\n"
562                "If you choose to encrypt the message with this key you don't\n"
563                "know for sure that it will go to the person you mean it to.\n"
564                "Do you trust it enough to use it anyway?"), key->uids->email);
565     aval = alertpanel
566             (_("Trust key"),
567              buf,
568              GTK_STOCK_NO, GTK_STOCK_YES, NULL);
569     g_free(buf);
570     if (aval == G_ALERTALTERNATE)
571         return TRUE;
572     else
573         return FALSE;
574 }
575
576
577 static gint 
578 cmp_name (GtkCList *clist, gconstpointer pa, gconstpointer pb)
579 {
580     gpgme_key_t a = ((GtkCListRow *)pa)->data;
581     gpgme_key_t b = ((GtkCListRow *)pb)->data;
582     const char *sa, *sb;
583     
584     sa = a? a->uids->name : NULL;
585     sb = b? b->uids->name : NULL;
586     if (!sa)
587         return !!sb;
588     if (!sb)
589         return -1;
590     return g_ascii_strcasecmp(sa, sb);
591 }
592
593 static gint 
594 cmp_email (GtkCList *clist, gconstpointer pa, gconstpointer pb)
595 {
596     gpgme_key_t a = ((GtkCListRow *)pa)->data;
597     gpgme_key_t b = ((GtkCListRow *)pb)->data;
598     const char *sa, *sb;
599     
600     sa = a? a->uids->email : NULL;
601     sb = b? b->uids->email : NULL;
602     if (!sa)
603         return !!sb;
604     if (!sb)
605         return -1;
606     return g_ascii_strcasecmp(sa, sb);
607 }
608
609 static void
610 sort_keys ( struct select_keys_s *sk, enum col_titles column)
611 {
612     GtkCList *clist = sk->clist;
613
614     switch (column) {
615       case COL_NAME:
616         gtk_clist_set_compare_func (clist, cmp_name);
617         break;
618       case COL_EMAIL:
619         gtk_clist_set_compare_func (clist, cmp_email);
620         break;
621       default:
622         return;
623     }
624
625     /* column clicked again: toggle as-/decending */
626     if ( sk->sort_column == column) {
627         sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
628                         GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
629     }
630     else
631         sk->sort_type = GTK_SORT_ASCENDING;
632
633     sk->sort_column = column;
634     gtk_clist_set_sort_type (clist, sk->sort_type);
635     gtk_clist_sort (clist);
636 }
637
638 static void
639 sort_keys_name (GtkWidget *widget, gpointer data)
640 {
641     sort_keys ((struct select_keys_s*)data, COL_NAME);
642 }
643
644 static void
645 sort_keys_email (GtkWidget *widget, gpointer data)
646 {
647     sort_keys ((struct select_keys_s*)data, COL_EMAIL);
648 }
649
650 #endif /*USE_GPGME*/