/*
 * gnome-keyring
 *
 * Copyright (C) 2008 Stefan Walter
 * 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-fingerprint.h"
#include "gcr-icons.h"
#include "gcr-internal.h"
#include "gcr-library.h"
#include "gcr-import-interaction.h"
#include "gcr-internal.h"
#include "gcr-parser.h"
#include "gcr-pkcs11-importer.h"

#include "egg/egg-hex.h"

#include <gck/gck.h>

#include <gcrypt.h>

#include <glib/gi18n-lib.h>

enum {
	PROP_0,
	PROP_LABEL,
	PROP_ICON,
	PROP_INTERACTION,
	PROP_SLOT,
	PROP_IMPORTED,
	PROP_QUEUED,
	PROP_URI
};

typedef struct _GcrPkcs11ImporterClass GcrPkcs11ImporterClass;

struct _GcrPkcs11Importer {
	GObject parent;
	GckSlot *slot;
	GList *objects;
	GckSession *session;
	GQueue *queue;
	GTlsInteraction *interaction;
	gboolean any_private;
};

struct _GcrPkcs11ImporterClass {
	GObjectClass parent_class;
};

typedef struct  {
	GcrPkcs11Importer *importer;
	GCancellable *cancellable;
	gboolean prompted;
	gboolean async;
	GckBuilder *supplement;
} GcrImporterData;

/* State forward declarations */
static void   state_cancelled                  (GSimpleAsyncResult *res,
                                                gboolean async);

static void   state_complete                   (GSimpleAsyncResult *res,
                                                gboolean async);

static void   state_create_object              (GSimpleAsyncResult *res,
                                                gboolean async);

static void   state_supplement                 (GSimpleAsyncResult *res,
                                                gboolean async);

static void   state_open_session               (GSimpleAsyncResult *res,
                                                gboolean async);

static void   _gcr_pkcs11_importer_init_iface  (GcrImporterIface *iface);

G_DEFINE_TYPE_WITH_CODE (GcrPkcs11Importer, _gcr_pkcs11_importer, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (GCR_TYPE_IMPORTER, _gcr_pkcs11_importer_init_iface);
);

#define BLOCK 4096

static void
gcr_importer_data_free (gpointer data)
{
	GcrImporterData *state = data;

	g_clear_object (&state->cancellable);
	g_clear_object (&state->importer);
	gck_builder_unref (state->supplement);
	g_free (state);
}

static void
next_state (GSimpleAsyncResult *res,
            void (*state) (GSimpleAsyncResult *, gboolean))
{
	GcrImporterData *data = g_simple_async_result_get_op_res_gpointer (res);

	g_assert (state);

	if (g_cancellable_is_cancelled (data->cancellable))
		state = state_cancelled;

	(state) (res, data->async);
}

/* ---------------------------------------------------------------------------------
 * COMPLETE
 */

static void
state_complete (GSimpleAsyncResult *res,
                gboolean async)
{
	g_simple_async_result_complete (res);
}

static void
state_cancelled (GSimpleAsyncResult *res,
                 gboolean async)
{
	GcrImporterData *data = g_simple_async_result_get_op_res_gpointer (res);
	GError *error = NULL;

	if (data->cancellable && !g_cancellable_is_cancelled (data->cancellable))
		g_cancellable_cancel (data->cancellable);

	g_cancellable_set_error_if_cancelled (data->cancellable, &error);
	g_simple_async_result_take_error (res, error);
	next_state (res, state_complete);
}

/* ---------------------------------------------------------------------------------
 * CREATE OBJECTS
 */

static void
complete_create_object (GSimpleAsyncResult *res,
                        GckObject *object,
                        GError *error)
{
	GcrImporterData *data = g_simple_async_result_get_op_res_gpointer (res);
	GcrPkcs11Importer *self = data->importer;

	if (object == NULL) {
		g_simple_async_result_take_error (res, error);
		next_state (res, state_complete);

	} else {
		self->objects = g_list_append (self->objects, object);
		next_state (res, state_create_object);
	}
}

