Blame gcr/gcr-gnupg-collection.c

Packit b00eeb
/*
Packit b00eeb
 * gnome-keyring
Packit b00eeb
 *
Packit b00eeb
 * Copyright (C) 2011 Collabora Ltd.
Packit b00eeb
 *
Packit b00eeb
 * This program is free software; you can redistribute it and/or modify
Packit b00eeb
 * it under the terms of the GNU Lesser General Public License as
Packit b00eeb
 * published by the Free Software Foundation; either version 2.1 of
Packit b00eeb
 * the License, or (at your option) any later version.
Packit b00eeb
 *
Packit b00eeb
 * This program is distributed in the hope that it will be useful, but
Packit b00eeb
 * WITHOUT ANY WARRANTY; without even the implied warranty of
Packit b00eeb
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit b00eeb
 * Lesser General Public License for more details.
Packit b00eeb
 *
Packit b00eeb
 * You should have received a copy of the GNU Lesser General Public
Packit b00eeb
 * License along with this program; if not, see <http://www.gnu.org/licenses/>.
Packit b00eeb
 *
Packit b00eeb
 * Author: Stef Walter <stefw@collabora.co.uk>
Packit b00eeb
 */
Packit b00eeb
Packit b00eeb
#include "config.h"
Packit b00eeb
Packit b00eeb
#include "gcr-callback-output-stream.h"
Packit b00eeb
#include "gcr-collection.h"
Packit b00eeb
#include "gcr-gnupg-collection.h"
Packit b00eeb
#include "gcr-gnupg-key.h"
Packit b00eeb
#include "gcr-gnupg-process.h"
Packit b00eeb
#include "gcr-gnupg-records.h"
Packit b00eeb
#include "gcr-gnupg-util.h"
Packit b00eeb
#include "gcr-internal.h"
Packit b00eeb
#include "gcr-record.h"
Packit b00eeb
#include "gcr-util.h"
Packit b00eeb
Packit b00eeb
#include <sys/wait.h>
Packit b00eeb
#include <string.h>
Packit b00eeb
Packit b00eeb
enum {
Packit b00eeb
	PROP_0,
Packit b00eeb
	PROP_DIRECTORY
Packit b00eeb
};
Packit b00eeb
Packit b00eeb
struct _GcrGnupgCollectionPrivate {
Packit b00eeb
	GHashTable *items;          /* char *keyid -> GcrGnupgKey* */
Packit b00eeb
	gchar *directory;
Packit b00eeb
};
Packit b00eeb
Packit b00eeb
/* Forward declarations */
Packit b00eeb
static void _gcr_collection_iface (GcrCollectionIface *iface);
Packit b00eeb
Packit b00eeb
G_DEFINE_TYPE_WITH_CODE (GcrGnupgCollection, _gcr_gnupg_collection, G_TYPE_OBJECT,
Packit b00eeb
	G_IMPLEMENT_INTERFACE (GCR_TYPE_COLLECTION, _gcr_collection_iface)
Packit b00eeb
);
Packit b00eeb
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
_gcr_gnupg_collection_init (GcrGnupgCollection *self)
Packit b00eeb
{
Packit b00eeb
	self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_GNUPG_COLLECTION,
Packit b00eeb
	                                        GcrGnupgCollectionPrivate);
Packit b00eeb
Packit b00eeb
	self->pv->items = g_hash_table_new_full (g_str_hash, g_str_equal,
Packit b00eeb
	                                         g_free, g_object_unref);
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
_gcr_gnupg_collection_set_property (GObject *obj, guint prop_id, const GValue *value,
Packit b00eeb
                                    GParamSpec *pspec)
