1 /* select-keys.c - GTK+ based key selection
2 * Copyright (C) 2001-2016 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/>.
27 #include <glib/gi18n.h>
28 #include <gdk/gdkkeysyms.h>
30 #include "select-keys.h"
33 #include "inputdialog.h"
34 #include "manage_window.h"
35 #include "alertpanel.h"
37 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
38 #define DIMof(type,member) DIM(((type *)0)->member)
52 #define COL_ALGO_WIDTH 70
53 #define COL_KEYID_WIDTH 120
54 #define COL_NAME_WIDTH 115
55 #define COL_ADDRESS_WIDTH 140
56 #define COL_TRUST_WIDTH 20
58 struct select_keys_s {
64 unsigned int num_keys;
66 gpgme_ctx_t select_ctx;
67 gpgme_protocol_t proto;
68 GtkSortType sort_type;
69 enum col_titles sort_column;
70 SelectionResult result;
74 static void set_row (GtkListStore *store, gpgme_key_t key, gpgme_protocol_t proto);
75 static gpgme_key_t fill_view (struct select_keys_s *sk, const char *pattern,
76 gpgme_protocol_t proto);
77 static void create_dialog (struct select_keys_s *sk);
78 static void open_dialog (struct select_keys_s *sk);
79 static void close_dialog (struct select_keys_s *sk);
80 static gint delete_event_cb (GtkWidget *widget,
81 GdkEventAny *event, gpointer data);
82 static gboolean key_pressed_cb (GtkWidget *widget,
83 GdkEventKey *event, gpointer data);
84 static void select_btn_cb (GtkWidget *widget, gpointer data);
85 static void cancel_btn_cb (GtkWidget *widget, gpointer data);
86 static void dont_encrypt_btn_cb (GtkWidget *widget, gpointer data);
87 static void other_btn_cb (GtkWidget *widget, gpointer data);
88 static void sort_keys (struct select_keys_s *sk, enum col_titles column);
89 static void sort_keys_name (GtkWidget *widget, gpointer data);
90 static void sort_keys_email (GtkWidget *widget, gpointer data);
92 static gboolean use_untrusted (gpgme_key_t, gpgme_user_id_t uid, gpgme_protocol_t proto);
95 update_progress (struct select_keys_s *sk, int running, const char *pattern)
97 static int windmill[] = { '-', '\\', '|', '/' };
101 buf = g_strdup_printf (_("No exact match for '%s'; please select the key."),
104 buf = g_strdup_printf (_("Collecting info for '%s' ... %c"),
106 windmill[running%DIM(windmill)]);
107 gtk_label_set_text (sk->toplabel, buf);
113 * gpgmegtk_recipient_selection:
114 * @recp_names: A list of email addresses
116 * Select a list of recipients from a given list of email addresses.
117 * This may pop up a window to present the user a choice, it will also
118 * check that the recipients key are all valid.
120 * Return value: NULL on error or a list of list of recipients.
123 gpgmegtk_recipient_selection (GSList *recp_names, SelectionResult *result,
124 gpgme_protocol_t proto)
126 struct select_keys_s sk;
127 gpgme_key_t key = NULL;
128 memset (&sk, 0, sizeof sk);
133 sk.pattern = recp_names? recp_names->data:NULL;
135 if (sk.view != NULL) {
136 GtkTreeModel *model =
137 gtk_tree_view_get_model(GTK_TREE_VIEW(sk.view));
138 gtk_list_store_clear(GTK_LIST_STORE(model));
140 key = fill_view (&sk, sk.pattern, proto);
141 update_progress (&sk, 0, sk.pattern ? sk.pattern : "NULL");
143 gtk_widget_show_all (sk.window);
146 gtk_widget_hide (sk.window);
147 sk.kset = g_realloc(sk.kset,
148 sizeof(gpgme_key_t) * (sk.num_keys + 1));
150 sk.kset[sk.num_keys] = key;
153 sk.result = KEY_SELECTION_OK;
154 gpgme_release (sk.select_ctx);
155 sk.select_ctx = NULL;
156 debug_print("used %s\n", key->uids->email);
160 recp_names = recp_names->next;
161 } while (sk.okay && recp_names);
169 sk.kset = g_realloc(sk.kset, sizeof(gpgme_key_t) * (sk.num_keys + 1));
170 sk.kset[sk.num_keys] = NULL;
178 destroy_key (gpointer data)
180 gpgme_key_t key = data;
182 debug_print("unref key %p\n", key);
184 gpgme_key_unref (key);
188 set_row (GtkListStore *store, gpgme_key_t key, gpgme_protocol_t proto)
191 gchar *algo_buf, *name, *address;
193 gsize by_read = 0, by_written = 0;
194 gchar *ret_str = NULL;
196 /* first check whether the key is capable of encryption which is not
197 * the case for revoked, expired or sign-only keys */
198 if (!key->can_encrypt || key->revoked || key->expired || key->disabled)
201 algo_buf = g_strdup_printf ("%du/%s",
202 key->subkeys->length,
203 gpgme_pubkey_algo_name(key->subkeys->pubkey_algo) );
208 if (proto == GPGME_PROTOCOL_CMS) {
209 if (strstr(s, ",CN="))
210 s = strstr(s, ",CN=")+4;
211 else if (strstr(s, "CN="))
212 s = strstr(s, "CN=")+3;
216 if (!g_utf8_validate(s, -1, NULL))
217 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
218 if (ret_str && by_written) {
223 if (proto == GPGME_PROTOCOL_CMS && (!key->uids->email || !*key->uids->email)) {
224 gpgme_user_id_t uid = key->uids->next;
228 s = key->uids->email;
230 s = key->uids->email;
234 if (!g_utf8_validate(s, -1, NULL))
235 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
236 if (ret_str && by_written) {
239 address = g_strdup(s);
241 switch (key->uids->validity)
243 case GPGME_VALIDITY_UNDEFINED:
246 case GPGME_VALIDITY_NEVER:
249 case GPGME_VALIDITY_MARGINAL:
252 case GPGME_VALIDITY_FULL:
255 case GPGME_VALIDITY_ULTIMATE:
258 case GPGME_VALIDITY_UNKNOWN:
264 gtk_list_store_append(store, &iter);
265 gtk_list_store_set(store, &iter,
267 COL_KEYID, key->uids->name,
269 COL_ADDRESS, address,
281 fill_view (struct select_keys_s *sk, const char *pattern, gpgme_protocol_t proto)
285 GtkTreeSelection *sel;
292 gboolean exact_match = FALSE;
293 gpgme_key_t last_key = NULL;
294 gpgme_user_id_t last_uid = NULL;
296 cm_return_val_if_fail (sk, NULL);
299 cm_return_val_if_fail (view, NULL);
300 model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
302 debug_print ("select_keys:fill_view: pattern '%s' proto %d\n", pattern != NULL ? pattern : "NULL", proto);
304 err = gpgme_new (&ctx);
307 gpgme_set_protocol(ctx, proto);
308 sk->select_ctx = ctx;
310 update_progress (sk, ++running, pattern);
311 while (gtk_events_pending ())
312 gtk_main_iteration ();
314 err = gpgme_op_keylist_start (ctx, pattern, 0);
316 debug_print ("** gpgme_op_keylist_start(%s) failed: %s\n",
317 pattern != NULL ? pattern : "NULL", gpgme_strerror (err));
318 sk->select_ctx = NULL;
322 update_progress (sk, ++running, pattern);
323 while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
324 gpgme_user_id_t uid = key->uids;
325 if (!key->can_encrypt || key->revoked || key->expired || key->disabled) {
326 gpgme_key_unref(key);
329 debug_print ("%% %s:%d: insert\n", __FILE__ ,__LINE__ );
330 set_row (GTK_LIST_STORE(model), key, proto );
331 for (; uid; uid = uid->next) {
332 gchar *raw_mail = NULL;
336 if (uid->revoked || uid->invalid)
338 raw_mail = g_strdup(uid->email);
339 extract_address(raw_mail);
340 if (pattern != NULL && !strcasecmp(pattern, raw_mail)) {
349 /* Select the first row */
350 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
351 if (gtk_tree_model_get_iter_first(model, &iter))
352 gtk_tree_selection_select_iter(sel, &iter);
355 if (last_key != NULL)
356 gpgme_key_unref(last_key);
359 update_progress (sk, ++running, pattern);
360 while (gtk_events_pending ())
361 gtk_main_iteration ();
364 if (exact_match == TRUE && num_results == 1) {
365 if (last_key->uids->validity < GPGME_VALIDITY_FULL &&
366 !use_untrusted(last_key, last_uid, proto))
370 debug_print ("%% %s:%d: ready\n", __FILE__ ,__LINE__ );
371 if (gpgme_err_code(err) != GPG_ERR_EOF) {
372 debug_print ("** gpgme_op_keylist_next failed: %s\n",
373 gpgme_strerror (err));
374 gpgme_op_keylist_end(ctx);
376 if (!exact_match || num_results != 1) {
377 sk->select_ctx = NULL;
381 if (exact_match && num_results == 1)
384 if (last_key != NULL)
385 gpgme_key_unref(last_key);
392 view_row_activated_cb(GtkTreeView *view,
394 GtkTreeViewColumn *column,
397 select_btn_cb(NULL, user_data);
402 create_dialog (struct select_keys_s *sk)
405 GtkWidget *vbox, *vbox2, *hbox;
407 GtkWidget *scrolledwin;
410 GtkWidget *select_btn, *cancel_btn, *dont_encrypt_btn, *other_btn;
412 GtkCellRenderer *rdr;
413 GtkTreeViewColumn *col;
414 GtkTreeSelection *sel;
417 g_assert (!sk->window);
418 window = gtkut_window_new (GTK_WINDOW_TOPLEVEL, "select-keys");
419 gtk_widget_set_size_request (window, 560, 280);
420 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
421 gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
422 gtk_window_set_modal (GTK_WINDOW (window), TRUE);
423 g_signal_connect (G_OBJECT (window), "delete_event",
424 G_CALLBACK (delete_event_cb), sk);
425 g_signal_connect (G_OBJECT (window), "key_press_event",
426 G_CALLBACK (key_pressed_cb), sk);
427 MANAGE_WINDOW_SIGNALS_CONNECT (window);
429 vbox = gtk_vbox_new (FALSE, 8);
430 gtk_container_add (GTK_CONTAINER (window), vbox);
432 hbox = gtk_hbox_new(FALSE, 4);
433 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
434 label = gtk_label_new ( "" );
435 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
437 hbox = gtk_hbox_new (FALSE, 8);
438 gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
439 gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
441 scrolledwin = gtk_scrolled_window_new (NULL, NULL);
442 gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
443 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
444 GTK_POLICY_AUTOMATIC,
445 GTK_POLICY_AUTOMATIC);
447 store = gtk_list_store_new(N_COL_TITLES,
456 view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
457 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), TRUE);
458 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(view), FALSE);
459 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
460 gtk_tree_selection_set_mode(sel, GTK_SELECTION_BROWSE);
462 rdr = gtk_cell_renderer_text_new();
463 col = gtk_tree_view_column_new_with_attributes(_("Size"), rdr,
464 "markup", COL_ALGO, NULL);
465 gtk_tree_view_column_set_min_width(col, COL_ALGO_WIDTH);
466 gtk_tree_view_column_set_sort_column_id(col, i++);
467 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
469 col = gtk_tree_view_column_new_with_attributes(_("Key ID"), rdr,
470 "markup", COL_KEYID, NULL);
471 gtk_tree_view_column_set_min_width(col, COL_KEYID_WIDTH);
472 gtk_tree_view_column_set_sort_column_id(col, i++);
473 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
475 col = gtk_tree_view_column_new_with_attributes(_("Name"), rdr,
476 "markup", COL_NAME, NULL);
477 gtk_tree_view_column_set_min_width(col, COL_NAME_WIDTH);
478 gtk_tree_view_column_set_sort_column_id(col, i++);
479 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
481 col = gtk_tree_view_column_new_with_attributes(_("Address"), rdr,
482 "markup", COL_ADDRESS, NULL);
483 gtk_tree_view_column_set_min_width(col, COL_ADDRESS_WIDTH);
484 gtk_tree_view_column_set_sort_column_id(col, i++);
485 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
487 col = gtk_tree_view_column_new_with_attributes(_("Trust"), rdr,
488 "markup", COL_TRUST, NULL);
489 gtk_tree_view_column_set_min_width(col, COL_TRUST_WIDTH);
490 gtk_tree_view_column_set_sort_column_id(col, i++);
491 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
493 g_signal_connect(G_OBJECT(view), "row-activated",
494 G_CALLBACK(view_row_activated_cb), sk);
496 gtk_container_add (GTK_CONTAINER (scrolledwin), view);
498 hbox = gtk_hbox_new (FALSE, 8);
499 gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
501 /* TRANSLATORS: check that the accelerators in _Select, _Other and
502 * Do_n't encrypt are different than the one in the stock Cancel
504 gtkut_stock_button_set_create (&bbox,
505 &select_btn, _("_Select"),
506 &other_btn, _("_Other"),
507 &dont_encrypt_btn, _("Do_n't encrypt"));
509 cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
510 gtk_widget_set_can_default(cancel_btn, TRUE);
511 gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
512 gtk_widget_show(cancel_btn);
513 gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
514 gtk_widget_grab_default (select_btn);
516 g_signal_connect (G_OBJECT (select_btn), "clicked",
517 G_CALLBACK (select_btn_cb), sk);
518 g_signal_connect (G_OBJECT(cancel_btn), "clicked",
519 G_CALLBACK (cancel_btn_cb), sk);
520 g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
521 G_CALLBACK (dont_encrypt_btn_cb), sk);
522 g_signal_connect (G_OBJECT (other_btn), "clicked",
523 G_CALLBACK (other_btn_cb), sk);
525 vbox2 = gtk_vbox_new (FALSE, 4);
526 gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
529 sk->toplabel = GTK_LABEL (label);
534 /* Function called by gtk_tree_model_foreach() upon dialog close,
535 * which unrefs the gpgme_key_t pointer from each model line */
537 close_dialog_foreach_func(GtkTreeModel *model,
544 gtk_tree_model_get(model, iter, COL_PTR, &key, -1);
545 gpgme_key_unref(key);
551 open_dialog (struct select_keys_s *sk)
555 manage_window_set_transient (GTK_WINDOW (sk->window));
557 sk->sort_column = N_COL_TITLES; /* use an invalid value */
558 sk->sort_type = GTK_SORT_ASCENDING;
563 close_dialog (struct select_keys_s *sk)
566 cm_return_if_fail (sk);
568 debug_print("pgpcore select-keys dialog closing\n");
569 if (sk->view != NULL) {
570 model = gtk_tree_view_get_model(GTK_TREE_VIEW(sk->view));
571 gtk_tree_model_foreach(model, close_dialog_foreach_func, NULL);
572 gtk_list_store_clear(GTK_LIST_STORE(model));
575 gtk_widget_destroy (sk->window);
581 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
583 struct select_keys_s *sk = data;
593 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
595 struct select_keys_s *sk = data;
597 cm_return_val_if_fail (sk, FALSE);
598 if (event && event->keyval == GDK_KEY_Escape) {
607 select_btn_cb (GtkWidget *widget, gpointer data)
609 struct select_keys_s *sk = data;
613 GtkTreeSelection *sel;
616 cm_return_if_fail (sk);
617 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(sk->view));
618 if (!gtk_tree_selection_get_selected(sel, &model, &iter)) {
619 debug_print ("** nothing selected\n");
623 gtk_tree_model_get(model, &iter, COL_PTR, &key, -1);
626 for (uid = key->uids; uid; uid = uid->next) {
627 gchar *raw_mail = NULL;
631 raw_mail = g_strdup(uid->email);
632 extract_address(raw_mail);
633 if (sk->pattern && !strcasecmp(sk->pattern, raw_mail)) {
642 if ( uid->validity < GPGME_VALIDITY_FULL ) {
643 use_key = use_untrusted(key, uid, sk->proto);
645 debug_print ("** Key untrusted, will not encrypt\n");
649 sk->kset = g_realloc(sk->kset,
650 sizeof(gpgme_key_t) * (sk->num_keys + 1));
652 sk->kset[sk->num_keys] = key;
655 sk->result = KEY_SELECTION_OK;
662 cancel_btn_cb (GtkWidget *widget, gpointer data)
664 struct select_keys_s *sk = data;
666 cm_return_if_fail (sk);
668 sk->result = KEY_SELECTION_CANCEL;
670 gpgme_cancel (sk->select_ctx);
675 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
677 struct select_keys_s *sk = data;
679 cm_return_if_fail (sk);
681 sk->result = KEY_SELECTION_DONT;
683 gpgme_cancel (sk->select_ctx);
688 other_btn_cb (GtkWidget *widget, gpointer data)
690 struct select_keys_s *sk = data;
693 cm_return_if_fail (sk);
694 uid = input_dialog ( _("Add key"),
695 _("Enter another user or key ID:"),
699 if (fill_view (sk, uid, sk->proto) != NULL) {
700 gpgme_release(sk->select_ctx);
701 sk->select_ctx = NULL;
703 update_progress (sk, 0, sk->pattern);
709 use_untrusted (gpgme_key_t key, gpgme_user_id_t uid, gpgme_protocol_t proto)
714 if (proto != GPGME_PROTOCOL_OpenPGP)
717 title = g_strdup_printf(_("Encrypt to %s <%s>"), uid->name, uid->email);
718 buf = g_strdup_printf(_("This encryption key is not fully trusted.\n"
719 "If you choose to encrypt the message with this key, you don't\n"
720 "know for sure that it will go to the person you mean it to.\n\n"
721 "Key details: ID %s, primary identity %s <%s>\n\n"
722 "Do you trust this key enough to use it anyway?"),
723 key->subkeys->keyid, key->uids->name, key->uids->email);
724 aval = alertpanel(title, buf,
725 GTK_STOCK_NO, GTK_STOCK_YES, NULL, ALERTFOCUS_FIRST);
728 if (aval == G_ALERTALTERNATE)