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 ? sk.pattern : "NULL");
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 text[COL_KEYID] = key->subkeys->keyid;
199 if (proto == GPGME_PROTOCOL_CMS) {
200 if (strstr(s, ",CN="))
201 s = strstr(s, ",CN=")+4;
202 else if (strstr(s, "CN="))
203 s = strstr(s, "CN=")+3;
207 if (!g_utf8_validate(s, -1, NULL))
208 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
209 if (ret_str && by_written) {
214 if (proto == GPGME_PROTOCOL_CMS && (!key->uids->email || !*key->uids->email)) {
215 gpgme_user_id_t uid = key->uids->next;
219 s = key->uids->email;
221 s = key->uids->email;
225 if (!g_utf8_validate(s, -1, NULL))
226 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
227 if (ret_str && by_written) {
232 switch (key->uids->validity)
234 case GPGME_VALIDITY_UNDEFINED:
237 case GPGME_VALIDITY_NEVER:
240 case GPGME_VALIDITY_MARGINAL:
243 case GPGME_VALIDITY_FULL:
246 case GPGME_VALIDITY_ULTIMATE:
249 case GPGME_VALIDITY_UNKNOWN:
254 text[COL_VALIDITY] = s;
256 row = gtk_cmclist_append (clist, (gchar**)text);
259 gtk_cmclist_set_row_data_full (clist, row, key, destroy_key);
263 fill_clist (struct select_keys_s *sk, const char *pattern, gpgme_protocol_t proto)
271 gboolean exact_match = FALSE;
272 gpgme_key_t last_key = NULL;
273 gpgme_user_id_t last_uid = NULL;
274 cm_return_val_if_fail (sk, NULL);
276 cm_return_val_if_fail (clist, NULL);
278 debug_print ("select_keys:fill_clist: pattern '%s' proto %d\n", pattern != NULL ? pattern : "NULL", proto);
280 /*gtk_cmclist_freeze (select_keys.clist);*/
281 err = gpgme_new (&ctx);
284 gpgme_set_protocol(ctx, proto);
285 sk->select_ctx = ctx;
287 update_progress (sk, ++running, pattern);
288 while (gtk_events_pending ())
289 gtk_main_iteration ();
291 err = gpgme_op_keylist_start (ctx, pattern, 0);
293 debug_print ("** gpgme_op_keylist_start(%s) failed: %s",
294 pattern != NULL ? pattern : "NULL", gpgme_strerror (err));
295 sk->select_ctx = NULL;
299 update_progress (sk, ++running, pattern);
300 while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
301 gpgme_user_id_t uid = key->uids;
302 if (!key->can_encrypt || key->revoked || key->expired || key->disabled)
304 debug_print ("%% %s:%d: insert\n", __FILE__ ,__LINE__ );
305 set_row (clist, key, proto );
306 for (; uid; uid = uid->next) {
307 gchar *raw_mail = NULL;
311 if (uid->revoked || uid->invalid)
313 raw_mail = g_strdup(uid->email);
314 extract_address(raw_mail);
315 if (pattern != NULL && !strcasecmp(pattern, raw_mail)) {
326 update_progress (sk, ++running, pattern);
327 while (gtk_events_pending ())
328 gtk_main_iteration ();
331 if (exact_match == TRUE && num_results == 1) {
332 if (last_key->uids->validity < GPGME_VALIDITY_FULL &&
333 !use_untrusted(last_key, last_uid, proto))
337 debug_print ("%% %s:%d: ready\n", __FILE__ ,__LINE__ );
338 if (gpgme_err_code(err) != GPG_ERR_EOF) {
339 debug_print ("** gpgme_op_keylist_next failed: %s",
340 gpgme_strerror (err));
341 gpgme_op_keylist_end(ctx);
343 if (!exact_match || num_results != 1) {
344 sk->select_ctx = NULL;
347 /*gtk_cmclist_thaw (select_keys.clist);*/
348 return (exact_match == TRUE && num_results == 1 ? last_key:NULL);
353 create_dialog (struct select_keys_s *sk)
356 GtkWidget *vbox, *vbox2, *hbox;
358 GtkWidget *scrolledwin;
361 GtkWidget *select_btn, *cancel_btn, *dont_encrypt_btn, *other_btn;
362 const char *titles[N_COL_TITLES];
364 g_assert (!sk->window);
365 window = gtkut_window_new (GTK_WINDOW_TOPLEVEL, "select-keys");
366 gtk_widget_set_size_request (window, 560, 280);
367 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
368 gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
369 gtk_window_set_modal (GTK_WINDOW (window), TRUE);
370 g_signal_connect (G_OBJECT (window), "delete_event",
371 G_CALLBACK (delete_event_cb), sk);
372 g_signal_connect (G_OBJECT (window), "key_press_event",
373 G_CALLBACK (key_pressed_cb), sk);
374 MANAGE_WINDOW_SIGNALS_CONNECT (window);
376 vbox = gtk_vbox_new (FALSE, 8);
377 gtk_container_add (GTK_CONTAINER (window), vbox);
379 hbox = gtk_hbox_new(FALSE, 4);
380 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
381 label = gtk_label_new ( "" );
382 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
384 hbox = gtk_hbox_new (FALSE, 8);
385 gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
386 gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
388 scrolledwin = gtk_scrolled_window_new (NULL, NULL);
389 gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
390 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
391 GTK_POLICY_AUTOMATIC,
392 GTK_POLICY_AUTOMATIC);
394 titles[COL_ALGO] = _("Size");
395 titles[COL_KEYID] = _("Key ID");
396 titles[COL_NAME] = _("Name");
397 titles[COL_EMAIL] = _("Address");
398 titles[COL_VALIDITY] = _("Trust");
400 clist = gtk_cmclist_new_with_titles (N_COL_TITLES, (char**)titles);
401 gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
402 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_ALGO, 70);
403 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_KEYID, 120);
404 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_NAME, 115);
405 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_EMAIL, 140);
406 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_VALIDITY, 20);
407 gtk_cmclist_set_selection_mode (GTK_CMCLIST(clist), GTK_SELECTION_BROWSE);
408 g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_NAME].button),
410 G_CALLBACK(sort_keys_name), sk);
411 g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_EMAIL].button),
413 G_CALLBACK(sort_keys_email), sk);
415 hbox = gtk_hbox_new (FALSE, 8);
416 gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
418 /* TRANSLATORS: check that the accelerators in _Select, _Other and
419 * Do_n't encrypt are different than the one in the stock Cancel
421 gtkut_stock_button_set_create (&bbox,
422 &select_btn, _("_Select"),
423 &other_btn, _("_Other"),
424 &dont_encrypt_btn, _("Do_n't encrypt"));
426 cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
427 gtkut_widget_set_can_default(cancel_btn, TRUE);
428 gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
429 gtk_widget_show(cancel_btn);
430 gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
431 gtk_widget_grab_default (select_btn);
433 g_signal_connect (G_OBJECT (select_btn), "clicked",
434 G_CALLBACK (select_btn_cb), sk);
435 g_signal_connect (G_OBJECT(cancel_btn), "clicked",
436 G_CALLBACK (cancel_btn_cb), sk);
437 g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
438 G_CALLBACK (dont_encrypt_btn_cb), sk);
439 g_signal_connect (G_OBJECT (other_btn), "clicked",
440 G_CALLBACK (other_btn_cb), sk);
442 vbox2 = gtk_vbox_new (FALSE, 4);
443 gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
446 sk->toplabel = GTK_LABEL (label);
447 sk->clist = GTK_CMCLIST (clist);
452 open_dialog (struct select_keys_s *sk)
456 manage_window_set_transient (GTK_WINDOW (sk->window));
458 sk->sort_column = N_COL_TITLES; /* use an invalid value */
459 sk->sort_type = GTK_SORT_ASCENDING;
464 close_dialog (struct select_keys_s *sk)
466 cm_return_if_fail (sk);
467 gtk_widget_destroy (sk->window);
473 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
475 struct select_keys_s *sk = data;
485 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
487 struct select_keys_s *sk = data;
489 cm_return_val_if_fail (sk, FALSE);
490 if (event && event->keyval == GDK_KEY_Escape) {
499 select_btn_cb (GtkWidget *widget, gpointer data)
501 struct select_keys_s *sk = data;
506 cm_return_if_fail (sk);
507 if (!sk->clist->selection) {
508 debug_print ("** nothing selected");
511 row = GPOINTER_TO_INT(sk->clist->selection->data);
512 key = gtk_cmclist_get_row_data(sk->clist, row);
515 for (uid = key->uids; uid; uid = uid->next) {
516 gchar *raw_mail = NULL;
520 raw_mail = g_strdup(uid->email);
521 extract_address(raw_mail);
522 if (sk->pattern && !strcasecmp(sk->pattern, raw_mail)) {
531 if ( uid->validity < GPGME_VALIDITY_FULL ) {
532 use_key = use_untrusted(key, uid, sk->proto);
534 debug_print ("** Key untrusted, will not encrypt");
538 sk->kset = g_realloc(sk->kset,
539 sizeof(gpgme_key_t) * (sk->num_keys + 1));
541 sk->kset[sk->num_keys] = key;
544 sk->result = KEY_SELECTION_OK;
551 cancel_btn_cb (GtkWidget *widget, gpointer data)
553 struct select_keys_s *sk = data;
555 cm_return_if_fail (sk);
557 sk->result = KEY_SELECTION_CANCEL;
559 gpgme_cancel (sk->select_ctx);
564 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
566 struct select_keys_s *sk = data;
568 cm_return_if_fail (sk);
570 sk->result = KEY_SELECTION_DONT;
572 gpgme_cancel (sk->select_ctx);
577 other_btn_cb (GtkWidget *widget, gpointer data)
579 struct select_keys_s *sk = data;
582 cm_return_if_fail (sk);
583 uid = input_dialog ( _("Add key"),
584 _("Enter another user or key ID:"),
588 if (fill_clist (sk, uid, sk->proto) != NULL) {
589 gpgme_release(sk->select_ctx);
590 sk->select_ctx = NULL;
592 update_progress (sk, 0, sk->pattern);
598 use_untrusted (gpgme_key_t key, gpgme_user_id_t uid, gpgme_protocol_t proto)
603 if (proto != GPGME_PROTOCOL_OpenPGP)
606 title = g_strdup_printf(_("Encrypt to %s <%s>"), uid->name, uid->email);
607 buf = g_strdup_printf(_("This encryption key is not fully trusted.\n"
608 "If you choose to encrypt the message with this key, you don't\n"
609 "know for sure that it will go to the person you mean it to.\n\n"
610 "Key details: ID %s, primary identity %s <%s>\n\n"
611 "Do you trust this key enough to use it anyway?"),
612 key->subkeys->keyid, key->uids->name, key->uids->email);
615 GTK_STOCK_NO, GTK_STOCK_YES, NULL);
618 if (aval == G_ALERTALTERNATE)
626 cmp_name (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
628 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
629 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
632 sa = a? a->uids->name : NULL;
633 sb = b? b->uids->name : NULL;
638 return g_ascii_strcasecmp(sa, sb);
642 cmp_email (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
644 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
645 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
648 sa = a? a->uids->email : NULL;
649 sb = b? b->uids->email : NULL;
654 return g_ascii_strcasecmp(sa, sb);
658 sort_keys ( struct select_keys_s *sk, enum col_titles column)
660 GtkCMCList *clist = sk->clist;
664 gtk_cmclist_set_compare_func (clist, cmp_name);
667 gtk_cmclist_set_compare_func (clist, cmp_email);
673 /* column clicked again: toggle as-/decending */
674 if ( sk->sort_column == column) {
675 sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
676 GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
679 sk->sort_type = GTK_SORT_ASCENDING;
681 sk->sort_column = column;
682 gtk_cmclist_set_sort_type (clist, sk->sort_type);
683 gtk_cmclist_sort (clist);
687 sort_keys_name (GtkWidget *widget, gpointer data)
689 sort_keys ((struct select_keys_s*)data, COL_NAME);
693 sort_keys_email (GtkWidget *widget, gpointer data)
695 sort_keys ((struct select_keys_s*)data, COL_EMAIL);