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_unref (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)) {
323 if (last_key != NULL)
324 gpgme_key_unref(last_key);
327 update_progress (sk, ++running, pattern);
328 while (gtk_events_pending ())
329 gtk_main_iteration ();
332 if (exact_match == TRUE && num_results == 1) {
333 if (last_key->uids->validity < GPGME_VALIDITY_FULL &&
334 !use_untrusted(last_key, last_uid, proto))
338 debug_print ("%% %s:%d: ready\n", __FILE__ ,__LINE__ );
339 if (gpgme_err_code(err) != GPG_ERR_EOF) {
340 debug_print ("** gpgme_op_keylist_next failed: %s\n",
341 gpgme_strerror (err));
342 gpgme_op_keylist_end(ctx);
344 if (!exact_match || num_results != 1) {
345 sk->select_ctx = NULL;
348 /*gtk_cmclist_thaw (select_keys.clist);*/
349 if (exact_match && num_results == 1)
352 gpgme_key_unref(last_key);
358 create_dialog (struct select_keys_s *sk)
361 GtkWidget *vbox, *vbox2, *hbox;
363 GtkWidget *scrolledwin;
366 GtkWidget *select_btn, *cancel_btn, *dont_encrypt_btn, *other_btn;
367 const char *titles[N_COL_TITLES];
369 g_assert (!sk->window);
370 window = gtkut_window_new (GTK_WINDOW_TOPLEVEL, "select-keys");
371 gtk_widget_set_size_request (window, 560, 280);
372 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
373 gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
374 gtk_window_set_modal (GTK_WINDOW (window), TRUE);
375 g_signal_connect (G_OBJECT (window), "delete_event",
376 G_CALLBACK (delete_event_cb), sk);
377 g_signal_connect (G_OBJECT (window), "key_press_event",
378 G_CALLBACK (key_pressed_cb), sk);
379 MANAGE_WINDOW_SIGNALS_CONNECT (window);
381 vbox = gtk_vbox_new (FALSE, 8);
382 gtk_container_add (GTK_CONTAINER (window), vbox);
384 hbox = gtk_hbox_new(FALSE, 4);
385 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
386 label = gtk_label_new ( "" );
387 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
389 hbox = gtk_hbox_new (FALSE, 8);
390 gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
391 gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
393 scrolledwin = gtk_scrolled_window_new (NULL, NULL);
394 gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
395 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
396 GTK_POLICY_AUTOMATIC,
397 GTK_POLICY_AUTOMATIC);
399 titles[COL_ALGO] = _("Size");
400 titles[COL_KEYID] = _("Key ID");
401 titles[COL_NAME] = _("Name");
402 titles[COL_EMAIL] = _("Address");
403 titles[COL_VALIDITY] = _("Trust");
405 clist = gtk_cmclist_new_with_titles (N_COL_TITLES, (char**)titles);
406 gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
407 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_ALGO, 70);
408 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_KEYID, 120);
409 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_NAME, 115);
410 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_EMAIL, 140);
411 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_VALIDITY, 20);
412 gtk_cmclist_set_selection_mode (GTK_CMCLIST(clist), GTK_SELECTION_BROWSE);
413 g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_NAME].button),
415 G_CALLBACK(sort_keys_name), sk);
416 g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_EMAIL].button),
418 G_CALLBACK(sort_keys_email), sk);
420 hbox = gtk_hbox_new (FALSE, 8);
421 gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
423 /* TRANSLATORS: check that the accelerators in _Select, _Other and
424 * Do_n't encrypt are different than the one in the stock Cancel
426 gtkut_stock_button_set_create (&bbox,
427 &select_btn, _("_Select"),
428 &other_btn, _("_Other"),
429 &dont_encrypt_btn, _("Do_n't encrypt"));
431 cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
432 gtkut_widget_set_can_default(cancel_btn, TRUE);
433 gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
434 gtk_widget_show(cancel_btn);
435 gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
436 gtk_widget_grab_default (select_btn);
438 g_signal_connect (G_OBJECT (select_btn), "clicked",
439 G_CALLBACK (select_btn_cb), sk);
440 g_signal_connect (G_OBJECT(cancel_btn), "clicked",
441 G_CALLBACK (cancel_btn_cb), sk);
442 g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
443 G_CALLBACK (dont_encrypt_btn_cb), sk);
444 g_signal_connect (G_OBJECT (other_btn), "clicked",
445 G_CALLBACK (other_btn_cb), sk);
447 vbox2 = gtk_vbox_new (FALSE, 4);
448 gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
451 sk->toplabel = GTK_LABEL (label);
452 sk->clist = GTK_CMCLIST (clist);
457 open_dialog (struct select_keys_s *sk)
461 manage_window_set_transient (GTK_WINDOW (sk->window));
463 sk->sort_column = N_COL_TITLES; /* use an invalid value */
464 sk->sort_type = GTK_SORT_ASCENDING;
469 close_dialog (struct select_keys_s *sk)
471 cm_return_if_fail (sk);
472 gtk_widget_destroy (sk->window);
478 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
480 struct select_keys_s *sk = data;
490 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
492 struct select_keys_s *sk = data;
494 cm_return_val_if_fail (sk, FALSE);
495 if (event && event->keyval == GDK_KEY_Escape) {
504 select_btn_cb (GtkWidget *widget, gpointer data)
506 struct select_keys_s *sk = data;
511 cm_return_if_fail (sk);
512 if (!sk->clist->selection) {
513 debug_print ("** nothing selected\n");
516 row = GPOINTER_TO_INT(sk->clist->selection->data);
517 key = gtk_cmclist_get_row_data(sk->clist, row);
520 for (uid = key->uids; uid; uid = uid->next) {
521 gchar *raw_mail = NULL;
525 raw_mail = g_strdup(uid->email);
526 extract_address(raw_mail);
527 if (sk->pattern && !strcasecmp(sk->pattern, raw_mail)) {
536 if ( uid->validity < GPGME_VALIDITY_FULL ) {
537 use_key = use_untrusted(key, uid, sk->proto);
539 debug_print ("** Key untrusted, will not encrypt\n");
543 sk->kset = g_realloc(sk->kset,
544 sizeof(gpgme_key_t) * (sk->num_keys + 1));
546 sk->kset[sk->num_keys] = key;
549 sk->result = KEY_SELECTION_OK;
556 cancel_btn_cb (GtkWidget *widget, gpointer data)
558 struct select_keys_s *sk = data;
560 cm_return_if_fail (sk);
562 sk->result = KEY_SELECTION_CANCEL;
564 gpgme_cancel (sk->select_ctx);
569 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
571 struct select_keys_s *sk = data;
573 cm_return_if_fail (sk);
575 sk->result = KEY_SELECTION_DONT;
577 gpgme_cancel (sk->select_ctx);
582 other_btn_cb (GtkWidget *widget, gpointer data)
584 struct select_keys_s *sk = data;
587 cm_return_if_fail (sk);
588 uid = input_dialog ( _("Add key"),
589 _("Enter another user or key ID:"),
593 if (fill_clist (sk, uid, sk->proto) != NULL) {
594 gpgme_release(sk->select_ctx);
595 sk->select_ctx = NULL;
597 update_progress (sk, 0, sk->pattern);
603 use_untrusted (gpgme_key_t key, gpgme_user_id_t uid, gpgme_protocol_t proto)
608 if (proto != GPGME_PROTOCOL_OpenPGP)
611 title = g_strdup_printf(_("Encrypt to %s <%s>"), uid->name, uid->email);
612 buf = g_strdup_printf(_("This encryption key is not fully trusted.\n"
613 "If you choose to encrypt the message with this key, you don't\n"
614 "know for sure that it will go to the person you mean it to.\n\n"
615 "Key details: ID %s, primary identity %s <%s>\n\n"
616 "Do you trust this key enough to use it anyway?"),
617 key->subkeys->keyid, key->uids->name, key->uids->email);
620 GTK_STOCK_NO, GTK_STOCK_YES, NULL);
623 if (aval == G_ALERTALTERNATE)
631 cmp_name (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
633 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
634 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
637 sa = a? a->uids->name : NULL;
638 sb = b? b->uids->name : NULL;
643 return g_ascii_strcasecmp(sa, sb);
647 cmp_email (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
649 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
650 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
653 sa = a? a->uids->email : NULL;
654 sb = b? b->uids->email : NULL;
659 return g_ascii_strcasecmp(sa, sb);
663 sort_keys ( struct select_keys_s *sk, enum col_titles column)
665 GtkCMCList *clist = sk->clist;
669 gtk_cmclist_set_compare_func (clist, cmp_name);
672 gtk_cmclist_set_compare_func (clist, cmp_email);
678 /* column clicked again: toggle as-/decending */
679 if ( sk->sort_column == column) {
680 sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
681 GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
684 sk->sort_type = GTK_SORT_ASCENDING;
686 sk->sort_column = column;
687 gtk_cmclist_set_sort_type (clist, sk->sort_type);
688 gtk_cmclist_sort (clist);
692 sort_keys_name (GtkWidget *widget, gpointer data)
694 sort_keys ((struct select_keys_s*)data, COL_NAME);
698 sort_keys_email (GtkWidget *widget, gpointer data)
700 sort_keys ((struct select_keys_s*)data, COL_EMAIL);