Statistics
| Revision:

root / src / select-keys.c @ 92

History | View | Annotate | Download (15.5 kB)

1
/* select-keys.c - GTK+ based key selection
2
 *      Copyright (C) 2001 Werner Koch (dd9jn)
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 2 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, write to the Free Software
16
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
 */
18
19
#ifdef HAVE_CONFIG_H
20
#  include <config.h>
21
#endif
22
23
#ifdef USE_GPGME
24
#include <stdio.h>
25
#include <stdlib.h>
26
27
#include <glib.h>
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>
42
43
#include "select-keys.h"
44
#include "utils.h"
45
#include "gtkutils.h"
46
#include "inputdialog.h"
47
#include "manage_window.h"
48
#include "alertpanel.h"
49
50
#define DIM(v) (sizeof(v)/sizeof((v)[0]))
51
#define DIMof(type,member)   DIM(((type *)0)->member)
52
53
54
enum col_titles { 
55
    COL_ALGO,
56
    COL_KEYID,
57
    COL_NAME,
58
    COL_EMAIL,
59
    COL_VALIDITY,
60
61
    N_COL_TITLES
62
};
63
64
struct select_keys_s {
65
    int okay;
66
    GtkWidget *window;
67
    GtkLabel *toplabel;
68
    GtkCList *clist;
69
    const char *pattern;
70
    unsigned int num_keys;
71
    gpgme_key_t *kset;
72
    gpgme_ctx_t select_ctx;
73
74
    GtkSortType sort_type;
75
    enum col_titles sort_column;
76
    
77
};
78
79
80
static void set_row (GtkCList *clist, gpgme_key_t key);
81
static void fill_clist (struct select_keys_s *sk, const char *pattern);
82
static void create_dialog (struct select_keys_s *sk);
83
static void open_dialog (struct select_keys_s *sk);
84
static void close_dialog (struct select_keys_s *sk);
85
static gint delete_event_cb (GtkWidget *widget,
86
                             GdkEventAny *event, gpointer data);
87
static gboolean key_pressed_cb (GtkWidget *widget,
88
                                GdkEventKey *event, gpointer data);
89
static void select_btn_cb (GtkWidget *widget, gpointer data);
90
static void cancel_btn_cb (GtkWidget *widget, gpointer data);
91
static void other_btn_cb (GtkWidget *widget, gpointer data);
92
static void sort_keys (struct select_keys_s *sk, enum col_titles column);
93
static void sort_keys_name (GtkWidget *widget, gpointer data);
94
static void sort_keys_email (GtkWidget *widget, gpointer data);
95
96
static gboolean use_untrusted (gpgme_key_t);
97
98
static void
99
update_progress (struct select_keys_s *sk, int running, const char *pattern)
100
{
101
    static int windmill[] = { '-', '\\', '|', '/' };
102
    char *buf;
103
104
    if (!running)
105
        buf = g_strdup_printf (_("Please select key for `%s'"), 
106
                               pattern);
107
    else 
108
        buf = g_strdup_printf (_("Collecting info for `%s' ... %c"), 
109
                               pattern,
110
                               windmill[running%DIM(windmill)]);
111
    gtk_label_set_text (sk->toplabel, buf);
112
    g_free (buf);
113
}
114
115
116
/**
117
 * gpgmegtk_recipient_selection:
118
 * @recp_names: A list of email addresses
119
 * 
120
 * Select a list of recipients from a given list of email addresses.
121
 * This may pop up a window to present the user a choice, it will also
122
 * check that the recipients key are all valid.
123
 * 
124
 * Return value: NULL on error or a list of list of recipients.
125
 **/
