summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMilan Crha <mcrha@redhat.com>2018-07-04 11:46:52 +0200
committerMilan Crha <mcrha@redhat.com>2018-07-04 11:46:52 +0200
commitea9ed9224edab871cb539e0b7cc0821480012836 (patch)
tree5d921d01f3a5dc160af0988565e39eca81ea2d27
parent20a03b5d1695a05915c5ae1c7112707fac8ad464 (diff)
downloadevolution-data-server-ea9ed9224edab871cb539e0b7cc0821480012836.tar.gz
Bug 704246 - Cannot send encrypted mail to contact with certificate
-rw-r--r--docs/reference/camel/camel-docs.sgml.in4
-rw-r--r--docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in1
-rw-r--r--src/addressbook/libebook/CMakeLists.txt4
-rw-r--r--src/addressbook/libebook/e-book-utils.c303
-rw-r--r--src/addressbook/libebook/e-book-utils.h41
-rw-r--r--src/addressbook/libebook/libebook.h1
-rw-r--r--src/addressbook/libedata-book/e-book-cache.c48
-rw-r--r--src/addressbook/libedata-book/e-book-sqlite.c17
-rw-r--r--src/camel/camel-enums.h16
-rw-r--r--src/camel/camel-gpg-context.c112
-rw-r--r--src/camel/camel-session.c57
-rw-r--r--src/camel/camel-session.h16
-rw-r--r--src/camel/camel-smime-context.c31
13 files changed, 631 insertions, 20 deletions
diff --git a/docs/reference/camel/camel-docs.sgml.in b/docs/reference/camel/camel-docs.sgml.in
index 431553d51..2e66b6d4a 100644
--- a/docs/reference/camel/camel-docs.sgml.in
+++ b/docs/reference/camel/camel-docs.sgml.in
@@ -297,6 +297,10 @@
<title>Index of deprecated symbols</title>
<xi:include href="xml/api-index-deprecated.xml"><xi:fallback /></xi:include>
</index>
+ <index id="api-index-3-30" role="3.30">
+ <title>Index of new symbols in 3.30</title>
+ <xi:include href="xml/api-index-3.30.xml"><xi:fallback /></xi:include>
+ </index>
<index id="api-index-3-28" role="3.28">
<title>Index of new symbols in 3.28</title>
<xi:include href="xml/api-index-3.28.xml"><xi:fallback /></xi:include>
diff --git a/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in b/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in
index d3cb9453a..d9673b592 100644
--- a/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in
+++ b/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in
@@ -138,6 +138,7 @@
<xi:include href="xml/e-book-client-view.xml"/>
<xi:include href="xml/e-book-client-cursor.xml"/>
<xi:include href="xml/e-book-query.xml"/>
+ <xi:include href="xml/e-book-utils.xml"/>
<xi:include href="xml/e-vcard.xml"/>
<xi:include href="xml/e-contact.xml"/>
</chapter>
diff --git a/src/addressbook/libebook/CMakeLists.txt b/src/addressbook/libebook/CMakeLists.txt
index ffb725a4a..a004e99d1 100644
--- a/src/addressbook/libebook/CMakeLists.txt
+++ b/src/addressbook/libebook/CMakeLists.txt
@@ -17,6 +17,7 @@ set(SOURCES
e-book-client.c
e-book-client-cursor.c
e-book-client-view.c
+ e-book-utils.c
e-book-view-private.h
e-book-view.c
e-destination.c
@@ -30,8 +31,9 @@ set(HEADERS
e-book-client.h
e-book-client-cursor.h
e-book-client-view.h
- e-book-view.h
e-book-types.h
+ e-book-utils.h
+ e-book-view.h
e-destination.h
${CMAKE_CURRENT_BINARY_DIR}/e-book-enumtypes.h
)
diff --git a/src/addressbook/libebook/e-book-utils.c b/src/addressbook/libebook/e-book-utils.c
new file mode 100644
index 000000000..ac82617ff
--- /dev/null
+++ b/src/addressbook/libebook/e-book-utils.c
@@ -0,0 +1,303 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2018 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-data-server-config.h"
+
+#include "camel/camel.h"
+#include "libebook-contacts/libebook-contacts.h"
+
+#include "e-book-client.h"
+
+#include "e-book-utils.h"
+
+typedef struct _RecipientCertificatesData {
+ GMutex lock;
+ GCond cond;
+ guint32 flags;
+ gboolean is_source; /* if FALSE, then it's EBookClient */
+ GHashTable *recipients; /* gchar *email ~> gchar *base64_cert */
+ guint has_pending;
+ GCancellable *cancellable;
+} RecipientCertificatesData;
+
+static void
+book_utils_get_recipient_certificates_thread (gpointer data,
+ gpointer user_data)
+{
+ RecipientCertificatesData *rcd = user_data;
+ EContactField field_id;
+ GHashTableIter iter;
+ gpointer key, value;
+ GString *sexp;
+ const gchar *fieldname;
+
+ g_return_if_fail (rcd != NULL);
+
+ g_mutex_lock (&rcd->lock);
+
+ if (g_cancellable_is_cancelled (rcd->cancellable)) {
+ rcd->has_pending--;
+ if (!rcd->has_pending)
+ g_cond_signal (&rcd->cond);
+
+ g_mutex_unlock (&rcd->lock);
+
+ return;
+ }
+
+ fieldname = e_contact_field_name (E_CONTACT_EMAIL);
+ sexp = g_string_new ("");
+
+ g_hash_table_iter_init (&iter, rcd->recipients);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ if (key && !value) {
+ if (sexp->len)
+ g_string_append_c (sexp, ' ');
+ g_string_append_printf (sexp, "(is \"%s\"", fieldname);
+ e_sexp_encode_string (sexp, key);
+ g_string_append_c (sexp, ')');
+ }
+ }
+
+ g_mutex_unlock (&rcd->lock);
+
+ field_id = (rcd->flags & CAMEL_RECIPIENT_CERTIFICATE_SMIME) ? E_CONTACT_X509_CERT : E_CONTACT_PGP_CERT;
+
+ if (sexp->len) {
+ gchar *prefix;
+
+ prefix = g_strdup_printf ("(and (exists \"%s\") (or ", e_contact_field_name (field_id));
+ g_string_prepend (sexp, prefix);
+ g_free (prefix);
+ g_string_append (sexp, "))");
+ }
+
+ if (sexp->len) {
+ EBookClient *client;
+ GSList *contacts = NULL;
+
+ if (rcd->is_source) {
+ client = (EBookClient *) e_book_client_connect_sync (data, 30, rcd->cancellable, NULL);
+ } else {
+ client = g_object_ref (data);
+ }
+
+ if (client && e_book_client_get_contacts_sync (client, sexp->str, &contacts, rcd->cancellable, NULL) && contacts) {
+ GSList *link;
+ GHashTableIter iter;
+ gpointer value;
+ gboolean all_done;
+
+ g_mutex_lock (&rcd->lock);
+
+ for (link = contacts; link; link = g_slist_next (link)) {
+ EContact *contact = link->data;
+ GList *emails, *elink;
+ gchar *base64_data = NULL;
+
+ /* Update only those which were not found yet. One could choose the best
+ certificate for S/MIME, but not for PGP easily, thus which is returned
+ depends on the order they had been received (the first recognized
+ is used). */
+
+ emails = e_contact_get (contact, E_CONTACT_EMAIL);
+
+ for (elink = emails; elink; elink = g_list_next (elink)) {
+ const gchar *email_address = elink->data;
+ gpointer orig_key = NULL, stored_value = NULL;
+
+ if (email_address && g_hash_table_lookup_extended (rcd->recipients, email_address, &orig_key, &stored_value) && !stored_value) {
+ if (!base64_data) {
+ GList *cert_attrs, *clink;
+
+ cert_attrs = e_contact_get_attributes (contact, field_id);
+ for (clink = cert_attrs; clink; clink = g_list_next (clink)) {
+ EVCardAttribute *cattr = clink->data;
+
+ if ((field_id == E_CONTACT_X509_CERT && e_vcard_attribute_has_type (cattr, "X509")) ||
+ (field_id == E_CONTACT_PGP_CERT && e_vcard_attribute_has_type (cattr, "PGP"))) {
+ GString *decoded;
+
+ decoded = e_vcard_attribute_get_value_decoded (cattr);
+ if (decoded && decoded->len) {
+ base64_data = g_base64_encode ((const guchar *) decoded->str, decoded->len);
+ g_string_free (decoded, TRUE);
+ break;
+ }
+
+ if (decoded)
+ g_string_free (decoded, TRUE);
+ }
+ }
+
+ g_list_free_full (cert_attrs, (GDestroyNotify) e_vcard_attribute_free);
+
+ /* First insert takes ownership of the base64_data */
+ if (base64_data)
+ g_hash_table_insert (rcd->recipients, orig_key, base64_data);
+ } else {
+ g_hash_table_insert (rcd->recipients, orig_key, g_strdup (base64_data));
+ }
+ }
+ }
+
+ g_list_free_full (emails, g_free);
+ }
+
+ all_done = TRUE;
+ g_hash_table_iter_init (&iter, rcd->recipients);
+ while (all_done && g_hash_table_iter_next (&iter, NULL, &value)) {
+ all_done = value != NULL;
+ }
+
+ g_mutex_unlock (&rcd->lock);
+
+ g_slist_free_full (contacts, g_object_unref);
+
+ /* Do not wait for all books to finish when all recipients have their certificate */
+ if (all_done)
+ g_cancellable_cancel (rcd->cancellable);
+ }
+
+ g_clear_object (&client);
+ }
+
+ g_mutex_lock (&rcd->lock);
+ rcd->has_pending--;
+ if (!rcd->has_pending)
+ g_cond_signal (&rcd->cond);
+ g_mutex_unlock (&rcd->lock);
+}
+
+/**
+ * e_book_utils_get_recipient_certificates_sync:
+ * @registry: an #ESourceRegistry
+ * @only_clients: (element-type EBookClient) (nullable): optional #GSList of
+ * the #EBookClient objects to search for the certificates in, or %NULL
+ * @flags: bit-or of #CamelRecipientCertificateFlags
+ * @recipients: (element-type utf8): a #GPtrArray of recipients' email addresses
+ * @out_certificates: (element-type utf8) (out): a #GSList of gathered certificates
+ * encoded in base64
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Synchronously searches for @recipients S/MIME or PGP certificates either
+ * in provided @only_clients #EBookClient-s, or, when %NULL, in each found
+ * address book configured for auto-completion.
+ *
+ * This function can be used within camel_session_get_recipient_certificates_sync()
+ * implementation.
+ *
+ * Returns: %TRUE when no fatal error occurred, %FALSE otherwise.
+ *
+ * Since: 3.30
+ **/
+gboolean
+e_book_utils_get_recipient_certificates_sync (ESourceRegistry *registry,
+ const GSList *only_clients,
+ guint32 flags,
+ const GPtrArray *recipients,
+ GSList **out_certificates,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GSList *clients, *link; /* contains either EBookClient or ESource objects */
+ RecipientCertificatesData rcd;
+ GThreadPool *thread_pool;
+ guint ii;
+
+ g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
+ g_return_val_if_fail (recipients != NULL, FALSE);
+ g_return_val_if_fail (out_certificates != NULL, FALSE);
+
+ *out_certificates = NULL;
+
+ clients = g_slist_copy_deep ((GSList *) only_clients, (GCopyFunc) g_object_ref, NULL);
+
+ if (!clients) {
+ GList *sources, *llink;
+
+ sources = e_source_registry_list_enabled (registry, E_SOURCE_EXTENSION_ADDRESS_BOOK);
+
+ for (llink = sources; llink; llink = g_list_next (llink)) {
+ ESource *source = llink->data;
+
+ /* Default is TRUE, thus when not there, then include it */
+ if (!e_source_has_extension (source, E_SOURCE_EXTENSION_AUTOCOMPLETE) ||
+ e_source_autocomplete_get_include_me (e_source_get_extension (source, E_SOURCE_EXTENSION_AUTOCOMPLETE)))
+ clients = g_slist_prepend (clients, g_object_ref (source));
+ }
+
+ g_list_free_full (sources, g_object_unref);
+ }
+
+ /* Not a fatal error, there's just no address book to search in */
+ if (!clients)
+ return TRUE;
+
+ g_mutex_init (&rcd.lock);
+ g_cond_init (&rcd.cond);
+ rcd.flags = flags;
+ rcd.is_source = !only_clients;
+ rcd.has_pending = 0;
+ rcd.recipients = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, NULL, g_free);
+ rcd.cancellable = camel_operation_new_proxy (cancellable);
+
+ for (ii = 0; ii < recipients->len; ii++) {
+ g_hash_table_insert (rcd.recipients, recipients->pdata[ii], NULL);
+ }
+
+ thread_pool = g_thread_pool_new (book_utils_get_recipient_certificates_thread, &rcd, 10, FALSE, NULL);
+
+ g_mutex_lock (&rcd.lock);
+
+ for (link = clients; link && !g_cancellable_is_cancelled (cancellable); link = g_slist_next (link)) {
+ g_thread_pool_push (thread_pool, link->data, NULL);
+ rcd.has_pending++;
+ }
+
+ while (rcd.has_pending) {
+ g_cond_wait (&rcd.cond, &rcd.lock);
+ }
+ g_mutex_unlock (&rcd.lock);
+
+ g_thread_pool_free (thread_pool, TRUE, TRUE);
+
+ for (ii = 0; ii < recipients->len; ii++) {
+ gchar *base64_data;
+
+ base64_data = g_hash_table_lookup (rcd.recipients, recipients->pdata[ii]);
+ if (base64_data && *base64_data) {
+ *out_certificates = g_slist_prepend (*out_certificates, base64_data);
+ /* Move ownership of the base64_data to out_certificates */
+ g_warn_if_fail (g_hash_table_steal (rcd.recipients, recipients->pdata[ii]));
+ } else {
+ *out_certificates = g_slist_prepend (*out_certificates, NULL);
+ }
+ }
+
+ *out_certificates = g_slist_reverse (*out_certificates);
+
+ g_hash_table_destroy (rcd.recipients);
+ g_clear_object (&rcd.cancellable);
+ g_mutex_clear (&rcd.lock);
+ g_cond_clear (&rcd.cond);
+ g_slist_free_full (clients, g_object_unref);
+
+ return TRUE;
+}
diff --git a/src/addressbook/libebook/e-book-utils.h b/src/addressbook/libebook/e-book-utils.h
new file mode 100644
index 000000000..c4a5e75c1
--- /dev/null
+++ b/src/addressbook/libebook/e-book-utils.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2018 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if !defined (__LIBEBOOK_H_INSIDE__) && !defined (LIBEBOOK_COMPILATION)
+#error "Only <libebook/libebook.h> should be included directly."
+#endif
+
+#ifndef E_BOOK_UTILS_H
+#define E_BOOK_UTILS_H
+
+#include <libedataserver/libedataserver.h>
+
+G_BEGIN_DECLS
+
+gboolean e_book_utils_get_recipient_certificates_sync
+ (ESourceRegistry *registry,
+ const GSList *only_clients, /* EBookClient * */
+ guint32 flags, /* bit-or of CamelRecipientCertificateFlags */
+ const GPtrArray *recipients, /* gchar * */
+ GSList **out_certificates, /* gchar * */
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* E_BOOK_UTILS_H */
diff --git a/src/addressbook/libebook/libebook.h b/src/addressbook/libebook/libebook.h
index 92240581f..ab142fd70 100644
--- a/src/addressbook/libebook/libebook.h
+++ b/src/addressbook/libebook/libebook.h
@@ -27,6 +27,7 @@
#include <libebook/e-book-client.h>
#include <libebook/e-book-enumtypes.h>
#include <libebook/e-book-types.h>
+#include <libebook/e-book-utils.h>
#include <libebook/e-book-view.h>
#include <libebook/e-book.h>
#include <libebook/e-destination.h>
diff --git a/src/addressbook/libedata-book/e-book-cache.c b/src/addressbook/libedata-book/e-book-cache.c
index fbf931a7c..1401d8f40 100644
--- a/src/addressbook/libedata-book/e-book-cache.c
+++ b/src/addressbook/libedata-book/e-book-cache.c
@@ -49,7 +49,7 @@
#include "e-book-cache.h"
-#define E_BOOK_CACHE_VERSION 1
+#define E_BOOK_CACHE_VERSION 2
#define INSERT_MULTI_STMT_BYTES 128
#define COLUMN_DEFINITION_BYTES 32
#define GENERATED_QUERY_BYTES 1024
@@ -228,7 +228,8 @@ static EContactField default_summary_fields[] = {
E_CONTACT_IS_LIST,
E_CONTACT_LIST_SHOW_ADDRESSES,
E_CONTACT_WANTS_HTML,
- E_CONTACT_X509_CERT
+ E_CONTACT_X509_CERT,
+ E_CONTACT_PGP_CERT
};
/* Create indexes on full_name and email fields as autocompletion
@@ -4229,6 +4230,41 @@ e_book_cache_gather_table_names_cb (ECache *cache,
}
static gboolean
+e_book_cache_fill_pgp_cert_column (ECache *cache,
+ const gchar *uid,
+ const gchar *revision,
+ const gchar *object,
+ EOfflineState offline_state,
+ gint ncols,
+ const gchar *column_names[],
+ const gchar *column_values[],
+ gchar **out_revision,
+ gchar **out_object,
+ EOfflineState *out_offline_state,
+ ECacheColumnValues **out_other_columns,
+ gpointer user_data)
+{
+ EContact *contact;
+ EContactCert *cert;
+
+ g_return_val_if_fail (object != NULL, FALSE);
+ g_return_val_if_fail (out_other_columns != NULL, FALSE);
+
+ contact = e_contact_new_from_vcard (object);
+ if (!contact)
+ return TRUE;
+
+ *out_other_columns = e_cache_column_values_new ();
+ cert = e_contact_get (contact, E_CONTACT_PGP_CERT);
+
+ e_cache_column_values_take_value (*out_other_columns, e_contact_field_name (E_CONTACT_PGP_CERT), g_strdup_printf ("%d", cert ? 1 : 0));
+
+ e_contact_cert_free (cert);
+
+ return TRUE;
+}
+
+static gboolean
e_book_cache_migrate (ECache *cache,
gint from_version,
GCancellable *cancellable,
@@ -4297,8 +4333,12 @@ e_book_cache_migrate (ECache *cache,
}
/* Add any version-related changes here */
- /*if (from_version < E_BOOK_CACHE_VERSION) {
- }*/
+ if (success && from_version > 0 && from_version < E_BOOK_CACHE_VERSION) {
+ if (from_version == 1) {
+ /* Version 2 added E_CONTACT_PGP_CERT existence into the summary */
+ success = e_cache_foreach_update (cache, E_CACHE_INCLUDE_DELETED, NULL, e_book_cache_fill_pgp_cert_column, NULL, cancellable, error);
+ }
+ }
return success;
}
diff --git a/src/addressbook/libedata-book/e-book-sqlite.c b/src/addressbook/libedata-book/e-book-sqlite.c
index 169b00214..e43da968e 100644
--- a/src/addressbook/libedata-book/e-book-sqlite.c
+++ b/src/addressbook/libedata-book/e-book-sqlite.c
@@ -270,7 +270,7 @@ ebsql_origin_str (EbSqlCursorOrigin origin)
} \
} G_STMT_END
-#define FOLDER_VERSION 11
+#define FOLDER_VERSION 12
#define INSERT_MULTI_STMT_BYTES 128
#define COLUMN_DEFINITION_BYTES 32
#define GENERATED_QUERY_BYTES 1024
@@ -443,6 +443,7 @@ static EContactField default_summary_fields[] = {
E_CONTACT_LIST_SHOW_ADDRESSES,
E_CONTACT_WANTS_HTML,
E_CONTACT_X509_CERT,
+ E_CONTACT_PGP_CERT
};
/* Create indexes on full_name and email fields as autocompletion
@@ -2423,6 +2424,13 @@ ebsql_introspect_summary (EBookSqlite *ebsql,
}
}
+
+ if (previous_schema < 12) {
+ if (summary_field_array_index (summary_fields, E_CONTACT_PGP_CERT) < 0) {
+ summary_field_append (summary_fields, ebsql->priv->folderid,
+ E_CONTACT_PGP_CERT, NULL);
+ }
+ }
}
introspect_summary_finish:
@@ -3085,6 +3093,13 @@ ebsql_new_internal (const gchar *path,
ebsql, previous_schema,
already_exists, error);
+
+ /* Schema 12 added E_CONTACT_PGP_CERT column into the summary;
+ the ebsql_init_locale() also calls ebsql_upgrade() for schema 10-,
+ thus call it here only for schema 11, to populate the PGP column */
+ if (success && previous_schema == 11)
+ success = ebsql_upgrade (ebsql, EBSQL_CHANGE_LAST, error);
+
if (success)
success = ebsql_commit_transaction (ebsql, error);
else
diff --git a/src/camel/camel-enums.h b/src/camel/camel-enums.h
index a97810098..df3c9a70f 100644
--- a/src/camel/camel-enums.h
+++ b/src/camel/camel-enums.h
@@ -419,6 +419,22 @@ typedef enum {
} CamelSaslAnonTraceType;
/**
+ * CamelRecipientCertificateFlags:
+ * @CAMEL_RECIPIENT_CERTIFICATE_SMIME: Retrieve S/MIME certificates; this cannot be used
+ * together with @CAMEL_RECIPIENT_CERTIFICATE_PGP
+ * @CAMEL_RECIPIENT_CERTIFICATE_PGP: Retrieve PGP keys; this cannot be used
+ * together with @CAMEL_RECIPIENT_CERTIFICATE_SMIME.
+ *
+ * Flags used to camel_session_get_recipient_certificates_sync() call.
+ *
+ * Since: 3.30
+ **/
+typedef enum { /*< flags >*/
+ CAMEL_RECIPIENT_CERTIFICATE_SMIME = 1 << 0,
+ CAMEL_RECIPIENT_CERTIFICATE_PGP = 1 << 1
+} CamelRecipientCertificateFlags;
+
+/**
* CamelServiceConnectionStatus:
* @CAMEL_SERVICE_DISCONNECTED:
* #CamelService is disconnected from a remote server.
diff --git a/src/camel/camel-gpg-context.c b/src/camel/camel-gpg-context.c
index 0af41e411..5b97b0ddb 100644
--- a/src/camel/camel-gpg-context.c
+++ b/src/camel/camel-gpg-context.c
@@ -48,6 +48,7 @@
#endif
#include "camel-debug.h"
+#include "camel-file-utils.h"
#include "camel-gpg-context.h"
#include "camel-iconv.h"
#include "camel-internet-address.h"
@@ -95,6 +96,23 @@ G_DEFINE_TYPE (CamelGpgContext, camel_gpg_context, CAMEL_TYPE_CIPHER_CONTEXT)
static const gchar *gpg_ctx_get_executable_name (void);
+typedef struct _GpgRecipientsData {
+ gchar *keyid;
+ gchar *known_key_data;
+} GpgRecipientsData;
+
+static void
+gpg_recipients_data_free (gpointer ptr)
+{
+ GpgRecipientsData *rd = ptr;
+
+ if (rd) {
+ g_free (rd->keyid);
+ g_free (rd->known_key_data);
+ g_free (rd);
+ }
+}
+
enum _GpgCtxMode {
GPG_CTX_MODE_SIGN,
GPG_CTX_MODE_VERIFY,
@@ -114,12 +132,14 @@ enum _GpgTrustMetric {
struct _GpgCtx {
enum _GpgCtxMode mode;
CamelSession *session;
+ GCancellable *cancellable;
GHashTable *userid_hint;
pid_t pid;
GSList *userids;
gchar *sigfile;
- GPtrArray *recipients;
+ GPtrArray *recipients; /* GpgRecipientsData * */
+ GSList *recipient_key_files; /* gchar * with filenames of keys in the tmp directory */
CamelCipherHash hash;
gint stdin_fd;
@@ -186,7 +206,8 @@ struct _GpgCtx {
};
static struct _GpgCtx *
-gpg_ctx_new (CamelCipherContext *context)
+gpg_ctx_new (CamelCipherContext *context,
+ GCancellable *cancellable)
{
struct _GpgCtx *gpg;
const gchar *charset;
@@ -198,6 +219,7 @@ gpg_ctx_new (CamelCipherContext *context)
gpg = g_new (struct _GpgCtx, 1);
gpg->mode = GPG_CTX_MODE_SIGN;
gpg->session = g_object_ref (session);
+ gpg->cancellable = cancellable;
gpg->userid_hint = g_hash_table_new (g_str_hash, g_str_equal);
gpg->complete = FALSE;
gpg->seen_eof1 = TRUE;
@@ -209,6 +231,7 @@ gpg_ctx_new (CamelCipherContext *context)
gpg->userids = NULL;
gpg->sigfile = NULL;
gpg->recipients = NULL;
+ gpg->recipient_key_files = NULL;
gpg->hash = CAMEL_CIPHER_HASH_DEFAULT;
gpg->always_trust = FALSE;
gpg->prefer_inline = FALSE;
@@ -341,8 +364,10 @@ gpg_ctx_set_userid (struct _GpgCtx *gpg,
static void
gpg_ctx_add_recipient (struct _GpgCtx *gpg,
- const gchar *keyid)
+ const gchar *keyid,
+ const gchar *known_key_data)
{
+ GpgRecipientsData *rd;
gchar *safe_keyid;
if (gpg->mode != GPG_CTX_MODE_ENCRYPT)
@@ -361,7 +386,11 @@ gpg_ctx_add_recipient (struct _GpgCtx *gpg,
safe_keyid = g_strdup (keyid);
}
- g_ptr_array_add (gpg->recipients, safe_keyid);
+ rd = g_new0 (GpgRecipientsData, 1);
+ rd->keyid = safe_keyid;
+ rd->known_key_data = g_strdup (known_key_data);
+
+ g_ptr_array_add (gpg->recipients, rd);
}
static void
@@ -466,11 +495,22 @@ gpg_ctx_free (struct _GpgCtx *gpg)
if (gpg->recipients) {
for (i = 0; i < gpg->recipients->len; i++)
- g_free (gpg->recipients->pdata[i]);
+ gpg_recipients_data_free (gpg->recipients->pdata[i]);
g_ptr_array_free (gpg->recipients, TRUE);
}
+ if (gpg->recipient_key_files) {
+ GSList *link;
+
+ for (link = gpg->recipient_key_files; link; link = g_slist_next (link)) {
+ g_unlink (link->data);
+ }
+
+ g_slist_free_full (gpg->recipient_key_files, g_free);
+ gpg->recipient_key_files = NULL;
+ }
+
if (gpg->stdin_fd != -1)
close (gpg->stdin_fd);
if (gpg->stdout_fd != -1)
@@ -705,8 +745,42 @@ gpg_ctx_get_argv (struct _GpgCtx *gpg,
}
if (gpg->recipients) {
for (i = 0; i < gpg->recipients->len; i++) {
- g_ptr_array_add (argv, (guint8 *) "-r");
- g_ptr_array_add (argv, gpg->recipients->pdata[i]);
+ const GpgRecipientsData *rd = gpg->recipients->pdata[i];
+ gboolean add_with_keyid = TRUE;
+
+ if (!rd)
+ continue;
+
+ if (rd->known_key_data && *(rd->known_key_data)) {
+ gsize len = 0;
+ guchar *data;
+
+ data = g_base64_decode (rd->known_key_data, &len);
+ if (data && len) {
+ gchar *filename = NULL;
+ gint filefd;
+
+ filefd = g_file_open_tmp ("camel-gpg-key-XXXXXX", &filename, NULL);
+ if (filefd) {
+ gpg->recipient_key_files = g_slist_prepend (gpg->recipient_key_files, filename);
+
+ if (camel_write (filefd, (const gchar *) data, len, gpg->cancellable, NULL) == len) {
+ add_with_keyid = FALSE;
+ g_ptr_array_add (argv, (guint8 *) "--recipient-file");
+ g_ptr_array_add (argv, (guint8 *) filename);
+ }
+
+ close (filefd);
+ }
+ }
+
+ g_free (data);
+ }
+
+ if (add_with_keyid) {
+ g_ptr_array_add (argv, (guint8 *) "-r");
+ g_ptr_array_add (argv, rd->keyid);
+ }
}
}
g_ptr_array_add (argv, (guint8 *) "--output");
@@ -2093,7 +2167,7 @@ gpg_sign_sync (CamelCipherContext *context,
}
#endif
- gpg = gpg_ctx_new (context);
+ gpg = gpg_ctx_new (context, cancellable);
gpg_ctx_set_mode (gpg, GPG_CTX_MODE_SIGN);
gpg_ctx_set_hash (gpg, hash);
gpg_ctx_set_armor (gpg, TRUE);
@@ -2330,7 +2404,7 @@ gpg_verify_sync (CamelCipherContext *context,
g_seekable_seek (G_SEEKABLE (canon_stream), 0, G_SEEK_SET, NULL, NULL);
- gpg = gpg_ctx_new (context);
+ gpg = gpg_ctx_new (context, cancellable);
gpg_ctx_set_mode (gpg, GPG_CTX_MODE_VERIFY);
gpg_ctx_set_load_photos (gpg, camel_cipher_can_load_photos ());
if (sigfile)
@@ -2438,10 +2512,15 @@ gpg_encrypt_sync (CamelCipherContext *context,
CamelMultipartEncrypted *mpe;
gboolean success = FALSE;
gboolean prefer_inline;
+ GSList *gathered_keys = NULL, *link;
gint i;
class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context);
+ if (!camel_session_get_recipient_certificates_sync (camel_cipher_context_get_session (context),
+ CAMEL_RECIPIENT_CERTIFICATE_PGP, recipients, &gathered_keys, cancellable, error))
+ return FALSE;
+
prefer_inline = ctx->priv->prefer_inline &&
camel_content_type_is (camel_mime_part_get_content_type (ipart), "text", "plain");
@@ -2455,7 +2534,7 @@ gpg_encrypt_sync (CamelCipherContext *context,
goto fail1;
}
- gpg = gpg_ctx_new (context);
+ gpg = gpg_ctx_new (context, cancellable);
gpg_ctx_set_mode (gpg, GPG_CTX_MODE_ENCRYPT);
gpg_ctx_set_armor (gpg, TRUE);
gpg_ctx_set_userid (gpg, userid);
@@ -2464,8 +2543,14 @@ gpg_encrypt_sync (CamelCipherContext *context,
gpg_ctx_set_always_trust (gpg, ctx->priv->always_trust);
gpg_ctx_set_prefer_inline (gpg, prefer_inline);
- for (i = 0; i < recipients->len; i++) {
- gpg_ctx_add_recipient (gpg, recipients->pdata[i]);
+ if (gathered_keys && g_slist_length (gathered_keys) != recipients->len) {
+ g_slist_free_full (gathered_keys, g_free);
+ gathered_keys = NULL;
+ }
+
+ for (link = gathered_keys, i = 0; i < recipients->len; i++) {
+ gpg_ctx_add_recipient (gpg, recipients->pdata[i], link ? link->data : NULL);
+ link = g_slist_next (link);
}
if (!gpg_ctx_op_start (gpg, error))
@@ -2560,6 +2645,7 @@ gpg_encrypt_sync (CamelCipherContext *context,
fail:
gpg_ctx_free (gpg);
fail1:
+ g_slist_free_full (gathered_keys, g_free);
g_object_unref (istream);
g_object_unref (ostream);
@@ -2633,7 +2719,7 @@ gpg_decrypt_sync (CamelCipherContext *context,
ostream = camel_stream_mem_new ();
camel_stream_mem_set_secure ((CamelStreamMem *) ostream);
- gpg = gpg_ctx_new (context);
+ gpg = gpg_ctx_new (context, cancellable);
gpg_ctx_set_mode (gpg, GPG_CTX_MODE_DECRYPT);
gpg_ctx_set_load_photos (gpg, camel_cipher_can_load_photos ());
gpg_ctx_set_istream (gpg, istream);
diff --git a/src/camel/camel-session.c b/src/camel/camel-session.c
index 4431d7ad5..8e090b195 100644
--- a/src/camel/camel-session.c
+++ b/src/camel/camel-session.c
@@ -1963,3 +1963,60 @@ camel_session_get_oauth2_access_token_sync (CamelSession *session,
return klass->get_oauth2_access_token_sync (session, service, out_access_token, out_expires_in, cancellable, error);
}
+
+/**
+ * camel_session_get_recipient_certificates_sync:
+ * @session: a #CamelSession
+ * @flags: bit-or of #CamelRecipientCertificateFlags
+ * @recipients: (element-type utf8): a #GPtrArray of recipients
+ * @out_certificates: (element-type utf8) (out): a #GSList of gathered certificates
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Searches for S/MIME certificates or PGP keys for the given @recipients,
+ * which are returned as base64 encoded strings in @out_certificates.
+ * This is used when encrypting messages. The @flags influence what
+ * the @out_certificates will contain. The order of items in @out_certificates
+ * should match the order of items in @recipients, with %NULL data for those
+ * which could not be found.
+ *
+ * The function should return failure only if some fatal error happened.
+ * It's not an error when certificates for some, or all, recipients
+ * could not be found.
+ *
+ * This method is optional and the default implementation returns %TRUE
+ * and sets the @out_certificates to %NULL. It's the only exception
+ * when the length of @recipients and @out_certificates can differ.
+ * In all other cases the length of the two should match.
+ *
+ * The @out_certificates will be freed with g_slist_free_full (certificates, g_free);
+ * when done with it.
+ *
+ * Returns: Whether succeeded, or better whether no fatal error happened.
+ *
+ * Since: 3.30
+ **/
+gboolean
+camel_session_get_recipient_certificates_sync (CamelSession *session,
+ guint32 flags, /* bit-or of CamelRecipientCertificateFlags */
+ const GPtrArray *recipients, /* gchar * */
+ GSList **out_certificates, /* gchar * */
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelSessionClass *klass;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
+ g_return_val_if_fail (recipients != NULL, FALSE);
+ g_return_val_if_fail (out_certificates != NULL, FALSE);
+
+ *out_certificates = NULL;
+
+ klass = CAMEL_SESSION_GET_CLASS (session);
+ g_return_val_if_fail (klass != NULL, FALSE);
+
+ if (!klass->get_recipient_certificates_sync)
+ return TRUE;
+
+ return klass->get_recipient_certificates_sync (session, flags, recipients, out_certificates, cancellable, error);
+}
diff --git a/src/camel/camel-session.h b/src/camel/camel-session.h
index c01a93569..31a4fc553 100644
--- a/src/camel/camel-session.h
+++ b/src/camel/camel-session.h
@@ -139,9 +139,16 @@ struct _CamelSessionClass {
gint *out_expires_in,
GCancellable *cancellable,
GError **error);
+ gboolean (*get_recipient_certificates_sync)
+ (CamelSession *session,
+ guint32 flags, /* bit-or of CamelRecipientCertificateFlags */
+ const GPtrArray *recipients, /* gchar * */
+ GSList **out_certificates, /* gchar * */
+ GCancellable *cancellable,
+ GError **error);
/* Padding for future expansion */
- gpointer reserved_methods[20];
+ gpointer reserved_methods[19];
/* Signals */
void (*job_started) (CamelSession *session,
@@ -273,6 +280,13 @@ gboolean camel_session_get_oauth2_access_token_sync
gint *out_expires_in,
GCancellable *cancellable,
GError **error);
+gboolean camel_session_get_recipient_certificates_sync
+ (CamelSession *session,
+ guint32 flags, /* bit-or of CamelRecipientCertificateFlags */
+ const GPtrArray *recipients, /* gchar * */
+ GSList **out_certificates, /* gchar * */
+ GCancellable *cancellable,
+ GError **error);
G_END_DECLS
diff --git a/src/camel/camel-smime-context.c b/src/camel/camel-smime-context.c
index 45129a4a2..4761c32d3 100644
--- a/src/camel/camel-smime-context.c
+++ b/src/camel/camel-smime-context.c
@@ -1228,10 +1228,16 @@ smime_context_encrypt_sync (CamelCipherContext *context,
CamelDataWrapper *dw;
CamelContentType *ct;
GByteArray *buffer;
+ GSList *gathered_certificates = NULL, *link;
+
+ if (!camel_session_get_recipient_certificates_sync (camel_cipher_context_get_session (context),
+ CAMEL_RECIPIENT_CERTIFICATE_SMIME, recipients, &gathered_certificates, cancellable, error))
+ return FALSE;
poolp = PORT_NewArena (1024);
if (poolp == NULL) {
set_nss_error (error, g_strerror (ENOMEM));
+ g_slist_free_full (gathered_certificates, g_free);
return FALSE;
}
@@ -1239,6 +1245,7 @@ smime_context_encrypt_sync (CamelCipherContext *context,
recipient_certs = (CERTCertificate **) PORT_ArenaZAlloc (poolp, sizeof (recipient_certs[0]) * (recipients->len + 1));
if (recipient_certs == NULL) {
set_nss_error (error, g_strerror (ENOMEM));
+ g_slist_free_full (gathered_certificates, g_free);
goto fail;
}
@@ -1252,6 +1259,30 @@ smime_context_encrypt_sync (CamelCipherContext *context,
frd.certs_missing = g_hash_table_size (frd.recipients_table);
frd.now = PR_Now();
+ for (link = gathered_certificates; link; link = g_slist_next (link)) {
+ const gchar *certstr = link->data;
+
+ if (certstr && *certstr) {
+ CERTCertificate *cert = NULL;
+ gsize len = 0;
+ guchar *data;
+
+ data = g_base64_decode (certstr, &len);
+
+ if (data && len)
+ cert = CERT_DecodeCertFromPackage ((gchar *) data, len);
+
+ g_free (data);
+
+ if (cert) {
+ camel_smime_find_recipients_certs (cert, NULL, &frd);
+ CERT_DestroyCertificate (cert);
+ }
+ }
+ }
+
+ g_slist_free_full (gathered_certificates, g_free);
+
/* Just ignore the return value */
(void) PK11_TraverseSlotCerts (camel_smime_find_recipients_certs, &frd, NULL);