static void
on_create_object (GObject *source,
                  GAsyncResult *result,
                  gpointer user_data)
{
	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
	GError *error = NULL;
	GckObject *object;

	object = gck_session_create_object_finish (GCK_SESSION (source), result, &error);
	complete_create_object (res, object, error);
	g_object_unref (res);
}

static void
state_create_object (GSimpleAsyncResult *res,
                     gboolean async)
{
	GcrImporterData *data = g_simple_async_result_get_op_res_gpointer (res);
	GcrPkcs11Importer *self = data->importer;
	GckAttributes *attrs;
	GckObject *object;
	GError *error = NULL;

	/* No more objects */
	if (g_queue_is_empty (self->queue)) {
		next_state (res, state_complete);

	} else {

		/* Pop first one off the list */
		attrs = g_queue_pop_head (self->queue);
		g_assert (attrs != NULL);

		if (async) {
			gck_session_create_object_async (self->session, attrs,
			                                 data->cancellable, on_create_object,
			                                 g_object_ref (res));
		} else {
			object = gck_session_create_object (self->session, attrs,
			                                    data->cancellable, &error);
			complete_create_object (res, object, error);
		}

		gck_attributes_unref (attrs);
	}
}

/* ---------------------------------------------------------------------------------
 * SUPPLEMENTING and FIXING UP
 */

typedef struct {
	GckAttributes *certificate;
	GckAttributes *private_key;
} CertificateKeyPair;

static void
supplement_with_attributes (GckBuilder *builder,
                            GckAttributes *supplements)
{
	const GckAttribute *supplement;
	gint i;

	for (i = 0; i < gck_attributes_count (supplements); i++) {
		supplement = gck_attributes_at (supplements, i);
		if (!gck_attribute_is_invalid (supplement) && supplement->length != 0)
			gck_builder_add_attribute (builder, supplement);
	}
}

static void
supplement_id_for_data (GckBuilder *builder,
                        guchar *nonce,
                        gsize n_once,
                        gpointer data,
                        gsize n_data)
{
	gcry_md_hd_t mdh;
	gcry_error_t gcry;

	if (gck_builder_find (builder, CKA_ID) != NULL)
		return;

	gcry = gcry_md_open (&mdh, GCRY_MD_SHA1, 0);
	g_return_if_fail (gcry == 0);

	gcry_md_write (mdh, nonce, n_once);
	gcry_md_write (mdh, data, n_data);

	gck_builder_add_data (builder, CKA_ID,
	                      gcry_md_read (mdh, 0),
	                      gcry_md_get_algo_dlen (GCRY_MD_SHA1));

	gcry_md_close (mdh);
}