126
gpgme_key_t *
127
gpgmegtk_recipient_selection (GSList *recp_names)
128
{
129
    struct select_keys_s sk;
130
131
    memset (&sk, 0, sizeof sk);
132
133
    open_dialog (&sk);
134
135
    do {
136
        sk.pattern = recp_names? recp_names->data:NULL;
137
        gtk_clist_clear (sk.clist);
138
        fill_clist (&sk, sk.pattern);
139
        update_progress (&sk, 0, sk.pattern);
140
        gtk_main ();
141
        if (recp_names)
142
            recp_names = recp_names->next;
143
    } while (sk.okay && recp_names);
144
145
    close_dialog (&sk);
146
147
    if (!sk.okay) {
148
        g_free(sk.kset);
149
        sk.kset = NULL;
150
    } else {
151
        sk.kset = g_realloc(sk.kset, sizeof(gpgme_key_t) * (sk.num_keys + 1));
152
        sk.kset[sk.num_keys] = NULL;
153
    }
154
    return sk.kset;
155
} 
156
157
static void
158
destroy_key (gpointer data)
159
{
160
    gpgme_key_t key = data;
161
    gpgme_key_release (key);
162
}
163
164
static void
165
set_row (GtkCList *clist, gpgme_key_t key)
166
{
167
    const char *s;
168
    const char *text[N_COL_TITLES];
169
    char *algo_buf;
170
    int row;
171
172
    /* first check whether the key is capable of encryption which is not
173
     * the case for revoked, expired or sign-only keys */
174
    if (!key->can_encrypt)
175
        return;
176
    algo_buf = g_strdup_printf ("%du/%s", 
177
         key->subkeys->length,
178
         gpgme_pubkey_algo_name(key->subkeys->pubkey_algo) );
179
    text[COL_ALGO] = algo_buf;
180
181
    s = key->subkeys->keyid;
182
    if (strlen (s) == 16)
183
        s += 8; /* show only the short keyID */
184
    text[COL_KEYID] = s;
185
186
    s = key->uids->name;
187
    text[COL_NAME] = s;
188
189
    s = key->uids->email;
190
    text[COL_EMAIL] = s;
191
192
    switch (key->uids->validity)
193
      {
194
      case GPGME_VALIDITY_UNDEFINED:
195
        s = "q";
196
        break;
197
      case GPGME_VALIDITY_NEVER:
198
        s = "n";
199
        break;
200
      case GPGME_VALIDITY_MARGINAL:
201
        s = "m";
202
        break;
203
      case GPGME_VALIDITY_FULL:
204
        s = "f";
205
        break;
206
      case GPGME_VALIDITY_ULTIMATE:
207
        s = "u";
208
        break;
209
      case GPGME_VALIDITY_UNKNOWN:
210
      default:
211
        s = "?";
212
        break;
213
      }
214
    text[COL_VALIDITY] = s;
215
216
    row = gtk_clist_append (clist, (gchar**)text);
217
    g_free (algo_buf);
218
219
    gtk_clist_set_row_data_full (clist, row, key, destroy_key);
220
}
221
222
static void 
223
fill_clist (struct select_keys_s *sk, const char *pattern)
224
{
225
    GtkCList *clist;
226
    gpgme_ctx_t ctx;
227
    gpgme_error_t err;
228
    gpgme_key_t key;
229
    int running=0;
230
231
    g_return_if_fail (sk);
232
    clist = sk->clist;
233
    g_return_if_fail (clist);
234
235
    debug_print ("select_keys:fill_clist:  pattern `%s'\n", pattern);
236
237
    /*gtk_clist_freeze (select_keys.clist);*/
238
    err = gpgme_new (&ctx);
239
    g_assert (!err);
240
241
    sk->select_ctx = ctx;
242
243
    update_progress (sk, ++running, pattern);
244
    while (gtk_events_pending ())
245
        gtk_main_iteration ();
246
247
    err = gpgme_op_keylist_start (ctx, pattern, 0);
248
    if (err) {
249
        debug_print ("** gpgme_op_keylist_start(%s) failed: %s",
250
                     pattern, gpgme_strerror (err));
251
        sk->select_ctx = NULL;
252
        gpgme_release(ctx);
253
        return;
254
    }
255
    update_progress (sk, ++running, pattern);
256
    while ( !(err = gpgme_op_keylist_next ( ctx, &key )) ) {
257
        debug_print ("%% %s:%d:  insert\n", __FILE__ ,__LINE__ );
258
        set_row (clist, key ); key = NULL;
259
        update_progress (sk, ++running, pattern);
260
        while (gtk_events_pending ())
261
            gtk_main_iteration ();
262
    }
263
    debug_print ("%% %s:%d:  ready\n", __FILE__ ,__LINE__ );
264
    if (gpgme_err_code(err) != GPG_ERR_EOF) {
265
        debug_print ("** gpgme_op_keylist_next failed: %s",
266
                     gpgme_strerror (err));
267
        gpgme_op_keylist_end(ctx);
268
    }
269
    sk->select_ctx = NULL;
270
    gpgme_release (ctx);
271
    /*gtk_clist_thaw (select_keys.clist);*/
272
}
273
274
275
static void 
276
create_dialog (struct select_keys_s *sk)
277
{
278
    GtkWidget *window;
279
    GtkWidget *vbox, *vbox2, *hbox;
280
    GtkWidget *bbox;
281
    GtkWidget *scrolledwin;
282
    GtkWidget *clist;
283
    GtkWidget *label;
284
    GtkWidget *select_btn, *cancel_btn, *other_btn;
285
    const char *titles[N_COL_TITLES];
286
287
    g_assert (!sk->window);
288
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
289
    gtk_widget_set_size_request (window, 520, 280);
290
    gtk_container_set_border_width (GTK_CONTAINER (window), 8);
291
    gtk_window_set_title (GTK_WINDOW (window), _("Select Keys"));
292
    gtk_window_set_modal (GTK_WINDOW (window), TRUE);
293
    g_signal_connect (G_OBJECT (window), "delete_event",
294
                      G_CALLBACK (delete_event_cb), sk);
295
    g_signal_connect (G_OBJECT (window), "key_press_event",
296
                      G_CALLBACK (key_pressed_cb), sk);
297
    MANAGE_WINDOW_SIGNALS_CONNECT (window);
298
299
    vbox = gtk_vbox_new (FALSE, 8);
300
    gtk_container_add (GTK_CONTAINER (window), vbox);
301
302
    hbox  = gtk_hbox_new(FALSE, 4);
303
    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
304
    label = gtk_label_new ( "" );
305
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
306
307
    hbox = gtk_hbox_new (FALSE, 8);
308
    gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
309
    gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
310
311
    scrolledwin = gtk_scrolled_window_new (NULL, NULL);
312
    gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
313
    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
314
                                    GTK_POLICY_AUTOMATIC,
315
                                    GTK_POLICY_AUTOMATIC);
