[balsa] New files needed for previous commit



commit 4e5b5537c74760914532a70b4203e5b12ff2d099
Author: Peter Bloomfield <PeterBloomfield bellsouth net>
Date:   Sun Dec 11 14:04:34 2011 -0500

    New files needed for previous commit
    
    	* libbalsa/gmime-multipart-crypt.c
    	* libbalsa/gmime-multipart-crypt.h
    	* libbalsa/libbalsa-gpgme-cb.c
    	* libbalsa/libbalsa-gpgme-cb.h
    	* libbalsa/libbalsa-gpgme.c
    	* libbalsa/libbalsa-gpgme.h

 ChangeLog                        |   11 +
 libbalsa/gmime-multipart-crypt.c |  576 +++++++++++++++++++++++
 libbalsa/gmime-multipart-crypt.h |   69 +++
 libbalsa/libbalsa-gpgme-cb.c     |  428 +++++++++++++++++
 libbalsa/libbalsa-gpgme-cb.h     |   59 +++
 libbalsa/libbalsa-gpgme.c        |  968 ++++++++++++++++++++++++++++++++++++++
 libbalsa/libbalsa-gpgme.h        |  115 +++++
 7 files changed, 2226 insertions(+), 0 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index e82dbc9..fef9c83 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2011-12-11  Albrecht DreÃ
