[balsa] New files needed for previous commit
- From: Peter Bloomfield <PeterB src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [balsa] New files needed for previous commit
- Date: Sun, 11 Dec 2011 19:14:28 +0000 (UTC)
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]