Fix a few memory leaks when enumerating keys with GpgME.
[claws.git] / src / plugins / pgpcore / select-keys.c
1 /* select-keys.c - GTK+ based key selection
2  * Copyright (C) 2001-2016 Werner Koch (dd9jn) and the Claws Mail team
3  *
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.
8  *
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.
13  *
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/>.
16  */
17
18 #ifdef HAVE_CONFIG_H
19 #  include <config.h>
20 #endif
21
22 #ifdef USE_GPGME
23 #include <stdio.h>
24 #include <stdlib.h>
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <gtk/gtk.h>
30 #include "select-keys.h"
31 #include "utils.h"
32 #include "gtkutils.h"
33 #include "inputdialog.h"
34 #include "manage_window.h"
35 #include "alertpanel.h"
36
37 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
38 #define DIMof(type,member)   DIM(((type *)0)->member)
39
40
41 enum col_titles { 
42     COL_ALGO,
43     COL_KEYID,
44     COL_NAME,
45     COL_EMAIL,
46     COL_VALIDITY,
47
48     N_COL_TITLES
49 };
50
51 struct select_keys_s {
52     int okay;
53     GtkWidget *window;
54     GtkLabel *toplabel;
55     GtkCMCList *clist;
56     const char *pattern;
57     unsigned int num_keys;
58     gpgme_key_t *kset;
59     gpgme_ctx_t select_ctx;
60     gpgme_protocol_t proto;
61     GtkSortType sort_type;
62     enum col_titles sort_column;
63     SelectionResult result;
64 };
65
66
67 static void set_row (GtkCMCList *clist, gpgme_key_t key, gpgme_protocol_t proto);
68 static gpgme_key_t fill_clist (struct select_keys_s *sk, const char *pattern,
69                         gpgme_protocol_t proto);
70 static void create_dialog (struct select_keys_s *sk);
71 static void open_dialog (struct select_keys_s *sk);
72 static void close_dialog (struct select_keys_s *sk);
73 static gint delete_event_cb (GtkWidget *widget,
74                              GdkEventAny *event, gpointer data);
75 static gboolean key_pressed_cb (GtkWidget *widget,
76                                 GdkEventKey *event, gpointer data);
77 static void select_btn_cb (GtkWidget *widget, gpointer data);
78 static void cancel_btn_cb (GtkWidget *widget, gpointer data);
79 static void dont_encrypt_btn_cb (GtkWidget *widget, gpointer data);
80 static void other_btn_cb (GtkWidget *widget, gpointer data);
81 static void sort_keys (struct select_keys_s *sk, enum col_titles column);
82 static void sort_keys_name (GtkWidget *widget, gpointer data);
83 static void sort_keys_email (GtkWidget *widget, gpointer data);
84
85 static gboolean use_untrusted (gpgme_key_t, gpgme_user_id_t uid, gpgme_protocol_t proto);
86
87 static void
88 update_progress (struct select_keys_s *sk, int running, const char *pattern)
89 {
90     static int windmill[] = { '-', '\\', '|', '/' };
91     char *buf;
92
93     if (!running)
94         buf = g_strdup_printf (_("No exact match for '%s'; please select the key."),
95                                pattern);
96     else 
97         buf = g_strdup_printf (_("Collecting info for '%s' ... %c"),
98                                pattern,
99                                windmill[running%DIM(windmill)]);
100     gtk_label_set_text (sk->toplabel, buf);
101     g_free (buf);
102 }
103
104
105 /**
106  * gpgmegtk_recipient_selection:
107  * @recp_names: A list of email addresses
108  * 
109  * Select a list of recipients from a given list of email addresses.
110  * This may pop up a window to present the user a choice, it will also
111  * check that the recipients key are all valid.
112  * 
113  * Return value: NULL on error or a list of list of recipients.
114  **/
115 gpgme_key_t *
116 gpgmegtk_recipient_selection (GSList *recp_names, SelectionResult *result,
117                                 gpgme_protocol_t proto)
118 {
119     struct select_keys_s sk;
120     gpgme_key_t key = NULL;
121     memset (&sk, 0, sizeof sk);
122
123     open_dialog (&sk);
124
125     do {
126         sk.pattern = recp_names? recp_names->data:NULL;
127         sk.proto = proto;
128         gtk_cmclist_clear (sk.clist);
129         key = fill_clist (&sk, sk.pattern, proto);
130         update_progress (&sk, 0, sk.pattern ? sk.pattern : "NULL");
131         if (!key) {
132                 gtk_widget_show_all (sk.window);
133                 gtk_main ();
134         } else {
135                 gtk_widget_hide (sk.window);
136                 sk.kset = g_realloc(sk.kset,
137                         sizeof(gpgme_key_t) * (sk.num_keys + 1));
138                 gpgme_key_ref(key);
139                 sk.kset[sk.num_keys] = key;
140                 sk.num_keys++;
141                 sk.okay = 1;
142                 sk.result = KEY_SELECTION_OK;
143                 gpgme_release (sk.select_ctx);
144                 sk.select_ctx = NULL;
145                 debug_print("used %s\n", key->uids->email);
146         }
147         key = NULL;
148         if (recp_names)
149             recp_names = recp_names->next;
150     } while (sk.okay && recp_names);
151
152     close_dialog (&sk);
153
154     if (!sk.okay) {
155         g_free(sk.kset);
156         sk.kset = NULL;
157     } else {
158         sk.kset = g_realloc(sk.kset, sizeof(gpgme_key_t) * (sk.num_keys + 1));
159         sk.kset[sk.num_keys] = NULL;
160     }
161     if (result)
162             *result = sk.result;
163     return sk.kset;
164
165
166 static void
167 destroy_key (gpointer data)
168 {
169     gpgme_key_t key = data;
170     gpgme_key_unref (key);
171 }
172
173 static void
174 set_row (GtkCMCList *clist, gpgme_key_t key, gpgme_protocol_t proto)
175 {
176     const char *s;
177     const char *text[N_COL_TITLES];
178     char *algo_buf;
179     int row;
180     gsize by_read = 0, by_written = 0;
181     gchar *ret_str = NULL;
182
183     /* first check whether the key is capable of encryption which is not
184      * the case for revoked, expired or sign-only keys */
185     if (!key->can_encrypt || key->revoked || key->expired || key->disabled)
186         return;
187
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;
192
193     text[COL_KEYID] = key->subkeys->keyid;
194
195     s = key->uids->name;
196     if (!s || !*s)
197         s = key->uids->uid;
198     if (proto == GPGME_PROTOCOL_CMS) {
199         if (strstr(s, ",CN="))
200                 s = strstr(s, ",CN=")+4;
201         else if (strstr(s, "CN="))
202                 s = strstr(s, "CN=")+3;
203     } 
204     
205     ret_str = NULL;
206     if (!g_utf8_validate(s, -1, NULL))
207             ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
208     if (ret_str && by_written) {
209         s = ret_str;
210     }
211     text[COL_NAME] = s;
212
213     if (proto == GPGME_PROTOCOL_CMS && (!key->uids->email || !*key->uids->email)) {
214         gpgme_user_id_t uid = key->uids->next;
215         if (uid)
216                 s = uid->email;
217         else
218                 s = key->uids->email;
219     } else {
220         s = key->uids->email;
221     }
222     
223     ret_str = NULL;
224     if (!g_utf8_validate(s, -1, NULL))
225             ret_str = g_locale_to_utf8 (s, strlen(s), &by_read, &by_written, NULL);
226     if (ret_str && by_written) {
227         s = ret_str;
228     }
229     text[COL_EMAIL] = s;
230
231     switch (key->uids->validity)
232       {
233       case GPGME_VALIDITY_UNDEFINED:
234         s = _("Undefined");
235         break;
236       case GPGME_VALIDITY_NEVER:
237         s = _("Never");
238         break;
239       case GPGME_VALIDITY_MARGINAL:
240         s = _("Marginal");
241         break;
242       case GPGME_VALIDITY_FULL:
243         s = _("Full");
244         break;
245       case GPGME_VALIDITY_ULTIMATE:
246         s = _("Ultimate");
247         break;
248       case GPGME_VALIDITY_UNKNOWN:
249       default:
250         s = _("Unknown");
251         break;
252       }
253     text[COL_VALIDITY] = s;
254
255     row = gtk_cmclist_append (clist, (gchar**)text);
256     g_free (algo_buf);
257
258     gtk_cmclist_set_row_data_full (clist, row, key, destroy_key);
259 }
260
261 static gpgme_key_t 
262 fill_clist (struct select_keys_s *sk, const char *pattern, gpgme_protocol_t proto)
263 {
264     GtkCMCList *clist;
265     gpgme_ctx_t ctx;
266     gpgme_error_t err;
267     gpgme_key_t key;
268     int running=0;
269     int num_results = 0;
270     gboolean exact_match = FALSE;
271     gpgme_key_t last_key = NULL;
272     gpgme_user_id_t last_uid = NULL;
273     cm_return_val_if_fail (sk, NULL);
274     clist = sk->clist;
275     cm_return_val_if_fail (clist, NULL);
276
277     debug_print ("select_keys:fill_clist:  pattern '%s' proto %d\n", pattern != NULL ? pattern : "NULL", proto);
278
279     /*gtk_cmclist_freeze (select_keys.clist);*/
280     err = gpgme_new (&ctx);
281     g_assert (!err);
282
283     gpgme_set_protocol(ctx, proto);
284     sk->select_ctx = ctx;
285
286     update_progress (sk, ++running, pattern);
287     while (gtk_events_pending ())
288         gtk_main_iteration ();
289
290     err = gpgme_op_keylist_start (ctx, pattern, 0);
291     if (err) {
292         debug_print ("** gpgme_op_keylist_start(%s) failed: %s\n",
293                      pattern != NULL ? pattern : "NULL", gpgme_strerror (err));
294         sk->select_ctx = NULL;
295         gpgme_release(ctx);
296         return NULL;
297     }
298     update_progress (sk, ++running, pattern);
299     while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
300         gpgme_user_id_t uid = key->uids;
301         if (!key->can_encrypt || key->revoked || key->expired || key->disabled)
302                 continue;
303         debug_print ("%% %s:%d:  insert\n", __FILE__ ,__LINE__ );
304         set_row (clist, key, proto ); 
305         for (; uid; uid = uid->next) {
306                 gchar *raw_mail = NULL;
307
308                 if (!uid->email)
309                         continue;
310                 if (uid->revoked || uid->invalid)
311                         continue;
312                 raw_mail = g_strdup(uid->email);
313                 extract_address(raw_mail);
314                 if (pattern != NULL && !strcasecmp(pattern, raw_mail)) {
315                         exact_match = TRUE;
316                         last_uid = uid;
317                         g_free(raw_mail);
318                         break;
319                 }
320                 g_free(raw_mail);
321         }
322         num_results++;
323         if (last_key != NULL)
324                 gpgme_key_unref(last_key);
325         last_key = key;
326         key = NULL;
327         update_progress (sk, ++running, pattern);
328         while (gtk_events_pending ())
329             gtk_main_iteration ();
330     }
331  
332     if (exact_match == TRUE && num_results == 1) {
333             if (last_key->uids->validity < GPGME_VALIDITY_FULL && 
334                 !use_untrusted(last_key, last_uid, proto))
335                     exact_match = FALSE;
336     }
337
338     debug_print ("%% %s:%d:  ready\n", __FILE__ ,__LINE__ );
339     if (gpgme_err_code(err) != GPG_ERR_EOF) {
340         debug_print ("** gpgme_op_keylist_next failed: %s\n",
341                      gpgme_strerror (err));
342         gpgme_op_keylist_end(ctx);
343     }
344     if (!exact_match || num_results != 1) {
345             sk->select_ctx = NULL;
346             gpgme_release (ctx);
347     }
348     /*gtk_cmclist_thaw (select_keys.clist);*/
349     if (exact_match && num_results == 1)
350             return last_key;
351
352     gpgme_key_unref(last_key);
353     return NULL;
354 }
355
356
357 static void 
358 create_dialog (struct select_keys_s *sk)
359 {
360     GtkWidget *window;
361     GtkWidget *vbox, *vbox2, *hbox;
362     GtkWidget *bbox;
363     GtkWidget *scrolledwin;
364     GtkWidget *clist;
365     GtkWidget *label;
366     GtkWidget *select_btn, *cancel_btn, *dont_encrypt_btn, *other_btn;
367     const char *titles[N_COL_TITLES];
368
369     g_assert (!sk->window);
370     window = gtkut_window_new (GTK_WINDOW_TOPLEVEL, "select-keys");
371     gtk_widget_set_size_request (window, 560, 280);
372     gtk_container_set_border_width (GTK_CONTAINER (window), 8);
373     gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
374     gtk_window_set_modal (GTK_WINDOW (window), TRUE);
375     g_signal_connect (G_OBJECT (window), "delete_event",
376                       G_CALLBACK (delete_event_cb), sk);
377     g_signal_connect (G_OBJECT (window), "key_press_event",
378                       G_CALLBACK (key_pressed_cb), sk);
379     MANAGE_WINDOW_SIGNALS_CONNECT (window);
380
381     vbox = gtk_vbox_new (FALSE, 8);
382     gtk_container_add (GTK_CONTAINER (window), vbox);
383
384     hbox  = gtk_hbox_new(FALSE, 4);
385     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
386     label = gtk_label_new ( "" );
387     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
388
389     hbox = gtk_hbox_new (FALSE, 8);
390     gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
391     gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
392
393     scrolledwin = gtk_scrolled_window_new (NULL, NULL);
394     gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
395     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
396                                     GTK_POLICY_AUTOMATIC,
397                                     GTK_POLICY_AUTOMATIC);
398
399     titles[COL_ALGO]     = _("Size");
400     titles[COL_KEYID]    = _("Key ID");
401     titles[COL_NAME]     = _("Name");
402     titles[COL_EMAIL]    = _("Address");
403     titles[COL_VALIDITY] = _("Trust");
404
405     clist = gtk_cmclist_new_with_titles (N_COL_TITLES, (char**)titles);
406     gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
407     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_ALGO,      70);
408     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_KEYID,    120);
409     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_NAME,     115);
410     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_EMAIL,    140);
411     gtk_cmclist_set_column_width (GTK_CMCLIST(clist), COL_VALIDITY,  20);
412     gtk_cmclist_set_selection_mode (GTK_CMCLIST(clist), GTK_SELECTION_BROWSE);
413     g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_NAME].button),
414                       "clicked",
415                       G_CALLBACK(sort_keys_name), sk);
416     g_signal_connect (G_OBJECT(GTK_CMCLIST(clist)->column[COL_EMAIL].button),
417                       "clicked",
418                       G_CALLBACK(sort_keys_email), sk);
419
420     hbox = gtk_hbox_new (FALSE, 8);
421     gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
422
423     /* TRANSLATORS: check that the accelerators in _Select, _Other and
424      * Do_n't encrypt are different than the one in the stock Cancel
425      * button */
426     gtkut_stock_button_set_create (&bbox, 
427                                    &select_btn, _("_Select"),
428                                    &other_btn, _("_Other"),
429                                    &dont_encrypt_btn, _("Do_n't encrypt"));
430     
431     cancel_btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
432     gtkut_widget_set_can_default(cancel_btn, TRUE);
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);
437
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);
446
447     vbox2 = gtk_vbox_new (FALSE, 4);
448     gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
449
450     sk->window = window;
451     sk->toplabel = GTK_LABEL (label);
452     sk->clist  = GTK_CMCLIST (clist);
453 }
454
455
456 static void
457 open_dialog (struct select_keys_s *sk)
458 {
459     if (!sk->window)
460         create_dialog (sk);
461     manage_window_set_transient (GTK_WINDOW (sk->window));
462     sk->okay = 0;
463     sk->sort_column = N_COL_TITLES; /* use an invalid value */
464     sk->sort_type = GTK_SORT_ASCENDING;
465 }
466
467
468 static void
469 close_dialog (struct select_keys_s *sk)
470 {
471     cm_return_if_fail (sk);
472     gtk_widget_destroy (sk->window);
473     sk->window = NULL;
474 }
475
476
477 static gint
478 delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
479 {
480     struct select_keys_s *sk = data;
481
482     sk->okay = 0;
483     gtk_main_quit ();
484
485     return TRUE;
486 }
487
488
489 static gboolean
490 key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
491 {
492     struct select_keys_s *sk = data;
493
494     cm_return_val_if_fail (sk, FALSE);
495     if (event && event->keyval == GDK_KEY_Escape) {
496         sk->okay = 0;
497         gtk_main_quit ();
498     }
499     return FALSE;
500 }
501
502
503 static void 
504 select_btn_cb (GtkWidget *widget, gpointer data)
505 {
506     struct select_keys_s *sk = data;
507     int row;
508     gboolean use_key;
509     gpgme_key_t key;
510
511     cm_return_if_fail (sk);
512     if (!sk->clist->selection) {
513         debug_print ("** nothing selected\n");
514         return;
515     }
516     row = GPOINTER_TO_INT(sk->clist->selection->data);
517     key = gtk_cmclist_get_row_data(sk->clist, row);
518     if (key) {
519         gpgme_user_id_t uid;
520         for (uid = key->uids; uid; uid = uid->next) {
521                 gchar *raw_mail = NULL;
522
523                 if (!uid->email)
524                         continue;
525                 raw_mail = g_strdup(uid->email);
526                 extract_address(raw_mail);
527                 if (sk->pattern && !strcasecmp(sk->pattern, raw_mail)) {
528                         g_free(raw_mail);
529                         break;
530                 }
531                 g_free(raw_mail);
532         }
533         if (!uid)
534                 uid = key->uids;
535
536         if ( uid->validity < GPGME_VALIDITY_FULL ) {
537             use_key = use_untrusted(key, uid, sk->proto);
538             if (!use_key) {
539                 debug_print ("** Key untrusted, will not encrypt\n");
540                 return;
541             }
542         }
543         sk->kset = g_realloc(sk->kset,
544                 sizeof(gpgme_key_t) * (sk->num_keys + 1));
545         gpgme_key_ref(key);
546         sk->kset[sk->num_keys] = key;
547         sk->num_keys++;
548         sk->okay = 1;
549         sk->result = KEY_SELECTION_OK;
550         gtk_main_quit ();
551     }
552 }
553
554
555 static void 
556 cancel_btn_cb (GtkWidget *widget, gpointer data)
557 {
558     struct select_keys_s *sk = data;
559
560     cm_return_if_fail (sk);
561     sk->okay = 0;
562     sk->result = KEY_SELECTION_CANCEL;
563     if (sk->select_ctx)
564         gpgme_cancel (sk->select_ctx);
565     gtk_main_quit ();
566 }
567
568 static void 
569 dont_encrypt_btn_cb (GtkWidget *widget, gpointer data)
570 {
571     struct select_keys_s *sk = data;
572
573     cm_return_if_fail (sk);
574     sk->okay = 0;
575     sk->result = KEY_SELECTION_DONT;
576     if (sk->select_ctx)
577         gpgme_cancel (sk->select_ctx);
578     gtk_main_quit ();
579 }
580
581 static void
582 other_btn_cb (GtkWidget *widget, gpointer data)
583 {
584     struct select_keys_s *sk = data;
585     char *uid;
586
587     cm_return_if_fail (sk);
588     uid = input_dialog ( _("Add key"),
589                          _("Enter another user or key ID:"),
590                          NULL );
591     if (!uid)
592         return;
593     if (fill_clist (sk, uid, sk->proto) != NULL) {
594             gpgme_release(sk->select_ctx);
595             sk->select_ctx = NULL;
596     }
597     update_progress (sk, 0, sk->pattern);
598     g_free (uid);
599 }
600
601
602 static gboolean
603 use_untrusted (gpgme_key_t key, gpgme_user_id_t uid, gpgme_protocol_t proto)
604 {
605     AlertValue aval;
606     gchar *buf = NULL;
607     gchar *title = NULL;
608     if (proto != GPGME_PROTOCOL_OpenPGP)
609         return TRUE;
610
611     title = g_strdup_printf(_("Encrypt to %s <%s>"), uid->name, uid->email);
612     buf = g_strdup_printf(_("This encryption key is not fully trusted.\n"
613                "If you choose to encrypt the message with this key, you don't\n"
614                "know for sure that it will go to the person you mean it to.\n\n"
615                "Key details: ID %s, primary identity %s <%s>\n\n"
616                "Do you trust this key enough to use it anyway?"), 
617                key->subkeys->keyid, key->uids->name, key->uids->email);
618     aval = alertpanel
619             (title, buf,
620              GTK_STOCK_NO, GTK_STOCK_YES, NULL);
621     g_free(buf);
622     g_free(title);
623     if (aval == G_ALERTALTERNATE)
624         return TRUE;
625     else
626         return FALSE;
627 }
628
629
630 static gint 
631 cmp_name (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
632 {
633     gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
634     gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
635     const char *sa, *sb;
636     
637     sa = a? a->uids->name : NULL;
638     sb = b? b->uids->name : NULL;
639     if (!sa)
640         return !!sb;
641     if (!sb)
642         return -1;
643     return g_ascii_strcasecmp(sa, sb);
644 }
645
646 static gint 
647 cmp_email (GtkCMCList *clist, gconstpointer pa, gconstpointer pb)
648 {
649     gpgme_key_t a = ((GtkCMCListRow *)pa)->data;
650     gpgme_key_t b = ((GtkCMCListRow *)pb)->data;
651     const char *sa, *sb;
652     
653     sa = a? a->uids->email : NULL;
654     sb = b? b->uids->email : NULL;
655     if (!sa)
656         return !!sb;
657     if (!sb)
658         return -1;
659     return g_ascii_strcasecmp(sa, sb);
660 }
661
662 static void
663 sort_keys ( struct select_keys_s *sk, enum col_titles column)
664 {
665     GtkCMCList *clist = sk->clist;
666
667     switch (column) {
668       case COL_NAME:
669         gtk_cmclist_set_compare_func (clist, cmp_name);
670         break;
671       case COL_EMAIL:
672         gtk_cmclist_set_compare_func (clist, cmp_email);
673         break;
674       default:
675         return;
676     }
677
678     /* column clicked again: toggle as-/decending */
679     if ( sk->sort_column == column) {
680         sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
681                         GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
682     }
683     else
684         sk->sort_type = GTK_SORT_ASCENDING;
685
686     sk->sort_column = column;
687     gtk_cmclist_set_sort_type (clist, sk->sort_type);
688     gtk_cmclist_sort (clist);
689 }
690
691 static void
692 sort_keys_name (GtkWidget *widget, gpointer data)
693 {
694     sort_keys ((struct select_keys_s*)data, COL_NAME);
695 }
696
697 static void
698 sort_keys_email (GtkWidget *widget, gpointer data)
699 {
700     sort_keys ((struct select_keys_s*)data, COL_EMAIL);
701 }
702
703 #endif /*USE_GPGME*/