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