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->subkeys->keyid,
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 g_object_unref(store);
458 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), TRUE);
459 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(view), FALSE);
460 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
461 gtk_tree_selection_set_mode(sel, GTK_SELECTION_BROWSE);
463 rdr = gtk_cell_renderer_text_new();
464 col = gtk_tree_view_column_new_with_attributes(_("Size"), rdr,
465 "markup", COL_ALGO, NULL);
466 gtk_tree_view_column_set_min_width(col, COL_ALGO_WIDTH);
467 gtk_tree_view_column_set_sort_column_id(col, i++);
468 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
470 col = gtk_tree_view_column_new_with_attributes(_("Key ID"), rdr,
471 "markup", COL_KEYID, NULL);
472 gtk_tree_view_column_set_min_width(col, COL_KEYID_WIDTH);
473 gtk_tree_view_column_set_sort_column_id(col, i++);
474 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
476 col = gtk_tree_view_column_new_with_attributes(_("Name"), rdr,
477 "markup", COL_NAME, NULL);
478 gtk_tree_view_column_set_min_width(col, COL_NAME_WIDTH);
479 gtk_tree_view_column_set_sort_column_id(col, i++);
480 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
482 col = gtk_tree_view_column_new_with_attributes(_("Address"), rdr,
483 "markup", COL_ADDRESS, NULL);
484 gtk_tree_view_column_set_min_width(col, COL_ADDRESS_WIDTH);
485 gtk_tree_view_column_set_sort_column_id(col, i++);
486 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
488 col = gtk_tree_view_column_new_with_attributes(_("Trust"), rdr,
489 "markup", COL_TRUST, NULL);
490 gtk_tree_view_column_set_min_width(col, COL_TRUST_WIDTH);
491 gtk_tree_view_column_set_sort_column_id(col, i++);
492 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
494 g_signal_connect(G_OBJECT(view), "row-activated",
495 G_CALLBACK(view_row_activated_cb), sk);
497 gtk_container_add (GTK_CONTAINER (scrolledwin), view);
499 hbox = gtk_hbox_new (FALSE, 8);
500 gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
502 /* TRANSLATORS: check that the accelerators in _Select, _Other and
503 * Do_n't encrypt are different than the one in the stock Cancel
505 gtkut_stock_button_set_create (&bbox,
506 &select_btn, _("_Select"),
507 &other_btn, _("_Other"),
508 &dont_encrypt_btn, _("Do_n't encrypt"));
510 cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
511 gtk_widget_set_can_default(cancel_btn, TRUE);
512 gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
513 gtk_widget_show(cancel_btn);
514 gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
515 gtk_widget_grab_default (select_btn);
517 g_signal_connect (G_OBJECT (select_btn), "clicked",
518 G_CALLBACK (select_btn_cb), sk);
519 g_signal_connect (G_OBJECT(cancel_btn), "clicked",
520 G_CALLBACK (cancel_btn_cb), sk);
521 g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
522 G_CALLBACK (dont_encrypt_btn_cb), sk);
523 g_signal_connect (G_OBJECT (other_btn), "clicked",
524 G_CALLBACK (other_btn_cb), sk);
526 vbox2 = gtk_vbox_new (FALSE, 4);
527 gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
530 sk->toplabel = GTK_LABEL (label);
535 /* Function called by gtk_tree_model_foreach() upon dialog close,
536 * which unrefs the gpgme_key_t pointer from each model line */
538 close_dialog_foreach_func(GtkTreeModel *model,
545 gtk_tree_model_get(model, iter, COL_PTR, &key, -1);
546 gpgme_key_unref(key);
552 open_dialog (struct select_keys_s *sk)
556 manage_window_set_transient (GTK_WINDOW (sk->window));
558 sk->sort_column = N_COL_TITLES; /* use an invalid value */
559 sk->sort_type = GTK_SORT_ASCENDING;
564 close_dialog (struct select_keys_s *sk)
567 cm_return_if_fail (sk);
569 debug_print("pgpcore select-keys dialog closing\n");
570 if (sk->view != NULL) {
571 model = gtk_tree_view_get_model(GTK_TREE_VIEW(sk->view));
572 gtk_tree_model_foreach(model, close_dialog_foreach_func, NULL);
573 gtk_list_store_clear(GTK_LIST_STORE(model));
576 gtk_widget_destroy (sk->window);
582 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
584 struct select_keys_s *sk = data;
594 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
596 struct select_keys_s *sk = data;
598 cm_return_val_if_fail (sk, FALSE);
599 if (event && event->keyval == GDK_KEY_Escape) {
608 select_btn_cb (GtkWidget *widget, gpointer data)
610 struct select_keys_s *sk = data;
614 cm_return_if_fail (sk);
616 key = gtkut_tree_view_get_selected_pointer(
617 GTK_TREE_VIEW(sk->view), COL_PTR,
621 for (uid = key->uids; uid; uid = uid->next) {
622 gchar *raw_mail = NULL;
626 raw_mail = g_strdup(uid->email);
627 extract_address(raw_mail);
628 if (sk->pattern && !strcasecmp(sk->pattern, raw_mail)) {
637 if ( uid->validity < GPGME_VALIDITY_FULL ) {
638 use_key = use_untrusted(key, uid, sk->proto);
640 debug_print ("** Key untrusted, will not encrypt\n");
644 sk->kset = g_realloc(sk->kset,
645 sizeof(gpgme_key_t) * (sk->num_keys + 1));
647 sk->kset[sk->num_keys] = key;
650 sk->result = KEY_SELECTION_OK;
657 cancel_btn_cb (GtkWidget *widget, gpointer data)
659 struct select_keys_s *sk = data;
661 cm_return_if_fail (sk);
663 sk->result = KEY_SELECTION_CANCEL;
665 gpgme_cancel (sk->select_ctx);
670 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
672 struct select_keys_s *sk = data;
674 cm_return_if_fail (sk);
676 sk->result = KEY_SELECTION_DONT;
678 gpgme_cancel (sk->select_ctx);
683 other_btn_cb (GtkWidget *widget, gpointer data)
685 struct select_keys_s *sk = data;
688 cm_return_if_fail (sk);
689 uid = input_dialog ( _("Add key"),
690 _("Enter another user or key ID:"),
694 if (fill_view (sk, uid, sk->proto) != NULL) {
695 gpgme_release(sk->select_ctx);
696 sk->select_ctx = NULL;
698 update_progress (sk, 0, sk->pattern);
704 use_untrusted (gpgme_key_t key, gpgme_user_id_t uid, gpgme_protocol_t proto)
709 if (proto != GPGME_PROTOCOL_OpenPGP)
712 title = g_strdup_printf(_("Encrypt to %s <%s>"), uid->name, uid->email);
713 buf = g_strdup_printf(_("This encryption key is not fully trusted.\n"
714 "If you choose to encrypt the message with this key, you don't\n"
715 "know for sure that it will go to the person you mean it to.\n\n"
716 "Key details: ID %s, primary identity %s <%s>\n\n"
717 "Do you trust this key enough to use it anyway?"),
718 key->subkeys->keyid, key->uids->name, key->uids->email);
719 aval = alertpanel(title, buf,
720 GTK_STOCK_NO, GTK_STOCK_YES, NULL, ALERTFOCUS_FIRST);
723 if (aval == G_ALERTALTERNATE)