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 (GtkCList *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_clist_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 (GtkCList *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)
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 s = key->subkeys->keyid;
194 if (strlen (s) == 16)
195 s += 8; /* show only the short keyID */
202 if (proto == GPGME_PROTOCOL_CMS) {
203 if (strstr(s, ",CN="))
204 s = strstr(s, ",CN=")+4;
205 else if (strstr(s, "CN="))
206 s = strstr(s, "CN=")+3;
210 if (!g_utf8_validate(s, -1, NULL))
211 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
212 if (ret_str && by_written) {
217 if (proto == GPGME_PROTOCOL_CMS && (!key->uids->email || !*key->uids->email)) {
218 gpgme_user_id_t uid = key->uids->next;
222 s = key->uids->email;
224 s = key->uids->email;
228 if (!g_utf8_validate(s, -1, NULL))
229 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
230 if (ret_str && by_written) {
235 switch (key->uids->validity)
237 case GPGME_VALIDITY_UNDEFINED:
240 case GPGME_VALIDITY_NEVER:
243 case GPGME_VALIDITY_MARGINAL:
246 case GPGME_VALIDITY_FULL:
249 case GPGME_VALIDITY_ULTIMATE:
252 case GPGME_VALIDITY_UNKNOWN:
257 text[COL_VALIDITY] = s;
259 row = gtk_clist_append (clist, (gchar**)text);
262 gtk_clist_set_row_data_full (clist, row, key, destroy_key);
266 fill_clist (struct select_keys_s *sk, const char *pattern, gpgme_protocol_t proto)
274 gboolean exact_match = FALSE;
275 gpgme_key_t last_key = NULL;
276 g_return_val_if_fail (sk, NULL);
278 g_return_val_if_fail (clist, NULL);
280 debug_print ("select_keys:fill_clist: pattern '%s' proto %d\n", pattern, proto);
282 /*gtk_clist_freeze (select_keys.clist);*/
283 err = gpgme_new (&ctx);
286 gpgme_set_protocol(ctx, proto);
287 sk->select_ctx = ctx;
289 update_progress (sk, ++running, pattern);
290 while (gtk_events_pending ())
291 gtk_main_iteration ();
293 err = gpgme_op_keylist_start (ctx, pattern, 0);
295 debug_print ("** gpgme_op_keylist_start(%s) failed: %s",
296 pattern, gpgme_strerror (err));
297 sk->select_ctx = NULL;
301 update_progress (sk, ++running, pattern);
302 while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
303 gpgme_user_id_t uid = key->uids;
304 if (!key->can_encrypt)
306 debug_print ("%% %s:%d: insert\n", __FILE__ ,__LINE__ );
307 set_row (clist, key, proto );
308 for (; uid; uid = uid->next) {
309 gchar *raw_mail = NULL;
313 raw_mail = g_strdup(uid->email);
314 extract_address(raw_mail);
315 if (!strcmp(pattern, raw_mail)) {
325 update_progress (sk, ++running, pattern);
326 while (gtk_events_pending ())
327 gtk_main_iteration ();
330 if (exact_match == TRUE && num_results == 1) {
331 if (last_key->uids->validity < GPGME_VALIDITY_FULL &&
332 !use_untrusted(last_key, proto))
336 debug_print ("%% %s:%d: ready\n", __FILE__ ,__LINE__ );
337 if (gpgme_err_code(err) != GPG_ERR_EOF) {
338 debug_print ("** gpgme_op_keylist_next failed: %s",
339 gpgme_strerror (err));
340 gpgme_op_keylist_end(ctx);
342 if (!exact_match || num_results != 1) {
343 sk->select_ctx = NULL;
346 /*gtk_clist_thaw (select_keys.clist);*/
347 return (exact_match == TRUE && num_results == 1 ? last_key:NULL);
352 create_dialog (struct select_keys_s *sk)
355 GtkWidget *vbox, *vbox2, *hbox;
357 GtkWidget *scrolledwin;
360 GtkWidget *select_btn, *cancel_btn, *dont_encrypt_btn, *other_btn;
361 const char *titles[N_COL_TITLES];
363 g_assert (!sk->window);
364 window = gtkut_window_new (GTK_WINDOW_TOPLEVEL, "select-keys");
365 gtk_widget_set_size_request (window, 520, 280);
366 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
367 gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
368 gtk_window_set_modal (GTK_WINDOW (window), TRUE);
369 g_signal_connect (G_OBJECT (window), "delete_event",
370 G_CALLBACK (delete_event_cb), sk);
371 g_signal_connect (G_OBJECT (window), "key_press_event",
372 G_CALLBACK (key_pressed_cb), sk);
373 MANAGE_WINDOW_SIGNALS_CONNECT (window);
375 vbox = gtk_vbox_new (FALSE, 8);
376 gtk_container_add (GTK_CONTAINER (window), vbox);
378 hbox = gtk_hbox_new(FALSE, 4);
379 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
380 label = gtk_label_new ( "" );
381 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
383 hbox = gtk_hbox_new (FALSE, 8);
384 gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
385 gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
387 scrolledwin = gtk_scrolled_window_new (NULL, NULL);
388 gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
389 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
390 GTK_POLICY_AUTOMATIC,
391 GTK_POLICY_AUTOMATIC);
393 titles[COL_ALGO] = _("Size");
394 titles[COL_KEYID] = _("Key ID");
395 titles[COL_NAME] = _("Name");
396 titles[COL_EMAIL] = _("Address");
397 titles[COL_VALIDITY] = _("Val");
399 clist = gtk_clist_new_with_titles (N_COL_TITLES, (char**)titles);
400 gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
401 gtk_clist_set_column_width (GTK_CLIST(clist), COL_ALGO, 72);
402 gtk_clist_set_column_width (GTK_CLIST(clist), COL_KEYID, 76);
403 gtk_clist_set_column_width (GTK_CLIST(clist), COL_NAME, 130);
404 gtk_clist_set_column_width (GTK_CLIST(clist), COL_EMAIL, 130);
405 gtk_clist_set_column_width (GTK_CLIST(clist), COL_VALIDITY, 20);
406 gtk_clist_set_selection_mode (GTK_CLIST(clist), GTK_SELECTION_BROWSE);
407 g_signal_connect (G_OBJECT(GTK_CLIST(clist)->column[COL_NAME].button),
409 G_CALLBACK(sort_keys_name), sk);
410 g_signal_connect (G_OBJECT(GTK_CLIST(clist)->column[COL_EMAIL].button),
412 G_CALLBACK(sort_keys_email), sk);
414 hbox = gtk_hbox_new (FALSE, 8);
415 gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
417 gtkut_stock_button_set_create (&bbox,
418 &select_btn, _("Select"),
419 &other_btn, _("Other"),
420 &dont_encrypt_btn, _("Don't encrypt"));
422 cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
423 GTK_WIDGET_SET_FLAGS(cancel_btn, GTK_CAN_DEFAULT);
424 gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
425 gtk_widget_show(cancel_btn);
426 gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
427 gtk_widget_grab_default (select_btn);
429 g_signal_connect (G_OBJECT (select_btn), "clicked",
430 G_CALLBACK (select_btn_cb), sk);
431 g_signal_connect (G_OBJECT(cancel_btn), "clicked",
432 G_CALLBACK (cancel_btn_cb), sk);
433 g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
434 G_CALLBACK (dont_encrypt_btn_cb), sk);
435 g_signal_connect (G_OBJECT (other_btn), "clicked",
436 G_CALLBACK (other_btn_cb), sk);
438 vbox2 = gtk_vbox_new (FALSE, 4);
439 gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
442 sk->toplabel = GTK_LABEL (label);
443 sk->clist = GTK_CLIST (clist);
448 open_dialog (struct select_keys_s *sk)
452 manage_window_set_transient (GTK_WINDOW (sk->window));
454 sk->sort_column = N_COL_TITLES; /* use an invalid value */
455 sk->sort_type = GTK_SORT_ASCENDING;
460 close_dialog (struct select_keys_s *sk)
462 g_return_if_fail (sk);
463 gtk_widget_destroy (sk->window);
469 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
471 struct select_keys_s *sk = data;
481 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
483 struct select_keys_s *sk = data;
485 g_return_val_if_fail (sk, FALSE);
486 if (event && event->keyval == GDK_Escape) {
495 select_btn_cb (GtkWidget *widget, gpointer data)
497 struct select_keys_s *sk = data;
502 g_return_if_fail (sk);
503 if (!sk->clist->selection) {
504 debug_print ("** nothing selected");
507 row = GPOINTER_TO_INT(sk->clist->selection->data);
508 key = gtk_clist_get_row_data(sk->clist, row);
510 if ( key->uids->validity < GPGME_VALIDITY_FULL ) {
511 use_key = use_untrusted(key, sk->proto);
513 debug_print ("** Key untrusted, will not encrypt");
517 sk->kset = g_realloc(sk->kset,
518 sizeof(gpgme_key_t) * (sk->num_keys + 1));
520 sk->kset[sk->num_keys] = key;
523 sk->result = KEY_SELECTION_OK;
530 cancel_btn_cb (GtkWidget *widget, gpointer data)
532 struct select_keys_s *sk = data;
534 g_return_if_fail (sk);
536 sk->result = KEY_SELECTION_CANCEL;
538 gpgme_cancel (sk->select_ctx);
543 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
545 struct select_keys_s *sk = data;
547 g_return_if_fail (sk);
549 sk->result = KEY_SELECTION_DONT;
551 gpgme_cancel (sk->select_ctx);
556 other_btn_cb (GtkWidget *widget, gpointer data)
558 struct select_keys_s *sk = data;
561 g_return_if_fail (sk);
562 uid = input_dialog ( _("Add key"),
563 _("Enter another user or key ID:"),
567 if (fill_clist (sk, uid, sk->proto) != NULL) {
568 gpgme_release(sk->select_ctx);
569 sk->select_ctx = NULL;
571 update_progress (sk, 0, sk->pattern);
577 use_untrusted (gpgme_key_t key, gpgme_protocol_t proto)
582 if (proto != GPGME_PROTOCOL_OpenPGP)
585 buf = g_strdup_printf(_("The key of '%s' is not fully trusted.\n"
586 "If you choose to encrypt the message with this key you don't\n"
587 "know for sure that it will go to the person you mean it to.\n"
588 "Do you trust it enough to use it anyway?"), key->uids->email);
592 GTK_STOCK_NO, GTK_STOCK_YES, NULL);
594 if (aval == G_ALERTALTERNATE)
602 cmp_name (GtkCList *clist, gconstpointer pa, gconstpointer pb)
604 gpgme_key_t a = ((GtkCListRow *)pa)->data;
605 gpgme_key_t b = ((GtkCListRow *)pb)->data;
608 sa = a? a->uids->name : NULL;
609 sb = b? b->uids->name : NULL;
614 return g_ascii_strcasecmp(sa, sb);
618 cmp_email (GtkCList *clist, gconstpointer pa, gconstpointer pb)
620 gpgme_key_t a = ((GtkCListRow *)pa)->data;
621 gpgme_key_t b = ((GtkCListRow *)pb)->data;
624 sa = a? a->uids->email : NULL;
625 sb = b? b->uids->email : NULL;
630 return g_ascii_strcasecmp(sa, sb);
634 sort_keys ( struct select_keys_s *sk, enum col_titles column)
636 GtkCList *clist = sk->clist;
640 gtk_clist_set_compare_func (clist, cmp_name);
643 gtk_clist_set_compare_func (clist, cmp_email);
649 /* column clicked again: toggle as-/decending */
650 if ( sk->sort_column == column) {
651 sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
652 GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
655 sk->sort_type = GTK_SORT_ASCENDING;
657 sk->sort_column = column;
658 gtk_clist_set_sort_type (clist, sk->sort_type);
659 gtk_clist_sort (clist);
663 sort_keys_name (GtkWidget *widget, gpointer data)
665 sort_keys ((struct select_keys_s*)data, COL_NAME);
669 sort_keys_email (GtkWidget *widget, gpointer data)
671 sort_keys ((struct select_keys_s*)data, COL_EMAIL);