1 /* select-keys.c - GTK+ based key selection
2 * Copyright (C) 2001-2012 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/>.
28 #include <glib/gi18n.h>
29 #include <gdk/gdkkeysyms.h>
31 #include "select-keys.h"
34 #include "inputdialog.h"
35 #include "manage_window.h"
36 #include "alertpanel.h"
38 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
39 #define DIMof(type,member) DIM(((type *)0)->member)
52 struct select_keys_s {
58 unsigned int num_keys;
60 gpgme_ctx_t select_ctx;
61 gpgme_protocol_t proto;
62 GtkSortType sort_type;
63 enum col_titles sort_column;
64 SelectionResult result;
68 static void set_row (GtkCMCList *clist, gpgme_key_t key, gpgme_protocol_t proto);
69 static gpgme_key_t fill_clist (struct select_keys_s *sk, const char *pattern,
70 gpgme_protocol_t proto);
71 static void create_dialog (struct select_keys_s *sk);
72 static void open_dialog (struct select_keys_s *sk);
73 static void close_dialog (struct select_keys_s *sk);
74 static gint delete_event_cb (GtkWidget *widget,
75 GdkEventAny *event, gpointer data);
76 static gboolean key_pressed_cb (GtkWidget *widget,
77 GdkEventKey *event, gpointer data);
78 static void select_btn_cb (GtkWidget *widget, gpointer data);
79 static void cancel_btn_cb (GtkWidget *widget, gpointer data);
80 static void dont_encrypt_btn_cb (GtkWidget *widget, gpointer data);
81 static void other_btn_cb (GtkWidget *widget, gpointer data);
82 static void sort_keys (struct select_keys_s *sk, enum col_titles column);
83 static void sort_keys_name (GtkWidget *widget, gpointer data);
84 static void sort_keys_email (GtkWidget *widget, gpointer data);
86 static gboolean use_untrusted (gpgme_key_t, gpgme_user_id_t uid, gpgme_protocol_t proto);
89 update_progress (struct select_keys_s *sk, int running, const char *pattern)
91 static int windmill[] = { '-', '\\', '|', '/' };
95 buf = g_strdup_printf (_("No exact match for '%s'; please select the key."),
98 buf = g_strdup_printf (_("Collecting info for '%s' ... %c"),
100 windmill[running%DIM(windmill)]);
101 gtk_label_set_text (sk->toplabel, buf);
107 * gpgmegtk_recipient_selection:
108 * @recp_names: A list of email addresses
110 * Select a list of recipients from a given list of email addresses.
111 * This may pop up a window to present the user a choice, it will also
112 * check that the recipients key are all valid.
114 * Return value: NULL on error or a list of list of recipients.
117 gpgmegtk_recipient_selection (GSList *recp_names, SelectionResult *result,
118 gpgme_protocol_t proto)
120 struct select_keys_s sk;
121 gpgme_key_t key = NULL;
122 memset (&sk, 0, sizeof sk);
127 sk.pattern = recp_names? recp_names->data:NULL;
129 gtk_cmclist_clear (sk.clist);
130 key = fill_clist (&sk, sk.pattern, proto);
131 update_progress (&sk, 0, sk.pattern);
133 gtk_widget_show_all (sk.window);
136 gtk_widget_hide (sk.window);
137 sk.kset = g_realloc(sk.kset,
138 sizeof(gpgme_key_t) * (sk.num_keys + 1));
140 sk.kset[sk.num_keys] = key;
143 sk.result = KEY_SELECTION_OK;
144 gpgme_release (sk.select_ctx);
145 sk.select_ctx = NULL;
146 debug_print("used %s\n", key->uids->email);
150 recp_names = recp_names->next;
151 } while (sk.okay && recp_names);
159 sk.kset = g_realloc(sk.kset, sizeof(gpgme_key_t) * (sk.num_keys + 1));
160 sk.kset[sk.num_keys] = NULL;
168 destroy_key (gpointer data)
170 gpgme_key_t key = data;
171 gpgme_key_release (key);
175 set_row (GtkCMCList *clist, gpgme_key_t key, gpgme_protocol_t proto)
178 const char *text[N_COL_TITLES];
181 gsize by_read = 0, by_written = 0;
182 gchar *ret_str = NULL;
184 /* first check whether the key is capable of encryption which is not
185 * the case for revoked, expired or sign-only keys */
186 if (!key->can_encrypt || key->revoked || key->expired || key->disabled)
189 algo_buf = g_strdup_printf ("%du/%s",
190 key->subkeys->length,
191 gpgme_pubkey_algo_name(key->subkeys->pubkey_algo) );
192 text[COL_ALGO] = algo_buf;
194 s = key->subkeys->keyid;
195 if (strlen (s) == 16)
196 s += 8; /* show only the short keyID */
203 if (proto == GPGME_PROTOCOL_CMS) {
204 if (strstr(s, ",CN="))
205 s = strstr(s, ",CN=")+4;
206 else if (strstr(s, "CN="))
207 s = strstr(s, "CN=")+3;
211 if (!g_utf8_validate(s, -1, NULL))
212 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
213 if (ret_str && by_written) {
218 if (proto == GPGME_PROTOCOL_CMS && (!key->uids->email || !*key->uids->email)) {
219 gpgme_user_id_t uid = key->uids->next;
223 s = key->uids->email;
225 s = key->uids->email;
229 if (!g_utf8_validate(s, -1, NULL))
230 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
231 if (ret_str && by_written) {
236 switch (key->uids->validity)
238 case GPGME_VALIDITY_UNDEFINED:
241 case GPGME_VALIDITY_NEVER:
244 case GPGME_VALIDITY_MARGINAL:
247 case GPGME_VALIDITY_FULL:
250 case GPGME_VALIDITY_ULTIMATE:
253 case GPGME_VALIDITY_UNKNOWN:
258 text[COL_VALIDITY] = s;
260 row = gtk_cmclist_append (clist, (gchar**)text);
263 gtk_cmclist_set_row_data_full (clist, row, key, destroy_key);
267 fill_clist (struct select_keys_s *sk, const char *pattern, gpgme_protocol_t proto)
275 gboolean exact_match = FALSE;
276 gpgme_key_t last_key = NULL;
277 gpgme_user_id_t last_uid = NULL;
278 cm_return_val_if_fail (sk, NULL);
280 cm_return_val_if_fail (clist, NULL);
282 debug_print ("select_keys:fill_clist: pattern '%s' proto %d\n", pattern, proto);
284 /*gtk_cmclist_freeze (select_keys.clist);*/
285 err = gpgme_new (&ctx);
288 gpgme_set_protocol(ctx, proto);
289 sk->select_ctx = ctx;
291 update_progress (sk, ++running, pattern);
292 while (gtk_events_pending ())
293 gtk_main_iteration ();
295 err = gpgme_op_keylist_start (ctx, pattern, 0);
297 debug_print ("** gpgme_op_keylist_start(%s) failed: %s",
298 pattern, gpgme_strerror (err));
299 sk->select_ctx = NULL;
303 update_progress (sk, ++running, pattern);
304 while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
305 gpgme_user_id_t uid = key->uids;
306 if (!key->can_encrypt || key->revoked || key->expired || key->disabled)
308 debug_print ("%% %s:%d: insert\n", __FILE__ ,__LINE__ );
309 set_row (clist, key, proto );
310 for (; uid; uid = uid->next) {
311 gchar *raw_mail = NULL;
315 raw_mail = g_strdup(uid->email);
316 extract_address(raw_mail);
317 if (!strcasecmp(pattern, raw_mail)) {
328 update_progress (sk, ++running, pattern);
329 while (gtk_events_pending ())
330 gtk_main_iteration ();
333 if (exact_match == TRUE && num_results == 1) {
334 if (last_key->uids->validity < GPGME_VALIDITY_FULL &&
335 !use_untrusted(last_key, last_uid, proto))
339 debug_print ("%% %s:%d: ready\n", __FILE__ ,__LINE__ );
340 if (gpgme_err_code(err) != GPG_ERR_EOF) {
341 debug_print ("** gpgme_op_keylist_next failed: %s",
342 gpgme_strerror (err));
343 gpgme_op_keylist_end(ctx);
345 if (!exact_match || num_results != 1) {
346 sk->select_ctx = NULL;
349 /*gtk_cmclist_thaw (select_keys.clist);*/
350 return (exact_match == TRUE && num_results == 1 ? last_key:NULL);
355 create_dialog (struct select_keys_s *sk)
358 GtkWidget *vbox, *vbox2, *hbox;
360 GtkWidget *scrolledwin;
363 GtkWidget *select_btn, *cancel_btn, *dont_encrypt_btn, *other_btn;
364 const char *titles[N_COL_TITLES];
366 g_assert (!sk->window);
367 window = gtkut_window_new (GTK_WINDOW_TOPLEVEL, "select-keys");
368 gtk_widget_set_size_request (window, 520, 280);
369 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
370 gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
371 gtk_window_set_modal (GTK_WINDOW (window), TRUE);
372 g_signal_connect (G_OBJECT (window), "delete_event",
373 G_CALLBACK (delete_event_cb), sk);
374 g_signal_connect (G_OBJECT (window), "key_press_event",
375 G_CALLBACK (key_pressed_cb), sk);
376 MANAGE_WINDOW_SIGNALS_CONNECT (window);
378 vbox = gtk_vbox_new (FALSE, 8);
379 gtk_container_add (GTK_CONTAINER (window), vbox);
381 hbox = gtk_hbox_new(FALSE, 4);
382 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
383 label = gtk_label_new ( "" );
384 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
386 hbox = gtk_hbox_new (FALSE, 8);
387 gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
388 gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
390 scrolledwin = gtk_scrolled_window_new (NULL, NULL);
391 gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
392 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
393 GTK_POLICY_AUTOMATIC,
394 GTK_POLICY_AUTOMATIC);
396 titles[COL_ALGO] = _("Size");
397 titles[COL_KEYID] = _("Key ID");
398 titles[COL_NAME] = _("Name");
399 titles[COL_EMAIL] = _("Address");
400 titles[COL_VALIDITY] = _("Trust");
402 clist = gtk_cmclist_new_with_titles (N_COL_TITLES, (char**)titles);
403 gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
404 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_ALGO, 72);
405 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_KEYID, 76);
406 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_NAME, 130);
407 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_EMAIL, 130);
408 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_VALIDITY, 20);
409 gtk_cmclist_set_selection_mode (GTK_CMCLIST(clist), GTK_SELECTION_BROWSE);
410 g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_NAME].button),
412 G_CALLBACK(sort_keys_name), sk);
413 g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_EMAIL].button),
415 G_CALLBACK(sort_keys_email), sk);
417 hbox = gtk_hbox_new (FALSE, 8);
418 gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
420 /* TRANSLATORS: check that the accelerators in _Select, _Other and
421 * Do_n't encrypt are different than the one in the stock Cancel
423 gtkut_stock_button_set_create (&bbox,
424 &select_btn, _("_Select"),
425 &other_btn, _("_Other"),
426 &dont_encrypt_btn, _("Do_n't encrypt"));
428 cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
429 gtkut_widget_set_can_default(cancel_btn, TRUE);
430 gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
431 gtk_widget_show(cancel_btn);
432 gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
433 gtk_widget_grab_default (select_btn);
435 g_signal_connect (G_OBJECT (select_btn), "clicked",
436 G_CALLBACK (select_btn_cb), sk);
437 g_signal_connect (G_OBJECT(cancel_btn), "clicked",
438 G_CALLBACK (cancel_btn_cb), sk);
439 g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
440 G_CALLBACK (dont_encrypt_btn_cb), sk);
441 g_signal_connect (G_OBJECT (other_btn), "clicked",
442 G_CALLBACK (other_btn_cb), sk);
444 vbox2 = gtk_vbox_new (FALSE, 4);
445 gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
448 sk->toplabel = GTK_LABEL (label);
449 sk->clist = GTK_CMCLIST (clist);
454 open_dialog (struct select_keys_s *sk)
458 manage_window_set_transient (GTK_WINDOW (sk->window));
460 sk->sort_column = N_COL_TITLES; /* use an invalid value */
461 sk->sort_type = GTK_SORT_ASCENDING;
466 close_dialog (struct select_keys_s *sk)
468 cm_return_if_fail (sk);
469 gtk_widget_destroy (sk->window);
475 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
477 struct select_keys_s *sk = data;
487 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
489 struct select_keys_s *sk = data;
491 cm_return_val_if_fail (sk, FALSE);
492 if (event && event->keyval == GDK_KEY_Escape) {
501 select_btn_cb (GtkWidget *widget, gpointer data)
503 struct select_keys_s *sk = data;
508 cm_return_if_fail (sk);
509 if (!sk->clist->selection) {
510 debug_print ("** nothing selected");
513 row = GPOINTER_TO_INT(sk->clist->selection->data);
514 key = gtk_cmclist_get_row_data(sk->clist, row);
517 for (uid = key->uids; uid; uid = uid->next) {
518 gchar *raw_mail = NULL;
522 raw_mail = g_strdup(uid->email);
523 extract_address(raw_mail);
524 if (sk->pattern && !strcasecmp(sk->pattern, raw_mail)) {
533 if ( uid->validity < GPGME_VALIDITY_FULL ) {
534 use_key = use_untrusted(key, uid, sk->proto);
536 debug_print ("** Key untrusted, will not encrypt");
540 sk->kset = g_realloc(sk->kset,
541 sizeof(gpgme_key_t) * (sk->num_keys + 1));
543 sk->kset[sk->num_keys] = key;
546 sk->result = KEY_SELECTION_OK;
553 cancel_btn_cb (GtkWidget *widget, gpointer data)
555 struct select_keys_s *sk = data;
557 cm_return_if_fail (sk);
559 sk->result = KEY_SELECTION_CANCEL;
561 gpgme_cancel (sk->select_ctx);
566 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
568 struct select_keys_s *sk = data;
570 cm_return_if_fail (sk);
572 sk->result = KEY_SELECTION_DONT;
574 gpgme_cancel (sk->select_ctx);
579 other_btn_cb (GtkWidget *widget, gpointer data)
581 struct select_keys_s *sk = data;
584 cm_return_if_fail (sk);
585 uid = input_dialog ( _("Add key"),
586 _("Enter another user or key ID:"),
590 if (fill_clist (sk, uid, sk->proto) != NULL) {
591 gpgme_release(sk->select_ctx);
592 sk->select_ctx = NULL;
594 update_progress (sk, 0, sk->pattern);
600 use_untrusted (gpgme_key_t key, gpgme_user_id_t uid, gpgme_protocol_t proto)
605 if (proto != GPGME_PROTOCOL_OpenPGP)
608 title = g_strdup_printf(_("Encrypt to %s <%s>"), uid->name, uid->email);
609 buf = g_strdup_printf(_("This encryption key is not fully trusted.\n"
610 "If you choose to encrypt the message with this key, you don't\n"
611 "know for sure that it will go to the person you mean it to.\n\n"
612 "Key details: ID %s, primary identity %s <%s>\n\n"
613 "Do you trust this key enough to use it anyway?"),
614 key->subkeys->keyid, key->uids->name, key->uids->email);
617 GTK_STOCK_NO, GTK_STOCK_YES, NULL);
620 if (aval == G_ALERTALTERNATE)
628 cmp_name (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
630 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
631 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
634 sa = a? a->uids->name : NULL;
635 sb = b? b->uids->name : NULL;
640 return g_ascii_strcasecmp(sa, sb);
644 cmp_email (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
646 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
647 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
650 sa = a? a->uids->email : NULL;
651 sb = b? b->uids->email : NULL;
656 return g_ascii_strcasecmp(sa, sb);
660 sort_keys ( struct select_keys_s *sk, enum col_titles column)
662 GtkCMCList *clist = sk->clist;
666 gtk_cmclist_set_compare_func (clist, cmp_name);
669 gtk_cmclist_set_compare_func (clist, cmp_email);
675 /* column clicked again: toggle as-/decending */
676 if ( sk->sort_column == column) {
677 sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
678 GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
681 sk->sort_type = GTK_SORT_ASCENDING;
683 sk->sort_column = column;
684 gtk_cmclist_set_sort_type (clist, sk->sort_type);
685 gtk_cmclist_sort (clist);
689 sort_keys_name (GtkWidget *widget, gpointer data)
691 sort_keys ((struct select_keys_s*)data, COL_NAME);
695 sort_keys_email (GtkWidget *widget, gpointer data)
697 sort_keys ((struct select_keys_s*)data, COL_EMAIL);