/*
* gnome-keyring
*
* Copyright (C) 2009 Stefan Walter
*
* 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
* .
*/
#include "config.h"
#include "gkm-secret-collection.h"
#include "gkm-secret-fields.h"
#include "gkm-secret-item.h"
#include "gkm-secret-search.h"
#include "gkm/gkm-attributes.h"
#include "gkm/gkm-manager.h"
#include "gkm/gkm-module.h"
#include "gkm/gkm-session.h"
#include "gkm/gkm-transaction.h"
#include "gkm/gkm-util.h"
#include "pkcs11i.h"
#include
enum {
PROP_0,
PROP_COLLECTION_ID,
PROP_FIELDS,
PROP_SCHEMA_NAME
};
struct _GkmSecretSearch {
GkmObject parent;
gchar *collection_id;
GHashTable *fields;
gchar *schema_name;
GList *managers;
GHashTable *objects;
};
G_DEFINE_TYPE (GkmSecretSearch, gkm_secret_search, GKM_TYPE_OBJECT);
static gboolean
match_object_against_criteria (GkmSecretSearch *self, GkmObject *object)
{
GkmSecretCollection *collection;
GkmSecretItem *item;
GHashTable *fields;
const gchar *identifier;
const gchar *schema;
if (!GKM_IS_SECRET_ITEM (object))
return FALSE;
item = GKM_SECRET_ITEM (object);
/* Collection should match unless any collection allowed */
if (self->collection_id) {
collection = gkm_secret_item_get_collection (item);
g_return_val_if_fail (collection, FALSE);
identifier = gkm_secret_object_get_identifier (GKM_SECRET_OBJECT (collection));
g_return_val_if_fail (identifier, FALSE);
if (!g_str_equal (identifier, self->collection_id))
return FALSE;
}
fields = gkm_secret_item_get_fields (item);
/* Match the schema, if we have one */
if (self->schema_name) {
schema = gkm_secret_item_get_schema (item);
/* Does the item has a schema set from the item type? */
if (schema != NULL) {
if (!g_str_equal (schema, self->schema_name))
return FALSE;
/* See if the item has a schema set in the attributes */
} else {
if (!gkm_secret_fields_match_one (fields, GKM_SECRET_FIELD_SCHEMA, self->schema_name))
return FALSE;
}
}
/* Fields should match using our special algorithm */
return gkm_secret_fields_match (fields, self->fields);
}
static void
on_manager_added_object (GkmManager *manager, GkmObject *object, gpointer user_data)
{
GkmSecretSearch *self = user_data;
g_return_if_fail (GKM_IS_SECRET_SEARCH (self));
g_return_if_fail (g_hash_table_lookup (self->objects, object) == NULL);
if (match_object_against_criteria (self, object)) {
g_hash_table_replace (self->objects, g_object_ref (object), "unused");
gkm_object_notify_attribute (GKM_OBJECT (self), CKA_G_MATCHED);
}
}
static void
on_manager_removed_object (GkmManager *manager, GkmObject *object, gpointer user_data)
{
GkmSecretSearch *self = user_data;
g_return_if_fail (GKM_IS_SECRET_SEARCH (self));
if (g_hash_table_remove (self->objects, object))
gkm_object_notify_attribute (GKM_OBJECT (self), CKA_G_MATCHED);
}
static void
on_manager_changed_object (GkmManager *manager, GkmObject *object,
CK_ATTRIBUTE_TYPE type, gpointer user_data)
{
GkmSecretSearch *self = user_data;
CK_OBJECT_HANDLE handle;
if (type != CKA_G_FIELDS)
return;
g_return_if_fail (GKM_IS_SECRET_SEARCH (self));
handle = gkm_object_get_handle (object);
g_return_if_fail (handle);
/* Should we have this object? */
if (match_object_against_criteria (self, object)) {
if (g_hash_table_lookup (self->objects, object) == NULL) {
g_hash_table_replace (self->objects, g_object_ref (object), "unused");
gkm_object_notify_attribute (GKM_OBJECT (self), CKA_G_MATCHED);
}
/* Should we not have this object? */
} else {
if (g_hash_table_remove (self->objects, object))
gkm_object_notify_attribute (GKM_OBJECT (self), CKA_G_MATCHED);
}
}
static void
on_manager_gone_away (gpointer user_data, GObject *where_the_object_was)
{
GkmSecretSearch *self = GKM_SECRET_SEARCH (user_data);
GList *l;
g_return_if_fail (self);
l = g_list_find (self->managers, where_the_object_was);
g_return_if_fail (l != NULL);
self->managers = g_list_delete_link (self->managers, l);
}
static void
populate_search_from_manager (GkmSecretSearch *self, GkmSession *session, GkmManager *manager)
{
GList *objects, *o;
self->managers = g_list_append (self->managers, manager);
/* Add in all the objects */
objects = gkm_manager_find_by_class (manager, session, CKO_SECRET_KEY);
for (o = objects; o; o = g_list_next (o))
on_manager_added_object (manager, o->data, self);
g_list_free (objects);
/* Track this manager */
g_object_weak_ref (G_OBJECT (manager), on_manager_gone_away, self);
/* Watch for further events of objects */
g_signal_connect (manager, "object-added", G_CALLBACK (on_manager_added_object), self);
g_signal_connect (manager, "object-removed", G_CALLBACK (on_manager_removed_object), self);
g_signal_connect (manager, "attribute-changed", G_CALLBACK (on_manager_changed_object), self);
}
static GkmObject*
factory_create_search (GkmSession *session, GkmTransaction *transaction,
CK_ATTRIBUTE_PTR attrs, CK_ULONG n_attrs)
{
GkmManager *s_manager, *m_manager;
GkmSecretSearch *search;
gchar *identifier = NULL;
CK_ATTRIBUTE *attr;
GHashTable *fields;
gchar *schema_name;
GkmModule *module;
CK_RV rv;
g_return_val_if_fail (GKM_IS_TRANSACTION (transaction), NULL);
g_return_val_if_fail (attrs || !n_attrs, NULL);
/* Find the fields being requested */
attr = gkm_attributes_find (attrs, n_attrs, CKA_G_FIELDS);
if (attr == NULL) {
gkm_transaction_fail (transaction, CKR_TEMPLATE_INCOMPLETE);
return NULL;
}
/* Parse the fields, into our internal representation */
rv = gkm_secret_fields_parse (attr, &fields, &schema_name);
gkm_attribute_consume (attr);
if (rv != CKR_OK) {
gkm_transaction_fail (transaction, rv);
return NULL;
}
/* Remove the schema name from the search fields, handle that separately */
g_hash_table_remove (fields, GKM_SECRET_FIELD_SCHEMA);
s_manager = gkm_session_get_manager (session);
module = gkm_session_get_module (session);
m_manager = gkm_module_get_manager (module);
/* See if a collection attribute was specified, not present means all collections */
attr = gkm_attributes_find (attrs, n_attrs, CKA_G_COLLECTION);
if (attr) {
rv = gkm_attribute_get_string (attr, &identifier);
if (rv != CKR_OK) {
g_free (schema_name);
g_hash_table_unref (fields);
gkm_transaction_fail (transaction, rv);
return NULL;
}
}
search = g_object_new (GKM_TYPE_SECRET_SEARCH,
"module", module,
"manager", s_manager,
"fields", fields,
"schema-name", schema_name,
"collection-id", identifier,
NULL);
/* Load any new items or collections */
gkm_module_refresh_token (module);
populate_search_from_manager (search, session, s_manager);
populate_search_from_manager (search, session, m_manager);
gkm_session_complete_object_creation (session, transaction, GKM_OBJECT (search),
TRUE, attrs, n_attrs);
g_hash_table_unref (fields);
g_free (schema_name);
return GKM_OBJECT (search);
}
static gint
on_matched_sort_modified (gconstpointer a,
gconstpointer b)
{
glong modified_a;
glong modified_b;
/* Sorting in reverse order */
modified_a = gkm_secret_object_get_modified (GKM_SECRET_OBJECT (a));
modified_b = gkm_secret_object_get_modified (GKM_SECRET_OBJECT (b));
if (modified_a < modified_b)
return 1;
if (modified_a > modified_b)
return -1;
return 0;
}
static CK_RV
attribute_set_handles (GHashTable *objects,
CK_ATTRIBUTE_PTR attr)
{
GList *list, *l;
GArray *array;
gulong handle;
CK_RV rv;
g_assert (objects);
g_assert (attr);
/* Want the length */
if (!attr->pValue) {
attr->ulValueLen = sizeof (CK_OBJECT_HANDLE) * g_hash_table_size (objects);
return CKR_OK;
}
/* Get the actual values */
list = g_list_sort (g_hash_table_get_keys (objects), on_matched_sort_modified);
array = g_array_new (FALSE, TRUE, sizeof (CK_OBJECT_HANDLE));
for (l = list; l != NULL; l = g_list_next (l)) {
handle = gkm_object_get_handle (l->data);
g_array_append_val (array, handle);
}
rv = gkm_attribute_set_data (attr, array->data, array->len * sizeof (CK_OBJECT_HANDLE));
g_array_free (array, TRUE);
g_list_free (list);
return rv;
}
/* -----------------------------------------------------------------------------
* OBJECT
*/
static CK_RV
gkm_secret_search_get_attribute (GkmObject *base, GkmSession *session, CK_ATTRIBUTE_PTR attr)
{
GkmSecretSearch *self = GKM_SECRET_SEARCH (base);
switch (attr->type) {
case CKA_CLASS:
return gkm_attribute_set_ulong (attr, CKO_G_SEARCH);
case CKA_MODIFIABLE:
return gkm_attribute_set_bool (attr, CK_TRUE); /* TODO: This is needed for deleting? */
case CKA_G_COLLECTION:
if (!self->collection_id)
return gkm_attribute_set_empty (attr);
return gkm_attribute_set_string (attr, self->collection_id);
case CKA_G_FIELDS:
return gkm_secret_fields_serialize (attr, self->fields, self->schema_name);
case CKA_G_MATCHED:
return attribute_set_handles (self->objects, attr);
}
return GKM_OBJECT_CLASS (gkm_secret_search_parent_class)->get_attribute (base, session, attr);
}
static void
gkm_secret_search_init (GkmSecretSearch *self)
{
self->objects = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
}
static GObject*
gkm_secret_search_constructor (GType type, guint n_props, GObjectConstructParam *props)
{
GkmSecretSearch *self = GKM_SECRET_SEARCH (G_OBJECT_CLASS (gkm_secret_search_parent_class)->constructor(type, n_props, props));
g_return_val_if_fail (self, NULL);
g_return_val_if_fail (self->fields, NULL);
return G_OBJECT (self);
}
static void
gkm_secret_search_set_property (GObject *obj, guint prop_id, const GValue *value,
GParamSpec *pspec)
{
GkmSecretSearch *self = GKM_SECRET_SEARCH (obj);
switch (prop_id) {
case PROP_COLLECTION_ID:
g_return_if_fail (!self->collection_id);
self->collection_id = g_value_dup_string (value);
break;
case PROP_FIELDS:
g_return_if_fail (!self->fields);
self->fields = g_value_dup_boxed (value);
g_return_if_fail (self->fields);
break;
case PROP_SCHEMA_NAME:
g_return_if_fail (self->schema_name == NULL);
self->schema_name = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
break;
}
}
static void
gkm_secret_search_get_property (GObject *obj, guint prop_id, GValue *value,
GParamSpec *pspec)
{
GkmSecretSearch *self = GKM_SECRET_SEARCH (obj);
switch (prop_id) {
case PROP_COLLECTION_ID:
g_value_set_string (value, self->collection_id);
break;
case PROP_FIELDS:
g_return_if_fail (self->fields);
g_value_set_boxed (value, gkm_secret_search_get_fields (self));
break;
case PROP_SCHEMA_NAME:
g_value_set_string (value, self->schema_name);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
break;
}
}
static void
gkm_secret_search_dispose (GObject *obj)
{
GkmSecretSearch *self = GKM_SECRET_SEARCH (obj);
GList *l;
for (l = self->managers; l; l = g_list_next (l)) {
g_signal_handlers_disconnect_by_func (l->data, on_manager_added_object, self);
g_signal_handlers_disconnect_by_func (l->data, on_manager_removed_object, self);
g_signal_handlers_disconnect_by_func (l->data, on_manager_changed_object, self);
g_object_weak_unref (G_OBJECT (l->data), on_manager_gone_away, self);
}
g_list_free (self->managers);
self->managers = NULL;
g_free (self->collection_id);
self->collection_id = NULL;
g_hash_table_remove_all (self->objects);
G_OBJECT_CLASS (gkm_secret_search_parent_class)->dispose (obj);
}
static void
gkm_secret_search_finalize (GObject *obj)
{
GkmSecretSearch *self = GKM_SECRET_SEARCH (obj);
g_assert (!self->managers);
g_free (self->schema_name);
self->schema_name = NULL;
if (self->fields)
g_hash_table_destroy (self->fields);
self->fields = NULL;
g_hash_table_destroy (self->objects);
G_OBJECT_CLASS (gkm_secret_search_parent_class)->finalize (obj);
}
static void
gkm_secret_search_class_init (GkmSecretSearchClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GkmObjectClass *gkm_class = GKM_OBJECT_CLASS (klass);
gkm_secret_search_parent_class = g_type_class_peek_parent (klass);
gobject_class->constructor = gkm_secret_search_constructor;
gobject_class->dispose = gkm_secret_search_dispose;
gobject_class->finalize = gkm_secret_search_finalize;
gobject_class->set_property = gkm_secret_search_set_property;
gobject_class->get_property = gkm_secret_search_get_property;
gkm_class->get_attribute = gkm_secret_search_get_attribute;
g_object_class_install_property (gobject_class, PROP_COLLECTION_ID,
g_param_spec_string ("collection-id", "Collection ID", "Item's Collection's Identifier",
NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (gobject_class, PROP_FIELDS,
g_param_spec_boxed ("fields", "Fields", "Item's fields",
GKM_BOXED_SECRET_FIELDS, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (gobject_class, PROP_SCHEMA_NAME,
g_param_spec_string ("schema_name", "Schema Name", "Schema name to match",
NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
}
/* -----------------------------------------------------------------------------
* PUBLIC
*/
GkmFactory*
gkm_secret_search_get_factory (void)
{
static CK_OBJECT_CLASS klass = CKO_G_SEARCH;
static CK_BBOOL token = CK_FALSE;
static CK_ATTRIBUTE attributes[] = {
{ CKA_CLASS, &klass, sizeof (klass) },
{ CKA_TOKEN, &token, sizeof (token) },
};
static GkmFactory factory = {
attributes,
G_N_ELEMENTS (attributes),
factory_create_search
};
return &factory;
}
GHashTable*
gkm_secret_search_get_fields (GkmSecretSearch *self)
{
g_return_val_if_fail (GKM_IS_SECRET_SEARCH (self), NULL);
return self->fields;
}
const gchar *
gkm_secret_search_get_schema_name (GkmSecretSearch *self)
{
g_return_val_if_fail (GKM_IS_SECRET_SEARCH (self), NULL);
return self->schema_name;
}
const gchar*
gkm_secret_search_get_collection_id (GkmSecretSearch *self)
{
g_return_val_if_fail (GKM_IS_SECRET_SEARCH (self), NULL);
return self->collection_id;
}