Commit 3627d75e authored by Milan Crha's avatar Milan Crha

Bug 704246 - Cannot send encrypted mail to contact with certificate

parent 4c6fa185
......@@ -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">
......
......@@ -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>
......
......@@ -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>
......
......@@ -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)
......
......@@ -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 */
......@@ -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. */
......
......@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment