1 /* select-keys.c - GTK based key selection
2 * Copyright (C) 2001-2022 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]))
50 #define COL_ALGO_WIDTH 70
51 #define COL_KEYID_WIDTH 120
52 #define COL_NAME_WIDTH 115
53 #define COL_ADDRESS_WIDTH 140
54 #define COL_TRUST_WIDTH 20
56 struct select_keys_s
{
62 unsigned int num_keys
;
64 gpgme_ctx_t select_ctx
;
65 gpgme_protocol_t proto
;
66 GtkSortType sort_type
;
67 enum col_titles sort_column
;
68 SelectionResult result
;
72 static void set_row (GtkListStore
*store
, gpgme_key_t key
, gpgme_protocol_t proto
);
73 static gpgme_key_t
fill_view (struct select_keys_s
*sk
, const char *pattern
,
74 gpgme_protocol_t proto
);
75 static void create_dialog (struct select_keys_s
*sk
);
76 static void open_dialog (struct select_keys_s
*sk
);
77 static void close_dialog (struct select_keys_s
*sk
);
78 static gint
delete_event_cb (GtkWidget
*widget
,
79 GdkEventAny
*event
, gpointer data
);
80 static gboolean
key_pressed_cb (GtkWidget
*widget
,
81 GdkEventKey
*event
, gpointer data
);
82 static void select_btn_cb (GtkWidget
*widget
, gpointer data
);
83 static void cancel_btn_cb (GtkWidget
*widget
, gpointer data
);
84 static void dont_encrypt_btn_cb (GtkWidget
*widget
, gpointer data
);
85 static void other_btn_cb (GtkWidget
*widget
, gpointer data
);
87 static gboolean
use_untrusted (gpgme_key_t
, gpgme_user_id_t uid
, gpgme_protocol_t proto
);
90 update_progress (struct select_keys_s
*sk
, int running
, const char *pattern
)
92 static int windmill
[] = { '-', '\\', '|', '/' };
96 buf
= g_strdup_printf (_("No exact match for '%s'; please select the key."),
99 buf
= g_strdup_printf (_("Collecting info for '%s' ... %c"),
101 windmill
[running
%DIM(windmill
)]);
102 gtk_label_set_text (sk
->toplabel
, buf
);
108 * gpgmegtk_recipient_selection:
109 * @recp_names: A list of email addresses
111 * Select a list of recipients from a given list of email addresses.
112 * This may pop up a window to present the user a choice, it will also
113 * check that the recipients key are all valid.
115 * Return value: NULL on error or a list of list of recipients.
118 gpgmegtk_recipient_selection (GSList
*recp_names
, SelectionResult
*result
,
119 gpgme_protocol_t proto
)
121 struct select_keys_s sk
;
122 gpgme_key_t key
= NULL
;
123 memset (&sk
, 0, sizeof sk
);
128 sk
.pattern
= recp_names
? recp_names
->data
:NULL
;
130 if (sk
.view
!= NULL
) {
131 GtkTreeModel
*model
=
132 gtk_tree_view_get_model(GTK_TREE_VIEW(sk
.view
));
133 gtk_list_store_clear(GTK_LIST_STORE(model
));
135 key
= fill_view (&sk
, sk
.pattern
, proto
);
136 update_progress (&sk
, 0, sk
.pattern
? sk
.pattern
: "NULL");
138 gtk_widget_show_all (sk
.window
);
141 gtk_widget_hide (sk
.window
);
142 sk
.kset
= g_realloc(sk
.kset
,
143 sizeof(gpgme_key_t
) * (sk
.num_keys
+ 1));
145 sk
.kset
[sk
.num_keys
] = key
;
148 sk
.result
= KEY_SELECTION_OK
;
149 gpgme_release (sk
.select_ctx
);
150 sk
.select_ctx
= NULL
;
151 debug_print("used %s\n", key
->uids
->email
);
155 recp_names
= recp_names
->next
;
156 } while (sk
.okay
&& recp_names
);
164 sk
.kset
= g_realloc(sk
.kset
, sizeof(gpgme_key_t
) * (sk
.num_keys
+ 1));
165 sk
.kset
[sk
.num_keys
] = NULL
;
173 set_row (GtkListStore
*store
, gpgme_key_t key
, gpgme_protocol_t proto
)
176 gchar
*algo_buf
, *name
, *address
;
178 gsize by_read
= 0, by_written
= 0;
179 gchar
*ret_str
= NULL
;
181 /* first check whether the key is capable of encryption which is not
182 * the case for revoked, expired or sign-only keys */
183 if (!key
->can_encrypt
|| key
->revoked
|| key
->expired
|| key
->disabled
)
186 algo_buf
= g_strdup_printf ("%du/%s",
187 key
->subkeys
->length
,
188 gpgme_pubkey_algo_name(key
->subkeys
->pubkey_algo
) );
193 if (proto
== GPGME_PROTOCOL_CMS
) {
194 if (strstr(s
, ",CN="))
195 s
= strstr(s
, ",CN=")+4;
196 else if (strstr(s
, "CN="))
197 s
= strstr(s
, "CN=")+3;
201 if (!g_utf8_validate(s
, -1, NULL
))
202 ret_str
= g_locale_to_utf8 (s
, strlen(s
), &by_read
, &by_written
, NULL
);
203 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
);
230 switch (key
->uids
->validity
)
232 case GPGME_VALIDITY_UNDEFINED
:
235 case GPGME_VALIDITY_NEVER
:
238 case GPGME_VALIDITY_MARGINAL
:
241 case GPGME_VALIDITY_FULL
:
244 case GPGME_VALIDITY_ULTIMATE
:
247 case GPGME_VALIDITY_UNKNOWN
:
253 gtk_list_store_append(store
, &iter
);
254 gtk_list_store_set(store
, &iter
,
256 COL_KEYID
, key
->subkeys
->keyid
,
258 COL_ADDRESS
, address
,
270 fill_view (struct select_keys_s
*sk
, const char *pattern
, gpgme_protocol_t proto
)
274 GtkTreeSelection
*sel
;
281 gboolean exact_match
= FALSE
;
282 gpgme_key_t last_key
= NULL
;
283 gpgme_user_id_t last_uid
= NULL
;
285 cm_return_val_if_fail (sk
, NULL
);
288 cm_return_val_if_fail (view
, NULL
);
289 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(view
));
291 debug_print ("select_keys:fill_view: pattern '%s' proto %d\n", pattern
!= NULL
? pattern
: "NULL", proto
);
293 err
= gpgme_new (&ctx
);
296 gpgme_set_protocol(ctx
, proto
);
297 sk
->select_ctx
= ctx
;
299 update_progress (sk
, ++running
, pattern
);
300 while (gtk_events_pending ())
301 gtk_main_iteration ();
303 err
= gpgme_op_keylist_start (ctx
, pattern
, 0);
305 debug_print ("** gpgme_op_keylist_start(%s) failed: %s\n",
306 pattern
!= NULL
? pattern
: "NULL", gpgme_strerror (err
));
307 sk
->select_ctx
= NULL
;
311 update_progress (sk
, ++running
, pattern
);
312 while ( !(err
= gpgme_op_keylist_next ( ctx
, &key
)) ) {
313 gpgme_user_id_t uid
= key
->uids
;
314 if (!key
->can_encrypt
|| key
->revoked
|| key
->expired
|| key
->disabled
) {
315 gpgme_key_unref(key
);
318 debug_print ("%% %s:%d: insert\n", __FILE__
,__LINE__
);
319 set_row (GTK_LIST_STORE(model
), key
, proto
);
320 for (; uid
; uid
= uid
->next
) {
321 gchar
*raw_mail
= NULL
;
325 if (uid
->revoked
|| uid
->invalid
)
327 raw_mail
= g_strdup(uid
->email
);
328 extract_address(raw_mail
);
329 if (pattern
!= NULL
&& !strcasecmp(pattern
, raw_mail
)) {
338 /* Select the first row */
339 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(view
));
340 if (gtk_tree_model_get_iter_first(model
, &iter
))
341 gtk_tree_selection_select_iter(sel
, &iter
);
344 if (last_key
!= NULL
)
345 gpgme_key_unref(last_key
);
348 update_progress (sk
, ++running
, pattern
);
349 while (gtk_events_pending ())
350 gtk_main_iteration ();
353 if (exact_match
== TRUE
&& num_results
== 1) {
354 if (last_key
->uids
->validity
< GPGME_VALIDITY_FULL
&&
355 !use_untrusted(last_key
, last_uid
, proto
))
359 debug_print ("%% %s:%d: ready\n", __FILE__
,__LINE__
);
360 if (gpgme_err_code(err
) != GPG_ERR_EOF
) {
361 debug_print ("** gpgme_op_keylist_next failed: %s\n",
362 gpgme_strerror (err
));
363 gpgme_op_keylist_end(ctx
);
365 if (!exact_match
|| num_results
!= 1) {
366 sk
->select_ctx
= NULL
;
370 if (exact_match
&& num_results
== 1)
373 if (last_key
!= NULL
)
374 gpgme_key_unref(last_key
);
381 view_row_activated_cb(GtkTreeView
*view
,
383 GtkTreeViewColumn
*column
,
386 select_btn_cb(NULL
, user_data
);
391 create_dialog (struct select_keys_s
*sk
)
394 GtkWidget
*vbox
, *vbox2
, *hbox
;
396 GtkWidget
*scrolledwin
;
399 GtkWidget
*select_btn
, *cancel_btn
, *dont_encrypt_btn
, *other_btn
;
401 GtkCellRenderer
*rdr
;
402 GtkTreeViewColumn
*col
;
403 GtkTreeSelection
*sel
;
406 g_assert (!sk
->window
);
407 window
= gtkut_window_new (GTK_WINDOW_TOPLEVEL
, "select-keys");
408 gtk_widget_set_size_request (window
, 560, 280);
409 gtk_container_set_border_width (GTK_CONTAINER (window
), 8);
410 gtk_window_set_title (GTK_WINDOW (window
), _("Select Keys"));
411 gtk_window_set_modal (GTK_WINDOW (window
), TRUE
);
412 gtk_window_set_type_hint(GTK_WINDOW(window
), GDK_WINDOW_TYPE_HINT_DIALOG
);
413 g_signal_connect (G_OBJECT (window
), "delete_event",
414 G_CALLBACK (delete_event_cb
), sk
);
415 g_signal_connect (G_OBJECT (window
), "key_press_event",
416 G_CALLBACK (key_pressed_cb
), sk
);
417 MANAGE_WINDOW_SIGNALS_CONNECT (window
);
419 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 8);
420 gtk_container_add (GTK_CONTAINER (window
), vbox
);
422 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 4);
423 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, FALSE
, 0);
424 label
= gtk_label_new ( "" );
425 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
427 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 8);
428 gtk_box_pack_start (GTK_BOX (vbox
), hbox
, TRUE
, TRUE
, 0);
429 gtk_container_set_border_width (GTK_CONTAINER (hbox
), 2);
431 scrolledwin
= gtk_scrolled_window_new (NULL
, NULL
);
432 gtk_box_pack_start (GTK_BOX (hbox
), scrolledwin
, TRUE
, TRUE
, 0);
433 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin
),
434 GTK_POLICY_AUTOMATIC
,
435 GTK_POLICY_AUTOMATIC
);
437 store
= gtk_list_store_new(N_COL_TITLES
,
446 view
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(store
));
447 g_object_unref(store
);
448 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view
), TRUE
);
449 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(view
), FALSE
);
450 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(view
));
451 gtk_tree_selection_set_mode(sel
, GTK_SELECTION_BROWSE
);
453 rdr
= gtk_cell_renderer_text_new();
454 col
= gtk_tree_view_column_new_with_attributes(_("Size"), rdr
,
455 "markup", COL_ALGO
, NULL
);
456 gtk_tree_view_column_set_min_width(col
, COL_ALGO_WIDTH
);
457 gtk_tree_view_column_set_sort_column_id(col
, i
++);
458 gtk_tree_view_append_column(GTK_TREE_VIEW(view
), col
);
460 col
= gtk_tree_view_column_new_with_attributes(_("Key ID"), rdr
,
461 "markup", COL_KEYID
, NULL
);
462 gtk_tree_view_column_set_min_width(col
, COL_KEYID_WIDTH
);
463 gtk_tree_view_column_set_sort_column_id(col
, i
++);
464 gtk_tree_view_append_column(GTK_TREE_VIEW(view
), col
);
466 col
= gtk_tree_view_column_new_with_attributes(_("Name"), rdr
,
467 "markup", COL_NAME
, NULL
);
468 gtk_tree_view_column_set_min_width(col
, COL_NAME_WIDTH
);
469 gtk_tree_view_column_set_sort_column_id(col
, i
++);
470 gtk_tree_view_append_column(GTK_TREE_VIEW(view
), col
);
472 col
= gtk_tree_view_column_new_with_attributes(_("Address"), rdr
,
473 "markup", COL_ADDRESS
, NULL
);
474 gtk_tree_view_column_set_min_width(col
, COL_ADDRESS_WIDTH
);
475 gtk_tree_view_column_set_sort_column_id(col
, i
++);
476 gtk_tree_view_append_column(GTK_TREE_VIEW(view
), col
);
478 col
= gtk_tree_view_column_new_with_attributes(_("Trust"), rdr
,
479 "markup", COL_TRUST
, NULL
);
480 gtk_tree_view_column_set_min_width(col
, COL_TRUST_WIDTH
);
481 gtk_tree_view_column_set_sort_column_id(col
, i
++);
482 gtk_tree_view_append_column(GTK_TREE_VIEW(view
), col
);
484 g_signal_connect(G_OBJECT(view
), "row-activated",
485 G_CALLBACK(view_row_activated_cb
), sk
);
487 gtk_container_add (GTK_CONTAINER (scrolledwin
), view
);
489 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 8);
490 gtk_box_pack_end (GTK_BOX (vbox
), hbox
, FALSE
, FALSE
, 0);
492 /* TRANSLATORS: check that the accelerators in _Select, _Other and
493 * Do_n't encrypt are different than the one in the stock Cancel
495 gtkut_stock_button_set_create (&bbox
,
496 &select_btn
, NULL
, _("_Select"),
497 &other_btn
, NULL
, _("_Other"),
498 &dont_encrypt_btn
, NULL
, _("Do_n't encrypt"));
500 cancel_btn
= gtk_button_new_with_mnemonic("_Cancel");
501 gtk_widget_set_can_default(cancel_btn
, TRUE
);
502 gtk_box_pack_start(GTK_BOX(bbox
), cancel_btn
, TRUE
, TRUE
, 0);
503 gtk_widget_show(cancel_btn
);
504 gtk_box_pack_end (GTK_BOX (hbox
), bbox
, FALSE
, FALSE
, 0);
505 gtk_widget_grab_default (select_btn
);
507 g_signal_connect (G_OBJECT (select_btn
), "clicked",
508 G_CALLBACK (select_btn_cb
), sk
);
509 g_signal_connect (G_OBJECT(cancel_btn
), "clicked",
510 G_CALLBACK (cancel_btn_cb
), sk
);
511 g_signal_connect (G_OBJECT(dont_encrypt_btn
), "clicked",
512 G_CALLBACK (dont_encrypt_btn_cb
), sk
);
513 g_signal_connect (G_OBJECT (other_btn
), "clicked",
514 G_CALLBACK (other_btn_cb
), sk
);
516 vbox2
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 4);
517 gtk_box_pack_start (GTK_BOX (hbox
), vbox2
, FALSE
, FALSE
, 0);
520 sk
->toplabel
= GTK_LABEL (label
);
525 /* Function called by gtk_tree_model_foreach() upon dialog close,
526 * which unrefs the gpgme_key_t pointer from each model line */
528 close_dialog_foreach_func(GtkTreeModel
*model
,
535 gtk_tree_model_get(model
, iter
, COL_PTR
, &key
, -1);
536 gpgme_key_unref(key
);
542 open_dialog (struct select_keys_s
*sk
)
546 manage_window_set_transient (GTK_WINDOW (sk
->window
));
548 sk
->sort_column
= N_COL_TITLES
; /* use an invalid value */
549 sk
->sort_type
= GTK_SORT_ASCENDING
;
554 close_dialog (struct select_keys_s
*sk
)
557 cm_return_if_fail (sk
);
559 debug_print("pgpcore select-keys dialog closing\n");
560 if (sk
->view
!= NULL
) {
561 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(sk
->view
));
562 gtk_tree_model_foreach(model
, close_dialog_foreach_func
, NULL
);
563 gtk_list_store_clear(GTK_LIST_STORE(model
));
566 gtk_widget_destroy (sk
->window
);
572 delete_event_cb (GtkWidget
*widget
, GdkEventAny
*event
, gpointer data
)
574 struct select_keys_s
*sk
= data
;
584 key_pressed_cb (GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
586 struct select_keys_s
*sk
= data
;
588 cm_return_val_if_fail (sk
, FALSE
);
589 if (event
&& event
->keyval
== GDK_KEY_Escape
) {
598 select_btn_cb (GtkWidget
*widget
, gpointer data
)
600 struct select_keys_s
*sk
= data
;
604 cm_return_if_fail (sk
);
606 key
= gtkut_tree_view_get_selected_pointer(
607 GTK_TREE_VIEW(sk
->view
), COL_PTR
,
611 for (uid
= key
->uids
; uid
; uid
= uid
->next
) {
612 gchar
*raw_mail
= NULL
;
616 raw_mail
= g_strdup(uid
->email
);
617 extract_address(raw_mail
);
618 if (sk
->pattern
&& !strcasecmp(sk
->pattern
, raw_mail
)) {
627 if ( uid
->validity
< GPGME_VALIDITY_FULL
) {
628 use_key
= use_untrusted(key
, uid
, sk
->proto
);
630 debug_print ("** Key untrusted, will not encrypt\n");
634 sk
->kset
= g_realloc(sk
->kset
,
635 sizeof(gpgme_key_t
) * (sk
->num_keys
+ 1));
637 sk
->kset
[sk
->num_keys
] = key
;
640 sk
->result
= KEY_SELECTION_OK
;
647 cancel_btn_cb (GtkWidget
*widget
, gpointer data
)
649 struct select_keys_s
*sk
= data
;
651 cm_return_if_fail (sk
);
653 sk
->result
= KEY_SELECTION_CANCEL
;
655 gpgme_cancel (sk
->select_ctx
);
660 dont_encrypt_btn_cb (GtkWidget
*widget
, gpointer data
)
662 struct select_keys_s
*sk
= data
;
664 cm_return_if_fail (sk
);
666 sk
->result
= KEY_SELECTION_DONT
;
668 gpgme_cancel (sk
->select_ctx
);
673 other_btn_cb (GtkWidget
*widget
, gpointer data
)
675 struct select_keys_s
*sk
= data
;
678 cm_return_if_fail (sk
);
679 uid
= input_dialog ( _("Add key"),
680 _("Enter another user or key ID:"),
684 if (fill_view (sk
, uid
, sk
->proto
) != NULL
) {
685 gpgme_release(sk
->select_ctx
);
686 sk
->select_ctx
= NULL
;
688 update_progress (sk
, 0, sk
->pattern
);
694 use_untrusted (gpgme_key_t key
, gpgme_user_id_t uid
, gpgme_protocol_t proto
)
699 if (proto
!= GPGME_PROTOCOL_OpenPGP
)
702 title
= g_strdup_printf(_("Encrypt to %s <%s>"), uid
->name
, uid
->email
);
703 buf
= g_strdup_printf(_("This encryption key is not fully trusted.\n"
704 "If you choose to encrypt the message with this key, you don't\n"
705 "know for sure that it will go to the person you mean it to.\n\n"
706 "Key details: ID %s, primary identity %s <%s>\n\n"
707 "Do you trust this key enough to use it anyway?"),
708 key
->subkeys
->keyid
, key
->uids
->name
, key
->uids
->email
);
709 aval
= alertpanel(title
, buf
,
710 NULL
, _("_No"), NULL
, _("_Yes"), NULL
, NULL
, ALERTFOCUS_FIRST
);
713 if (aval
== G_ALERTALTERNATE
)