1 /* select-keys.c - GTK+ based key selection
2 * Copyright (C) 2001-2007 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)
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 g_return_val_if_fail (sk, NULL);
279 g_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)
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 gtkut_stock_button_set_create (&bbox,
419 &select_btn, _("Select"),
420 &other_btn, _("Other"),
421 &dont_encrypt_btn, _("Don't encrypt"));
423 cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
424 GTK_WIDGET_SET_FLAGS(cancel_btn, GTK_CAN_DEFAULT);
425 gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
426 gtk_widget_show(cancel_btn);
427 gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
428 gtk_widget_grab_default (select_btn);
430 g_signal_connect (G_OBJECT (select_btn), "clicked",
431 G_CALLBACK (select_btn_cb), sk);
432 g_signal_connect (G_OBJECT(cancel_btn), "clicked",
433 G_CALLBACK (cancel_btn_cb), sk);
434 g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
435 G_CALLBACK (dont_encrypt_btn_cb), sk);
436 g_signal_connect (G_OBJECT (other_btn), "clicked",
437 G_CALLBACK (other_btn_cb), sk);
439 vbox2 = gtk_vbox_new (FALSE, 4);
440 gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
443 sk->toplabel = GTK_LABEL (label);
444 sk->clist = GTK_CMCLIST (clist);
449 open_dialog (struct select_keys_s *sk)
453 manage_window_set_transient (GTK_WINDOW (sk->window));
455 sk->sort_column = N_COL_TITLES; /* use an invalid value */
456 sk->sort_type = GTK_SORT_ASCENDING;
461 close_dialog (struct select_keys_s *sk)
463 g_return_if_fail (sk);
464 gtk_widget_destroy (sk->window);
470 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
472 struct select_keys_s *sk = data;
482 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
484 struct select_keys_s *sk = data;
486 g_return_val_if_fail (sk, FALSE);
487 if (event && event->keyval == GDK_Escape) {
496 select_btn_cb (GtkWidget *widget, gpointer data)
498 struct select_keys_s *sk = data;
503 g_return_if_fail (sk);
504 if (!sk->clist->selection) {
505 debug_print ("** nothing selected");
508 row = GPOINTER_TO_INT(sk->clist->selection->data);
509 key = gtk_cmclist_get_row_data(sk->clist, row);
511 if ( key->uids->validity < GPGME_VALIDITY_FULL ) {
512 use_key = use_untrusted(key, sk->proto);
514 debug_print ("** Key untrusted, will not encrypt");
518 sk->kset = g_realloc(sk->kset,
519 sizeof(gpgme_key_t) * (sk->num_keys + 1));
521 sk->kset[sk->num_keys] = key;
524 sk->result = KEY_SELECTION_OK;
531 cancel_btn_cb (GtkWidget *widget, gpointer data)
533 struct select_keys_s *sk = data;
535 g_return_if_fail (sk);
537 sk->result = KEY_SELECTION_CANCEL;
539 gpgme_cancel (sk->select_ctx);
544 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
546 struct select_keys_s *sk = data;
548 g_return_if_fail (sk);
550 sk->result = KEY_SELECTION_DONT;
552 gpgme_cancel (sk->select_ctx);
557 other_btn_cb (GtkWidget *widget, gpointer data)
559 struct select_keys_s *sk = data;
562 g_return_if_fail (sk);
563 uid = input_dialog ( _("Add key"),
564 _("Enter another user or key ID:"),
568 if (fill_clist (sk, uid, sk->proto) != NULL) {
569 gpgme_release(sk->select_ctx);
570 sk->select_ctx = NULL;
572 update_progress (sk, 0, sk->pattern);
578 use_untrusted (gpgme_key_t key, gpgme_protocol_t proto)
583 if (proto != GPGME_PROTOCOL_OpenPGP)
586 buf = g_strdup_printf(_("The key of '%s' is not fully trusted.\n"
587 "If you choose to encrypt the message with this key you don't\n"
588 "know for sure that it will go to the person you mean it to.\n"
589 "Do you trust it enough to use it anyway?"), key->uids->email);
593 GTK_STOCK_NO, GTK_STOCK_YES, NULL);
595 if (aval == G_ALERTALTERNATE)
603 cmp_name (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
605 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
606 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
609 sa = a? a->uids->name : NULL;
610 sb = b? b->uids->name : NULL;
615 return g_ascii_strcasecmp(sa, sb);
619 cmp_email (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
621 gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
622 gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
625 sa = a? a->uids->email : NULL;
626 sb = b? b->uids->email : NULL;
631 return g_ascii_strcasecmp(sa, sb);
635 sort_keys ( struct select_keys_s *sk, enum col_titles column)
637 GtkCMCList *clist = sk->clist;
641 gtk_cmclist_set_compare_func (clist, cmp_name);
644 gtk_cmclist_set_compare_func (clist, cmp_email);
650 /* column clicked again: toggle as-/decending */
651 if ( sk->sort_column == column) {
652 sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
653 GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
656 sk->sort_type = GTK_SORT_ASCENDING;
658 sk->sort_column = column;
659 gtk_cmclist_set_sort_type (clist, sk->sort_type);
660 gtk_cmclist_sort (clist);
664 sort_keys_name (GtkWidget *widget, gpointer data)
666 sort_keys ((struct select_keys_s*)data, COL_NAME);
670 sort_keys_email (GtkWidget *widget, gpointer data)
672 sort_keys ((struct select_keys_s*)data, COL_EMAIL);