diff --git a/libbalsa/libbalsa-gpgme-cb.c b/libbalsa/libbalsa-gpgme-cb.c index c360be0..327afab 100644 --- a/libbalsa/libbalsa-gpgme-cb.c +++ b/libbalsa/libbalsa-gpgme-cb.c @@ -71,6 +71,8 @@ typedef struct { static void key_selection_changed_cb(GtkTreeSelection * selection, gpgme_key_t * key); +static gint sort_iter_cmp_fn(GtkTreeModel *model, GtkTreeIter *a, + GtkTreeIter *b, gpointer data); static gchar *get_passphrase_real(const gchar * uid_hint, const gchar * passphrase_info, int prev_was_bad, GtkWindow * parent); @@ -139,7 +141,7 @@ lb_gpgme_passphrase(void *hook, const gchar * uid_hint, gpgme_key_t -lb_gpgme_select_key(const gchar * user_name, gboolean secret, GList * keys, +lb_gpgme_select_key(const gchar * user_name, lb_key_sel_md_t mode, GList * keys, gpgme_protocol_t protocol, GtkWindow * parent) { static const gchar *col_titles[] = @@ -149,13 +151,15 @@ lb_gpgme_select_key(const gchar * user_name, gboolean secret, GList * keys, GtkWidget *label; GtkWidget *scrolled_window; GtkWidget *tree_view; - GtkTreeStore *model; + GtkListStore *model; + GtkTreeSortable *sortable; GtkTreeSelection *selection; GtkTreeIter iter; gint i, last_col; gchar *prompt; gchar *upcase_name; gpgme_key_t use_key = NULL; + gint width, height; /* FIXME: create dialog according to the Gnome HIG */ dialog = gtk_dialog_new_with_buttons(_("Select key"), @@ -165,22 +169,38 @@ lb_gpgme_select_key(const gchar * user_name, gboolean secret, GList * keys, _("_OK"), GTK_RESPONSE_OK, _("_Cancel"), GTK_RESPONSE_CANCEL, NULL); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL); + gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK, FALSE); #if HAVE_MACOSX_DESKTOP libbalsa_macosx_menu_for_parent(dialog, parent); #endif vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); + gtk_widget_set_vexpand (vbox, TRUE); gtk_container_add(GTK_CONTAINER (gtk_dialog_get_content_area(GTK_DIALOG(dialog))), vbox); - gtk_container_set_border_width(GTK_CONTAINER(vbox), 12); - if (secret) - prompt = - g_strdup_printf(_("Select the private key for the signer %s"), - user_name); - else - prompt = g_strdup_printf(_ - ("Select the public key for the recipient %s"), - user_name); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 12); + switch (mode) { + case LB_SELECT_PRIVATE_KEY: + prompt = + g_strdup_printf(_("Select the private key for the signer %s"), + user_name); + break; + case LB_SELECT_PUBLIC_KEY_USER: + prompt = + g_strdup_printf(_("Select the public key for the recipient %s"), + user_name); + break; + case LB_SELECT_PUBLIC_KEY_ANY: + prompt = + g_strdup_printf(_("There seems to be no public key for recipient " + "%s in your key ring.\nIf you are sure that the " + "recipient owns a different key, select it from " + "the list."), user_name); + break; + default: + g_assert_not_reached(); + } label = gtk_label_new(prompt); gtk_widget_set_halign(label, GTK_ALIGN_START); g_free(prompt); @@ -191,17 +211,22 @@ lb_gpgme_select_key(const gchar * user_name, gboolean secret, GList * keys, (scrolled_window), GTK_SHADOW_ETCHED_IN); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), - GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0); - model = gtk_tree_store_new(GPG_KEY_NUM_COLUMNS, G_TYPE_STRING, /* user ID */ + model = gtk_list_store_new(GPG_KEY_NUM_COLUMNS, G_TYPE_STRING, /* user ID */ G_TYPE_STRING, /* key ID */ G_TYPE_INT, /* length */ G_TYPE_STRING, /* validity (gpg encrypt only) */ G_TYPE_POINTER); /* key */ + sortable = GTK_TREE_SORTABLE(model); + gtk_tree_sortable_set_sort_func(sortable, 0, sort_iter_cmp_fn, NULL, NULL); + gtk_tree_sortable_set_sort_column_id(sortable, 0, GTK_SORT_ASCENDING); tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); + g_object_set_data(G_OBJECT(selection), "dialog", dialog); + g_object_set_data(G_OBJECT(selection), "first", GUINT_TO_POINTER(1)); gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE); g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(key_selection_changed_cb), &use_key); @@ -216,9 +241,11 @@ lb_gpgme_select_key(const gchar * user_name, gboolean secret, GList * keys, gboolean uid_found; /* find the relevant subkey */ - while (subkey && ((secret && !subkey->can_sign) || - (!secret && !subkey->can_encrypt))) + while (subkey && + (((mode == LB_SELECT_PRIVATE_KEY) && !subkey->can_sign) || + ((mode != LB_SELECT_PRIVATE_KEY) && !subkey->can_encrypt))) { subkey = subkey->next; + } /* find the relevant uid */ uid_found = FALSE; @@ -227,7 +254,9 @@ lb_gpgme_select_key(const gchar * user_name, gboolean secret, GList * keys, uid_info = libbalsa_cert_subject_readable(uid->uid); /* check the email field which may or may not be present */ - if (uid->email && !g_ascii_strcasecmp(uid->email, user_name)) + if (uid->email && + ((mode == LB_SELECT_PUBLIC_KEY_ANY) || + !g_ascii_strcasecmp(uid->email, user_name))) uid_found = TRUE; else { /* no email or no match, check the uid */ @@ -242,9 +271,9 @@ lb_gpgme_select_key(const gchar * user_name, gboolean secret, GList * keys, } /* append the element */ - if (subkey && uid) { - gtk_tree_store_append(GTK_TREE_STORE(model), &iter, NULL); - gtk_tree_store_set(GTK_TREE_STORE(model), &iter, + if (subkey && uid && uid_info) { + gtk_list_store_append(model, &iter); + gtk_list_store_set(model, &iter, GPG_KEY_USER_ID_COLUMN, uid_info, GPG_KEY_ID_COLUMN, subkey->keyid, GPG_KEY_LENGTH_COLUMN, subkey->length, @@ -260,7 +289,7 @@ lb_gpgme_select_key(const gchar * user_name, gboolean secret, GList * keys, g_object_unref(G_OBJECT(model)); /* show the validity only if we are asking for a gpg public key */ - last_col = (protocol == GPGME_PROTOCOL_CMS || secret) ? + last_col = (protocol == GPGME_PROTOCOL_CMS || (mode == LB_SELECT_PRIVATE_KEY)) ? GPG_KEY_LENGTH_COLUMN : GPG_KEY_VALIDITY_COLUMN; for (i = 0; i <= last_col; i++) { GtkCellRenderer *renderer; @@ -272,11 +301,14 @@ lb_gpgme_select_key(const gchar * user_name, gboolean secret, GList * keys, renderer, "text", i, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column); - gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_column_set_resizable(column, (i == 0) ? TRUE : FALSE); } gtk_container_add(GTK_CONTAINER(scrolled_window), tree_view); - gtk_window_set_default_size(GTK_WINDOW(dialog), 500, 300); + + /* set window size to 2/3 of the parent */ + gtk_window_get_size(parent, &width, &height); + gtk_window_set_default_size(GTK_WINDOW(dialog), (2 * width) / 3, (2 * height) / 3); gtk_widget_show_all(gtk_dialog_get_content_area(GTK_DIALOG(dialog))); if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_OK) @@ -425,9 +457,37 @@ get_passphrase_idle(gpointer data) static void key_selection_changed_cb(GtkTreeSelection * selection, gpgme_key_t * key) { - GtkTreeIter iter; - GtkTreeModel *model; - - if (gtk_tree_selection_get_selected(selection, &model, &iter)) - gtk_tree_model_get(model, &iter, GPG_KEY_PTR_COLUMN, key, -1); + GtkDialog *dialog = + GTK_DIALOG(g_object_get_data(G_OBJECT(selection), "dialog")); + + if (GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(selection), "first")) != 0) { + gtk_tree_selection_unselect_all(selection); + g_object_set_data(G_OBJECT(selection), "first", GUINT_TO_POINTER(0)); + } else { + GtkTreeIter iter; + GtkTreeModel *model; + + if (gtk_tree_selection_get_selected(selection, &model, &iter)) { + gtk_tree_model_get(model, &iter, GPG_KEY_PTR_COLUMN, key, -1); + gtk_dialog_set_response_sensitive(dialog, GTK_RESPONSE_OK, TRUE); + } else { + gtk_dialog_set_response_sensitive(dialog, GTK_RESPONSE_OK, FALSE); + } + } } + +/* compare function for the key list */ +static gint +sort_iter_cmp_fn(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, + gpointer data) +{ + gchar *name1, *name2; + gint result; + + gtk_tree_model_get(model, a, 0, &name1, -1); + gtk_tree_model_get(model, b, 0, &name2, -1); + result = g_utf8_collate(name1, name2); + g_free(name1); + g_free(name2); + return result; + } diff --git a/libbalsa/libbalsa-gpgme-cb.h b/libbalsa/libbalsa-gpgme-cb.h index 802ce5f..c16f59e 100644 --- a/libbalsa/libbalsa-gpgme-cb.h +++ b/libbalsa/libbalsa-gpgme-cb.h @@ -37,10 +37,17 @@ extern "C" { #endif /* __cplusplus */ +typedef enum { + LB_SELECT_PRIVATE_KEY = 1, + LB_SELECT_PUBLIC_KEY_USER, + LB_SELECT_PUBLIC_KEY_ANY +} lb_key_sel_md_t; + + gpgme_error_t lb_gpgme_passphrase(void *hook, const gchar * uid_hint, const gchar * passphrase_info, int prev_was_bad, int fd); -gpgme_key_t lb_gpgme_select_key(const gchar * user_name, gboolean secret, +gpgme_key_t lb_gpgme_select_key(const gchar * user_name, lb_key_sel_md_t mode, GList * keys, gpgme_protocol_t protocol, GtkWindow * parent); gboolean lb_gpgme_accept_low_trust_key(const gchar * user_name, diff --git a/libbalsa/libbalsa-gpgme.c b/libbalsa/libbalsa-gpgme.c index 1110677..f22cf2c 100644 --- a/libbalsa/libbalsa-gpgme.c +++ b/libbalsa/libbalsa-gpgme.c @@ -823,8 +823,9 @@ get_key_from_name(gpgme_ctx_t ctx, const gchar * name, gboolean secret, if (g_list_length(keys) > 1) { if (select_key_cb) key = - select_key_cb(name, secret, keys, gpgme_get_protocol(ctx), - parent); + select_key_cb(name, + secret ? LB_SELECT_PRIVATE_KEY : LB_SELECT_PUBLIC_KEY_USER, + keys, gpgme_get_protocol(ctx), parent); else { if (error) g_set_error(error, GPGME_ERROR_QUARK, @@ -885,6 +886,57 @@ get_key_from_name(gpgme_ctx_t ctx, const gchar * name, gboolean secret, } +static gpgme_key_t +get_pubkey(gpgme_ctx_t ctx, const gchar * name, gboolean accept_all, + GtkWindow * parent, GError ** error) +{ + GList *keys = NULL; + gpgme_key_t key; + gpgme_error_t err; + time_t now = time(NULL); + + /* let gpgme list keys */ + if ((err = gpgme_op_keylist_start(ctx, NULL, 0)) != GPG_ERR_NO_ERROR) { + gchar *msg = g_strdup_printf(_("could not list keys")); + + g_set_error_from_gpgme(error, err, msg); + g_free(msg); + return NULL; + } + + while ((err = gpgme_op_keylist_next(ctx, &key)) == GPG_ERR_NO_ERROR) { + /* check if this key and the relevant subkey are usable */ + if (check_key(key, 0, now)) + keys = g_list_append(keys, key); + } + + if (gpg_err_code(err) != GPG_ERR_EOF || !keys) { + gchar *msg = g_strdup_printf(_("could not list keys")); + + g_set_error_from_gpgme(error, err, msg); + g_free(msg); + gpgme_op_keylist_end(ctx); + g_list_foreach(keys, (GFunc) gpgme_key_unref, NULL); + g_list_free(keys); + return NULL; + } + gpgme_op_keylist_end(ctx); + + /* let the user select a key from the list, even if there is only one */ + if (select_key_cb) + key = select_key_cb(name, LB_SELECT_PUBLIC_KEY_ANY, keys, + gpgme_get_protocol(ctx), parent); + else + key = NULL; + if (key) { + gpgme_key_ref(key); + g_list_foreach(keys, (GFunc) gpgme_key_unref, NULL); + } + g_list_free(keys); + return key; +} + + /* * Add signer to ctx's list of signers and return TRUE on success or FALSE * on error. @@ -893,19 +945,20 @@ static gboolean gpgme_add_signer(gpgme_ctx_t ctx, const gchar * signer, GtkWindow * parent, GError ** error) { - gpgme_key_t key; + gboolean result = FALSE; + gpgme_key_t key; /* note: private (secret) key has never low trust... */ - if (! - (key = get_key_from_name(ctx, signer, TRUE, FALSE, parent, error))) - return FALSE; - - /* set the key (the previous operation guaranteed that it exists, no - * need 2 check return values...) */ - gpgme_signers_add(ctx, key); - gpgme_key_unref(key); + key = get_key_from_name(ctx, signer, TRUE, FALSE, parent, error); + if (key != NULL) { + /* set the key (the previous operation guaranteed that it exists, no + * need 2 check return values...) */ + gpgme_signers_add(ctx, key); + gpgme_key_unref(key); + result = TRUE; + } - return TRUE; + return result; } @@ -927,12 +980,13 @@ gpgme_build_recipients(gpgme_ctx_t ctx, GPtrArray * rcpt_list, gchar *name = (gchar *) g_ptr_array_index(rcpt_list, num_rcpts); gpgme_key_t key; - if (! - (key = - get_key_from_name(ctx, name, FALSE, accept_low_trust, parent, - error))) { - release_keylist(rcpt); - return NULL; + key = get_key_from_name(ctx, name, FALSE, accept_low_trust, parent, error); + if (key == NULL) { + key = get_pubkey(ctx, name, accept_low_trust, parent, error); + if (key == NULL) { + release_keylist(rcpt); + return NULL; + } } /* set the recipient */ diff --git a/libbalsa/libbalsa-gpgme.h b/libbalsa/libbalsa-gpgme.h index b58f0a2..3eccf47 100644 --- a/libbalsa/libbalsa-gpgme.h +++ b/libbalsa/libbalsa-gpgme.h @@ -31,6 +31,7 @@ #include #include #include +#include "libbalsa-gpgme-cb.h" #include "gmime-gpgme-signature.h" @@ -55,7 +56,7 @@ extern "C" { * - parent window */ typedef gpgme_key_t(*lbgpgme_select_key_cb) (const gchar *, - gboolean, + lb_key_sel_md_t, GList *, gpgme_protocol_t, GtkWindow *);