316
317
    titles[COL_ALGO]     = _("Size");
318
    titles[COL_KEYID]    = _("Key ID");
319
    titles[COL_NAME]     = _("Name");
320
    titles[COL_EMAIL]    = _("Address");
321
    titles[COL_VALIDITY] = _("Val");
322
323
    clist = gtk_clist_new_with_titles (N_COL_TITLES, (char**)titles);
324
    gtk_container_add (GTK_CONTAINER (scrolledwin), clist);
325
    gtk_clist_set_column_width (GTK_CLIST(clist), COL_ALGO,      72);
326
    gtk_clist_set_column_width (GTK_CLIST(clist), COL_KEYID,     76);
327
    gtk_clist_set_column_width (GTK_CLIST(clist), COL_NAME,     130);
328
    gtk_clist_set_column_width (GTK_CLIST(clist), COL_EMAIL,    130);
329
    gtk_clist_set_column_width (GTK_CLIST(clist), COL_VALIDITY,  20);
330
    gtk_clist_set_selection_mode (GTK_CLIST(clist), GTK_SELECTION_BROWSE);
331
    g_signal_connect (G_OBJECT(GTK_CLIST(clist)->column[COL_NAME].button),
332
                      "clicked",
333
                      G_CALLBACK(sort_keys_name), sk);
