/*
* gnome-keyring
*
* Copyright (C) 2011 Collabora Ltd.
*
* This program 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; either version 2.1 of
* the License, or (at your option) any later version.
*
* This program 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 program; if not, see <http://www.gnu.org/licenses/>.
*
* Author: Stef Walter <stefw@collabora.co.uk>
*/
#include "config.h"
#include "gcr-deprecated-base.h"
#include "gcr-importer.h"
#include "gcr-internal.h"
#include "gcr-gnupg-importer.h"
#include "gcr-parser.h"
#include "gcr-pkcs11-importer.h"
#include "gcr/gcr-marshal.h"
#include <glib/gi18n-lib.h>
/**
* SECTION:gcr-importer
* @title: GcrImporter
* @short_description: Import certificates and keys
*
* An interface which allows importing of certificates and keys. Each
* #GcrImporter is registered with a set of PKCS\#11 attributes to match
* stuff that it can import.
*
* An importer gets passed a #GcrParser and accesses the currently parsed
* item. To create a set of importers that can import the currently parsed
* item in a #GcrParser, use gcr_importer_create_for_parsed(). The list of
* importers returned has the parsed item queued for import.
*
* To queue additional items with a importer use gcr_importer_queue_for_parsed().
* In addition you can try and queue an additional item with a set of importers
* using the gcr_importer_queue_and_filter_for_parsed().
*
* To start the import use gcr_importer_import() or the async variants.
*/
/**
* GcrImporter:
*
* Imports certificates and keys
*/
/**
* GcrImporterIface:
* @parent: parent interface
* @create_for_parsed: implementation of gcr_importer_create_for_parsed(), required
* @queue_for_parsed: implementation of gcr_importer_queue_for_parsed(), required
* @import_sync: optional implemantionon of gcr_importer_import()
* @import_async: implementation of gcr_importer_import_async(), required
* @import_finish: implementation of gcr_importer_import_finish()
*
* Interface implemented for a #GcrImporter.
*/
typedef GcrImporterIface GcrImporterInterface;
G_DEFINE_INTERFACE (GcrImporter, gcr_importer, G_TYPE_OBJECT);
typedef struct _GcrRegistered {
GckAttributes *attrs;
GType importer_type;
} GcrRegistered;
static GArray *registered_importers = NULL;
static gboolean registered_sorted = FALSE;
static void
gcr_importer_default_init (GcrImporterIface *iface)
{
static volatile gsize initialized = 0;
if (g_once_init_enter (&initialized)) {
/**
* GcrImporter:label:
*
* The label for the importer.
*/
g_object_interface_install_property (iface,
g_param_spec_string ("label", "Label", "The label for the importer",
"", G_PARAM_READABLE));
/**
* GcrImporter:icon:
*
* The icon for the importer.
*/
g_object_interface_install_property (iface,
g_param_spec_object ("icon", "Icon", "The icon for the importer",
G_TYPE_ICON, G_PARAM_READABLE));
/**
* GcrImporter:interaction:
*
* The interaction for the importer.
*/
g_object_interface_install_property (iface,
g_param_spec_object ("interaction", "Interaction",
"Interaction for prompts",
G_TYPE_TLS_INTERACTION, G_PARAM_READWRITE));
/**
* GcrImporter:uri:
*
* The URI of the location imported to.
*/
g_object_interface_install_property (iface,
g_param_spec_string ("uri", "URI", "URI of location",
NULL, G_PARAM_READABLE));
g_once_init_leave (&initialized, 1);
}
}
/**
* gcr_importer_register:
* @importer_type: the GType of the importer being registered
* @attrs: the attributes that this importer is compatible with
*
* Register an importer to handle parsed items that match the given attributes.
*
* If @attrs are a floating reference, then it is consumed.
*/
void
gcr_importer_register (GType importer_type,
GckAttributes *attrs)
{
GcrRegistered registered;
if (!registered_importers)
registered_importers = g_array_new (FALSE, FALSE, sizeof (GcrRegistered));
registered.importer_type = importer_type;
registered.attrs = gck_attributes_ref_sink (attrs);
g_array_append_val (registered_importers, registered);
registered_sorted = FALSE;
}
static gint
sort_registered_by_n_attrs (gconstpointer a, gconstpointer b)
{
const GcrRegistered *ra = a;
const GcrRegistered *rb = b;
gulong na, nb;
g_assert (a);
g_assert (b);
na = gck_attributes_count (ra->attrs);
nb = gck_attributes_count (rb->attrs);
/* Note we're sorting in reverse order */
if (na < nb)
return 1;
return (na == nb) ? 0 : -1;
}
static gboolean
check_if_seen_or_add (GHashTable *seen,
gpointer key)
{
if (g_hash_table_lookup (seen, key))
return TRUE;
g_hash_table_insert (seen, key, key);
return FALSE;
}
/**
* gcr_importer_create_for_parsed:
* @parsed: a parser with a parsed item to import
*
* Create a set of importers which can import this parsed item.
* The parsed item is represented by the state of the GcrParser at the
* time of calling this method.
*
* Returns: (element-type Gcr.Importer) (transfer full): a list of importers
* which can import the parsed item, which should be freed with
* g_object_unref(), or %NULL if no types of importers can be created
*/
GList *
gcr_importer_create_for_parsed (GcrParsed *parsed)
{
GcrRegistered *registered;
GcrImporterIface *iface;
gpointer instance_class;
GckAttributes *attrs;
gboolean matched;
gulong n_attrs;
GList *results = NULL;
GHashTable *seen;
gulong j;
gsize i;
g_return_val_if_fail (parsed != NULL, NULL);
gcr_importer_register_well_known ();
if (!registered_importers)
return NULL;
if (!registered_sorted) {
g_array_sort (registered_importers, sort_registered_by_n_attrs);
registered_sorted = TRUE;
}
attrs = gcr_parsed_get_attributes (parsed);
if (attrs != NULL)
gck_attributes_ref (attrs);
else
attrs = gck_attributes_new_empty (GCK_INVALID);
seen = g_hash_table_new (g_direct_hash, g_direct_equal);
gchar *a = gck_attributes_to_string (attrs);
g_debug ("looking for importer for: %s", a);
g_free (a);
for (i = 0; i < registered_importers->len; ++i) {
registered = &(g_array_index (registered_importers, GcrRegistered, i));
n_attrs = gck_attributes_count (registered->attrs);
matched = TRUE;
for (j = 0; j < n_attrs; ++j) {
if (!gck_attributes_contains (attrs, gck_attributes_at (registered->attrs, j))) {
matched = FALSE;
break;
}
}
gchar *a = gck_attributes_to_string (registered->attrs);
g_debug ("importer %s %s: %s", g_type_name (registered->importer_type),
matched ? "matched" : "didn't match", a);
g_free (a);
if (matched) {
if (check_if_seen_or_add (seen, GUINT_TO_POINTER (registered->importer_type)))
continue;
instance_class = g_type_class_ref (registered->importer_type);
iface = g_type_interface_peek (instance_class, GCR_TYPE_IMPORTER);
g_return_val_if_fail (iface != NULL, NULL);
g_return_val_if_fail (iface->create_for_parsed, NULL);
results = g_list_concat (results, (iface->create_for_parsed) (parsed));
g_type_class_unref (instance_class);
}
}
g_hash_table_unref (seen);
gck_attributes_unref (attrs);
return results;
}
/**
* gcr_importer_queue_for_parsed:
* @importer: an importer to add additional items to
* @parsed: a parsed item to import
*
* Queues an additional item to be imported. The parsed item is represented
* by the state of the #GcrParser at the time of calling this method.
*
* If the parsed item is incompatible with the importer, then this will
* fail and the item will not be queued.
*
* Returns: whether the item was queued or not
*/
gboolean
gcr_importer_queue_for_parsed (GcrImporter *importer,
GcrParsed *parsed)
{
GcrImporterIface *iface;
g_return_val_if_fail (GCR_IS_IMPORTER (importer), FALSE);
g_return_val_if_fail (parsed != NULL, FALSE);
iface = GCR_IMPORTER_GET_INTERFACE (importer);
g_return_val_if_fail (iface != NULL, FALSE);
g_return_val_if_fail (iface->queue_for_parsed != NULL, FALSE);
return (iface->queue_for_parsed) (importer, parsed);
}
/**
* gcr_importer_queue_and_filter_for_parsed:
* @importers: (element-type Gcr.Importer): a set of importers
* @parsed: a parsed item
*
* Queues an additional item to be imported in all compattible importers
* in the set. The parsed item is represented by the state of the #GcrParser
* at the time of calling this method.
*
* If the parsed item is incompatible with an importer, then that the item
* will not be queued on that importer.
*
* Returns: (transfer full) (element-type Gcr.Importer): a new set of importers
* that queued the item, which should be freed with gck_list_unref_free()
*/
GList *
gcr_importer_queue_and_filter_for_parsed (GList *importers,
GcrParsed *parsed)
{
GList *results = NULL;
GList *l;
for (l = importers; l != NULL; l = g_list_next (l)) {
if (gcr_importer_queue_for_parsed (l->data, parsed))
results = g_list_prepend (results, g_object_ref (l->data));
}
return g_list_reverse (results);
}
typedef struct {
gboolean complete;
GCond *cond;
GMutex *mutex;
GError *error;
GMainContext *context;
} ImportClosure;
static void
on_import_async_complete (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
ImportClosure *closure = user_data;
GError *error = NULL;
if (!gcr_importer_import_finish (GCR_IMPORTER (source), result, &error)) {
if (error == NULL) {
g_warning ("%s::import_finished returned false, but did not set error",
G_OBJECT_TYPE_NAME (source));
}
}
g_mutex_lock (closure->mutex);
closure->complete = TRUE;
closure->error = error;
g_cond_signal (closure->cond);
g_mutex_unlock (closure->mutex);
}
/**
* gcr_importer_import:
* @importer: the importer
* @cancellable: a #GCancellable, or %NULL
* @error: the location to place an error on failure, or %NULL
*
* Import the queued items in the importer. This call will block
* until the operation completes.
*
* Returns: whether the items were imported successfully or not
*/
gboolean
gcr_importer_import (GcrImporter *importer,
GCancellable *cancellable,
GError **error)
{
gboolean result;
ImportClosure *closure;
GcrImporterIface *iface;
g_return_val_if_fail (GCR_IS_IMPORTER (importer), FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
iface = GCR_IMPORTER_GET_INTERFACE (importer);
if (iface->import_sync)
return (iface->import_sync) (importer, cancellable, error);
g_return_val_if_fail (iface->import_async != NULL, FALSE);
g_return_val_if_fail (iface->import_finish != NULL, FALSE);
closure = g_new0 (ImportClosure, 1);
closure->cond = g_new (GCond, 1);
g_cond_init (closure->cond);
closure->mutex = g_new (GMutex, 1);
g_mutex_init (closure->mutex);
closure->context = g_main_context_get_thread_default ();
g_mutex_lock (closure->mutex);
(iface->import_async) (importer, cancellable, on_import_async_complete, closure);
/*
* Handle the case where we've been called from within the main context
* or in the case where the main context is not running. This approximates
* the behavior of a modal dialog.
*/
if (g_main_context_acquire (closure->context)) {
while (!closure->complete) {
g_mutex_unlock (closure->mutex);
g_main_context_iteration (closure->context, TRUE);
g_mutex_lock (closure->mutex);
}
g_main_context_release (closure->context);
/*
* Handle the case where we're in a different thread than the main
* context and a main loop is running.
*/
} else {
while (!closure->complete)
g_cond_wait (closure->cond, closure->mutex);
}
g_mutex_unlock (closure->mutex);
result = (closure->error == NULL);
if (closure->error)
g_propagate_error (error, closure->error);
g_cond_clear (closure->cond);
g_free (closure->cond);
g_mutex_clear (closure->mutex);
g_free (closure->mutex);
g_free (closure);
return result;
}
/**
* gcr_importer_import_async:
* @importer: the importer
* @cancellable: a #GCancellable, or %NULL
* @callback: called when the operation completes
* @user_data: data to be passed to the callback
*
* Import the queued items in the importer. This function returns immediately
* and completes asynchronously.
*/
void
gcr_importer_import_async (GcrImporter *importer,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GcrImporterIface *iface;
g_return_if_fail (GCR_IS_IMPORTER (importer));
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
iface = GCR_IMPORTER_GET_INTERFACE (importer);
g_return_if_fail (iface != NULL);
g_return_if_fail (iface->import_async != NULL);
return (iface->import_async) (importer, cancellable, callback, user_data);
}
/**
* gcr_importer_import_finish:
* @importer: the importer
* @result: an asynchronous result
* @error: the location to place an error on failure, or %NULL
*
* Complete an asynchronous operation to import queued items.
*
* Returns: whether the import succeeded or failed
*/
gboolean
gcr_importer_import_finish (GcrImporter *importer,
GAsyncResult *result,
GError **error)
{
GcrImporterIface *iface;
g_return_val_if_fail (GCR_IS_IMPORTER (importer), FALSE);
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
iface = GCR_IMPORTER_GET_INTERFACE (importer);
g_return_val_if_fail (iface != NULL, FALSE);
g_return_val_if_fail (iface->import_finish != NULL, FALSE);
return (iface->import_finish) (importer, result, error);
}
/**
* gcr_importer_get_interaction:
* @importer: the importer
*
* Get the interaction used to prompt the user when needed by this
* importer.
*
* Returns: (transfer none) (allow-none): the interaction or %NULL
*/
GTlsInteraction *
gcr_importer_get_interaction (GcrImporter *importer)
{
GTlsInteraction *interaction = NULL;
g_return_val_if_fail (GCR_IS_IMPORTER (importer), NULL);
g_object_get (importer, "interaction", &interaction, NULL);
if (interaction != NULL)
g_object_unref (interaction);
return interaction;
}
/**
* gcr_importer_set_interaction:
* @importer: the importer
* @interaction: the interaction used by the importer
*
* Set the interaction used to prompt the user when needed by this
* importer.
*/
void
gcr_importer_set_interaction (GcrImporter *importer,
GTlsInteraction *interaction)
{
g_return_if_fail (GCR_IS_IMPORTER (importer));
g_object_set (importer, "interaction", interaction, NULL);
}
/**
* gcr_importer_register_well_known:
*
* Register built-in PKCS\#11 and GnuPG importers.
*/
void
gcr_importer_register_well_known (void)
{
g_type_class_unref (g_type_class_ref (GCR_TYPE_PKCS11_IMPORTER));
g_type_class_unref (g_type_class_ref (GCR_TYPE_GNUPG_IMPORTER));
}
#ifndef GCR_DISABLE_DEPRECATED
/**
* gcr_importer_get_parser:
* @self: An importer
*
* Has no effect. Use gcr_importer_listen() instead.
*
* Returns: %NULL is always returned.
* Deprecated: Since 3.0.0
*/
GcrParser*
gcr_importer_get_parser (GcrImporter *self)
{
g_warning ("gcr_importer_get_parser() is no longer supported "
"Use gcr_importer_listen() instead.");
return NULL;
}
/**
* gcr_importer_set_parser:
* @self: An importer
* @parser: A parser
*
* Has no effect. Use gcr_importer_listen() instead.
*
* Deprecated: Since 3.0.0
*/
void
gcr_importer_set_parser (GcrImporter *self,
GcrParser *parser)
{
g_warning ("gcr_importer_set_parser() is no longer supported "
"Use gcr_importer_listen() instead.");
}
/*
* gcr_importer_get_slot:
* @self: The importer
*
* Returns %NULL.
*
* Deprecated: since 3.4.0
*/
GckSlot *
gcr_importer_get_slot (GcrImporter *self)
{
g_warning ("gcr_importer_get_slot() is no longer supported.");
return NULL;
}
/**
* gcr_importer_set_slot:
* @self: The importer
* @slot: The slot to import to
*
* Has no effect.
*
* Deprecated: since 3.4.0
*/
void
gcr_importer_set_slot (GcrImporter *self,
GckSlot *slot)
{
g_warning ("gcr_importer_set_slot() is no longer supported.");
}
/**
* gcr_importer_get_prompt_behavior:
* @self: The importer
*
* Does nothing.
*
* Returns: zero
*
* Deprecated: since 3.4.0
*/
GcrImporterPromptBehavior
gcr_importer_get_prompt_behavior (GcrImporter *self)
{
g_warning ("gcr_importer_get_prompt_behavior() is no longer supported.");
return 0;
}
/**
* gcr_importer_set_prompt_behavior:
* @self: The importer
* @behavior: The prompt behavior flag
*
* Has no effect.
*
* Deprecated: since 3.4.0
*/
void
gcr_importer_set_prompt_behavior (GcrImporter *self,
GcrImporterPromptBehavior behavior)
{
g_warning ("gcr_importer_set_prompt_behavior() is no longer supported.");
}
#endif /* GCR_DISABLE_DEPRECATED */