[evolution-data-server] Bug 704246 - Cannot send encrypted mail to contact with certificate



commit ea9ed9224edab871cb539e0b7cc0821480012836
Author: Milan Crha <mcrha redhat com>
Date:   Wed Jul 4 11:46:52 2018 +0200

    Bug 704246 - Cannot send encrypted mail to contact with certificate

 docs/reference/camel/camel-docs.sgml.in            |   4 +
 .../evolution-data-server-docs.sgml.in             |   1 +
 src/addressbook/libebook/CMakeLists.txt            |   4 +-
 src/addressbook/libebook/e-book-utils.c            | 303 +++++++++++++++++++++
 src/addressbook/libebook/e-book-utils.h            |  41 +++
 src/addressbook/libebook/libebook.h                |   1 +
 src/addressbook/libedata-book/e-book-cache.c       |  48 +++-
 src/addressbook/libedata-book/e-book-sqlite.c      |  17 +-
 src/camel/camel-enums.h                            |  16 ++
 src/camel/camel-gpg-context.c                      | 112 +++++++-
 src/camel/camel-session.c                          |  57 ++++
 src/camel/camel-session.h                          |  16 +-
 src/camel/camel-smime-context.c                    |  31 +++
 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
@@ -4228,6 +4229,41 @@ e_book_cache_gather_table_names_cb (ECache *cache,
        return TRUE;
 }
 
+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,
@@ -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
@@ -418,6 +418,22 @@ typedef enum {
        CAMEL_SASL_ANON_TRACE_EMPTY
 } 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:
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);
 


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