static void
supplement_attributes (GcrPkcs11Importer *self,
                       GckAttributes *supplements)
{
	GckBuilder builder = GCK_BUILDER_INIT;
	GHashTable *pairs;
	GHashTable *paired;
	CertificateKeyPair *pair;
	gboolean supplemented = FALSE;
	GckAttributes *attrs;
	gulong klass;
	guchar *finger;
	gchar *fingerprint;
	guchar nonce[20];
	GHashTableIter iter;
	gsize n_finger;
	GQueue *queue;
	GList *l;

	/* A table of certificate/key pairs by fingerprint */
	pairs = g_hash_table_new_full (g_str_hash, g_str_equal,
	                               g_free, g_free);

	for (l = self->queue->head; l != NULL; l = g_list_next (l)) {
		attrs = l->data;
		if (!gck_attributes_find_ulong (attrs, CKA_CLASS, &klass))
			g_return_if_reached ();

		/* Make a string fingerprint for this guy */
		finger = gcr_fingerprint_from_attributes (attrs, G_CHECKSUM_SHA1,
		                                          &n_finger);
		if (finger) {
			fingerprint = egg_hex_encode (finger, n_finger);
			g_free (finger);

			pair = g_hash_table_lookup (pairs, fingerprint);
			if (pair == NULL) {
				pair = g_new0 (CertificateKeyPair, 1);
				g_hash_table_insert (pairs, fingerprint, pair);
			} else {
				g_free (fingerprint);
			}
		} else {
			pair = NULL;
		}

		fingerprint = NULL;
		gck_builder_add_all (&builder, attrs);
		gck_builder_set_boolean (&builder, CKA_TOKEN, CK_TRUE);

		switch (klass) {
		case CKO_CERTIFICATE:
			gck_builder_set_boolean (&builder, CKA_PRIVATE, FALSE);
			break;
		case CKO_PRIVATE_KEY:
			gck_builder_set_boolean (&builder, CKA_PRIVATE, TRUE);
			gck_builder_add_boolean (&builder, CKA_DECRYPT, TRUE);
			gck_builder_add_boolean (&builder, CKA_SIGN, TRUE);
			gck_builder_add_boolean (&builder, CKA_SIGN_RECOVER, TRUE);
			gck_builder_add_boolean (&builder, CKA_UNWRAP, TRUE);
			gck_builder_add_boolean (&builder, CKA_SENSITIVE, TRUE);
			break;
		}

		gck_attributes_unref (attrs);
		l->data = attrs = gck_attributes_ref_sink (gck_builder_end (&builder));

		switch (klass) {
		case CKO_CERTIFICATE:
			if (pair != NULL && pair->certificate == NULL)
				pair->certificate = attrs;
			break;
		case CKO_PRIVATE_KEY:
			if (pair != NULL && pair->private_key == NULL)
				pair->private_key = attrs;
			break;
		}
	}

	/* For generation of CKA_ID's */
	gcry_create_nonce (nonce, sizeof (nonce));

	/* A table for marking which attributes are in the pairs table */
	paired = g_hash_table_new (g_direct_hash, g_direct_equal);

	/* Now move everything in pairs to the front */
	queue = g_queue_new ();
	g_hash_table_iter_init (&iter, pairs);
	while (g_hash_table_iter_next (&iter, (gpointer *)&fingerprint, (gpointer *)&pair)) {
		if (pair->certificate != NULL && pair->private_key != NULL) {
			/*
			 * Generate a CKA_ID based on the fingerprint and nonce,
			 * and do the same CKA_ID for both private key and certificate.
			 */

			gck_builder_add_all (&builder, pair->private_key);
			supplement_with_attributes (&builder, supplements);
			supplement_id_for_data (&builder, nonce, sizeof (nonce),
			                        fingerprint, strlen (fingerprint));
			g_queue_push_tail (queue, gck_attributes_ref_sink (gck_builder_end (&builder)));
			g_hash_table_insert (paired, pair->private_key, "present");

			gck_builder_add_all (&builder, pair->certificate);
			supplement_with_attributes (&builder, supplements);
			supplement_id_for_data (&builder, nonce, sizeof (nonce),
			                        fingerprint, strlen (fingerprint));
			g_queue_push_tail (queue, gck_attributes_ref_sink (gck_builder_end (&builder)));
			g_hash_table_insert (paired, pair->certificate, "present");

			/* Used the suplements for the pairs, don't use for unpaired stuff */
			supplemented = TRUE;
		}
	}

	/* Go through the old queue, and look for anything not paired */
	for (l = self->queue->head; l != NULL; l = g_list_next (l)) {
		attrs = l->data;
		if (!g_hash_table_lookup (paired, attrs)) {
			gck_builder_add_all (&builder, attrs);
			if (!supplemented)
				supplement_with_attributes (&builder, supplements);

			/*
			 * Generate a CKA_ID based on the location of attrs in,
			 * memory, since this together with the nonce should
			 * be unique.
			 */
			supplement_id_for_data (&builder, nonce, sizeof (nonce),
			                        &attrs, sizeof (gpointer));

			g_queue_push_tail (queue, gck_attributes_ref_sink (gck_builder_end (&builder)));
		}
	}

	/* And swap the new queue into place */
	g_queue_foreach (self->queue, (GFunc)gck_attributes_unref, NULL);
	g_queue_free (self->queue);
	self->queue = queue;

	g_hash_table_destroy (paired);
	g_hash_table_destroy (pairs);
}

static void
complete_supplement (GSimpleAsyncResult *res,
                     GError *error)
{
	GcrImporterData *data = g_simple_async_result_get_op_res_gpointer (res);
	GckAttributes *attributes;

	if (error == NULL) {
		attributes = gck_attributes_ref_sink (gck_builder_end (data->supplement));
		supplement_attributes (data->importer, attributes);
		gck_attributes_unref (attributes);

		next_state (res, state_create_object);
	} else {
		g_simple_async_result_take_error (res, error);
		next_state (res, state_complete);
	}
}

