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)
51 struct select_keys_s {
57 unsigned int num_keys;
59 gpgme_ctx_t select_ctx;
60 gpgme_protocol_t proto;
61 GtkSortType sort_type;
62 enum col_titles sort_column;
63 SelectionResult result;
67 static void set_row (GtkCMCList *clist, gpgme_key_t key, gpgme_protocol_t proto);
68 static gpgme_key_t fill_clist (struct select_keys_s *sk, const char *pattern,
69 gpgme_protocol_t proto);
70 static void create_dialog (struct select_keys_s *sk);
71 static void open_dialog (struct select_keys_s *sk);
72 static void close_dialog (struct select_keys_s *sk);
73 static gint delete_event_cb (GtkWidget *widget,
74 GdkEventAny *event, gpointer data);
75 static gboolean key_pressed_cb (GtkWidget *widget,
76 GdkEventKey *event, gpointer data);
77 static void select_btn_cb (GtkWidget *widget, gpointer data);
78 static void cancel_btn_cb (GtkWidget *widget, gpointer data);
79 static void dont_encrypt_btn_cb (GtkWidget *widget, gpointer data);
80 static void other_btn_cb (GtkWidget *widget, gpointer data);
81 static void sort_keys (struct select_keys_s *sk, enum col_titles column);
82 static void sort_keys_name (GtkWidget *widget, gpointer data);
83 static void sort_keys_email (GtkWidget *widget, gpointer data);
85 static gboolean use_untrusted (gpgme_key_t, gpgme_user_id_t uid, gpgme_protocol_t proto);
88 update_progress (struct select_keys_s *sk, int running, const char *pattern)
90 static int windmill[] = { '-', '\\', '|', '/' };
94 buf = g_strdup_printf (_("No exact match for '%s'; please select the key."),
97 buf = g_strdup_printf (_("Collecting info for '%s' ... %c"),
99 windmill[running%DIM(windmill)]);
100 gtk_label_set_text (sk->toplabel, buf);
106 * gpgmegtk_recipient_selection:
107 * @recp_names: A list of email addresses
109 * Select a list of recipients from a given list of email addresses.
110 * This may pop up a window to present the user a choice, it will also
111 * check that the recipients key are all valid.
113 * Return value: NULL on error or a list of list of recipients.
116 gpgmegtk_recipient_selection (GSList *recp_names, SelectionResult *result,
117 gpgme_protocol_t proto)
119 struct select_keys_s sk;
120 gpgme_key_t key = NULL;
121 memset (&sk, 0, sizeof sk);
126 sk.pattern = recp_names? recp_names->data:NULL;
128 gtk_cmclist_clear (sk.clist);
129 key = fill_clist (&sk, sk.pattern, proto);
130 update_progress (&sk, 0, sk.pattern ? sk.pattern : "NULL");
132 gtk_widget_show_all (sk.window);
135 gtk_widget_hide (sk.window);
136 sk.kset = g_realloc(sk.kset,
137 sizeof(gpgme_key_t) * (sk.num_keys + 1));
139 sk.kset[sk.num_keys] = key;
142 sk.result = KEY_SELECTION_OK;
143 gpgme_release (sk.select_ctx);
144 sk.select_ctx = NULL;
145 debug_print("used %s\n", key->uids->email);
149 recp_names = recp_names->next;
150 } while (sk.okay && recp_names);
158 sk.kset = g_realloc(sk.kset, sizeof(gpgme_key_t) * (sk.num_keys + 1));
159 sk.kset[sk.num_keys] = NULL;
167 destroy_key (gpointer data)
169 gpgme_key_t key = data;
170 gpgme_key_release (key);
174 set_row (GtkCMCList *clist, gpgme_key_t key, gpgme_protocol_t proto)
177 const char *text[N_COL_TITLES];
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) );
191 text[COL_ALGO] = algo_buf;
193 text[COL_KEYID] = key->subkeys->keyid;
198 if (proto == GPGME_PROTOCOL_CMS) {
199 if (strstr(s, ",CN="))
200 s = strstr(s, ",CN=")+4;
201 else if (strstr(s, "CN="))
202 s = strstr(s, "CN=")+3;
206 if (!g_utf8_validate(s, -1, NULL))
207 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
208 if (ret_str && by_written) {
213 if (proto == GPGME_PROTOCOL_CMS && (!key->uids->email || !*key->uids->email)) {
214 gpgme_user_id_t uid = key->uids->next;
218 s = key->uids->email;
220 s = key->uids->email;
224 if (!g_utf8_validate(s, -1, NULL))
225 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
226 if (ret_str && by_written) {
231 switch (key->uids->validity)
233 case GPGME_VALIDITY_UNDEFINED:
236 case GPGME_VALIDITY_NEVER:
239 case GPGME_VALIDITY_MARGINAL:
242 case GPGME_VALIDITY_FULL:
245 case GPGME_VALIDITY_ULTIMATE:
248 case GPGME_VALIDITY_UNKNOWN:
253 text[COL_VALIDITY] = s;
255 row = gtk_cmclist_append (clist, (gchar**)text);
258 gtk_cmclist_set_row_data_full (clist, row, key, destroy_key);
262 fill_clist (struct select_keys_s *sk, const char *pattern, gpgme_protocol_t proto)
270 gboolean exact_match = FALSE;
271 gpgme_key_t last_key = NULL;
272 gpgme_user_id_t last_uid = NULL;
273 cm_return_val_if_fail (sk, NULL);
275 cm_return_val_if_fail (clist, NULL);
277 debug_print ("select_keys:fill_clist: pattern '%s' proto %d\n", pattern != NULL ? pattern : "NULL", proto);
279 /*gtk_cmclist_freeze (select_keys.clist);*/
280 err = gpgme_new (&ctx);
283 gpgme_set_protocol(ctx, proto);
284 sk->select_ctx = ctx;
286 update_progress (sk, ++running, pattern);
287 while (gtk_events_pending ())
288 gtk_main_iteration ();
290 err = gpgme_op_keylist_start (ctx, pattern, 0);
292 debug_print ("** gpgme_op_keylist_start(%s) failed: %s\n",
293 pattern != NULL ? pattern : "NULL", gpgme_strerror (err));
294 sk->select_ctx = NULL;
298 update_progress (sk, ++running, pattern);
299 while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
300 gpgme_user_id_t uid = key->uids;
301 if (!key->can_encrypt || key->revoked || key->expired || key->disabled)
303 debug_print ("%% %s:%d: insert\n", __FILE__ ,__LINE__ );
304 set_row (clist, key, proto );
305 for (; uid; uid = uid->next) {
306 gchar *raw_mail = NULL;
310 if (uid->revoked || uid->invalid)
312 raw_mail = g_strdup(uid->email);
313 extract_address(raw_mail);
314 if (pattern != NULL && !strcasecmp(pattern, raw_mail)) {
325 update_progress (sk, ++running, pattern);
326 while (gtk_events_pending ())
327 gtk_main_iteration ();
330 if (exact_match == TRUE && num_results == 1) {
331 if (last_key->uids->validity < GPGME_VALIDITY_FULL &&
332 !use_untrusted(last_key, last_uid, proto))
336 debug_print ("%% %s:%d: ready\n", __FILE__ ,__LINE__ );
337 if (gpgme_err_code(err) != GPG_ERR_EOF) {
338 debug_print ("** gpgme_op_keylist_next failed: %s\n",
339 gpgme_strerror (err));
340 gpgme_op_keylist_end(ctx);
342 if (!exact_match || num_results != 1) {
343 sk->select_ctx = NULL;
346 /*gtk_cmclist_thaw (select_keys.clist);*/
347 return (exact_match == TRUE && num_results == 1 ? last_key:NULL);
352 create_dialog (struct select_keys_s *sk)
355 GtkWidget *vbox, *vbox2, *hbox;
357 GtkWidget *scrolledwin;
360 GtkWidget *select_btn, *cancel_btn, *dont_encrypt_btn, *other_btn;
361 const char *titles[N_COL_TITLES];
363 g_assert (!sk->window);
364 window = gtkut_window_new (GTK_WINDOW_TOPLEVEL, "select-keys");
365 gtk_widget_set_size_request (window, 560, 280);
366 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
367 gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
368 gtk_window_set_modal (GTK_WINDOW (window), TRUE);
369 g_signal_connect (G_OBJECT (window), "delete_event",
370 G_CALLBACK (delete_event_cb), sk);
371 g_signal_connect (G_OBJECT (window), "key_press_event",
372 G_CALLBACK (key_pressed_cb), sk);
373 MANAGE_WINDOW_SIGNALS_CONNECT (window);
375 vbox = gtk_vbox_new (FALSE, 8);
376 gtk_container_add (GTK_CONTAINER (window), vbox);
378 hbox = gtk_hbox_new(FALSE, 4);
379 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
380 label = gtk_label_new ( "" );
381 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
383 hbox = gtk_hbox_new (FALSE, 8);
384 gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
385 gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
387 scrolledwin = gtk_scrolled_window_new (NULL, NULL);
388 gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
389 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
390 GTK_POLICY_AUTOMATIC,
391 GTK_POLICY_AUTOMATIC);
393 titles[COL_ALGO] = _("Size");
394 titles[COL_KEYID] = _("Key ID");
395 titles[COL_NAME] = _("Name");
396 titles[COL_EMAIL] = _("Address");
397 titles[COL_VALIDITY] = _("Trust");
399 clist = gtk_cmclist_new_with_titles (N_COL_TITLES, (char**)titles);
400 gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
401 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_ALGO, 70);
402 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_KEYID, 120);
403 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_NAME, 115);
404 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_EMAIL, 140);
405 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_VALIDITY, 20);
406 gtk_cmclist_set_selection_mode (GTK_CMCLIST(clist), GTK_SELECTION_BROWSE);
407 g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_NAME].button),
409 G_CALLBACK(sort_keys_name), sk);
410 g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_EMAIL].button),
412 G_CALLBACK(sort_keys_email), sk);
414 hbox = gtk_hbox_new (FALSE, 8);
415 gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
417 /* TRANSLATORS: check that the accelerators in _Select, _Other and
418 * Do_n't encrypt are different than the one in the stock Cancel
420 gtkut_stock_button_set_create (&bbox,
421 &select_btn, _("_Select"),
422 &other_btn, _("_Other"),
423 &dont_encrypt_btn, _("Do_n't encrypt"));
425 cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
426 gtkut_widget_set_can_default(cancel_btn, TRUE);
427 gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
428 gtk_widget_show(cancel_btn);
429 gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
430 gtk_widget_grab_default (select_btn);
432 g_signal_connect (G_OBJECT (select_btn), "clicked",
433 G_CALLBACK (select_btn_cb), sk);
434 g_signal_connect (G_OBJECT(cancel_btn), "clicked",
435 G_CALLBACK (cancel_btn_cb), sk);
436 g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
437 G_CALLBACK (dont_encrypt_btn_cb), sk);
438 g_signal_connect (G_OBJECT (other_btn), "clicked",
439 G_CALLBACK (other_btn_cb), sk);
441 vbox2 = gtk_vbox_new (FALSE, 4);
442 gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
445 sk->toplabel = GTK_LABEL (label);
446 sk->clist = GTK_CMCLIST (clist);
451 open_dialog (struct select_keys_s *sk)
455 manage_window_set_transient (GTK_WINDOW (sk->window));
457 sk->sort_column = N_COL_TITLES; /* use an invalid value */
458 sk->sort_type = GTK_SORT_ASCENDING;
463 close_dialog (struct select_keys_s *sk)
465 cm_return_if_fail (sk);
466 gtk_widget_destroy (sk->window);
472 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
474 struct select_keys_s *sk = data;
484 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
486 struct select_keys_s *sk = data;
488 cm_return_val_if_fail (sk, FALSE);
489 if (event && event->keyval == GDK_KEY_Escape) {
498 select_btn_cb (GtkWidget *widget, gpointer data)
500 struct select_keys_s *sk = data;
505 cm_return_if_fail (sk);
506 if (!sk->clist->selection) {
507 debug_print ("** nothing selected\n");
510 row = GPOINTER_TO_INT(sk->clist->selection->data);
511 key = gtk_cmclist_get_row_data(sk->clist, row);
514 for (uid = key->uids; uid; uid = uid->next) {
515 gchar *raw_mail = NULL;
519 raw_mail = g_strdup(uid->email);
520 extract_address(raw_mail);
521 if (sk->pattern && !strcasecmp(sk->pattern, raw_mail)) {
530 if ( uid->validity < GPGME_VALIDITY_FULL ) {
531 use_key = use_untrusted(key, uid, sk->proto);
533 debug_print ("** Key untrusted, will not encrypt\n");
537 sk->kset = g_realloc(sk->kset,
538 sizeof(gpgme_key_t) * (sk->num_keys + 1));
540 sk->kset[sk->num_keys] = key;
543 sk->result = KEY_SELECTION_OK;
550 cancel_btn_cb (GtkWidget *widget, gpointer data)
552 struct select_keys_s *sk = data;
554 cm_return_if_fail (sk);
556 sk->result = KEY_SELECTION_CANCEL;
558 gpgme_cancel (sk->select_ctx);
563 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
565 struct select_keys_s *sk = data;
567 cm_return_if_fail (sk);
569 sk->result = KEY_SELECTION_DONT;
571 gpgme_cancel (sk->select_ctx);
576 other_btn_cb (GtkWidget *widget, gpointer data)
578 struct select_keys_s *sk = data;
581 cm_return_if_fail (sk);
582 uid = input_dialog ( _("Add key"),
583 _("Enter another user or key ID:"),
587 if (fill_clist (sk, uid, sk->proto) != NULL) {
588 gpgme_release(sk->select_ctx);
589 sk->select_ctx = NULL;
591 update_progress (sk, 0, sk->pattern);
597 use_untrusted (gpgme_key_t key, gpgme_user_id_t uid, gpgme_protocol_t proto)
602 if (proto != GPGME_PROTOCOL_OpenPGP)
605 title = g_strdup_printf(_("Encrypt to %s <%s>"), uid->name, uid->email);
606 buf = g_strdup_printf(_("This encryption key is not fully trusted.\n"
607 "If you choose to encrypt the message with this key, you don't\n"
608 "know for sure that it will go to the person you mean it to.\n\n"
609 "Key details: ID %s, primary identity %s <%s>\n\n"
610 "Do you trust this key enough to use it anyway?"),
611 key->subkeys->keyid, key->uids->name, key->uids->email);
614 GTK_STOCK_NO, GTK_STOCK_YES, NULL);
617 if (aval == G_ALERTALTERNATE)
625 cmp_name (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
627 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
628 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
631 sa = a? a->uids->name : NULL;
632 sb = b? b->uids->name : NULL;
637 return g_ascii_strcasecmp(sa, sb);
641 cmp_email (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
643 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
644 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
647 sa = a? a->uids->email : NULL;
648 sb = b? b->uids->email : NULL;
653 return g_ascii_strcasecmp(sa, sb);
657 sort_keys ( struct select_keys_s *sk, enum col_titles column)
659 GtkCMCList *clist = sk->clist;
663 gtk_cmclist_set_compare_func (clist, cmp_name);
666 gtk_cmclist_set_compare_func (clist, cmp_email);
672 /* column clicked again: toggle as-/decending */
673 if ( sk->sort_column == column) {
674 sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
675 GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
678 sk->sort_type = GTK_SORT_ASCENDING;
680 sk->sort_column = column;
681 gtk_cmclist_set_sort_type (clist, sk->sort_type);
682 gtk_cmclist_sort (clist);
686 sort_keys_name (GtkWidget *widget, gpointer data)
688 sort_keys ((struct select_keys_s*)data, COL_NAME);
692 sort_keys_email (GtkWidget *widget, gpointer data)
694 sort_keys ((struct select_keys_s*)data, COL_EMAIL);