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);
89 static gboolean use_untrusted (gpgme_key_t, gpgme_user_id_t uid, gpgme_protocol_t proto);
92 update_progress (struct select_keys_s *sk, int running, const char *pattern)
94 static int windmill[] = { '-', '\\', '|', '/' };
98 buf = g_strdup_printf (_("No exact match for '%s'; please select the key."),
101 buf = g_strdup_printf (_("Collecting info for '%s' ... %c"),
103 windmill[running%DIM(windmill)]);
104 gtk_label_set_text (sk->toplabel, buf);
110 * gpgmegtk_recipient_selection:
111 * @recp_names: A list of email addresses
113 * Select a list of recipients from a given list of email addresses.
114 * This may pop up a window to present the user a choice, it will also
115 * check that the recipients key are all valid.
117 * Return value: NULL on error or a list of list of recipients.
120 gpgmegtk_recipient_selection (GSList *recp_names, SelectionResult *result,
121 gpgme_protocol_t proto)
123 struct select_keys_s sk;
124 gpgme_key_t key = NULL;
125 memset (&sk, 0, sizeof sk);
130 sk.pattern = recp_names? recp_names->data:NULL;
132 if (sk.view != NULL) {
133 GtkTreeModel *model =
134 gtk_tree_view_get_model(GTK_TREE_VIEW(sk.view));
135 gtk_list_store_clear(GTK_LIST_STORE(model));
137 key = fill_view (&sk, sk.pattern, proto);
138 update_progress (&sk, 0, sk.pattern ? sk.pattern : "NULL");
140 gtk_widget_show_all (sk.window);
143 gtk_widget_hide (sk.window);
144 sk.kset = g_realloc(sk.kset,
145 sizeof(gpgme_key_t) * (sk.num_keys + 1));
147 sk.kset[sk.num_keys] = key;
150 sk.result = KEY_SELECTION_OK;
151 gpgme_release (sk.select_ctx);
152 sk.select_ctx = NULL;
153 debug_print("used %s\n", key->uids->email);
157 recp_names = recp_names->next;
158 } while (sk.okay && recp_names);
166 sk.kset = g_realloc(sk.kset, sizeof(gpgme_key_t) * (sk.num_keys + 1));
167 sk.kset[sk.num_keys] = NULL;
175 set_row (GtkListStore *store, gpgme_key_t key, gpgme_protocol_t proto)
178 gchar *algo_buf, *name, *address;
180 gsize by_read = 0, by_written = 0;
181 gchar *ret_str = NULL;
183 /* first check whether the key is capable of encryption which is not
184 * the case for revoked, expired or sign-only keys */
185 if (!key->can_encrypt || key->revoked || key->expired || key->disabled)
188 algo_buf = g_strdup_printf ("%du/%s",
189 key->subkeys->length,
190 gpgme_pubkey_algo_name(key->subkeys->pubkey_algo) );
195 if (proto == GPGME_PROTOCOL_CMS) {
196 if (strstr(s, ",CN="))
197 s = strstr(s, ",CN=")+4;
198 else if (strstr(s, "CN="))
199 s = strstr(s, "CN=")+3;
203 if (!g_utf8_validate(s, -1, NULL))
204 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
205 if (ret_str && by_written) {
210 if (proto == GPGME_PROTOCOL_CMS && (!key->uids->email || !*key->uids->email)) {
211 gpgme_user_id_t uid = key->uids->next;
215 s = key->uids->email;
217 s = key->uids->email;
221 if (!g_utf8_validate(s, -1, NULL))
222 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
223 if (ret_str && by_written) {
226 address = g_strdup(s);
228 switch (key->uids->validity)
230 case GPGME_VALIDITY_UNDEFINED:
233 case GPGME_VALIDITY_NEVER:
236 case GPGME_VALIDITY_MARGINAL:
239 case GPGME_VALIDITY_FULL:
242 case GPGME_VALIDITY_ULTIMATE:
245 case GPGME_VALIDITY_UNKNOWN:
251 gtk_list_store_append(store, &iter);
252 gtk_list_store_set(store, &iter,
254 COL_KEYID, key->subkeys->keyid,
256 COL_ADDRESS, address,
268 fill_view (struct select_keys_s *sk, const char *pattern, gpgme_protocol_t proto)
272 GtkTreeSelection *sel;
279 gboolean exact_match = FALSE;
280 gpgme_key_t last_key = NULL;
281 gpgme_user_id_t last_uid = NULL;
283 cm_return_val_if_fail (sk, NULL);
286 cm_return_val_if_fail (view, NULL);
287 model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
289 debug_print ("select_keys:fill_view: pattern '%s' proto %d\n", pattern != NULL ? pattern : "NULL", proto);
291 err = gpgme_new (&ctx);
294 gpgme_set_protocol(ctx, proto);
295 sk->select_ctx = ctx;
297 update_progress (sk, ++running, pattern);
298 while (gtk_events_pending ())
299 gtk_main_iteration ();
301 err = gpgme_op_keylist_start (ctx, pattern, 0);
303 debug_print ("** gpgme_op_keylist_start(%s) failed: %s\n",
304 pattern != NULL ? pattern : "NULL", gpgme_strerror (err));
305 sk->select_ctx = NULL;
309 update_progress (sk, ++running, pattern);
310 while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
311 gpgme_user_id_t uid = key->uids;
312 if (!key->can_encrypt || key->revoked || key->expired || key->disabled) {
313 gpgme_key_unref(key);
316 debug_print ("%% %s:%d: insert\n", __FILE__ ,__LINE__ );
317 set_row (GTK_LIST_STORE(model), key, proto );
318 for (; uid; uid = uid->next) {
319 gchar *raw_mail = NULL;
323 if (uid->revoked || uid->invalid)
325 raw_mail = g_strdup(uid->email);
326 extract_address(raw_mail);
327 if (pattern != NULL && !strcasecmp(pattern, raw_mail)) {
336 /* Select the first row */
337 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
338 if (gtk_tree_model_get_iter_first(model, &iter))
339 gtk_tree_selection_select_iter(sel, &iter);
342 if (last_key != NULL)
343 gpgme_key_unref(last_key);
346 update_progress (sk, ++running, pattern);
347 while (gtk_events_pending ())
348 gtk_main_iteration ();
351 if (exact_match == TRUE && num_results == 1) {
352 if (last_key->uids->validity < GPGME_VALIDITY_FULL &&
353 !use_untrusted(last_key, last_uid, proto))
357 debug_print ("%% %s:%d: ready\n", __FILE__ ,__LINE__ );
358 if (gpgme_err_code(err) != GPG_ERR_EOF) {
359 debug_print ("** gpgme_op_keylist_next failed: %s\n",
360 gpgme_strerror (err));
361 gpgme_op_keylist_end(ctx);
363 if (!exact_match || num_results != 1) {
364 sk->select_ctx = NULL;
368 if (exact_match && num_results == 1)
371 if (last_key != NULL)
372 gpgme_key_unref(last_key);
379 view_row_activated_cb(GtkTreeView *view,
381 GtkTreeViewColumn *column,
384 select_btn_cb(NULL, user_data);
389 create_dialog (struct select_keys_s *sk)
392 GtkWidget *vbox, *vbox2, *hbox;
394 GtkWidget *scrolledwin;
397 GtkWidget *select_btn, *cancel_btn, *dont_encrypt_btn, *other_btn;
399 GtkCellRenderer *rdr;
400 GtkTreeViewColumn *col;
401 GtkTreeSelection *sel;
404 g_assert (!sk->window);
405 window = gtkut_window_new (GTK_WINDOW_TOPLEVEL, "select-keys");
406 gtk_widget_set_size_request (window, 560, 280);
407 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
408 gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
409 gtk_window_set_modal (GTK_WINDOW (window), TRUE);
410 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
411 g_signal_connect (G_OBJECT (window), "delete_event",
412 G_CALLBACK (delete_event_cb), sk);
413 g_signal_connect (G_OBJECT (window), "key_press_event",
414 G_CALLBACK (key_pressed_cb), sk);
415 MANAGE_WINDOW_SIGNALS_CONNECT (window);
417 vbox = gtk_vbox_new (FALSE, 8);
418 gtk_container_add (GTK_CONTAINER (window), vbox);
420 hbox = gtk_hbox_new(FALSE, 4);
421 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
422 label = gtk_label_new ( "" );
423 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
425 hbox = gtk_hbox_new (FALSE, 8);
426 gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
427 gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
429 scrolledwin = gtk_scrolled_window_new (NULL, NULL);
430 gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
431 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
432 GTK_POLICY_AUTOMATIC,
433 GTK_POLICY_AUTOMATIC);
435 store = gtk_list_store_new(N_COL_TITLES,
444 view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
445 g_object_unref(store);
446 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), TRUE);
447 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(view), FALSE);
448 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
449 gtk_tree_selection_set_mode(sel, GTK_SELECTION_BROWSE);
451 rdr = gtk_cell_renderer_text_new();
452 col = gtk_tree_view_column_new_with_attributes(_("Size"), rdr,
453 "markup", COL_ALGO, NULL);
454 gtk_tree_view_column_set_min_width(col, COL_ALGO_WIDTH);
455 gtk_tree_view_column_set_sort_column_id(col, i++);
456 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
458 col = gtk_tree_view_column_new_with_attributes(_("Key ID"), rdr,
459 "markup", COL_KEYID, NULL);
460 gtk_tree_view_column_set_min_width(col, COL_KEYID_WIDTH);
461 gtk_tree_view_column_set_sort_column_id(col, i++);
462 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
464 col = gtk_tree_view_column_new_with_attributes(_("Name"), rdr,
465 "markup", COL_NAME, NULL);
466 gtk_tree_view_column_set_min_width(col, COL_NAME_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(_("Address"), rdr,
471 "markup", COL_ADDRESS, NULL);
472 gtk_tree_view_column_set_min_width(col, COL_ADDRESS_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(_("Trust"), rdr,
477 "markup", COL_TRUST, NULL);
478 gtk_tree_view_column_set_min_width(col, COL_TRUST_WIDTH);
479 gtk_tree_view_column_set_sort_column_id(col, i++);
480 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
482 g_signal_connect(G_OBJECT(view), "row-activated",
483 G_CALLBACK(view_row_activated_cb), sk);
485 gtk_container_add (GTK_CONTAINER (scrolledwin), view);
487 hbox = gtk_hbox_new (FALSE, 8);
488 gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
490 /* TRANSLATORS: check that the accelerators in _Select, _Other and
491 * Do_n't encrypt are different than the one in the stock Cancel
493 gtkut_stock_button_set_create (&bbox,
494 &select_btn, _("_Select"),
495 &other_btn, _("_Other"),
496 &dont_encrypt_btn, _("Do_n't encrypt"));
498 cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
499 gtk_widget_set_can_default(cancel_btn, TRUE);
500 gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
501 gtk_widget_show(cancel_btn);
502 gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
503 gtk_widget_grab_default (select_btn);
505 g_signal_connect (G_OBJECT (select_btn), "clicked",
506 G_CALLBACK (select_btn_cb), sk);
507 g_signal_connect (G_OBJECT(cancel_btn), "clicked",
508 G_CALLBACK (cancel_btn_cb), sk);
509 g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
510 G_CALLBACK (dont_encrypt_btn_cb), sk);
511 g_signal_connect (G_OBJECT (other_btn), "clicked",
512 G_CALLBACK (other_btn_cb), sk);
514 vbox2 = gtk_vbox_new (FALSE, 4);
515 gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
518 sk->toplabel = GTK_LABEL (label);
523 /* Function called by gtk_tree_model_foreach() upon dialog close,
524 * which unrefs the gpgme_key_t pointer from each model line */
526 close_dialog_foreach_func(GtkTreeModel *model,
533 gtk_tree_model_get(model, iter, COL_PTR, &key, -1);
534 gpgme_key_unref(key);
540 open_dialog (struct select_keys_s *sk)
544 manage_window_set_transient (GTK_WINDOW (sk->window));
546 sk->sort_column = N_COL_TITLES; /* use an invalid value */
547 sk->sort_type = GTK_SORT_ASCENDING;
552 close_dialog (struct select_keys_s *sk)
555 cm_return_if_fail (sk);
557 debug_print("pgpcore select-keys dialog closing\n");
558 if (sk->view != NULL) {
559 model = gtk_tree_view_get_model(GTK_TREE_VIEW(sk->view));
560 gtk_tree_model_foreach(model, close_dialog_foreach_func, NULL);
561 gtk_list_store_clear(GTK_LIST_STORE(model));
564 gtk_widget_destroy (sk->window);
570 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
572 struct select_keys_s *sk = data;
582 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
584 struct select_keys_s *sk = data;
586 cm_return_val_if_fail (sk, FALSE);
587 if (event && event->keyval == GDK_KEY_Escape) {
596 select_btn_cb (GtkWidget *widget, gpointer data)
598 struct select_keys_s *sk = data;
602 cm_return_if_fail (sk);
604 key = gtkut_tree_view_get_selected_pointer(
605 GTK_TREE_VIEW(sk->view), COL_PTR,
609 for (uid = key->uids; uid; uid = uid->next) {
610 gchar *raw_mail = NULL;
614 raw_mail = g_strdup(uid->email);
615 extract_address(raw_mail);
616 if (sk->pattern && !strcasecmp(sk->pattern, raw_mail)) {
625 if ( uid->validity < GPGME_VALIDITY_FULL ) {
626 use_key = use_untrusted(key, uid, sk->proto);
628 debug_print ("** Key untrusted, will not encrypt\n");
632 sk->kset = g_realloc(sk->kset,
633 sizeof(gpgme_key_t) * (sk->num_keys + 1));
635 sk->kset[sk->num_keys] = key;
638 sk->result = KEY_SELECTION_OK;
645 cancel_btn_cb (GtkWidget *widget, gpointer data)
647 struct select_keys_s *sk = data;
649 cm_return_if_fail (sk);
651 sk->result = KEY_SELECTION_CANCEL;
653 gpgme_cancel (sk->select_ctx);
658 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
660 struct select_keys_s *sk = data;
662 cm_return_if_fail (sk);
664 sk->result = KEY_SELECTION_DONT;
666 gpgme_cancel (sk->select_ctx);
671 other_btn_cb (GtkWidget *widget, gpointer data)
673 struct select_keys_s *sk = data;
676 cm_return_if_fail (sk);
677 uid = input_dialog ( _("Add key"),
678 _("Enter another user or key ID:"),
682 if (fill_view (sk, uid, sk->proto) != NULL) {
683 gpgme_release(sk->select_ctx);
684 sk->select_ctx = NULL;
686 update_progress (sk, 0, sk->pattern);
692 use_untrusted (gpgme_key_t key, gpgme_user_id_t uid, gpgme_protocol_t proto)
697 if (proto != GPGME_PROTOCOL_OpenPGP)
700 title = g_strdup_printf(_("Encrypt to %s <%s>"), uid->name, uid->email);
701 buf = g_strdup_printf(_("This encryption key is not fully trusted.\n"
702 "If you choose to encrypt the message with this key, you don't\n"
703 "know for sure that it will go to the person you mean it to.\n\n"
704 "Key details: ID %s, primary identity %s <%s>\n\n"
705 "Do you trust this key enough to use it anyway?"),
706 key->subkeys->keyid, key->uids->name, key->uids->email);
707 aval = alertpanel(title, buf,
708 GTK_STOCK_NO, GTK_STOCK_YES, NULL, ALERTFOCUS_FIRST);
711 if (aval == G_ALERTALTERNATE)