334
    g_signal_connect (G_OBJECT(GTK_CLIST(clist)->column[COL_EMAIL].button),
335
                      "clicked",
336
                      G_CALLBACK(sort_keys_email), sk);
337
338
    hbox = gtk_hbox_new (FALSE, 8);
339
    gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
340
341
    gtkut_stock_button_set_create (&bbox, 
342
                                   &select_btn, _("Select"),
343
                                   &cancel_btn, GTK_STOCK_CANCEL,
344
                                   &other_btn,  _("Other"));
345
    gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
346
    gtk_widget_grab_default (select_btn);
347
348
    g_signal_connect (G_OBJECT (select_btn), "clicked",
349
                      G_CALLBACK (select_btn_cb), sk);
350
    g_signal_connect (G_OBJECT(cancel_btn), "clicked",
351
                      G_CALLBACK (cancel_btn_cb), sk);
352
    g_signal_connect (G_OBJECT (other_btn), "clicked",
353
                      G_CALLBACK (other_btn_cb), sk);
354
355
    vbox2 = gtk_vbox_new (FALSE, 4);
356
    gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 0);
357
358
    gtk_widget_show_all (window);
359
360
    sk->window = window;
361
    sk->toplabel = GTK_LABEL (label);
362
    sk->clist  = GTK_CLIST (clist);