static void
on_supplement_done (GObject *source,
                    GAsyncResult *result,
                    gpointer user_data)
{
	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
	GcrImporterData *data = g_simple_async_result_get_op_res_gpointer (res);
	GcrPkcs11Importer *self = data->importer;
	GError *error = NULL;

	gcr_import_interaction_supplement_finish (GCR_IMPORT_INTERACTION (self->interaction),
	                                          result, &error);
	complete_supplement (res, error);
	g_object_unref (res);
}

static void
state_supplement (GSimpleAsyncResult *res,
                  gboolean async)
{
	GcrImporterData *data = g_simple_async_result_get_op_res_gpointer (res);
	GcrPkcs11Importer *self = data->importer;
	GError *error = NULL;

	if (self->interaction == NULL || !GCR_IS_IMPORT_INTERACTION (self->interaction)) {
		complete_supplement (res, NULL);

	} else if (async) {
		gcr_import_interaction_supplement_async (GCR_IMPORT_INTERACTION (self->interaction),
		                                         data->supplement, data->cancellable,
		                                         on_supplement_done, g_object_ref (res));

	} else {
		gcr_import_interaction_supplement (GCR_IMPORT_INTERACTION (self->interaction),
		                                   data->supplement, data->cancellable, &error);
		complete_supplement (res, error);
	}
}

static void
supplement_prep (GSimpleAsyncResult *res)
{
	GcrImporterData *data = g_simple_async_result_get_op_res_gpointer (res);
	GcrPkcs11Importer *self = data->importer;
	const GckAttribute *the_label = NULL;
	const GckAttribute *attr;
	gboolean first = TRUE;
	GList *l;

	if (data->supplement)
		gck_builder_unref (data->supplement);
	data->supplement = gck_builder_new (GCK_BUILDER_NONE);

	/* Do we have a consistent label across all objects? */
	for (l = self->queue->head; l != NULL; l = g_list_next (l)) {
		attr = gck_attributes_find (l->data, CKA_LABEL);
		if (first)
			the_label = attr;
		else if (!gck_attribute_equal (the_label, attr))
			the_label = NULL;
		first = FALSE;
	}

	/* If consistent label, set that in supplement data */
	if (the_label != NULL)
		gck_builder_add_data (data->supplement, CKA_LABEL, the_label->value, the_label->length);
	else
		gck_builder_add_empty (data->supplement, CKA_LABEL);

	if (GCR_IS_IMPORT_INTERACTION (self->interaction))
		gcr_import_interaction_supplement_prep (GCR_IMPORT_INTERACTION (self->interaction),
		                                        data->supplement);
}

/* ---------------------------------------------------------------------------------
 * OPEN SESSION
 */

static void
complete_open_session (GSimpleAsyncResult *res,
                       GckSession *session,
                       GError *error)
{
	GcrImporterData *data = g_simple_async_result_get_op_res_gpointer (res);
	GcrPkcs11Importer *self = data->importer;

	if (!session) {
		g_simple_async_result_take_error (res, error);
		next_state (res, state_complete);

	} else {
		g_clear_object (&self->session);
		self->session = session;
		next_state (res, state_supplement);
	}
}

static void
on_open_session (GObject *source,
                 GAsyncResult *result,
                 gpointer user_data)
{
	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
	GError *error = NULL;
	GckSession *session;

	session = gck_session_open_finish (result, &error);
	complete_open_session (res, session, error);
	g_object_unref (res);
}

static void
state_open_session (GSimpleAsyncResult *res,
                    gboolean async)
{
	GcrImporterData *data = g_simple_async_result_get_op_res_gpointer (res);
	GcrPkcs11Importer *self = data->importer;
	guint options = GCK_SESSION_READ_WRITE | GCK_SESSION_LOGIN_USER;
	GckSession *session;
	GError *error = NULL;

	if (async) {
		gck_session_open_async (self->slot, options, self->interaction,
		                        data->cancellable, on_open_session, g_object_ref (res));
	} else {
		session = gck_session_open (self->slot, options, self->interaction,
		                            data->cancellable, &error);
		complete_open_session (res, session, error);
	}
}

