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>
30 #include <gtk/gtkmain.h>
31 #include <gtk/gtkwidget.h>
32 #include <gtk/gtkwindow.h>
33 #include <gtk/gtkscrolledwindow.h>
34 #include <gtk/gtkvbox.h>
35 #include <gtk/gtkhbox.h>
36 #include <gtk/gtkclist.h>
37 #include <gtk/gtklabel.h>
38 #include <gtk/gtkentry.h>
39 #include <gtk/gtkhbbox.h>
40 #include <gtk/gtkbutton.h>
41 #include <gtk/gtkstock.h>
43 #include "select-keys.h"
46 #include "inputdialog.h"
47 #include "manage_window.h"
48 #include "alertpanel.h"
50 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
51 #define DIMof(type,member) DIM(((type *)0)->member)
64 struct select_keys_s {
70 unsigned int num_keys;
72 gpgme_ctx_t select_ctx;
73 gpgme_protocol_t proto;
74 GtkSortType sort_type;
75 enum col_titles sort_column;
76 SelectionResult result;
80 static void set_row (GtkCList *clist, gpgme_key_t key, gpgme_protocol_t proto);
81 static gpgme_key_t fill_clist (struct select_keys_s *sk, const char *pattern,
82 gpgme_protocol_t proto);
83 static void create_dialog (struct select_keys_s *sk);
84 static void open_dialog (struct select_keys_s *sk);
85 static void close_dialog (struct select_keys_s *sk);
86 static gint delete_event_cb (GtkWidget *widget,
87 GdkEventAny *event, gpointer data);
88 static gboolean key_pressed_cb (GtkWidget *widget,
89 GdkEventKey *event, gpointer data);
90 static void select_btn_cb (GtkWidget *widget, gpointer data);
91 static void cancel_btn_cb (GtkWidget *widget, gpointer data);
92 static void dont_encrypt_btn_cb (GtkWidget *widget, gpointer data);
93 static void other_btn_cb (GtkWidget *widget, gpointer data);
94 static void sort_keys (struct select_keys_s *sk, enum col_titles column);
95 static void sort_keys_name (GtkWidget *widget, gpointer data);
96 static void sort_keys_email (GtkWidget *widget, gpointer data);
98 static gboolean use_untrusted (gpgme_key_t, gpgme_protocol_t proto);
101 update_progress (struct select_keys_s *sk, int running, const char *pattern)
103 static int windmill[] = { '-', '\\', '|', '/' };
107 buf = g_strdup_printf (_("No exact match for '%s'; please select the key."),
110 buf = g_strdup_printf (_("Collecting info for '%s' ... %c"),
112 windmill[running%DIM(windmill)]);
113 gtk_label_set_text (sk->toplabel, buf);
119 * gpgmegtk_recipient_selection:
120 * @recp_names: A list of email addresses
122 * Select a list of recipients from a given list of email addresses.
123 * This may pop up a window to present the user a choice, it will also
124 * check that the recipients key are all valid.
126 * Return value: NULL on error or a list of list of recipients.
129 gpgmegtk_recipient_selection (GSList *recp_names, SelectionResult *result,
130 gpgme_protocol_t proto)
132 struct select_keys_s sk;
133 gpgme_key_t key = NULL;
134 memset (&sk, 0, sizeof sk);
139 sk.pattern = recp_names? recp_names->data:NULL;
141 gtk_clist_clear (sk.clist);
142 key = fill_clist (&sk, sk.pattern, proto);
143 update_progress (&sk, 0, sk.pattern);
145 gtk_widget_show_all (sk.window);
148 gtk_widget_hide (sk.window);
149 sk.kset = g_realloc(sk.kset,
150 sizeof(gpgme_key_t) * (sk.num_keys + 1));
152 sk.kset[sk.num_keys] = key;
155 sk.result = KEY_SELECTION_OK;
156 gpgme_release (sk.select_ctx);
157 sk.select_ctx = NULL;
158 debug_print("used %s\n", key->uids->email);
162 recp_names = recp_names->next;
163 } while (sk.okay && recp_names);
171 sk.kset = g_realloc(sk.kset, sizeof(gpgme_key_t) * (sk.num_keys + 1));
172 sk.kset[sk.num_keys] = NULL;
180 destroy_key (gpointer data)
182 gpgme_key_t key = data;
183 gpgme_key_release (key);
187 set_row (GtkCList *clist, gpgme_key_t key, gpgme_protocol_t proto)
190 const char *text[N_COL_TITLES];
193 gsize by_read = 0, by_written = 0;
194 gchar *ret_str = NULL;
196 /* first check whether the key is capable of encryption which is not
197 * the case for revoked, expired or sign-only keys */
198 if (!key->can_encrypt)
200 algo_buf = g_strdup_printf ("%du/%s",
201 key->subkeys->length,
202 gpgme_pubkey_algo_name(key->subkeys->pubkey_algo) );
203 text[COL_ALGO] = algo_buf;
205 s = key->subkeys->keyid;
206 if (strlen (s) == 16)
207 s += 8; /* show only the short keyID */
214 if (proto == GPGME_PROTOCOL_CMS) {
215 if (strstr(s, ",CN="))
216 s = strstr(s, ",CN=")+4;
217 else if (strstr(s, "CN="))
218 s = strstr(s, "CN=")+3;
222 if (!g_utf8_validate(s, -1, NULL))
223 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
224 if (ret_str && by_written) {
229 if (proto == GPGME_PROTOCOL_CMS && (!key->uids->email || !*key->uids->email)) {
230 gpgme_user_id_t uid = key->uids->next;
234 s = key->uids->email;
236 s = key->uids->email;
240 if (!g_utf8_validate(s, -1, NULL))
241 ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
242 if (ret_str && by_written) {
247 switch (key->uids->validity)
249 case GPGME_VALIDITY_UNDEFINED:
252 case GPGME_VALIDITY_NEVER:
255 case GPGME_VALIDITY_MARGINAL:
258 case GPGME_VALIDITY_FULL:
261 case GPGME_VALIDITY_ULTIMATE:
264 case GPGME_VALIDITY_UNKNOWN:
269 text[COL_VALIDITY] = s;
271 row = gtk_clist_append (clist, (gchar**)text);
274 gtk_clist_set_row_data_full (clist, row, key, destroy_key);
278 fill_clist (struct select_keys_s *sk, const char *pattern, gpgme_protocol_t proto)
286 gboolean exact_match = FALSE;
287 gpgme_key_t last_key = NULL;
288 g_return_val_if_fail (sk, NULL);
290 g_return_val_if_fail (clist, NULL);
292 debug_print ("select_keys:fill_clist: pattern '%s' proto %d\n", pattern, proto);
294 /*gtk_clist_freeze (select_keys.clist);*/
295 err = gpgme_new (&ctx);
298 gpgme_set_protocol(ctx, proto);
299 sk->select_ctx = ctx;
301 update_progress (sk, ++running, pattern);
302 while (gtk_events_pending ())
303 gtk_main_iteration ();
305 err = gpgme_op_keylist_start (ctx, pattern, 0);
307 debug_print ("** gpgme_op_keylist_start(%s) failed: %s",
308 pattern, gpgme_strerror (err));
309 sk->select_ctx = NULL;
313 update_progress (sk, ++running, pattern);
314 while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
315 gpgme_user_id_t uid = key->uids;
316 debug_print ("%% %s:%d: insert\n", __FILE__ ,__LINE__ );
317 set_row (clist, key, proto );
318 for (; uid; uid = uid->next) {
319 gchar *raw_mail = NULL;
322 raw_mail = g_strdup(uid->email);
323 extract_address(raw_mail);
324 if (!strcmp(pattern, raw_mail)) {
334 update_progress (sk, ++running, pattern);
335 while (gtk_events_pending ())
336 gtk_main_iteration ();
339 if (exact_match == TRUE && num_results == 1) {
340 if (last_key->uids->validity < GPGME_VALIDITY_FULL &&
341 !use_untrusted(last_key, proto))
345 debug_print ("%% %s:%d: ready\n", __FILE__ ,__LINE__ );
346 if (gpgme_err_code(err) != GPG_ERR_EOF) {
347 debug_print ("** gpgme_op_keylist_next failed: %s",
348 gpgme_strerror (err));
349 gpgme_op_keylist_end(ctx);
351 if (!exact_match || num_results != 1) {
352 sk->select_ctx = NULL;
355 /*gtk_clist_thaw (select_keys.clist);*/
356 return (exact_match == TRUE && num_results == 1 ? last_key:NULL);
361 create_dialog (struct select_keys_s *sk)
364 GtkWidget *vbox, *vbox2, *hbox;
366 GtkWidget *scrolledwin;
369 GtkWidget *select_btn, *cancel_btn, *dont_encrypt_btn, *other_btn;
370 const char *titles[N_COL_TITLES];
372 g_assert (!sk->window);
373 window = gtkut_window_new (GTK_WINDOW_TOPLEVEL, "select-keys");
374 gtk_widget_set_size_request (window, 520, 280);
375 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
376 gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
377 gtk_window_set_modal (GTK_WINDOW (window), TRUE);
378 g_signal_connect (G_OBJECT (window), "delete_event",
379 G_CALLBACK (delete_event_cb), sk);
380 g_signal_connect (G_OBJECT (window), "key_press_event",
381 G_CALLBACK (key_pressed_cb), sk);
382 MANAGE_WINDOW_SIGNALS_CONNECT (window);
384 vbox = gtk_vbox_new (FALSE, 8);
385 gtk_container_add (GTK_CONTAINER (window), vbox);
387 hbox = gtk_hbox_new(FALSE, 4);
388 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
389 label = gtk_label_new ( "" );
390 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
392 hbox = gtk_hbox_new (FALSE, 8);
393 gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
394 gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
396 scrolledwin = gtk_scrolled_window_new (NULL, NULL);
397 gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
398 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
399 GTK_POLICY_AUTOMATIC,
400 GTK_POLICY_AUTOMATIC);
402 titles[COL_ALGO] = _("Size");
403 titles[COL_KEYID] = _("Key ID");
404 titles[COL_NAME] = _("Name");
405 titles[COL_EMAIL] = _("Address");
406 titles[COL_VALIDITY] = _("Val");
408 clist = gtk_clist_new_with_titles (N_COL_TITLES, (char**)titles);
409 gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
410 gtk_clist_set_column_width (GTK_CLIST(clist), COL_ALGO, 72);
411 gtk_clist_set_column_width (GTK_CLIST(clist), COL_KEYID, 76);
412 gtk_clist_set_column_width (GTK_CLIST(clist), COL_NAME, 130);
413 gtk_clist_set_column_width (GTK_CLIST(clist), COL_EMAIL, 130);
414 gtk_clist_set_column_width (GTK_CLIST(clist), COL_VALIDITY, 20);
415 gtk_clist_set_selection_mode (GTK_CLIST(clist), GTK_SELECTION_BROWSE);
416 g_signal_connect (G_OBJECT(GTK_CLIST(clist)->column[COL_NAME].button),
418 G_CALLBACK(sort_keys_name), sk);
419 g_signal_connect (G_OBJECT(GTK_CLIST(clist)->column[COL_EMAIL].button),
421 G_CALLBACK(sort_keys_email), sk);
423 hbox = gtk_hbox_new (FALSE, 8);
424 gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
426 gtkut_stock_button_set_create (&bbox,
427 &select_btn, _("Select"),
428 &other_btn, _("Other"),
429 &dont_encrypt_btn, _("Don't encrypt"));
431 cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
432 GTK_WIDGET_SET_FLAGS(cancel_btn, GTK_CAN_DEFAULT);
433 gtk_box_pack_start(GTK_BOX(bbox), cancel_btn, TRUE, TRUE, 0);
434 gtk_widget_show(cancel_btn);
435 gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
436 gtk_widget_grab_default (select_btn);
438 g_signal_connect (G_OBJECT (select_btn), "clicked",
439 G_CALLBACK (select_btn_cb), sk);
440 g_signal_connect (G_OBJECT(cancel_btn), "clicked",
441 G_CALLBACK (cancel_btn_cb), sk);
442 g_signal_connect (G_OBJECT(dont_encrypt_btn), "clicked",
443 G_CALLBACK (dont_encrypt_btn_cb), sk);
444 g_signal_connect (G_OBJECT (other_btn), "clicked",
445 G_CALLBACK (other_btn_cb), sk);
447 vbox2 = gtk_vbox_new (FALSE, 4);
448 gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
451 sk->toplabel = GTK_LABEL (label);
452 sk->clist = GTK_CLIST (clist);
457 open_dialog (struct select_keys_s *sk)
461 manage_window_set_transient (GTK_WINDOW (sk->window));
463 sk->sort_column = N_COL_TITLES; /* use an invalid value */
464 sk->sort_type = GTK_SORT_ASCENDING;
469 close_dialog (struct select_keys_s *sk)
471 g_return_if_fail (sk);
472 gtk_widget_destroy (sk->window);
478 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
480 struct select_keys_s *sk = data;
490 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
492 struct select_keys_s *sk = data;
494 g_return_val_if_fail (sk, FALSE);
495 if (event && event->keyval == GDK_Escape) {
504 select_btn_cb (GtkWidget *widget, gpointer data)
506 struct select_keys_s *sk = data;
511 g_return_if_fail (sk);
512 if (!sk->clist->selection) {
513 debug_print ("** nothing selected");
516 row = GPOINTER_TO_INT(sk->clist->selection->data);
517 key = gtk_clist_get_row_data(sk->clist, row);
519 if ( key->uids->validity < GPGME_VALIDITY_FULL ) {
520 use_key = use_untrusted(key, sk->proto);
522 debug_print ("** Key untrusted, will not encrypt");
526 sk->kset = g_realloc(sk->kset,
527 sizeof(gpgme_key_t) * (sk->num_keys + 1));
529 sk->kset[sk->num_keys] = key;
532 sk->result = KEY_SELECTION_OK;
539 cancel_btn_cb (GtkWidget *widget, gpointer data)
541 struct select_keys_s *sk = data;
543 g_return_if_fail (sk);
545 sk->result = KEY_SELECTION_CANCEL;
547 gpgme_cancel (sk->select_ctx);
552 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
554 struct select_keys_s *sk = data;
556 g_return_if_fail (sk);
558 sk->result = KEY_SELECTION_DONT;
560 gpgme_cancel (sk->select_ctx);
565 other_btn_cb (GtkWidget *widget, gpointer data)
567 struct select_keys_s *sk = data;
570 g_return_if_fail (sk);
571 uid = input_dialog ( _("Add key"),
572 _("Enter another user or key ID:"),
576 if (fill_clist (sk, uid, sk->proto) != NULL) {
577 gpgme_release(sk->select_ctx);
578 sk->select_ctx = NULL;
580 update_progress (sk, 0, sk->pattern);
586 use_untrusted (gpgme_key_t key, gpgme_protocol_t proto)
591 if (proto != GPGME_PROTOCOL_OpenPGP)
594 buf = g_strdup_printf(_("The key of '%s' is not fully trusted.\n"
595 "If you choose to encrypt the message with this key you don't\n"
596 "know for sure that it will go to the person you mean it to.\n"
597 "Do you trust it enough to use it anyway?"), key->uids->email);
601 GTK_STOCK_NO, GTK_STOCK_YES, NULL);
603 if (aval == G_ALERTALTERNATE)
611 cmp_name (GtkCList *clist, gconstpointer pa, gconstpointer pb)
613 gpgme_key_t a = ((GtkCListRow *)pa)->data;
614 gpgme_key_t b = ((GtkCListRow *)pb)->data;
617 sa = a? a->uids->name : NULL;
618 sb = b? b->uids->name : NULL;
623 return g_ascii_strcasecmp(sa, sb);
627 cmp_email (GtkCList *clist, gconstpointer pa, gconstpointer pb)
629 gpgme_key_t a = ((GtkCListRow *)pa)->data;
630 gpgme_key_t b = ((GtkCListRow *)pb)->data;
633 sa = a? a->uids->email : NULL;
634 sb = b? b->uids->email : NULL;
639 return g_ascii_strcasecmp(sa, sb);
643 sort_keys ( struct select_keys_s *sk, enum col_titles column)
645 GtkCList *clist = sk->clist;
649 gtk_clist_set_compare_func (clist, cmp_name);
652 gtk_clist_set_compare_func (clist, cmp_email);
658 /* column clicked again: toggle as-/decending */
659 if ( sk->sort_column == column) {
660 sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
661 GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
664 sk->sort_type = GTK_SORT_ASCENDING;
666 sk->sort_column = column;
667 gtk_clist_set_sort_type (clist, sk->sort_type);
668 gtk_clist_sort (clist);
672 sort_keys_name (GtkWidget *widget, gpointer data)
674 sort_keys ((struct select_keys_s*)data, COL_NAME);
678 sort_keys_email (GtkWidget *widget, gpointer data)
680 sort_keys ((struct select_keys_s*)data, COL_EMAIL);