363
}
364
365
366
static void
367
open_dialog (struct select_keys_s *sk)
368
{
369
    if (!sk->window)
370
        create_dialog (sk);
371
    manage_window_set_transient (GTK_WINDOW (sk->window));
372
    sk->okay = 0;
373
    sk->sort_column = N_COL_TITLES; /* use an invalid value */
374
    sk->sort_type = GTK_SORT_ASCENDING;
375
    gtk_widget_show (sk->window);
376
}
377
378
379
static void
380
close_dialog (struct select_keys_s *sk)
381
{
382
    g_return_if_fail (sk);
383
    gtk_widget_destroy (sk->window);
384
    sk->window = NULL;
385
}
386
387
388
static gint
389
delete_event_cb (GtkWidget *widget, GdkEventAny *event, gpointer data)
390
{
391
    struct select_keys_s *sk = data;
392
393
    sk->okay = 0;
394
    gtk_main_quit ();
395
396
    return TRUE;
397
}
398
399
400
static gboolean
401
key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
402
{
403
    struct select_keys_s *sk = data;
404
405
    g_return_val_if_fail (sk, FALSE);
406
    if (event && event->keyval == GDK_Escape) {
407
        sk->okay = 0;
408
        gtk_main_quit ();
409
    }
410
    return FALSE;
411
}
412
413
414
static void 
415
select_btn_cb (GtkWidget *widget, gpointer data)
416
{
417
    struct select_keys_s *sk = data;
418
    int row;
419
    gboolean use_key;
420
    gpgme_key_t key;
421
422
    g_return_if_fail (sk);
423
    if (!sk->clist->selection) {
424
        debug_print ("** nothing selected");
425
        return;
426
    }
427
    row = GPOINTER_TO_INT(sk->clist->selection->data);
428
    key = gtk_clist_get_row_data(sk->clist, row);
429
    if (key) {
430
        if ( key->uids->validity < GPGME_VALIDITY_FULL ) {
431
            use_key = use_untrusted(key);
432
            if (!use_key) {
433
                debug_print ("** Key untrusted, will not encrypt");
434
                return;
435
            }
436
        }
437
        sk->kset = g_realloc(sk->kset,
438
                sizeof(gpgme_key_t) * (sk->num_keys + 1));
439
        gpgme_key_ref(key);
440
        sk->kset[sk->num_keys] = key;
441
        sk->num_keys++;
442
            sk->okay = 1;
443
            gtk_main_quit ();
444
        }
445
}
446
447
448
static void 
449
cancel_btn_cb (GtkWidget *widget, gpointer data)
450
{
451
    struct select_keys_s *sk = data;
452
453
    g_return_if_fail (sk);
454
    sk->okay = 0;
455
    if (sk->select_ctx)
456
        gpgme_cancel (sk->select_ctx);
457
    gtk_main_quit ();
458
}
459
460
461
static void
462
other_btn_cb (GtkWidget *widget, gpointer data)
463
{
464
    struct select_keys_s *sk = data;
465
    char *uid;
466
467
    g_return_if_fail (sk);
468
    uid = input_dialog ( _("Add key"),
469
                         _("Enter another user or key ID:"),
470
                         NULL );
471
    if (!uid)
472
        return;
473
    fill_clist (sk, uid);
474
    update_progress (sk, 0, sk->pattern);
475
    g_free (uid);
476
}
477
478
479
static gboolean
480
use_untrusted (gpgme_key_t key)
481
{
482
    AlertValue aval;
483
484
    aval = alertpanel
485
            (_("Trust key"),
486
             _("The selected key is not fully trusted.\n"
487
               "If you choose to encrypt the message with this key you don't\n"
488
               "know for sure that it will go to the person you mean it to.\n"
489
               "Do you trust it enough to use it anyway?"),
490
             GTK_STOCK_YES, GTK_STOCK_NO, NULL);
491
    if (aval == G_ALERTDEFAULT)
492
        return TRUE;
493
    else
494
        return FALSE;
495
}
496
497
498
static gint 
499
cmp_name (GtkCList *clist, gconstpointer pa, gconstpointer pb)
500
{
501
    gpgme_key_t a = ((GtkCListRow *)pa)->data;
502
    gpgme_key_t b = ((GtkCListRow *)pb)->data;
503
    const char *sa, *sb;
504
    
505
    sa = a? a->uids->name : NULL;
506
    sb = b? b->uids->name : NULL;
507
    if (!sa)
508
        return !!sb;
509
    if (!sb)
510
        return -1;
511
    return strcasecmp(sa, sb);
512
}
513
514
static gint 
515
cmp_email (GtkCList *clist, gconstpointer pa, gconstpointer pb)
516
{
517
    gpgme_key_t a = ((GtkCListRow *)pa)->data;
518
    gpgme_key_t b = ((GtkCListRow *)pb)->data;
519
    const char *sa, *sb;
520
    
521
    sa = a? a->uids->email : NULL;
522
    sb = b? b->uids->email : NULL;
523
    if (!sa)
524
        return !!sb;
525
    if (!sb)
526
        return -1;
527
    return strcasecmp(sa, sb);
528
}
529
530
static void
531
sort_keys ( struct select_keys_s *sk, enum col_titles column)
532
{
533
    GtkCList *clist = sk->clist;
534
535
    switch (column) {
536
      case COL_NAME:
537
        gtk_clist_set_compare_func (clist, cmp_name);
538
        break;
539
      case COL_EMAIL:
540
        gtk_clist_set_compare_func (clist, cmp_email);
541
        break;
542
      default:
543
        return;
544
    }
545
546
    /* column clicked again: toggle as-/decending */
547
    if ( sk->sort_column == column) {
548
        sk->sort_type = sk->sort_type == GTK_SORT_ASCENDING ?
549
                        GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
550
    }
551
    else
552
        sk->sort_type = GTK_SORT_ASCENDING;
553
554
    sk->sort_column = column;
555
    gtk_clist_set_sort_type (clist, sk->sort_type);
556
    gtk_clist_sort (clist);
557
}
558
559
static void
560
sort_keys_name (GtkWidget *widget, gpointer data)
561
{
562
    sort_keys ((struct select_keys_s*)data, COL_NAME);
563
}
564
565
static void
566
sort_keys_email (GtkWidget *widget, gpointer data)
567
{
568
    sort_keys ((struct select_keys_s*)data, COL_EMAIL);
569
}
570
571
#endif /*USE_GPGME*/