static void
_gcr_pkcs11_importer_init (GcrPkcs11Importer *self)
{
	self->queue = g_queue_new ();
}

static void
_gcr_pkcs11_importer_dispose (GObject *obj)
{
	GcrPkcs11Importer *self = GCR_PKCS11_IMPORTER (obj);

	gck_list_unref_free (self->objects);
	self->objects = NULL;
	g_clear_object (&self->session);
	g_clear_object (&self->interaction);

	while (!g_queue_is_empty (self->queue))
		gck_attributes_unref (g_queue_pop_head (self->queue));

	G_OBJECT_CLASS (_gcr_pkcs11_importer_parent_class)->dispose (obj);
}

static void
_gcr_pkcs11_importer_finalize (GObject *obj)
{
	GcrPkcs11Importer *self = GCR_PKCS11_IMPORTER (obj);

	g_queue_free (self->queue);
	g_clear_object (&self->slot);

	G_OBJECT_CLASS (_gcr_pkcs11_importer_parent_class)->finalize (obj);
}

static void
_gcr_pkcs11_importer_set_property (GObject *obj,
                                   guint prop_id,
                                   const GValue *value,
                                   GParamSpec *pspec)
{
	GcrPkcs11Importer *self = GCR_PKCS11_IMPORTER (obj);

	switch (prop_id) {
	case PROP_SLOT:
		self->slot = g_value_dup_object (value);
		g_return_if_fail (self->slot);
		break;
	case PROP_INTERACTION:
		g_clear_object (&self->interaction);
		self->interaction = g_value_dup_object (value);
		g_object_notify (G_OBJECT (self), "interaction");
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
		break;
	}
}

static gchar *
calculate_label (GcrPkcs11Importer *self)
{
	GckTokenInfo *info;
	gchar *result;

	info = gck_slot_get_token_info (self->slot);
	result = g_strdup (info->label);
	gck_token_info_free (info);

	return result;
}

static GIcon *
calculate_icon (GcrPkcs11Importer *self,
                GckTokenInfo *token_info)
{
	GckTokenInfo *info = NULL;
	GIcon *result;

	if (token_info == NULL)
		info = token_info = gck_slot_get_token_info (self->slot);
	result = gcr_icon_for_token (token_info);
	gck_token_info_free (info);

	return result;
}

static gchar *
calculate_uri (GcrPkcs11Importer *self)
{
	GckUriData *data;
	gchar *uri;

	data = gck_uri_data_new ();
	data->token_info = gck_slot_get_token_info (self->slot);
	uri = gck_uri_build (data, GCK_URI_FOR_TOKEN);
	data->token_info = NULL;
	gck_uri_data_free (data);

	return uri;
}

static void
_gcr_pkcs11_importer_get_property (GObject *obj,
                                   guint prop_id,
                                   GValue *value,
                                   GParamSpec *pspec)
{
	GcrPkcs11Importer *self = GCR_PKCS11_IMPORTER (obj);

	switch (prop_id) {
	case PROP_LABEL:
		g_value_take_string (value, calculate_label (self));
		break;
	case PROP_ICON:
		g_value_take_object (value, calculate_icon (self, NULL));
		break;
	case PROP_SLOT:
		g_value_set_object (value, _gcr_pkcs11_importer_get_slot (self));
		break;
	case PROP_IMPORTED:
		g_value_take_boxed (value, _gcr_pkcs11_importer_get_imported (self));
		break;
	case PROP_QUEUED:
		g_value_set_pointer (value, _gcr_pkcs11_importer_get_queued (self));
		break;
	case PROP_INTERACTION:
		g_value_set_object (value, self->interaction);
		break;
	case PROP_URI:
		g_value_take_string (value, calculate_uri (self));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
		break;
	}
}

