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;
171 debug_print("unref key %p\n", key);
173 gpgme_key_unref (key);
177 set_row (GtkCMCList *clist, gpgme_key_t key, gpgme_protocol_t proto)
180 const char *text[N_COL_TITLES];
183 gsize by_read = 0, by_written = 0;
184 gchar *ret_str = NULL;
186 /* first check whether the key is capable of encryption which is not
187 * the case for revoked, expired or sign-only keys */
188 if (!key->can_encrypt || key->revoked || key->expired || key->disabled)
191 algo_buf = g_strdup_printf ("%du/%s",
192 key->subkeys->length,
193 gpgme_pubkey_algo_name(key->subkeys->pubkey_algo) );
194 text[COL_ALGO] = algo_buf;
196 text[COL_KEYID] = key->subkeys->keyid;
201 if (proto == GPGME_PROTOCOL_CMS) {
202 if (strstr(s, ",CN="))
203 s = strstr(s, ",CN=")+4;
204 else if (strstr(s, "CN="))
205 s = strstr(s, "CN=")+3;
209 if (!g_utf8_validate(s, -1, NULL))
210 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
211 if (ret_str && by_written) {
216 if (proto == GPGME_PROTOCOL_CMS && (!key->uids->email || !*key->uids->email)) {
217 gpgme_user_id_t uid = key->uids->next;
221 s = key->uids->email;
223 s = key->uids->email;
227 if (!g_utf8_validate(s, -1, NULL))
228 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
229 if (ret_str && by_written) {
234 switch (key->uids->validity)
236 case GPGME_VALIDITY_UNDEFINED:
239 case GPGME_VALIDITY_NEVER:
242 case GPGME_VALIDITY_MARGINAL:
245 case GPGME_VALIDITY_FULL:
248 case GPGME_VALIDITY_ULTIMATE:
251 case GPGME_VALIDITY_UNKNOWN:
256 text[COL_VALIDITY] = s;
258 row = gtk_cmclist_append (clist, (gchar**)text);
262 gtk_cmclist_set_row_data_full (clist, row, key, destroy_key);
266 fill_clist (struct select_keys_s *sk, const char *pattern, gpgme_protocol_t proto)
274 gboolean exact_match = FALSE;
275 gpgme_key_t last_key = NULL;
276 gpgme_user_id_t last_uid = NULL;
277 cm_return_val_if_fail (sk, NULL);
279 cm_return_val_if_fail (clist, NULL);
281 debug_print ("select_keys:fill_clist: pattern '%s' proto %d\n", pattern != NULL ? pattern : "NULL", proto);
283 /*gtk_cmclist_freeze (select_keys.clist);*/
284 err = gpgme_new (&ctx);
287 gpgme_set_protocol(ctx, proto);
288 sk->select_ctx = ctx;
290 update_progress (sk, ++running, pattern);
291 while (gtk_events_pending ())
292 gtk_main_iteration ();
294 err = gpgme_op_keylist_start (ctx, pattern, 0);
296 debug_print ("** gpgme_op_keylist_start(%s) failed: %s\n",
297 pattern != NULL ? pattern : "NULL", gpgme_strerror (err));
298 sk->select_ctx = NULL;
302 update_progress (sk, ++running, pattern);
303 while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
304 gpgme_user_id_t uid = key->uids;
305 if (!key->can_encrypt || key->revoked || key->expired || key->disabled) {
306 gpgme_key_unref(key);
309 debug_print ("%% %s:%d: insert\n", __FILE__ ,__LINE__ );
310 set_row (clist, key, proto );
311 for (; uid; uid = uid->next) {
312 gchar *raw_mail = NULL;
316 if (uid->revoked || uid->invalid)
318 raw_mail = g_strdup(uid->email);
319 extract_address(raw_mail);
320 if (pattern != NULL && !strcasecmp(pattern, raw_mail)) {
329 if (last_key != NULL)
330 gpgme_key_unref(last_key);
333 update_progress (sk, ++running, pattern);
334 while (gtk_events_pending ())
335 gtk_main_iteration ();
338 if (exact_match == TRUE && num_results == 1) {
339 if (last_key->uids->validity < GPGME_VALIDITY_FULL &&
340 !use_untrusted(last_key, last_uid, proto))
344 debug_print ("%% %s:%d: ready\n", __FILE__ ,__LINE__ );
345 if (gpgme_err_code(err) != GPG_ERR_EOF) {
346 debug_print ("** gpgme_op_keylist_next failed: %s\n",
347 gpgme_strerror (err));
348 gpgme_op_keylist_end(ctx);
350 if (!exact_match || num_results != 1) {
351 sk->select_ctx = NULL;
354 /*gtk_cmclist_thaw (select_keys.clist);*/
355 if (exact_match && num_results == 1)
358 if (last_key != NULL)
359 gpgme_key_unref(last_key);
366 create_dialog (struct select_keys_s *sk)
369 GtkWidget *vbox, *vbox2, *hbox;
371 GtkWidget *scrolledwin;
374 GtkWidget *select_btn, *cancel_btn, *dont_encrypt_btn, *other_btn;
375 const char *titles[N_COL_TITLES];
377 g_assert (!sk->window);
378 window = gtkut_window_new (GTK_WINDOW_TOPLEVEL, "select-keys");
379 gtk_widget_set_size_request (window, 560, 280);
380 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
381 gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
382 gtk_window_set_modal (GTK_WINDOW (window), TRUE);
383 g_signal_connect (G_OBJECT (window), "delete_event",
384 G_CALLBACK (delete_event_cb), sk);
385 g_signal_connect (G_OBJECT (window), "key_press_event",
386 G_CALLBACK (key_pressed_cb), sk);
387 MANAGE_WINDOW_SIGNALS_CONNECT (window);
389 vbox = gtk_vbox_new (FALSE, 8);
390 gtk_container_add (GTK_CONTAINER (window), vbox);
392 hbox = gtk_hbox_new(FALSE, 4);
393 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
394 label = gtk_label_new ( "" );
395 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
397 hbox = gtk_hbox_new (FALSE, 8);
398 gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
399 gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
401 scrolledwin = gtk_scrolled_window_new (NULL, NULL);
402 gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
403 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
404 GTK_POLICY_AUTOMATIC,
405 GTK_POLICY_AUTOMATIC);
407 titles[COL_ALGO] = _("Size");
408 titles[COL_KEYID] = _("Key ID");
409 titles[COL_NAME] = _("Name");
410 titles[COL_EMAIL] = _("Address");
411 titles[COL_VALIDITY] = _("Trust");
413 clist = gtk_cmclist_new_with_titles (N_COL_TITLES, (char**)titles);
414 gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
415 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_ALGO, 70);
416 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_KEYID, 120);
417 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_NAME, 115);
418 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_EMAIL, 140);
419 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_VALIDITY, 20);
420 gtk_cmclist_set_selection_mode (GTK_CMCLIST(clist), GTK_SELECTION_BROWSE);
421 g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_NAME].button),
423 G_CALLBACK(sort_keys_name), sk);
424 g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_EMAIL].button),
426 G_CALLBACK(sort_keys_email), sk);
428 hbox = gtk_hbox_new (FALSE, 8);
429 gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
431 /* TRANSLATORS: check that the accelerators in _Select, _Other and
432 * Do_n't encrypt are different than the one in the stock Cancel
434 gtkut_stock_button_set_create (&bbox,
435 &select_btn, _("_Select"),
436 &other_btn, _("_Other"),
437 &dont_encrypt_btn, _("Do_n't encrypt"));
439 cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
440 gtk_widget_set_can_default(cancel_btn, TRUE);
441 gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
442 gtk_widget_show(cancel_btn);
443 gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
444 gtk_widget_grab_default (select_btn);
446 g_signal_connect (G_OBJECT (select_btn), "clicked",
447 G_CALLBACK (select_btn_cb), sk);
448 g_signal_connect (G_OBJECT(cancel_btn), "clicked",
449 G_CALLBACK (cancel_btn_cb), sk);
450 g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
451 G_CALLBACK (dont_encrypt_btn_cb), sk);
452 g_signal_connect (G_OBJECT (other_btn), "clicked",
453 G_CALLBACK (other_btn_cb), sk);
455 vbox2 = gtk_vbox_new (FALSE, 4);
456 gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
459 sk->toplabel = GTK_LABEL (label);
460 sk->clist = GTK_CMCLIST (clist);
465 open_dialog (struct select_keys_s *sk)
469 manage_window_set_transient (GTK_WINDOW (sk->window));
471 sk->sort_column = N_COL_TITLES; /* use an invalid value */
472 sk->sort_type = GTK_SORT_ASCENDING;
477 close_dialog (struct select_keys_s *sk)
479 cm_return_if_fail (sk);
480 gtk_widget_destroy (sk->window);
486 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
488 struct select_keys_s *sk = data;
498 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
500 struct select_keys_s *sk = data;
502 cm_return_val_if_fail (sk, FALSE);
503 if (event && event->keyval == GDK_KEY_Escape) {
512 select_btn_cb (GtkWidget *widget, gpointer data)
514 struct select_keys_s *sk = data;
519 cm_return_if_fail (sk);
520 if (!sk->clist->selection) {
521 debug_print ("** nothing selected\n");
524 row = GPOINTER_TO_INT(sk->clist->selection->data);
525 key = gtk_cmclist_get_row_data(sk->clist, row);
528 for (uid = key->uids; uid; uid = uid->next) {
529 gchar *raw_mail = NULL;
533 raw_mail = g_strdup(uid->email);
534 extract_address(raw_mail);
535 if (sk->pattern && !strcasecmp(sk->pattern, raw_mail)) {
544 if ( uid->validity < GPGME_VALIDITY_FULL ) {
545 use_key = use_untrusted(key, uid, sk->proto);
547 debug_print ("** Key untrusted, will not encrypt\n");
551 sk->kset = g_realloc(sk->kset,
552 sizeof(gpgme_key_t) * (sk->num_keys + 1));
554 sk->kset[sk->num_keys] = key;
557 sk->result = KEY_SELECTION_OK;
564 cancel_btn_cb (GtkWidget *widget, gpointer data)
566 struct select_keys_s *sk = data;
568 cm_return_if_fail (sk);
570 sk->result = KEY_SELECTION_CANCEL;
572 gpgme_cancel (sk->select_ctx);
577 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
579 struct select_keys_s *sk = data;
581 cm_return_if_fail (sk);
583 sk->result = KEY_SELECTION_DONT;
585 gpgme_cancel (sk->select_ctx);
590 other_btn_cb (GtkWidget *widget, gpointer data)
592 struct select_keys_s *sk = data;
595 cm_return_if_fail (sk);
596 uid = input_dialog ( _("Add key"),
597 _("Enter another user or key ID:"),
601 if (fill_clist (sk, uid, sk->proto) != NULL) {
602 gpgme_release(sk->select_ctx);
603 sk->select_ctx = NULL;
605 update_progress (sk, 0, sk->pattern);
611 use_untrusted (gpgme_key_t key, gpgme_user_id_t uid, gpgme_protocol_t proto)
616 if (proto != GPGME_PROTOCOL_OpenPGP)
619 title = g_strdup_printf(_("Encrypt to %s <%s>"), uid->name, uid->email);
620 buf = g_strdup_printf(_("This encryption key is not fully trusted.\n"
621 "If you choose to encrypt the message with this key, you don't\n"
622 "know for sure that it will go to the person you mean it to.\n\n"
623 "Key details: ID %s, primary identity %s <%s>\n\n"
624 "Do you trust this key enough to use it anyway?"),
625 key->subkeys->keyid, key->uids->name, key->uids->email);
626 aval = alertpanel(title, buf,
627 GTK_STOCK_NO, GTK_STOCK_YES, NULL, ALERTFOCUS_FIRST);
630 if (aval == G_ALERTALTERNATE)
638 cmp_name (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
640 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
641 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
644 sa = a? a->uids->name : NULL;
645 sb = b? b->uids->name : NULL;
650 return g_ascii_strcasecmp(sa, sb);
654 cmp_email (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
656 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
657 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
660 sa = a? a->uids->email : NULL;
661 sb = b? b->uids->email : NULL;
666 return g_ascii_strcasecmp(sa, sb);
670 sort_keys ( struct select_keys_s *sk, enum col_titles column)
672 GtkCMCList *clist = sk->clist;
676 gtk_cmclist_set_compare_func (clist, cmp_name);
679 gtk_cmclist_set_compare_func (clist, cmp_email);
685 /* column clicked again: toggle as-/decending */
686 if ( sk->sort_column == column) {
687 sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
688 GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
691 sk->sort_type = GTK_SORT_ASCENDING;
693 sk->sort_column = column;
694 gtk_cmclist_set_sort_type (clist, sk->sort_type);
695 gtk_cmclist_sort (clist);
699 sort_keys_name (GtkWidget *widget, gpointer data)
701 sort_keys ((struct select_keys_s*)data, COL_NAME);
705 sort_keys_email (GtkWidget *widget, gpointer data)
707 sort_keys ((struct select_keys_s*)data, COL_EMAIL);