1 /* select-keys.c - GTK+ based key selection
2 * Copyright (C) 2001-2012 Werner Koch (dd9jn) and the Claws Mail team
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 3 of the License, or
7 * (at your option) any later version.
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.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
28 #include <glib/gi18n.h>
29 #include <gdk/gdkkeysyms.h>
31 #include "select-keys.h"
34 #include "inputdialog.h"
35 #include "manage_window.h"
36 #include "alertpanel.h"
38 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
39 #define DIMof(type,member) DIM(((type *)0)->member)
52 struct select_keys_s {
58 unsigned int num_keys;
60 gpgme_ctx_t select_ctx;
61 gpgme_protocol_t proto;
62 GtkSortType sort_type;
63 enum col_titles sort_column;
64 SelectionResult result;
68 static void set_row (GtkCMCList *clist, gpgme_key_t key, gpgme_protocol_t proto);
69 static gpgme_key_t fill_clist (struct select_keys_s *sk, const char *pattern,
70 gpgme_protocol_t proto);
71 static void create_dialog (struct select_keys_s *sk);
72 static void open_dialog (struct select_keys_s *sk);
73 static void close_dialog (struct select_keys_s *sk);
74 static gint delete_event_cb (GtkWidget *widget,
75 GdkEventAny *event, gpointer data);
76 static gboolean key_pressed_cb (GtkWidget *widget,
77 GdkEventKey *event, gpointer data);
78 static void select_btn_cb (GtkWidget *widget, gpointer data);
79 static void cancel_btn_cb (GtkWidget *widget, gpointer data);
80 static void dont_encrypt_btn_cb (GtkWidget *widget, gpointer data);
81 static void other_btn_cb (GtkWidget *widget, gpointer data);
82 static void sort_keys (struct select_keys_s *sk, enum col_titles column);
83 static void sort_keys_name (GtkWidget *widget, gpointer data);
84 static void sort_keys_email (GtkWidget *widget, gpointer data);
86 static gboolean use_untrusted (gpgme_key_t, gpgme_user_id_t uid, gpgme_protocol_t proto);
89 update_progress (struct select_keys_s *sk, int running, const char *pattern)
91 static int windmill[] = { '-', '\\', '|', '/' };
95 buf = g_strdup_printf (_("No exact match for '%s'; please select the key."),
98 buf = g_strdup_printf (_("Collecting info for '%s' ... %c"),
100 windmill[running%DIM(windmill)]);
101 gtk_label_set_text (sk->toplabel, buf);
107 * gpgmegtk_recipient_selection:
108 * @recp_names: A list of email addresses
110 * Select a list of recipients from a given list of email addresses.
111 * This may pop up a window to present the user a choice, it will also
112 * check that the recipients key are all valid.
114 * Return value: NULL on error or a list of list of recipients.
117 gpgmegtk_recipient_selection (GSList *recp_names, SelectionResult *result,
118 gpgme_protocol_t proto)
120 struct select_keys_s sk;
121 gpgme_key_t key = NULL;
122 memset (&sk, 0, sizeof sk);
127 sk.pattern = recp_names? recp_names->data:NULL;
129 gtk_cmclist_clear (sk.clist);
130 key = fill_clist (&sk, sk.pattern, proto);
131 update_progress (&sk, 0, sk.pattern);
133 gtk_widget_show_all (sk.window);
136 gtk_widget_hide (sk.window);
137 sk.kset = g_realloc(sk.kset,
138 sizeof(gpgme_key_t) * (sk.num_keys + 1));
140 sk.kset[sk.num_keys] = key;
143 sk.result = KEY_SELECTION_OK;
144 gpgme_release (sk.select_ctx);
145 sk.select_ctx = NULL;
146 debug_print("used %s\n", key->uids->email);
150 recp_names = recp_names->next;
151 } while (sk.okay && recp_names);
159 sk.kset = g_realloc(sk.kset, sizeof(gpgme_key_t) * (sk.num_keys + 1));
160 sk.kset[sk.num_keys] = NULL;
168 destroy_key (gpointer data)
170 gpgme_key_t key = data;
171 gpgme_key_release (key);
175 set_row (GtkCMCList *clist, gpgme_key_t key, gpgme_protocol_t proto)
178 const char *text[N_COL_TITLES];
181 gsize by_read = 0, by_written = 0;
182 gchar *ret_str = NULL;
184 /* first check whether the key is capable of encryption which is not
185 * the case for revoked, expired or sign-only keys */
186 if (!key->can_encrypt || key->revoked || key->expired || key->disabled)
189 algo_buf = g_strdup_printf ("%du/%s",
190 key->subkeys->length,
191 gpgme_pubkey_algo_name(key->subkeys->pubkey_algo) );
192 text[COL_ALGO] = algo_buf;
194 text[COL_KEYID] = key->subkeys->keyid;
199 if (proto == GPGME_PROTOCOL_CMS) {
200 if (strstr(s, ",CN="))
201 s = strstr(s, ",CN=")+4;
202 else if (strstr(s, "CN="))
203 s = strstr(s, "CN=")+3;
207 if (!g_utf8_validate(s, -1, NULL))
208 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
209 if (ret_str && by_written) {
214 if (proto == GPGME_PROTOCOL_CMS && (!key->uids->email || !*key->uids->email)) {
215 gpgme_user_id_t uid = key->uids->next;
219 s = key->uids->email;
221 s = key->uids->email;
225 if (!g_utf8_validate(s, -1, NULL))
226 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
227 if (ret_str && by_written) {
232 switch (key->uids->validity)
234 case GPGME_VALIDITY_UNDEFINED:
237 case GPGME_VALIDITY_NEVER:
240 case GPGME_VALIDITY_MARGINAL:
243 case GPGME_VALIDITY_FULL:
246 case GPGME_VALIDITY_ULTIMATE:
249 case GPGME_VALIDITY_UNKNOWN:
254 text[COL_VALIDITY] = s;
256 row = gtk_cmclist_append (clist, (gchar**)text);
259 gtk_cmclist_set_row_data_full (clist, row, key, destroy_key);
263 fill_clist (struct select_keys_s *sk, const char *pattern, gpgme_protocol_t proto)
271 gboolean exact_match = FALSE;
272 gpgme_key_t last_key = NULL;
273 gpgme_user_id_t last_uid = NULL;
274 cm_return_val_if_fail (sk, NULL);
276 cm_return_val_if_fail (clist, NULL);
278 debug_print ("select_keys:fill_clist: pattern '%s' proto %d\n", pattern, proto);
280 /*gtk_cmclist_freeze (select_keys.clist);*/
281 err = gpgme_new (&ctx);
284 gpgme_set_protocol(ctx, proto);
285 sk->select_ctx = ctx;
287 update_progress (sk, ++running, pattern);
288 while (gtk_events_pending ())
289 gtk_main_iteration ();
291 err = gpgme_op_keylist_start (ctx, pattern, 0);
293 debug_print ("** gpgme_op_keylist_start(%s) failed: %s",
294 pattern, gpgme_strerror (err));
295 sk->select_ctx = NULL;
299 update_progress (sk, ++running, pattern);
300 while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
301 gpgme_user_id_t uid = key->uids;
302 if (!key->can_encrypt || key->revoked || key->expired || key->disabled)
304 debug_print ("%% %s:%d: insert\n", __FILE__ ,__LINE__ );
305 set_row (clist, key, proto );
306 for (; uid; uid = uid->next) {
307 gchar *raw_mail = NULL;
311 raw_mail = g_strdup(uid->email);
312 extract_address(raw_mail);
313 if (!strcasecmp(pattern, raw_mail)) {
324 update_progress (sk, ++running, pattern);
325 while (gtk_events_pending ())
326 gtk_main_iteration ();
329 if (exact_match == TRUE && num_results == 1) {
330 if (last_key->uids->validity < GPGME_VALIDITY_FULL &&
331 !use_untrusted(last_key, last_uid, proto))
335 debug_print ("%% %s:%d: ready\n", __FILE__ ,__LINE__ );
336 if (gpgme_err_code(err) != GPG_ERR_EOF) {
337 debug_print ("** gpgme_op_keylist_next failed: %s",
338 gpgme_strerror (err));
339 gpgme_op_keylist_end(ctx);
341 if (!exact_match || num_results != 1) {
342 sk->select_ctx = NULL;
345 /*gtk_cmclist_thaw (select_keys.clist);*/
346 return (exact_match == TRUE && num_results == 1 ? last_key:NULL);
351 create_dialog (struct select_keys_s *sk)
354 GtkWidget *vbox, *vbox2, *hbox;
356 GtkWidget *scrolledwin;
359 GtkWidget *select_btn, *cancel_btn, *dont_encrypt_btn, *other_btn;
360 const char *titles[N_COL_TITLES];
362 g_assert (!sk->window);
363 window = gtkut_window_new (GTK_WINDOW_TOPLEVEL, "select-keys");
364 gtk_widget_set_size_request (window, 560, 280);
365 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
366 gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
367 gtk_window_set_modal (GTK_WINDOW (window), TRUE);
368 g_signal_connect (G_OBJECT (window), "delete_event",
369 G_CALLBACK (delete_event_cb), sk);
370 g_signal_connect (G_OBJECT (window), "key_press_event",
371 G_CALLBACK (key_pressed_cb), sk);
372 MANAGE_WINDOW_SIGNALS_CONNECT (window);
374 vbox = gtk_vbox_new (FALSE, 8);
375 gtk_container_add (GTK_CONTAINER (window), vbox);
377 hbox = gtk_hbox_new(FALSE, 4);
378 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
379 label = gtk_label_new ( "" );
380 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
382 hbox = gtk_hbox_new (FALSE, 8);
383 gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
384 gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
386 scrolledwin = gtk_scrolled_window_new (NULL, NULL);
387 gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
388 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
389 GTK_POLICY_AUTOMATIC,
390 GTK_POLICY_AUTOMATIC);
392 titles[COL_ALGO] = _("Size");
393 titles[COL_KEYID] = _("Key ID");
394 titles[COL_NAME] = _("Name");
395 titles[COL_EMAIL] = _("Address");
396 titles[COL_VALIDITY] = _("Trust");
398 clist = gtk_cmclist_new_with_titles (N_COL_TITLES, (char**)titles);
399 gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
400 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_ALGO, 70);
401 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_KEYID, 120);
402 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_NAME, 115);
403 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_EMAIL, 140);
404 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_VALIDITY, 20);
405 gtk_cmclist_set_selection_mode (GTK_CMCLIST(clist), GTK_SELECTION_BROWSE);
406 g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_NAME].button),
408 G_CALLBACK(sort_keys_name), sk);
409 g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_EMAIL].button),
411 G_CALLBACK(sort_keys_email), sk);
413 hbox = gtk_hbox_new (FALSE, 8);
414 gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
416 /* TRANSLATORS: check that the accelerators in _Select, _Other and
417 * Do_n't encrypt are different than the one in the stock Cancel
419 gtkut_stock_button_set_create (&bbox,
420 &select_btn, _("_Select"),
421 &other_btn, _("_Other"),
422 &dont_encrypt_btn, _("Do_n't encrypt"));
424 cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
425 gtkut_widget_set_can_default(cancel_btn, TRUE);
426 gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
427 gtk_widget_show(cancel_btn);
428 gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
429 gtk_widget_grab_default (select_btn);
431 g_signal_connect (G_OBJECT (select_btn), "clicked",
432 G_CALLBACK (select_btn_cb), sk);
433 g_signal_connect (G_OBJECT(cancel_btn), "clicked",
434 G_CALLBACK (cancel_btn_cb), sk);
435 g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
436 G_CALLBACK (dont_encrypt_btn_cb), sk);
437 g_signal_connect (G_OBJECT (other_btn), "clicked",
438 G_CALLBACK (other_btn_cb), sk);
440 vbox2 = gtk_vbox_new (FALSE, 4);
441 gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
444 sk->toplabel = GTK_LABEL (label);
445 sk->clist = GTK_CMCLIST (clist);
450 open_dialog (struct select_keys_s *sk)
454 manage_window_set_transient (GTK_WINDOW (sk->window));
456 sk->sort_column = N_COL_TITLES; /* use an invalid value */
457 sk->sort_type = GTK_SORT_ASCENDING;
462 close_dialog (struct select_keys_s *sk)
464 cm_return_if_fail (sk);
465 gtk_widget_destroy (sk->window);
471 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
473 struct select_keys_s *sk = data;
483 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
485 struct select_keys_s *sk = data;
487 cm_return_val_if_fail (sk, FALSE);
488 if (event && event->keyval == GDK_KEY_Escape) {
497 select_btn_cb (GtkWidget *widget, gpointer data)
499 struct select_keys_s *sk = data;
504 cm_return_if_fail (sk);
505 if (!sk->clist->selection) {
506 debug_print ("** nothing selected");
509 row = GPOINTER_TO_INT(sk->clist->selection->data);
510 key = gtk_cmclist_get_row_data(sk->clist, row);
513 for (uid = key->uids; uid; uid = uid->next) {
514 gchar *raw_mail = NULL;
518 raw_mail = g_strdup(uid->email);
519 extract_address(raw_mail);
520 if (sk->pattern && !strcasecmp(sk->pattern, raw_mail)) {
529 if ( uid->validity < GPGME_VALIDITY_FULL ) {
530 use_key = use_untrusted(key, uid, sk->proto);
532 debug_print ("** Key untrusted, will not encrypt");
536 sk->kset = g_realloc(sk->kset,
537 sizeof(gpgme_key_t) * (sk->num_keys + 1));
539 sk->kset[sk->num_keys] = key;
542 sk->result = KEY_SELECTION_OK;
549 cancel_btn_cb (GtkWidget *widget, gpointer data)
551 struct select_keys_s *sk = data;
553 cm_return_if_fail (sk);
555 sk->result = KEY_SELECTION_CANCEL;
557 gpgme_cancel (sk->select_ctx);
562 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
564 struct select_keys_s *sk = data;
566 cm_return_if_fail (sk);
568 sk->result = KEY_SELECTION_DONT;
570 gpgme_cancel (sk->select_ctx);
575 other_btn_cb (GtkWidget *widget, gpointer data)
577 struct select_keys_s *sk = data;
580 cm_return_if_fail (sk);
581 uid = input_dialog ( _("Add key"),
582 _("Enter another user or key ID:"),
586 if (fill_clist (sk, uid, sk->proto) != NULL) {
587 gpgme_release(sk->select_ctx);
588 sk->select_ctx = NULL;
590 update_progress (sk, 0, sk->pattern);
596 use_untrusted (gpgme_key_t key, gpgme_user_id_t uid, gpgme_protocol_t proto)
601 if (proto != GPGME_PROTOCOL_OpenPGP)
604 title = g_strdup_printf(_("Encrypt to %s <%s>"), uid->name, uid->email);
605 buf = g_strdup_printf(_("This encryption key is not fully trusted.\n"
606 "If you choose to encrypt the message with this key, you don't\n"
607 "know for sure that it will go to the person you mean it to.\n\n"
608 "Key details: ID %s, primary identity %s <%s>\n\n"
609 "Do you trust this key enough to use it anyway?"),
610 key->subkeys->keyid, key->uids->name, key->uids->email);
613 GTK_STOCK_NO, GTK_STOCK_YES, NULL);
616 if (aval == G_ALERTALTERNATE)
624 cmp_name (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
626 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
627 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
630 sa = a? a->uids->name : NULL;
631 sb = b? b->uids->name : NULL;
636 return g_ascii_strcasecmp(sa, sb);
640 cmp_email (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
642 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
643 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
646 sa = a? a->uids->email : NULL;
647 sb = b? b->uids->email : NULL;
652 return g_ascii_strcasecmp(sa, sb);
656 sort_keys ( struct select_keys_s *sk, enum col_titles column)
658 GtkCMCList *clist = sk->clist;
662 gtk_cmclist_set_compare_func (clist, cmp_name);
665 gtk_cmclist_set_compare_func (clist, cmp_email);
671 /* column clicked again: toggle as-/decending */
672 if ( sk->sort_column == column) {
673 sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
674 GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
677 sk->sort_type = GTK_SORT_ASCENDING;
679 sk->sort_column = column;
680 gtk_cmclist_set_sort_type (clist, sk->sort_type);
681 gtk_cmclist_sort (clist);
685 sort_keys_name (GtkWidget *widget, gpointer data)
687 sort_keys ((struct select_keys_s*)data, COL_NAME);
691 sort_keys_email (GtkWidget *widget, gpointer data)
693 sort_keys ((struct select_keys_s*)data, COL_EMAIL);