static void
_gcr_pkcs11_importer_class_init (GcrPkcs11ImporterClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
	GckBuilder builder = GCK_BUILDER_INIT;

	gobject_class->dispose = _gcr_pkcs11_importer_dispose;
	gobject_class->finalize = _gcr_pkcs11_importer_finalize;
	gobject_class->set_property = _gcr_pkcs11_importer_set_property;
	gobject_class->get_property = _gcr_pkcs11_importer_get_property;

	g_object_class_override_property (gobject_class, PROP_LABEL, "label");
	g_object_class_override_property (gobject_class, PROP_ICON, "icon");
	g_object_class_override_property (gobject_class, PROP_INTERACTION, "interaction");
	g_object_class_override_property (gobject_class, PROP_URI, "uri");

	g_object_class_install_property (gobject_class, PROP_SLOT,
	           g_param_spec_object ("slot", "Slot", "PKCS#11 slot to import data into",
	                                GCK_TYPE_SLOT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

	g_object_class_install_property (gobject_class, PROP_IMPORTED,
	           g_param_spec_boxed ("imported", "Imported", "Imported objects",
	                               GCK_TYPE_LIST, G_PARAM_READABLE));

	g_object_class_install_property (gobject_class, PROP_QUEUED,
	           g_param_spec_pointer ("queued", "Queued", "Queued attributes",
	                                 G_PARAM_READABLE));

	gck_builder_add_ulong (&builder, CKA_CLASS, CKO_CERTIFICATE);
	gck_builder_add_ulong (&builder, CKA_CERTIFICATE_TYPE, CKC_X_509);
	gcr_importer_register (GCR_TYPE_PKCS11_IMPORTER, gck_builder_end (&builder));

	gck_builder_add_ulong (&builder, CKA_CLASS, CKO_PRIVATE_KEY);
	gcr_importer_register (GCR_TYPE_PKCS11_IMPORTER, gck_builder_end (&builder));

	_gcr_initialize_library ();
}

static GList *
list_all_slots (void)
{
	GList *modules;
	GList *results;

	modules = gcr_pkcs11_get_modules ();
	results = gck_modules_get_slots (modules, TRUE);
	gck_list_unref_free (modules);

	return results;
}

static const char *token_blacklist[] = {
	"pkcs11:manufacturer=Gnome%20Keyring;serial=1:SECRET:MAIN",
	"pkcs11:manufacturer=Gnome%20Keyring;serial=1%3aXDG%3aDEFAULT",
	NULL
};

static gboolean
is_slot_importable (GckSlot *slot,
                    GckTokenInfo *token)
{
	GError *error = NULL;
	GckUriData *uri;
	gboolean match;
	guint i;

	if (token->flags & CKF_WRITE_PROTECTED) {
		g_debug ("token is not importable: %s: write protected", token->label);
		return FALSE;
	}
	if (!(token->flags & CKF_TOKEN_INITIALIZED)) {
		g_debug ("token is not importable: %s: not initialized", token->label);
		return FALSE;
	}
	if ((token->flags & CKF_LOGIN_REQUIRED) &&
	    !(token->flags & CKF_USER_PIN_INITIALIZED)) {
		g_debug ("token is not importable: %s: user pin not initialized", token->label);
		return FALSE;
	}

	for (i = 0; token_blacklist[i] != NULL; i++) {
		uri = gck_uri_parse (token_blacklist[i], GCK_URI_FOR_TOKEN | GCK_URI_FOR_MODULE, &error);
		if (uri == NULL) {
			g_warning ("couldn't parse pkcs11 blacklist uri: %s", error->message);
			g_clear_error (&error);
			continue;
		}

		match = gck_slot_match (slot, uri);
		gck_uri_data_free (uri);

		if (match) {
			g_debug ("token is not importable: %s: on the black list", token->label);
			return FALSE;
		}
	}

	return TRUE;
}

static GList *
_gcr_pkcs11_importer_create_for_parsed (GcrParsed *parsed)
{
	GcrImporter *self;
	GList *slots, *l;
	GList *results = NULL;
	GckTokenInfo *token_info;
	gboolean importable;

	slots = list_all_slots ();
	for (l = slots; l != NULL; l = g_list_next (l)) {
		token_info = gck_slot_get_token_info (l->data);
		importable = is_slot_importable (l->data, token_info);

		if (importable) {
			g_debug ("creating importer for token: %s", token_info->label);
			self = _gcr_pkcs11_importer_new (l->data);
			if (!gcr_importer_queue_for_parsed (self, parsed))
				g_assert_not_reached ();
			results = g_list_prepend (results, self);
		}

		gck_token_info_free (token_info);
	}
	gck_list_unref_free (slots);

	return g_list_reverse (results);
}

static gboolean
_gcr_pkcs11_importer_queue_for_parsed (GcrImporter *importer,
                                       GcrParsed *parsed)
{
	GcrPkcs11Importer *self = GCR_PKCS11_IMPORTER (importer);
	GckAttributes *attrs;
	const gchar *label;

	attrs = gcr_parsed_get_attributes (parsed);
	label = gcr_parsed_get_label (parsed);
	_gcr_pkcs11_importer_queue (self, label, attrs);

	return TRUE;
}

static void
_gcr_pkcs11_importer_import_async (GcrImporter *importer,
                                   GCancellable *cancellable,
                                   GAsyncReadyCallback callback,
                                   gpointer user_data)
{
	GSimpleAsyncResult *res;
	GcrImporterData *data;

	res = g_simple_async_result_new (G_OBJECT (importer), callback, user_data,
	                                 _gcr_pkcs11_importer_import_async);
	data = g_new0 (GcrImporterData, 1);
	data->async = TRUE;
	data->importer = g_object_ref (importer);
	data->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
	g_simple_async_result_set_op_res_gpointer (res, data, gcr_importer_data_free);

	supplement_prep (res);

	next_state (res, state_open_session);
	g_object_unref (res);
}

static gboolean
_gcr_pkcs11_importer_import_finish (GcrImporter *importer,
                                    GAsyncResult *result,
                                    GError **error)
{
	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (importer),
	                      _gcr_pkcs11_importer_import_async), FALSE);

	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
		return FALSE;

	return TRUE;
}

