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