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