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