+
+	New files needed for previous commit (Peter's oops)
+
+	* libbalsa/gmime-multipart-crypt.c
+	* libbalsa/gmime-multipart-crypt.h
+	* libbalsa/libbalsa-gpgme-cb.c
+	* libbalsa/libbalsa-gpgme-cb.h
+	* libbalsa/libbalsa-gpgme.c
+	* libbalsa/libbalsa-gpgme.h
+
 2011-12-10  Albrecht DreÃ
 
 	New crypto implementation
diff --git a/libbalsa/gmime-multipart-crypt.c b/libbalsa/gmime-multipart-crypt.c
new file mode 100644
index 0000000..17b59fd
--- /dev/null
+++ b/libbalsa/gmime-multipart-crypt.c
@@ -0,0 +1,576 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/*
+ * gmime/gpgme implementation for multipart/signed and multipart/encrypted
+ * Copyright (C) 2011 Albrecht Dreà <albrecht dress arcor de>
+ *
+ * The functions in this module were copied from the original GMime
+ * implementation of multipart/signed and multipart/encrypted parts.
+ * However, instead of using the complex GMime crypto contexts (which have
+ * a varying API over the different versions), this module directly calls
+ * the GpgME backend functions implemented in libbalsa-gpgme.[hc].
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  
+ * 02111-1307, USA.
+ */
+
+#if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
+#include "config.h"
+#endif				/* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <unistd.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+#include "libbalsa-gpgme.h"
+#include "gmime-multipart-crypt.h"
+
+
+/**
+ * sign_prepare:
+ * @mime_part: MIME part
+ *
+ * Prepare a part (and all subparts) to be signed. To do this we need
+ * to set the encoding of all parts (that are not already encoded to
+ * either QP or Base64) to QP.
+ **/
+static void
+sign_prepare(GMimeObject * mime_part)
+{
+    GMimeContentEncoding encoding;
+    GMimeMultipart *multipart;
+    GMimeObject *subpart;
+    int i, n;
+
+    if (GMIME_IS_MULTIPART(mime_part)) {
+	multipart = (GMimeMultipart *) mime_part;
+
+	if (GMIME_IS_MULTIPART_SIGNED(multipart) ||
+	    GMIME_IS_MULTIPART_ENCRYPTED(multipart)) {
+	    /* must not modify these parts as they must be treated as opaque */
+	    return;
+	}
+
+	n = g_mime_multipart_get_count(multipart);
+	for (i = 0; i < n; i++) {
+	    subpart = g_mime_multipart_get_part(multipart, i);
+	    sign_prepare(subpart);
+	}
+    } else if (GMIME_IS_MESSAGE_PART(mime_part)) {
+	subpart = GMIME_MESSAGE_PART(mime_part)->message->mime_part;
+	sign_prepare(subpart);
+    } else {
+	encoding = g_mime_part_get_content_encoding(GMIME_PART(mime_part));
+	if (encoding != GMIME_CONTENT_ENCODING_BASE64)
+	    g_mime_part_set_content_encoding(GMIME_PART(mime_part),
+					     GMIME_CONTENT_ENCODING_QUOTEDPRINTABLE);
+    }
+}
+
+
+int
+g_mime_gpgme_mps_sign(GMimeMultipartSigned * mps, GMimeObject * content,
+		      const gchar * userid, gpgme_protocol_t protocol,
+		      GtkWindow * parent, GError ** err)
+{
+    GMimeStream *stream;
+    GMimeStream *filtered;
+    GMimeStream *sigstream;
+    GMimeFilter *filter;
+    GMimeContentType *content_type;
+    GMimeDataWrapper *wrapper;
+    GMimeParser *parser;
+    GMimePart *signature;
+    const gchar *sig_type;
+    gpgme_hash_algo_t hash_algo;
+
+    g_return_val_if_fail(GMIME_IS_MULTIPART_SIGNED(mps), -1);
+    g_return_val_if_fail(GMIME_IS_OBJECT(content), -1);
+
+    /* Prepare all the parts for signing... */
+    sign_prepare(content);
+
+    /* get the cleartext */
+    stream = g_mime_stream_mem_new();
+    filtered = g_mime_stream_filter_new(stream);
+
+    /* Note: see rfc3156, section 3 - second note */
+    filter = g_mime_filter_from_new(GMIME_FILTER_FROM_MODE_ARMOR);
+    g_mime_stream_filter_add(GMIME_STREAM_FILTER(filtered), filter);
+    g_object_unref(filter);
+
+    /* Note: see rfc3156, section 5.4 (this is the main difference between rfc2015 and rfc3156) */
+    filter = g_mime_filter_strip_new();
+    g_mime_stream_filter_add(GMIME_STREAM_FILTER(filtered), filter);
+    g_object_unref(filter);
+
+    g_mime_object_write_to_stream(content, filtered);
+    g_mime_stream_flush(filtered);
+    g_object_unref(filtered);
+    g_mime_stream_reset(stream);
+
+    /* Note: see rfc2015 or rfc3156, section 5.1 */
+    filtered = g_mime_stream_filter_new(stream);
+    filter = g_mime_filter_crlf_new(TRUE, FALSE);
+    g_mime_stream_filter_add(GMIME_STREAM_FILTER(filtered), filter);
+    g_object_unref(filter);
+
+    /* construct the signature stream */
+    sigstream = g_mime_stream_mem_new();
+
+    /* sign the content stream */
+    hash_algo =
+	libbalsa_gpgme_sign(userid, filtered, sigstream, protocol, FALSE,
+			    parent, err);
+    if (hash_algo == GPGME_MD_NONE) {
+	g_object_unref(sigstream);
+	g_object_unref(filtered);
+	g_object_unref(stream);
+	return -1;
+    }
+
+    g_object_unref(filtered);
+    g_mime_stream_reset(sigstream);
+    g_mime_stream_reset(stream);
+
+    /* set the multipart/signed protocol and micalg */
+    content_type = g_mime_object_get_content_type(GMIME_OBJECT(mps));
+    if (protocol == GPGME_PROTOCOL_OpenPGP) {
+	gchar *micalg;
+
+	micalg =
+	    g_strdup_printf("PGP-%s", gpgme_hash_algo_name(hash_algo));
+	g_mime_content_type_set_parameter(content_type, "micalg", micalg);
+	g_free(micalg);
+	sig_type = "application/pgp-signature";
+    } else {
+	g_mime_content_type_set_parameter(content_type, "micalg",
+					  gpgme_hash_algo_name(hash_algo));
+	sig_type = "application/pkcs7-signature";
+    }
+    g_mime_content_type_set_parameter(content_type, "protocol", sig_type);
+    g_mime_multipart_set_boundary(GMIME_MULTIPART(mps), NULL);
+
+    /* construct the content part */
+    parser = g_mime_parser_new_with_stream(stream);
+    content = g_mime_parser_construct_part(parser);
+    g_object_unref(stream);
+    g_object_unref(parser);
+
+    /* construct the signature part */
+    content_type = g_mime_content_type_new_from_string(sig_type);
+    signature =
+	g_mime_part_new_with_type(content_type->type,
+				  content_type->subtype);
+    g_object_unref(content_type);
+
+    wrapper = g_mime_data_wrapper_new();
+    g_mime_data_wrapper_set_stream(wrapper, sigstream);
+    g_mime_part_set_content_object(signature, wrapper);
+    g_object_unref(sigstream);
+    g_object_unref(wrapper);
+
+    /* FIXME: temporary hack, this info should probably be set in
+     * the CipherContext class - maybe ::sign can take/output a
+     * GMimePart instead. */
+    if (protocol == GPGME_PROTOCOL_CMS) {
+	g_mime_part_set_content_encoding(signature,
+					 GMIME_CONTENT_ENCODING_BASE64);
+	g_mime_part_set_filename(signature, "smime.p7m");
+    }
+
+    /* save the content and signature parts */
+    /* FIXME: make sure there aren't any other parts?? */
+    g_mime_multipart_add(GMIME_MULTIPART(mps), content);
+    g_mime_multipart_add(GMIME_MULTIPART(mps), (GMimeObject *) signature);
+    g_object_unref(signature);
+    g_object_unref(content);
+
+    return 0;
+}
+
+
+GMimeGpgmeSigstat *
+g_mime_gpgme_mps_verify(GMimeMultipartSigned * mps, GError ** error)
+{
+    const gchar *protocol;
+    gpgme_protocol_t crypto_prot;
+    gchar *content_type;
+    GMimeObject *content;
+    GMimeObject *signature;
+    GMimeStream *stream;
+    GMimeStream *filtered_stream;
+    GMimeFilter *crlf_filter;
+    GMimeDataWrapper *wrapper;
+    GMimeStream *sigstream;
+    GMimeGpgmeSigstat *result;
+
+    g_return_val_if_fail(GMIME_IS_MULTIPART_SIGNED(mps), NULL);
+
+    if (g_mime_multipart_get_count((GMimeMultipart *) mps) < 2) {
+	g_set_error(error, GMIME_ERROR, GMIME_ERROR_PARSE_ERROR, "%s",
+		    _
+		    ("Cannot verify multipart/signed part due to missing subparts."));
+	return NULL;
+    }
+
+    /* grab the protocol so we can configure the GpgME context */
+    protocol =
+	g_mime_object_get_content_type_parameter(GMIME_OBJECT(mps),
+						 "protocol");
+    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
+	crypto_prot = GPGME_PROTOCOL_UNKNOWN;
+
+    /* eject on unknown protocols */
+    if (crypto_prot == GPGME_PROTOCOL_UNKNOWN) {
+	g_set_error(error, GPGME_ERROR_QUARK, GPG_ERR_INV_VALUE,
+		    _("unsupported protocol '%s'"), protocol);
+	return NULL;
+    }
+
+    signature =
+	g_mime_multipart_get_part(GMIME_MULTIPART(mps),
+				  GMIME_MULTIPART_SIGNED_SIGNATURE);
+
+    /* make sure the protocol matches the signature content-type */
+    content_type = g_mime_content_type_to_string(signature->content_type);
+    if (g_ascii_strcasecmp(content_type, protocol) != 0) {
+	g_set_error(error, GMIME_ERROR, GMIME_ERROR_PARSE_ERROR, "%s",
+		    _
+		    ("Cannot verify multipart/signed part: signature content-type does not match protocol."));
+	g_free(content_type);
+	return NULL;
+    }
+    g_free(content_type);
+
+    content =
+	g_mime_multipart_get_part(GMIME_MULTIPART(mps),
+				  GMIME_MULTIPART_SIGNED_CONTENT);
+
+    /* get the content stream */
+    stream = g_mime_stream_mem_new();
+    filtered_stream = g_mime_stream_filter_new(stream);
+
+    /* Note: see rfc2015 or rfc3156, section 5.1 */
+    crlf_filter = g_mime_filter_crlf_new(TRUE, FALSE);
+    g_mime_stream_filter_add(GMIME_STREAM_FILTER(filtered_stream),
+			     crlf_filter);
+    g_object_unref(crlf_filter);
+
+    g_mime_object_write_to_stream(content, filtered_stream);
+    g_mime_stream_flush(filtered_stream);
+    g_object_unref(filtered_stream);
+    g_mime_stream_reset(stream);
+
+    /* get the signature stream */
+    wrapper = g_mime_part_get_content_object(GMIME_PART(signature));
+
+    /* FIXME: temporary hack for Balsa to support S/MIME,
+     * ::verify() should probably take a mime part so it can
+     * decode this itself if it needs to. */
+    if (crypto_prot == GPGME_PROTOCOL_CMS) {
+	sigstream = g_mime_stream_mem_new();
+	g_mime_data_wrapper_write_to_stream(wrapper, sigstream);
+    } else {
+	sigstream = g_mime_data_wrapper_get_stream(wrapper);
+    }
+
+    g_mime_stream_reset(sigstream);
+
+    /* verify the signature */
+    result =
+	libbalsa_gpgme_verify(stream, sigstream, crypto_prot, FALSE,
+			      error);
+    g_object_unref(stream);
+
+    return result;
+}
+
+
+int
+g_mime_gpgme_mpe_encrypt(GMimeMultipartEncrypted * mpe,
+			 GMimeObject * content, GPtrArray * recipients,
+			 gboolean trust_all, GtkWindow * parent,
+			 GError ** err)
+{
+    GMimeStream *filtered_stream;
+    GMimeStream *ciphertext;
+    GMimeStream *stream;
+    GMimePart *version_part;
+    GMimePart *encrypted_part;
+    GMimeContentType *content_type;
+    GMimeDataWrapper *wrapper;
+    GMimeFilter *crlf_filter;
+
+    g_return_val_if_fail(GMIME_IS_MULTIPART_ENCRYPTED(mpe), -1);
+    g_return_val_if_fail(GMIME_IS_OBJECT(content), -1);
+
+    /* get the cleartext */
+    stream = g_mime_stream_mem_new();
+    filtered_stream = g_mime_stream_filter_new(stream);
+
+    crlf_filter = g_mime_filter_crlf_new(TRUE, FALSE);
+    g_mime_stream_filter_add(GMIME_STREAM_FILTER(filtered_stream),
+			     crlf_filter);
+    g_object_unref(crlf_filter);
+
+    g_mime_object_write_to_stream(content, filtered_stream);
+    g_mime_stream_flush(filtered_stream);
+    g_object_unref(filtered_stream);
+
+    /* reset the content stream */
+    g_mime_stream_reset(stream);
+
+    /* encrypt the content stream */
+    ciphertext = g_mime_stream_mem_new();
+    if (libbalsa_gpgme_encrypt
+	(recipients, NULL, stream, ciphertext, GPGME_PROTOCOL_OpenPGP,
+	 FALSE, trust_all, parent, err) == -1) {
+	g_object_unref(ciphertext);
+	g_object_unref(stream);
+	return -1;
+    }
+
+    g_object_unref(stream);
+    g_mime_stream_reset(ciphertext);
+
+    /* construct the version part */
+    content_type =
+	g_mime_content_type_new_from_string("application/pgp-encrypted");
+    version_part =
+	g_mime_part_new_with_type(content_type->type,
+				  content_type->subtype);
+    g_object_unref(content_type);
+
+    content_type =
+	g_mime_content_type_new_from_string("application/pgp-encrypted");
+    g_mime_object_set_content_type(GMIME_OBJECT(version_part),
+				   content_type);
+    g_mime_part_set_content_encoding(version_part,
+				     GMIME_CONTENT_ENCODING_7BIT);
+    stream =
+	g_mime_stream_mem_new_with_buffer("Version: 1\n",
+					  strlen("Version: 1\n"));
+    wrapper =
+	g_mime_data_wrapper_new_with_stream(stream,
+					    GMIME_CONTENT_ENCODING_7BIT);
+    g_mime_part_set_content_object(version_part, wrapper);
+    g_object_unref(wrapper);
+    g_object_unref(stream);
+
+#if !defined(HAVE_GMIME_2_6)
+    mpe->decrypted = content;
+    g_object_ref(content);
+#endif
+
+    /* construct the encrypted mime part */
+    encrypted_part =
+	g_mime_part_new_with_type("application", "octet-stream");
+    g_mime_part_set_content_encoding(encrypted_part,
+				     GMIME_CONTENT_ENCODING_7BIT);
+    wrapper =
+	g_mime_data_wrapper_new_with_stream(ciphertext,
+					    GMIME_CONTENT_ENCODING_7BIT);
+    g_mime_part_set_content_object(encrypted_part, wrapper);
+    g_object_unref(ciphertext);
+    g_object_unref(wrapper);
+
+    /* save the version and encrypted parts */
+    /* FIXME: make sure there aren't any other parts?? */
+    g_mime_multipart_add(GMIME_MULTIPART(mpe), GMIME_OBJECT(version_part));
+    g_mime_multipart_add(GMIME_MULTIPART(mpe),
+			 GMIME_OBJECT(encrypted_part));
+    g_object_unref(encrypted_part);
+    g_object_unref(version_part);
+
+    /* set the content-type params for this multipart/encrypted part */
+    g_mime_object_set_content_type_parameter(GMIME_OBJECT(mpe), "protocol",
+					     "application/pgp-encrypted");
+    g_mime_multipart_set_boundary(GMIME_MULTIPART(mpe), NULL);
+
+    return 0;
+}
+
+
+static GMimeStream *
+g_mime_data_wrapper_get_decoded_stream(GMimeDataWrapper * wrapper)
+{
+    GMimeStream *decoded_stream;
+    GMimeFilter *decoder;
+
+    g_mime_stream_reset(wrapper->stream);
+
+    switch (wrapper->encoding) {
+    case GMIME_CONTENT_ENCODING_BASE64:
+    case GMIME_CONTENT_ENCODING_QUOTEDPRINTABLE:
+    case GMIME_CONTENT_ENCODING_UUENCODE:
+	decoder = g_mime_filter_basic_new(wrapper->encoding, FALSE);
+	decoded_stream = g_mime_stream_filter_new(wrapper->stream);
+	g_mime_stream_filter_add(GMIME_STREAM_FILTER(decoded_stream),
+				 decoder);
+	g_object_unref(decoder);
+	break;
+    default:
+	decoded_stream = wrapper->stream;
+	g_object_ref(wrapper->stream);
+	break;
+    }
+
+    return decoded_stream;
+}
+
+
+GMimeObject *
+g_mime_gpgme_mpe_decrypt(GMimeMultipartEncrypted * mpe,
+			 GMimeGpgmeSigstat ** signature,
+			 GtkWindow * parent, GError ** err)
+{
+    GMimeObject *decrypted, *version, *encrypted;
+    GMimeStream *stream, *ciphertext;
+    GMimeStream *filtered_stream;
+    GMimeContentType *mime_type;
+    GMimeGpgmeSigstat *sigstat;
+    GMimeDataWrapper *wrapper;
+    GMimeFilter *crlf_filter;
+    GMimeParser *parser;
+    const char *protocol;
+    char *content_type;
+
+    g_return_val_if_fail(GMIME_IS_MULTIPART_ENCRYPTED(mpe), NULL);
+
+#if !defined(HAVE_GMIME_2_6)
+    if (mpe->decrypted) {
+	/* we seem to have already decrypted the part */
+	return mpe->decrypted;
+    }
+#endif
+
+    if (signature && *signature) {
+	g_object_unref(G_OBJECT(*signature));
+	*signature = NULL;
+    }
+
+    protocol =
+	g_mime_object_get_content_type_parameter(GMIME_OBJECT(mpe),
+						 "protocol");
+
+    /* make sure the protocol is present and matches the cipher encrypt protocol */
+    if (!protocol
+	|| g_ascii_strcasecmp("application/pgp-encrypted",
+			      protocol) != 0) {
+	g_set_error(err, GMIME_ERROR, GMIME_ERROR_PROTOCOL_ERROR,
+		    _
+		    ("Cannot decrypt multipart/encrypted part: unsupported encryption protocol '%s'."),
+		    protocol ? protocol : _("(none)"));
+	return NULL;
+    }
+
+    version =
+	g_mime_multipart_get_part(GMIME_MULTIPART(mpe),
+				  GMIME_MULTIPART_ENCRYPTED_VERSION);
+
+    /* make sure the protocol matches the version part's content-type */
+    content_type = g_mime_content_type_to_string(version->content_type);
+    if (g_ascii_strcasecmp(content_type, protocol) != 0) {
+	g_set_error(err, GMIME_ERROR, GMIME_ERROR_PROTOCOL_ERROR, "%s",
+		    _
+		    ("Cannot decrypt multipart/encrypted part: content-type does not match protocol."));
+	g_free(content_type);
+	return NULL;
+    }
+    g_free(content_type);
+
+    /* get the encrypted part and check that it is of type application/octet-stream */
+    encrypted =
+	g_mime_multipart_get_part(GMIME_MULTIPART(mpe),
+				  GMIME_MULTIPART_ENCRYPTED_CONTENT);
+    mime_type = g_mime_object_get_content_type(encrypted);
+    if (!g_mime_content_type_is_type
+	(mime_type, "application", "octet-stream")) {
+	g_set_error(err, GMIME_ERROR, GMIME_ERROR_PROTOCOL_ERROR, "%s",
+		    _
+		    ("Cannot decrypt multipart/encrypted part: unexpected content type"));
+	return NULL;
+    }
+
+    /* get the ciphertext stream */
+    wrapper = g_mime_part_get_content_object(GMIME_PART(encrypted));
+    ciphertext = g_mime_data_wrapper_get_decoded_stream(wrapper);
+    g_mime_stream_reset(ciphertext);
+
+    stream = g_mime_stream_mem_new();
+    filtered_stream = g_mime_stream_filter_new(stream);
+    crlf_filter = g_mime_filter_crlf_new(FALSE, FALSE);
+    g_mime_stream_filter_add(GMIME_STREAM_FILTER(filtered_stream),
+			     crlf_filter);
+    g_object_unref(crlf_filter);
+
+    /* get the cleartext */
+    sigstat =
+	libbalsa_gpgme_decrypt(ciphertext, filtered_stream,
+			       GPGME_PROTOCOL_OpenPGP, parent, err);
+    if (!sigstat) {
+	g_object_unref(filtered_stream);
+	g_object_unref(ciphertext);
+	g_object_unref(stream);
+	return NULL;
+    }
+
+    g_mime_stream_flush(filtered_stream);
+    g_object_unref(filtered_stream);
+    g_object_unref(ciphertext);
+
+    g_mime_stream_reset(stream);
+    parser = g_mime_parser_new();
+    g_mime_parser_init_with_stream(parser, stream);
+    g_object_unref(stream);
+
+    decrypted = g_mime_parser_construct_part(parser);
+    g_object_unref(parser);
+
+    if (!decrypted) {
+	g_set_error(err, GMIME_ERROR, GMIME_ERROR_PARSE_ERROR, "%s",
+		    _
+		    ("Cannot decrypt multipart/encrypted part: failed to parse decrypted content"));
+	g_object_unref(G_OBJECT(sigstat));
+	return NULL;
+    }
+
+
+    /* cache the decrypted part */
+#if !defined(HAVE_GMIME_2_6)
+    mpe->decrypted = decrypted;
+#endif
+    if (signature) {
+	if (sigstat->status != GPG_ERR_NOT_SIGNED)
+	    *signature = sigstat;
+	else
+	    g_object_unref(G_OBJECT(sigstat));
+    }
+
+    return decrypted;
+}
diff --git a/libbalsa/gmime-multipart-crypt.h b/libbalsa/gmime-multipart-crypt.h
new file mode 100644
index 0000000..f7cc428
--- /dev/null
+++ b/libbalsa/gmime-multipart-crypt.h
@@ -0,0 +1,69 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/*
+ * gmime/gpgme implementation for multipart/signed and multipart/encrypted
+ * Copyright (C) 2011 Albrecht Dreà <albrecht dress arcor de>
+ *
+ * The functions in this module were copied from the original GMime
+ * implementation of multipart/signed and multipart/encrypted parts.
+ * However, instead of using the complex GMime crypto contexts (which have
+ * a varying API over the different versions), this module directly calls
+ * the GpgME backend functions implemented in libbalsa-gpgme.[hc].
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  
+ * 02111-1307, USA.
+ */
+
+#ifndef __GMIME_MULTIPART_CRYPT_H__
+#define __GMIME_MULTIPART_CRYPT_H__
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gmime/gmime.h>
+#include <gpgme.h>
+#include "gmime-gpgme-signature.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#ifdef MAKE_EMACS_HAPPY
+}
+#endif
+#endif				/* __cplusplus */
+
+
+GMimeGpgmeSigstat *g_mime_gpgme_mps_verify(GMimeMultipartSigned * mps,
+					   GError ** error);
+int g_mime_gpgme_mps_sign(GMimeMultipartSigned * mps,
+			  GMimeObject * content, const gchar * userid,
+			  gpgme_protocol_t protocol, GtkWindow * parent,
+			  GError ** err);
+int g_mime_gpgme_mpe_encrypt(GMimeMultipartEncrypted * mpe,
+			     GMimeObject * content, GPtrArray * recipients,
+			     gboolean trust_all, GtkWindow * parent,
+			     GError ** err);
+GMimeObject *g_mime_gpgme_mpe_decrypt(GMimeMultipartEncrypted * mpe,
+				      GMimeGpgmeSigstat ** signature,
+				      GtkWindow * parent, GError ** err);
+
+
+#ifdef __cplusplus
+}
+#endif				/* __cplusplus */
+
+#endif				/* __GMIME_MULTIPART_CRYPT_H__ */
diff --git a/libbalsa/libbalsa-gpgme-cb.c b/libbalsa/libbalsa-gpgme-cb.c
new file mode 100644
index 0000000..da0a8d9
--- /dev/null
+++ b/libbalsa/libbalsa-gpgme-cb.c
@@ -0,0 +1,428 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/*
+ * gpgme standard callback functions for balsa
+ * Copyright (C) 2011 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#if HAVE_MACOSX_DESKTOP
+#include "macosx-helpers.h"
+#endif
+
+#ifdef BALSA_USE_THREADS
+#include <pthread.h>
+#include "misc.h"
+#endif
+
+#include <gpgme.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "rfc3156.h"
+#include "libbalsa-gpgme-cb.h"
+
+
+/* stuff to get a key fingerprint from a selection list */
+enum {
+    GPG_KEY_USER_ID_COLUMN = 0,
+    GPG_KEY_ID_COLUMN,
+    GPG_KEY_LENGTH_COLUMN,
+    GPG_KEY_VALIDITY_COLUMN,
+    GPG_KEY_PTR_COLUMN,
+    GPG_KEY_NUM_COLUMNS
+};
+
+#ifdef BALSA_USE_THREADS
+/* FIXME: is this really necessary? */
+typedef struct {
+    pthread_cond_t cond;
+    const gchar *uid_hint;
+    const gchar *passphrase_info;
+    gint was_bad;
+    GtkWindow *parent;
+    gchar *res;
+} ask_passphrase_data_t;
+#endif
+
+
+static void key_selection_changed_cb(GtkTreeSelection * selection,
+				     gpgme_key_t * key);
+static gchar *get_passphrase_real(const gchar * uid_hint,
+				  const gchar * passphrase_info,
+				  int prev_was_bad, GtkWindow * parent);
+
+#ifdef BALSA_USE_THREADS
+static gboolean get_passphrase_idle(gpointer data);
+#endif
+
+
+gpgme_error_t
+lb_gpgme_passphrase(void *hook, const gchar * uid_hint,
+		    const gchar * passphrase_info, int prev_was_bad,
+		    int fd)
+{
+    int foo, bar;
+    gchar *passwd;
+    gchar *p;
+    GtkWindow *parent;
+
+    if (hook && GTK_IS_WINDOW(hook))
+	parent = (GtkWindow *) hook;
+    else
+	parent = NULL;
+
+#ifdef BALSA_USE_THREADS
+    if (!libbalsa_am_i_subthread())
+	passwd =
+	    get_passphrase_real(uid_hint, passphrase_info, prev_was_bad,
+				parent);
+    else {
+	static pthread_mutex_t get_passphrase_lock =
+	    PTHREAD_MUTEX_INITIALIZER;
+	ask_passphrase_data_t apd;
+
+	pthread_mutex_lock(&get_passphrase_lock);
+	pthread_cond_init(&apd.cond, NULL);
+	apd.uid_hint = uid_hint;
+	apd.was_bad = prev_was_bad;
+	apd.passphrase_info = passphrase_info;
+	apd.parent = parent;
+	g_idle_add(get_passphrase_idle, &apd);
+	pthread_cond_wait(&apd.cond, &get_passphrase_lock);
+	pthread_cond_destroy(&apd.cond);
+	pthread_mutex_unlock(&get_passphrase_lock);
+	passwd = apd.res;
+    }
+#else
+    passwd = get_passphrase_real(context, uid_hint, prev_was_bad);
+#endif				/* BALSA_USE_THREADS */
+
+    if (!passwd) {
+	foo = write(fd, "\n", 1);
+	return foo > 0 ? GPG_ERR_CANCELED : GPG_ERR_EIO;
+    }
+
+    /* send the passphrase and erase the string */
+    foo = write(fd, passwd, strlen(passwd));
+    for (p = passwd; *p; p++)
+	*p = random();
+    g_free(passwd);
+    bar = write(fd, "\n", 1);
+    return foo > 0 && bar > 0 ? GPG_ERR_NO_ERROR : GPG_ERR_EIO;
+}
+
+
+gpgme_key_t
+lb_gpgme_select_key(const gchar * user_name, gboolean secret, GList * keys,
+		    gpgme_protocol_t protocol, GtkWindow * parent)
+{
+    static const gchar *col_titles[] =
+	{ N_("User ID"), N_("Key ID"), N_("Length"), N_("Validity") };
+    GtkWidget *dialog;
+    GtkWidget *vbox;
+    GtkWidget *label;
+    GtkWidget *scrolled_window;
+    GtkWidget *tree_view;
+    GtkTreeStore *model;
+    GtkTreeSelection *selection;
+    GtkTreeIter iter;
+    gint i, last_col;
+    gchar *prompt;
+    gchar *upcase_name;
+    gpgme_key_t use_key = NULL;
+
+    /* FIXME: create dialog according to the Gnome HIG */
+    dialog = gtk_dialog_new_with_buttons(_("Select key"),
+					 parent,
+					 GTK_DIALOG_DESTROY_WITH_PARENT,
+					 GTK_STOCK_OK, GTK_RESPONSE_OK,
+					 GTK_STOCK_CANCEL,
+					 GTK_RESPONSE_CANCEL, NULL);
+#if HAVE_MACOSX_DESKTOP
+    libbalsa_macosx_menu_for_parent(dialog, parent);
+#endif
+    vbox = gtk_vbox_new(FALSE, 12);
+    gtk_container_add(GTK_CONTAINER
+		      (gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
+		      vbox);
+    gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
+    if (secret)
+	prompt =
+	    g_strdup_printf(_("Select the private key for the signer %s"),
+			    user_name);
+    else
+	prompt = g_strdup_printf(_
+				 ("Select the public key for the recipient %s"),
+				 user_name);
+    label = gtk_label_new(prompt);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+    g_free(prompt);
+    gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
+
+    scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW
+					(scrolled_window),
+					GTK_SHADOW_ETCHED_IN);
+    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+				   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+    gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0);
+
+    model = gtk_tree_store_new(GPG_KEY_NUM_COLUMNS, G_TYPE_STRING,	/* user ID */
+			       G_TYPE_STRING,	/* key ID */
+			       G_TYPE_INT,	/* length */
+			       G_TYPE_STRING,	/* validity (gpg encrypt only) */
+			       G_TYPE_POINTER);	/* key */
+
+    tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
+    gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
+    g_signal_connect(G_OBJECT(selection), "changed",
+		     G_CALLBACK(key_selection_changed_cb), &use_key);
+
+    /* add the keys */
+    upcase_name = g_ascii_strup(user_name, -1);
+    while (keys) {
+	gpgme_key_t key = (gpgme_key_t) keys->data;
+	gpgme_subkey_t subkey = key->subkeys;
+	gpgme_user_id_t uid = key->uids;
+	gchar *uid_info = NULL;
+	gboolean uid_found;
+
+	/* find the relevant subkey */
+	while (subkey && ((secret && !subkey->can_sign) ||
+			  (!secret && !subkey->can_encrypt)))
+	    subkey = subkey->next;
+
+	/* find the relevant uid */
+	uid_found = FALSE;
+	while (uid && !uid_found) {
+	    g_free(uid_info);
+	    uid_info = libbalsa_cert_subject_readable(uid->uid);
+
+	    /* check the email field which may or may not be present */
+	    if (uid->email && !g_ascii_strcasecmp(uid->email, user_name))
+		uid_found = TRUE;
+	    else {
+		/* no email or no match, check the uid */
+		gchar *upcase_uid = g_ascii_strup(uid_info, -1);
+
+		if (strstr(upcase_uid, upcase_name))
+		    uid_found = TRUE;
+		else
+		    uid = uid->next;
+		g_free(upcase_uid);
+	    }
+	}
+
+	/* append the element */
+	if (subkey && uid) {
+	    gtk_tree_store_append(GTK_TREE_STORE(model), &iter, NULL);
+	    gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
+			       GPG_KEY_USER_ID_COLUMN, uid_info,
+			       GPG_KEY_ID_COLUMN, subkey->keyid,
+			       GPG_KEY_LENGTH_COLUMN, subkey->length,
+			       GPG_KEY_VALIDITY_COLUMN,
+			       libbalsa_gpgme_validity_to_gchar_short(uid->
+								      validity),
+			       GPG_KEY_PTR_COLUMN, key, -1);
+	}
+	g_free(uid_info);
+	keys = g_list_next(keys);
+    }
+    g_free(upcase_name);
+
+    g_object_unref(G_OBJECT(model));
+    /* show the validity only if we are asking for a gpg public key */
+    last_col = (protocol == GPGME_PROTOCOL_CMS || secret) ?
+	GPG_KEY_LENGTH_COLUMN : GPG_KEY_VALIDITY_COLUMN;
+    for (i = 0; i <= last_col; i++) {
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *column;
+
+	renderer = gtk_cell_renderer_text_new();
+	column =
+	    gtk_tree_view_column_new_with_attributes(_(col_titles[i]),
+						     renderer, "text", i,
+						     NULL);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
+	gtk_tree_view_column_set_resizable(column, TRUE);
+    }
+
+    gtk_container_add(GTK_CONTAINER(scrolled_window), tree_view);
+    gtk_window_set_default_size(GTK_WINDOW(dialog), 500, 300);
+    gtk_widget_show_all(gtk_dialog_get_content_area(GTK_DIALOG(dialog)));
+
+    if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_OK)
+	use_key = NULL;
+    gtk_widget_destroy(dialog);
+
+    return use_key;
+}
+
+
+gboolean
+lb_gpgme_accept_low_trust_key(const gchar * user_name,
+			      const gpgme_user_id_t user_id,
+			      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?"));
+#if HAVE_MACOSX_DESKTOP
+    libbalsa_macosx_menu_for_parent(dialog, parent);
+#endif
+
+    /* ask the user */
+    result = gtk_dialog_run(GTK_DIALOG(dialog));
+    gtk_widget_destroy(dialog);
+
+    return result == GTK_RESPONSE_YES;
+
+}
+
+
+#include "padlock-keyhole.xpm"
+
+
+static gchar *
+get_passphrase_real(const gchar * uid_hint, const gchar * passphrase_info,
+		    int prev_was_bad, GtkWindow * parent)
+{
+    static GdkPixbuf *padlock_keyhole = NULL;
+    GtkWidget *dialog, *entry, *vbox, *hbox;
+    gchar *prompt, *passwd;
+
+    /* FIXME: create dialog according to the Gnome HIG */
+    dialog = gtk_dialog_new_with_buttons(_("Enter Passphrase"), parent,
+					 GTK_DIALOG_DESTROY_WITH_PARENT,
+					 GTK_STOCK_OK, GTK_RESPONSE_OK,
+					 GTK_STOCK_CANCEL,
+					 GTK_RESPONSE_CANCEL, NULL);
+#if HAVE_MACOSX_DESKTOP
+    libbalsa_macosx_menu_for_parent(dialog, parent);
+#endif
+    hbox = gtk_hbox_new(FALSE, 12);
+    gtk_container_set_border_width(GTK_CONTAINER(hbox), 12);
+    gtk_container_add(GTK_CONTAINER
+		      (gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
+		      hbox);
+
+    vbox = gtk_vbox_new(FALSE, 12);
+    gtk_container_add(GTK_CONTAINER(hbox), vbox);
+    if (!padlock_keyhole)
+	padlock_keyhole =
+	    gdk_pixbuf_new_from_xpm_data(padlock_keyhole_xpm);
+    gtk_box_pack_start(GTK_BOX(vbox),
+		       gtk_image_new_from_pixbuf(padlock_keyhole), FALSE,
+		       FALSE, 0);
+    vbox = gtk_vbox_new(FALSE, 12);
+    gtk_container_add(GTK_CONTAINER(hbox), vbox);
+    if (prev_was_bad)
+	prompt =
+	    g_strdup_printf(_
+			    ("%s\nThe passphrase for this key was bad, please try again!\n\nKey: %s"),
+			    passphrase_info, uid_hint);
+    else
+	prompt =
+	    g_strdup_printf(_
+			    ("%s\nPlease enter the passphrase for the secret key!\n\nKey: %s"),
+			    passphrase_info, uid_hint);
+    gtk_container_add(GTK_CONTAINER(vbox), gtk_label_new(prompt));
+    g_free(prompt);
+    entry = gtk_entry_new();
+    gtk_container_add(GTK_CONTAINER(vbox), entry);
+
+    gtk_widget_show_all(gtk_dialog_get_content_area(GTK_DIALOG(dialog)));
+    gtk_entry_set_width_chars(GTK_ENTRY(entry), 40);
+    gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+
+    gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
+    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
+    gtk_widget_grab_focus(entry);
+    if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK)
+	passwd = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
+    else
+	passwd = NULL;
+
+    gtk_widget_destroy(dialog);
+
+    return passwd;
+}
+
+
+#ifdef BALSA_USE_THREADS
+/* get_passphrase_idle:
+   called in MT mode by the main thread.
+ */
+static gboolean
+get_passphrase_idle(gpointer data)
+{
+    ask_passphrase_data_t *apd = (ask_passphrase_data_t *) data;
+
+    gdk_threads_enter();
+    apd->res =
+	get_passphrase_real(apd->uid_hint, apd->passphrase_info,
+			    apd->was_bad, apd->parent);
+    gdk_threads_leave();
+    pthread_cond_signal(&apd->cond);
+    return FALSE;
+}
+#endif
+
+
+/* callback function if a new row is selected in the list */
+static void
+key_selection_changed_cb(GtkTreeSelection * selection, gpgme_key_t * key)
+{
+    GtkTreeIter iter;
+    GtkTreeModel *model;
+
+    if (gtk_tree_selection_get_selected(selection, &model, &iter))
+	gtk_tree_model_get(model, &iter, GPG_KEY_PTR_COLUMN, key, -1);
+}
diff --git a/libbalsa/libbalsa-gpgme-cb.h b/libbalsa/libbalsa-gpgme-cb.h
new file mode 100644
index 0000000..c0364bf
--- /dev/null
+++ b/libbalsa/libbalsa-gpgme-cb.h
@@ -0,0 +1,59 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/*
+ * gpgme standard callback functions for balsa
+ * Copyright (C) 2011 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef LIBBALSA_GPGME_CB_H_
+#define LIBBALSA_GPGME_CB_H_
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gpgme.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#ifdef MAKE_EMACS_HAPPY
+}
+#endif
+#endif				/* __cplusplus */
+
+
+gpgme_error_t lb_gpgme_passphrase(void *hook, const gchar * uid_hint,
+				  const gchar * passphrase_info,
+				  int prev_was_bad, int fd);
+gpgme_key_t lb_gpgme_select_key(const gchar * user_name, gboolean secret,
+				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);
+
+
+#ifdef __cplusplus
+}
+#endif				/* __cplusplus */
+
+
+#endif				/* LIBBALSA_GPGME_CB_H_ */
diff --git a/libbalsa/libbalsa-gpgme.c b/libbalsa/libbalsa-gpgme.c
new file mode 100644
index 0000000..6932acb
--- /dev/null
+++ b/libbalsa/libbalsa-gpgme.c
@@ -0,0 +1,968 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/*
+ * gpgme low-level stuff for gmime/balsa
+ * Copyright (C) 2011 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#ifdef BALSA_USE_THREADS
+#include <pthread.h>
+#include "misc.h"
+#endif
+
+#if HAVE_MACOSX_DESKTOP
+#include "macosx-helpers.h"
+#endif
+
+#include <string.h>
+#include <gpgme.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gmime/gmime.h>
+#include "gmime-gpgme-signature.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,
+					   GPtrArray * rcpt_list,
+					   gboolean accept_low_trust,
+					   GtkWindow * parent,
+					   GError ** error);
+static void release_keylist(gpgme_key_t * keylist);
+
+/* callbacks for gpgme file handling */
+static ssize_t g_mime_gpgme_stream_rd(GMimeStream * stream, void *buffer,
+				      size_t size);
+static ssize_t g_mime_gpgme_stream_wr(GMimeStream * stream, void *buffer,
+				      size_t size);
+static void cb_data_release(void *handle);
+
+
+#if defined(ENABLE_NLS)
+static const gchar *get_utf8_locale(int category);
+#endif
+
+
+static gboolean has_proto_openpgp = FALSE;
+static gboolean has_proto_cms = FALSE;
+
+static gpgme_passphrase_cb_t gpgme_passphrase_cb = NULL;
+static lbgpgme_select_key_cb select_key_cb = NULL;
+static lbgpgme_accept_low_trust_cb accept_low_trust_cb = NULL;
+
+
+/** \brief Initialise GpgME
+ *
+ * \param get_passphrase Callback function to read a passphrase from the
+ *        user.  Note that this function is used \em only for OpenPGP and
+ *        \em only if no GPG Agent is running and can therefore usually be
+ *        NULL.  The first (HOOK) argument the passed function accepts
+ *        shall be the parent GtkWindow.
+ * \param select_key Callback function to let the user select a key from a
+ *        list if more than one is available.
+ * \param accept_low_trust Callback function to ask the user whether a low
+ *	  trust key shall be accepted.
+ *
+ * Initialise the GpgME backend and remember the callback functions.
+ *
+ * \note This function \em must be called before using any other function
+ *       from this module.
+ */
+void
+libbalsa_gpgme_init(gpgme_passphrase_cb_t get_passphrase,
+		    lbgpgme_select_key_cb select_key,
+		    lbgpgme_accept_low_trust_cb accept_low_trust)
+{
+    gpgme_engine_info_t e;
+    const gchar *agent_info;
+
+    /* 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));
+#endif				/* ENABLE_NLS */
+
+    /* dump the available engines */
+    if (gpgme_get_engine_info(&e) == GPG_ERR_NO_ERROR) {
+	while (e) {
+	    g_message("protocol %s: engine %s (home %s, version %s)",
+		      gpgme_get_protocol_name(e->protocol),
+		      e->file_name, e->home_dir, e->version);
+	    e = e->next;
+	}
+    }
+
+    /* check for gpg-agent */
+    agent_info = g_getenv("GPG_AGENT_INFO");
+    if (agent_info) {
+	g_message("gpg-agent found: %s", agent_info);
+	gpgme_passphrase_cb = NULL;
+    } else {
+	gpgme_passphrase_cb = get_passphrase;
+    }
+
+    /* verify that the engines we need are there */
+    if (gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP) ==
+	GPG_ERR_NO_ERROR) {
+	g_message("OpenPGP protocol supported");
+	has_proto_openpgp = TRUE;
+    } else {
+	g_warning
+	    ("OpenPGP protocol not supported, basic crypto will not work!");
+	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;
+    } else {
+	g_warning("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 = select_key_cb;
+    accept_low_trust = accept_low_trust_cb;
+}
+
+
+/** \brief Check if a crypto engine is available
+ *
+ * \param protocol Protocol for which the engine is checked.
+ * \return TRUE is the engine for the passed protocol is available.
+ *
+ * Check the availability of the crypto engine for a specific protocol.
+ */
+gboolean
+libbalsa_gpgme_check_crypto_engine(gpgme_protocol_t protocol)
+{
+    switch (protocol) {
+    case GPGME_PROTOCOL_OpenPGP:
+	return has_proto_openpgp;
+    case GPGME_PROTOCOL_CMS:
+	return has_proto_cms;
+    default:
+	return FALSE;
+    }
+}
+
+
+/** \brief Verify a signature
+ *
+ * \param content GMime stream of the signed matter.
+ * \param sig_plain GMime signature stream for a detached signature, or the
+ *        output stream for the checked matter in single-part mode.
+ * \param protocol GpgME crypto protocol of the signature.
+ * \param singlepart_mode TRUE indicates single-part mode (i.e. sig_plain
+ *        an output stream).
+ * \param error Filled with error information on error.
+ * \return A new signature status object on success, or NULL on error.
+ *
+ * Verify a signature by calling GpgME on the passed streams, and create a
+ * new signature object on success.
+ */
+GMimeGpgmeSigstat *
+libbalsa_gpgme_verify(GMimeStream * content, GMimeStream * sig_plain,
+		      gpgme_protocol_t protocol, gboolean singlepart_mode,
+		      GError ** error)
+{
+    gpgme_error_t err;
+    gpgme_ctx_t ctx;
+    struct gpgme_data_cbs cbs = {
+	(gpgme_data_read_cb_t) g_mime_gpgme_stream_rd,	/* read method */
+	(gpgme_data_write_cb_t) g_mime_gpgme_stream_wr,	/* write method */
+	NULL,			/* seek method */
+	cb_data_release		/* release method */
+    };
+    gpgme_data_t cont_data;
+    gpgme_data_t sig_plain_data;
+    GMimeGpgmeSigstat *result;
+
+    /* paranoia checks */
+    g_return_val_if_fail(GMIME_IS_STREAM(content), NULL);
+    g_return_val_if_fail(GMIME_IS_STREAM(sig_plain), NULL);
+    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 message stream */
+    if ((err =
+	 gpgme_data_new_from_cbs(&cont_data, &cbs,
+				 content)) != GPG_ERR_NO_ERROR) {
+	g_set_error_from_gpgme(error, err,
+			       _("could not get data from stream"));
+	gpgme_release(ctx);
+	return NULL;
+    }
+
+    /* create data object for the detached signature stream or the
+     * "decrypted" plaintext */
+    if ((err =
+	 gpgme_data_new_from_cbs(&sig_plain_data, &cbs,
+				 sig_plain)) != GPG_ERR_NO_ERROR) {
+	g_set_error_from_gpgme(error, err,
+			       _("could not get data from stream"));
+	gpgme_data_release(cont_data);
+	gpgme_release(ctx);
+	return NULL;
+    }
+
+    /* verify the signature */
+    if (singlepart_mode)
+	err = gpgme_op_verify(ctx, cont_data, NULL, sig_plain_data);
+    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,
+			       _("signature verification failed"));
+	result = g_mime_gpgme_sigstat_new();
+	result->status = err;
+	result->protocol = gpgme_get_protocol(ctx);
+    } else
+	result = g_mime_gpgme_sigstat_new_from_gpgme_ctx(ctx);
+
+    /* release gmgme data buffers, destroy the context and return the
+     * signature object */
+    gpgme_data_release(cont_data);
+    gpgme_data_release(sig_plain_data);
+    gpgme_release(ctx);
+    return result;
+}
+
+
+/** \brief Sign data
+ *
+ * \param userid User ID of the signer.
+ * \param istream GMime input stream.
+ * \param ostream GMime output stream.
+ * \param protocol GpgME crypto protocol of the signature.
+ * \param singlepart_mode TRUE indicates single-part mode (integrated
+ *        signature), FALSE a detached signature.
+ * \param parent Parent window to be passed to the passphrase callback
+ *        function.
+ * \param error Filled with error information on error.
+ * \return The hash algorithm used for creating the signature, or
+ *         GPGME_MD_NONE on error.
+ *
+ * Sign the passed matter and write the detached signature or the signed
+ * input and the signature, respectively, to the output stream.  The global
+ * callback to read the passphrase for the user's private key will be
+ * called by GpgME if no GPG Agent is running.
+ */
+gpgme_hash_algo_t
+libbalsa_gpgme_sign(const gchar * userid, GMimeStream * istream,
+		    GMimeStream * ostream, gpgme_protocol_t protocol,
+		    gboolean singlepart_mode, GtkWindow * parent,
+		    GError ** error)
+{
+    gpgme_error_t err;
+    gpgme_ctx_t ctx;
+    gpgme_sig_mode_t sig_mode;
+    gpgme_data_t in;
+    gpgme_data_t out;
+    gpgme_hash_algo_t hash_algo;
+    struct gpgme_data_cbs cbs = {
+	(gpgme_data_read_cb_t) g_mime_gpgme_stream_rd,	/* read method */
+	(gpgme_data_write_cb_t) g_mime_gpgme_stream_wr,	/* write method */
+	NULL,			/* seek method */
+	cb_data_release		/* release method */
+    };
+
+    /* paranoia checks */
+    g_return_val_if_fail(GMIME_IS_STREAM(istream), GPGME_MD_NONE);
+    g_return_val_if_fail(GMIME_IS_STREAM(ostream), GPGME_MD_NONE);
+    g_return_val_if_fail(protocol == GPGME_PROTOCOL_OpenPGP ||
+			 protocol == GPGME_PROTOCOL_CMS, GPGME_MD_NONE);
+
+    /* create the GpgME context */
+    if ((err =
+	 gpgme_new_with_protocol(&ctx, protocol, parent,
+				 error)) != GPG_ERR_NO_ERROR)
+	return GPGME_MD_NONE;
+
+    /* set the signature mode */
+    if (singlepart_mode) {
+	if (protocol == GPGME_PROTOCOL_OpenPGP)
+	    sig_mode = GPGME_SIG_MODE_CLEAR;
+	else
+	    sig_mode = GPGME_SIG_MODE_NORMAL;
+    } else
+	sig_mode = GPGME_SIG_MODE_DETACH;
+
+    /* find the secret key for the "sign_for" address */
+    if (!gpgme_add_signer(ctx, userid, parent, error)) {
+	gpgme_release(ctx);
+	return GPGME_MD_NONE;
+    }
+
+    /* OpenPGP signatures are ASCII armored */
+    gpgme_set_armor(ctx, protocol == GPGME_PROTOCOL_OpenPGP);
+
+    /* create gpgme data objects */
+    if ((err =
+	 gpgme_data_new_from_cbs(&in, &cbs,
+				 istream)) != GPG_ERR_NO_ERROR) {
+	g_set_error_from_gpgme(error, err,
+			       _("could not get data from stream"));
+	gpgme_release(ctx);
+	return GPGME_MD_NONE;
+    }
+    if ((err =
+	 gpgme_data_new_from_cbs(&out, &cbs,
+				 ostream)) != GPG_ERR_NO_ERROR) {
+	g_set_error_from_gpgme(error, err,
+			       _("could not create new data object"));
+	gpgme_data_release(in);
+	gpgme_release(ctx);
+	return GPGME_MD_NONE;
+    }
+
+    /* 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"));
+	hash_algo = GPGME_MD_NONE;
+    } else
+	hash_algo = gpgme_op_sign_result(ctx)->signatures->hash_algo;
+
+    /* clean up */
+    gpgme_data_release(in);
+    gpgme_data_release(out);
+    gpgme_release(ctx);
+    return hash_algo;
+}
+
+
+/** \brief Encrypt data
+ *
+ * \param recipients Array of User ID for which the matter shall be
+ *        encrypted using their public keys.
+ * \param sign_for User ID of the signer or NULL if the matter shall not be
+ *        signed.  Note that combined signing and encryption is allowed \em
+ *        only in OpenPGP single-part (i.e. RFC 2440) mode.
+ * \param istream GMime input stream.
+ * \param ostream GMime output stream.
+ * \param protocol GpgME crypto protocol to use for encryption.
+ * \param singlepart_mode TRUE indicates single-part mode (integrated
+ *        signature), FALSE a detached signature.
+ * \param trust_all_keys TRUE if all low-truct keys shall be accepted for
+ *        encryption.  Otherwise, the function will use the global callback
+ *        to ask the user whether a low-trust key shall be accepted.
+ * \param parent Parent window to be passed to the callback functions.
+ * \param error Filled with error information on error.
+ * \return 0 on success, or -1 on error.
+ *
+ * Encrypt the passed matter and write the result to the output stream.
+ * Combined signing and encryption is allowed for single-part OpenPGP mode
+ * only.
+ */
+int
+libbalsa_gpgme_encrypt(GPtrArray * recipients, const char *sign_for,
+		       GMimeStream * istream, GMimeStream * ostream,
+		       gpgme_protocol_t protocol, gboolean singlepart_mode,
+		       gboolean trust_all_keys, GtkWindow * parent,
+		       GError ** error)
+{
+    gpgme_ctx_t ctx;
+    gpgme_error_t err;
+    gpgme_key_t *rcpt_keys;
+    gpgme_data_t plain;
+    gpgme_data_t crypt;
+    struct gpgme_data_cbs cbs = {
+	(gpgme_data_read_cb_t) g_mime_gpgme_stream_rd,	/* read method */
+	(gpgme_data_write_cb_t) g_mime_gpgme_stream_wr,	/* write method */
+	NULL,			/* seek method */
+	cb_data_release		/* release method */
+    };
+
+    /* paranoia checks */
+    g_return_val_if_fail(recipients != NULL, -1);
+    g_return_val_if_fail(GMIME_IS_STREAM(istream), -1);
+    g_return_val_if_fail(GMIME_IS_STREAM(ostream), GPGME_MD_NONE);
+    g_return_val_if_fail(protocol == GPGME_PROTOCOL_OpenPGP ||
+			 protocol == GPGME_PROTOCOL_CMS, -1);
+
+    /* create the GpgME context */
+    if ((err =
+	 gpgme_new_with_protocol(&ctx, protocol, parent,
+				 error)) != GPG_ERR_NO_ERROR)
+	return -1;
+
+    /* sign & encrypt is valid only for single-part OpenPGP */
+    if (sign_for != NULL
+	&& (!singlepart_mode || protocol != GPGME_PROTOCOL_OpenPGP)) {
+	if (error)
+	    g_set_error(error, GPGME_ERROR_QUARK, GPG_ERR_INV_ENGINE,
+			_
+			("combined signing and encryption is defined only for RFC 2440"));
+	gpgme_release(ctx);
+	return -1;
+    }
+
+    /* if requested, find the secret key for "userid" */
+    if (sign_for && !gpgme_add_signer(ctx, sign_for, parent, error)) {
+	gpgme_release(ctx);
+	return -1;
+    }
+
+    /* build the list of recipients */
+    if (!
+	(rcpt_keys =
+	 gpgme_build_recipients(ctx, recipients, trust_all_keys, parent,
+				error))) {
+	gpgme_release(ctx);
+	return -1;
+    }
+
+    /* create the data objects */
+    if (protocol == GPGME_PROTOCOL_OpenPGP) {
+	gpgme_set_armor(ctx, 1);
+	gpgme_set_textmode(ctx, singlepart_mode);
+    } else {
+	gpgme_set_armor(ctx, 0);
+	gpgme_set_textmode(ctx, 0);
+    }
+    if ((err =
+	 gpgme_data_new_from_cbs(&plain, &cbs,
+				 istream)) != GPG_ERR_NO_ERROR) {
+	g_set_error_from_gpgme(error, err,
+			       _("could not get data from stream"));
+	release_keylist(rcpt_keys);
+	gpgme_release(ctx);
+	return -1;
+    }
+    if ((err =
+	 gpgme_data_new_from_cbs(&crypt, &cbs,
+				 ostream)) != GPG_ERR_NO_ERROR) {
+	g_set_error_from_gpgme(error, err,
+			       _("could not create new data object"));
+	release_keylist(rcpt_keys);
+	gpgme_data_release(plain);
+	gpgme_release(ctx);
+	return -1;
+    }
+
+    /* do the encrypt or sign and encrypt operation
+     * Note: we set "always trust" here, as if we detected an untrusted key
+     * earlier, the user already accepted it */
+    if (sign_for)
+	err =
+	    gpgme_op_encrypt_sign(ctx, rcpt_keys,
+				  GPGME_ENCRYPT_ALWAYS_TRUST, plain,
+				  crypt);
+    else
+	err =
+	    gpgme_op_encrypt(ctx, rcpt_keys, GPGME_ENCRYPT_ALWAYS_TRUST,
+			     plain, crypt);
+
+    release_keylist(rcpt_keys);
+    gpgme_data_release(plain);
+    gpgme_data_release(crypt);
+    gpgme_release(ctx);
+    if (err != GPG_ERR_NO_ERROR) {
+	if (sign_for)
+	    g_set_error_from_gpgme(error, err,
+				   _("signing and encryption failed"));
+	else
+	    g_set_error_from_gpgme(error, err, _("encryption failed"));
+	return -1;
+    } else
+	return 0;
+}
+
+
+/** \brief Decrypt data
+ *
+ * \param istream GMime input (encrypted) stream.
+ * \param ostream GMime output (decrypted) stream.
+ * \param protocol GpgME crypto protocol to use.
+ * \param parent Parent window to be passed to the passphrase callback
+ *        function.
+ * \param error Filled with error information on error.
+ * \return A new signature status object on success, or NULL on error.
+ *
+ * Decrypt and -if applicable- verify the signature of the passed data
+ * stream.  If the input is not signed the returned signature status will
+ * be GPG_ERR_NOT_SIGNED.
+ */
+GMimeGpgmeSigstat *
+libbalsa_gpgme_decrypt(GMimeStream * crypted, GMimeStream * plain,
+		       gpgme_protocol_t protocol, GtkWindow * parent,
+		       GError ** error)
+{
+    gpgme_ctx_t ctx;
+    gpgme_error_t err;
+    gpgme_data_t plain_data;
+    gpgme_data_t crypt_data;
+    GMimeGpgmeSigstat *result;
+    struct gpgme_data_cbs cbs = {
+	(gpgme_data_read_cb_t) g_mime_gpgme_stream_rd,	/* read method */
+	(gpgme_data_write_cb_t) g_mime_gpgme_stream_wr,	/* write method */
+	NULL,			/* seek method */
+	cb_data_release		/* release method */
+    };
+
+    /* paranoia checks */
+    g_return_val_if_fail(GMIME_IS_STREAM(crypted), NULL);
+    g_return_val_if_fail(GMIME_IS_STREAM(plain), NULL);
+    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, parent,
+				 error)) != GPG_ERR_NO_ERROR)
+	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,
+			       _("could not get data from stream"));
+	gpgme_release(ctx);
+	return NULL;
+    }
+    if ((err =
+	 gpgme_data_new_from_cbs(&plain_data, &cbs,
+				 plain)) != GPG_ERR_NO_ERROR) {
+	g_set_error_from_gpgme(error, err,
+			       _("could not create new data object"));
+	gpgme_data_release(crypt_data);
+	gpgme_release(ctx);
+	return NULL;
+    }
+
+    /* try to decrypt */
+    if ((err =
+	 gpgme_op_decrypt_verify(ctx, crypt_data,
+				 plain_data)) != GPG_ERR_NO_ERROR) {
+	g_set_error_from_gpgme(error, err, _("decryption failed"));
+	result = NULL;
+    } else {
+	/* decryption successful, check for signature */
+	result = g_mime_gpgme_sigstat_new_from_gpgme_ctx(ctx);
+    }
+
+    /* clean up */
+    gpgme_data_release(plain_data);
+    gpgme_data_release(crypt_data);
+    gpgme_release(ctx);
+
+    return result;
+}
+
+/* ---- local stuff ---------------------------------------------------- */
+
+#define UTF8_VALID_STR(s)						\
+    do {								\
+	if ((s) && !g_utf8_validate(s, -1, NULL)) {			\
+	    gsize bwr;							\
+	    gchar * newstr = g_locale_to_utf8(s, -1, NULL, &bwr, NULL);	\
+									\
+	    g_free(s);							\
+	    s = newstr;							\
+	}								\
+    } while (0)
+
+
+/*
+ * set a GError form GpgME information
+ */
+static void
+g_set_error_from_gpgme(GError ** error, gpgme_error_t gpgme_err,
+		       const gchar * message)
+{
+    gchar *errstr;
+    gchar *srcstr;
+
+    if (!error)
+	return;
+
+    srcstr = g_strdup(gpgme_strsource(gpgme_err));
+    UTF8_VALID_STR(srcstr);
+    errstr = g_strdup(gpgme_strerror(gpgme_err));
+    UTF8_VALID_STR(errstr);
+    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
+g_mime_gpgme_stream_rd(GMimeStream * stream, void *buffer, size_t size)
+{
+    ssize_t result;
+
+    result = g_mime_stream_read(stream, buffer, size);
+    if (result == -1 && g_mime_stream_eos(stream))
+	result = 0;
+
+    return result;
+}
+
+
+/*
+ * callback to write data to a stream
+ */
+static ssize_t
+g_mime_gpgme_stream_wr(GMimeStream * stream, void *buffer, size_t size)
+{
+    return g_mime_stream_write(stream, buffer, size);
+}
+
+
+/*
+ * dummy function for callback based gpgme data objects
+ */
+static void
+cb_data_release(void *handle)
+{
+    /* must just be present... bug or feature?!? */
+}
+
+
+/*
+ * create a GpgME context for the passed protocol
+ */
+static gpgme_error_t
+gpgme_new_with_protocol(gpgme_ctx_t * ctx, gpgme_protocol_t protocol,
+			GtkWindow * parent, GError ** error)
+{
+    gpgme_error_t err;
+
+
+    /* 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);
+	} else {
+	    if (protocol == GPGME_PROTOCOL_OpenPGP)
+		gpgme_set_passphrase_cb(*ctx, gpgme_passphrase_cb, parent);
+	}
+    }
+
+    return err;
+}
+
+
+/*
+ * 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.
+ */
+#define KEY_IS_OK(k)   (!((k)->expired || (k)->revoked || \
+                          (k)->disabled || (k)->invalid))
+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 (KEY_IS_OK(key)) {
+	    gpgme_subkey_t subkey = key->subkeys;
+
+	    while (subkey && ((secret && !subkey->can_sign) ||
+			      (!secret && !subkey->can_encrypt)))
+		subkey = subkey->next;
+
+	    if (subkey && KEY_IS_OK(subkey) &&
+		(subkey->expires == 0 || subkey->expires > now))
+		keys = g_list_append(keys, key);
+	    else
+		found_bad = TRUE;
+	} 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);
+	    }
+	}
+	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, 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);
+	    }
+	}
+	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);
+	    }
+	}
+    }
+
+    return key;
+}
+
+
+/*
+ * Add signer to ctx's list of signers and return TRUE on success or FALSE
+ * on error.
+ */
+static gboolean
+gpgme_add_signer(gpgme_ctx_t ctx, const gchar * signer, GtkWindow * parent,
+		 GError ** error)
+{
+    gpgme_key_t key = NULL;
+
+    /* note: private (secret) key has never low trust... */
+    if (!
+	(key = get_key_from_name(ctx, signer, TRUE, FALSE, parent, error)))
+	return FALSE;
+
+    /* set the key (the previous operation guaranteed that it exists, no
+     * need 2 check return values...) */
+    gpgme_signers_add(ctx, key);
+    gpgme_key_unref(key);
+
+    return TRUE;
+}
+
+
+/*
+ * 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.
+ */
+static gpgme_key_t *
+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;
+
+	if (!
+	    (key =
+	     get_key_from_name(ctx, name, FALSE, accept_low_trust, parent,
+			       error))) {
+	    release_keylist(rcpt);
+	    return NULL;
+	}
+
+	/* set the recipient */
+	rcpt[num_rcpts] = key;
+    }
+
+    return rcpt;
+}
+
+
+/*
+ * helper function: unref all keys in the NULL-terminated array keylist and
+ * finally release the array itself
+ */
+static void
+release_keylist(gpgme_key_t * keylist)
+{
+    gpgme_key_t *key = keylist;
+
+    while (*key) {
+	gpgme_key_unref(*key);
+	key++;
+    }
+    g_free(keylist);
+}
+
+
+#if defined(ENABLE_NLS)
+/*
+ * convert a locale name to utf-8
+ */
+static const gchar *
+get_utf8_locale(int category)
+{
+    gchar *locale;
+    static gchar localebuf[64];	/* should be large enough */
+    gchar *dot;
+
+    if (!(locale = setlocale(category, NULL)))
+	return NULL;
+    strncpy(localebuf, locale, 57);
+    localebuf[57] = '\0';
+    dot = strchr(localebuf, '.');
+    if (!dot)
+	dot = localebuf + strlen(localebuf);
+    strcpy(dot, ".UTF-8");
+    return localebuf;
+}
+#endif
diff --git a/libbalsa/libbalsa-gpgme.h b/libbalsa/libbalsa-gpgme.h
new file mode 100644
index 0000000..3a865f5
--- /dev/null
+++ b/libbalsa/libbalsa-gpgme.h
@@ -0,0 +1,115 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/*
+ * gpgme low-level stuff for gmime/balsa
+ * Copyright (C) 2011 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef LIBBALSA_GPGME_H_
+#define LIBBALSA_GPGME_H_
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gpgme.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gmime/gmime.h>
+#include "gmime-gpgme-signature.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#ifdef MAKE_EMACS_HAPPY
+}
+#endif
+#endif				/* __cplusplus */
+
+
+#define GPG_ERR_KEY_SELECTION          GPG_ERR_USER_14
+#define GPG_ERR_TRY_AGAIN              GPG_ERR_USER_15
+#define GPG_ERR_NOT_SIGNED             GPG_ERR_USER_16
+
+#define GPGME_ERROR_QUARK (g_quark_from_static_string("gmime-gpgme"))
+
+
+/** Callback to select a key from a list
+ * Parameters:
+ * - user name
+ * - TRUE is a secret key shall be selected
+ * - list of available keys (gpgme_key_t data elements)
+ * - protocol
+ * - parent window
+ */ typedef gpgme_key_t(*lbgpgme_select_key_cb) (const gchar *,
+						 gboolean,
+						 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
+ * - parent window
+ */
+typedef gboolean(*lbgpgme_accept_low_trust_cb) (const gchar *,
+						const gpgme_user_id_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);
+gboolean libbalsa_gpgme_check_crypto_engine(gpgme_protocol_t protocol);
+
+GMimeGpgmeSigstat *libbalsa_gpgme_verify(GMimeStream * content,
+					 GMimeStream * sig_plain,
+					 gpgme_protocol_t protocol,
+					 gboolean singlepart_mode,
+					 GError ** error);
+
+gpgme_hash_algo_t libbalsa_gpgme_sign(const gchar * userid,
+				      GMimeStream * istream,
+				      GMimeStream * ostream,
+				      gpgme_protocol_t protocol,
+				      gboolean singlepart_mode,
+				      GtkWindow * parent, GError ** error);
+
+int libbalsa_gpgme_encrypt(GPtrArray * recipients,
+			   const char *sign_for,
+			   GMimeStream * istream,
+			   GMimeStream * ostream,
+			   gpgme_protocol_t protocol,
+			   gboolean singlepart_mode,
+			   gboolean trust_all_keys,
+			   GtkWindow * parent, GError ** error);
+
+GMimeGpgmeSigstat *libbalsa_gpgme_decrypt(GMimeStream * crypted,
+					  GMimeStream * plain,
+					  gpgme_protocol_t protocol,
+					  GtkWindow * parent,
+					  GError ** error);
+
+
+#ifdef __cplusplus
+}
+#endif				/* __cplusplus */
+
+#endif				/* LIBBALSA_GPGME_H_ */



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