[evolution] Bug 704246 - Cannot send encrypted mail to contact with certificate
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution] Bug 704246 - Cannot send encrypted mail to contact with certificate
- Date: Wed, 4 Jul 2018 09:58:36 +0000 (UTC)
commit 3627d75e1cb968b3111b1c952c3a572f97613a09
Author: Milan Crha <mcrha redhat com>
Date: Wed Jul 4 11:56:33 2018 +0200
Bug 704246 - Cannot send encrypted mail to contact with certificate
data/org.gnome.evolution.mail.gschema.xml.in | 15 +-
.../evolution-mail-composer-docs.sgml.in | 4 +
.../evolution-mail-engine-docs.sgml.in | 4 +
src/composer/e-msg-composer.c | 128 ++++++++++++++-
src/libemail-engine/e-mail-engine-enums.h | 19 +++
src/libemail-engine/e-mail-session.c | 175 ++++++++++++++++++++-
src/libemail-engine/e-mail-session.h | 6 +
7 files changed, 346 insertions(+), 5 deletions(-)
---
diff --git a/data/org.gnome.evolution.mail.gschema.xml.in b/data/org.gnome.evolution.mail.gschema.xml.in
index 9564c946bd..a3e381a1c3 100644
--- a/data/org.gnome.evolution.mail.gschema.xml.in
+++ b/data/org.gnome.evolution.mail.gschema.xml.in
@@ -23,7 +23,7 @@
<value nick='outlook' value='3'/>
</enum>
- <!-- Keep this synchronized with EMailImageLoadingPolicy. -->
+ <!-- Keep this synchronized with EImageLoadingPolicy. -->
<enum id="org.gnome.evolution.mail.ImageLoadingPolicy">
<value nick='never' value='0'/>
<value nick='sometimes' value='1'/>
@@ -37,6 +37,13 @@
<value nick='inconsistent' value='2'/>
</enum>
+ <!-- Keep this synchronized with EMailRecipientCertificateLookup. -->
+ <enum id="org.gnome.evolution.mail.MailRecipientCertificateLookup">
+ <value nick='off' value='0'/>
+ <value nick='autocompleted' value='1'/>
+ <value nick='books' value='2'/>
+ </enum>
+
<schema gettext-domain="evolution" id="org.gnome.evolution.mail" path="/org/gnome/evolution/mail/">
<key name="prompt-check-if-default-mailer" type="b">
<default>true</default>
@@ -787,6 +794,12 @@
<default>false</default>
<_summary>Collapse archive folders in Move/Copy message to Folder and Go to Folder
selectors.</_summary>
</key>
+ <key name="lookup-recipient-certificates" enum="org.gnome.evolution.mail.MailRecipientCertificateLookup">
+ <default>'books'</default>
+ <_summary>Where to lookup recipient S/MIME certificates or PGP keys when encrypting
messages.</_summary>
+ <_description>The “off” value completely disables certificate lookup; the “autocompleted” value
provides certificates only for auto-completed contacts; the “books” value uses certificates from
auto-completed contacts and searches in books marked for auto-completion.</_description>
+ </key>
+
<!-- The following keys are deprecated. -->
<key name="forward-style" type="i">
diff --git a/docs/reference/evolution-mail-composer/evolution-mail-composer-docs.sgml.in
b/docs/reference/evolution-mail-composer/evolution-mail-composer-docs.sgml.in
index 2ed1fee181..b610a05024 100644
--- a/docs/reference/evolution-mail-composer/evolution-mail-composer-docs.sgml.in
+++ b/docs/reference/evolution-mail-composer/evolution-mail-composer-docs.sgml.in
@@ -29,6 +29,10 @@
<title>API Index</title>
<xi:include href="xml/api-index-full.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-22" role="3.22">
<title>Index of new symbols in 3.22</title>
<xi:include href="xml/api-index-3.22.xml"><xi:fallback /></xi:include>
diff --git a/docs/reference/evolution-mail-engine/evolution-mail-engine-docs.sgml.in
b/docs/reference/evolution-mail-engine/evolution-mail-engine-docs.sgml.in
index d440c0674e..6f2e36fb14 100644
--- a/docs/reference/evolution-mail-engine/evolution-mail-engine-docs.sgml.in
+++ b/docs/reference/evolution-mail-engine/evolution-mail-engine-docs.sgml.in
@@ -46,6 +46,10 @@
<title>API Index</title>
<xi:include href="xml/api-index-full.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-24" role="3.24">
<title>Index of new symbols in 3.24</title>
<xi:include href="xml/api-index-3.24.xml"><xi:fallback /></xi:include>
diff --git a/src/composer/e-msg-composer.c b/src/composer/e-msg-composer.c
index 73bb890069..529a80d8de 100644
--- a/src/composer/e-msg-composer.c
+++ b/src/composer/e-msg-composer.c
@@ -64,6 +64,7 @@ struct _AsyncContext {
GtkPrintOperationAction print_action;
GPtrArray *recipients;
+ GSList *recipients_with_certificate; /* EContact * */
guint skip_content : 1;
guint need_thread : 1;
@@ -167,6 +168,9 @@ async_context_free (AsyncContext *context)
if (context->recipients != NULL)
g_ptr_array_free (context->recipients, TRUE);
+ if (context->recipients_with_certificate)
+ g_slist_free_full (context->recipients_with_certificate, g_object_unref);
+
g_slice_free (AsyncContext, context);
}
@@ -716,6 +720,107 @@ composer_add_quoted_printable_filter (CamelStream *stream)
g_object_unref (filter);
}
+/* Extracts auto-completed contacts which have X.509 or PGP certificate set.
+ This should be called in the GUI thread, because it accesses GtkWidget-s. */
+static GSList * /* EContact * */
+composer_get_completed_recipients_with_certificate (EMsgComposer *composer)
+{
+ EComposerHeaderTable *table;
+ GSList *contacts = NULL;
+ EDestination **to, **cc, **bcc;
+ gint ii;
+
+ g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
+
+ table = e_msg_composer_get_header_table (composer);
+ to = e_composer_header_table_get_destinations_to (table);
+ cc = e_composer_header_table_get_destinations_cc (table);
+ bcc = e_composer_header_table_get_destinations_bcc (table);
+
+ #define traverse_destv(x) \
+ for (ii = 0; x && x[ii]; ii++) { \
+ EDestination *dest = x[ii]; \
+ EContactCert *x509cert, *pgpcert; \
+ EContact *contact; \
+ \
+ contact = e_destination_get_contact (dest); \
+ \
+ /* Get certificates only for individuals, not for lists */ \
+ if (!contact || e_destination_is_evolution_list (dest)) \
+ continue; \
+ \
+ x509cert = e_contact_get (contact, E_CONTACT_X509_CERT); \
+ pgpcert = e_contact_get (contact, E_CONTACT_PGP_CERT); \
+ \
+ if (x509cert || pgpcert) \
+ contacts = g_slist_prepend (contacts, e_contact_duplicate (contact)); \
+ \
+ e_contact_cert_free (x509cert); \
+ e_contact_cert_free (pgpcert); \
+ }
+
+ traverse_destv (to);
+ traverse_destv (cc);
+ traverse_destv (bcc);
+
+ #undef traverse_destv
+
+ e_destination_freev (to);
+ e_destination_freev (cc);
+ e_destination_freev (bcc);
+
+ return contacts;
+}
+
+static gchar *
+composer_get_recipient_certificate_cb (EMailSession *session,
+ guint32 flags, /* bit-or of CamelRecipientCertificateFlags */
+ const gchar *email_address,
+ gpointer user_data)
+{
+ AsyncContext *context = user_data;
+ EContactField field_id;
+ GSList *link;
+ gchar *base64_cert = NULL;
+
+ g_return_val_if_fail (context != NULL, NULL);
+
+ if (!email_address || !*email_address)
+ return NULL;
+
+ if ((flags & CAMEL_RECIPIENT_CERTIFICATE_SMIME) != 0)
+ field_id = E_CONTACT_X509_CERT;
+ else
+ field_id = E_CONTACT_PGP_CERT;
+
+ for (link = context->recipients_with_certificate; link && !base64_cert; link = g_slist_next (link)) {
+ EContact *contact = link->data;
+ GList *emails, *elink;
+ EContactCert *cert;
+
+ cert = e_contact_get (contact, field_id);
+ if (!cert || !cert->data || !cert->length) {
+ e_contact_cert_free (cert);
+ continue;
+ }
+
+ emails = e_contact_get (contact, E_CONTACT_EMAIL);
+
+ for (elink = emails; elink && !base64_cert; elink = g_list_next (elink)) {
+ const gchar *contact_email = elink->data;
+
+ if (contact_email && g_ascii_strcasecmp (contact_email, email_address) == 0) {
+ base64_cert = g_base64_encode ((const guchar *) cert->data, cert->length);
+ }
+ }
+
+ g_list_free_full (emails, g_free);
+ e_contact_cert_free (cert);
+ }
+
+ return base64_cert;
+}
+
/* Helper for composer_build_message_thread() */
static gboolean
composer_build_message_pgp (AsyncContext *context,
@@ -792,6 +897,7 @@ composer_build_message_pgp (AsyncContext *context,
if (context->pgp_encrypt) {
CamelMimePart *npart;
+ gulong handler_id;
gboolean success;
npart = camel_mime_part_new ();
@@ -807,10 +913,16 @@ composer_build_message_pgp (AsyncContext *context,
camel_gpg_context_set_always_trust (CAMEL_GPG_CONTEXT (cipher), always_trust);
camel_gpg_context_set_prefer_inline (CAMEL_GPG_CONTEXT (cipher), prefer_inline);
+ handler_id = g_signal_connect (context->session, "get-recipient-certificate",
+ G_CALLBACK (composer_get_recipient_certificate_cb), context);
+
success = camel_cipher_context_encrypt_sync (
cipher, pgp_key_id, context->recipients,
mime_part, npart, cancellable, error);
+ if (handler_id)
+ g_signal_handler_disconnect (context->session, handler_id);
+
g_object_unref (cipher);
if (encrypt_to_self && pgp_key_id != NULL)
@@ -951,6 +1063,7 @@ composer_build_message_smime (AsyncContext *context,
}
if (context->smime_encrypt) {
+ gulong handler_id;
gboolean success;
/* Check to see if we should encrypt to self.
@@ -965,12 +1078,18 @@ composer_build_message_smime (AsyncContext *context,
(CamelSMIMEContext *) cipher, TRUE,
encryption_certificate);
+ handler_id = g_signal_connect (context->session, "get-recipient-certificate",
+ G_CALLBACK (composer_get_recipient_certificate_cb), context);
+
success = camel_cipher_context_encrypt_sync (
cipher, NULL,
context->recipients, mime_part,
CAMEL_MIME_PART (context->message),
cancellable, error);
+ if (handler_id)
+ g_signal_handler_disconnect (context->session, handler_id);
+
g_object_unref (cipher);
if (!success)
@@ -1534,13 +1653,16 @@ composer_build_message (EMsgComposer *composer,
}
/* Run any blocking operations in a separate thread. */
- if (context->need_thread)
+ if (context->need_thread) {
+ context->recipients_with_certificate = composer_get_completed_recipients_with_certificate
(composer);
+
g_simple_async_result_run_in_thread (
simple, (GSimpleAsyncThreadFunc)
composer_build_message_thread,
io_priority, cancellable);
- else
+ } else {
g_simple_async_result_complete (simple);
+ }
e_msg_composer_dec_soft_busy (composer);
@@ -2675,7 +2797,7 @@ e_msg_composer_is_busy (EMsgComposer *composer)
* Returns: %TRUE when e_msg_composer_is_busy() returns %TRUE or
* when the asynchronous operations are disabled.
*
- * Since: 3.29.3
+ * Since: 3.30
**/
gboolean
e_msg_composer_is_soft_busy (EMsgComposer *composer)
diff --git a/src/libemail-engine/e-mail-engine-enums.h b/src/libemail-engine/e-mail-engine-enums.h
index b8e98ed4a8..19d7f9daaf 100644
--- a/src/libemail-engine/e-mail-engine-enums.h
+++ b/src/libemail-engine/e-mail-engine-enums.h
@@ -39,6 +39,25 @@ typedef enum {
E_MAIL_NUM_LOCAL_FOLDERS
} EMailLocalFolder;
+/**
+ * EMailRecipientCertificateLookup:
+ * @E_MAIL_RECIPIENT_CERTIFICATE_LOOKUP_OFF: Do not do any recipient certificate lookup
+ * @E_MAIL_RECIPIENT_CERTIFICATE_LOOKUP_AUTOCOMPLETED: Lookup recipient certificates
+ * between auto-completed recipients only
+ * @E_MAIL_RECIPIENT_CERTIFICATE_LOOKUP_BOOKS: Lookup recipient certificates between
+ * auto-completed recipients and all books marked for auto-completion
+ *
+ * Used to set whether and where S/MIME certificates or PGP keys for message encryption
+ * should be looked up for.
+ *
+ * Since: 3.30
+ **/
+typedef enum {
+ E_MAIL_RECIPIENT_CERTIFICATE_LOOKUP_OFF,
+ E_MAIL_RECIPIENT_CERTIFICATE_LOOKUP_AUTOCOMPLETED,
+ E_MAIL_RECIPIENT_CERTIFICATE_LOOKUP_BOOKS
+} EMailRecipientCertificateLookup;
+
G_END_DECLS
#endif /* E_MAIL_ENGINE_ENUMS_H */
diff --git a/src/libemail-engine/e-mail-session.c b/src/libemail-engine/e-mail-session.c
index c86947c9e8..2270d7c0ab 100644
--- a/src/libemail-engine/e-mail-session.c
+++ b/src/libemail-engine/e-mail-session.c
@@ -128,6 +128,7 @@ enum {
STORE_ADDED,
STORE_REMOVED,
ALLOW_AUTH_PROMPT,
+ GET_RECIPIENT_CERTIFICATE,
LAST_SIGNAL
};
@@ -1546,6 +1547,146 @@ mail_session_get_oauth2_access_token_sync (CamelSession *session,
return success;
}
+static gboolean
+mail_session_is_email_address (const gchar *str)
+{
+ gboolean has_at = FALSE, has_dot_after_at = FALSE;
+ gint ii;
+
+ if (!str)
+ return FALSE;
+
+ for (ii = 0; str[ii]; ii++) {
+ if (str[ii] == '@') {
+ if (has_at)
+ return FALSE;
+
+ has_at = TRUE;
+ } else if (has_at && str[ii] == '.') {
+ has_dot_after_at = TRUE;
+ } else if (g_ascii_isspace (str[ii])) {
+ return FALSE;
+ } else if (strchr ("<>;,\\\"'|", str[ii])) {
+ return FALSE;
+ }
+ }
+
+ return has_at && has_dot_after_at;
+}
+
+static gboolean
+mail_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)
+{
+ GHashTable *certificates; /* guint index-to-recipients ~> gchar *certificate */
+ EMailRecipientCertificateLookup lookup_settings;
+ GSettings *settings;
+ gboolean success = TRUE;
+ guint ii;
+
+ g_return_val_if_fail (E_IS_MAIL_SESSION (session), FALSE);
+ g_return_val_if_fail (recipients != NULL, FALSE);
+ g_return_val_if_fail (out_certificates != NULL, FALSE);
+
+ *out_certificates = NULL;
+
+ settings = e_util_ref_settings ("org.gnome.evolution.mail");
+ lookup_settings = g_settings_get_enum (settings, "lookup-recipient-certificates");
+ g_object_unref (settings);
+
+ if (lookup_settings == E_MAIL_RECIPIENT_CERTIFICATE_LOOKUP_OFF)
+ return TRUE;
+
+ certificates = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ for (ii = 0; ii < recipients->len; ii++) {
+ gchar *certstr = NULL;
+
+ g_signal_emit (session, signals[GET_RECIPIENT_CERTIFICATE], 0, flags, recipients->pdata[ii],
&certstr);
+
+ if (certstr && *certstr)
+ g_hash_table_insert (certificates, GUINT_TO_POINTER (ii + 1), certstr);
+ else
+ g_free (certstr);
+ }
+
+ if (lookup_settings == E_MAIL_RECIPIENT_CERTIFICATE_LOOKUP_BOOKS &&
+ g_hash_table_size (certificates) != recipients->len) {
+ ESourceRegistry *registry;
+ GPtrArray *todo_recipients;
+ GSList *found_certificates = NULL;
+
+ todo_recipients = g_ptr_array_new ();
+ for (ii = 0; ii < recipients->len; ii++) {
+ /* Lookup address books only with email addresses. */
+ if (!g_hash_table_contains (certificates, GUINT_TO_POINTER (ii + 1)) &&
+ mail_session_is_email_address (recipients->pdata[ii])) {
+ g_ptr_array_add (todo_recipients, recipients->pdata[ii]);
+ }
+ }
+
+ if (todo_recipients->len) {
+ registry = e_mail_session_get_registry (E_MAIL_SESSION (session));
+
+ if ((flags & CAMEL_RECIPIENT_CERTIFICATE_SMIME) != 0)
+ camel_operation_push_message (cancellable, "%s", _("Looking up recipient
S/MIME certificates in address books…"));
+ else
+ camel_operation_push_message (cancellable, "%s", _("Looking up recipient PGP
keys in address books…"));
+
+ success = e_book_utils_get_recipient_certificates_sync (registry, NULL, flags,
todo_recipients, &found_certificates, cancellable, error);
+
+ camel_operation_pop_message (cancellable);
+ }
+
+ if (success && found_certificates && g_slist_length (found_certificates) ==
todo_recipients->len) {
+ GSList *link;
+
+ for (link = found_certificates, ii = 0; link && ii < recipients->len; ii++) {
+ if (!g_hash_table_contains (certificates, GUINT_TO_POINTER (ii + 1))) {
+ if (link->data) {
+ g_hash_table_insert (certificates, GUINT_TO_POINTER (ii + 1),
link->data);
+ link->data = NULL;
+ }
+
+ link = g_slist_next (link);
+ }
+ }
+ }
+
+ g_slist_free_full (found_certificates, g_free);
+ g_ptr_array_free (todo_recipients, TRUE);
+ }
+
+ if (success) {
+ for (ii = 0; ii < recipients->len; ii++) {
+ *out_certificates = g_slist_prepend (*out_certificates,
+ g_hash_table_lookup (certificates, GUINT_TO_POINTER (ii + 1)));
+ }
+
+ *out_certificates = g_slist_reverse (*out_certificates);
+ } else {
+ GHashTableIter iter;
+ gpointer value;
+
+ /* There is no destructor for the 'value', to be able to easily pass it to
+ the out_certificates. This code is here to free the values, though it might
+ not be usually used, because e_book_utils_get_recipient_certificates_sync()
+ returns TRUE usually. */
+ g_hash_table_iter_init (&iter, certificates);
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
+ g_free (value);
+ }
+ }
+
+ g_hash_table_destroy (certificates);
+
+ return success;
+}
+
static EMVFolderContext *
mail_session_create_vfolder_context (EMailSession *session)
{
@@ -1573,6 +1714,7 @@ e_mail_session_class_init (EMailSessionClass *class)
session_class->forget_password = mail_session_forget_password;
session_class->forward_to_sync = mail_session_forward_to_sync;
session_class->get_oauth2_access_token_sync = mail_session_get_oauth2_access_token_sync;
+ session_class->get_recipient_certificates_sync = mail_session_get_recipient_certificates_sync;
class->create_vfolder_context = mail_session_create_vfolder_context;
@@ -1688,7 +1830,7 @@ e_mail_session_class_init (EMailSessionClass *class)
CAMEL_TYPE_STORE);
/**
- * EMailSession::store-removed
+ * EMailSession::allow-auth-prompt
* @session: the #EMailSession that emitted the signal
* @source: an #ESource
*
@@ -1707,6 +1849,37 @@ e_mail_session_class_init (EMailSessionClass *class)
G_TYPE_NONE, 1,
E_TYPE_SOURCE);
+ /**
+ * EMailSession::get-recipient-certificate
+ * @session: the #EMailSession that emitted the signal
+ * @flags: a bit-or of #CamelRecipientCertificateFlags
+ * @email_address: recipient's email address
+ *
+ * This signal is used to get recipient's S/MIME certificate or
+ * PGP key for encryption, as part of camel_session_get_recipient_certificates_sync().
+ * The listener is not supposed to do any expensive look ups, it should only check
+ * whether it has the certificate available for the given @email_address and
+ * eventually return it as base64 encoded string.
+ *
+ * The caller of the action signal will free returned pointer with g_free(),
+ * when no longer needed.
+ *
+ * Returns: (transfer full) (nullable): %NULL when the certificate not known,
+ * or a newly allocated base64-encoded string with the certificate.
+ *
+ * Since: 3.30
+ **/
+ signals[GET_RECIPIENT_CERTIFICATE] = g_signal_new (
+ "get-recipient-certificate",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMailSessionClass, get_recipient_certificate),
+ NULL, NULL,
+ NULL,
+ G_TYPE_STRING, 2,
+ G_TYPE_UINT,
+ G_TYPE_STRING);
+
camel_null_store_register_provider ();
/* Make sure ESourceCamel picks up the "none" provider. */
diff --git a/src/libemail-engine/e-mail-session.h b/src/libemail-engine/e-mail-session.h
index 44374cd353..7d72b31045 100644
--- a/src/libemail-engine/e-mail-session.h
+++ b/src/libemail-engine/e-mail-session.h
@@ -84,6 +84,12 @@ struct _EMailSessionClass {
CamelStore *store);
void (*allow_auth_prompt) (EMailSession *session,
ESource *source);
+ gchar * (*get_recipient_certificate)
+ (EMailSession *session,
+ guint flags, /* bit-or of CamelRecipientCertificateFlags */
+ const gchar *email_address);
+ /* Padding for future expansion */
+ gpointer reserved[10];
};
GType e_mail_session_get_type (void);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]