Packit b00eeb
{
Packit b00eeb
	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (obj);
Packit b00eeb
Packit b00eeb
	switch (prop_id) {
Packit b00eeb
	case PROP_DIRECTORY:
Packit b00eeb
		g_return_if_fail (!self->pv->directory);
Packit b00eeb
		self->pv->directory = g_value_dup_string (value);
Packit b00eeb
		if (self->pv->directory && !g_path_is_absolute (self->pv->directory)) {
Packit b00eeb
			g_warning ("gnupg collection directory path should be absolute: %s",
Packit b00eeb
			           self->pv->directory);
Packit b00eeb
		}
Packit b00eeb
		break;
Packit b00eeb
	default:
Packit b00eeb
		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
Packit b00eeb
		break;
Packit b00eeb
	}
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
_gcr_gnupg_collection_get_property (GObject *obj, guint prop_id, GValue *value,
Packit b00eeb
                                    GParamSpec *pspec)
Packit b00eeb
{
Packit b00eeb
	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (obj);
Packit b00eeb
Packit b00eeb
	switch (prop_id) {
Packit b00eeb
	case PROP_DIRECTORY:
Packit b00eeb
		g_value_set_string (value, self->pv->directory);
Packit b00eeb
		break;
Packit b00eeb
	default:
Packit b00eeb
		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
Packit b00eeb
		break;
Packit b00eeb
	}
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
_gcr_gnupg_collection_dispose (GObject *obj)
Packit b00eeb
{
Packit b00eeb
	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (obj);
Packit b00eeb
Packit b00eeb
	g_hash_table_remove_all (self->pv->items);
Packit b00eeb
Packit b00eeb
	G_OBJECT_CLASS (_gcr_gnupg_collection_parent_class)->dispose (obj);
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
_gcr_gnupg_collection_finalize (GObject *obj)
Packit b00eeb
{
Packit b00eeb
	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (obj);
Packit b00eeb
Packit b00eeb
	g_assert (self->pv->items);
Packit b00eeb
	g_assert (g_hash_table_size (self->pv->items) == 0);
Packit b00eeb
	g_hash_table_destroy (self->pv->items);
Packit b00eeb
	self->pv->items = NULL;
Packit b00eeb
Packit b00eeb
	g_free (self->pv->directory);
Packit b00eeb
	self->pv->directory = NULL;
Packit b00eeb
Packit b00eeb
	G_OBJECT_CLASS (_gcr_gnupg_collection_parent_class)->finalize (obj);
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
_gcr_gnupg_collection_class_init (GcrGnupgCollectionClass *klass)
Packit b00eeb
{
Packit b00eeb
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
Packit b00eeb
Packit b00eeb
	gobject_class->get_property = _gcr_gnupg_collection_get_property;
Packit b00eeb
	gobject_class->set_property = _gcr_gnupg_collection_set_property;
Packit b00eeb
	gobject_class->dispose = _gcr_gnupg_collection_dispose;
Packit b00eeb
	gobject_class->finalize = _gcr_gnupg_collection_finalize;
Packit b00eeb
Packit b00eeb
	/**
Packit b00eeb
	 * GcrGnupgCollection:directory:
Packit b00eeb
	 *
Packit b00eeb
	 * Directory to load the gnupg keys from, or %NULL for default
Packit b00eeb
	 * ~/.gnupg/ directory.
Packit b00eeb
	 */
Packit b00eeb
	g_object_class_install_property (gobject_class, PROP_DIRECTORY,
Packit b00eeb
	           g_param_spec_string ("directory", "Directory", "Gnupg Directory",
Packit b00eeb
	                                NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
Packit b00eeb
Packit b00eeb
	g_type_class_add_private (gobject_class, sizeof (GcrGnupgCollectionPrivate));
Packit b00eeb
	_gcr_initialize_library ();
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static guint
Packit b00eeb
gcr_gnupg_collection_real_get_length (GcrCollection *coll)
Packit b00eeb
{
Packit b00eeb
	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (coll);
Packit b00eeb
	return g_hash_table_size (self->pv->items);
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static GList*
Packit b00eeb
gcr_gnupg_collection_real_get_objects (GcrCollection *coll)
Packit b00eeb
{
Packit b00eeb
	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (coll);
Packit b00eeb
	return g_hash_table_get_values (self->pv->items);
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static gboolean
Packit b00eeb
gcr_gnupg_collection_real_contains (GcrCollection *collection,
Packit b00eeb
                                    GObject *object)
Packit b00eeb
{
Packit b00eeb
	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (collection);
Packit b00eeb
	GcrGnupgKey *key;
Packit b00eeb
Packit b00eeb
	if (!GCR_IS_GNUPG_KEY (object))
Packit b00eeb
		return FALSE;
Packit b00eeb
	key = g_hash_table_lookup (self->pv->items,
Packit b00eeb
	                           _gcr_gnupg_key_get_keyid (GCR_GNUPG_KEY (object)));
Packit b00eeb
	if (key != NULL && G_OBJECT (key) == object)
Packit b00eeb
		return TRUE;
Packit b00eeb
	return FALSE;
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
_gcr_collection_iface (GcrCollectionIface *iface)
Packit b00eeb
{
Packit b00eeb
	iface->get_length = gcr_gnupg_collection_real_get_length;
Packit b00eeb
	iface->get_objects = gcr_gnupg_collection_real_get_objects;
Packit b00eeb
	iface->contains = gcr_gnupg_collection_real_contains;
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
/**
Packit b00eeb
 * _gcr_gnupg_collection_new:
Packit b00eeb
 * @directory: (allow-none): The gnupg home directory.
Packit b00eeb
 *
Packit b00eeb
 * Create a new GcrGnupgCollection.
Packit b00eeb
 *
Packit b00eeb
 * The gnupg home directory is where the keyring files live. If directory is
Packit b00eeb
 * %NULL then the default gnupg home directory is used.
Packit b00eeb
 *
Packit b00eeb
 * Returns: (transfer full) (type Gcr.GnupgCollection): A newly allocated collection.
Packit b00eeb
 */
Packit b00eeb
GcrCollection*
Packit b00eeb
_gcr_gnupg_collection_new (const gchar *directory)
Packit b00eeb
{
Packit b00eeb
	return g_object_new (GCR_TYPE_GNUPG_COLLECTION,
Packit b00eeb
	                     "directory", directory,
Packit b00eeb
	                     NULL);
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
/*
Packit b00eeb
 * We have to run the gnupg process twice to list the public and then the
Packit b00eeb
 * secret keys. These phases are tracked by GcrLoadingPhase. If the first
Packit b00eeb
 * phase completes successfully (using gpg --list-keys) then we move on to
Packit b00eeb
 * the second phase where the secret keys are loaded (using gpg --list-secret-keys)
Packit b00eeb
 *
Packit b00eeb
 * If a key is loaded as a public key by the public phase, it can be updated by
Packit b00eeb
 * the secret phase. A key discovered in the secret phase must have a public
Packit b00eeb
 * counterpart already loaded by the public phase.
Packit b00eeb
 */
Packit b00eeb
Packit b00eeb
typedef enum {
Packit b00eeb
	GCR_LOADING_PHASE_PUBLIC = 1,
Packit b00eeb
	GCR_LOADING_PHASE_SECRET = 2,
Packit b00eeb
} GcrLoadingPhase;
Packit b00eeb
Packit b00eeb
/*
Packit b00eeb
 * We use @difference to track the keys that were in the collection before
Packit b00eeb
 * the load process, and then remove any not found, at the end of the load
Packit b00eeb
 * process. Strings are directly used from collection->pv->items keys.
Packit b00eeb
 */
Packit b00eeb
Packit b00eeb
typedef struct {
Packit b00eeb
	GcrGnupgCollection *collection;       /* reffed pointer back to collection */
Packit b00eeb
	GcrLoadingPhase loading_phase;        /* Whether loading public or private */
Packit b00eeb
	GPtrArray *records;                   /* GcrRecord* not yet made into a key */
Packit b00eeb
	GcrGnupgProcess *process;             /* The gnupg process itself */
Packit b00eeb
	GCancellable *cancel;                 /* Cancellation for process */
Packit b00eeb
	GString *out_data;                    /* Pending output not yet parsed into colons */
Packit b00eeb
	GHashTable *difference;               /* Hashset gchar *keyid -> gchar *keyid */
Packit b00eeb
Packit b00eeb
	guint error_sig;
Packit b00eeb
	guint status_sig;
Packit b00eeb
	GOutputStream *output;
Packit b00eeb
	GOutputStream *outattr;
Packit b00eeb
Packit b00eeb
	GQueue *attribute_queue;              /* Queue of unprocessed GcrRecord* status records */
Packit b00eeb
	GByteArray *attribute_buf;            /* Buffer of unprocessed attribute data received */
Packit b00eeb
	GHashTable *attributes;               /* Processed attributes waiting for a matching key */
Packit b00eeb
} GcrGnupgCollectionLoad;
Packit b00eeb
Packit b00eeb
/* Forward declarations */
Packit b00eeb
static void spawn_gnupg_list_process (GcrGnupgCollectionLoad *load, GSimpleAsyncResult *res);
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
_gcr_gnupg_collection_load_free (gpointer data)
Packit b00eeb
{
Packit b00eeb
	GcrGnupgCollectionLoad *load = data;
Packit b00eeb
	g_assert (load);
Packit b00eeb
Packit b00eeb
	g_ptr_array_unref (load->records);
Packit b00eeb
	g_string_free (load->out_data, TRUE);
Packit b00eeb
	g_hash_table_destroy (load->difference);
Packit b00eeb
	g_object_unref (load->collection);
Packit b00eeb
Packit b00eeb
	if (load->process) {
Packit b00eeb
		if (load->error_sig)
Packit b00eeb
			g_signal_handler_disconnect (load->process, load->error_sig);
Packit b00eeb
		if (load->status_sig)
Packit b00eeb
			g_signal_handler_disconnect (load->process, load->status_sig);
Packit b00eeb
		g_object_unref (load->process);
Packit b00eeb
	}
Packit b00eeb
Packit b00eeb
	g_output_stream_close (load->output, NULL, NULL);
Packit b00eeb
	g_object_unref (load->output);
Packit b00eeb
	g_output_stream_close (load->outattr, NULL, NULL);
Packit b00eeb
	g_object_unref (load->outattr);
Packit b00eeb
Packit b00eeb
	if (load->cancel)
Packit b00eeb
		g_object_unref (load->cancel);
Packit b00eeb
Packit b00eeb
	if (load->attribute_queue) {
Packit b00eeb
		while (!g_queue_is_empty (load->attribute_queue))
Packit b00eeb
			_gcr_record_free (g_queue_pop_head (load->attribute_queue));
Packit b00eeb
		g_queue_free (load->attribute_queue);
Packit b00eeb
	}
Packit b00eeb
	if (load->attribute_buf)
Packit b00eeb
		g_byte_array_unref (load->attribute_buf);
Packit b00eeb
	if (load->attributes)
Packit b00eeb
		g_hash_table_destroy (load->attributes);
Packit b00eeb
Packit b00eeb
	g_slice_free (GcrGnupgCollectionLoad, load);
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
process_records_as_public_key (GcrGnupgCollectionLoad *load, GPtrArray *records,
Packit b00eeb
                               const gchar *keyid)
Packit b00eeb
{
Packit b00eeb
	GPtrArray *attr_records = NULL;
Packit b00eeb
	const gchar *fingerprint;
Packit b00eeb
	gchar *orig_fingerprint;
Packit b00eeb
	GcrGnupgKey *key;
Packit b00eeb
	guint i;
Packit b00eeb
Packit b00eeb
	/* Add in any attributes we have loaded */
Packit b00eeb
	fingerprint = _gcr_gnupg_records_get_fingerprint (records);
Packit b00eeb
	if (fingerprint && load->attributes)
Packit b00eeb
		attr_records = g_hash_table_lookup (load->attributes, fingerprint);
Packit b00eeb
	if (attr_records) {
Packit b00eeb
		g_debug ("adding %d user id attribute(s) to key/fingerprint: %s/%s",
Packit b00eeb
		         (gint)attr_records->len, keyid, fingerprint);
Packit b00eeb
Packit b00eeb
		if (!g_hash_table_lookup_extended (load->attributes, fingerprint,
Packit b00eeb
		                                   (gpointer*)&orig_fingerprint, NULL))
Packit b00eeb
			g_assert_not_reached ();
Packit b00eeb
		if (!g_hash_table_steal (load->attributes, fingerprint))
Packit b00eeb
			g_assert_not_reached ();
Packit b00eeb
		g_free (orig_fingerprint);
Packit b00eeb
Packit b00eeb
		/* Move all the attribute records over to main records set */
Packit b00eeb
		for (i = 0; i < attr_records->len; i++)
Packit b00eeb
			g_ptr_array_add (records, attr_records->pdata[i]);
Packit b00eeb
Packit b00eeb
		/* Shallow free of attr_records array */
Packit b00eeb
		g_free (g_ptr_array_free (attr_records, FALSE));
Packit b00eeb
	}
Packit b00eeb
Packit b00eeb
	/* Note that we've seen this keyid */
Packit b00eeb
	g_hash_table_remove (load->difference, keyid);
Packit b00eeb
Packit b00eeb
	key = g_hash_table_lookup (load->collection->pv->items, keyid);
Packit b00eeb
Packit b00eeb
	/* Already have this key, just update */
Packit b00eeb
	if (key) {
Packit b00eeb
		g_debug ("updating public key: %s", keyid);
Packit b00eeb
		_gcr_gnupg_key_set_public_records (key, records);
Packit b00eeb
Packit b00eeb
	/* Add a new key */
Packit b00eeb
	} else {
Packit b00eeb
		key = _gcr_gnupg_key_new (records, NULL);
Packit b00eeb
		g_debug ("creating public key: %s", keyid);
Packit b00eeb
		g_hash_table_insert (load->collection->pv->items, g_strdup (keyid), key);
Packit b00eeb
		gcr_collection_emit_added (GCR_COLLECTION (load->collection), G_OBJECT (key));
Packit b00eeb
	}
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
process_records_as_secret_key (GcrGnupgCollectionLoad *load, GPtrArray *records,
Packit b00eeb
                               const gchar *keyid)
Packit b00eeb
{
Packit b00eeb
	GcrGnupgKey *key;
Packit b00eeb
Packit b00eeb
	key = g_hash_table_lookup (load->collection->pv->items, keyid);
Packit b00eeb
Packit b00eeb
	/* Don't have this key */
Packit b00eeb
	if (key == NULL) {
Packit b00eeb
		g_message ("Secret key seen but no public key for: %s", keyid);
Packit b00eeb
Packit b00eeb
	/* Tell the private key that it's a secret one */
Packit b00eeb
	} else {
Packit b00eeb
		g_debug ("adding secret records to key: %s", keyid);
Packit b00eeb
		_gcr_gnupg_key_set_secret_records (key, records);
Packit b00eeb
	}
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
process_records_as_key (GcrGnupgCollectionLoad *load)
Packit b00eeb
{
Packit b00eeb
	GPtrArray *records;
Packit b00eeb
	const gchar *keyid;
Packit b00eeb
	GQuark schema;
Packit b00eeb
Packit b00eeb
	g_assert (load->records->len);
Packit b00eeb
Packit b00eeb
	records = load->records;
Packit b00eeb
	load->records = g_ptr_array_new_with_free_func (_gcr_record_free);
Packit b00eeb
Packit b00eeb
	keyid = _gcr_gnupg_records_get_keyid (records);
Packit b00eeb
	if (keyid) {
Packit b00eeb
		schema = _gcr_record_get_schema (records->pdata[0]);
Packit b00eeb
Packit b00eeb
		/* A public key */
Packit b00eeb
		if (schema == GCR_RECORD_SCHEMA_PUB)
Packit b00eeb
			process_records_as_public_key (load, records, keyid);
Packit b00eeb
Packit b00eeb
		/* A secret key */
Packit b00eeb
		else if (schema == GCR_RECORD_SCHEMA_SEC)
Packit b00eeb
			process_records_as_secret_key (load, records, keyid);
Packit b00eeb
Packit b00eeb
		else
Packit b00eeb
			g_assert_not_reached ();
Packit b00eeb
Packit b00eeb
	} else {
Packit b00eeb
		g_warning ("parsed gnupg data had no keyid");
Packit b00eeb
	}
Packit b00eeb
Packit b00eeb
	g_ptr_array_unref (records);
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static gboolean
Packit b00eeb
process_outstanding_attribute (GcrGnupgCollectionLoad *load, GcrRecord *record)
Packit b00eeb
{
Packit b00eeb
	const gchar *fingerprint;
Packit b00eeb
	GPtrArray *records;
Packit b00eeb
	GcrRecord *xa1;
Packit b00eeb
	guint length;
Packit b00eeb
Packit b00eeb
	if (!_gcr_record_get_uint (record, GCR_RECORD_ATTRIBUTE_LENGTH, &length))
Packit b00eeb
		g_return_val_if_reached (FALSE);
Packit b00eeb
	fingerprint = _gcr_record_get_raw (record, GCR_RECORD_ATTRIBUTE_KEY_FINGERPRINT);
Packit b00eeb
	g_return_val_if_fail (fingerprint != NULL, FALSE);
Packit b00eeb
Packit b00eeb
	/* Do we have enough data for this attribute? */
Packit b00eeb
	if (!load->attribute_buf || load->attribute_buf->len < length) {
Packit b00eeb
		g_debug ("not enough attribute data in buffer: %u", length);
Packit b00eeb
		return FALSE;
Packit b00eeb
	}
Packit b00eeb
Packit b00eeb
	if (!load->attributes)
Packit b00eeb
		load->attributes = g_hash_table_new_full (g_str_hash, g_str_equal,
Packit b00eeb
							  g_free, (GDestroyNotify)g_ptr_array_unref);
Packit b00eeb
Packit b00eeb
	records = g_hash_table_lookup (load->attributes, fingerprint);
Packit b00eeb
	if (!records) {
Packit b00eeb
		records = g_ptr_array_new_with_free_func (_gcr_record_free);
Packit b00eeb
		g_hash_table_insert (load->attributes, g_strdup (fingerprint), records);
Packit b00eeb
	}
Packit b00eeb
Packit b00eeb
	g_debug ("new attribute of length %d for key with fingerprint %s",
Packit b00eeb
	         length, fingerprint);
Packit b00eeb
Packit b00eeb
	xa1 = _gcr_gnupg_build_xa1_record (record, load->attribute_buf->data, length);
Packit b00eeb
	g_ptr_array_add (records, xa1);
Packit b00eeb
Packit b00eeb
	/* Did we use up all the attribute data? Get rid of the buffer */
Packit b00eeb
	if (length == load->attribute_buf->len) {
Packit b00eeb
		g_byte_array_unref (load->attribute_buf);
Packit b00eeb
		load->attribute_buf = NULL;
Packit b00eeb
Packit b00eeb
	/* Otherwise clear out the used data from buffer */
Packit b00eeb
	} else {
Packit b00eeb
		g_byte_array_remove_range (load->attribute_buf, 0, length);
Packit b00eeb
	}
Packit b00eeb
Packit b00eeb
	return TRUE;
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
process_outstanding_attributes (GcrGnupgCollectionLoad *load)
Packit b00eeb
{
Packit b00eeb
	GcrRecord *record;
Packit b00eeb
Packit b00eeb
	if (load->attribute_queue == NULL)
Packit b00eeb
		return;
Packit b00eeb
Packit b00eeb
	g_debug ("%d outstanding attribute records",
Packit b00eeb
	         (gint)g_queue_get_length (load->attribute_queue));
Packit b00eeb
Packit b00eeb
	for (;;) {
Packit b00eeb
		record = g_queue_peek_head (load->attribute_queue);
Packit b00eeb
		if (record == NULL)
Packit b00eeb
			break;
Packit b00eeb
		if (!process_outstanding_attribute (load, record))
Packit b00eeb
			break;
Packit b00eeb
		g_queue_pop_head (load->attribute_queue);
Packit b00eeb
		_gcr_record_free (record);
Packit b00eeb
	}
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
on_line_parse_output (const gchar *line, gpointer user_data)
Packit b00eeb
{
Packit b00eeb
	GcrGnupgCollectionLoad *load = user_data;
Packit b00eeb
	GcrRecord *record;
Packit b00eeb
	GQuark schema;
Packit b00eeb
Packit b00eeb
	g_debug ("output: %s", line);
Packit b00eeb
Packit b00eeb
	record = _gcr_record_parse_colons (line, -1);
Packit b00eeb
	if (!record) {
Packit b00eeb
		g_warning ("invalid gnupg output line: %s", line);
Packit b00eeb
		return;
Packit b00eeb
	}
Packit b00eeb
Packit b00eeb
	schema = _gcr_record_get_schema (record);
Packit b00eeb
Packit b00eeb
	/*
Packit b00eeb
	 * Each time we see a line with 'pub' or 'sec' schema we assume that
Packit b00eeb
	 * it's a new key being listed.
Packit b00eeb
	 */
Packit b00eeb
	if (schema == GCR_RECORD_SCHEMA_PUB || schema == GCR_RECORD_SCHEMA_SEC) {
Packit b00eeb
		g_debug ("start of new key");
Packit b00eeb
		if (load->records->len)
Packit b00eeb
			process_records_as_key (load);
Packit b00eeb
		g_assert (!load->records->len);
Packit b00eeb
		g_ptr_array_add (load->records, record);
Packit b00eeb
		record = NULL;
Packit b00eeb
Packit b00eeb
	/*
Packit b00eeb
	 * 'uid' and 'fpr' schema lines get added to the key that came before.
Packit b00eeb
	 */
Packit b00eeb
	} else if (schema == GCR_RECORD_SCHEMA_UID ||
Packit b00eeb
	           schema == GCR_RECORD_SCHEMA_FPR) {
Packit b00eeb
		if (load->records->len) {
Packit b00eeb
			g_ptr_array_add (load->records, record);
Packit b00eeb
			record = NULL;
Packit b00eeb
		}
Packit b00eeb
	}
Packit b00eeb
Packit b00eeb
	if (record != NULL)
Packit b00eeb
		_gcr_record_free (record);
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
Packit b00eeb
static gssize
Packit b00eeb
on_gnupg_process_output_data (gconstpointer buffer,
Packit b00eeb
                              gsize count,
Packit b00eeb
                              GCancellable *cancellable,
Packit b00eeb
                              gpointer user_data,
Packit b00eeb
                              GError **error)
Packit b00eeb
{
Packit b00eeb
	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
Packit b00eeb
	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
Packit b00eeb
Packit b00eeb
	g_string_append_len (load->out_data, buffer, count);
Packit b00eeb
	_gcr_util_parse_lines (load->out_data, FALSE, on_line_parse_output, load);
Packit b00eeb
	return count;
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
on_gnupg_process_error_line (GcrGnupgProcess *process, const gchar *line,
Packit b00eeb
                             gpointer user_data)
Packit b00eeb
{
Packit b00eeb
	g_printerr ("%s\n", line);
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
on_gnupg_process_status_record (GcrGnupgProcess *process, GcrRecord *record,
Packit b00eeb
                                gpointer user_data)
Packit b00eeb
{
Packit b00eeb
	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
Packit b00eeb
	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
Packit b00eeb
Packit b00eeb
	if (GCR_RECORD_SCHEMA_ATTRIBUTE != _gcr_record_get_schema (record))
Packit b00eeb
		return;
Packit b00eeb
Packit b00eeb
	if (!load->attribute_queue)
Packit b00eeb
		load->attribute_queue = g_queue_new ();
Packit b00eeb
Packit b00eeb
	g_queue_push_tail (load->attribute_queue, _gcr_record_copy (record));
Packit b00eeb
	process_outstanding_attributes (load);
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static gssize
Packit b00eeb
on_gnupg_process_attribute_data (gconstpointer buffer,
Packit b00eeb
                                 gsize count,
Packit b00eeb
                                 GCancellable *cancellable,
Packit b00eeb
                                 gpointer user_data,
Packit b00eeb
                                 GError **error)
Packit b00eeb
{
Packit b00eeb
	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
Packit b00eeb
	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
Packit b00eeb
Packit b00eeb
	/* If we don't have a buffer, just claim this one */
Packit b00eeb
	if (!load->attribute_buf)
Packit b00eeb
		load->attribute_buf = g_byte_array_new ();
Packit b00eeb
Packit b00eeb
	g_byte_array_append (load->attribute_buf, buffer, count);
Packit b00eeb
Packit b00eeb
	process_outstanding_attributes (load);
Packit b00eeb
	return count;
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
on_gnupg_process_completed (GObject *source, GAsyncResult *result, gpointer user_data)
Packit b00eeb
{
Packit b00eeb
	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
Packit b00eeb
	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
Packit b00eeb
	GHashTableIter iter;
Packit b00eeb
	GError *error = NULL;
Packit b00eeb
	GObject *object;
Packit b00eeb
	gpointer keyid;
Packit b00eeb
Packit b00eeb
	if (!_gcr_gnupg_process_run_finish (GCR_GNUPG_PROCESS (source), result, &error)) {
Packit b00eeb
		g_simple_async_result_set_from_error (res, error);
Packit b00eeb
		g_simple_async_result_complete (res);
Packit b00eeb
		g_object_unref (res);
Packit b00eeb
		g_clear_error (&error);
Packit b00eeb
		return;
Packit b00eeb
	}
Packit b00eeb
Packit b00eeb
	/* Process any remaining output */
Packit b00eeb
	_gcr_util_parse_lines (load->out_data, TRUE, on_line_parse_output, load);
Packit b00eeb
Packit b00eeb
	/* Process last bit as a key, if any */
Packit b00eeb
	if (load->records->len)
Packit b00eeb
		process_records_as_key (load);
Packit b00eeb
Packit b00eeb
	/* If we completed loading public keys, then go and load secret */
Packit b00eeb
	switch (load->loading_phase) {
Packit b00eeb
	case GCR_LOADING_PHASE_PUBLIC:
Packit b00eeb
		g_debug ("public load phase completed");
Packit b00eeb
		load->loading_phase = GCR_LOADING_PHASE_SECRET;
Packit b00eeb
		spawn_gnupg_list_process (load, res);
Packit b00eeb
		g_object_unref (res);
Packit b00eeb
		return;
Packit b00eeb
	case GCR_LOADING_PHASE_SECRET:
Packit b00eeb
		g_debug ("secret load phase completed");
Packit b00eeb
		/* continue below */
Packit b00eeb
		break;
Packit b00eeb
	default:
Packit b00eeb
		g_assert_not_reached ();
Packit b00eeb
	}
Packit b00eeb
Packit b00eeb
	/* Remove any keys that we still have in the difference */
Packit b00eeb
	g_hash_table_iter_init (&iter, load->difference);
Packit b00eeb
	while (g_hash_table_iter_next (&iter, &keyid, NULL)) {
Packit b00eeb
		object = g_hash_table_lookup (load->collection->pv->items, keyid);
Packit b00eeb
		if (object != NULL) {
Packit b00eeb
			g_object_ref (object);
Packit b00eeb
			g_debug ("removing key no longer present in keyring: %s", (gchar*)keyid);
Packit b00eeb
			g_hash_table_remove (load->collection->pv->items, keyid);
Packit b00eeb
			gcr_collection_emit_removed (GCR_COLLECTION (load->collection), object);
Packit b00eeb
			g_object_unref (object);
Packit b00eeb
		}
Packit b00eeb
	}
Packit b00eeb
Packit b00eeb
	g_simple_async_result_complete (res);
Packit b00eeb
	g_object_unref (res);
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
static void
Packit b00eeb
spawn_gnupg_list_process (GcrGnupgCollectionLoad *load, GSimpleAsyncResult *res)
Packit b00eeb
{
Packit b00eeb
	GcrGnupgProcessFlags flags = 0;
Packit b00eeb
	GPtrArray *argv;
Packit b00eeb
Packit b00eeb
	argv = g_ptr_array_new ();
Packit b00eeb
Packit b00eeb
	switch (load->loading_phase) {
Packit b00eeb
	case GCR_LOADING_PHASE_PUBLIC:
Packit b00eeb
		g_debug ("starting public load phase");
Packit b00eeb
		g_ptr_array_add (argv, (gpointer)"--list-keys");
Packit b00eeb
		/* Load photos in public phase */
Packit b00eeb
		flags = GCR_GNUPG_PROCESS_WITH_ATTRIBUTES |
Packit b00eeb
		        GCR_GNUPG_PROCESS_WITH_STATUS;
Packit b00eeb
		break;
Packit b00eeb
	case GCR_LOADING_PHASE_SECRET:
Packit b00eeb
		g_debug ("starting secret load phase");
Packit b00eeb
		g_ptr_array_add (argv, (gpointer)"--list-secret-keys");
Packit b00eeb
		break;
Packit b00eeb
	default:
Packit b00eeb
		g_assert_not_reached ();
Packit b00eeb
	}
Packit b00eeb
Packit b00eeb
	g_ptr_array_add (argv, (gpointer)"--fixed-list-mode");
Packit b00eeb
	g_ptr_array_add (argv, (gpointer)"--with-colons");
Packit b00eeb
	g_ptr_array_add (argv, (gpointer)"--with-fingerprint");
Packit b00eeb
	g_ptr_array_add (argv, NULL);
Packit b00eeb
Packit b00eeb
	/* res is unreffed in on_gnupg_process_completed */
Packit b00eeb
	_gcr_gnupg_process_run_async (load->process, (const gchar**)argv->pdata, NULL, flags,
Packit b00eeb
	                              load->cancel, on_gnupg_process_completed,
Packit b00eeb
	                              g_object_ref (res));
Packit b00eeb
Packit b00eeb
	g_ptr_array_unref (argv);
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
/**
Packit b00eeb
 * _gcr_gnupg_collection_load_async:
Packit b00eeb
 * @self: The collection
Packit b00eeb
 * @cancellable: Cancellation object or %NULL
Packit b00eeb
 * @callback: Callback to call when result is ready
Packit b00eeb
 * @user_data: Data for callback
Packit b00eeb
 *
Packit b00eeb
 * Start an operation to load or reload the list of gnupg keys in this
Packit b00eeb
 * collection.
Packit b00eeb
 */
Packit b00eeb
void
Packit b00eeb
_gcr_gnupg_collection_load_async (GcrGnupgCollection *self, GCancellable *cancellable,
Packit b00eeb
                                  GAsyncReadyCallback callback, gpointer user_data)
Packit b00eeb
{
Packit b00eeb
	GSimpleAsyncResult *res;
Packit b00eeb
	GcrGnupgCollectionLoad *load;
Packit b00eeb
	GHashTableIter iter;
Packit b00eeb
	gpointer keyid;
Packit b00eeb
Packit b00eeb
	g_return_if_fail (GCR_IS_GNUPG_COLLECTION (self));
Packit b00eeb
Packit b00eeb
	/* TODO: Cancellation not yet implemented */
Packit b00eeb
Packit b00eeb
	res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
Packit b00eeb
	                                 _gcr_gnupg_collection_load_async);
Packit b00eeb
Packit b00eeb
	load = g_slice_new0 (GcrGnupgCollectionLoad);
Packit b00eeb
	load->records = g_ptr_array_new_with_free_func (_gcr_record_free);
Packit b00eeb
	load->out_data = g_string_sized_new (1024);
Packit b00eeb
	load->collection = g_object_ref (self);
Packit b00eeb
	load->cancel = cancellable ? g_object_ref (cancellable) : cancellable;
Packit b00eeb
Packit b00eeb
	load->output = _gcr_callback_output_stream_new (on_gnupg_process_output_data, res, NULL);
Packit b00eeb
	load->outattr = _gcr_callback_output_stream_new (on_gnupg_process_attribute_data, res, NULL);
Packit b00eeb
Packit b00eeb
	load->process = _gcr_gnupg_process_new (self->pv->directory, NULL);
Packit b00eeb
	_gcr_gnupg_process_set_output_stream (load->process, load->output);
Packit b00eeb
	_gcr_gnupg_process_set_attribute_stream (load->process, load->outattr);
Packit b00eeb
	load->error_sig = g_signal_connect (load->process, "error-line", G_CALLBACK (on_gnupg_process_error_line), res);
Packit b00eeb
	load->status_sig = g_signal_connect (load->process, "status-record", G_CALLBACK (on_gnupg_process_status_record), res);
Packit b00eeb
Packit b00eeb
	/*
Packit b00eeb
	 * Track all the keys we currently have, at end remove those that
Packit b00eeb
	 * didn't get listed by the gpg process.
Packit b00eeb
	 */
Packit b00eeb
	load->difference = g_hash_table_new (g_str_hash, g_str_equal);
Packit b00eeb
	g_hash_table_iter_init (&iter, self->pv->items);
Packit b00eeb
	while (g_hash_table_iter_next (&iter, &keyid, NULL))
Packit b00eeb
		g_hash_table_insert (load->difference, keyid, keyid);
Packit b00eeb
Packit b00eeb
	g_simple_async_result_set_op_res_gpointer (res, load,
Packit b00eeb
	                                           _gcr_gnupg_collection_load_free);
Packit b00eeb
Packit b00eeb
	load->loading_phase = GCR_LOADING_PHASE_PUBLIC;
Packit b00eeb
	spawn_gnupg_list_process (load, res);
Packit b00eeb
Packit b00eeb
	g_object_unref (res);
Packit b00eeb
}
Packit b00eeb
Packit b00eeb
/**
Packit b00eeb
 * _gcr_gnupg_collection_load_finish:
Packit b00eeb
 * @self: The collection
Packit b00eeb
 * @result: The result passed to the callback
Packit b00eeb
 * @error: Location to raise an error on failure.
Packit b00eeb
 *
Packit b00eeb
 * Get the result of an operation to load or reload the list of gnupg keys
Packit b00eeb
 * in this collection.
Packit b00eeb
 */
Packit b00eeb
gboolean
Packit b00eeb
_gcr_gnupg_collection_load_finish (GcrGnupgCollection *self, GAsyncResult *result,
Packit b00eeb
                                   GError **error)
Packit b00eeb
{
Packit b00eeb
	g_return_val_if_fail (GCR_IS_GNUPG_COLLECTION (self), FALSE);
Packit b00eeb
	g_return_val_if_fail (!error || !*error, FALSE);
Packit b00eeb
Packit b00eeb
	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
Packit b00eeb
	                      _gcr_gnupg_collection_load_async), FALSE);
Packit b00eeb
Packit b00eeb
	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
Packit b00eeb
		return FALSE;
Packit b00eeb
Packit b00eeb
	return TRUE;
Packit b00eeb
}