[balsa] Gpgme-related improvements



commit f1c71cf04762bd61e8a5856fca8f2c5557bd899d
Author: Albrecht Dreß <albrecht dress arcor de>
Date:   Sun Jul 2 06:48:35 2017 -0400

    Gpgme-related improvements
    
        * libbalsa/libbalsa-gpgme-keys.[ch]: (new) implement key listing
          and key server operations
        * libbalsa/libbalsa-gpgme-widgets.[ch]: (new) implement key widget and dialogue
        * configure.ac: drop otions "--with-gpg-app" and "--enable-smime"
        * libbalsa/Makefile.am: remove option for building without s/mime support
        * libbalsa/gmime-multipart-crypt.c, libbalsa/identity.c,
          libbalsa/send.c, src/balsa-print-object.c, src/sendmsg-window.c: remove conditionals
          for s/mime support
        * libbalsa/libbalsa-gpgme-cb.[ch]: use new key dialogue, fix prototype indent
        * libbalsa/libbalsa-gpgme.[ch]: export functions for setting a gpgme error
          and creating a context, do not set gpg application, remove conditionals
          for s/mime support, fix loading a key for a name or fingerprint,
          use new key listing function
        * libbalsa/rfc3156.[ch}: remove conditionals for s/mime support,
          use convenience functions exported by libbalsa-gpgme.c,
          export new function creating a short signature status,
          remove functions talking to the keyserver through gpg directly,
          use GLib functions to check to the trust db update lock,
          check for public key availibility by using the new key list function
        * src/balsa-mime-widget-crypto.c: use new key widget, drop GPG-related conditionals
    
    Signed-off-by: Peter Bloomfield <PeterBloomfield bellsouth net>

 ChangeLog                         |   25 ++
 configure.ac                      |   83 -----
 libbalsa/Makefile.am              |   30 +-
 libbalsa/gmime-multipart-crypt.c  |    2 -
 libbalsa/identity.c               |    4 -
 libbalsa/libbalsa-gpgme-cb.c      |   42 +--
 libbalsa/libbalsa-gpgme-cb.h      |    6 +-
 libbalsa/libbalsa-gpgme-keys.c    |  423 +++++++++++++++++++++++++
 libbalsa/libbalsa-gpgme-keys.h    |   88 ++++++
 libbalsa/libbalsa-gpgme-widgets.c |  513 ++++++++++++++++++++++++++++++
 libbalsa/libbalsa-gpgme-widgets.h |   91 ++++++
 libbalsa/libbalsa-gpgme.c         |  626 +++++++++++++++++--------------------
 libbalsa/libbalsa-gpgme.h         |   43 ++-
 libbalsa/rfc3156.c                |  383 +++++++----------------
 libbalsa/rfc3156.h                |   22 +-
 libbalsa/send.c                   |    9 +-
 src/balsa-mime-widget-crypto.c    |   53 ++--
 src/balsa-print-object.c          |    2 -
 src/sendmsg-window.c              |    7 +-
 19 files changed, 1624 insertions(+), 828 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index 6a90752..b29e51a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,28 @@
+2017-07-02  Albrecht Dreß
+
+       Gpgme-related improvements
+
+       * libbalsa/libbalsa-gpgme-keys.[ch]: (new) implement key listing
+         and key server operations
+       * libbalsa/libbalsa-gpgme-widgets.[ch]: (new) implement key widget and dialogue
+       * configure.ac: drop otions "--with-gpg-app" and "--enable-smime"
+       * libbalsa/Makefile.am: remove option for building without s/mime support
+       * libbalsa/gmime-multipart-crypt.c, libbalsa/identity.c,
+       libbalsa/send.c, src/balsa-print-object.c, src/sendmsg-window.c: remove conditionals
+         for s/mime support
+       * libbalsa/libbalsa-gpgme-cb.[ch]: use new key dialogue, fix prototype indent
+       * libbalsa/libbalsa-gpgme.[ch]: export functions for setting a gpgme error
+         and creating a context, do not set gpg application, remove conditionals
+         for s/mime support, fix loading a key for a name or fingerprint,
+         use new key listing function
+       * libbalsa/rfc3156.[ch}: remove conditionals for s/mime support,
+         use convenience functions exported by libbalsa-gpgme.c,
+         export new function creating a short signature status,
+         remove functions talking to the keyserver through gpg directly,
+         use GLib functions to check to the trust db update lock,
+         check for public key availibility by using the new key list function
+       * src/balsa-mime-widget-crypto.c: use new key widget, drop GPG-related conditionals
+
 2017-06-30  Peter Bloomfield  <pbloomfield bellsouth net>
 
        Add assertions to remove some clang-static-analyzer false
diff --git a/configure.ac b/configure.ac
index c1cc703..acf96b2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -77,15 +77,6 @@ AC_ARG_WITH([gpgme],
    AC_HELP_STRING([--with-gpgme=gpgme-config],
                   [build with gpgme/GnuPG support (default=no, path to gpgme-config optional)]),
                  [ gpgmecfg=$withval ], [ gpgmecfg=no ])
