1 /* select-keys.c - GTK+ based key selection
2 * Copyright (C) 2001-2009 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_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 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, 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",
297 pattern, 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)
307 debug_print ("%% %s:%d: insert\n", __FILE__ ,__LINE__ );
308 set_row (clist, key, proto );
309 for (; uid; uid = uid->next) {
310 gchar *raw_mail = NULL;
314 raw_mail = g_strdup(uid->email);
315 extract_address(raw_mail);
316 if (!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, 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, 520, 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, 72);
403 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_KEYID, 76);
404 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_NAME, 130);
405 gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_EMAIL, 130);
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_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);
514 if ( key->uids->validity < GPGME_VALIDITY_FULL ) {
515 use_key = use_untrusted(key, sk->proto);
517 debug_print ("** Key untrusted, will not encrypt");
521 sk->kset = g_realloc(sk->kset,
522 sizeof(gpgme_key_t) * (sk->num_keys + 1));
524 sk->kset[sk->num_keys] = key;
527 sk->result = KEY_SELECTION_OK;
534 cancel_btn_cb (GtkWidget *widget, gpointer data)
536 struct select_keys_s *sk = data;
538 cm_return_if_fail (sk);
540 sk->result = KEY_SELECTION_CANCEL;
542 gpgme_cancel (sk->select_ctx);
547 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
549 struct select_keys_s *sk = data;
551 cm_return_if_fail (sk);
553 sk->result = KEY_SELECTION_DONT;
555 gpgme_cancel (sk->select_ctx);
560 other_btn_cb (GtkWidget *widget, gpointer data)
562 struct select_keys_s *sk = data;
565 cm_return_if_fail (sk);
566 uid = input_dialog ( _("Add key"),
567 _("Enter another user or key ID:"),
571 if (fill_clist (sk, uid, sk->proto) != NULL) {
572 gpgme_release(sk->select_ctx);
573 sk->select_ctx = NULL;
575 update_progress (sk, 0, sk->pattern);
581 use_untrusted (gpgme_key_t key, gpgme_protocol_t proto)
586 if (proto != GPGME_PROTOCOL_OpenPGP)
589 buf = g_strdup_printf(_("The key of '%s' is not fully trusted.\n"
590 "If you choose to encrypt the message with this key you don't\n"
591 "know for sure that it will go to the person you mean it to.\n"
592 "Do you trust it enough to use it anyway?"), key->uids->email);
596 GTK_STOCK_NO, GTK_STOCK_YES, NULL);
598 if (aval == G_ALERTALTERNATE)
606 cmp_name (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
608 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
609 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
612 sa = a? a->uids->name : NULL;
613 sb = b? b->uids->name : NULL;
618 return g_ascii_strcasecmp(sa, sb);
622 cmp_email (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
624 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
625 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
628 sa = a? a->uids->email : NULL;
629 sb = b? b->uids->email : NULL;
634 return g_ascii_strcasecmp(sa, sb);
638 sort_keys ( struct select_keys_s *sk, enum col_titles column)
640 GtkCMCList *clist = sk->clist;
644 gtk_cmclist_set_compare_func (clist, cmp_name);
647 gtk_cmclist_set_compare_func (clist, cmp_email);
653 /* column clicked again: toggle as-/decending */
654 if ( sk->sort_column == column) {
655 sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
656 GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
659 sk->sort_type = GTK_SORT_ASCENDING;
661 sk->sort_column = column;
662 gtk_cmclist_set_sort_type (clist, sk->sort_type);
663 gtk_cmclist_sort (clist);
667 sort_keys_name (GtkWidget *widget, gpointer data)
669 sort_keys ((struct select_keys_s*)data, COL_NAME);
673 sort_keys_email (GtkWidget *widget, gpointer data)
675 sort_keys ((struct select_keys_s*)data, COL_EMAIL);