/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 1999-2008 Novell, Inc. (www.novell.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 . * * Authors: Jeffrey Stedfast */ #include "evolution-data-server-config.h" #include #include #include "camel-cipher-context.h" #include "camel-debug.h" #include "camel-session.h" #include "camel-stream.h" #include "camel-operation.h" #include "camel-mime-utils.h" #include "camel-medium.h" #include "camel-multipart.h" #include "camel-multipart-encrypted.h" #include "camel-multipart-signed.h" #include "camel-mime-message.h" #include "camel-mime-filter-canon.h" #include "camel-stream-filter.h" #define CIPHER_LOCK(ctx) \ g_mutex_lock (&((CamelCipherContext *) ctx)->priv->lock) #define CIPHER_UNLOCK(ctx) \ g_mutex_unlock (&((CamelCipherContext *) ctx)->priv->lock); #define d(x) typedef struct _AsyncContext AsyncContext; struct _CamelCipherContextPrivate { CamelSession *session; GMutex lock; }; struct _AsyncContext { /* arguments */ CamelCipherHash hash; CamelMimePart *ipart; CamelMimePart *opart; CamelStream *stream; GPtrArray *strings; gchar *userid; }; enum { PROP_0, PROP_SESSION }; G_DEFINE_TYPE_WITH_PRIVATE (CamelCipherContext, camel_cipher_context, G_TYPE_OBJECT) G_DEFINE_BOXED_TYPE (CamelCipherValidity, camel_cipher_validity, camel_cipher_validity_clone, camel_cipher_validity_free) static void async_context_free (AsyncContext *async_context) { if (async_context->ipart != NULL) g_object_unref (async_context->ipart); if (async_context->opart != NULL) g_object_unref (async_context->opart); if (async_context->stream != NULL) g_object_unref (async_context->stream); if (async_context->strings != NULL) { g_ptr_array_foreach ( async_context->strings, (GFunc) g_free, NULL); g_ptr_array_free (async_context->strings, TRUE); } g_free (async_context->userid); g_slice_free (AsyncContext, async_context); } static void cipher_context_set_session (CamelCipherContext *context, CamelSession *session) { g_return_if_fail (CAMEL_IS_SESSION (session)); g_return_if_fail (context->priv->session == NULL); context->priv->session = g_object_ref (session); } static void cipher_context_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_SESSION: cipher_context_set_session ( CAMEL_CIPHER_CONTEXT (object), g_value_get_object (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void cipher_context_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_SESSION: g_value_set_object ( value, camel_cipher_context_get_session ( CAMEL_CIPHER_CONTEXT (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void cipher_context_dispose (GObject *object) { CamelCipherContextPrivate *priv; priv = CAMEL_CIPHER_CONTEXT (object)->priv; g_clear_object (&priv->session); /* Chain up to parent's dispose () method. */ G_OBJECT_CLASS (camel_cipher_context_parent_class)->dispose (object); } static void cipher_context_finalize (GObject *object) { CamelCipherContextPrivate *priv; priv = CAMEL_CIPHER_CONTEXT (object)->priv; g_mutex_clear (&priv->lock); /* Chain up to parent's finalize () method. */ G_OBJECT_CLASS (camel_cipher_context_parent_class)->finalize (object); } static const gchar * cipher_context_hash_to_id (CamelCipherContext *context, CamelCipherHash hash) { return NULL; } static CamelCipherHash cipher_context_id_to_hash (CamelCipherContext *context, const gchar *id) { return CAMEL_CIPHER_HASH_DEFAULT; } static gboolean cipher_context_sign_sync (CamelCipherContext *ctx, const gchar *userid, CamelCipherHash hash, CamelMimePart *ipart, CamelMimePart *opart, GCancellable *cancellable, GError **error) { g_set_error ( error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Signing is not supported by this cipher")); return FALSE; } static CamelCipherValidity * cipher_context_verify_sync (CamelCipherContext *context, CamelMimePart *sigpart, GCancellable *cancellable, GError **error) { g_set_error ( error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Verifying is not supported by this cipher")); return NULL; } static gboolean cipher_context_encrypt_sync (CamelCipherContext *context, const gchar *userid, GPtrArray *recipients, CamelMimePart *ipart, CamelMimePart *opart, GCancellable *cancellable, GError **error) { g_set_error ( error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Encryption is not supported by this cipher")); return FALSE; } static CamelCipherValidity * cipher_context_decrypt_sync (CamelCipherContext *context, CamelMimePart *ipart, CamelMimePart *opart, GCancellable *cancellable, GError **error) { g_set_error ( error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Decryption is not supported by this cipher")); return NULL; } static void camel_cipher_context_class_init (CamelCipherContextClass *class) { GObjectClass *object_class; object_class = G_OBJECT_CLASS (class); object_class->set_property = cipher_context_set_property; object_class->get_property = cipher_context_get_property; object_class->dispose = cipher_context_dispose; object_class->finalize = cipher_context_finalize; class->hash_to_id = cipher_context_hash_to_id; class->id_to_hash = cipher_context_id_to_hash; class->sign_sync = cipher_context_sign_sync; class->verify_sync = cipher_context_verify_sync; class->encrypt_sync = cipher_context_encrypt_sync; class->decrypt_sync = cipher_context_decrypt_sync; g_object_class_install_property ( object_class, PROP_SESSION, g_param_spec_object ( "session", "Session", NULL, CAMEL_TYPE_SESSION, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void camel_cipher_context_init (CamelCipherContext *context) { context->priv = camel_cipher_context_get_instance_private (context); g_mutex_init (&context->priv->lock); } /* Helper for camel_cipher_context_sign() */ static void cipher_context_sign_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { gboolean success; AsyncContext *async_context; GError *local_error = NULL; async_context = (AsyncContext *) task_data; success = camel_cipher_context_sign_sync ( CAMEL_CIPHER_CONTEXT (source_object), async_context->userid, async_context->hash, async_context->ipart, async_context->opart, cancellable, &local_error); if (local_error != NULL) { g_task_return_error (task, local_error); } else { g_task_return_boolean (task, success); } } /** * camel_cipher_context_sign_sync: * @context: a #CamelCipherContext * @userid: a private key to use to sign the stream * @hash: preferred Message-Integrity-Check hash algorithm * @ipart: input #CamelMimePart * @opart: output #CamelMimePart * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Converts the (unsigned) part @ipart into a new self-contained MIME * part @opart. This may be a multipart/signed part, or a simple part * for enveloped types. * * Returns: %TRUE on success, %FALSE on error * * Since: 3.0 **/ gboolean camel_cipher_context_sign_sync (CamelCipherContext *context, const gchar *userid, CamelCipherHash hash, CamelMimePart *ipart, CamelMimePart *opart, GCancellable *cancellable, GError **error) { CamelCipherContextClass *class; gboolean success; g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), FALSE); class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context); g_return_val_if_fail (class != NULL, FALSE); g_return_val_if_fail (class->sign_sync != NULL, FALSE); CIPHER_LOCK (context); /* Check for cancellation after locking. */ if (g_cancellable_set_error_if_cancelled (cancellable, error)) { CIPHER_UNLOCK (context); return FALSE; } camel_operation_push_message (cancellable, _("Signing message")); success = class->sign_sync ( context, userid, hash, ipart, opart, cancellable, error); CAMEL_CHECK_GERROR (context, sign_sync, success, error); camel_operation_pop_message (cancellable); CIPHER_UNLOCK (context); return success; } /** * camel_cipher_context_sign: * @context: a #CamelCipherContext * @userid: a private key to use to sign the stream * @hash: preferred Message-Integrity-Check hash algorithm * @ipart: input #CamelMimePart * @opart: output #CamelMimePart * @io_priority: the I/O priority of the request * @cancellable: optional #GCancellable object, or %NULL * @callback: a #GAsyncReadyCallback to call when the request is satisfied * @user_data: data to pass to the callback function * * Asynchronously converts the (unsigned) part @ipart into a new * self-contained MIME part @opart. This may be a multipart/signed part, * or a simple part for enveloped types. * * When the operation is finished, @callback will be called. You can then * call camel_cipher_context_sign_finish() to get the result of the operation. * * Since: 3.0 **/ void camel_cipher_context_sign (CamelCipherContext *context, const gchar *userid, CamelCipherHash hash, CamelMimePart *ipart, CamelMimePart *opart, gint io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; AsyncContext *async_context; g_return_if_fail (CAMEL_IS_CIPHER_CONTEXT (context)); g_return_if_fail (CAMEL_IS_MIME_PART (ipart)); g_return_if_fail (CAMEL_IS_MIME_PART (opart)); async_context = g_slice_new0 (AsyncContext); async_context->userid = g_strdup (userid); async_context->hash = hash; async_context->ipart = g_object_ref (ipart); async_context->opart = g_object_ref (opart); task = g_task_new (context, cancellable, callback, user_data); g_task_set_source_tag (task, camel_cipher_context_sign); g_task_set_priority (task, io_priority); g_task_set_task_data ( task, async_context, (GDestroyNotify) async_context_free); g_task_run_in_thread (task, cipher_context_sign_thread); g_object_unref (task); } /** * camel_cipher_context_sign_finish: * @context: a #CamelCipherContext * @result: a #GAsyncResult * @error: return location for a #GError, or %NULL * * Finishes the operation started with camel_cipher_context_sign(). * * Returns: %TRUE on success, %FALSE on error * * Since: 3.0 **/ gboolean camel_cipher_context_sign_finish (CamelCipherContext *context, GAsyncResult *result, GError **error) { g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), FALSE); g_return_val_if_fail (g_task_is_valid (result, context), FALSE); g_return_val_if_fail ( g_async_result_is_tagged ( result, camel_cipher_context_sign), FALSE); return g_task_propagate_boolean (G_TASK (result), error); } /** * camel_cipher_context_verify_sync: * @context: a #CamelCipherContext * @ipart: the #CamelMimePart to verify * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Verifies the signature. * * Returns: a #CamelCipherValidity structure containing information * about the integrity of the input stream, or %NULL on failure to * execute at all **/ CamelCipherValidity * camel_cipher_context_verify_sync (CamelCipherContext *context, CamelMimePart *ipart, GCancellable *cancellable, GError **error) { CamelCipherContextClass *class; CamelCipherValidity *valid; g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), NULL); g_return_val_if_fail (CAMEL_IS_MIME_PART (ipart), NULL); class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context); g_return_val_if_fail (class != NULL, NULL); g_return_val_if_fail (class->verify_sync != NULL, NULL); CIPHER_LOCK (context); /* Check for cancellation after locking. */ if (g_cancellable_set_error_if_cancelled (cancellable, error)) { CIPHER_UNLOCK (context); return NULL; } valid = class->verify_sync (context, ipart, cancellable, error); CAMEL_CHECK_GERROR (context, verify_sync, valid != NULL, error); CIPHER_UNLOCK (context); return valid; } /* Helper for camel_cipher_context_verify() */ static void cipher_context_verify_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { CamelCipherValidity *validity; AsyncContext *async_context; GError *local_error = NULL; async_context = (AsyncContext *) task_data; validity = camel_cipher_context_verify_sync ( CAMEL_CIPHER_CONTEXT (source_object), async_context->ipart, cancellable, &local_error); if (local_error != NULL) { g_warn_if_fail (validity == NULL); g_task_return_error (task, local_error); } else { g_task_return_pointer ( task, validity, (GDestroyNotify) camel_cipher_validity_free); } } /** * camel_cipher_context_verify: * @context: a #CamelCipherContext * @ipart: the #CamelMimePart to verify * @io_priority: the I/O priority of the request * @cancellable: optional #GCancellable object, or %NULL * @callback: a #GAsyncReadyCallback to call when the request is satisfied * @user_data: data to pass to the callback function * * Asynchronously verifies the signature. * * When the operation is finished, @callback will be called. You can * then call camel_cipher_context_verify_finish() to get the result of * the operation. * * Since: 3.0 **/ void camel_cipher_context_verify (CamelCipherContext *context, CamelMimePart *ipart, gint io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; AsyncContext *async_context; g_return_if_fail (CAMEL_IS_CIPHER_CONTEXT (context)); g_return_if_fail (CAMEL_IS_MIME_PART (ipart)); async_context = g_slice_new0 (AsyncContext); async_context->ipart = g_object_ref (ipart); task = g_task_new (context, cancellable, callback, user_data); g_task_set_source_tag (task, camel_cipher_context_verify); g_task_set_priority (task, io_priority); g_task_set_task_data ( task, async_context, (GDestroyNotify) async_context_free); g_task_run_in_thread (task, cipher_context_verify_thread); g_object_unref (task); } /** * camel_cipher_context_verify_finish: * @context: a #CamelCipherContext * @result: a #GAsyncResult * @error: return location for a #GError, or %NULL * * Finishes the operation started with camel_cipher_context_verify(). * * Returns: a #CamelCipherValidity structure containing information * about the integrity of the input stream, or %NULL on failure to * execute at all * * Since: 3.0 **/ CamelCipherValidity * camel_cipher_context_verify_finish (CamelCipherContext *context, GAsyncResult *result, GError **error) { g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), NULL); g_return_val_if_fail (g_task_is_valid (result, context), NULL); g_return_val_if_fail ( g_async_result_is_tagged ( result, camel_cipher_context_verify), NULL); return g_task_propagate_pointer (G_TASK (result), error); } /** * camel_cipher_context_encrypt_sync: * @context: a #CamelCipherContext * @userid: (nullable): key ID (or email address) to use when signing, or %NULL to not sign * @recipients: (element-type utf8): an array of recipient key IDs and/or email addresses * @ipart: clear-text #CamelMimePart * @opart: cipher-text #CamelMimePart * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Encrypts (and optionally signs) the clear-text @ipart and writes the * resulting cipher-text to @opart. * * Returns: %TRUE on success, %FALSE on error * * Since: 3.0 **/ gboolean camel_cipher_context_encrypt_sync (CamelCipherContext *context, const gchar *userid, GPtrArray *recipients, CamelMimePart *ipart, CamelMimePart *opart, GCancellable *cancellable, GError **error) { CamelCipherContextClass *class; gboolean success; g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), FALSE); g_return_val_if_fail (CAMEL_IS_MIME_PART (ipart), FALSE); g_return_val_if_fail (CAMEL_IS_MIME_PART (opart), FALSE); class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context); g_return_val_if_fail (class != NULL, FALSE); g_return_val_if_fail (class->encrypt_sync != NULL, FALSE); CIPHER_LOCK (context); /* Check for cancellation after locking. */ if (g_cancellable_set_error_if_cancelled (cancellable, error)) { CIPHER_UNLOCK (context); return FALSE; } camel_operation_push_message (cancellable, _("Encrypting message")); success = class->encrypt_sync ( context, userid, recipients, ipart, opart, cancellable, error); CAMEL_CHECK_GERROR (context, encrypt_sync, success, error); camel_operation_pop_message (cancellable); CIPHER_UNLOCK (context); return success; } /* Helper for camel_cipher_context_encrypt_thread() */ static void cipher_context_encrypt_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { gboolean success; AsyncContext *async_context; GError *local_error = NULL; async_context = (AsyncContext *) task_data; success = camel_cipher_context_encrypt_sync ( CAMEL_CIPHER_CONTEXT (source_object), async_context->userid, async_context->strings, async_context->ipart, async_context->opart, cancellable, &local_error); if (local_error != NULL) { g_task_return_error (task, local_error); } else { g_task_return_boolean (task, success); } } /** * camel_cipher_context_encrypt: * @context: a #CamelCipherContext * @userid: (nullable): key id (or email address) to use when signing, or %NULL to not sign * @recipients: (element-type utf8): an array of recipient key IDs and/or email addresses * @ipart: clear-text #CamelMimePart * @opart: cipher-text #CamelMimePart * @io_priority: the I/O priority of the request * @cancellable: optional #GCancellable object, or %NULL * @callback: a #GAsyncReadyCallback to call when the request is satisfied * @user_data: data to pass to the callback function * * Asynchronously encrypts (and optionally signs) the clear-text @ipart and * writes the resulting cipher-text to @opart. * * When the operation is finished, @callback will be called. You can * then call camel_cipher_context_encrypt_finish() to get the result of * the operation. * * Since: 3.0 **/ void camel_cipher_context_encrypt (CamelCipherContext *context, const gchar *userid, GPtrArray *recipients, CamelMimePart *ipart, CamelMimePart *opart, gint io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; AsyncContext *async_context; guint ii; g_return_if_fail (CAMEL_IS_CIPHER_CONTEXT (context)); g_return_if_fail (CAMEL_IS_MIME_PART (ipart)); g_return_if_fail (CAMEL_IS_MIME_PART (opart)); async_context = g_slice_new0 (AsyncContext); async_context->userid = g_strdup (userid); async_context->strings = g_ptr_array_new (); async_context->ipart = g_object_ref (ipart); async_context->opart = g_object_ref (opart); for (ii = 0; ii < recipients->len; ii++) g_ptr_array_add ( async_context->strings, g_strdup (recipients->pdata[ii])); task = g_task_new (context, cancellable, callback, user_data); g_task_set_source_tag (task, camel_cipher_context_encrypt); g_task_set_priority (task, io_priority); g_task_set_task_data ( task, async_context, (GDestroyNotify) async_context_free); g_task_run_in_thread (task, cipher_context_encrypt_thread); g_object_unref (task); } /** * camel_cipher_context_encrypt_finish: * @context: a #CamelCipherContext * @result: a #GAsyncResult * @error: return location for a #GError, or %NULL * * Finishes the operation started with camel_cipher_context_encrypt(). * * Returns: %TRUE on success, %FALSE on error * * Since: 3.0 **/ gboolean camel_cipher_context_encrypt_finish (CamelCipherContext *context, GAsyncResult *result, GError **error) { g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), FALSE); g_return_val_if_fail (g_task_is_valid (result, context), FALSE); g_return_val_if_fail ( g_async_result_is_tagged ( result, camel_cipher_context_encrypt), FALSE); return g_task_propagate_boolean (G_TASK (result), error); } /** * camel_cipher_context_decrypt_sync: * @context: a #CamelCipherContext * @ipart: cipher-text #CamelMimePart * @opart: clear-text #CamelMimePart * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Decrypts @ipart into @opart. * * Returns: a validity/encryption status, or %NULL on error * * Since: 3.0 **/ CamelCipherValidity * camel_cipher_context_decrypt_sync (CamelCipherContext *context, CamelMimePart *ipart, CamelMimePart *opart, GCancellable *cancellable, GError **error) { CamelCipherContextClass *class; CamelCipherValidity *valid; g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), NULL); g_return_val_if_fail (CAMEL_IS_MIME_PART (ipart), NULL); g_return_val_if_fail (CAMEL_IS_MIME_PART (opart), NULL); class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context); g_return_val_if_fail (class != NULL, NULL); g_return_val_if_fail (class->decrypt_sync != NULL, NULL); CIPHER_LOCK (context); /* Check for cancellation after locking. */ if (g_cancellable_set_error_if_cancelled (cancellable, error)) { CIPHER_UNLOCK (context); return NULL; } camel_operation_push_message (cancellable, _("Decrypting message")); valid = class->decrypt_sync ( context, ipart, opart, cancellable, error); CAMEL_CHECK_GERROR (context, decrypt_sync, valid != NULL, error); camel_operation_pop_message (cancellable); CIPHER_UNLOCK (context); return valid; } /* Helper for camel_cipher_context_decrypt() */ static void cipher_context_decrypt_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { CamelCipherValidity *validity; AsyncContext *async_context; GError *local_error = NULL; async_context = (AsyncContext *) task_data; validity = camel_cipher_context_decrypt_sync ( CAMEL_CIPHER_CONTEXT (source_object), async_context->ipart, async_context->opart, cancellable, &local_error); if (local_error != NULL) { g_warn_if_fail (validity == NULL); g_task_return_error (task, local_error); } else { g_task_return_pointer ( task, validity, (GDestroyNotify) camel_cipher_validity_free); } } /** * camel_cipher_context_decrypt: * @context: a #CamelCipherContext * @ipart: cipher-text #CamelMimePart * @opart: clear-text #CamelMimePart * @io_priority: the I/O priority of the request * @cancellable: optional #GCancellable object, or %NULL * @callback: a #GAsyncReadyCallback to call when the request is satisfied * @user_data: data to pass to the callback function * * Asynchronously decrypts @ipart into @opart. * * When the operation is finished, @callback will be called. You can * then call camel_cipher_context_decrypt_finish() to get the result of * the operation. * * Since: 3.0 **/ void camel_cipher_context_decrypt (CamelCipherContext *context, CamelMimePart *ipart, CamelMimePart *opart, gint io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; AsyncContext *async_context; g_return_if_fail (CAMEL_IS_CIPHER_CONTEXT (context)); g_return_if_fail (CAMEL_IS_MIME_PART (ipart)); g_return_if_fail (CAMEL_IS_MIME_PART (opart)); async_context = g_slice_new0 (AsyncContext); async_context->ipart = g_object_ref (ipart); async_context->opart = g_object_ref (opart); task = g_task_new (context, cancellable, callback, user_data); g_task_set_source_tag (task, camel_cipher_context_decrypt); g_task_set_priority (task, io_priority); g_task_set_task_data ( task, async_context, (GDestroyNotify) async_context_free); g_task_run_in_thread (task, cipher_context_decrypt_thread); g_object_unref (task); } /** * camel_cipher_context_decrypt_finish: * @context: a #CamelCipherContext * @result: a #GAsyncResult * @error: return location for a #GError, or %NULL * * Finishes the operation started with camel_cipher_context_decrypt(). * * Returns: a validity/encryption status, or %NULL on error * * Since: 3.0 **/ CamelCipherValidity * camel_cipher_context_decrypt_finish (CamelCipherContext *context, GAsyncResult *result, GError **error) { g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), NULL); g_return_val_if_fail (g_task_is_valid (result, context), NULL); g_return_val_if_fail ( g_async_result_is_tagged ( result, camel_cipher_context_decrypt), NULL); return g_task_propagate_pointer (G_TASK (result), error); } /* a couple of util functions */ CamelCipherHash camel_cipher_context_id_to_hash (CamelCipherContext *context, const gchar *id) { CamelCipherContextClass *class; g_return_val_if_fail ( CAMEL_IS_CIPHER_CONTEXT (context), CAMEL_CIPHER_HASH_DEFAULT); class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context); g_return_val_if_fail (class != NULL, CAMEL_CIPHER_HASH_DEFAULT); g_return_val_if_fail (class->id_to_hash != NULL, CAMEL_CIPHER_HASH_DEFAULT); return class->id_to_hash (context, id); } const gchar * camel_cipher_context_hash_to_id (CamelCipherContext *context, CamelCipherHash hash) { CamelCipherContextClass *class; g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), NULL); class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context); g_return_val_if_fail (class != NULL, NULL); g_return_val_if_fail (class->hash_to_id != NULL, NULL); return class->hash_to_id (context, hash); } /* Cipher Validity stuff */ static void ccv_certinfo_property_free (gpointer ptr) { CamelCipherCertInfoProperty *property = ptr; if (property) { g_free (property->name); if (property->value_free) property->value_free (property->value); g_free (property); } } static void ccv_certinfo_free (CamelCipherCertInfo *info) { g_return_if_fail (info != NULL); g_free (info->name); g_free (info->email); if (info->cert_data && info->cert_data_free) info->cert_data_free (info->cert_data); g_slist_free_full (info->properties, ccv_certinfo_property_free); g_free (info); } CamelCipherValidity * camel_cipher_validity_new (void) { CamelCipherValidity *validity; validity = g_malloc (sizeof (*validity)); camel_cipher_validity_init (validity); return validity; } void camel_cipher_validity_init (CamelCipherValidity *validity) { g_return_if_fail (validity != NULL); memset (validity, 0, sizeof (*validity)); g_queue_init (&validity->children); g_queue_init (&validity->sign.signers); g_queue_init (&validity->encrypt.encrypters); } gboolean camel_cipher_validity_get_valid (CamelCipherValidity *validity) { return validity != NULL && validity->sign.status == CAMEL_CIPHER_VALIDITY_SIGN_GOOD; } void camel_cipher_validity_set_valid (CamelCipherValidity *validity, gboolean valid) { g_return_if_fail (validity != NULL); validity->sign.status = valid ? CAMEL_CIPHER_VALIDITY_SIGN_GOOD : CAMEL_CIPHER_VALIDITY_SIGN_BAD; } gchar * camel_cipher_validity_get_description (CamelCipherValidity *validity) { g_return_val_if_fail (validity != NULL, NULL); return validity->sign.description; } void camel_cipher_validity_set_description (CamelCipherValidity *validity, const gchar *description) { g_return_if_fail (validity != NULL); g_free (validity->sign.description); validity->sign.description = g_strdup (description); } void camel_cipher_validity_clear (CamelCipherValidity *validity) { g_return_if_fail (validity != NULL); /* TODO: this doesn't free children/clear key lists */ g_free (validity->sign.description); g_free (validity->encrypt.description); camel_cipher_validity_init (validity); } CamelCipherValidity * camel_cipher_validity_clone (CamelCipherValidity *vin) { CamelCipherValidity *vo; GList *head, *link; g_return_val_if_fail (vin != NULL, NULL); vo = camel_cipher_validity_new (); vo->sign.status = vin->sign.status; vo->sign.description = g_strdup (vin->sign.description); vo->encrypt.status = vin->encrypt.status; vo->encrypt.description = g_strdup (vin->encrypt.description); head = g_queue_peek_head_link (&vin->sign.signers); for (link = head; link != NULL; link = g_list_next (link)) { CamelCipherCertInfo *info = link->data; gint index; if (info->cert_data && info->cert_data_clone && info->cert_data_free) index = camel_cipher_validity_add_certinfo_ex ( vo, CAMEL_CIPHER_VALIDITY_SIGN, info->name, info->email, info->cert_data_clone (info->cert_data), info->cert_data_free, info->cert_data_clone); else index = camel_cipher_validity_add_certinfo ( vo, CAMEL_CIPHER_VALIDITY_SIGN, info->name, info->email); if (index != -1 && info->properties) { GSList *link; for (link = info->properties; link; link = g_slist_next (link)) { CamelCipherCertInfoProperty *property = link->data; gpointer value; if (!property) continue; value = property->value_clone ? property->value_clone (property->value) : property->value; camel_cipher_validity_set_certinfo_property (vo, CAMEL_CIPHER_VALIDITY_SIGN, index, property->name, value, property->value_free, property->value_clone); } } } head = g_queue_peek_head_link (&vin->encrypt.encrypters); for (link = head; link != NULL; link = g_list_next (link)) { CamelCipherCertInfo *info = link->data; gint index; if (info->cert_data && info->cert_data_clone && info->cert_data_free) index = camel_cipher_validity_add_certinfo_ex ( vo, CAMEL_CIPHER_VALIDITY_SIGN, info->name, info->email, info->cert_data_clone (info->cert_data), info->cert_data_free, info->cert_data_clone); else index = camel_cipher_validity_add_certinfo ( vo, CAMEL_CIPHER_VALIDITY_ENCRYPT, info->name, info->email); if (index != -1 && info->properties) { GSList *link; for (link = info->properties; link; link = g_slist_next (link)) { CamelCipherCertInfoProperty *property = link->data; gpointer value; if (!property) continue; value = property->value_clone ? property->value_clone (property->value) : property->value; camel_cipher_validity_set_certinfo_property (vo, CAMEL_CIPHER_VALIDITY_ENCRYPT, index, property->name, value, property->value_free, property->value_clone); } } } return vo; } /** * camel_cipher_validity_add_certinfo: * @vin: a #CamelCipherValidity * @mode: a #CamelCipherValidityMode, where to add the additional certificate information * @name: a name to add * @email: an e-mail address to add * * Add a cert info to the signer or encrypter info. * * Returns: Index of the added certinfo; -1 on error **/ gint camel_cipher_validity_add_certinfo (CamelCipherValidity *vin, CamelCipherValidityMode mode, const gchar *name, const gchar *email) { return camel_cipher_validity_add_certinfo_ex (vin, mode, name, email, NULL, NULL, NULL); } /** * camel_cipher_validity_add_certinfo_ex: * @vin: a #CamelCipherValidity * @mode: a #CamelCipherValidityMode, where to add the additional certificate information * @name: a name to add * @email: an e-mail address to add * @cert_data: (nullable) (destroy cert_data_free): a certificate data, or %NULL * @cert_data_free: (nullable): a destroy function for @cert_data; required, when @cert_data is not %NULL * @cert_data_clone: (nullable) (scope call): a copy function for @cert_data, to copy the data; required, when @cert_data is not %NULL * * Add a cert info to the signer or encrypter info, with extended data set. * * Returns: Index of the added certinfo; -1 on error * * Since: 2.30 **/ gint camel_cipher_validity_add_certinfo_ex (CamelCipherValidity *vin, CamelCipherValidityMode mode, const gchar *name, const gchar *email, gpointer cert_data, GDestroyNotify cert_data_free, CamelCipherCloneFunc cert_data_clone) { CamelCipherCertInfo *info; GQueue *queue; g_return_val_if_fail (vin != NULL, -1); if (cert_data) { g_return_val_if_fail (cert_data_free != NULL, -1); g_return_val_if_fail (cert_data_clone != NULL, -1); } info = g_malloc0 (sizeof (*info)); info->name = g_strdup (name); info->email = g_strdup (email); if (cert_data) { info->cert_data = cert_data; info->cert_data_free = cert_data_free; info->cert_data_clone = cert_data_clone; } if (mode == CAMEL_CIPHER_VALIDITY_SIGN) queue = &vin->sign.signers; else queue = &vin->encrypt.encrypters; g_queue_push_tail (queue, info); return (gint) (g_queue_get_length (queue) - 1); } /** * camel_cipher_validity_get_certinfo_property: * @vin: a #CamelCipherValidity * @mode: which cipher validity part to use * @info_index: a 0-based index of the requested #CamelCipherCertInfo * @name: a property name * * Gets a named property @name value for the given @info_index of the @mode validity part. * * Returns: (transfer none) (nullable): Value of a named property of a #CamelCipherCertInfo, or %NULL when no such * property exists. The returned value is owned by the associated #CamelCipherCertInfo * and is valid until the cert info is freed. * * Since: 3.22 **/ gpointer camel_cipher_validity_get_certinfo_property (CamelCipherValidity *vin, CamelCipherValidityMode mode, gint info_index, const gchar *name) { GQueue *queue; CamelCipherCertInfo *cert_info; g_return_val_if_fail (vin != NULL, NULL); g_return_val_if_fail (name != NULL, NULL); if (mode == CAMEL_CIPHER_VALIDITY_SIGN) queue = &vin->sign.signers; else queue = &vin->encrypt.encrypters; g_return_val_if_fail (info_index >= 0 && info_index < g_queue_get_length (queue), NULL); cert_info = g_queue_peek_nth (queue, info_index); g_return_val_if_fail (cert_info != NULL, NULL); return camel_cipher_certinfo_get_property (cert_info, name); } /** * camel_cipher_validity_set_certinfo_property: * @vin: a #CamelCipherValidity * @mode: which cipher validity part to use * @info_index: a 0-based index of the requested #CamelCipherCertInfo * @name: a property name * @value: (nullable) (destroy value_free): a property value, or %NULL * @value_free: (nullable): a free function for the @value * @value_clone: (nullable) (scope call): a clone function for the @value * * Sets a named property @name value @value for the given @info_index * of the @mode validity part. If the @value is %NULL, then the property * is removed. With a non-%NULL @value also @value_free and @value_clone * functions cannot be %NULL. * * Since: 3.22 **/ void camel_cipher_validity_set_certinfo_property (CamelCipherValidity *vin, CamelCipherValidityMode mode, gint info_index, const gchar *name, gpointer value, GDestroyNotify value_free, CamelCipherCloneFunc value_clone) { GQueue *queue; CamelCipherCertInfo *cert_info; g_return_if_fail (vin != NULL); g_return_if_fail (name != NULL); if (mode == CAMEL_CIPHER_VALIDITY_SIGN) queue = &vin->sign.signers; else queue = &vin->encrypt.encrypters; g_return_if_fail (info_index >= 0 && info_index < g_queue_get_length (queue)); cert_info = g_queue_peek_nth (queue, info_index); g_return_if_fail (cert_info != NULL); camel_cipher_certinfo_set_property (cert_info, name, value, value_free, value_clone); } /** * camel_cipher_validity_envelope: * @parent: a #CamelCipherValidity * @valid: a new #CamelCipherValidity to conglomerate the @parent with * * Calculate a conglomerate validity based on wrapping one secure part inside * another one. **/ void camel_cipher_validity_envelope (CamelCipherValidity *parent, CamelCipherValidity *valid) { g_return_if_fail (parent != NULL); g_return_if_fail (valid != NULL); if (parent->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE && parent->encrypt.status == CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE && valid->sign.status == CAMEL_CIPHER_VALIDITY_SIGN_NONE && valid->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE) { GList *head, *link; /* case 1: only signed inside only encrypted -> merge both */ parent->encrypt.status = valid->encrypt.status; parent->encrypt.description = g_strdup (valid->encrypt.description); head = g_queue_peek_head_link (&valid->encrypt.encrypters); for (link = head; link != NULL; link = g_list_next (link)) { CamelCipherCertInfo *info = link->data; camel_cipher_validity_add_certinfo ( parent, CAMEL_CIPHER_VALIDITY_ENCRYPT, info->name, info->email); } } else if (parent->sign.status == CAMEL_CIPHER_VALIDITY_SIGN_NONE && parent->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE && valid->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE && valid->encrypt.status == CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE) { GList *head, *link; /* case 2: only encrypted inside only signed */ parent->sign.status = valid->sign.status; parent->sign.description = g_strdup (valid->sign.description); head = g_queue_peek_head_link (&valid->sign.signers); for (link = head; link != NULL; link = g_list_next (link)) { CamelCipherCertInfo *info = link->data; camel_cipher_validity_add_certinfo ( parent, CAMEL_CIPHER_VALIDITY_SIGN, info->name, info->email); } } /* Otherwise, I dunno - what do you do? */ } void camel_cipher_validity_free (CamelCipherValidity *validity) { CamelCipherValidity *child; CamelCipherCertInfo *info; GQueue *queue; if (validity == NULL) return; queue = &validity->children; while ((child = g_queue_pop_head (queue)) != NULL) camel_cipher_validity_free (child); queue = &validity->sign.signers; while ((info = g_queue_pop_head (queue)) != NULL) ccv_certinfo_free (info); queue = &validity->encrypt.encrypters; while ((info = g_queue_pop_head (queue)) != NULL) ccv_certinfo_free (info); camel_cipher_validity_clear (validity); g_free (validity); } /* ********************************************************************** */ /** * camel_cipher_certinfo_get_property: * @cert_info: a #CamelCipherCertInfo * @name: a property name * * Gets a named property @name value for the given @cert_info. * * Returns: (transfer none) (nullable): Value of a named property of the @cert_info, * or %NULL when no such property exists. The returned value is owned by * the @cert_info and is valid until the @cert_info is freed. * * Since: 3.22 **/ gpointer camel_cipher_certinfo_get_property (CamelCipherCertInfo *cert_info, const gchar *name) { GSList *link; g_return_val_if_fail (cert_info != NULL, NULL); g_return_val_if_fail (name != NULL, NULL); for (link = cert_info->properties; link; link = g_slist_next (link)) { CamelCipherCertInfoProperty *property = link->data; if (property && g_ascii_strcasecmp (property->name, name) == 0) return property->value; } return NULL; } /** * camel_cipher_certinfo_set_property: * @cert_info: a #CamelCipherCertInfo * @name: a property name * @value: (nullable) (destroy value_free): a property value, or %NULL * @value_free: (nullable): a free function for the @value * @value_clone: (nullable) (scope call): a clone function for the @value * * Sets a named property @name value @value for the given @cert_info. * If the @value is %NULL, then the property is removed. With a non-%NULL * @value also @value_free and @value_clone functions cannot be %NULL. * * Since: 3.22 **/ void camel_cipher_certinfo_set_property (CamelCipherCertInfo *cert_info, const gchar *name, gpointer value, GDestroyNotify value_free, CamelCipherCloneFunc value_clone) { CamelCipherCertInfoProperty *property; GSList *link; g_return_if_fail (cert_info != NULL); g_return_if_fail (name != NULL); if (value) { g_return_if_fail (value_free != NULL); g_return_if_fail (value_clone != NULL); } for (link = cert_info->properties; link; link = g_slist_next (link)) { property = link->data; if (property && g_ascii_strcasecmp (property->name, name) == 0) { if (value && property->value != value) { /* Replace current value with the new value. */ property->value_free (property->value); property->value = value; property->value_free = value_free; property->value_clone = value_clone; } else if (!value) { cert_info->properties = g_slist_remove (cert_info->properties, property); ccv_certinfo_property_free (property); } break; } } if (value && !link) { property = g_new0 (CamelCipherCertInfoProperty, 1); property->name = g_strdup (name); property->value = value; property->value_free = value_free; property->value_clone = value_clone; cert_info->properties = g_slist_prepend (cert_info->properties, property); } } /* ********************************************************************** */ /** * camel_cipher_context_new: * @session: a #CamelSession * * This creates a new CamelCipherContext object which is used to sign, * verify, encrypt and decrypt streams. * * Returns: the new CamelCipherContext **/ CamelCipherContext * camel_cipher_context_new (CamelSession *session) { g_return_val_if_fail (session != NULL, NULL); return g_object_new ( CAMEL_TYPE_CIPHER_CONTEXT, "session", session, NULL); } /** * camel_cipher_context_get_session: * @context: a #CamelCipherContext * * Returns: (transfer none): * * Since: 2.32 **/ CamelSession * camel_cipher_context_get_session (CamelCipherContext *context) { g_return_val_if_fail (CAMEL_IS_CIPHER_CONTEXT (context), NULL); return context->priv->session; } /* See rfc3156, section 2 and others */ /* We do this simply: Anything not base64 must be qp * This is so that we can safely translate any occurance of "From " * into the quoted-printable escaped version safely. */ static void cc_prepare_sign (CamelMimePart *part) { CamelDataWrapper *dw; CamelTransferEncoding encoding; gint parts, i; dw = camel_medium_get_content ((CamelMedium *) part); if (!dw) return; /* should not change encoding for these, they have the right encoding set already */ if (CAMEL_IS_MULTIPART_SIGNED (dw) || CAMEL_IS_MULTIPART_ENCRYPTED (dw)) return; if (CAMEL_IS_MULTIPART (dw)) { parts = camel_multipart_get_number ((CamelMultipart *) dw); for (i = 0; i < parts; i++) cc_prepare_sign (camel_multipart_get_part ((CamelMultipart *) dw, i)); } else if (CAMEL_IS_MIME_MESSAGE (dw)) { cc_prepare_sign ((CamelMimePart *) dw); } else { encoding = camel_mime_part_get_encoding (part); if (encoding != CAMEL_TRANSFER_ENCODING_BASE64 && encoding != CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE) { camel_mime_part_set_encoding (part, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE); } } } /** * camel_cipher_canonical_to_stream: * @part: Part to write. * @flags: flags for the canonicalisation filter (CamelMimeFilterCanon) * @ostream: stream to write canonicalised output to. * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Writes a part to a stream in a canonicalised format, suitable for signing/encrypting. * * The transfer encoding paramaters for the part may be changed by this function. * * Returns: -1 on error; **/ gint camel_cipher_canonical_to_stream (CamelMimePart *part, guint32 flags, CamelStream *ostream, GCancellable *cancellable, GError **error) { CamelStream *filter; CamelMimeFilter *canon; gint res = -1; g_return_val_if_fail (CAMEL_IS_MIME_PART (part), -1); g_return_val_if_fail (CAMEL_IS_STREAM (ostream), -1); if (flags & (CAMEL_MIME_FILTER_CANON_FROM | CAMEL_MIME_FILTER_CANON_STRIP)) cc_prepare_sign (part); filter = camel_stream_filter_new (ostream); canon = camel_mime_filter_canon_new (flags); camel_stream_filter_add (CAMEL_STREAM_FILTER (filter), canon); g_object_unref (canon); if (camel_data_wrapper_write_to_stream_sync ( CAMEL_DATA_WRAPPER (part), filter, cancellable, error) != -1 && camel_stream_flush (filter, cancellable, error) != -1) res = 0; g_object_unref (filter); /* Reset stream position to beginning. */ if (G_IS_SEEKABLE (ostream)) g_seekable_seek ( G_SEEKABLE (ostream), 0, G_SEEK_SET, NULL, NULL); return res; } /** * camel_cipher_can_load_photos: * * Returns: Whether ciphers can load photos, as being setup by the user. * * Since: 3.22 **/ gboolean camel_cipher_can_load_photos (void) { GSettings *settings; gboolean load_photos; settings = g_settings_new ("org.gnome.evolution-data-server"); load_photos = g_settings_get_boolean (settings, "camel-cipher-load-photos"); g_clear_object (&settings); return load_photos; }