-AC_ARG_ENABLE([smime],
-   AC_HELP_STRING([--enable-smime],
-                  [include S/MIME support (needs gpgme and gpgsm, experimental for gpg < 2.0.4)]),
-                 [ have_smime=$enableval ], [ have_smime=check ])
-AC_ARG_WITH([gpg-app],
-   AC_HELP_STRING([--with-gpg-app=PATH],
-                  [use PATH as GnuPG application (default=gpg2 if >= 2.0.4, otherwise gpg 1.x)]),
-                 [ gpgapp=$withval ], [ gpgapp=no ])
-
 
 
 AC_ARG_WITH(canberra,
@@ -396,11 +387,9 @@ if test x"$gpgmecfg" != xno ; then
         gpgme_mi=`echo $gpgmever|sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
        if test $gpgme_ve -lt 1; then
                gpgmecfg=no
-               have_smime=no
        fi
        if test \( $gpgme_ve -eq 1 \) -a \( $gpgme_ma -lt 2 \) ; then
                gpgmecfg=no
-               have_smime=no
        fi
        if test x"$gpgmecfg" != xno ; then
                if test \( $gpgme_ve -eq 1 \) -a \( $gpgme_ma -lt 8 \) ; then
@@ -417,81 +406,9 @@ if test x"$gpgmecfg" != xno ; then
        else
                AC_MSG_WARN([sorry, you need at least gpgme version 1.2.0])
        fi
-else
-       have_smime=no
 fi
 AM_CONDITIONAL([BUILD_WITH_GPGME], [test $gpgmecfg = "yes"])
 
-# check the GnuPG engine application version
-# note: gpg2 interacts fine with gpgme only since version 2.0.4
-AC_DEFUN([AC_CHECK_GPG_VER],[
-       if ! test -x $1 ; then
-               AC_MSG_ERROR([$1 is not executable])
-       fi
-       AC_MSG_CHECKING([$1 version])
-       gpgver=`$1 --version | sed -e 's/.* //' -e q`
-       AC_MSG_RESULT($gpgver)
-        gpg_ve=`echo $gpgver|sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'`
-        gpg_ma=`echo $gpgver|sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'`
-        gpg_mi=`echo $gpgver|sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
-       HAVE_GPG2=no
-       if test \( $gpg_ve -eq 2 \) -a \( $gpg_ma -eq 0 \) -a \( $gpg_mi -lt 4 \) ; then
-               ifelse([$3], , :, [$3])
-       else
-               if test $gpg_ve -eq 2 ; then
-                       HAVE_GPG2=yes
-               fi
-               ifelse([$2], , :, [$2])
-       fi])
-
-# find a suitable GnuPG engine if gpgme is enabled
-if test x"$gpgmecfg" != xno ; then
-       if test x"$gpgapp" != xno ; then
-               AC_CHECK_GPG_VER($gpgapp, , 
-                                AC_MSG_ERROR([gpg2 < 2.0.4 does not communicate properly with gpgme]))
-       else
-               AC_PATH_PROG(gpg2, gpg2, [no])
-               if test x"$gpg2" != xno ; then
-                       AC_CHECK_GPG_VER($gpg2, [gpgapp=$gpg2],
-                                        AC_MSG_WARN([gpg2 < 2.0.4 does not communicate properly with gpgme]))
-               fi
-               if test x"$gpgapp" = xno ; then
-                       AC_PATH_PROG(gpg, gpg, [no])
-                       if test x"$gpg" != xno ; then
-                               AC_CHECK_GPG_VER($gpg, [gpgapp=$gpg],
-                                               AC_MSG_WARN([gpg2 < 2.0.4 does not communicate properly with 
gpgme]))
-                       fi
-               fi
-               if test x"$gpgapp" = xno ; then
-                       AC_PATH_PROG(gpg1, gpg1, [no])
-                       if test x"$gpg1" != xno ; then
-                               AC_CHECK_GPG_VER($gpg1, [gpgapp=$gpg1],
-                                               AC_MSG_WARN([gpg2 < 2.0.4 does not communicate properly with 
gpgme]))
-                       fi
-               fi
-       fi
-       if test x"$gpgapp" != xno ; then
-               AC_DEFINE(HAVE_GPG,1,[Defined when gpg is available.])
-               AC_DEFINE_UNQUOTED(GPG_PATH,["$gpgapp"],[Path of gpg.])
-       else
-               AC_MSG_WARN([cannot find a suitable gpg application])
-               AC_MSG_WARN([please consider using --with-gpg-app to specify it])
-       fi
-else
-       AC_MSG_RESULT([no])
-fi
-
-# s/mime is mature for gpg >= 2.0.4
-AC_MSG_CHECKING(whether to include S/MIME support)
-if test \( x"$have_smime" = xyes \) -o \( \( x"$HAVE_GPG2" = xyes \) -a \( x"$have_smime" = xcheck \) \) ; 
then
-       AC_MSG_RESULT([yes])
-       have_smime=yes
-       AC_DEFINE(HAVE_SMIME,1,[Defined when supporting S/MIME])
-else
-       AC_MSG_RESULT([no])
-fi
-AM_CONDITIONAL([BUILD_WITH_SMIME], [test $have_smime = "yes"])
-
 
 # OpenLDAP configuration.
 #
diff --git a/libbalsa/Makefile.am b/libbalsa/Makefile.am
index f8e5780..a31936a 100644
--- a/libbalsa/Makefile.am
+++ b/libbalsa/Makefile.am
@@ -9,12 +9,18 @@ libbalsa_gpgme_extra =                \
        libbalsa-gpgme.c                \
        libbalsa-gpgme-cb.h             \
        libbalsa-gpgme-cb.c             \
+       libbalsa-gpgme-keys.h   \
+       libbalsa-gpgme-keys.c   \
+       libbalsa-gpgme-widgets.h\
+       libbalsa-gpgme-widgets.c\
        gmime-multipart-crypt.h \
        gmime-multipart-crypt.c \
        gmime-part-rfc2440.h    \
        gmime-part-rfc2440.c    \
        gmime-gpgme-signature.h \
-       gmime-gpgme-signature.c
+       gmime-gpgme-signature.c \
+       gmime-application-pkcs7.h       \
+       gmime-application-pkcs7.c
 libbalsa_gpgme_extra_dist =
 else
 libbalsa_gpgme_extra =
@@ -23,22 +29,16 @@ libbalsa_gpgme_extra_dist = \
        libbalsa-gpgme.c                \
        libbalsa-gpgme-cb.h             \
        libbalsa-gpgme-cb.c             \
+       libbalsa-gpgme-keys.h   \
+       libbalsa-gpgme-keys.c   \
+       libbalsa-gpgme-widgets.h\
+       libbalsa-gpgme-widgets.c\
        gmime-multipart-crypt.h \
        gmime-multipart-crypt.c \
        gmime-part-rfc2440.h    \
        gmime-part-rfc2440.c    \
        gmime-gpgme-signature.h \
-       gmime-gpgme-signature.c
-endif
-
-if BUILD_WITH_SMIME
-libbalsa_smime_extra =                 \
-       gmime-application-pkcs7.h       \
-       gmime-application-pkcs7.c
-libbalsa_smime_extra_dist =
-else
-libbalsa_smime_extra =
-libbalsa_smime_extra_dist =            \
+       gmime-gpgme-signature.c \
        gmime-application-pkcs7.h       \
        gmime-application-pkcs7.c
 endif
@@ -155,15 +155,13 @@ libbalsa_a_SOURCES =              \
        source-viewer.c         \
        url.c                   \
        url.h                   \
-       ${libbalsa_gpgme_extra} \
-       ${libbalsa_smime_extra}
+       ${libbalsa_gpgme_extra}
 
 
 EXTRA_DIST =                           \
        libbalsa-marshal.list           \
        padlock-keyhole.xpm             \
-       ${libbalsa_gpgme_extra_dist}    \
-       ${libbalsa_smime_extra_dist}
+       ${libbalsa_gpgme_extra_dist}
 
 AM_CPPFLAGS = -I${top_builddir} -I${top_srcdir} -I${top_srcdir}/libbalsa \
        -I${top_srcdir}/libnetclient \
diff --git a/libbalsa/gmime-multipart-crypt.c b/libbalsa/gmime-multipart-crypt.c
index 2820da2..9c4500e 100644
--- a/libbalsa/gmime-multipart-crypt.c
+++ b/libbalsa/gmime-multipart-crypt.c
@@ -228,13 +228,11 @@ g_mime_gpgme_mps_verify(GMimeMultipartSigned * mps, GError ** error)
     if (protocol) {
        if (g_ascii_strcasecmp("application/pgp-signature", protocol) == 0)
            crypto_prot = GPGME_PROTOCOL_OpenPGP;
-#if defined(HAVE_SMIME)
        else if (g_ascii_strcasecmp
                 ("application/pkcs7-signature", protocol) == 0
                 || g_ascii_strcasecmp("application/x-pkcs7-signature",
                                       protocol) == 0)
            crypto_prot = GPGME_PROTOCOL_CMS;
-#endif
        else
            crypto_prot = GPGME_PROTOCOL_UNKNOWN;
     } else
diff --git a/libbalsa/identity.c b/libbalsa/identity.c
index 04971a3..3a1a3b4 100644
--- a/libbalsa/identity.c
+++ b/libbalsa/identity.c
@@ -2045,11 +2045,9 @@ display_frame_set_gpg_mode(GObject * dialog, const gchar* key, gint * value)
         case LIBBALSA_PROTECT_OPENPGP:
            gtk_combo_box_set_active(opt_menu, 1);
             break;
-#ifdef HAVE_SMIME
         case LIBBALSA_PROTECT_SMIMEV3:
            gtk_combo_box_set_active(opt_menu, 2);
             break;
-#endif
         case LIBBALSA_PROTECT_RFC3156:
         default:
            gtk_combo_box_set_active(opt_menu, 0);
@@ -2086,10 +2084,8 @@ ident_dialog_add_gpg_menu(GtkWidget * grid, gint row, GtkDialog * dialog,
                   GINT_TO_POINTER(LIBBALSA_PROTECT_RFC3156), opt_menu);
     add_show_menu(_("GnuPG OpenPGP mode"),
                   GINT_TO_POINTER(LIBBALSA_PROTECT_OPENPGP), opt_menu);
-#ifdef HAVE_SMIME
     add_show_menu(_("GpgSM S/MIME mode"),
                   GINT_TO_POINTER(LIBBALSA_PROTECT_SMIMEV3), opt_menu);
-#endif
 }
 
 /* add_show_menu: helper function */
diff --git a/libbalsa/libbalsa-gpgme-cb.c b/libbalsa/libbalsa-gpgme-cb.c
index c2990b7..d0db84f 100644
--- a/libbalsa/libbalsa-gpgme-cb.c
+++ b/libbalsa/libbalsa-gpgme-cb.c
@@ -36,6 +36,7 @@
 #include <gtk/gtk.h>
 #include <stdlib.h>
 #include "rfc3156.h"
+#include "libbalsa-gpgme-widgets.h"
 #include "libbalsa-gpgme-cb.h"
 
 
@@ -307,36 +308,22 @@ lb_gpgme_select_key(const gchar * user_name, lb_key_sel_md_t mode, GList * keys,
 
 
 gboolean
-lb_gpgme_accept_low_trust_key(const gchar * user_name,
-                             const gpgme_user_id_t user_id,
-                             GtkWindow * parent)
+lb_gpgme_accept_low_trust_key(const gchar *user_name,
+                                                         gpgme_key_t  key,
+                                                         GtkWindow   *parent)
 {
     GtkWidget *dialog;
     gint result;
-    gchar *message1;
     gchar *message2;
 
     /* paranoia checks */
-    g_return_val_if_fail(user_id != NULL, FALSE);
-
-    /* build the message */
-    message1 =
-       g_strdup_printf(_("Insufficient trust for recipient %s"),
-                       user_name);
-    message2 =
-       g_strdup_printf(_
-                       ("The validity of the key with user ID “%s” is “%s”."),
-                       user_id->uid,
-                       libbalsa_gpgme_validity_to_gchar_short(user_id->
-                                                              validity));
-    dialog =
-       gtk_message_dialog_new_with_markup(parent,
-                                          GTK_DIALOG_DESTROY_WITH_PARENT,
-                                          GTK_MESSAGE_WARNING,
-                                          GTK_BUTTONS_YES_NO,
-                                          "<b>%s</b>\n\n%s\n%s", message1,
-                                          message2,
-                                          _("Use this key anyway?"));
+    g_return_val_if_fail((user_name != NULL) && (key != NULL), FALSE);
+
+    /* create the dialog */
+    message2 = g_strdup_printf(_("The owner trust for this key is “%s” only.\nUse this key anyway?"),
+       libbalsa_gpgme_validity_to_gchar_short(key->owner_trust));
+    dialog = libbalsa_key_dialog(parent, GTK_BUTTONS_YES_NO, key, GPG_SUBKEY_CAP_ENCRYPT, _("Insufficient 
key owner trust"),
+       message2);
 #if HAVE_MACOSX_DESKTOP
     libbalsa_macosx_menu_for_parent(dialog, parent);
 #endif
@@ -346,13 +333,16 @@ lb_gpgme_accept_low_trust_key(const gchar * user_name,
     gtk_widget_destroy(dialog);
 
     return result == GTK_RESPONSE_YES;
-
 }
 
 
 #include "padlock-keyhole.xpm"
 
-
+/*
+ * FIXME - usually, the passphrase should /never/ be requested using this function, but directly by GnuPG 
using pinentry which
+ * guarantees, inter alia, the use of safe (unpagable) memory.  For GnuPG >= 2.1 the pinentry mode has to be 
set to
+ * GPGME_PINENTRY_MODE_LOOPBACK to enable the passphrase callback.  Consider to remove this function 
completely...
+ */
 static gchar *
 get_passphrase_real(const gchar * uid_hint, const gchar * passphrase_info,
                    int prev_was_bad, GtkWindow * parent)
diff --git a/libbalsa/libbalsa-gpgme-cb.h b/libbalsa/libbalsa-gpgme-cb.h
index 3b34592..4c64fd8 100644
--- a/libbalsa/libbalsa-gpgme-cb.h
+++ b/libbalsa/libbalsa-gpgme-cb.h
@@ -46,9 +46,9 @@ gpgme_error_t lb_gpgme_passphrase(void *hook, const gchar * uid_hint,
 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,
-                                      const gpgme_user_id_t user_id,
-                                      GtkWindow * parent);
+gboolean lb_gpgme_accept_low_trust_key(const gchar *user_name,
+                                                                  gpgme_key_t  key,
+                                                                          GtkWindow   *parent);
 
 
 G_END_DECLS
diff --git a/libbalsa/libbalsa-gpgme-keys.c b/libbalsa/libbalsa-gpgme-keys.c
new file mode 100644
index 0000000..aba8c1f
--- /dev/null
+++ b/libbalsa/libbalsa-gpgme-keys.c
@@ -0,0 +1,423 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/*
+ * Balsa E-Mail Client
+ *
+ * gpgme key listing and key server operations
+ * Copyright (C) 2017 Albrecht Dreß <albrecht dress arcor de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "libbalsa-gpgme-keys.h"
+#include <glib/gi18n.h>
+#include "libbalsa.h"
+#include "libbalsa-gpgme-widgets.h"
+#include "libbalsa-gpgme.h"
+
+
+/* key server thread data */
+typedef struct _keyserver_op_t {
+       gpgme_ctx_t gpgme_ctx;
+       gchar *fingerprint;
+       GtkWindow *parent;
+} keyserver_op_t;
+
+
+static inline gboolean check_key(const gpgme_key_t key,
+                                                                gboolean          secret,
+                                                                gboolean          on_keyserver,
+                                                                gint64            now);
+static gpointer gpgme_keyserver_run(gpointer user_data);
+static GtkWidget *gpgme_keyserver_do_import(keyserver_op_t *keyserver_op,
+                                                                                   gpgme_key_t     key)
+       G_GNUC_WARN_UNUSED_RESULT;
+static gboolean gpgme_import_key(gpgme_ctx_t   ctx,
+                                                                gpgme_key_t   key,
+                                                                gchar       **import_info,
+                                                                gpgme_key_t  *imported_key,
+                                                                GError      **error);
+static gboolean show_keyserver_dialog(gpointer user_data);
+static void keyserver_op_free(keyserver_op_t *keyserver_op);
+
+
+/* documentation: see header file */
+gboolean
+libbalsa_gpgme_list_keys(gpgme_ctx_t   ctx,
+                                                GList       **keys,
+                                                guint        *bad_keys,
+                                                const gchar  *pattern,
+                                                gboolean      secret,
+                                                gboolean      on_keyserver,
+                                                GError      **error)
+{
+       gpgme_error_t gpgme_err = GPG_ERR_NO_ERROR;
+       gpgme_keylist_mode_t kl_mode;
+
+       g_return_val_if_fail((ctx != NULL) && (keys != NULL), FALSE);
+
+       /* set key list mode to external if we want to search a remote key server, or to the local key ring */
+       kl_mode = gpgme_get_keylist_mode(ctx);
+       if (on_keyserver) {
+               kl_mode &= ~GPGME_KEYLIST_MODE_LOCAL;
+               kl_mode |= GPGME_KEYLIST_MODE_EXTERN;
+       } else {
+               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) {
+               libbalsa_gpgme_set_error(error, gpgme_err, _("error setting key list mode"));
+       }
+
+       /* list keys */
+       if (gpgme_err == GPG_ERR_NO_ERROR) {
+               gpgme_err = gpgme_op_keylist_start(ctx, pattern, (int) secret);
+               if (gpgme_err != GPG_ERR_NO_ERROR) {
+                       libbalsa_gpgme_set_error(error, gpgme_err, _("could not list keys for “%s”"), 
pattern);
+               } else {
+                       GDateTime *current_time;
+                       gint64 now;
+                       guint bad = 0U;
+
+                       /* get the current time so we can check for expired keys */
+                       current_time = g_date_time_new_now_utc();
+                       now = g_date_time_to_unix(current_time);
+                       g_date_time_unref(current_time);
+
+                       /* loop over all keys */
+                       // FIXME - this may be /very/ slow, show a spinner?
+                       do {
+                               gpgme_key_t key;
+
+                               gpgme_err = gpgme_op_keylist_next(ctx, &key);
+                               if (gpgme_err == GPG_ERR_NO_ERROR) {
+                                       if (check_key(key, secret, on_keyserver, now)) {
+                                               *keys = g_list_prepend(*keys, key);
+                                       } else {
+                                               bad++;
+                                               gpgme_key_unref(key);
+                                       }
+                               } else if (gpgme_err_code(gpgme_err) != GPG_ERR_EOF) {
+                                       libbalsa_gpgme_set_error(error, gpgme_err, _("could not list keys for 
“%s”"), pattern);
+                               } else {
+                                       /* nothing to do, see MISRA C:2012, Rule 15.7 */
+                               }
+                       } while (gpgme_err == GPG_ERR_NO_ERROR);
+                       gpgme_op_keylist_end(ctx);
+
+                       if (*keys != NULL) {
+                               *keys = g_list_reverse(*keys);
+                       }
+                       if (bad_keys != NULL) {
+                               *bad_keys = bad;
+                       }
+               }
+       }
+
+       return (gpgme_err_code(gpgme_err) == GPG_ERR_EOF);
+}
+
+
+/* documentation: see header file */
+gboolean
+libbalsa_gpgme_keyserver_op(const gchar *fingerprint,
+                                                       GtkWindow   *parent,
+                                                       GError      **error)
+{
+       keyserver_op_t *keyserver_op;
+       gboolean result = FALSE;
+
+       g_return_val_if_fail(fingerprint != NULL, FALSE);
+
+       keyserver_op = g_new0(keyserver_op_t, 1U);
+       keyserver_op->gpgme_ctx = libbalsa_gpgme_new_with_proto(GPGME_PROTOCOL_OpenPGP, NULL, NULL, error);
+       if (keyserver_op->gpgme_ctx != NULL) {
+               size_t fp_len;
+               GThread *keyserver_th;
+
+               /* apparently it is not possible to search a key server for fingerprints longer than 16 hex 
chars (64 bit)... */
+               fp_len = strlen(fingerprint);
+               if (fp_len > 16U) {
+                       keyserver_op->fingerprint = g_strdup(&fingerprint[fp_len - 16U]);
+               } else {
+                       keyserver_op->fingerprint = g_strdup(fingerprint);
+               }
+               keyserver_op->parent = parent;
+
+               /* launch thread which takes ownership of the control data structure */
+               keyserver_th = g_thread_new("keyserver", gpgme_keyserver_run, keyserver_op);
+               g_thread_unref(keyserver_th);
+               result = TRUE;
+       } else {
+       keyserver_op_free(keyserver_op);
+       result = FALSE;
+       }
+
+    return result;
+}
+
+/* ---- local functions ------------------------------------------------------ */
+
+/** \brief Check if a key is usable
+ *
+ * \param key GpgME key
+ * \param secret TRUE for a private key, FALSE for a public key
+ * \param on_keyserver TRUE for a key on a key server, FALSE for a key in the local key ring
+ * \param now current time stamp in UTC
+ * \return TRUE if the key is usable, FALSE if it is expired, disabled, revoked or invalid
+ *
+ * Note that GpgME provides less information for keys on a key server, in particular regarding the sub-keys, 
so the check has to be
+ * relaxed for this case.
+ */
+static inline gboolean
+check_key(const gpgme_key_t key,
+                 gboolean          secret,
+                 gboolean          on_keyserver,
+                 gint64            now)
+{
+       gboolean result = FALSE;
+
+       if ((key->expired == 0U) && (key->revoked == 0U) && (key->disabled == 0U) && (key->invalid == 0U)) {
+               gpgme_subkey_t subkey = key->subkeys;
+
+               while (!result && (subkey != NULL)) {
+                       if ((on_keyserver || (secret && (subkey->can_sign != 0U)) || (!secret && 
(subkey->can_encrypt != 0U))) &&
+                               (subkey->expired == 0U) && (subkey->revoked == 0U) && (subkey->disabled == 
0U) && (subkey->invalid == 0U) &&
+                               ((subkey->expires == 0) || (subkey->expires > (long int) now))) {
+                               result = TRUE;
+                       } else {
+                               subkey = subkey->next;
+                       }
+               }
+       }
+
+       return result;
+}
+
+
+/** \brief Key server query thread
+ *
+ * \param user_data thread data, cast'ed to \ref keyserver_op_t *
+ * \return always NULL
+ *
+ * Use the passed key server thread data to call libbalsa_gpgme_list_keys().  On success, check if exactly 
\em one key has been
+ * returned and call gpgme_keyserver_do_import() as to import or update it in this case.  Call 
show_keyserver_dialog() as idle
+ * callback to present the user the results.
+ */
+static gpointer
+gpgme_keyserver_run(gpointer user_data)
+{
+       keyserver_op_t *keyserver_op = (keyserver_op_t *) user_data;
+       GList *keys = NULL;
+       gboolean result;
+       GError *error = NULL;
+
+       result = libbalsa_gpgme_list_keys(keyserver_op->gpgme_ctx, &keys, NULL, keyserver_op->fingerprint, 
FALSE, TRUE, &error);
+       if (result) {
+               GtkWidget *dialog;
+
+               if (keys == NULL) {
+                       dialog = gtk_message_dialog_new(keyserver_op->parent,
+                               GTK_DIALOG_DESTROY_WITH_PARENT | libbalsa_dialog_flags(), GTK_MESSAGE_INFO, 
GTK_BUTTONS_CLOSE,
+                               _("Cannot find a key with fingerprint %s on the key server."), 
keyserver_op->fingerprint);
+               } 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"),
+                               g_list_length(keys), keyserver_op->fingerprint);
+               } else {
+                       dialog = gpgme_keyserver_do_import(keyserver_op, (gpgme_key_t) keys->data);
+               }
+               g_list_free_full(keys, (GDestroyNotify) gpgme_key_unref);
+               g_idle_add(show_keyserver_dialog, dialog);
+       } else {
+               libbalsa_information(LIBBALSA_INFORMATION_ERROR, _("Searching the key server failed: %s"), 
error->message);
+               g_error_free(error);
+       }
+       keyserver_op_free(keyserver_op);
+
+       return NULL;
+}
+
+
+/** \brief Import a key
+ *
+ * \param keyserver_op key server thread data
+ * \param key the key which shall be imported
+ * \return a GtkDialog with information about the import
+ *
+ * Run gpgme_import_key() and create a dialogue either containing an error message on error, or the import 
data and the key details
+ * on success.
+ */
+static GtkWidget *
+gpgme_keyserver_do_import(keyserver_op_t *keyserver_op,
+                                                 gpgme_key_t     key)
+{
+       GtkWidget *dialog;
+       gboolean import_res;
+       gchar *import_msg = NULL;
+       GError *error = NULL;
+       gpgme_key_t imported_key = NULL;
+
+       import_res = gpgme_import_key(keyserver_op->gpgme_ctx, key, &import_msg, &imported_key, &error);
+       if (!import_res) {
+               dialog = gtk_message_dialog_new(keyserver_op->parent, GTK_DIALOG_DESTROY_WITH_PARENT | 
libbalsa_dialog_flags(),
+                       GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", error->message);
+               g_error_free(error);
+       } else {
+               if (imported_key == NULL) {
+                       dialog = gtk_message_dialog_new(keyserver_op->parent, GTK_DIALOG_DESTROY_WITH_PARENT 
| libbalsa_dialog_flags(),
+                               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);
+                       gpgme_key_unref(imported_key);
+               }
+               g_free(import_msg);
+       }
+
+       return dialog;
+}
+
+
+/** \brief Import or update a key
+ *
+ * \param ctx GpgME context
+ * \param key key which shall be imported or updated
+ * \param import_info filled with a newly allocated string giving more information about a successful 
operation
+ * \param imported_key filled with the imported key on success
+ * \param error filled with error information on error, may be NULL
+ * \return TRUE if the import operation was successful
+ *
+ * Try to import or update the passed key, typically returned by libbalsa_gpgme_list_keys() searching a key 
server, using the
+ * passed GpgME context.  On success, fill the information message with a human-readable description.
+ *
+ * \note As the import operation will retrieve more information from the key server, the returned key will 
include more information
+ *       than the originally passed key.
+ */
+static gboolean
+gpgme_import_key(gpgme_ctx_t   ctx,
+                                gpgme_key_t   key,
+                                gchar       **import_info,
+                                gpgme_key_t  *imported_key,
+                                GError      **error)
+{
+       gpgme_error_t gpgme_err;
+       gpgme_key_t keys[2];
+       gboolean result;
+
+       keys[0] = key;
+       keys[1] = NULL;
+       gpgme_err = gpgme_op_import_keys(ctx, keys);
+       if (gpgme_err != GPG_ERR_NO_ERROR) {
+               libbalsa_gpgme_set_error(error, gpgme_err, _("error importing key"));
+               result = FALSE;
+               *import_info = NULL;
+       } else {
+               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 ID's", 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."));
+                       }
+
+                       /* load the possibly changed key from the local ring, ignoring any errors */
+                       if (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) {
+                                       gpgme_err = gpgme_get_key(ctx, key->subkeys->fpr, imported_key, 0);
+                               }
+                       }
+               }
+               result = TRUE;
+       }
+
+       return result;
+}
+
+
+/** \brief Display a dialogue
+ *
+ * \param user_data dialogue widget, cast'ed to GtkWidget *
+ * \return always FALSE
+ *
+ * This helper function, called as idle callback, just shows the passed dialogue.
+ */
+static gboolean
+show_keyserver_dialog(gpointer user_data)
+{
+       GtkWidget *dialog = GTK_WIDGET(user_data);
+
+    gtk_dialog_run(GTK_DIALOG(dialog));
+    gtk_widget_destroy(dialog);
+       return FALSE;
+}
+
+
+/** \brief Free a key server thread data structure
+ *
+ * \param keyserver_op key server thread data
+ *
+ * Destroy the GpgME context and all other allocated data in the passed key server thread data structure and 
the structure itself.
+ */
+static void
+keyserver_op_free(keyserver_op_t *keyserver_op)
+{
+       if (keyserver_op != NULL) {
+               if (keyserver_op->gpgme_ctx != NULL) {
+                       gpgme_release(keyserver_op->gpgme_ctx);
+               }
+               g_free(keyserver_op->fingerprint);
+               g_free(keyserver_op);
+       }
+}
diff --git a/libbalsa/libbalsa-gpgme-keys.h b/libbalsa/libbalsa-gpgme-keys.h
new file mode 100644
index 0000000..110b500
--- /dev/null
+++ b/libbalsa/libbalsa-gpgme-keys.h
@@ -0,0 +1,88 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/*
+ * Balsa E-Mail Client
+ *
+ * gpgme key listing and key server operations
+ * Copyright (C) 2017 Albrecht Dreß <albrecht dress arcor de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBBALSA_GPGME_KEYSERVER_H_
+#define LIBBALSA_GPGME_KEYSERVER_H_
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gpgme.h>
+#include <gtk/gtk.h>
+
+
+G_BEGIN_DECLS
+
+
+/** \brief List keys
+ *
+ * \param ctx GpgME context
+ * \param keys filled with a list of gpgme_key_t items matching the search pattern
+ * \param bad_keys filled with the number of matching keys which are expired, disabled, revoked or invalid, 
may be NULL
+ * \param pattern key search pattern (e.g. name, fingerprint, ...), may be NULL to list all keys
+ * \param secret TRUE to search for private keys, FALSE to search for public keys
+ * \param on_keyserver TRUE to search on a key server, FALSE to search the local key ring
+ * \param error filled with error information on error, may be NULL
+ * \return TRUE on success, or FALSE if any error occurred
+ *
+ * Use the passed context to search for keys matching the passed criteria.  Note that even if the function 
returns success, the
+ * list of keys may be empty if no matching key could be found.
+ *
+ * \note Listing external (key server) keys for a fingerprint longer than 16 hex characters fails, so be 
sure to cut them
+ *       appropriately when calling this function with \em on_keyserver == TRUE.\n
+ *       The returned list of keys shall be freed by the caller.
+ * \todo We might want to add flags for returning only keys which have the is_qualified (subkey can be used 
for qualified
+ *       signatures according to local government regulations) and/or is_de_vs (complies with the rules for 
classified information
+ *       in Germany at the restricted level, VS-NfD, requires gpgme >= 1.9.0) properties set.
+ */
+gboolean libbalsa_gpgme_list_keys(gpgme_ctx_t   ctx,
+                                                                 GList       **keys,
+                                                                 guint        *bad_keys,
+                                                                 const gchar  *pattern,
+                                                                 gboolean      secret,
+                                                                 gboolean      on_keyserver,
+                                                                 GError      **error);
+
+/** \brief Search the key server for a key
+ *
+ * \param fingerprint key fingerprint to search for
+ * \param parent parent window
+ * \param error filled with error information on error, may be NULL
+ * \return TRUE on success, or FALSE on error
+ *
+ * Launch a new thread for searching the passed fingerprint on the key servers.  If the key is found, it is 
imported or updated in
+ * the local key ring, and a dialogue is displayed.
+ *
+ * \note The passed fingerprint may be longer than 16 hex characters (see \ref libbalsa_gpgme_list_keys) and 
is truncated
+ *       appropriately by this function if necessary.
+ */
+gboolean libbalsa_gpgme_keyserver_op(const gchar  *fingerprint,
+                                                                        GtkWindow    *parent,
+                                                                        GError      **error);
+
+
+G_END_DECLS
+
+
+
+#endif /* LIBBALSA_GPGME_KEYSERVER_H_ */
diff --git a/libbalsa/libbalsa-gpgme-widgets.c b/libbalsa/libbalsa-gpgme-widgets.c
new file mode 100644
index 0000000..53d341b
--- /dev/null
+++ b/libbalsa/libbalsa-gpgme-widgets.c
@@ -0,0 +1,513 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/*
+ * Balsa E-Mail Client
+ *
+ * gpgme -related widgets
+ * Copyright (C) 2017 Albrecht Dreß <albrecht dress arcor de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "libbalsa-gpgme-widgets.h"
+#include <string.h>
+#include <glib/gi18n.h>
+#include "rfc3156.h"
+
+
+static gchar *create_status_str(gboolean revoked,
+                                                               gboolean expired,
+                                                               gboolean disabled,
+                                                               gboolean invalid)
+       G_GNUC_WARN_UNUSED_RESULT;
+static gint create_key_grid_row(GtkGrid     *grid,
+                                                               gint         row,
+                                                               const gchar *key,
+                                                               const gchar *value,
+                                                               gboolean     warn);
+static GtkWidget *create_key_uid_widget(const gpgme_user_id_t uid)
+       G_GNUC_WARN_UNUSED_RESULT;
+static GtkWidget *create_key_label_with_warn(const gchar *text,
+                                                                                        gboolean     warn)
+       G_GNUC_WARN_UNUSED_RESULT;
+static GtkWidget *create_subkey_widget(gpgme_subkey_t subkey)
+       G_GNUC_WARN_UNUSED_RESULT;
+static gchar *create_purpose_str(gboolean can_sign,
+                                                            gboolean can_encrypt,
+                                                                gboolean can_certify,
+                                                                gboolean can_auth)
+       G_GNUC_WARN_UNUSED_RESULT;
+
+
+/* documentation: see header file */
+GtkWidget *
+libbalsa_gpgme_key(gpgme_key_t           key,
+                                  const gchar          *fingerprint,
+                                  lb_gpg_subkey_capa_t  subkey_capa,
+                                  gboolean              expanded)
+{
+       GtkWidget *key_data;
+       gchar *status_str;
+       gchar *uid_readable;
+       gint row = 0;
+
+       g_return_val_if_fail(key != NULL, NULL);
+
+       key_data = gtk_grid_new();
+       gtk_grid_set_row_spacing(GTK_GRID(key_data), 2);
+       gtk_grid_set_column_spacing(GTK_GRID(key_data), 6);
+
+       /* print a warning for a bad key status */
+       status_str = create_status_str(key->expired != 0U, key->revoked != 0U, key->disabled != 0U, 
key->invalid != 0U);
+       if (strlen(status_str) > 0U) {
+               row = create_key_grid_row(GTK_GRID(key_data), row, _("Key status:"), status_str, TRUE);
+       }
+       g_free(status_str);
+
+       /* primary User ID */
+       if (key->uids == NULL) {
+               row = create_key_grid_row(GTK_GRID(key_data), row, _("User ID:"), _("None"), FALSE);
+       } else {
+               GtkWidget *label;
+
+               if (key->uids->next != NULL) {
+                       label = gtk_label_new(_("Primary user ID:"));
+               } else {
+                       label = gtk_label_new(_("User ID:"));
+               }
+               gtk_widget_set_halign(label, GTK_ALIGN_START);
+               gtk_grid_attach(GTK_GRID(key_data), label, 0, row, 1, 1);
+
+               gtk_grid_attach(GTK_GRID(key_data), create_key_uid_widget(key->uids), 1, row++, 1, 1);
+       }
+
+       /* owner trust is valid for OpenPGP only */
+       if (key->protocol == GPGME_PROTOCOL_OpenPGP) {
+               row = create_key_grid_row(GTK_GRID(key_data), row, _("Key owner trust:"),
+                       libbalsa_gpgme_validity_to_gchar_short(key->owner_trust), FALSE);
+       }
+
+       /* add additional UID's (if any) */
+       if ((key->uids != NULL) && (key->uids->next != NULL)) {
+               GtkWidget *uid_expander;
+               GtkWidget *uid_box;
+               gpgme_user_id_t uid;
+
+               uid_expander = gtk_expander_new(_("Additional User ID's"));
+               gtk_expander_set_expanded(GTK_EXPANDER(uid_expander), expanded);
+               gtk_grid_attach(GTK_GRID(key_data), uid_expander, 0, row++, 2, 1);
+
+               uid_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
+               gtk_widget_set_margin_start(uid_box, 12);
+               gtk_container_add(GTK_CONTAINER(uid_expander), uid_box);
+               for (uid = key->uids->next; uid != NULL; uid = uid->next) {
+                       gtk_box_pack_end(GTK_BOX(uid_box), create_key_uid_widget(uid), FALSE, FALSE, 0U);
+               }
+       }
+
+       /* add the issuer information for CMS only */
+       if (key->protocol == GPGME_PROTOCOL_CMS) {
+               GtkWidget *issuer_expander;
+               GtkWidget *issuer_grid;
+               gint issuer_row = 0;
+
+               issuer_expander = gtk_expander_new(_("Issuer"));
+               gtk_expander_set_expanded(GTK_EXPANDER(issuer_expander), expanded);
+               gtk_grid_attach(GTK_GRID(key_data), issuer_expander, 0, row++, 2, 1);
+
+               issuer_grid = gtk_grid_new();
+               gtk_widget_set_margin_start(issuer_grid, 12);
+               gtk_grid_set_column_spacing(GTK_GRID(issuer_grid), 6);
+               gtk_container_add(GTK_CONTAINER(issuer_expander), issuer_grid);
+
+               if (key->issuer_name != NULL) {
+                       uid_readable = libbalsa_cert_subject_readable(key->issuer_name);
+                       issuer_row = create_key_grid_row(GTK_GRID(issuer_grid), issuer_row, _("Name:"), 
uid_readable, FALSE);
+                       g_free(uid_readable);
+               }
+               if (key->issuer_serial != NULL) {
+                       issuer_row = create_key_grid_row(GTK_GRID(issuer_grid), issuer_row, _("Serial 
number:"), key->issuer_serial, FALSE);
+               }
+               if (key->chain_id != NULL) {
+                       (void) create_key_grid_row(GTK_GRID(issuer_grid), issuer_row, _("Chain ID:"), 
key->chain_id, FALSE);
+               }
+       }
+
+       /* subkey information */
+       if (((fingerprint != NULL) || (subkey_capa != 0U)) && (key->subkeys != NULL)) {
+               GtkWidget *subkey_expander;
+               GtkWidget *subkey_box;
+               gpgme_subkey_t subkey;
+
+               if (fingerprint != NULL) {
+                       subkey_expander = gtk_expander_new(_("Subkey used"));
+               } else if (subkey_capa != GPG_SUBKEY_CAP_ALL) {
+                       gchar *capa_str;
+                       gchar *label_str;
+
+                       /* indicate that we show only subkeys with certain capabilities */
+                       capa_str = create_purpose_str((subkey_capa & GPG_SUBKEY_CAP_SIGN) != 0U, (subkey_capa 
& GPG_SUBKEY_CAP_ENCRYPT) != 0U,
+                               (subkey_capa & GPG_SUBKEY_CAP_CERTIFY) != 0U, (subkey_capa & 
GPG_SUBKEY_CAP_AUTH) != 0U);
+                       label_str = g_strdup_printf(_("Subkeys (%s only)"), capa_str);
+                       subkey_expander = gtk_expander_new(label_str);
+                       g_free(label_str);
+                       g_free(capa_str);
+               } else {
+                       subkey_expander = gtk_expander_new(_("Subkeys"));
+               }
+               gtk_expander_set_expanded(GTK_EXPANDER(subkey_expander), expanded);
+               gtk_grid_attach(GTK_GRID(key_data), subkey_expander, 0, row++, 2, 1);
+
+               subkey_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
+               gtk_widget_set_margin_start(subkey_box, 12);
+               gtk_container_add(GTK_CONTAINER(subkey_expander), subkey_box);
+
+               for (subkey = key->subkeys; subkey != NULL; subkey = subkey->next) {
+                       if (fingerprint != NULL) {
+                               if (strcmp(fingerprint, subkey->fpr) == 0) {
+                                       gtk_box_pack_end(GTK_BOX(subkey_box), create_subkey_widget(subkey), 
FALSE, FALSE, 2);
+                               }
+                       } else if ((((subkey_capa & GPG_SUBKEY_CAP_SIGN) != 0U) && (subkey->can_sign != 0)) ||
+                                          (((subkey_capa & GPG_SUBKEY_CAP_ENCRYPT) != 0U) && 
(subkey->can_encrypt != 0)) ||
+                                          (((subkey_capa & GPG_SUBKEY_CAP_CERTIFY) != 0U) && 
(subkey->can_certify != 0)) ||
+                                          (((subkey_capa & GPG_SUBKEY_CAP_AUTH) != 0U) && 
(subkey->can_authenticate != 0))) {
+                               gtk_box_pack_end(GTK_BOX(subkey_box), create_subkey_widget(subkey), FALSE, 
FALSE, 2);
+                       } else {
+                               /* do not print this subkey */
+                       }
+               }
+       }
+
+       return key_data;
+}
+
+
+GtkWidget *
+libbalsa_key_dialog(GtkWindow            *parent,
+                                   GtkButtonsType                buttons,
+                                   gpgme_key_t           key,
+                                       lb_gpg_subkey_capa_t  subkey_capa,
+                                       const gchar          *message1,
+                                       const gchar          *message2)
+{
+       GtkWidget *dialog;
+       GtkWidget *hbox;
+       GtkWidget *icon;
+       GtkWidget *vbox;
+       GtkWidget *label;
+       GtkWidget *key_data;
+       GtkWidget *scrolledw;
+
+       g_return_val_if_fail(key != NULL, NULL);
+
+       switch (buttons) {
+       case GTK_BUTTONS_CLOSE:
+               dialog = gtk_dialog_new_with_buttons(NULL, parent, GTK_DIALOG_DESTROY_WITH_PARENT | 
libbalsa_dialog_flags(),
+                       _("Close"), GTK_RESPONSE_CLOSE, NULL);
+               break;
+       case GTK_BUTTONS_YES_NO:
+               dialog = gtk_dialog_new_with_buttons(NULL, parent, GTK_DIALOG_DESTROY_WITH_PARENT | 
libbalsa_dialog_flags(),
+                       _("No"), GTK_RESPONSE_NO, _("Yes"), GTK_RESPONSE_YES, NULL);
+               break;
+       default:
+               g_error("%s: buttons type %d not yet implemented", __func__, buttons);
+       }
+       gtk_window_set_resizable(GTK_WINDOW(dialog), TRUE);
+
+       hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
+       gtk_container_set_border_width(GTK_CONTAINER(hbox), 6);
+       gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), hbox, TRUE, TRUE, 0);
+       gtk_box_set_homogeneous(GTK_BOX(hbox), FALSE);
+
+       /* standard key icon; "application-certificate" would be an alternative... */
+       icon = gtk_image_new_from_icon_name("dialog-password", GTK_ICON_SIZE_DIALOG);
+       gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 0);
+       gtk_widget_set_valign(icon, GTK_ALIGN_START);
+
+       vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
+       gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
+       gtk_box_set_homogeneous(GTK_BOX(vbox), FALSE);
+
+       if (message1 != NULL) {
+               char *markup;
+
+               label = gtk_label_new(NULL);
+               markup = g_markup_printf_escaped("<b><big>%s</big></b>", message1);
+               gtk_label_set_markup(GTK_LABEL(label), markup);
+               g_free(markup);
+               gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+               gtk_label_set_line_wrap(GTK_LABEL(label), FALSE);
+       }
+
+       if (message2 != NULL) {
+               label = gtk_label_new(message2);
+               gtk_widget_set_halign(label, GTK_ALIGN_START);
+               gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+               gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+       }
+
+       scrolledw = gtk_scrolled_window_new(NULL, NULL);
+       gtk_box_pack_start(GTK_BOX(vbox), scrolledw, TRUE, TRUE, 6);
+       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledw), GTK_POLICY_NEVER, 
GTK_POLICY_AUTOMATIC);
+       gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(scrolledw), 120);
+
+       key_data = libbalsa_gpgme_key(key, NULL, subkey_capa, TRUE);
+       gtk_container_add(GTK_CONTAINER(scrolledw), key_data);
+
+       gtk_widget_show_all(hbox);
+
+       return dialog;
+}
+
+
+/* ---- local stuff ---------------------------------------------------- */
+
+/** \brief Create a status string for an item
+ *
+ * \param revoked TRUE if the item is revoked
+ * \param expired TRUE if the item is expired
+ * \param disabled TRUE if the item is disabled
+ * \param invalid TRUE if the item is invalid
+ * \return a human-readable string containing the states which may be empty
+ *
+ * \todo Add the is_qualified ([sub]key can be used for qualified signatures according to local government 
regulations) flag?
+ */
+static gchar *
+create_status_str(gboolean revoked,
+                                 gboolean expired,
+                                 gboolean disabled,
+                                 gboolean invalid)
+{
+       GString *status;
+
+       status = g_string_new(NULL);
+       if (revoked) {
+               status = g_string_append(status, _("revoked"));
+       }
+       if (expired) {
+               g_string_append_printf(status, "%s%s", (status->len > 0U) ? ", " : "", _("expired"));
+       }
+       if (disabled) {
+               g_string_append_printf(status, "%s%s", (status->len > 0U) ? ", " : "", _("disabled"));
+       }
+       if (invalid) {
+               g_string_append_printf(status, "%s%s", (status->len > 0U) ? ", " : "", _("invalid"));
+       }
+       return g_string_free(status, FALSE);
+}
+
+
+/** \brief Add a grid row
+ *
+ * \param grid target grid
+ * \param row grid row index
+ * \param key key string in the 1st grid column
+ * \param value value string in the 2nd grid column
+ * \param warn prepend a warning icon and print \em value in red if TRUE
+ * \return the next grid row index
+ */
+static gint
+create_key_grid_row(GtkGrid     *grid,
+                                       gint         row,
+                                       const gchar *key,
+                                       const gchar *value,
+                                       gboolean     warn)
+{
+       GtkWidget *label;
+
+       label = gtk_label_new(key);
+       gtk_widget_set_halign(label, GTK_ALIGN_START);
+       gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1);
+
+       label = create_key_label_with_warn(value, warn);
+       gtk_widget_set_halign(label, GTK_ALIGN_START);
+       gtk_widget_set_hexpand(label, TRUE);
+       gtk_grid_attach(GTK_GRID(grid), label, 1, row, 1, 1);
+
+       return row + 1;
+}
+
+
+/** \brief Create a key's UID widget
+ *
+ * \param uid UID
+ * \return the uid widget
+ *
+ * Create a widget containing the passed UID subject.  If the UID is revoked or invalid, a warning icon is 
prepended, and the
+ * status information is added.
+ *
+ * \todo We might want to show the TOFU data (requires gpgme >= 1.7.0, and GPGME_KEYLIST_MODE_WITH_TOFU in 
key listing options).
+ *       Maybe also add an expander?
+ */
+static GtkWidget *
+create_key_uid_widget(const gpgme_user_id_t uid)
+{
+       gchar *uid_readable;
+       gchar *uid_status;
+       GtkWidget *result;
+
+       uid_readable = libbalsa_cert_subject_readable(uid->uid);
+       uid_status = create_status_str(uid->revoked != 0U, FALSE, FALSE, uid->invalid != 0U);
+       if (strlen(uid_status) > 0U) {
+               gchar *buf;
+
+               buf = g_strdup_printf("%s (%s)", uid_readable, uid_status);
+               result = create_key_label_with_warn(buf, TRUE);
+               g_free(buf);
+       } else {
+               result = create_key_label_with_warn(uid_readable, FALSE);
+       }
+       g_free(uid_status);
+       g_free(uid_readable);
+       return result;
+}
+
+
+/** \brief Create a label with warning information
+ *
+ * \param text label text
+ * \param warn TRUE if a visible warning shall be included
+ * \return the widget
+ *
+ * If \em warn is FALSE, the returned item is simply a GtkLabel.  Otherwise, it is a box, containing the 
label printed in red, with
+ * a warning icon prepended.  In both cases, the label is start-justified, selectable and line-wrappable.
+ */
+static GtkWidget *
+create_key_label_with_warn(const gchar *text,
+                                                  gboolean     warn)
+{
+       GtkWidget *result;
+
+       if (warn) {
+               GtkWidget *icon;
+               GtkWidget *label;
+               gchar *buf;
+
+               result = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+               icon = gtk_image_new_from_icon_name("gtk-dialog-warning", GTK_ICON_SIZE_MENU);
+               gtk_box_pack_start(GTK_BOX(result), icon, FALSE, FALSE, 0U);
+               buf = g_markup_printf_escaped("<span fgcolor=\"red\">%s</span>", text);
+               label = gtk_label_new(NULL);
+               gtk_label_set_markup(GTK_LABEL(label), buf);
+               g_free(buf);
+               gtk_widget_set_halign(label, GTK_ALIGN_START);
+               gtk_widget_set_hexpand(label, TRUE);
+               gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+               gtk_label_set_selectable(GTK_LABEL(label), TRUE);
+               gtk_box_pack_start(GTK_BOX(result), label, FALSE, TRUE, 0U);
+       } else {
+               result = gtk_label_new(text);
+               gtk_widget_set_halign(result, GTK_ALIGN_START);
+               gtk_widget_set_hexpand(result, TRUE);
+               gtk_label_set_selectable(GTK_LABEL(result), TRUE);
+               gtk_label_set_line_wrap(GTK_LABEL(result), TRUE);
+       }
+
+       return result;
+}
+
+
+/** \brief Create a widget with subkey information
+ *
+ * \param subkey the subkey which shall be added
+ * \return a nes GtkGrid containing the subkey information
+ *
+ * \note Time stamps are formatted using "%x %X" (preferred locale's date and time format), so we can avoid 
passing Balsa's
+ *       user-defined format string down here.  We might want to change this.
+ */
+static GtkWidget *
+create_subkey_widget(gpgme_subkey_t subkey)
+{
+       GtkWidget *subkey_grid;
+       gint subkey_row = 0;
+       gchar *status_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);
+       }
+       g_free(status_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,
+               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);
+       }
+       g_free(status_str);
+
+       if (subkey->timestamp == -1) {
+               timebuf = g_strdup(_("invalid timestamp"));
+       } else if (subkey->timestamp == 0) {
+               timebuf = g_strdup(_("not available"));
+       } else {
+               timebuf = libbalsa_date_to_utf8(subkey->timestamp, "%x %X");
+       }
+       subkey_row = create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Created:"), timebuf, FALSE);
+       g_free(timebuf);
+
+       if (subkey->expires == 0) {
+               timebuf = g_strdup(_("never"));
+       } 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);
+       g_free(timebuf);
+
+       return subkey_grid;
+}
+
+
+/** \brief Create a subkey purpose string
+ *
+ * \param can_sign indicates that the subkey can be used to create data signatures
+ * \param can_encrypt indicates that the subkey can be used for encryption
+ * \param can_certify indicates that the subkey can be used to create key certificates
+ * \param can_auth indicates that the subkey can be used for authentication
+ * \return a string indicating whether a subkey can be used for signing, encryption, certification or 
authentication
+ *
+ * \todo Add the is_qualified (subkey can be used for qualified signatures according to local government 
regulations) and is_de_vs
+ *       (complies with the rules for classified information in Germany at the restricted level, VS-NfD, 
requires gpgme >= 1.9.0)
+ *       flags?
+ */
+static gchar *
+create_purpose_str(gboolean can_sign,
+                                  gboolean can_encrypt,
+                                  gboolean can_certify,
+                                  gboolean can_auth)
+{
+       GString *purpose;
+
+       purpose = g_string_new(NULL);
+
+       if (can_sign) {
+               purpose = g_string_append(purpose, _("sign"));
+       }
+       if (can_encrypt) {
+               g_string_append_printf(purpose, "%s%s", (purpose->len > 0U) ? ", " : "", _("encrypt"));
+       }
+       if (can_certify) {
+               g_string_append_printf(purpose, "%s%s", (purpose->len > 0U) ? ", " : "", _("certify"));
+       }
+       if (can_auth) {
+               g_string_append_printf(purpose, "%s%s", (purpose->len > 0U) ? ", " : "", _("authenticate"));
+       }
+       return g_string_free(purpose, FALSE);
+}
diff --git a/libbalsa/libbalsa-gpgme-widgets.h b/libbalsa/libbalsa-gpgme-widgets.h
new file mode 100644
index 0000000..21723be
--- /dev/null
+++ b/libbalsa/libbalsa-gpgme-widgets.h
@@ -0,0 +1,91 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/*
+ * Balsa E-Mail Client
+ *
+ * gpgme -related widgets
+ * Copyright (C) 2017 Albrecht Dreß <albrecht dress arcor de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBBALSA_GPGME_WIDGETS_H_
+#define LIBBALSA_GPGME_WIDGETS_H_
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gpgme.h>
+#include <gtk/gtk.h>
+
+
+G_BEGIN_DECLS
+
+
+/* defines the capabilities a subkey can have */
+typedef enum {
+       GPG_SUBKEY_CAP_SIGN     = (1U << 0),
+       GPG_SUBKEY_CAP_ENCRYPT = (1U << 1),
+       GPG_SUBKEY_CAP_CERTIFY = (1U << 2),
+       GPG_SUBKEY_CAP_AUTH = (1U << 3)
+} lb_gpg_subkey_capa_t;
+
+#define GPG_SUBKEY_CAP_ALL (GPG_SUBKEY_CAP_SIGN + GPG_SUBKEY_CAP_ENCRYPT + GPG_SUBKEY_CAP_CERTIFY + 
GPG_SUBKEY_CAP_AUTH)
+
+
+/** \brief Create a key widget
+ *
+ * \param key GnuPG or S/MIME key
+ * \param fingerprint fingerprint of the subkey which shall be displayed, NULL to display all subkeys
+ * \param subkey_capa mask of capabilities for which subkeys shall be included, used only if \em fingerprint 
is NULL
+ * \param expanded whether the expanders shall be initially expanded
+ * \return a new widget containing details about the key
+ *
+ * Create a widget containing most information about the key, including all UID's, all requested subkeys and 
the issuer (S/MIME
+ * only).  Note that no information about the OpenPGP signatures of the UID's are included, as it is 
expensive to retrieve all
+ * signatures of a key.
+ */
+GtkWidget *libbalsa_gpgme_key(gpgme_key_t           key,
+                                                         const gchar          *fingerprint,
+                                                         lb_gpg_subkey_capa_t  subkey_capa,
+                                                         gboolean              expanded)
+       G_GNUC_WARN_UNUSED_RESULT;
+
+
+/** \brief Create a key message dialogue
+ *
+ * \param parent transient parent window, may be NULL
+ * \param buttons set of buttons to use (currently only GTK_BUTTONS_CLOSE and GTK_BUTTONS_YES_NO are 
implemented)
+ * \param key key data which shall be displayed
+ * \param subkey_capa mask of capabilities for which subkeys shall be included
+ * \param message1 primary message, printed centred in bold and a little larger, may be NULL to omit
+ * \param message2 secondary message, printed start-aligned id normal font, may be NULL to omit
+ * \return the new dialogue
+ *
+ * Create a new dialogue, similar to e.g. gtk_message_dialog_new().
+ */
+GtkWidget *libbalsa_key_dialog(GtkWindow            *parent,
+                                                          GtkButtonsType                buttons,
+                                                          gpgme_key_t           key,
+                                                          lb_gpg_subkey_capa_t  subkey_capa,
+                                                          const gchar          *message1,
+                                                          const gchar          *message2)
+       G_GNUC_WARN_UNUSED_RESULT;
+
+
+G_END_DECLS
+
+
+#endif /* LIBBALSA_GPGME_WIDGETS_H_ */
diff --git a/libbalsa/libbalsa-gpgme.c b/libbalsa/libbalsa-gpgme.c
index d9c6f49..d0641bd 100644
--- a/libbalsa/libbalsa-gpgme.c
+++ b/libbalsa/libbalsa-gpgme.c
@@ -37,16 +37,10 @@
 #include <gtk/gtk.h>
 #include <gmime/gmime.h>
 #include "gmime-gpgme-signature.h"
+#include "libbalsa-gpgme-keys.h"
 #include "libbalsa-gpgme.h"
 
 
-static void g_set_error_from_gpgme(GError ** error,
-                                  gpgme_error_t gpgme_err,
-                                  const gchar * message);
-static gpgme_error_t gpgme_new_with_protocol(gpgme_ctx_t * ctx,
-                                            gpgme_protocol_t protocol,
-                                            GtkWindow * parent,
-                                            GError ** error);
 static gboolean gpgme_add_signer(gpgme_ctx_t ctx, const gchar * signer,
                                 GtkWindow * parent, GError ** error);
 static gpgme_key_t *gpgme_build_recipients(gpgme_ctx_t ctx,
@@ -63,6 +57,8 @@ static ssize_t g_mime_gpgme_stream_wr(GMimeStream * stream, void *buffer,
                                      size_t size);
 static void cb_data_release(void *handle);
 
+static gchar *utf8_valid_str(const char *gpgme_str)
+       G_GNUC_WARN_UNUSED_RESULT;
 
 #if defined(ENABLE_NLS)
 static const gchar *get_utf8_locale(int category);
@@ -105,12 +101,6 @@ libbalsa_gpgme_init(gpgme_passphrase_cb_t get_passphrase,
     /* initialise the gpgme library */
     g_message("init gpgme version %s", gpgme_check_version(NULL));
 
-#ifdef HAVE_GPG
-    /* configure the GnuPG engine if a specific gpg path has been
-     * detected */
-    gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP, GPG_PATH, NULL);
-#endif
-
 #ifdef ENABLE_NLS
     gpgme_set_locale(NULL, LC_CTYPE, get_utf8_locale(LC_CTYPE));
     gpgme_set_locale(NULL, LC_MESSAGES, get_utf8_locale(LC_MESSAGES));
@@ -146,19 +136,13 @@ libbalsa_gpgme_init(gpgme_passphrase_cb_t get_passphrase,
        has_proto_openpgp = FALSE;
     }
 
-#ifdef HAVE_SMIME
-    if (gpgme_engine_check_version(GPGME_PROTOCOL_CMS) ==
-       GPG_ERR_NO_ERROR) {
-       g_message("CMS (aka S/MIME) protocol supported");
-       has_proto_cms = TRUE;
+    if (gpgme_engine_check_version(GPGME_PROTOCOL_CMS) == GPG_ERR_NO_ERROR) {
+       g_message("CMS (aka S/MIME) protocol supported");
+       has_proto_cms = TRUE;
     } else {
-       g_message("CMS protocol not supported, S/MIME will not work!");
-       has_proto_cms = FALSE;
+       g_message("CMS protocol not supported, S/MIME will not work!");
+       has_proto_cms = FALSE;
     }
-#else
-    g_message("built without CMS (aka S/MIME) protocol support");
-    has_proto_cms = FALSE;
-#endif
 
     /* remember callbacks */
     select_key_cb = select_key;
@@ -187,6 +171,44 @@ libbalsa_gpgme_check_crypto_engine(gpgme_protocol_t protocol)
 }
 
 
+/** \brief Create a new GpgME context for a protocol
+ *
+ * \param protocol requested protocol
+ * \param callback callback to request a passphrase, may be NULL to use pinentry (recommended)
+ * \param parent parent window, passed to the callback function
+ * \param error Filled with error information on error.
+ * \return the new gpgme context on success, or NULL on error
+ *
+ * This helper function creates a new GpgME context for the specified protocol.
+ */
+gpgme_ctx_t
+libbalsa_gpgme_new_with_proto(gpgme_protocol_t        protocol,
+                                                         gpgme_passphrase_cb_t   callback,
+                                                         GtkWindow                              *parent,
+                                                         GError                **error)
+{
+       gpgme_error_t err;
+       gpgme_ctx_t ctx = NULL;
+
+    /* create the GpgME context */
+       err = gpgme_new(&ctx);
+       if (err != GPG_ERR_NO_ERROR) {
+               libbalsa_gpgme_set_error(error, err, _("could not create context"));
+       } else {
+               err = gpgme_set_protocol(ctx, protocol);
+               if (err != GPG_ERR_NO_ERROR) {
+                       libbalsa_gpgme_set_error(error, err, _("could not set protocol “%s”"), 
gpgme_get_protocol_name(protocol));
+                   gpgme_release(ctx);
+                   ctx = NULL;
+               } else {
+                       gpgme_set_passphrase_cb(ctx, callback, parent);
+               }
+       }
+
+       return ctx;
+}
+
+
 /** \brief Verify a signature
  *
  * \param content GMime stream of the signed matter.
@@ -224,17 +246,17 @@ libbalsa_gpgme_verify(GMimeStream * content, GMimeStream * sig_plain,
     g_return_val_if_fail(protocol == GPGME_PROTOCOL_OpenPGP ||
                         protocol == GPGME_PROTOCOL_CMS, NULL);
 
-    /* create the GpgME context */
-    if ((err =
-        gpgme_new_with_protocol(&ctx, protocol, NULL,
-                                error)) != GPG_ERR_NO_ERROR)
-       return NULL;
+    /* create the GpgME context (no passphrase callback needed) */
+    ctx = libbalsa_gpgme_new_with_proto(protocol, NULL, NULL, error);
+    if (ctx == NULL) {
+       return NULL;
+    }
 
     /* create the message stream */
     if ((err =
         gpgme_data_new_from_cbs(&cont_data, &cbs,
                                 content)) != GPG_ERR_NO_ERROR) {
-       g_set_error_from_gpgme(error, err,
+       libbalsa_gpgme_set_error(error, err,
                               _("could not get data from stream"));
        gpgme_release(ctx);
        return NULL;
@@ -245,7 +267,7 @@ libbalsa_gpgme_verify(GMimeStream * content, GMimeStream * sig_plain,
     if ((err =
         gpgme_data_new_from_cbs(&sig_plain_data, &cbs,
                                 sig_plain)) != GPG_ERR_NO_ERROR) {
-       g_set_error_from_gpgme(error, err,
+       libbalsa_gpgme_set_error(error, err,
                               _("could not get data from stream"));
        gpgme_data_release(cont_data);
        gpgme_release(ctx);
@@ -258,7 +280,7 @@ libbalsa_gpgme_verify(GMimeStream * content, GMimeStream * sig_plain,
     else
        err = gpgme_op_verify(ctx, sig_plain_data, cont_data, NULL);
     if (err != GPG_ERR_NO_ERROR) {
-       g_set_error_from_gpgme(error, err,
+       libbalsa_gpgme_set_error(error, err,
                               _("signature verification failed"));
        result = g_mime_gpgme_sigstat_new();
        result->status = err;
@@ -319,10 +341,11 @@ libbalsa_gpgme_sign(const gchar * userid, GMimeStream * istream,
     g_return_val_if_fail(protocol == GPGME_PROTOCOL_OpenPGP ||
                         protocol == GPGME_PROTOCOL_CMS, GPGME_MD_NONE);
 
-    /* create the GpgME context */
-    if (gpgme_new_with_protocol(&ctx, protocol, parent,
-                                error) != GPG_ERR_NO_ERROR)
-       return GPGME_MD_NONE;
+    /* create the GpgME context (passphrase callback required) */
+    ctx = libbalsa_gpgme_new_with_proto(protocol, gpgme_passphrase_cb, parent, error);
+    if (ctx == NULL) {
+       return GPGME_MD_NONE;
+    }
 
     /* set the signature mode */
     if (singlepart_mode) {
@@ -346,7 +369,7 @@ libbalsa_gpgme_sign(const gchar * userid, GMimeStream * istream,
     if ((err =
         gpgme_data_new_from_cbs(&in, &cbs,
                                 istream)) != GPG_ERR_NO_ERROR) {
-       g_set_error_from_gpgme(error, err,
+       libbalsa_gpgme_set_error(error, err,
                               _("could not get data from stream"));
        gpgme_release(ctx);
        return GPGME_MD_NONE;
@@ -354,7 +377,7 @@ libbalsa_gpgme_sign(const gchar * userid, GMimeStream * istream,
     if ((err =
         gpgme_data_new_from_cbs(&out, &cbs,
                                 ostream)) != GPG_ERR_NO_ERROR) {
-       g_set_error_from_gpgme(error, err,
+       libbalsa_gpgme_set_error(error, err,
                               _("could not create new data object"));
        gpgme_data_release(in);
        gpgme_release(ctx);
@@ -364,7 +387,7 @@ libbalsa_gpgme_sign(const gchar * userid, GMimeStream * istream,
     /* sign and get the used hash algorithm */
     err = gpgme_op_sign(ctx, in, out, sig_mode);
     if (err != GPG_ERR_NO_ERROR) {
-       g_set_error_from_gpgme(error, err, _("signing failed"));
+       libbalsa_gpgme_set_error(error, err, _("signing failed"));
        hash_algo = GPGME_MD_NONE;
     } else
        hash_algo = gpgme_op_sign_result(ctx)->signatures->hash_algo;
@@ -426,10 +449,11 @@ libbalsa_gpgme_encrypt(GPtrArray * recipients, const char *sign_for,
     g_return_val_if_fail(protocol == GPGME_PROTOCOL_OpenPGP ||
                         protocol == GPGME_PROTOCOL_CMS, FALSE);
 
-    /* create the GpgME context */
-    if (gpgme_new_with_protocol(&ctx, protocol, parent,
-                                error) != GPG_ERR_NO_ERROR)
-       return FALSE;
+    /* create the GpgME context (passphrase callback required) */
+    ctx = libbalsa_gpgme_new_with_proto(protocol, gpgme_passphrase_cb, parent, error);
+    if (ctx == NULL) {
+       return FALSE;
+    }
 
     /* sign & encrypt is valid only for single-part OpenPGP */
     if (sign_for != NULL
@@ -468,7 +492,7 @@ libbalsa_gpgme_encrypt(GPtrArray * recipients, const char *sign_for,
     if ((err =
         gpgme_data_new_from_cbs(&plain, &cbs,
                                 istream)) != GPG_ERR_NO_ERROR) {
-       g_set_error_from_gpgme(error, err,
+       libbalsa_gpgme_set_error(error, err,
                               _("could not get data from stream"));
        release_keylist(rcpt_keys);
        gpgme_release(ctx);
@@ -477,7 +501,7 @@ libbalsa_gpgme_encrypt(GPtrArray * recipients, const char *sign_for,
     if ((err =
         gpgme_data_new_from_cbs(&crypt, &cbs,
                                 ostream)) != GPG_ERR_NO_ERROR) {
-       g_set_error_from_gpgme(error, err,
+       libbalsa_gpgme_set_error(error, err,
                               _("could not create new data object"));
        release_keylist(rcpt_keys);
        gpgme_data_release(plain);
@@ -504,10 +528,10 @@ libbalsa_gpgme_encrypt(GPtrArray * recipients, const char *sign_for,
     gpgme_release(ctx);
     if (err != GPG_ERR_NO_ERROR) {
        if (sign_for)
-           g_set_error_from_gpgme(error, err,
+               libbalsa_gpgme_set_error(error, err,
                                   _("signing and encryption failed"));
        else
-           g_set_error_from_gpgme(error, err, _("encryption failed"));
+               libbalsa_gpgme_set_error(error, err, _("encryption failed"));
        return FALSE;
     } else
        return TRUE;
@@ -551,16 +575,17 @@ libbalsa_gpgme_decrypt(GMimeStream * crypted, GMimeStream * plain,
     g_return_val_if_fail(protocol == GPGME_PROTOCOL_OpenPGP ||
                         protocol == GPGME_PROTOCOL_CMS, NULL);
 
-    /* create the GpgME context */
-    if (gpgme_new_with_protocol(&ctx, protocol, parent,
-                                error) != GPG_ERR_NO_ERROR)
-       return NULL;
+    /* create the GpgME context (passphrase callback required) */
+    ctx = libbalsa_gpgme_new_with_proto(protocol, gpgme_passphrase_cb, parent, error);
+    if (ctx == NULL) {
+       return NULL;
+    }
 
     /* create the data streams */
     if ((err =
         gpgme_data_new_from_cbs(&crypt_data, &cbs,
                                 crypted)) != GPG_ERR_NO_ERROR) {
-       g_set_error_from_gpgme(error, err,
+       libbalsa_gpgme_set_error(error, err,
                               _("could not get data from stream"));
        gpgme_release(ctx);
        return NULL;
@@ -568,7 +593,7 @@ libbalsa_gpgme_decrypt(GMimeStream * crypted, GMimeStream * plain,
     if ((err =
         gpgme_data_new_from_cbs(&plain_data, &cbs,
                                 plain)) != GPG_ERR_NO_ERROR) {
-       g_set_error_from_gpgme(error, err,
+       libbalsa_gpgme_set_error(error, err,
                               _("could not create new data object"));
        gpgme_data_release(crypt_data);
        gpgme_release(ctx);
@@ -579,7 +604,7 @@ libbalsa_gpgme_decrypt(GMimeStream * crypted, GMimeStream * plain,
     if ((err =
         gpgme_op_decrypt_verify(ctx, crypt_data,
                                 plain_data)) != GPG_ERR_NO_ERROR) {
-       g_set_error_from_gpgme(error, err, _("decryption failed"));
+       libbalsa_gpgme_set_error(error, err, _("decryption failed"));
        result = NULL;
     } else {
        /* decryption successful, check for signature */
@@ -594,6 +619,37 @@ libbalsa_gpgme_decrypt(GMimeStream * crypted, GMimeStream * plain,
     return result;
 }
 
+
+/*
+ * set a GError form GpgME information
+ */
+void
+libbalsa_gpgme_set_error(GError        **error,
+                                            gpgme_error_t   gpgme_err,
+                                                const gchar    *format,
+                                                ...)
+{
+    if (error != NULL) {
+       gchar errbuf[4096];             /* should be large enough... */
+        gchar *errstr;
+        gchar *srcstr;
+        gchar *msgstr;
+        va_list ap;
+
+        srcstr = utf8_valid_str(gpgme_strsource(gpgme_err));
+        gpgme_strerror_r(gpgme_err, errbuf, sizeof(errbuf));
+        errstr = utf8_valid_str(errbuf);
+        va_start(ap, format);
+        msgstr = g_strdup_vprintf(format, ap);
+        va_end(ap);
+        g_set_error(error, GPGME_ERROR_QUARK, gpgme_err, "%s: %s: %s", srcstr, msgstr, errstr);
+        g_free(msgstr);
+        g_free(errstr);
+        g_free(srcstr);
+    }
+}
+
+
 /* ---- local stuff ---------------------------------------------------- */
 
 static gchar *
@@ -616,29 +672,6 @@ utf8_valid_str(const char *gpgme_str)
 
 
 /*
- * set a GError form GpgME information
- */
-static void
-g_set_error_from_gpgme(GError ** error, gpgme_error_t gpgme_err,
-                      const gchar * message)
-{
-    if (error != NULL) {
-       gchar errbuf[4096];             /* should be large enough... */
-        gchar *errstr;
-        gchar *srcstr;
-
-        srcstr = utf8_valid_str(gpgme_strsource(gpgme_err));
-        gpgme_strerror_r(gpgme_err, errbuf, sizeof(errbuf));
-        errstr = utf8_valid_str(errbuf);
-        g_set_error(error, GPGME_ERROR_QUARK, gpgme_err, "%s: %s: %s", srcstr,
-               message, errstr);
-        g_free(srcstr);
-        g_free(errstr);
-    }
-}
-
-
-/*
  * callback to get data from a stream
  */
 static ssize_t
@@ -674,320 +707,223 @@ cb_data_release(void *handle)
 }
 
 
-/*
- * create a GpgME context for the passed protocol
+/** \brief Get a key for a name or fingerprint
+ *
+ * \param ctx GpgME context
+ * \param key filled with the key on success
+ * \param name pattern (mail address or fingerprint) of the requested key
+ * \param secret TRUE to select a secret (private) key for signing, FALSE to select a public key for 
encryption
+ * \param accept_all TRUE to accept a low-trust public key without confirmation
+ * \param parent transient parent window
+ * \param error filled with a human-readable error on error, may be NULL
+ * \return GPG_ERR_GENERAL if listing the keys failed, GPG_ERR_NO_KEY if no suitable key is available, 
GPG_ERR_CANCELED if the user
+ *         cancelled the operation, GPG_ERR_AMBIGUOUS if multiple keys exist, or GPG_ERR_NOT_TRUSTED if the 
key is not trusted
+ *
+ * Get a key for a name or a fingerprint.  A name will always be enclosed in "<...>" to get an exact match.  
If \em secret is set,
+ * choose only secret (private) keys (signing).  Otherwise, choose only public keys (encryption).  If 
multiple keys would match,
+ * call the key selection CB \ref select_key_cb (if present).  If no matching key could be found or if any 
error occurs, return an
+ * appropriate error code.
  */
 static gpgme_error_t
-gpgme_new_with_protocol(gpgme_ctx_t * ctx, gpgme_protocol_t protocol,
-                       GtkWindow * parent, GError ** error)
+get_key_from_name(gpgme_ctx_t   ctx,
+                                 gpgme_key_t  *key,
+                                 const gchar  *name,
+                                 gboolean      secret,
+                                 gboolean      accept_all,
+                                 GtkWindow    *parent,
+                                 GError      **error)
 {
-    gpgme_error_t err;
-
+       gchar *mail_name;
+       gboolean list_res;
+       GList *keys = NULL;
+       gpgme_key_t selected;
+       guint bad_keys = 0U;
+       gpgme_error_t result;
 
-    /* create the GpgME context */
-    if ((err = gpgme_new(ctx)) != GPG_ERR_NO_ERROR) {
-       g_set_error_from_gpgme(error, err, _("could not create context"));
-    } else {
-       if ((err = gpgme_set_protocol(*ctx, protocol)) != GPG_ERR_NO_ERROR) {
-           gchar *errmsg =
-               g_strdup_printf(_("could not set protocol “%s”"),
-                               gpgme_get_protocol_name(protocol));
-
-           g_set_error_from_gpgme(error, err, errmsg);
-           g_free(errmsg);
-           gpgme_release(*ctx);
+       /* enclose a mail address into "<...>" to perform an exact search */
+       if (strchr(name, '@') != NULL) {
+               mail_name = g_strconcat("<", name, ">", NULL);
        } else {
-           if (protocol == GPGME_PROTOCOL_OpenPGP)
-               gpgme_set_passphrase_cb(*ctx, gpgme_passphrase_cb, parent);
+               mail_name = g_strdup(name);
        }
-    }
 
-    return err;
-}
+       /* let gpgme list keys */
+       list_res = libbalsa_gpgme_list_keys(ctx, &keys, &bad_keys, mail_name, secret, FALSE, error);
+       g_free(mail_name);
+       if (!list_res) {
+               return GPG_ERR_GENERAL;
+       }
 
-/* return TRUE if the passed key is not expired, not revoked, not disabled
- * and not invalid, and has at least one subkey which can be used for signing
- * if secret is TRUE, or one subkey which can be used for encryption if secret
- * is FALSE, and the particular subkey is not expired, not revoked, not
- * disabled and not invalid */
-static gboolean
-check_key(const gpgme_key_t key, gboolean secret, time_t now)
-{
-       gboolean result = FALSE;
-
-       if (!key->expired && !key->revoked && !key->disabled && !key->invalid) {
-               gpgme_subkey_t subkey = key->subkeys;
-
-               while (!result && (subkey != NULL)) {
-                       if (((secret && subkey->can_sign) || (!secret && subkey->can_encrypt)) &&
-                               !subkey->expired && !subkey->revoked && !subkey->disabled && !subkey->invalid 
&&
-                               (subkey->expires == 0 || subkey->expires > now)) {
-                               result = TRUE;
-                       } else {
-                               subkey = subkey->next;
-                       }
+       if (keys == NULL) {
+               if (bad_keys > 0U) {
+                       g_set_error(error, GPGME_ERROR_QUARK, GPG_ERR_KEY_SELECTION,
+                               _("A key for “%s” is present, but it is expired, disabled, revoked or 
invalid"), name);
+               } else {
+                       g_set_error(error, GPGME_ERROR_QUARK, GPG_ERR_KEY_SELECTION,
+                               _("Could not find a key for “%s”"), name);
                }
+               return secret ? GPG_ERR_NO_SECKEY : GPG_ERR_NO_PUBKEY;
        }
 
-       return result;
-}
-
-/*
- * Get a key for name. If secret_only is set, choose only secret (private)
- * keys (signing). Otherwise, choose only public keys (encryption).
- * If multiple keys would match, call the key selection CB (if present). If
- * no matching key could be found or if any error occurs, return NULL and
- * set error.
- */
-static gpgme_key_t
-get_key_from_name(gpgme_ctx_t ctx, const gchar * name, gboolean secret,
-                 gboolean accept_all, GtkWindow * parent, GError ** error)
-{
-    GList *keys = NULL;
-    gpgme_key_t key;
-    gpgme_error_t err;
-    gboolean found_bad;
-    time_t now = time(NULL);
-
-    /* let gpgme list keys */
-    if ((err =
-        gpgme_op_keylist_start(ctx, name, secret)) != GPG_ERR_NO_ERROR) {
-       gchar *msg =
-           g_strdup_printf(_("could not list keys for “%s”"), name);
-
-       g_set_error_from_gpgme(error, err, msg);
-       g_free(msg);
-       return NULL;
-    }
-
-    found_bad = FALSE;
-    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, secret, now)) {
-               keys = g_list_append(keys, key);
-       } else
-           found_bad = TRUE;
-    }
-
-    if (gpg_err_code(err) != GPG_ERR_EOF) {
-       gchar *msg =
-           g_strdup_printf(_("could not list keys for “%s”"), name);
-
-       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);
-
-    if (!keys) {
-       if (error) {
-           if (strchr(name, '@')) {
-               if (found_bad)
-                   g_set_error(error, GPGME_ERROR_QUARK,
-                               GPG_ERR_KEY_SELECTION,
-                               _
-                               ("%s: a key for %s is present, but it is expired, disabled, revoked or 
invalid"),
-                               "gmime-gpgme", name);
-               else
-                   g_set_error(error, GPGME_ERROR_QUARK,
-                               GPG_ERR_KEY_SELECTION,
-                               _("%s: could not find a key for %s"),
-                               "gmime-gpgme", name);
-           } else {
-               if (found_bad)
-                   g_set_error(error, GPGME_ERROR_QUARK,
-                               GPG_ERR_KEY_SELECTION,
-                               _
-                               ("%s: a key with id %s is present, but it is expired, disabled, revoked or 
invalid"),
-                               "gmime-gpgme", name);
-               else
-                   g_set_error(error, GPGME_ERROR_QUARK,
-                               GPG_ERR_KEY_SELECTION,
-                               _("%s: could not find a key with id %s"),
-                               "gmime-gpgme", name);
-           }
+       /* let the user select a key from the list if there is more than one */
+       result = GPG_ERR_NO_ERROR;
+       if (g_list_length(keys) > 1U) {
+               if (select_key_cb != NULL) {
+                       selected = select_key_cb(name, secret ? LB_SELECT_PRIVATE_KEY : 
LB_SELECT_PUBLIC_KEY_USER,
+                               keys, gpgme_get_protocol(ctx), parent);
+                       if (selected == NULL) {
+                               result = GPG_ERR_CANCELED;
+                       }
+               } else {
+                       g_set_error(error, GPGME_ERROR_QUARK, GPG_ERR_KEY_SELECTION, _("Multiple keys for 
“%s”"), name);
+                       selected = NULL;
+                       result = GPG_ERR_AMBIGUOUS;
+               }
+       } else {
+               selected = (gpgme_key_t) keys->data;
        }
-       return NULL;
-    }
 
-    /* let the user select a key from the list if there is more than one */
-    if (g_list_length(keys) > 1) {
-       if (select_key_cb)
-           key =
-               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,
-                           GPG_ERR_KEY_SELECTION,
-                           _("%s: multiple keys for %s"), "gmime-gpgme",
-                           name);
-           key = NULL;
-       }
-       if (key)
-           gpgme_key_ref(key);
-       g_list_foreach(keys, (GFunc) gpgme_key_unref, NULL);
-    } else
-       key = (gpgme_key_t) keys->data;
-    g_list_free(keys);
-
-    /* OpenPGP: ask the user if a low-validity key should be trusted for
-     * encryption */
-    // FIXME - shouldn't we do the same for S/MIME?
-    if (key && !secret && !accept_all
-       && gpgme_get_protocol(ctx) == GPGME_PROTOCOL_OpenPGP) {
-       gpgme_user_id_t uid = key->uids;
-       gchar *upcase_name = g_ascii_strup(name, -1);
-       gboolean found = FALSE;
-
-       while (!found && uid) {
-           /* check the email field which may or may not be present */
-           if (uid->email && !g_ascii_strcasecmp(uid->email, name))
-               found = TRUE;
-           else {
-               /* no email or no match, check the uid */
-               gchar *upcase_uid = g_ascii_strup(uid->uid, -1);
-
-               if (strstr(upcase_uid, upcase_name))
-                   found = TRUE;
-               else
-                   uid = uid->next;
-               g_free(upcase_uid);
-           }
+       /* ref the selected key, free all others and the list */
+       if (selected != NULL) {
+               gpgme_key_ref(selected);
        }
-       g_free(upcase_name);
-
-       /* ask the user if a low-validity key shall be used */
-       if (uid && uid->validity < GPGME_VALIDITY_FULL) {
-           if (!accept_low_trust_cb
-               || !accept_low_trust_cb(name, uid, parent)) {
-               gpgme_key_unref(key);
-               key = NULL;
-               if (error)
-                   g_set_error(error, GPGME_ERROR_QUARK,
-                               GPG_ERR_KEY_SELECTION,
-                               _("%s: insufficient validity for UID %s"),
-                               "gmime-gpgme", name);
-           }
+       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?
+       if ((result == GPG_ERR_NO_ERROR) && !secret && !accept_all && (gpgme_get_protocol(ctx) == 
GPGME_PROTOCOL_OpenPGP) &&
+               (selected->owner_trust < GPGME_VALIDITY_FULL)) {
+               if ((accept_low_trust_cb == NULL) || !accept_low_trust_cb(name, selected, parent)) {
+                       gpgme_key_unref(selected);
+                       selected = NULL;
+                       g_set_error(error, GPGME_ERROR_QUARK, GPG_ERR_KEY_SELECTION, _("Insufficient key 
validity"));
+                       result = GPG_ERR_NOT_TRUSTED;
+               }
        }
-    }
 
-    return key;
+       *key = selected;
+       return result;
 }
 
 
+/** \brief Select a public key from all available keys
+ *
+ * \param ctx GpgME context
+ * \param name recipient's mail address, used only for display
+ * \param parent transient parent window
+ * \param error filled with a human-readable error on error, may be NULL
+ * \return the selected key or NULL if the user cancelled the operation
+ *
+ * This helper function loads all available keys and calls \ref select_key_cb to let the user choose one of 
them.
+ */
 static gpgme_key_t
-get_pubkey(gpgme_ctx_t ctx, const gchar * name, gboolean accept_all,
-       GtkWindow * parent, GError ** error)
+get_pubkey(gpgme_ctx_t   ctx,
+                  const gchar  *name,
+                  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_key_t key = NULL;
+
+       /* let gpgme list all available keys */
+       if (libbalsa_gpgme_list_keys(ctx, &keys, NULL, NULL, FALSE, FALSE, error)) {
+               if (keys != NULL) {
+                       /* 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_PUBLIC_KEY_ANY, keys, 
gpgme_get_protocol(ctx), parent);
+                               if (key != NULL) {
+                                       gpgme_key_ref(key);
+                               }
+                       }
+                       g_list_free_full(keys, (GDestroyNotify) gpgme_key_unref);
+               }
        }
-       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.
+/** \brief Add the private key for signing
+ *
+ * \param ctx GpgME context
+ * \param signer sender's (signers) mail address or key fingerprint
+ * \param parent transient parent window
+ * \param error filled with a human-readable error on error, may be NULL
+ * \return TRUE on success or FALSE if no suitable key is available
+ *
+ * Add the signer's key to the list of signers of the passed context.
  */
 static gboolean
-gpgme_add_signer(gpgme_ctx_t ctx, const gchar * signer, GtkWindow * parent,
-                GError ** error)
+gpgme_add_signer(gpgme_ctx_t   ctx,
+                                const gchar  *signer,
+                                GtkWindow    *parent,
+                                GError      **error)
 {
-       gboolean result = FALSE;
-       gpgme_key_t key;
+       gpgme_error_t result;
+       gpgme_key_t key = NULL;
 
     /* note: private (secret) key has never low trust... */
-       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);
+       result = get_key_from_name(ctx, &key, signer, TRUE, FALSE, parent, error);
+       if (result == GPG_ERR_NO_ERROR) {
+               /* set the key (the previous operation guaranteed that it exists, no need 2 check return 
values...) */
+               (void) gpgme_signers_add(ctx, key);
                gpgme_key_unref(key);
-               result = TRUE;
        }
 
-    return result;
+    return (result == GPG_ERR_NO_ERROR);
 }
 
 
-/*
- * Build a NULL-terminated array of keys for all recipients in rcpt_list
- * and return it. The caller has to take care that it's released. If
- * something goes wrong, NULL is returned.
+/** \brief Find public keys for a list of recipients
+ *
+ * \param ctx GpgME context
+ * \param rcpt_list array of <i>gchar *</i> elements, each containing a recipient's mailbox
+ * \param accept_low_trust TRUE to accept low-trust keys without confirmation
+ * \param parent transient parent window
+ * \param error filled with a human-readable error on error, may be NULL
+ * \return a newly allocated, NULL-terminated array of keys on success, NULL if any error occurred
+ *
+ * Build an array of keys for all recipients in rcpt_list and return it.
+ *
+ * \note The caller shall free the returned list by calling release_keylist().
  */
 static gpgme_key_t *
-gpgme_build_recipients(gpgme_ctx_t ctx, GPtrArray * rcpt_list,
-                      gboolean accept_low_trust, GtkWindow * parent,
-                      GError ** error)
+gpgme_build_recipients(gpgme_ctx_t   ctx,
+                                          GPtrArray    *rcpt_list,
+                                          gboolean      accept_low_trust,
+                                          GtkWindow    *parent,
+                                          GError      **error)
 {
-    gpgme_key_t *rcpt = g_new0(gpgme_key_t, rcpt_list->len + 1);
-    guint num_rcpts;
-
-    /* try to find the public key for every recipient */
-    for (num_rcpts = 0; num_rcpts < rcpt_list->len; num_rcpts++) {
-       gchar *name = (gchar *) g_ptr_array_index(rcpt_list, num_rcpts);
-       gpgme_key_t key;
-
-               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;
+       gpgme_key_t *rcpt = g_new0(gpgme_key_t, rcpt_list->len + 1U);
+       gpgme_error_t select_res;
+       guint num_rcpts;
+
+       /* try to find the public key for every recipient */
+       select_res = GPG_ERR_NO_ERROR;
+       for (num_rcpts = 0U; (select_res == GPG_ERR_NO_ERROR) && (num_rcpts < rcpt_list->len); num_rcpts++) {
+               gchar *name = (gchar *) g_ptr_array_index(rcpt_list, num_rcpts);
+               gpgme_key_t key = NULL;
+
+               select_res = get_key_from_name(ctx, &key, name, FALSE, accept_low_trust, parent, error);
+
+               /* if no public key exists for the user, as fallback list all keys so an other one may be 
selected */
+               if (select_res == GPG_ERR_NO_PUBKEY) {
+                       key = get_pubkey(ctx, name, parent, error);
+                       if (key != NULL) {
+                               select_res = GPG_ERR_NO_ERROR;          /* got one, clear error state */
                        }
+               }
+
+               /* set the recipient */
+               rcpt[num_rcpts] = key;
        }
 
-       /* set the recipient */
-       rcpt[num_rcpts] = key;
-    }
+       if (select_res != GPG_ERR_NO_ERROR) {
+               release_keylist(rcpt);
+               rcpt = NULL;
+       }
 
-    return rcpt;
+       return rcpt;
 }
 
 
diff --git a/libbalsa/libbalsa-gpgme.h b/libbalsa/libbalsa-gpgme.h
index 69ac400..e3f0ff3 100644
--- a/libbalsa/libbalsa-gpgme.h
+++ b/libbalsa/libbalsa-gpgme.h
@@ -46,39 +46,47 @@ G_BEGIN_DECLS
 /** Callback to select a key from a list
  * Parameters:
  * - user name
- * - TRUE is a secret key shall be selected
+ * - key selection mode
  * - list of available keys (gpgme_key_t data elements)
  * - protocol
  * - parent window
+ * Return: the key the user selected, or NULL if the operation shall be cancelled
  */
 typedef gpgme_key_t(*lbgpgme_select_key_cb) (const gchar *,
-                                                lb_key_sel_md_t,
-                                                GList *,
-                                                gpgme_protocol_t,
-                                                GtkWindow *);
+                                                                                        lb_key_sel_md_t,
+                                                                                        GList *,
+                                                                                        gpgme_protocol_t,
+                                                                                        GtkWindow *);
 
 /** Callback to ask the user whether a key with low trust shall be accepted
  * Parameters:
- * - user name
- * - GpgME user ID
+ * - recipient user name (email address)
+ * - the key with insufficient trust
  * - parent window
+ * Return: TRUE to accept the key, FALSE to reject it
  */
 typedef gboolean(*lbgpgme_accept_low_trust_cb) (const gchar *,
-                                               const gpgme_user_id_t,
-                                               GtkWindow *);
+                                                                                               gpgme_key_t,
+                                                                                               GtkWindow *);
 
 
 
-void libbalsa_gpgme_init(gpgme_passphrase_cb_t get_passphrase,
-                        lbgpgme_select_key_cb select_key_cb,
-                        lbgpgme_accept_low_trust_cb accept_low_trust);
+void libbalsa_gpgme_init(gpgme_passphrase_cb_t       get_passphrase,
+                                                lbgpgme_select_key_cb       select_key_cb,
+                                                lbgpgme_accept_low_trust_cb accept_low_trust);
 gboolean libbalsa_gpgme_check_crypto_engine(gpgme_protocol_t protocol);
+gpgme_ctx_t libbalsa_gpgme_new_with_proto(gpgme_protocol_t        protocol,
+                                                                                 gpgme_passphrase_cb_t   
callback,
+                                                                                 GtkWindow                   
           *parent,
+                                                                                 GError                
**error)
+       G_GNUC_WARN_UNUSED_RESULT;
 
 GMimeGpgmeSigstat *libbalsa_gpgme_verify(GMimeStream * content,
                                         GMimeStream * sig_plain,
                                         gpgme_protocol_t protocol,
                                         gboolean singlepart_mode,
-                                        GError ** error);
+                                        GError ** error)
+       G_GNUC_WARN_UNUSED_RESULT;
 
 gpgme_hash_algo_t libbalsa_gpgme_sign(const gchar * userid,
                                      GMimeStream * istream,
@@ -100,7 +108,14 @@ GMimeGpgmeSigstat *libbalsa_gpgme_decrypt(GMimeStream * crypted,
                                          GMimeStream * plain,
                                          gpgme_protocol_t protocol,
                                          GtkWindow * parent,
-                                         GError ** error);
+                                         GError ** error)
+       G_GNUC_WARN_UNUSED_RESULT;
+
+void libbalsa_gpgme_set_error(GError        **error,
+                                                 gpgme_error_t   gpgme_err,
+                                                         const gchar    *format,
+                                                         ...)
+       G_GNUC_PRINTF(3, 4);
 
 
 G_END_DECLS
diff --git a/libbalsa/rfc3156.c b/libbalsa/rfc3156.c
index fe94f8e..87084ea 100644
--- a/libbalsa/rfc3156.c
+++ b/libbalsa/rfc3156.c
@@ -25,21 +25,18 @@
 
 #include <string.h>
 #include <gpgme.h>
-#include <pwd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <stdlib.h>
 
 #include "libbalsa.h"
 #include "libbalsa_private.h"
+#include "libbalsa-gpgme-widgets.h"
+#include "libbalsa-gpgme-keys.h"
+#include "libbalsa-gpgme.h"
 
 #include "gmime-multipart-crypt.h"
 #include "gmime-gpgme-signature.h"
 #include "gmime-part-rfc2440.h"
 
-#ifdef HAVE_SMIME
-#  include "gmime-application-pkcs7.h"
-#endif
+#include "gmime-application-pkcs7.h"
 
 #if HAVE_MACOSX_DESKTOP
 #  include "macosx-helpers.h"
@@ -88,27 +85,21 @@ libbalsa_can_encrypt_for_all(InternetAddressList * recipients,
     /* silent paranoia checks */
     if (!recipients)
        return TRUE;  /* we can of course encrypt for nobody... */
-#ifndef HAVE_SMIME
-    if (protocol == GPGME_PROTOCOL_OpenPGP)
-       return FALSE;
-#endif
 
     /* check if gpg is currently available */
     if (protocol == GPGME_PROTOCOL_OpenPGP && gpg_updates_trustdb())
        return FALSE;
 
     /* create the gpgme context and set the protocol */
-    if (gpgme_new(&gpgme_ctx) != GPG_ERR_NO_ERROR)
-       return FALSE;
-    if (gpgme_set_protocol(gpgme_ctx, protocol) != GPG_ERR_NO_ERROR) {
-       gpgme_release(gpgme_ctx);
-       return FALSE;
+    gpgme_ctx = libbalsa_gpgme_new_with_proto(protocol, NULL, NULL, NULL);
+    if (gpgme_ctx == NULL) {
+       result = FALSE;
+    } else {
+       /* loop over all recipients and try to find valid keys */
+       result = have_pub_key_for(gpgme_ctx, recipients);
+       gpgme_release(gpgme_ctx);
     }
 
-    /* loop over all recipients and try to find valid keys */
-    result = have_pub_key_for(gpgme_ctx, recipients);
-    gpgme_release(gpgme_ctx);
-
     return result;
 }
 
@@ -208,9 +199,6 @@ libbalsa_sign_mime_object(GMimeObject ** content, const gchar * rfc822_for,
     /* paranoia checks */
     g_return_val_if_fail(rfc822_for != NULL, FALSE);
     g_return_val_if_fail(content != NULL, FALSE);
-#ifndef HAVE_SMIME
-    g_return_val_if_fail(protocol == GPGME_PROTOCOL_OpenPGP, FALSE);
-#endif
 
     /* check if gpg is currently available */
     if (protocol == GPGME_PROTOCOL_OpenPGP && gpg_updates_trustdb())
@@ -249,9 +237,6 @@ libbalsa_encrypt_mime_object(GMimeObject ** content, GList * rfc822_for,
     /* paranoia checks */
     g_return_val_if_fail(rfc822_for != NULL, FALSE);
     g_return_val_if_fail(content != NULL, FALSE);
-#ifndef HAVE_SMIME
-    g_return_val_if_fail(protocol == GPGME_PROTOCOL_OpenPGP, FALSE);
-#endif
 
     /* check if gpg is currently available */
     if (protocol == GPGME_PROTOCOL_OpenPGP && gpg_updates_trustdb())
@@ -271,16 +256,12 @@ libbalsa_encrypt_mime_object(GMimeObject ** content, GList * rfc822_for,
 
        encrypted_obj = GMIME_OBJECT(mpe);
        result = g_mime_gpgme_mpe_encrypt(mpe, *content, recipients, always_trust, parent, error);
-    }
-#ifdef HAVE_SMIME
-    else {
-       GMimePart *pkcs7 =
-           g_mime_part_new_with_type("application", "pkcs7-mime");
-       encrypted_obj = GMIME_OBJECT(pkcs7);
+    } else {
+       GMimePart *pkcs7 = g_mime_part_new_with_type("application", "pkcs7-mime");
+       encrypted_obj = GMIME_OBJECT(pkcs7);
 
-       result = g_mime_application_pkcs7_encrypt(pkcs7, *content, recipients, always_trust, parent, error);
+       result = g_mime_application_pkcs7_encrypt(pkcs7, *content, recipients, always_trust, parent, error);
     }
-#endif
     g_ptr_array_free(recipients, FALSE);
 
     /* error checking */
@@ -315,9 +296,6 @@ libbalsa_sign_encrypt_mime_object(GMimeObject ** content,
     g_return_val_if_fail(rfc822_signer != NULL, FALSE);
     g_return_val_if_fail(rfc822_for != NULL, FALSE);
     g_return_val_if_fail(content != NULL, FALSE);
-#ifndef HAVE_SMIME
-    g_return_val_if_fail(protocol == GPGME_PROTOCOL_OpenPGP, FALSE);
-#endif
 
     /* check if gpg is currently available */
     if (protocol == GPGME_PROTOCOL_OpenPGP && gpg_updates_trustdb())
@@ -363,9 +341,6 @@ libbalsa_body_check_signature(LibBalsaMessageBody * body,
     g_return_val_if_fail(body, FALSE);
     g_return_val_if_fail(body->mime_part != NULL, FALSE);
     g_return_val_if_fail(body->message, FALSE);
-#ifndef HAVE_SMIME
-    g_return_val_if_fail(protocol == GPGME_PROTOCOL_OpenPGP, FALSE);
-#endif
 
     /* check if gpg is currently available */
     if (protocol == GPGME_PROTOCOL_OpenPGP && gpg_updates_trustdb())
@@ -411,17 +386,12 @@ libbalsa_body_decrypt(LibBalsaMessageBody *body, gpgme_protocol_t protocol, GtkW
     GError *error = NULL;
     LibBalsaMessage *message;
     GMimeGpgmeSigstat *sig_state = NULL;
-#ifdef HAVE_SMIME
     gboolean smime_encrypted = FALSE;
-#endif
 
     /* paranoia checks */
     g_return_val_if_fail(body != NULL, body);
     g_return_val_if_fail(body->mime_part != NULL, body);
     g_return_val_if_fail(body->message != NULL, body);
-#ifndef HAVE_SMIME
-    g_return_val_if_fail(protocol == GPGME_PROTOCOL_OpenPGP, FALSE);
-#endif
 
     /* check if gpg is currently available */
     if (protocol == GPGME_PROTOCOL_OpenPGP && gpg_updates_trustdb())
@@ -431,33 +401,29 @@ libbalsa_body_decrypt(LibBalsaMessageBody *body, gpgme_protocol_t protocol, GtkW
     if (protocol == GPGME_PROTOCOL_OpenPGP) {
        if (!GMIME_IS_MULTIPART_ENCRYPTED(body->mime_part))
            return body;
-    }
-#ifdef HAVE_SMIME
-    else {
-       const char * smime_type = 
-           g_mime_object_get_content_type_parameter(body->mime_part,
-                                                    "smime-type");
+    } else {
+       const char * smime_type =
+               g_mime_object_get_content_type_parameter(body->mime_part,
+                       "smime-type");
 
-       if (!smime_type || !GMIME_IS_PART(body->mime_part))
-           return body;
-       if (!g_ascii_strcasecmp(smime_type, "enveloped-data"))
-           smime_encrypted = TRUE;
-       else
-            smime_encrypted = body->was_encrypted;
+       if (!smime_type || !GMIME_IS_PART(body->mime_part))
+               return body;
+       if (!g_ascii_strcasecmp(smime_type, "enveloped-data"))
+               smime_encrypted = TRUE;
+       else
+               smime_encrypted = body->was_encrypted;
     }
-#endif
 
     libbalsa_mailbox_lock_store(body->message->mailbox);
-    if (protocol == GPGME_PROTOCOL_OpenPGP)
-       mime_obj =
-           g_mime_gpgme_mpe_decrypt(GMIME_MULTIPART_ENCRYPTED(body->mime_part),
-                                    &sig_state, parent, &error);
-#ifdef HAVE_SMIME
-    else
-       mime_obj =
-           g_mime_application_pkcs7_decrypt_verify(GMIME_PART(body->mime_part),
-                                                   &sig_state, parent, &error);
-#endif
+    if (protocol == GPGME_PROTOCOL_OpenPGP) {
+       mime_obj =
+               g_mime_gpgme_mpe_decrypt(GMIME_MULTIPART_ENCRYPTED(body->mime_part),
+                       &sig_state, parent, &error);
+    } else {
+       mime_obj =
+               g_mime_application_pkcs7_decrypt_verify(GMIME_PART(body->mime_part),
+                       &sig_state, parent, &error);
+    }
     libbalsa_mailbox_unlock_store(body->message->mailbox);
 
     /* check the result */
@@ -478,12 +444,11 @@ libbalsa_body_decrypt(LibBalsaMessageBody *body, gpgme_protocol_t protocol, GtkW
     body = libbalsa_message_body_new(message);
 
     /* remember that is was encrypted */
-    if (protocol == GPGME_PROTOCOL_OpenPGP)
-       body->was_encrypted = TRUE;
-#ifdef HAVE_SMIME
-    else
-       body->was_encrypted = smime_encrypted;
-#endif
+    if (protocol == GPGME_PROTOCOL_OpenPGP) {
+       body->was_encrypted = TRUE;
+    } else {
+       body->was_encrypted = smime_encrypted;
+    }
     if (body->was_encrypted)
         body->message->prot_state = LIBBALSA_MSG_PROTECT_CRYPT;
 
@@ -759,8 +724,8 @@ append_time_t(GString *str, const gchar *format, time_t when,
 }
 
 gchar *
-libbalsa_signature_info_to_gchar(GMimeGpgmeSigstat * info,
-                                const gchar * date_string)
+libbalsa_signature_info_to_gchar_short(GMimeGpgmeSigstat *info,
+                                                                          const gchar       *date_string)
 {
     GString *msg;
     gchar *retval;
@@ -768,26 +733,38 @@ libbalsa_signature_info_to_gchar(GMimeGpgmeSigstat * info,
     g_return_val_if_fail(info != NULL, NULL);
     g_return_val_if_fail(date_string != NULL, NULL);
     msg = g_string_new(libbalsa_gpgme_sig_protocol_name(info->protocol));
-    msg =
-       g_string_append(msg,
-                       libbalsa_gpgme_sig_stat_to_gchar(info->status));
-    g_string_append_printf(msg, _("\nSignature validity: %s"),
-                          libbalsa_gpgme_validity_to_gchar(info->
-                                                           validity));
+    msg = g_string_append(msg, libbalsa_gpgme_sig_stat_to_gchar(info->status));
+    g_string_append_printf(msg, _("\nSignature validity: %s"), libbalsa_gpgme_validity_to_gchar(info-> 
validity));
     append_time_t(msg, _("\nSigned on: %s"), info->sign_time, date_string);
-    if (info->protocol == GPGME_PROTOCOL_OpenPGP && info->key)
-       g_string_append_printf(msg, _("\nKey owner trust: %s"),
-                              libbalsa_gpgme_validity_to_gchar_short
-                              (info->key->owner_trust));
-    if (info->fingerprint)
-       g_string_append_printf(msg, _("\nKey fingerprint: %s"),
-                              info->fingerprint);
+    if (info->fingerprint) {
+       g_string_append_printf(msg, _("\nKey fingerprint: %s"), info->fingerprint);
+    }
+
+    retval = msg->str;
+    g_string_free(msg, FALSE);
+    return retval;
+}
+
+gchar *
+libbalsa_signature_info_to_gchar(GMimeGpgmeSigstat *info,
+                                                                const gchar       *date_string)
+{
+    GString *msg;
+    gchar *retval;
+
+    g_return_val_if_fail(info != NULL, NULL);
+    g_return_val_if_fail(date_string != NULL, NULL);
+    msg = g_string_new(libbalsa_signature_info_to_gchar_short(info, date_string));
 
     /* add key information */
-    if (info->key) {
+    if (info->key != NULL) {
         gpgme_user_id_t uid;
         gpgme_subkey_t subkey;
 
+        if (info->protocol == GPGME_PROTOCOL_OpenPGP) {
+               g_string_append_printf(msg, _("\nKey owner trust: %s"), 
libbalsa_gpgme_validity_to_gchar_short(info->key->owner_trust));
+        }
+
         /* user ID's */
         if ((uid = info->key->uids)) {
             gchar *lead_text;
@@ -895,132 +872,6 @@ libbalsa_signature_info_to_gchar(GMimeGpgmeSigstat * info,
 }
 
 
-#ifdef HAVE_GPG
-
-#include <sys/wait.h>
-#include <fcntl.h>
-
-/* run gpg asynchronously to import or update a key */
-typedef struct _spawned_gpg_T {
-    gint child_pid;
-    gint standard_error;
-    GString *stderr_buf;
-    GtkWindow *parent;
-} spawned_gpg_T;
-
-static gboolean check_gpg_child(gpointer data);
-
-gboolean
-gpg_keyserver_op(const gchar * fingerprint, gpg_keyserver_action_t action,
-                 GtkWindow * parent)
-{
-    gchar **argv;
-    spawned_gpg_T *spawned_gpg;
-    gboolean spawnres;
-
-    /* launch gpg... */
-    argv = g_new(gchar *, 5);
-    argv[0] = g_strdup(GPG_PATH);
-    argv[1] = g_strdup("--no-greeting");
-    switch (action) {
-    case GPG_KEYSERVER_IMPORT:
-        argv[2] = g_strdup("--recv-keys");
-        break;
-    case GPG_KEYSERVER_UPDATE:
-        argv[2] = g_strdup("--refresh-keys");
-        break;
-    default:
-        g_assert_not_reached();
-    }
-    argv[3] = g_strdup(fingerprint);
-    argv[4] = NULL;
-    spawned_gpg = g_new0(spawned_gpg_T, 1);
-    spawnres =
-       g_spawn_async_with_pipes(NULL, argv, NULL,
-                                G_SPAWN_DO_NOT_REAP_CHILD |
-                                G_SPAWN_STDOUT_TO_DEV_NULL, NULL, NULL,
-                                &spawned_gpg->child_pid, NULL, NULL,
-                                &spawned_gpg->standard_error, NULL);
-    g_strfreev(argv);
-    if (spawnres == FALSE) {
-       libbalsa_information(LIBBALSA_INFORMATION_ERROR,
-                            _
-                            ("Could not launch %s to query the public key %s."),
-                            GPG_PATH, fingerprint);
-       g_free(spawned_gpg);
-       return FALSE;
-    }
-
-    /* install an idle handler to check if the child returnd successfully. */
-    fcntl(spawned_gpg->standard_error, F_SETFL, O_NONBLOCK);
-    spawned_gpg->stderr_buf = g_string_new("");
-    spawned_gpg->parent = parent;
-    g_timeout_add(250, check_gpg_child, spawned_gpg);
-
-    return TRUE;
-}
-
-
-static gboolean
-check_gpg_child(gpointer data)
-{
-    spawned_gpg_T *spawned_gpg = (spawned_gpg_T *) data;
-    int status;
-    ssize_t bytes_read;
-    gchar buffer[1024], *gpg_message;
-    GtkWidget *dialog;
-
-    /* read input from the child and append it to the buffer */
-    while ((bytes_read =
-           read(spawned_gpg->standard_error, buffer, 1023)) > 0) {
-       buffer[bytes_read] = '\0';
-       g_string_append(spawned_gpg->stderr_buf, buffer);
-    }
-
-    /* check if the child exited */
-    if (waitpid(spawned_gpg->child_pid, &status, WNOHANG) !=
-       spawned_gpg->child_pid)
-       return TRUE;
-
-    /* child exited, display some information... */
-    close(spawned_gpg->standard_error);
-
-    gpg_message =
-       g_locale_to_utf8(spawned_gpg->stderr_buf->str, -1, NULL,
-                        NULL, NULL);
-    gdk_threads_enter();
-    if (WEXITSTATUS(status) > 0)
-       dialog =
-           gtk_message_dialog_new(spawned_gpg->parent,
-                                  GTK_DIALOG_DESTROY_WITH_PARENT,
-                                  GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
-                                  _
-                                  ("Running %s failed with return value %d:\n%s"),
-                                  GPG_PATH, WEXITSTATUS(status), gpg_message);
-    else
-       dialog =
-           gtk_message_dialog_new(spawned_gpg->parent,
-                                  GTK_DIALOG_DESTROY_WITH_PARENT,
-                                  GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
-                                  _("Running %s successful:\n%s"),
-                                  GPG_PATH, gpg_message);
-#if HAVE_MACOSX_DESKTOP
-    libbalsa_macosx_menu_for_parent(dialog, spawned_gpg->parent);
-#endif
-    g_free(gpg_message);
-    g_string_free(spawned_gpg->stderr_buf, TRUE);
-    g_free(spawned_gpg);
-
-    gtk_dialog_run(GTK_DIALOG(dialog));
-    gtk_widget_destroy(dialog);
-    gdk_threads_leave();
-
-    return FALSE;
-}
-
-#endif                         /* HAVE_GPG */
-
-
 /* ==== local stuff ======================================================== */
 
 
@@ -1031,76 +882,54 @@ check_gpg_child(gpointer data)
 static gboolean
 gpg_updates_trustdb(void)
 {
-    static gchar *lockname = NULL;
-    struct passwd *pwent;
-    struct stat stat_buf;
-
-    if (lockname == NULL) {
-       if ((pwent = getpwuid(getuid())) != NULL) {
-           lockname =
-               g_strdup_printf("%s/.gnupg/trustdb.gpg.lock",
-                               pwent->pw_dir);
-        } else {
-            g_assert_not_reached();
-        }
-    }
+       static gchar *lockname = NULL;
+       gboolean result;
 
-    if (stat(lockname, &stat_buf) == 0) {
-       libbalsa_information(LIBBALSA_INFORMATION_ERROR, "%s%s",
-                            _
-                            ("GnuPG is rebuilding the trust database and is currently unavailable."),
-                            _("Try again later."));
-       return TRUE;
-    } else
-       return FALSE;
+       if (lockname == NULL) {
+               lockname = g_build_filename(g_get_home_dir(), ".gnupg", "trustdb.gpg.lock", NULL);
+       }
+
+       if (g_file_test(lockname, G_FILE_TEST_EXISTS)) {
+               libbalsa_information(LIBBALSA_INFORMATION_ERROR, "%s %s",
+                       _("GnuPG is rebuilding the trust database and is currently unavailable."),
+                       _("Try again later."));
+               result = TRUE;
+       } else {
+               result = FALSE;
+       }
+       return result;
 }
 
 
-/* check if the context contains a public key for the passed recipients */
-#define KEY_IS_OK(k)   (!((k)->expired || (k)->revoked || \
-                          (k)->disabled || (k)->invalid))
+/* check if the local key ring contains a public key for the passed recipients */
 static gboolean
-have_pub_key_for(gpgme_ctx_t gpgme_ctx, InternetAddressList * recipients)
+have_pub_key_for(gpgme_ctx_t          gpgme_ctx,
+                                InternetAddressList *recipients)
 {
-    gpgme_key_t key;
     gboolean result = TRUE;
-    time_t now = time(NULL);
     gint i;
 
-    for (i = 0; result && i < internet_address_list_length(recipients);
-         i++) {
-        InternetAddress *ia =
-            internet_address_list_get_address(recipients, i);
-
-       /* check all entries in the list, handle groups recursively */
-       if (INTERNET_ADDRESS_IS_GROUP(ia))
-           result =
-                have_pub_key_for(gpgme_ctx,
-                                 INTERNET_ADDRESS_GROUP(ia)->members);
-       else {
-           if (gpgme_op_keylist_start(gpgme_ctx,
-                                       INTERNET_ADDRESS_MAILBOX(ia)->addr,
-                                       FALSE) != GPG_ERR_NO_ERROR)
-               return FALSE;
-
-           result = FALSE;
-           while (!result &&
-                  gpgme_op_keylist_next(gpgme_ctx, &key) == GPG_ERR_NO_ERROR) {
-               /* check if this key and the relevant subkey are usable */
-               if (KEY_IS_OK(key)) {
-                   gpgme_subkey_t subkey = key->subkeys;
-
-                   while (subkey && !subkey->can_encrypt)
-                       subkey = subkey->next;
-
-                   if (subkey && KEY_IS_OK(subkey) && 
-                       (subkey->expires == 0 || subkey->expires > now))
-                       result = TRUE;
-               }
-               gpgme_key_unref(key);
-           }
-           gpgme_op_keylist_end(gpgme_ctx);
-       }
+    for (i = 0; result && (i < internet_address_list_length(recipients)); i++) {
+       InternetAddress *ia = internet_address_list_get_address(recipients, i);
+
+       /* check all entries in the list, handle groups recursively */
+       if (INTERNET_ADDRESS_IS_GROUP(ia)) {
+               result = have_pub_key_for(gpgme_ctx, INTERNET_ADDRESS_GROUP(ia)->members);
+       } else {
+               gchar *mail_name;
+               GList *keys = NULL;
+
+               result = FALSE;
+               /* enclose the mail address into "<...>" to perform an exact search */
+               mail_name = g_strconcat("<", INTERNET_ADDRESS_MAILBOX(ia)->addr, ">", NULL);
+               if (libbalsa_gpgme_list_keys(gpgme_ctx, &keys, NULL, mail_name, FALSE, FALSE, NULL)) {
+                       if (keys != NULL) {
+                               result = TRUE;
+                               g_list_free_full(keys, (GDestroyNotify) gpgme_key_unref);
+                       }
+               }
+               g_free(mail_name);
+       }
     }
 
     return result;
diff --git a/libbalsa/rfc3156.h b/libbalsa/rfc3156.h
index fdafa75..dfb641a 100644
--- a/libbalsa/rfc3156.h
+++ b/libbalsa/rfc3156.h
@@ -105,20 +105,12 @@ const gchar *libbalsa_gpgme_sig_protocol_name(gpgme_protocol_t protocol);
 const gchar *libbalsa_gpgme_sig_stat_to_gchar(gpgme_error_t stat);
 const gchar *libbalsa_gpgme_validity_to_gchar(gpgme_validity_t validity);
 const gchar *libbalsa_gpgme_validity_to_gchar_short(gpgme_validity_t validity);
-gchar *libbalsa_signature_info_to_gchar(GMimeGpgmeSigstat * info,
-                                       const gchar * date_string);
+gchar *libbalsa_signature_info_to_gchar(GMimeGpgmeSigstat *info,
+                                                                               const gchar       
*date_string)
+       G_GNUC_WARN_UNUSED_RESULT;
+gchar *libbalsa_signature_info_to_gchar_short(GMimeGpgmeSigstat *info,
+                                                                                     const gchar       
*date_string)
+       G_GNUC_WARN_UNUSED_RESULT;
 
-#ifdef HAVE_GPG
-
-typedef enum {
-    GPG_KEYSERVER_IMPORT = 1,
-    GPG_KEYSERVER_UPDATE
-} gpg_keyserver_action_t;
-
-gboolean gpg_keyserver_op(const gchar * fingerprint,
-                          gpg_keyserver_action_t action,
-                          GtkWindow * parent);
-
-#endif
 #endif                         /* HAVE_GPGME */
-#endif                         /* __RFC3156_GPG_H__ */
+#endif                         /* __RFC3156_H__ */
diff --git a/libbalsa/send.c b/libbalsa/send.c
index fc447d3..bfb189c 100644
--- a/libbalsa/send.c
+++ b/libbalsa/send.c
@@ -1785,17 +1785,12 @@ do_multipart_crypto(LibBalsaMessage *message,
     /* check which protocol should be used */
     if (message->gpg_mode & LIBBALSA_PROTECT_RFC3156) {
         protocol = GPGME_PROTOCOL_OpenPGP;
-    }
-#   ifdef HAVE_SMIME
-    else if (message->gpg_mode & LIBBALSA_PROTECT_SMIMEV3) {
+    } else if (message->gpg_mode & LIBBALSA_PROTECT_SMIMEV3) {
         protocol = GPGME_PROTOCOL_CMS;
-    }
-#   endif
-    else if (message->gpg_mode & LIBBALSA_PROTECT_OPENPGP) {
+    } else if (message->gpg_mode & LIBBALSA_PROTECT_OPENPGP) {
         return LIBBALSA_MESSAGE_CREATE_OK;  /* already done... */
     } else {
         return LIBBALSA_MESSAGE_ENCRYPT_ERROR;  /* hmmm.... */
-
     }
     always_trust = (message->gpg_mode & LIBBALSA_PROTECT_ALWAYS_TRUST) != 0;
     /* sign and/or encrypt */
diff --git a/src/balsa-mime-widget-crypto.c b/src/balsa-mime-widget-crypto.c
index 233f41a..360c293 100644
--- a/src/balsa-mime-widget-crypto.c
+++ b/src/balsa-mime-widget-crypto.c
@@ -26,12 +26,12 @@
 #include "balsa-app.h"
 #include "balsa-icons.h"
 #include <glib/gi18n.h>
+#include "libbalsa-gpgme-widgets.h"
+#include "libbalsa-gpgme-keys.h"
 #include "balsa-mime-widget.h"
 
 
-#ifdef HAVE_GPG
-static void on_gpg_key_button(GtkButton * button, const gchar * fingerprint);
-#endif
+static void on_gpg_key_button(GtkWidget *button, const gchar *fingerprint);
 
 
 BalsaMimeWidget *
@@ -67,39 +67,40 @@ balsa_mime_widget_signature_widget(LibBalsaMessageBody * mime_body,
        return NULL;
 
     infostr =
-        libbalsa_signature_info_to_gchar(mime_body->sig_info,
-                                         balsa_app.date_string);
-    if (!infostr)
+        libbalsa_signature_info_to_gchar_short(mime_body->sig_info, balsa_app.date_string);
+    if (infostr == NULL) {
         return NULL;
+    }
     lines = g_strsplit(infostr, "\n", 2);
     g_free(infostr);
 
     vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, BMW_VBOX_SPACE);
-    label = gtk_label_new(lines[1] ? lines[1] : lines[0]);
+    label = gtk_label_new((lines[1] != NULL) ? lines[1] : lines[0]);
     gtk_label_set_selectable(GTK_LABEL(label), TRUE);
     gtk_widget_set_halign(label, GTK_ALIGN_START);
     gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
-#ifdef HAVE_GPG
+    if (mime_body->sig_info->key != NULL) {
+       GtkWidget *key_widget;
+
+       /* show only the subkey which has been used to sign the message */
+       key_widget = libbalsa_gpgme_key(mime_body->sig_info->key, mime_body->sig_info->fingerprint, 0U, 
FALSE);
+        gtk_box_pack_start(GTK_BOX(vbox), key_widget, FALSE, FALSE, 0);
+    }
     if (mime_body->sig_info->protocol == GPGME_PROTOCOL_OpenPGP) {
         GtkWidget *button;
 
         if (mime_body->sig_info->status == GPG_ERR_NO_PUBKEY) {
-            button = gtk_button_new_with_mnemonic(_("_Run GnuPG to import this key"));
-            g_object_set_data(G_OBJECT(button), "gpg-keyserver-op",
-                              GINT_TO_POINTER(GPG_KEYSERVER_IMPORT));
+            button = gtk_button_new_with_mnemonic(_("_Search key server for this key"));
         } else {
-            button = gtk_button_new_with_mnemonic(_("_Run GnuPG to check for an update of this key"));
-            g_object_set_data(G_OBJECT(button), "gpg-keyserver-op",
-                              GINT_TO_POINTER(GPG_KEYSERVER_UPDATE));
+            button = gtk_button_new_with_mnemonic(_("_Search key server for updates of this key"));
         }
         g_signal_connect(G_OBJECT(button), "clicked",
                          G_CALLBACK(on_gpg_key_button),
                          (gpointer)mime_body->sig_info->fingerprint);
         gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
     }
-#endif /* HAVE_GPG */
 
-    if (lines[1]) {
+    if (lines[1] != NULL) {
         /* Hack alert: if we omit the box below and use the expander as signature widget
          * directly, setting the container border width of the container = the expander
          * causes its sensitive area to shrink to an almost unusable narrow line above
@@ -182,21 +183,17 @@ balsa_mime_widget_signature_icon_name(LibBalsaMsgProtectState protect_state)
 }
 
 
-#ifdef HAVE_GPG
-/*
- * We need gnupg to retreive a key from a key server...
- */
-
-/* Callback: run gpg to import a public key */
+/* Callback: try to import a public key */
 static void
-on_gpg_key_button(GtkButton * button, const gchar * fingerprint)
+on_gpg_key_button(GtkWidget   *button,
+                                 const gchar *fingerprint)
 {
-    gpg_keyserver_action_t action =
-        GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "gpg-keyserver-op"));
+       GError *error = NULL;
 
-    gpg_keyserver_op(fingerprint, action,
-                     balsa_get_parent_window(GTK_WIDGET(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);
+    }
 }
-#endif /* HAVE_GPG */
 
 #endif  /* HAVE_GPGME */
diff --git a/src/balsa-print-object.c b/src/balsa-print-object.c
index c993564..4ad4bb2 100644
--- a/src/balsa-print-object.c
+++ b/src/balsa-print-object.c
@@ -151,10 +151,8 @@ balsa_print_objects_append_from_body(GList * list,
         { "message/rfc822",                -1, balsa_print_object_emb_message },
 #ifdef HAVE_GPGME
         { "application/pgp-signature",     -1, balsa_print_object_mp_crypto },
-#ifdef HAVE_SMIME
         { "application/pkcs7-signature",   -1, balsa_print_object_mp_crypto },
         { "application/x-pkcs7-signature", -1, balsa_print_object_mp_crypto },
-#endif                         /* HAVE_SMIME */
 #endif                         /* HAVE_GPGME */
         { NULL,                            -1, balsa_print_object_default }
     };
diff --git a/src/sendmsg-window.c b/src/sendmsg-window.c
index 8eab501..5556372 100644
--- a/src/sendmsg-window.c
+++ b/src/sendmsg-window.c
@@ -6437,12 +6437,10 @@ bsmsg_update_gpg_ui_on_ident_change(BalsaSendmsg * bsmsg,
         bsmsg->gpg_mode |= LIBBALSA_PROTECT_OPENPGP;
         g_action_change_state(action, g_variant_new_string("open-pgp"));
         break;
-#ifdef HAVE_SMIME
     case LIBBALSA_PROTECT_SMIMEV3:
         bsmsg->gpg_mode |= LIBBALSA_PROTECT_SMIMEV3;
         g_action_change_state(action, g_variant_new_string("smime"));
         break;
-#endif
     case LIBBALSA_PROTECT_RFC3156:
     default:
         bsmsg->gpg_mode |= LIBBALSA_PROTECT_RFC3156;
@@ -6472,12 +6470,9 @@ bsmsg_setup_gpg_ui_by_mode(BalsaSendmsg *bsmsg, gint mode)
     sw_action_set_active(bsmsg, "encrypt", mode & LIBBALSA_PROTECT_ENCRYPT);
 
     action = sw_get_action(bsmsg, "gpg-mode");
-#ifdef HAVE_SMIME
     if (mode & LIBBALSA_PROTECT_SMIMEV3)
         g_action_change_state(action, g_variant_new_string("smime"));
-    else
-#endif
-    if (mode & LIBBALSA_PROTECT_OPENPGP)
+    else if (mode & LIBBALSA_PROTECT_OPENPGP)
         g_action_change_state(action, g_variant_new_string("open-pgp"));
     else
         g_action_change_state(action, g_variant_new_string("mime"));


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