static void
_gcr_pkcs11_importer_init_iface (GcrImporterIface *iface)
{
	iface->create_for_parsed = _gcr_pkcs11_importer_create_for_parsed;
	iface->queue_for_parsed = _gcr_pkcs11_importer_queue_for_parsed;
	iface->import_async = _gcr_pkcs11_importer_import_async;
	iface->import_finish = _gcr_pkcs11_importer_import_finish;
}

/**
 * _gcr_pkcs11_importer_new:
 *
 * Create a new #GcrPkcs11Importer.
 *
 * Returns: (transfer full) (type Gcr.Pkcs11Importer): the new importer
 */
GcrImporter *
_gcr_pkcs11_importer_new (GckSlot *slot)
{
	g_return_val_if_fail (GCK_IS_SLOT (slot), NULL);

	return g_object_new (GCR_TYPE_PKCS11_IMPORTER,
	                     "slot", slot,
	                     NULL);
}

GckSlot *
_gcr_pkcs11_importer_get_slot (GcrPkcs11Importer *self)
{
	g_return_val_if_fail (GCR_IS_PKCS11_IMPORTER (self), NULL);
	return self->slot;
}

GList *
_gcr_pkcs11_importer_get_imported (GcrPkcs11Importer *self)
{
	g_return_val_if_fail (GCR_IS_PKCS11_IMPORTER (self), NULL);
	return g_list_copy (self->objects);
}

GList *
_gcr_pkcs11_importer_get_queued (GcrPkcs11Importer *self)
{
	g_return_val_if_fail (GCR_IS_PKCS11_IMPORTER (self), NULL);
	return g_list_copy (self->queue->head);
}

void
_gcr_pkcs11_importer_queue (GcrPkcs11Importer *self,
                            const gchar *label,
                            GckAttributes *attrs)
{
	GckBuilder builder = GCK_BUILDER_INIT;

	g_return_if_fail (GCR_IS_PKCS11_IMPORTER (self));
	g_return_if_fail (attrs != NULL);

	if (label != NULL && !gck_attributes_find (attrs, CKA_LABEL)) {
		gck_builder_add_all (&builder, attrs);
		gck_builder_add_string (&builder, CKA_LABEL, label);
		attrs = gck_builder_end (&builder);
	}

	g_queue_push_tail (self->queue, gck_attributes_ref_sink (attrs));
}
