diff options
author | Milan Crha <mcrha@redhat.com> | 2018-07-04 11:46:52 +0200 |
---|---|---|
committer | Milan Crha <mcrha@redhat.com> | 2018-07-04 11:46:52 +0200 |
commit | ea9ed9224edab871cb539e0b7cc0821480012836 (patch) | |
tree | 5d921d01f3a5dc160af0988565e39eca81ea2d27 | |
parent | 20a03b5d1695a05915c5ae1c7112707fac8ad464 (diff) | |
download | evolution-data-server-ea9ed9224edab871cb539e0b7cc0821480012836.tar.gz |
Bug 704246 - Cannot send encrypted mail to contact with certificate
-rw-r--r-- | docs/reference/camel/camel-docs.sgml.in | 4 | ||||
-rw-r--r-- | docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in | 1 | ||||
-rw-r--r-- | src/addressbook/libebook/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/addressbook/libebook/e-book-utils.c | 303 | ||||
-rw-r--r-- | src/addressbook/libebook/e-book-utils.h | 41 | ||||
-rw-r--r-- | src/addressbook/libebook/libebook.h | 1 | ||||
-rw-r--r-- | src/addressbook/libedata-book/e-book-cache.c | 48 | ||||
-rw-r--r-- | src/addressbook/libedata-book/e-book-sqlite.c | 17 | ||||
-rw-r--r-- | src/camel/camel-enums.h | 16 | ||||
-rw-r--r-- | src/camel/camel-gpg-context.c | 112 | ||||
-rw-r--r-- | src/camel/camel-session.c | 57 | ||||
-rw-r--r-- | src/camel/camel-session.h | 16 | ||||
-rw-r--r-- | 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 @@ -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); |