[balsa] Crypto-related (mostly) fixes and improvements



commit 3056bc121abcbb9bf5d0add41346e0bb042225b3
Author: Albrecht Dreß <albrecht dress arcor de>
Date:   Thu Aug 10 21:35:39 2017 -0400

    Crypto-related (mostly) fixes and improvements
    
        * libbalsa/gmime-multipart-crypt.c: do not qp-encode 7-bit parts for encryption
        * libbalsa/identity.[ch]: implement separate forced signing key id's
          for gpg and s/mime including selection from the key list; clarify option text
        * libbalsa/libbalsa-gpgme-cb.c: simplify key list, show key details on double-click
        * libbalsa/libbalsa-gpgme-keys.[ch]: add functions for exporting
          and importing ascii-armoured keys; re-factor import result evaluation
        * libbalsa/libbalsa-gpgme-widgets.c: extend subkey details
        * libbalsa/libbalsa-gpgme.[ch]: fix context creation for s/mime;
          add helpers for configuring the gpgme context's home folder,
          for exporting a key to ASCII and for identifying the proper key id of a secret key;
          fix confusing comment
        * libbalsa/message.[ch]: use a reference to the sending identity
          instead of copying the key id
        * libbalsa/misc.c: re-factor deleting a folder and creating a temp folder
          (re-sent from last week's patch)
        * libbalsa/rfc3156.c: fix mem leak when encrypting a message
          (re-sent from last week's patch)
        * libbalsa/send.c: add helper for creating a gpg public key attachment
          and attach the key on request; fix mem leak in encryption
        * libbalsa/smtp-server.c: remove misleading/confusing comment
          (re-sent from last week's patch)
        * src/balsa-mime-widget-crypto.[ch]: implement display of application/pgp-keys parts
          and the import of the keys within them
        * src/balsa-mime-widget.c: call handler for application/pgp-keys parts
        * src/information-dialog.c: add missing dialogue flags
        * src/sendmsg-window.[ch], ui/sendmsg-window.ui: add user interface
          for attaching the GnuPG public key
    
    Signed-off-by: Peter Bloomfield <PeterBloomfield bellsouth net>

 ChangeLog                         |   32 ++++++
 libbalsa/gmime-multipart-crypt.c  |    6 +-
 libbalsa/identity.c               |  102 +++++++++++++++++---
 libbalsa/identity.h               |    3 +-
 libbalsa/libbalsa-gpgme-cb.c      |  147 +++++++++++++---------------
 libbalsa/libbalsa-gpgme-keys.c    |  196 +++++++++++++++++++++++++++----------
 libbalsa/libbalsa-gpgme-keys.h    |   33 ++++++
 libbalsa/libbalsa-gpgme-widgets.c |   55 +++++++++--
 libbalsa/libbalsa-gpgme.c         |  167 +++++++++++++++++++++++++++++++-
 libbalsa/libbalsa-gpgme.h         |   15 +++
 libbalsa/message.c                |    6 +-
 libbalsa/message.h                |    7 +-
 libbalsa/misc.c                   |   90 +++++++-----------
 libbalsa/rfc3156.c                |    4 +-
 libbalsa/send.c                   |  121 +++++++++++++++++++----
 libbalsa/smtp-server.c            |    1 -
 libnetclient/net-client.c         |    3 +-
 src/balsa-mime-widget-crypto.c    |  143 +++++++++++++++++++++++++++
 src/balsa-mime-widget-crypto.h    |    4 +
 src/balsa-mime-widget.c           |    1 +
 src/information-dialog.c          |    4 +-
 src/sendmsg-window.c              |   29 +++++-
 src/sendmsg-window.h              |    1 +
 ui/sendmsg-window.ui              |    7 ++
 24 files changed, 933 insertions(+), 244 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index 8f3939b..05b4e1f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,35 @@
+2017-08-10  Albrecht Dreß
+
+       Crypto-related (mostly) fixes and improvements
+
+       * libbalsa/gmime-multipart-crypt.c: do not qp-encode 7-bit parts for encryption
+       * libbalsa/identity.[ch]: implement separate forced signing key id's
+         for gpg and s/mime including selection from the key list; clarify option text
+       * libbalsa/libbalsa-gpgme-cb.c: simplify key list, show key details on double-click
+       * libbalsa/libbalsa-gpgme-keys.[ch]: add functions for exporting
+         and importing ascii-armoured keys; re-factor import result evaluation
+       * libbalsa/libbalsa-gpgme-widgets.c: extend subkey details
+       * libbalsa/libbalsa-gpgme.[ch]: fix context creation for s/mime;
+         add helpers for configuring the gpgme context's home folder,
+         for exporting a key to ASCII and for identifying the proper key id of a secret key;
+         fix confusing comment
+       * libbalsa/message.[ch]: use a reference to the sending identity
+         instead of copying the key id
+       * libbalsa/misc.c: re-factor deleting a folder and creating a temp folder
+         (re-sent from last week's patch)
+       * libbalsa/rfc3156.c: fix mem leak when encrypting a message
+         (re-sent from last week's patch)
+       * libbalsa/send.c: add helper for creating a gpg public key attachment
+         and attach the key on request; fix mem leak in encryption
+       * libbalsa/smtp-server.c: remove misleading/confusing comment
+         (re-sent from last week's patch)
+       * src/balsa-mime-widget-crypto.[ch]: implement display of application/pgp-keys parts
+         and the import of the keys within them
+       * src/balsa-mime-widget.c: call handler for application/pgp-keys parts
+       * src/information-dialog.c: add missing dialogue flags
+       * src/sendmsg-window.[ch], ui/sendmsg-window.ui: add user interface
+         for attaching the GnuPG public key
+
 2017-08-05  Peter Bloomfield  <pbloomfield bellsouth net>
 
        * libbalsa/imap/imap-tls.c (imap_create_ssl): unlock
diff --git a/libbalsa/gmime-multipart-crypt.c b/libbalsa/gmime-multipart-crypt.c
index 9c4500e..ad158fe 100644
--- a/libbalsa/gmime-multipart-crypt.c
+++ b/libbalsa/gmime-multipart-crypt.c
@@ -42,7 +42,9 @@
  *
  * Prepare a part (and all subparts) to be signed. To do this we need
  * to set the encoding of all parts (that are not already encoded to
- * either QP or Base64) to QP.
+ * either QP or Base64 or 7-bit) to QP.
+ *
+ * Ref: RFC 3156, sect. 3.
  **/
 static void
 sign_prepare(GMimeObject * mime_part)
@@ -72,7 +74,7 @@ sign_prepare(GMimeObject * mime_part)
        sign_prepare(subpart);
     } else {
        encoding = g_mime_part_get_content_encoding(GMIME_PART(mime_part));
-       if (encoding != GMIME_CONTENT_ENCODING_BASE64)
+       if ((encoding != GMIME_CONTENT_ENCODING_BASE64) && (encoding != GMIME_CONTENT_ENCODING_7BIT))
            g_mime_part_set_content_encoding(GMIME_PART(mime_part),
                                             GMIME_CONTENT_ENCODING_QUOTEDPRINTABLE);
     }
diff --git a/libbalsa/identity.c b/libbalsa/identity.c
index 3a1a3b4..5a9ea2f 100644
--- a/libbalsa/identity.c
+++ b/libbalsa/identity.c
@@ -33,6 +33,10 @@
 #  include "macosx-helpers.h"
 #endif
 
+#ifdef HAVE_GPGME
+#  include "libbalsa-gpgme.h"
+#endif
+
 #include <string.h>
 #include "smtp-server.h"
 
@@ -111,7 +115,8 @@ libbalsa_identity_init(LibBalsaIdentity* ident)
     ident->always_trust = FALSE;
     ident->warn_send_plain = TRUE;
     ident->crypt_protocol = LIBBALSA_PROTECT_OPENPGP;
-    ident->force_key_id = NULL;
+    ident->force_gpg_key_id = NULL;
+    ident->force_smime_key_id = NULL;
     ident->request_mdn = FALSE;
     ident->request_dsn = FALSE;
     /*
@@ -141,7 +146,8 @@ libbalsa_identity_finalize(GObject * object)
         g_object_unref(ident->smtp_server);
     g_free(ident->face);
     g_free(ident->x_face);
-    g_free(ident->force_key_id);
+    g_free(ident->force_gpg_key_id);
+    g_free(ident->force_smime_key_id);
 
     G_OBJECT_CLASS(parent_class)->finalize(object);
 }
@@ -584,6 +590,11 @@ static void ident_dialog_add_check_and_entry(GtkWidget *, gint, GtkDialog *,
                                              const gchar *, const gchar *);
 static void ident_dialog_add_entry(GtkWidget *, gint, GtkDialog *,
                                    const gchar *, const gchar *);
+static void ident_dialog_add_keysel_entry(GtkWidget   *grid,
+                                                                                 gint         row,
+                                                                                 GtkDialog   *dialog,
+                                                                                 const gchar *label_name,
+                                                                                 const gchar *entry_key);
 typedef enum LibBalsaIdentityPathType_ {
     LBI_PATH_TYPE_FACE,
     LBI_PATH_TYPE_XFACE
@@ -1046,15 +1057,19 @@ setup_ident_frame(GtkDialog * dialog, gboolean createp, gpointer tree,
                                 _("default protocol"),
                                 "identity-crypt-protocol");
     ident_dialog_add_checkbutton(grid, row++, dialog,
-                                 _("always trust GnuPG keys when encrypting"),
+                                 _("always trust GnuPG keys to encrypt messages"),
                                  "identity-trust-always", TRUE);
     ident_dialog_add_checkbutton(grid, row++, dialog,
                                  _("remind me if messages can be encrypted"),
                                  "identity-warn-send-plain", TRUE);
-    ident_dialog_add_entry(grid, row++, dialog,
-                           _("use secret key with this id for signing\n"
-                             "(leave empty for automatic selection)"),
-                           "identity-keyid");
+    ident_dialog_add_keysel_entry(grid, row++, dialog,
+                                         _("use secret key with this id for signing GnuPG messages\n"
+                                               "(leave empty for automatic selection)"),
+                                                                 "identity-keyid");
+    ident_dialog_add_keysel_entry(grid, row++, dialog,
+                                         _("use certificate with this id for signing S/MIME messages\n"
+                                               "(leave empty for automatic selection)"),
+                                                                 "identity-keyid-sm");
 #ifndef HAVE_GPGME
     gtk_widget_set_sensitive(grid, FALSE);
 #endif
@@ -1148,6 +1163,64 @@ ident_dialog_add_entry(GtkWidget * grid, gint row, GtkDialog * dialog,
         gtk_widget_grab_focus(entry);
 }
 
+
+#ifdef HAVE_GPGME
+static void
+choose_key(GtkButton *button, gpointer user_data)
+{
+       const gchar *target;
+       gpgme_protocol_t protocol;
+       gchar *email;
+       gchar *keyid;
+       GError *error = NULL;
+
+       target = g_object_get_data(G_OBJECT(button), "target");
+       if (strcmp(target, "identity-keyid") == 0) {
+               protocol = GPGME_PROTOCOL_OpenPGP;
+       } else {
+               protocol = GPGME_PROTOCOL_CMS;
+       }
+
+       email = ident_dialog_get_text(G_OBJECT(user_data), "identity-address");
+       keyid = libbalsa_gpgme_get_seckey(protocol, email, GTK_WINDOW(user_data), &error);
+       if (keyid != NULL) {
+               display_frame_set_field(G_OBJECT(user_data), target, keyid);
+               g_free(keyid);
+       }
+       if (error != NULL) {
+        libbalsa_information(LIBBALSA_INFORMATION_WARNING,
+                             _("Error selecting key: %s"), error->message);
+        g_clear_error(&error);
+       }
+}
+#endif
+
+
+/*
+ * Add a GtkEntry to the given dialog with a label next to it
+ * explaining the contents.  A reference to the entry is stored as
+ * object data attached to the dialog with the given key.  A button
+ * is added behind the entry to choose a key.
+ */
+static void
+ident_dialog_add_keysel_entry(GtkWidget   *grid,
+                                                         gint         row,
+                                                         GtkDialog   *dialog,
+                                         const gchar *label_name,
+                                                         const gchar *entry_key)
+{
+       GtkWidget *button;
+
+       ident_dialog_add_entry(grid, row, dialog, label_name, entry_key);
+       button = gtk_button_new_with_label(_("Choose…"));
+#ifdef HAVE_GPGME
+       g_object_set_data_full(G_OBJECT(button), "target", g_strdup(entry_key), (GDestroyNotify) g_free);
+       g_signal_connect(button, "clicked", G_CALLBACK(choose_key), dialog);
+#endif
+       gtk_grid_attach(GTK_GRID(grid), button, 2, row, 1, 1);
+}
+
+
 /*
  * Add a GtkFileChooserButton to the given dialog with a label next to it
  * explaining the contents.  A reference to the button is stored as
@@ -1472,8 +1545,10 @@ ident_dialog_update(GObject * dlg)
     id->warn_send_plain = ident_dialog_get_bool(dlg, "identity-warn-send-plain");
     id->crypt_protocol  = GPOINTER_TO_INT(ident_dialog_get_value
                                           (dlg, "identity-crypt-protocol"));
-    g_free(id->force_key_id);
-    id->force_key_id    = g_strstrip(ident_dialog_get_text(dlg, "identity-keyid"));
+    g_free(id->force_gpg_key_id);
+    id->force_gpg_key_id = g_strstrip(ident_dialog_get_text(dlg, "identity-keyid"));
+    g_free(id->force_smime_key_id);
+    id->force_smime_key_id = g_strstrip(ident_dialog_get_text(dlg, "identity-keyid-sm"));
 
     return TRUE;
 }
@@ -1856,7 +1931,8 @@ display_frame_update(GObject * dialog, LibBalsaIdentity* ident)
                               ident->warn_send_plain);
     display_frame_set_gpg_mode(dialog, "identity-crypt-protocol",
                           &ident->crypt_protocol);
-    display_frame_set_field(dialog, "identity-keyid", ident->force_key_id);
+    display_frame_set_field(dialog, "identity-keyid", ident->force_gpg_key_id);
+    display_frame_set_field(dialog, "identity-keyid-sm", ident->force_smime_key_id);
 }
 
 
@@ -1957,7 +2033,8 @@ libbalsa_identity_new_config(const gchar* name)
     ident->always_trust = libbalsa_conf_get_bool("GpgTrustAlways");
     ident->warn_send_plain = libbalsa_conf_get_bool("GpgWarnSendPlain=true");
     ident->crypt_protocol = libbalsa_conf_get_int("CryptProtocol=16");
-    ident->force_key_id = libbalsa_conf_get_string("ForceKeyID");
+    ident->force_gpg_key_id = libbalsa_conf_get_string("ForceKeyID");
+    ident->force_smime_key_id = libbalsa_conf_get_string("ForceKeyIDSMime");
 
     return ident;
 }
@@ -2002,7 +2079,8 @@ libbalsa_identity_save(LibBalsaIdentity* ident, const gchar* group)
     libbalsa_conf_set_bool("GpgTrustAlways", ident->always_trust);
     libbalsa_conf_set_bool("GpgWarnSendPlain", ident->warn_send_plain);
     libbalsa_conf_set_int("CryptProtocol", ident->crypt_protocol);
-    libbalsa_conf_set_string("ForceKeyID", ident->force_key_id);
+    libbalsa_conf_set_string("ForceKeyID", ident->force_gpg_key_id);
+    libbalsa_conf_set_string("ForceKeyIDSMime", ident->force_smime_key_id);
 
     libbalsa_conf_pop_group();
 }
diff --git a/libbalsa/identity.h b/libbalsa/identity.h
index ad04647..d8768d2 100644
--- a/libbalsa/identity.h
+++ b/libbalsa/identity.h
@@ -83,7 +83,8 @@ G_BEGIN_DECLS
        gboolean always_trust;
        gboolean warn_send_plain;
        gint crypt_protocol;
-        gchar *force_key_id;
+        gchar *force_gpg_key_id;
+        gchar *force_smime_key_id;
        LibBalsaSmtpServer *smtp_server;
     };
 
diff --git a/libbalsa/libbalsa-gpgme-cb.c b/libbalsa/libbalsa-gpgme-cb.c
index d0db84f..eb95ef8 100644
--- a/libbalsa/libbalsa-gpgme-cb.c
+++ b/libbalsa/libbalsa-gpgme-cb.c
@@ -43,9 +43,6 @@
 /* stuff to get a key fingerprint from a selection list */
 enum {
     GPG_KEY_USER_ID_COLUMN = 0,
-    GPG_KEY_ID_COLUMN,
-    GPG_KEY_LENGTH_COLUMN,
-    GPG_KEY_VALIDITY_COLUMN,
     GPG_KEY_PTR_COLUMN,
     GPG_KEY_NUM_COLUMNS
 };
@@ -128,12 +125,48 @@ lb_gpgme_passphrase(void *hook, const gchar * uid_hint,
 }
 
 
+static gboolean
+key_button_event_press_cb(GtkWidget      *widget,
+                                                 GdkEventButton *event,
+                                                 gpointer        data)
+{
+    GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
+    GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view);
+    GtkTreePath *path;
+    GtkTreeIter iter;
+    GtkTreeModel *model;
+
+    g_return_val_if_fail(event != NULL, FALSE);
+    if ((event->type != GDK_2BUTTON_PRESS) || event->window != gtk_tree_view_get_bin_window(tree_view)) {
+        return FALSE;
+    }
+
+    if (gtk_tree_view_get_path_at_pos(tree_view, event->x, event->y, &path, NULL, NULL, NULL)) {
+        if (!gtk_tree_selection_path_is_selected(selection, path)) {
+            gtk_tree_view_set_cursor(tree_view, path, NULL, FALSE);
+            gtk_tree_view_scroll_to_cell(tree_view, path, NULL, FALSE, 0, 0);
+        }
+        gtk_tree_path_free(path);
+    }
+
+    if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
+       gpgme_key_t key;
+       GtkWidget *dialog;
+
+               gtk_tree_model_get(model, &iter, GPG_KEY_PTR_COLUMN, &key, -1);
+               dialog = libbalsa_key_dialog(GTK_WINDOW(data), GTK_BUTTONS_CLOSE, key, GPG_SUBKEY_CAP_ALL, 
NULL, NULL);
+               (void) gtk_dialog_run(GTK_DIALOG(dialog));
+               gtk_widget_destroy(dialog);
+    }
+
+    return TRUE;
+}
+
+
 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)
 {
-    static const gchar *col_titles[] =
-       { N_("User ID"), N_("Key ID"), N_("Length"), N_("Validity") };
     GtkWidget *dialog;
     GtkWidget *vbox;
     GtkWidget *label;
@@ -143,11 +176,11 @@ lb_gpgme_select_key(const gchar * user_name, lb_key_sel_md_t mode, GList * keys,
        GtkTreeSortable *sortable;
     GtkTreeSelection *selection;
     GtkTreeIter iter;
-    gint i, last_col;
     gchar *prompt;
-    gchar *upcase_name;
     gpgme_key_t use_key = NULL;
     gint width, height;
+       GtkCellRenderer *renderer;
+       GtkTreeViewColumn *column;
 
     /* FIXME: create dialog according to the Gnome HIG */
     dialog = gtk_dialog_new_with_buttons(_("Select key"),
@@ -171,18 +204,18 @@ lb_gpgme_select_key(const gchar * user_name, lb_key_sel_md_t mode, GList * keys,
     switch (mode) {
        case LB_SELECT_PRIVATE_KEY:
                prompt =
-                       g_strdup_printf(_("Select the private key for the signer %s"),
+                       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"),
+                       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 "
+                                 "“%s” in your key ring.\nIf you are sure that the "
                                                          "recipient owns a different key, select it from "
                                                          "the list."), user_name);
                break;
@@ -194,6 +227,10 @@ lb_gpgme_select_key(const gchar * user_name, lb_key_sel_md_t mode, GList * keys,
     g_free(prompt);
     gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
 
+    label = gtk_label_new(_("Double-click key to show details"));
+    gtk_widget_set_halign(label, GTK_ALIGN_START);
+    gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
+
     scrolled_window = gtk_scrolled_window_new(NULL, NULL);
     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW
                                        (scrolled_window),
@@ -203,9 +240,6 @@ lb_gpgme_select_key(const gchar * user_name, lb_key_sel_md_t mode, GList * keys,
     gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0);
 
     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);
@@ -220,87 +254,44 @@ lb_gpgme_select_key(const gchar * user_name, lb_key_sel_md_t mode, GList * keys,
                     G_CALLBACK(key_selection_changed_cb), &use_key);
 
     /* add the keys */
-    upcase_name = g_ascii_strup(user_name, -1);
-    while (keys) {
-       gpgme_key_t key = (gpgme_key_t) keys->data;
-       gpgme_subkey_t subkey = key->subkeys;
-       gpgme_user_id_t uid = key->uids;
-       gchar *uid_info = NULL;
-       gboolean uid_found;
-
-       /* find the relevant subkey */
-       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;
-       while (uid && !uid_found) {
-           g_free(uid_info);
-           uid_info = libbalsa_cert_subject_readable(uid->uid);
-
-           /* check the email field which may or may not be present */
-           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 */
-               gchar *upcase_uid = g_ascii_strup(uid_info, -1);
-
-               if (strstr(upcase_uid, upcase_name))
-                   uid_found = TRUE;
-               else
-                   uid = uid->next;
-               g_free(upcase_uid);
-           }
-       }
-
-       /* append the element */
-       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,
-                              GPG_KEY_VALIDITY_COLUMN,
-                              libbalsa_gpgme_validity_to_gchar_short(uid->
-                                                                     validity),
-                              GPG_KEY_PTR_COLUMN, key, -1);
-       }
-       g_free(uid_info);
-       keys = g_list_next(keys);
+    while (keys != NULL) {
+       gpgme_key_t key = (gpgme_key_t) keys->data;
+
+       /* simply add the primary uid -- the user can show the full key details */
+       if ((key->uids != NULL) && (key->uids->uid != NULL)) {
+               gchar *uid_info;
+
+               uid_info = libbalsa_cert_subject_readable(key->uids->uid);
+               gtk_list_store_append(model, &iter);
+               gtk_list_store_set(model, &iter,
+                       GPG_KEY_USER_ID_COLUMN, uid_info,
+                               GPG_KEY_PTR_COLUMN, key, -1);
+               g_free(uid_info);
+       }
+       keys = g_list_next(keys);
     }
-    g_free(upcase_name);
 
     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 || (mode == LB_SELECT_PRIVATE_KEY)) ?
-       GPG_KEY_LENGTH_COLUMN : GPG_KEY_VALIDITY_COLUMN;
-    for (i = 0; i <= last_col; i++) {
-       GtkCellRenderer *renderer;
-       GtkTreeViewColumn *column;
 
        renderer = gtk_cell_renderer_text_new();
        column =
-           gtk_tree_view_column_new_with_attributes(_(col_titles[i]),
-                                                    renderer, "text", i,
+           gtk_tree_view_column_new_with_attributes(_("User ID"),
+                                                    renderer, "text", 0,
                                                     NULL);
        gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
-       gtk_tree_view_column_set_resizable(column, (i == 0) ? TRUE : FALSE);
-    }
+       gtk_tree_view_column_set_resizable(column, TRUE);
 
     gtk_container_add(GTK_CONTAINER(scrolled_window), tree_view);
+    g_signal_connect(tree_view, "button_press_event", G_CALLBACK(key_button_event_press_cb), dialog);
 
     /* 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)
-       use_key = NULL;
+    if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_OK) {
+       use_key = NULL;
+    }
     gtk_widget_destroy(dialog);
 
     return use_key;
diff --git a/libbalsa/libbalsa-gpgme-keys.c b/libbalsa/libbalsa-gpgme-keys.c
index f9f5050..40ba61e 100644
--- a/libbalsa/libbalsa-gpgme-keys.c
+++ b/libbalsa/libbalsa-gpgme-keys.c
@@ -48,6 +48,8 @@ static gboolean gpgme_import_key(gpgme_ctx_t   ctx,
                                                                 gchar       **import_info,
                                                                 gpgme_key_t  *imported_key,
                                                                 GError      **error);
+static gchar *gpgme_import_res_to_gchar(gpgme_import_result_t import_result)
+       G_GNUC_WARN_UNUSED_RESULT;
 static gboolean show_keyserver_dialog(gpointer user_data);
 static void keyserver_op_free(keyserver_op_t *keyserver_op);
 
@@ -168,6 +170,81 @@ libbalsa_gpgme_keyserver_op(const gchar *fingerprint,
     return result;
 }
 
+
+/* documentation: see header file */
+gchar *
+libbalsa_gpgme_export_key(gpgme_ctx_t   ctx,
+                                                 gpgme_key_t   key,
+                                                 const gchar  *name,
+                                                 GError      **error)
+{
+       gpgme_error_t gpgme_err;
+       gpgme_data_t buffer;
+       gchar *result = NULL;
+
+       g_return_val_if_fail((ctx != NULL) && (key != NULL), FALSE);
+
+       gpgme_set_armor(ctx, 1);
+       gpgme_err = gpgme_data_new(&buffer);
+       if (gpgme_err != GPG_ERR_NO_ERROR) {
+               libbalsa_gpgme_set_error(error, gpgme_err, _("cannot create data buffer"));
+       } else {
+               gpgme_key_t keys[2];
+
+               keys[0] = key;
+               keys[1] = NULL;
+               gpgme_err = gpgme_op_export_keys(ctx, keys, 0, buffer);
+               if (gpgme_err != GPG_ERR_NO_ERROR) {
+                       libbalsa_gpgme_set_error(error, gpgme_err, _("exporting key for “%s” failed"), name);
+               } else {
+                       off_t key_size;
+
+                       /* as we are working on a memory buffer, we can omit error checking... */
+                       key_size = gpgme_data_seek(buffer, 0, SEEK_END);
+                       result = g_malloc0(key_size + 1);
+                       (void) gpgme_data_seek(buffer, 0, SEEK_SET);
+                       (void) gpgme_data_read(buffer, result, key_size);
+               }
+               gpgme_data_release(buffer);
+       }
+
+       return result;
+}
+
+
+/* documentation: see header file */
+gboolean
+libbalsa_gpgme_import_ascii_key(gpgme_ctx_t   ctx,
+                                                               const gchar  *key_buf,
+                                                               gchar       **import_info,
+                                                               GError      **error)
+{
+       gpgme_data_t buffer;
+       gpgme_error_t gpgme_err;
+       gboolean result = FALSE;
+
+       g_return_val_if_fail((ctx != NULL) && (key_buf != NULL), FALSE);
+
+       gpgme_err = gpgme_data_new_from_mem(&buffer, key_buf, strlen(key_buf), 1);
+       if (gpgme_err != GPG_ERR_NO_ERROR) {
+               libbalsa_gpgme_set_error(error, gpgme_err, _("cannot create data buffer"));
+       } else {
+               gpgme_err = gpgme_op_import(ctx, buffer);
+               if (gpgme_err != GPG_ERR_NO_ERROR) {
+                       libbalsa_gpgme_set_error(error, gpgme_err, _("importing ASCII-armored key data 
failed"));
+               } else {
+                       result = TRUE;
+                       if (import_info != NULL) {
+                               *import_info = gpgme_import_res_to_gchar(gpgme_op_import_result(ctx));
+                       }
+               }
+               gpgme_data_release(buffer);
+       }
+
+       return result;
+}
+
+
 /* ---- local functions ------------------------------------------------------ */
 
 /** \brief Check if a key is usable
@@ -235,7 +312,7 @@ gpgme_keyserver_run(gpointer user_data)
                } else if (keys->next != NULL) {
                        dialog = gtk_message_dialog_new(keyserver_op->parent,
                                GTK_DIALOG_DESTROY_WITH_PARENT | libbalsa_dialog_flags(), 
GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
-                               _("Found %u keys with fingerprint %s on the key server. Please check and 
import the proper key manually"),
+                               _("Found %u keys with fingerprint %s on the key server. Please check and 
import the proper key manually."),
                                g_list_length(keys), keyserver_op->fingerprint);
                } else {
                        dialog = gpgme_keyserver_do_import(keyserver_op, (gpgme_key_t) keys->data);
@@ -282,7 +359,7 @@ gpgme_keyserver_do_import(keyserver_op_t *keyserver_op,
                                GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", import_msg);
                } else {
                        dialog = libbalsa_key_dialog(keyserver_op->parent, GTK_BUTTONS_CLOSE, imported_key, 
GPG_SUBKEY_CAP_ALL,
-                               _("Key imported"), import_msg);
+                               NULL, import_msg);
                        gpgme_key_unref(imported_key);
                }
                g_free(import_msg);
@@ -329,61 +406,80 @@ gpgme_import_key(gpgme_ctx_t   ctx,
                gpgme_import_result_t import_result;
 
                import_result = gpgme_op_import_result(ctx);
-               if (import_result->considered == 0) {
-                       *import_info = g_strdup(_("No key was imported or updated."));
-               } else {
-                       if (import_result->imported != 0) {
-                               *import_info = g_strdup(_("The key was imported into the local key ring."));
-                       } else if (import_result->unchanged == 0) {
-                               GString *info;
-
-                               info = g_string_new(_("The key was updated in the local key ring:"));
-                               if (import_result->new_user_ids > 0) {
-                                       g_string_append_printf(info,
-                                               ngettext("\n\342\200\242 %d new user ID", "\n\342\200\242 %d 
new user IDs", import_result->new_user_ids),
-                                               import_result->new_user_ids);
-                               }
-                               if (import_result->new_sub_keys > 0) {
-                                       g_string_append_printf(info,
-                                               ngettext("\n\342\200\242 %d new subkey", "\n\342\200\242 %d 
new subkeys", import_result->new_sub_keys),
-                                               import_result->new_sub_keys);
-                               }
-                               if (import_result->new_signatures > 0) {
-                                       g_string_append_printf(info,
-                                               ngettext("\n\342\200\242 %d new signature", "\n\342\200\242 
%d new signatures",
-                                                       import_result->new_signatures),
-                                               import_result->new_signatures);
-                               }
-                               if (import_result->new_revocations > 0) {
-                                       g_string_append_printf(info,
-                                               ngettext("\n\342\200\242 %d new revocation", "\n\342\200\242 
%d new revocations",
-                                                       import_result->new_revocations),
-                                                       import_result->new_revocations);
-                               }
-                               *import_info = g_string_free(info, FALSE);
-                       } else {
-                               *import_info = g_strdup(_("No changes for the key were found on the key 
server."));
+               *import_info = gpgme_import_res_to_gchar(import_result);
+
+               /* the key has been considered: load the possibly changed key from the local ring, ignoring 
any errors */
+               if ((import_result->considered != 0) && (key->subkeys != NULL)) {
+                       gpgme_keylist_mode_t kl_mode;
+
+                       /* ensure local key list mode */
+                       kl_mode = gpgme_get_keylist_mode(ctx);
+                       kl_mode &= ~GPGME_KEYLIST_MODE_EXTERN;
+                       kl_mode |= GPGME_KEYLIST_MODE_LOCAL;
+                       gpgme_err = gpgme_set_keylist_mode(ctx, kl_mode);
+                       if (gpgme_err == GPG_ERR_NO_ERROR) {
+                               (void) gpgme_get_key(ctx, key->subkeys->fpr, imported_key, 0);
                        }
+               }
 
-                       /* load the possibly changed key from the local ring, ignoring any errors */
-                       if (key->subkeys != NULL) {
-                               gpgme_keylist_mode_t kl_mode;
+               result = TRUE;
+       }
 
-                               /* ensure local key list mode */
-                               kl_mode = gpgme_get_keylist_mode(ctx);
-                               kl_mode &= ~GPGME_KEYLIST_MODE_EXTERN;
-                               kl_mode |= GPGME_KEYLIST_MODE_LOCAL;
-                               gpgme_err = gpgme_set_keylist_mode(ctx, kl_mode);
-                               if (gpgme_err == GPG_ERR_NO_ERROR) {
-                                       /* gpgme_err = gpgme_get_key(ctx, key->subkeys->fpr, imported_key, 
0); */
-                                       (void) gpgme_get_key(ctx, key->subkeys->fpr, imported_key, 0);
-                               }
+       return result;
+}
+
+
+/** \brief Create a human-readable import result message
+ *
+ * \param import_result GpgME import result data
+ * \return a newly allocated human-readable string containing the key import results
+ *
+ * This helper function collects the information about the last import operation using the passed context 
into a human-readable
+ * string.
+ */
+static gchar *
+gpgme_import_res_to_gchar(gpgme_import_result_t import_result)
+{
+       gchar *import_info;
+
+       if (import_result->considered == 0) {
+               import_info = g_strdup(_("No key was imported or updated."));
+       } else {
+               if (import_result->imported != 0) {
+                       import_info = g_strdup(_("The key was imported into the local key ring."));
+               } else if (import_result->unchanged == 0) {
+                       GString *info;
+
+                       info = g_string_new(_("The key was updated in the local key ring:"));
+                       if (import_result->new_user_ids > 0) {
+                               g_string_append_printf(info,
+                                       ngettext("\n\342\200\242 %d new user ID", "\n\342\200\242 %d new user 
IDs", import_result->new_user_ids),
+                                       import_result->new_user_ids);
+                       }
+                       if (import_result->new_sub_keys > 0) {
+                               g_string_append_printf(info,
+                                       ngettext("\n\342\200\242 %d new subkey", "\n\342\200\242 %d new 
subkeys", import_result->new_sub_keys),
+                                       import_result->new_sub_keys);
+                       }
+                       if (import_result->new_signatures > 0) {
+                               g_string_append_printf(info,
+                                       ngettext("\n\342\200\242 %d new signature", "\n\342\200\242 %d new 
signatures",
+                                               import_result->new_signatures),
+                                       import_result->new_signatures);
                        }
+                       if (import_result->new_revocations > 0) {
+                               g_string_append_printf(info,
+                                       ngettext("\n\342\200\242 %d new revocation", "\n\342\200\242 %d new 
revocations",
+                                               import_result->new_revocations),
+                                               import_result->new_revocations);
+                       }
+                       import_info = g_string_free(info, FALSE);
+               } else {
+                       import_info = g_strdup(_("The existing key in the key ring was not changed."));
                }
-               result = TRUE;
        }
 
-       return result;
+       return import_info;
 }
 
 
diff --git a/libbalsa/libbalsa-gpgme-keys.h b/libbalsa/libbalsa-gpgme-keys.h
index 110b500..aee10b8 100644
--- a/libbalsa/libbalsa-gpgme-keys.h
+++ b/libbalsa/libbalsa-gpgme-keys.h
@@ -80,6 +80,39 @@ gboolean libbalsa_gpgme_keyserver_op(const gchar  *fingerprint,
                                                                         GtkWindow    *parent,
                                                                         GError      **error);
 
+/** \brief Export a public key
+ *
+ * \param ctx GpgME context
+ * \param key the key which shall be exported
+ * \param name key description, used only for creating an error string on error
+ * \param error filled with error information on error, may be NULL
+ * \return a newly allocated string containing the key on success, NULL on error
+ *
+ * Export the passed key as ASCII armoured string.
+ *
+ * \note The returned string shall be freed by the caller.
+ */
+gchar *libbalsa_gpgme_export_key(gpgme_ctx_t   ctx,
+                                                                gpgme_key_t   key,
+                                                                const gchar  *name,
+                                                                GError      **error)
+       G_GNUC_WARN_UNUSED_RESULT;
+
+/** \brief Import an ASCII-armoured key
+ *
+ * \param ctx GpgME context
+ * \param key_buf ASCII-armoured GnuPG key buffer
+ * \param import_info filled with human-readable information about the import, may be NULL
+ * \param error filled with error information on error, may be NULL
+ * \return TRUE on success, or FALSE on error
+ *
+ * Import an ASCII-armoured GnuPG key into the key ring.
+ */
+gboolean libbalsa_gpgme_import_ascii_key(gpgme_ctx_t   ctx,
+                                                                                const gchar  *key_buf,
+                                                                                gchar       **import_info,
+                                                                                GError      **error);
+
 
 G_END_DECLS
 
diff --git a/libbalsa/libbalsa-gpgme-widgets.c b/libbalsa/libbalsa-gpgme-widgets.c
index 38d779e..23ecdca 100644
--- a/libbalsa/libbalsa-gpgme-widgets.c
+++ b/libbalsa/libbalsa-gpgme-widgets.c
@@ -48,6 +48,8 @@ static gchar *create_purpose_str(gboolean can_sign,
                                                                 gboolean can_certify,
                                                                 gboolean can_auth)
        G_GNUC_WARN_UNUSED_RESULT;
+static gchar *create_subkey_type_str(gpgme_subkey_t subkey)
+       G_GNUC_WARN_UNUSED_RESULT;
 
 
 /* documentation: see header file */
@@ -431,27 +433,31 @@ create_subkey_widget(gpgme_subkey_t subkey)
 {
        GtkWidget *subkey_grid;
        gint subkey_row = 0;
-       gchar *status_str;
+       gchar *details_str;
        gchar *timebuf;
 
        subkey_grid = gtk_grid_new();
        gtk_grid_set_column_spacing(GTK_GRID(subkey_grid), 6);
 
        /* print a warning for a bad subkey status */
-       status_str = create_status_str(subkey->expired != 0U, subkey->revoked != 0U, subkey->disabled != 0U, 
subkey->invalid != 0U);
-       if (strlen(status_str) > 0U) {
-               subkey_row = create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Status:"), status_str, 
TRUE);
+       details_str = create_status_str(subkey->expired != 0U, subkey->revoked != 0U, subkey->disabled != 0U, 
subkey->invalid != 0U);
+       if (strlen(details_str) > 0U) {
+               subkey_row = create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Status:"), 
details_str, TRUE);
        }
-       g_free(status_str);
+       g_free(details_str);
 
        subkey_row = create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Fingerprint:"), subkey->fpr, 
FALSE);
 
-       status_str = create_purpose_str(subkey->can_sign != 0U, subkey->can_encrypt != 0, subkey->can_certify 
!= 0U,
+       details_str = create_subkey_type_str(subkey);
+       subkey_row = create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Type:"), details_str, FALSE);
+       g_free(details_str);
+
+       details_str = create_purpose_str(subkey->can_sign != 0U, subkey->can_encrypt != 0, 
subkey->can_certify != 0U,
                subkey->can_authenticate != 0U);
-       if (strlen(status_str) > 0U) {
-               subkey_row = create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Capabilities:"), 
status_str, FALSE);
+       if (strlen(details_str) > 0U) {
+               subkey_row = create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Capabilities:"), 
details_str, FALSE);
        }
-       g_free(status_str);
+       g_free(details_str);
 
        if (subkey->timestamp == -1) {
                timebuf = g_strdup(_("invalid timestamp"));
@@ -468,7 +474,6 @@ create_subkey_widget(gpgme_subkey_t subkey)
        } else {
                timebuf = libbalsa_date_to_utf8(subkey->expires, "%x %X");
        }
-       /* subkey_row = create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Expires:"), timebuf, 
FALSE); */
        (void) create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Expires:"), timebuf, FALSE);
        g_free(timebuf);
 
@@ -512,3 +517,33 @@ create_purpose_str(gboolean can_sign,
        }
        return g_string_free(purpose, FALSE);
 }
+
+
+/** \brief Create a subkey type string
+ *
+ * \param subkey the subkey
+ * \return a newly allocated string containing a human-readable description of the subkey type
+ *
+ * Create a string containing the length of the subkey in bits, the public key algorithm supported by it and 
for ECC algorithms the
+ * name of the curve.  Note that the latter is available for Gpgme >= 1.5.0 only.
+ */
+static gchar *
+create_subkey_type_str(gpgme_subkey_t subkey)
+{
+       GString *type_str;
+       const gchar *algo;
+
+       type_str = g_string_new(NULL);
+       g_string_append_printf(type_str, _("%u bits"), subkey->length);
+       algo = gpgme_pubkey_algo_name(subkey->pubkey_algo);
+       if (algo != NULL) {
+               g_string_append_printf(type_str, " %s", algo);
+       }
+#if GPGME_VERSION_NUMBER >= 0x010500
+       if (subkey->curve != NULL) {
+               g_string_append_printf(type_str, _(" curve “%s”"), subkey->curve);
+       }
+#endif
+
+       return g_string_free(type_str, FALSE);
+}
diff --git a/libbalsa/libbalsa-gpgme.c b/libbalsa/libbalsa-gpgme.c
index 3f71895..4acf9da 100644
--- a/libbalsa/libbalsa-gpgme.c
+++ b/libbalsa/libbalsa-gpgme.c
@@ -39,6 +39,7 @@
 #include "gmime-gpgme-signature.h"
 #include "libbalsa-gpgme-keys.h"
 #include "libbalsa-gpgme.h"
+#include "libbalsa.h"
 
 
 static gboolean gpgme_add_signer(gpgme_ctx_t ctx, const gchar * signer,
@@ -48,6 +49,13 @@ static gpgme_key_t *gpgme_build_recipients(gpgme_ctx_t ctx,
                                           gboolean accept_low_trust,
                                           GtkWindow * parent,
                                           GError ** error);
+static gpgme_error_t get_key_from_name(gpgme_ctx_t   ctx,
+                                                                          gpgme_key_t  *key,
+                                                                          const gchar  *name,
+                                                                          gboolean      secret,
+                                                                          gboolean      accept_all,
+                                                                          GtkWindow    *parent,
+                                                                          GError      **error);
 static void release_keylist(gpgme_key_t * keylist);
 
 /* callbacks for gpgme file handling */
@@ -201,7 +209,14 @@ libbalsa_gpgme_new_with_proto(gpgme_protocol_t        protocol,
                    gpgme_release(ctx);
                    ctx = NULL;
                } else {
-                       gpgme_set_passphrase_cb(ctx, callback, parent);
+                       if (protocol == GPGME_PROTOCOL_CMS) {
+                               /* s/mime signing fails with error "not implemented" if a passphrase callback 
has been set... */
+                               gpgme_set_passphrase_cb(ctx, NULL, NULL);
+                               /* ...but make sure the user certificate is always included when signing */
+                               gpgme_set_include_certs(ctx, 1);
+                       } else {
+                               gpgme_set_passphrase_cb(ctx, callback, parent);
+                       }
                }
        }
 
@@ -209,6 +224,50 @@ libbalsa_gpgme_new_with_proto(gpgme_protocol_t        protocol,
 }
 
 
+/** \brief Set the configuration folder for a GpgME context
+ *
+ * \param ctx GpgME context
+ * \param home_dir configuration directory for the crypto engine, or NULL for the default one
+ * \param error Filled with error information on error.
+ * \return TRUE on success, or FALSE on error
+ *
+ * Set the configuration and key ring folder for a GpgME context.
+ */
+gboolean
+libbalsa_gpgme_ctx_set_home(gpgme_ctx_t   ctx,
+                                                       const gchar  *home_dir,
+                                                       GError      **error)
+{
+       gpgme_protocol_t protocol;
+       gpgme_engine_info_t engine_info;
+       gpgme_engine_info_t this_engine;
+       gboolean result = FALSE;
+
+       g_return_val_if_fail(ctx != NULL, FALSE);
+
+       protocol = gpgme_get_protocol(ctx);
+    engine_info = gpgme_ctx_get_engine_info(ctx);
+    for (this_engine = engine_info;
+        (this_engine != NULL) && (this_engine->protocol != protocol);
+        this_engine = this_engine->next) {
+       /* nothing to do */
+    }
+    if (this_engine != NULL) {
+       gpgme_error_t err;
+
+       err = gpgme_ctx_set_engine_info(ctx, protocol, this_engine->file_name, home_dir);
+       if (err == GPG_ERR_NO_ERROR) {
+               result = TRUE;
+       } else {
+               libbalsa_gpgme_set_error(error, err, _("could not set folder “%s” for engine “%s”"), home_dir,
+                       gpgme_get_protocol_name(protocol));
+       }
+    }
+
+    return result;
+}
+
+
 /** \brief Verify a signature
  *
  * \param content GMime stream of the signed matter.
@@ -620,6 +679,108 @@ libbalsa_gpgme_decrypt(GMimeStream * crypted, GMimeStream * plain,
 }
 
 
+/** \brief Export a public key
+ *
+ * \param protocol GpgME crypto protocol to use
+ * \param name pattern (mail address or fingerprint) of the requested key
+ * \param parent parent window to be passed to the callback functions
+ * \param error Filled with error information on error
+ * \return a newly allocated string containing the ASCII-armored public key on success
+ *
+ * Return the ASCII-armored key matching the passed pattern.  If necessary, the user is asked to select a 
key from a list of
+ * multiple matching keys.
+ */
+gchar *
+libbalsa_gpgme_get_pubkey(gpgme_protocol_t   protocol,
+                                                 const gchar       *name,
+                                                 GtkWindow             *parent,
+                                                 GError           **error)
+{
+       gpgme_ctx_t ctx;
+       gchar *armored_key = NULL;
+
+       g_return_val_if_fail(name != NULL, NULL);
+
+       ctx = libbalsa_gpgme_new_with_proto(protocol, NULL, NULL, error);
+       if (ctx != NULL) {
+               gpgme_error_t gpgme_err;
+               gpgme_key_t key = NULL;
+
+               gpgme_err = get_key_from_name(ctx, &key, name, FALSE, FALSE, parent, error);
+               if (gpgme_err == GPG_ERR_NO_ERROR) {
+                       armored_key = libbalsa_gpgme_export_key(ctx, key, name, error);
+                       gpgme_key_unref(key);
+               }
+           gpgme_release(ctx);
+       }
+
+       return armored_key;
+}
+
+
+/** \brief Get the key id of a secret key
+ *
+ * \param protocol GpgME protocol (OpenPGP or CMS)
+ * \param name email address for which the key shall be selected
+ * \param parent parent window to be passed to the callback functions
+ * \param error Filled with error information on error
+ * \return a newly allocated string containing the key id key on success, shall be freed by the caller
+ *
+ * Call libbalsa_gpgme_list_keys() to list all secret keys for the passed protocol, and \em always call \ref 
select_key_cb to let
+ * the user choose the secret key, even if only one is available.
+ */
+gchar *
+libbalsa_gpgme_get_seckey(gpgme_protocol_t   protocol,
+                                                 const gchar       *name,
+                                                 GtkWindow             *parent,
+                                                 GError           **error)
+{
+       gpgme_ctx_t ctx;
+       gchar *keyid = NULL;
+
+       ctx = libbalsa_gpgme_new_with_proto(protocol, NULL, NULL, error);
+       if (ctx != NULL) {
+               GList *keys = NULL;
+
+               /* let gpgme list all available keys */
+               if (libbalsa_gpgme_list_keys(ctx, &keys, NULL, name, TRUE, FALSE, error)) {
+                       if (keys != NULL) {
+                               gpgme_key_t key;
+
+                               /* let the user select a key from the list, even if there is only one */
+                               if (select_key_cb != NULL) {
+                                       key = select_key_cb(name, LB_SELECT_PRIVATE_KEY, keys, 
gpgme_get_protocol(ctx), parent);
+                                       if (key != NULL) {
+                                               gpgme_subkey_t subkey;
+
+                                               for (subkey = key->subkeys; (subkey != NULL) && (keyid == 
NULL); subkey = subkey->next) {
+                                                       if ((subkey->can_sign != 0) && (subkey->expired == 
0U) && (subkey->revoked == 0U) &&
+                                                               (subkey->disabled == 0U) && (subkey->invalid 
== 0U)) {
+                                                               keyid = g_strdup(subkey->keyid);
+                                                       }
+                                               }
+                                       }
+                               }
+                               g_list_free_full(keys, (GDestroyNotify) gpgme_key_unref);
+                       } else {
+                               GtkWidget *dialog;
+
+                               dialog = gtk_message_dialog_new(parent,
+                                       GTK_DIALOG_DESTROY_WITH_PARENT | libbalsa_dialog_flags(),
+                                       GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
+                                       _("No private key for protocol %s is available for the signer “%s”"),
+                                       gpgme_get_protocol_name(protocol), name);
+                               (void) gtk_dialog_run(GTK_DIALOG(dialog));
+                               gtk_widget_destroy(dialog);
+                       }
+               }
+           gpgme_release(ctx);
+       }
+
+       return keyid;
+}
+
+
 /*
  * set a GError form GpgME information
  */
@@ -789,8 +950,8 @@ get_key_from_name(gpgme_ctx_t   ctx,
        }
        g_list_free_full(keys, (GDestroyNotify) gpgme_key_unref);
 
-       /* OpenPGP: ask the user if a low-validity key should be trusted for encryption */
-       // FIXME - shouldn't we do the same for S/MIME?
+       /* OpenPGP: ask the user if a low-validity key should be trusted for encryption (Note: owner_trust is 
not applicable to
+        * S/MIME certificates) */
        if ((selected != NULL) &&
                 (result == GPG_ERR_NO_ERROR) && !secret && !accept_all && (gpgme_get_protocol(ctx) == 
GPGME_PROTOCOL_OpenPGP) &&
                (selected->owner_trust < GPGME_VALIDITY_FULL)) {
diff --git a/libbalsa/libbalsa-gpgme.h b/libbalsa/libbalsa-gpgme.h
index e3f0ff3..8a52164 100644
--- a/libbalsa/libbalsa-gpgme.h
+++ b/libbalsa/libbalsa-gpgme.h
@@ -80,6 +80,9 @@ gpgme_ctx_t libbalsa_gpgme_new_with_proto(gpgme_protocol_t        protocol,
                                                                                  GtkWindow                   
           *parent,
                                                                                  GError                
**error)
        G_GNUC_WARN_UNUSED_RESULT;
+gboolean libbalsa_gpgme_ctx_set_home(gpgme_ctx_t   ctx,
+                                                                        const gchar  *home_dir,
+                                                                        GError      **error);
 
 GMimeGpgmeSigstat *libbalsa_gpgme_verify(GMimeStream * content,
                                         GMimeStream * sig_plain,
@@ -111,6 +114,18 @@ GMimeGpgmeSigstat *libbalsa_gpgme_decrypt(GMimeStream * crypted,
                                          GError ** error)
        G_GNUC_WARN_UNUSED_RESULT;
 
+gchar *libbalsa_gpgme_get_pubkey(gpgme_protocol_t   protocol,
+                                                                const gchar       *name,
+                                                                GtkWindow                 *parent,
+                                                                GError           **error)
+       G_GNUC_WARN_UNUSED_RESULT;
+
+gchar *libbalsa_gpgme_get_seckey(gpgme_protocol_t   protocol,
+                                                                const gchar       *name,
+                                                                GtkWindow                 *parent,
+                                                                GError           **error)
+       G_GNUC_WARN_UNUSED_RESULT;
+
 void libbalsa_gpgme_set_error(GError        **error,
                                                  gpgme_error_t   gpgme_err,
                                                          const gchar    *format,
diff --git a/libbalsa/message.c b/libbalsa/message.c
index 3e228de..d9ad40d 100644
--- a/libbalsa/message.c
+++ b/libbalsa/message.c
@@ -101,7 +101,7 @@ libbalsa_message_init(LibBalsaMessage * message)
     message->has_all_headers = 0;
 #ifdef HAVE_GPGME
     message->prot_state = LIBBALSA_MSG_PROTECT_NONE;
-    message->force_key_id = NULL;
+    message->ident = NULL;
 #endif
 }
 
@@ -180,7 +180,9 @@ libbalsa_message_finalize(GObject * object)
     }
 
 #ifdef HAVE_GPGME
-    g_free(message->force_key_id);
+    if (message->ident != NULL) {
+       g_object_unref(message->ident);
+    }
 #endif
 
     if (message->tempdir) {
diff --git a/libbalsa/message.h b/libbalsa/message.h
index 751f08d..49c7dd9 100644
--- a/libbalsa/message.h
+++ b/libbalsa/message.h
@@ -203,11 +203,14 @@ struct _LibBalsaMessage {
     /* GPG sign and/or encrypt message (sending) */
     guint gpg_mode;
 
+    /* attach the GnuPG public key to the message (sending) */
+    gboolean att_pubkey;
+
     /* protection (i.e. sign/encrypt) status (received message) */
     LibBalsaMsgProtectState prot_state;
 
-    /* forced id of the senders secret key, empty to choose it from the mail address */
-    gchar * force_key_id;
+    /* sender identity, required for choosing a forced GnuPG or S/MIME key */
+    LibBalsaIdentity *ident;
 #endif
 
     /* request a DSN (sending) */
diff --git a/libbalsa/misc.c b/libbalsa/misc.c
index c05da73..40b2ff8 100644
--- a/libbalsa/misc.c
+++ b/libbalsa/misc.c
@@ -29,7 +29,6 @@
 
 #define _SVID_SOURCE           1
 #include <ctype.h>
-#include <dirent.h>
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -43,6 +42,7 @@
 #include "libbalsa_private.h"
 #include "html.h"
 #include <glib/gi18n.h>
+#include <glib/gstdio.h>
 
 static const gchar *libbalsa_get_codeset_name(const gchar *txt, 
                                              LibBalsaCodeset Codeset);
@@ -191,41 +191,39 @@ libbalsa_wrap_string(gchar * str, int width)
 gboolean
 libbalsa_delete_directory_contents(const gchar *path)
 {
-    struct stat sb;
-    DIR *d;
-    struct dirent *de;
-    gchar *new_path;
-
-    d = opendir(path);
-    g_return_val_if_fail(d, FALSE);
-
-    for (de = readdir(d); de; de = readdir(d)) {
-       if (strcmp(de->d_name, ".") == 0 ||
-           strcmp(de->d_name, "..") == 0)
-           continue;
-       new_path = g_strdup_printf("%s/%s", path, de->d_name);
-
-       stat(new_path, &sb);
-       if (S_ISDIR(sb.st_mode)) {
-           if (!libbalsa_delete_directory_contents(new_path) ||
-               rmdir(new_path) == -1) {
-               g_free(new_path);
-               closedir(d);
-               return FALSE;
-           }
-       } else {
-           if (unlink( new_path ) == -1) {
-               g_free(new_path);
-               closedir(d);
-               return FALSE;
-           }
-       }
-       g_free(new_path);
-       new_path = 0;
+       GDir *dir;
+       gboolean result;
+
+    g_return_val_if_fail(path != NULL, FALSE);
+    dir = g_dir_open(path, 0, NULL);
+    if (dir == NULL) {
+       result = FALSE;
+    } else {
+       const gchar *item;
+
+       result = TRUE;
+       item = g_dir_read_name(dir);
+       while (result && (item != NULL)) {
+               gchar *full_path;
+
+               full_path = g_build_filename(path, item, NULL);
+               if (g_file_test(full_path, G_FILE_TEST_IS_DIR)) {
+                       result = libbalsa_delete_directory_contents(full_path);
+                       if (g_rmdir(full_path) != 0) {
+                               result = FALSE;
+                       }
+               } else {
+                       if (g_unlink(full_path) != 0) {
+                               result = FALSE;
+                       }
+               }
+               g_free(full_path);
+               item = g_dir_read_name(dir);
+       }
+       g_dir_close(dir);
     }
 
-    closedir(d);
-    return TRUE;
+    return result;
 }
 
 /* libbalsa_expand_path:
@@ -252,29 +250,9 @@ libbalsa_expand_path(const gchar * path)
 gboolean 
 libbalsa_mktempdir (char **s)
 {
-    gchar *name;
-    int fd;
-
     g_return_val_if_fail(s != NULL, FALSE);
-
-    do {
-       GError *error = NULL;
-       fd = g_file_open_tmp("balsa-tmpdir-XXXXXX", &name, &error);
-       close(fd);
-       unlink(name);
-       /* Here is a short time that the name could be reused */
-       fd = mkdir(name, 0700);
-       if (fd == -1) {
-           g_free(name);
-           if (!g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_EXIST))
-               return FALSE;
-       }
-       if (error)
-           g_error_free(error);
-    } while (fd == -1);
-    *s = name;
-    /* FIXME: rmdir(name) at sometime */
-    return TRUE;
+    *s = g_build_filename(g_get_tmp_dir(), "balsa-tmpdir-XXXXXX", NULL);
+    return g_mkdtemp_full(*s, 0700) != NULL;
 }
 
 /* libbalsa_set_fallback_codeset: sets the codeset for incorrectly
diff --git a/libbalsa/rfc3156.c b/libbalsa/rfc3156.c
index 87084ea..f3b5fe8 100644
--- a/libbalsa/rfc3156.c
+++ b/libbalsa/rfc3156.c
@@ -262,7 +262,7 @@ libbalsa_encrypt_mime_object(GMimeObject ** content, GList * rfc822_for,
 
        result = g_mime_application_pkcs7_encrypt(pkcs7, *content, recipients, always_trust, parent, error);
     }
-    g_ptr_array_free(recipients, FALSE);
+    g_ptr_array_unref(recipients);
 
     /* error checking */
     if (!result) {
@@ -497,7 +497,7 @@ libbalsa_rfc2440_sign_encrypt(GMimePart *part, const gchar *sign_for,
                                              always_trust, parent, error);
     /* clean up */
     if (recipients)
-       g_ptr_array_free(recipients, FALSE);
+       g_ptr_array_unref(recipients);
     return result;
 }
 
diff --git a/libbalsa/send.c b/libbalsa/send.c
index 3d48c8b..ca45177 100644
--- a/libbalsa/send.c
+++ b/libbalsa/send.c
@@ -41,9 +41,14 @@
 #include "net-client-smtp.h"
 #include "gmime-filter-header.h"
 #include "smtp-server.h"
+#include "identity.h"
 
 #include "libbalsa-progress.h"
 
+#ifdef HAVE_GPGME
+#include "libbalsa-gpgme.h"
+#endif
+
 #include <glib/gi18n.h>
 
 typedef struct _MessageQueueItem MessageQueueItem;
@@ -238,6 +243,9 @@ static LibBalsaMsgCreateResult do_multipart_crypto(LibBalsaMessage *message,
                                                    GtkWindow       *parent,
                                                    GError         **error);
 
+static GMimePart *lb_create_pubkey_part(LibBalsaMessage  *message,
+                                                                       GtkWindow        *parent,
+                                                                               GError          **error);
 #endif
 
 static gpointer balsa_send_message_real(SendMessageInfo *info);
@@ -1205,12 +1213,20 @@ libbalsa_message_create_mime_message(LibBalsaMessage *message,
     InternetAddressList *ia_list;
     gchar *tmp;
     GList *list;
+    gboolean attach_pubkey = FALSE;
 #ifdef HAVE_GPGME
     GtkWindow *parent = g_object_get_data(G_OBJECT(message), "parent-window");
 #endif
 
+#ifdef HAVE_GPGME
+    /* attach the public key only if we send the message, not if we just postpone it */
+    if (!postponing && message->att_pubkey && ((message->gpg_mode & LIBBALSA_PROTECT_PROTOCOL) != 0)) {
+       attach_pubkey = TRUE;
+    }
+#endif
+
     body = message->body_list;
-    if ((body != NULL) && (body->next != NULL)) {
+    if ((body != NULL) && ((body->next != NULL) || attach_pubkey)) {
         mime_root = GMIME_OBJECT(g_mime_multipart_new_with_subtype(message->subtype));
     }
 
@@ -1384,6 +1400,26 @@ libbalsa_message_create_mime_message(LibBalsaMessage *message,
     }
 
 #ifdef HAVE_GPGME
+    if (attach_pubkey) {
+       GMimePart *pubkey_part;
+
+       pubkey_part = lb_create_pubkey_part(message, parent, error);
+       if (pubkey_part == NULL) {
+            if (mime_root != NULL) {
+                g_object_unref(G_OBJECT(mime_root));
+            }
+            return LIBBALSA_MESSAGE_CREATE_ERROR;
+       }
+        if (mime_root != NULL) {
+            g_mime_multipart_add(GMIME_MULTIPART(mime_root), GMIME_OBJECT(pubkey_part));
+            g_object_unref(G_OBJECT(pubkey_part));
+        } else {
+            mime_root = GMIME_OBJECT(pubkey_part);
+        }
+    }
+#endif
+
+#ifdef HAVE_GPGME
     if ((message->body_list != NULL) && !postponing) {
         LibBalsaMsgCreateResult crypt_res =
             do_multipart_crypto(message, &mime_root, parent, error);
@@ -1672,22 +1708,71 @@ libbalsa_fill_msg_queue_item_from_queu(LibBalsaMessage  *message,
 
 
 #ifdef HAVE_GPGME
+/*
+ * If the identity contains a forced key ID for the passed protocol, return the key ID.  Otherwise, return 
the email address of the
+ * "From:" address list to let GpeME automagically select the proper key.
+ */
 static const gchar *
-lb_send_from(LibBalsaMessage *message)
+lb_send_from(LibBalsaMessage  *message,
+                        gpgme_protocol_t  protocol)
 {
-    InternetAddress *ia =
-        internet_address_list_get_address(message->headers->from, 0);
+       const gchar *from_id;
+
+       if ((protocol == GPGME_PROTOCOL_OpenPGP) &&
+               (message->ident->force_gpg_key_id != NULL) &&
+               (message->ident->force_gpg_key_id[0] != '\0')) {
+               from_id = message->ident->force_gpg_key_id;
+       } else if ((protocol == GPGME_PROTOCOL_CMS) &&
+               (message->ident->force_smime_key_id != NULL) &&
+               (message->ident->force_smime_key_id[0] != '\0')) {
+               from_id = message->ident->force_smime_key_id;
+       } else {
+               InternetAddress *ia = internet_address_list_get_address(message->headers->from, 0);
 
-    if (message->force_key_id != NULL) {
-        return message->force_key_id;
-    }
+               while (INTERNET_ADDRESS_IS_GROUP(ia)) {
+                       ia = internet_address_list_get_address(((InternetAddressGroup *) ia)->members, 0);
+               }
+               from_id = ((InternetAddressMailbox *) ia)->addr;
+       }
 
-    while (INTERNET_ADDRESS_IS_GROUP(ia)) {
-        ia = internet_address_list_get_address(((InternetAddressGroup *)
-                                                ia)->members, 0);
-    }
+    return from_id;
+}
 
-    return ((InternetAddressMailbox *) ia)->addr;
+
+static GMimePart *
+lb_create_pubkey_part(LibBalsaMessage  *message,
+                                     GtkWindow        *parent,
+                                     GError          **error)
+{
+       const gchar *key_id;
+       gchar *keybuf;
+       GMimePart *mime_part = NULL;
+
+       key_id = lb_send_from(message, GPGME_PROTOCOL_OpenPGP);
+       keybuf = libbalsa_gpgme_get_pubkey(GPGME_PROTOCOL_OpenPGP, key_id, parent, error);
+       if (keybuf != NULL) {
+           GMimeStream *stream;
+           GMimeDataWrapper *wrapper;
+           gchar *filename;
+
+           mime_part = g_mime_part_new_with_type("application", "pgp-keys");
+           filename = g_strconcat(key_id, ".asc", NULL);
+               g_mime_object_set_content_type_parameter(GMIME_OBJECT(mime_part), "name", filename);
+               g_mime_object_set_disposition(GMIME_OBJECT(mime_part), GMIME_DISPOSITION_ATTACHMENT);
+           g_mime_object_set_content_disposition_parameter(GMIME_OBJECT(mime_part), "filename", filename);
+               g_free(filename);
+           g_mime_part_set_content_encoding(mime_part, GMIME_CONTENT_ENCODING_7BIT);
+           stream = g_mime_stream_mem_new();
+           g_mime_stream_write(stream, keybuf, strlen(keybuf));
+           g_free(keybuf);
+           wrapper = g_mime_data_wrapper_new();
+           g_mime_data_wrapper_set_stream(wrapper, stream);
+           g_object_unref(stream);
+           g_mime_part_set_content_object(mime_part, wrapper);
+           g_object_unref(wrapper);
+       }
+
+       return mime_part;
 }
 
 
@@ -1703,7 +1788,7 @@ libbalsa_create_rfc2440_buffer(LibBalsaMessage *message,
     switch (mode & LIBBALSA_PROTECT_MODE) {
     case LIBBALSA_PROTECT_SIGN:       /* sign only */
         if (!libbalsa_rfc2440_sign_encrypt(mime_part,
-                                           lb_send_from(message),
+                                           lb_send_from(message, GPGME_PROTOCOL_OpenPGP),
                                            NULL, FALSE,
                                            parent, error)) {
             return LIBBALSA_MESSAGE_SIGN_ERROR;
@@ -1739,7 +1824,7 @@ libbalsa_create_rfc2440_buffer(LibBalsaMessage *message,
         if (mode & LIBBALSA_PROTECT_SIGN) {
             result =
                 libbalsa_rfc2440_sign_encrypt(mime_part,
-                                              lb_send_from(message),
+                                              lb_send_from(message, GPGME_PROTOCOL_OpenPGP),
                                               encrypt_for,
                                               always_trust,
                                               parent, error);
@@ -1797,7 +1882,7 @@ do_multipart_crypto(LibBalsaMessage *message,
     switch (message->gpg_mode & LIBBALSA_PROTECT_MODE) {
     case LIBBALSA_PROTECT_SIGN:       /* sign message */
         if (!libbalsa_sign_mime_object(mime_root,
-                                       lb_send_from(message),
+                                       lb_send_from(message, protocol),
                                        protocol, parent, error)) {
             return LIBBALSA_MESSAGE_SIGN_ERROR;
         }
@@ -1817,7 +1902,7 @@ do_multipart_crypto(LibBalsaMessage *message,
         encrypt_for = get_mailbox_names(encrypt_for,
                                         message->headers->cc_list);
         encrypt_for = g_list_append(encrypt_for,
-                                    g_strdup(lb_send_from(message)));
+                                    g_strdup(lb_send_from(message, protocol)));
         if (message->headers->bcc_list
             && (internet_address_list_length(message->headers->
                                              bcc_list) > 0)) {
@@ -1829,7 +1914,7 @@ do_multipart_crypto(LibBalsaMessage *message,
         if (message->gpg_mode & LIBBALSA_PROTECT_SIGN) {
             success =
                 libbalsa_sign_encrypt_mime_object(mime_root,
-                                                  lb_send_from(message),
+                                                  lb_send_from(message, protocol),
                                                   encrypt_for, protocol,
                                                   always_trust, parent,
                                                   error);
@@ -1839,7 +1924,7 @@ do_multipart_crypto(LibBalsaMessage *message,
                                              protocol, always_trust,
                                              parent, error);
         }
-        g_list_free(encrypt_for);
+        g_list_free_full(encrypt_for, (GDestroyNotify) g_free);
 
         if (!success) {
             return LIBBALSA_MESSAGE_ENCRYPT_ERROR;
diff --git a/libbalsa/smtp-server.c b/libbalsa/smtp-server.c
index d1a24e2..3157ca2 100644
--- a/libbalsa/smtp-server.c
+++ b/libbalsa/smtp-server.c
@@ -47,7 +47,6 @@ struct _LibBalsaSmtpServer {
     gchar *name;
     guint big_message; /* size of partial messages; in kB; 0 disables splitting */
     gint lock_state;   /* 0 means unlocked; access via atomic operations */
-    // FIXME - add an atomic flag if an operation is running on this server
 };
 
 typedef struct _LibBalsaSmtpServerClass {
diff --git a/libnetclient/net-client.c b/libnetclient/net-client.c
index 8fabc37..4dab4ec 100644
--- a/libnetclient/net-client.c
+++ b/libnetclient/net-client.c
@@ -163,7 +163,8 @@ net_client_read_line(NetClient *client, gchar **recv_line, GError **error)
                        /* check that the protocol-specific maximum line length is not exceeded */
                        if ((client->priv->max_line_len > 0U) && (length > client->priv->max_line_len)) {
                                g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) 
NET_CLIENT_ERROR_LINE_TOO_LONG,
-                                       _("reply length %lu exceeds the maximum allowed length %lu"), 
(unsigned long) length, (unsigned long) client->priv->max_line_len);
+                                       _("reply length %lu exceeds the maximum allowed length %lu"),
+                                       (unsigned long) length, (unsigned long) client->priv->max_line_len);
                                g_free(line_buf);
                        } else {
                                g_debug("R '%s'", line_buf);
diff --git a/src/balsa-mime-widget-crypto.c b/src/balsa-mime-widget-crypto.c
index 360c293..0ce7189 100644
--- a/src/balsa-mime-widget-crypto.c
+++ b/src/balsa-mime-widget-crypto.c
@@ -26,12 +26,16 @@
 #include "balsa-app.h"
 #include "balsa-icons.h"
 #include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include "libbalsa-gpgme.h"
 #include "libbalsa-gpgme-widgets.h"
 #include "libbalsa-gpgme-keys.h"
 #include "balsa-mime-widget.h"
 
 
 static void on_gpg_key_button(GtkWidget *button, const gchar *fingerprint);
+static void on_key_import_button(GtkButton *button, gpointer user_data);
+static gboolean create_import_keys_widget(GtkBox *box, const gchar *key_buf, GError **error);
 
 
 BalsaMimeWidget *
@@ -53,6 +57,41 @@ balsa_mime_widget_new_signature(BalsaMessage * bm,
     return mw;
 }
 
+BalsaMimeWidget *
+balsa_mime_widget_new_pgpkey(BalsaMessage        *bm,
+                                                        LibBalsaMessageBody *mime_body,
+                                                        const gchar             *content_type,
+                                                        gpointer                         data)
+{
+    gssize body_size;
+    gchar *body_buf = NULL;
+    GError *err = NULL;
+       BalsaMimeWidget *mw = NULL;
+
+    g_return_val_if_fail(mime_body != NULL, NULL);
+    g_return_val_if_fail(content_type != NULL, NULL);
+
+    body_size = libbalsa_message_body_get_content(mime_body, &body_buf, &err);
+    if (body_size < 0) {
+        balsa_information(LIBBALSA_INFORMATION_ERROR, _("Could not save a text part: %s"),
+                          err ? err->message : "Unknown error");
+        g_clear_error(&err);
+    } else {
+               mw = g_object_new(BALSA_TYPE_MIME_WIDGET, NULL);
+               mw->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, BMW_VBOX_SPACE);
+               if (!create_import_keys_widget(GTK_BOX(mw->widget), body_buf, &err)) {
+            balsa_information(LIBBALSA_INFORMATION_ERROR, _("Could not process GnuPG keys: %s"),
+                              err ? err->message : "Unknown error");
+            g_clear_error(&err);
+            g_object_unref(G_OBJECT(mw));
+            mw = NULL;
+               }
+       g_free(body_buf);
+    }
+
+    return mw;
+}
+
 GtkWidget *
 balsa_mime_widget_signature_widget(LibBalsaMessageBody * mime_body,
                                   const gchar * content_type)
@@ -193,7 +232,111 @@ on_gpg_key_button(GtkWidget   *button,
     if (!libbalsa_gpgme_keyserver_op(fingerprint, balsa_get_parent_window(button), &error)) {
        libbalsa_information(LIBBALSA_INFORMATION_ERROR, "%s", error->message);
        g_error_free(error);
+    } else {
+               gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE);
     }
 }
 
+
+/* Callback: import an attached key */
+static void
+on_key_import_button(GtkButton *button,
+                                        gpointer   user_data)
+{
+       gpgme_ctx_t ctx;
+       gboolean success;
+       GError *error = NULL;
+       gchar *import_info = NULL;
+       GtkWidget *dialog;
+
+       ctx = libbalsa_gpgme_new_with_proto(GPGME_PROTOCOL_OpenPGP, NULL, NULL, &error);
+       if (ctx != NULL) {
+               success = libbalsa_gpgme_import_ascii_key(ctx, g_object_get_data(G_OBJECT(button), 
"keydata"), &import_info, &error);
+               gpgme_release(ctx);
+       } else {
+               success = FALSE;
+       }
+
+       if (success) {
+               dialog = gtk_message_dialog_new(GTK_WINDOW(balsa_app.main_window),
+                       GTK_DIALOG_DESTROY_WITH_PARENT | libbalsa_dialog_flags(),
+                       GTK_MESSAGE_INFO,
+                       GTK_BUTTONS_CLOSE,
+                       _("Import GnuPG key:\n%s"), import_info);
+               gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE);
+       } else {
+               dialog = gtk_message_dialog_new(GTK_WINDOW(balsa_app.main_window),
+                       GTK_DIALOG_DESTROY_WITH_PARENT | libbalsa_dialog_flags(),
+                       GTK_MESSAGE_ERROR,
+                       GTK_BUTTONS_CLOSE,
+                       _("Error importing key data: %s"), error->message);
+               g_clear_error(&error);
+       }
+       g_free(import_info);
+       (void) gtk_dialog_run(GTK_DIALOG(dialog));
+       gtk_widget_destroy(dialog);
+}
+
+
+static gboolean
+create_import_keys_widget(GtkBox *box, const gchar *key_buf, GError **error)
+{
+       gboolean success = FALSE;
+       gpgme_ctx_t ctx;
+
+       ctx = libbalsa_gpgme_new_with_proto(GPGME_PROTOCOL_OpenPGP, NULL, NULL, error);
+       if (ctx != NULL) {
+               gchar *temp_dir = NULL;
+
+               if (!libbalsa_mktempdir(&temp_dir)) {
+                       g_warning("Failed to create a temporary folder");
+               } else {
+                       GList *keys = NULL;
+
+                       success = libbalsa_gpgme_ctx_set_home(ctx, temp_dir, error) &&
+                               libbalsa_gpgme_import_ascii_key(ctx, key_buf, NULL, error) &&
+                               libbalsa_gpgme_list_keys(ctx, &keys, NULL, NULL, FALSE, FALSE, error);
+
+                       if (success && (keys != NULL)) {
+                               GList *item;
+
+                               for (item = keys; success && (item != NULL); item = item->next) {
+                                       gpgme_key_t this_key = (gpgme_key_t) item->data;
+                                       gchar *key_ascii;
+                                       GtkWidget *key_widget;
+                                       GtkWidget *import_btn;
+
+                                       key_ascii = libbalsa_gpgme_export_key(ctx, this_key, _("(imported)"), 
error);
+
+                                       if (key_ascii == NULL) {
+                                               success = FALSE;
+                                       } else {
+                                               key_widget = libbalsa_gpgme_key(this_key, NULL, 
GPG_SUBKEY_CAP_ALL, FALSE);
+                                               gtk_box_pack_start(box, key_widget, FALSE, FALSE, 0);
+
+                                               import_btn = gtk_button_new_with_label(_("Import key into the 
local key ring"));
+                                               g_object_set_data_full(G_OBJECT(import_btn), "keydata", 
key_ascii, (GDestroyNotify) g_free);
+                                               g_signal_connect(G_OBJECT(import_btn), "clicked", (GCallback) 
on_key_import_button, NULL);
+                                               gtk_box_pack_start(box, import_btn, FALSE, FALSE, 0);
+
+                                               if (item->next != NULL) {
+                                                       gtk_box_pack_start(box, 
gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), FALSE, FALSE,
+                                                               BMW_VBOX_SPACE);
+                                               }
+                                       }
+                               }
+
+                               g_list_free_full(keys, (GDestroyNotify) gpgme_key_release);
+                       }
+
+                       libbalsa_delete_directory_contents(temp_dir);
+                       g_rmdir(temp_dir);
+               }
+
+               gpgme_release(ctx);
+       }
+
+       return success;
+}
+
 #endif  /* HAVE_GPGME */
diff --git a/src/balsa-mime-widget-crypto.h b/src/balsa-mime-widget-crypto.h
index 4ec6a09..3e72fe9 100644
--- a/src/balsa-mime-widget-crypto.h
+++ b/src/balsa-mime-widget-crypto.h
@@ -36,6 +36,10 @@ G_BEGIN_DECLS
 BalsaMimeWidget *balsa_mime_widget_new_signature(BalsaMessage * bm,
                                                 LibBalsaMessageBody * mime_body,
                                                 const gchar * content_type, gpointer data);
+BalsaMimeWidget *balsa_mime_widget_new_pgpkey(BalsaMessage        *bm,
+                                                                                         LibBalsaMessageBody 
*mime_body,
+                                                                                         const gchar         
    *content_type,
+                                                                                         gpointer            
             data);
 GtkWidget * balsa_mime_widget_signature_widget(LibBalsaMessageBody * mime_body,
                                               const gchar * content_type);
 GtkWidget * balsa_mime_widget_crypto_frame(LibBalsaMessageBody * mime_body, GtkWidget * child,
diff --git a/src/balsa-mime-widget.c b/src/balsa-mime-widget.c
index fdd11a9..9f632dc 100644
--- a/src/balsa-mime-widget.c
+++ b/src/balsa-mime-widget.c
@@ -123,6 +123,7 @@ static mime_delegate_t mime_delegate[] =
       {FALSE, "application/pgp-signature",     balsa_mime_widget_new_signature},
       {FALSE, "application/pkcs7-signature",   balsa_mime_widget_new_signature},
       {FALSE, "application/x-pkcs7-signature", balsa_mime_widget_new_signature},
+         {FALSE, "application/pgp-keys",                  balsa_mime_widget_new_pgpkey},
 #endif
       {FALSE, NULL,         NULL}
     };
diff --git a/src/information-dialog.c b/src/information-dialog.c
index adaba10..f6194b1 100644
--- a/src/information-dialog.c
+++ b/src/information-dialog.c
@@ -180,8 +180,8 @@ balsa_information_dialog(GtkWindow *parent, LibBalsaInformationType type,
      * the message string. */
     messagebox =
         gtk_message_dialog_new(GTK_WINDOW(parent),
-                               GTK_DIALOG_DESTROY_WITH_PARENT,
-                               message_type, GTK_BUTTONS_CLOSE,
+                               GTK_DIALOG_DESTROY_WITH_PARENT | libbalsa_dialog_flags(),
+                                                          message_type, GTK_BUTTONS_CLOSE,
                                "%s", msg);
 #if HAVE_MACOSX_DESKTOP
     libbalsa_macosx_menu_for_parent(messagebox, GTK_WINDOW(parent));
diff --git a/src/sendmsg-window.c b/src/sendmsg-window.c
index 9384bc8..8407592 100644
--- a/src/sendmsg-window.c
+++ b/src/sendmsg-window.c
@@ -4938,13 +4938,15 @@ bsmsg2message(BalsaSendmsg * bsmsg)
 
     message->headers->date = time(NULL);
 #ifdef HAVE_GPGME
-    if (balsa_app.has_openpgp || balsa_app.has_smime)
+    if (balsa_app.has_openpgp || balsa_app.has_smime) {
         message->gpg_mode =
             (bsmsg->gpg_mode & LIBBALSA_PROTECT_MODE) != 0 ? bsmsg->gpg_mode : 0;
-    else
+        message->att_pubkey = bsmsg->attach_pubkey;
+        message->ident = g_object_ref(ident);
+    } else {
         message->gpg_mode = 0;
-    if (ident->force_key_id && *ident->force_key_id)
-        message->force_key_id = g_strdup(ident->force_key_id);
+        message->att_pubkey = FALSE;
+    }
 #endif
 
     /* remember the parent window */
@@ -5350,6 +5352,8 @@ message_postpone(BalsaSendmsg * bsmsg)
 #ifdef HAVE_GPGME
     g_ptr_array_add(headers, g_strdup("X-Balsa-Crypto"));
     g_ptr_array_add(headers, g_strdup_printf("%d", bsmsg->gpg_mode));
+    g_ptr_array_add(headers, g_strdup("X-Balsa-Att-Pubkey"));
+    g_ptr_array_add(headers, g_strdup_printf("%d", bsmsg->attach_pubkey));
 #endif
 
 #if HAVE_GSPELL || HAVE_GTKSPELL
@@ -6001,6 +6005,15 @@ sw_encrypt_change_state(GSimpleAction * action, GVariant * state, gpointer data)
 }
 
 static void
+sw_att_pubkey_change_state(GSimpleAction * action, GVariant * state, gpointer data)
+{
+    BalsaSendmsg *bsmsg = (BalsaSendmsg *) data;
+
+    bsmsg->attach_pubkey = g_variant_get_boolean(state);
+    g_simple_action_set_state(action, state);
+}
+
+static void
 sw_gpg_mode_change_state(GSimpleAction  * action,
                          GVariant       * state,
                          gpointer         data)
@@ -6447,6 +6460,7 @@ bsmsg_setup_gpg_ui(BalsaSendmsg *bsmsg)
     /* make everything insensitive if we don't have crypto support */
     sw_action_set_enabled(bsmsg, "gpg-mode", balsa_app.has_openpgp ||
                           balsa_app.has_smime);
+    sw_action_set_enabled(bsmsg, "attpubkey", balsa_app.has_openpgp);
 }
 
 static void
@@ -6531,6 +6545,8 @@ static GActionEntry win_entries[] = {
                          sw_gpg_mode_change_state       },
     {"gpg-mode",         libbalsa_radio_activated, "s", "'smime'",
                          sw_gpg_mode_change_state       },
+       {"attpubkey",        libbalsa_toggle_activated, NULL, "false",
+                                                sw_att_pubkey_change_state     },
 #endif /* HAVE_GPGME */
     /* Only a toolbar button: */
     {"toolbar-send",     sw_toolbar_send_activated      }
@@ -6603,6 +6619,7 @@ sendmsg_window_new()
 #endif                          /* HAVE_GTKSPELL */
 #ifdef HAVE_GPGME
     bsmsg->gpg_mode = LIBBALSA_PROTECT_RFC3156;
+    bsmsg->attach_pubkey = FALSE;
 #endif
     bsmsg->autosave_timeout_id = /* autosave every 5 minutes */
         g_timeout_add_seconds(60*5, (GSourceFunc)sw_autosave_timeout_cb, bsmsg);
@@ -6911,6 +6928,10 @@ sendmsg_window_continue(LibBalsaMailbox * mailbox, guint msgno)
     if ((postpone_hdr =
          libbalsa_message_get_user_header(message, "X-Balsa-Crypto")))
         bsmsg_setup_gpg_ui_by_mode(bsmsg, atoi(postpone_hdr));
+    postpone_hdr = libbalsa_message_get_user_header(message, "X-Balsa-Att-Pubkey");
+    if (postpone_hdr != NULL) {
+       sw_action_set_active(bsmsg, "attpubkey", atoi(postpone_hdr) != 0);
+    }
 #endif
     if ((postpone_hdr =
          libbalsa_message_get_user_header(message, "X-Balsa-MDN")))
diff --git a/src/sendmsg-window.h b/src/sendmsg-window.h
index 5fccf6c..e77aa4a 100644
--- a/src/sendmsg-window.h
+++ b/src/sendmsg-window.h
@@ -95,6 +95,7 @@ G_BEGIN_DECLS
                                /* is closed.                          */
 #ifdef HAVE_GPGME
        guint gpg_mode;
+       gboolean attach_pubkey;
 #endif
 
 #if !HAVE_GTKSOURCEVIEW
diff --git a/ui/sendmsg-window.ui b/ui/sendmsg-window.ui
index fc4461d..a763842 100644
--- a/ui/sendmsg-window.ui
+++ b/ui/sendmsg-window.ui
@@ -250,6 +250,13 @@
           <attribute name='target'>smime</attribute>
         </item>
       </section>
+      <section>
+        <item>
+          <attribute name='label'
+            translatable='yes'>Attach GnuPG _Public Key</attribute>
+          <attribute name='action'>win.attpubkey</attribute>
+        </item>
+      </section>
     </submenu>
   </menu>
 </interface>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]