changeset 19499:47a2d00ab060

propagate from branch 'im.pidgin.pidgin' (head 1ade185e4daf699979b517ddb70cee47cd15e7ee) to branch 'im.pidgin.soc.2007.certmgr' (head 59d49a3a37747b811f234de6931e7c575bf885fb)
author William Ehlhardt <williamehlhardt@gmail.com>
date Tue, 28 Aug 2007 01:14:08 +0000
parents 8c00833b9959 (current diff) 7589b218f89a (diff)
children 6e4b16081dff
files configure.ac libpurple/Makefile.am libpurple/util.c libpurple/util.h pidgin/gtkblist.c pidgin/pixmaps/emotes/default/24/madtongue.png
diffstat 27 files changed, 4741 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- a/configure.ac	Tue Aug 28 00:13:20 2007 +0000
+++ b/configure.ac	Tue Aug 28 01:14:08 2007 +0000
@@ -2240,6 +2240,7 @@
 		   libpurple/version.h
 		   share/Makefile
 		   share/sounds/Makefile
+		   share/ca-certs/Makefile
 		   finch/Makefile
 		   finch/libgnt/Makefile
 		   finch/libgnt/gnt.pc
--- a/doc/Makefile.am	Tue Aug 28 00:13:20 2007 +0000
+++ b/doc/Makefile.am	Tue Aug 28 01:14:08 2007 +0000
@@ -3,11 +3,13 @@
 EXTRA_DIST = \
 	C-HOWTO.dox \
 	PERL-HOWTO.dox \
+	SIGNAL-HOWTO.dox \
 	TCL-HOWTO.dox \
 	TracFooter.html \
 	TracHeader.html \
 	account-signals.dox \
 	blist-signals.dox \
+	certificate-signals.dox \
 	cipher-signals.dox \
 	connection-signals.dox \
 	conversation-signals.dox \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/SIGNAL-HOWTO.dox	Tue Aug 28 01:14:08 2007 +0000
@@ -0,0 +1,137 @@
+/** @page signal-howto Signals HOWTO
+
+ @section Introduction
+  The libpurple signals interface is used for general event notification, such
+  as plugins being loaded or unloaded, allowing the GUI frontend to respond
+  appropriately to changing internal data. Unfortunately, its use is not at all
+  obvious from the information in the header files. This document uses code
+  snippets from the Pidgin/libpurple plugin systems to illustrate the proper
+  use of signals.
+
+ @section overview Overview of Signals
+  Signals in libpurple are very similar to those in GTK+. When certain events
+  happen, a named signal is "emitted" from a certain object. Emitting the
+  signal triggers a series of callbacks that have been "connected" to that
+  signal for that object. These callbacks take appropriate action in response
+  to the signal.
+
+ @section registering_signal Registering a Signal
+  The first step of using a signal is registering it with libpurple so that
+  callbacks may be connected to it. This is done using purple_signal_register()
+  Here is a slightly modified example from @c purple_plugins_init in 
+  @c libpurple/plugin.c :
+
+  @code
+	purple_signal_register( purple_plugins_get_handle(), /* Instance */
+				"plugin-load",               /* Signal name */
+				purple_marshal_VOID__POINTER,/* Marshal function */
+				NULL,                        /* Callback return value type */
+				1,                           /* Number of callback arguments (not including void *data) */
+				purple_value_new(PURPLE_TYPE_SUBTYPE,PURPLE_SUBTYPE_PLUGIN) /* Type of first callback argument */
+				);
+  @endcode
+
+  @subsection Instance
+  A reference to the object from which this signal is emitted, and to which
+  potential callbacks should be connected. In this case, it will be the entire
+  plugin module emitting the signal.
+  
+  @subsection signalname Signal Name
+  Unique identifier for the signal itself.
+
+  @subsection therest Callback function definition
+  The rest of the arguments specify the form of the callback function.
+
+  @subsubsection marshalfunc Marshal Function
+  @c purple_marshal_VOID__POINTER represents the callback function prototype,
+  not including a "data" argument, explained later. The form is 
+  @c purple_marshal_RETURNVALUETYPE__ARG1TYPE_ARG2TYPE_ETC. See signals.h for
+  more possible types.
+
+  In this case, the callback will have the form
+  @code
+	void cb(void *arg1, void *data)
+  @endcode
+
+  If @c purple_marshal_BOOLEAN__POINTER_POINTER_POINTER were specified, it
+  would be:
+  @code
+	gboolean cb(void *arg1, void *arg2, void *arg3, void *data)
+  @endcode
+
+  The @c void @c *data argument at the end of each callback function
+  provides the data argument given to purple_signal_connect() .
+
+  @subsubsection cb_ret_type Callback return value type
+  In our case, this is NULL, meaning "returns void".
+  @todo This could be described better.
+
+  @subsubsection num_args Number of arguments
+  The number of arguments (not including @c data ) that the callback function
+  will take.
+
+  @subsubsection type_arg Type of argument
+  @c purple_value_new(PURPLE_TYPE_SUBTYPE,PURPLE_SUBTYPE_PLUGIN) specifies that
+  the first argument given to the callback will be a @c PurplePlugin* . You
+  will need as many "type of argument" arguments to purple_signal_register() as
+  you specified in "Number of arguments" above.
+
+  @todo Describe this more.
+
+  @See value.h
+
+  @section connect Connecting to the signal
+  Once the signal is registered, you can connect callbacks to it. First, you
+  must define a callback function, such as this one from gtkplugin.c :
+  @code
+static void plugin_load_cb(PurplePlugin *plugin, gpointer data)
+{
+	GtkTreeView *view = (GtkTreeView *)data;
+	plugin_loading_common(plugin, view, TRUE);
+}
+  @endcode
+  Note that the callback function prototype matches that specified in the call
+  to purple_signal_register() above.
+
+  Once the callback function is defined, you can connect it to the signal.
+  Again from gtkplugin.c , in @c pidgin_plugin_dialog_show() :
+  @code
+	purple_signal_connect(purple_plugins_get_handle(), "plugin-load", /* What to connect to */
+		plugin_dialog, /* Object receiving the signal */
+		PURPLE_CALLBACK(plugin_load_cb), /* Callback function */
+		event_view, /* Data to pass to the callback function
+		);
+  @endcode
+
+  The first two arguments ("What to connect to") specify the object emitting
+  the signal (the plugin module) and what signal to listen for ("plugin-load").
+
+  The object receiving the signal is @c plugin_dialog , the Pidgin plugins
+  dialog. When @c plugin_dialog is deleted, then 
+  @c purple_signals_disconnect_by_handle(plugin_dialog) should be called to
+  remove all signal connections it is associated with.
+
+  The callback function is given using a helper macro, and finally the
+  @c data argument to be passed to @c plugin_load_cb is given as @c event_view,
+  a pointer to the GTK widget that @c plugin_load_cb needs to update.
+
+  @section emit-signal Emitting a signal
+  Connecting callbacks to signals is all well and good, but how do you "fire"
+  the signal and trigger the callback? At some point, you must "emit" the
+  signal, which immediately calls all connected callbacks.
+
+  As seen in @c purple_plugin_load() in plugin.c :
+  @code
+	purple_signal_emit(purple_plugins_get_handle(), "plugin-load", plugin);
+  @endcode
+  This causes the signal "plugin-load" to be emitted from the plugin module
+  (given by @c purple_plugins_get_handle() ), with the newly loaded plugin as
+  the argument to pass to any registered callback functions.
+
+  In our example, @c plugin_load_cb is called immediately as
+  @code
+	plugin_load_cb(plugin, event_view);
+  @endcode
+  and does whatever it does.
+  
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/certificate-signals.dox	Tue Aug 28 01:14:08 2007 +0000
@@ -0,0 +1,31 @@
+/** @page certificate-signals Certificate Signals
+
+ @signals
+  @signal certificate-stored
+  @signal certificate-deleted
+ @endsignals
+
+ <hr>
+
+ @signaldef certificate-stored
+  @signalproto
+void (*certificate_stored)(PurpleCertificatePool *pool, const gchar *id, gpointer data);
+  @endsignalproto
+  @signaldesc
+   Emitted when a pool stores a certificate. Connect to the pool instance.
+  @param pool    Pool the certificate has been stored into
+  @param id      Key the certificate was stored under
+ @endsignaldef
+
+ @signaldef certificate-deleted
+  @signalproto
+void (*certificate_deleted)(PurpleCertificatePool *pool, const gchar *id, gpointer data);
+  @endsignalproto
+  @signaldesc
+   Emitted when a pool deletes a certificate. Connect to the pool instance.
+  @param pool    Pool the certificate was deleted from
+  @param id      Key that was deleted
+ @endsignaldef
+
+ */
+// vim: syntax=c tw=75 et
--- a/libpurple/Makefile.am	Tue Aug 28 00:13:20 2007 +0000
+++ b/libpurple/Makefile.am	Tue Aug 28 01:14:08 2007 +0000
@@ -36,6 +36,7 @@
 	accountopt.c \
 	blist.c \
 	buddyicon.c \
+	certificate.c \
 	cipher.c \
 	circbuffer.c \
 	cmds.c \
@@ -85,6 +86,7 @@
 	accountopt.h \
 	blist.h \
 	buddyicon.h \
+	certificate.h \
 	cipher.h \
 	circbuffer.h \
 	cmds.h \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/certificate.c	Tue Aug 28 01:14:08 2007 +0000
@@ -0,0 +1,1817 @@
+/**
+ * @file certificate.c Public-Key Certificate API
+ * @ingroup core
+ */
+
+/*
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <glib.h>
+
+#include "certificate.h"
+#include "debug.h"
+#include "internal.h"
+#include "request.h"
+#include "signals.h"
+#include "util.h"
+
+/** List holding pointers to all registered certificate schemes */
+static GList *cert_schemes = NULL;
+/** List of registered Verifiers */
+static GList *cert_verifiers = NULL;
+/** List of registered Pools */
+static GList *cert_pools = NULL;
+
+void
+purple_certificate_verify (PurpleCertificateVerifier *verifier,
+			   const gchar *subject_name, GList *cert_chain,
+			   PurpleCertificateVerifiedCallback cb,
+			   gpointer cb_data)
+{
+	PurpleCertificateVerificationRequest *vrq;
+	PurpleCertificateScheme *scheme;
+	
+	g_return_if_fail(subject_name != NULL);
+	/* If you don't have a cert to check, why are you requesting that it
+	   be verified? */
+	g_return_if_fail(cert_chain != NULL);
+	g_return_if_fail(cb != NULL);
+
+	/* Look up the CertificateScheme */
+	scheme = purple_certificate_find_scheme(verifier->scheme_name);
+	g_return_if_fail(scheme);
+
+	/* Check that at least the first cert in the chain matches the
+	   Verifier scheme */
+	g_return_if_fail(scheme ==
+			 ((PurpleCertificate *) (cert_chain->data))->scheme);
+
+	/* Construct and fill in the request fields */
+	vrq = g_new0(PurpleCertificateVerificationRequest, 1);
+	vrq->verifier = verifier;
+	vrq->scheme = scheme;
+	vrq->subject_name = g_strdup(subject_name);
+	vrq->cert_chain = purple_certificate_copy_list(cert_chain);
+	vrq->cb = cb;
+	vrq->cb_data = cb_data;
+
+	/* Initiate verification */
+	(verifier->start_verification)(vrq);
+}
+
+void
+purple_certificate_verify_complete(PurpleCertificateVerificationRequest *vrq,
+				   PurpleCertificateVerificationStatus st)
+{
+	PurpleCertificateVerifier *vr;
+
+	g_return_if_fail(vrq);
+
+	/* Pass the results on to the request's callback */
+	(vrq->cb)(st, vrq->cb_data);
+
+	/* And now to eliminate the request */
+	/* Fetch the Verifier responsible... */
+	vr = vrq->verifier;
+	/* ...and order it to KILL */
+	(vr->destroy_request)(vrq);
+
+	/* Now the internals have been cleaned up, so clean up the libpurple-
+	   created elements */
+	g_free(vrq->subject_name);
+	purple_certificate_destroy_list(vrq->cert_chain);
+
+	/*  A structure born
+	 *          to much ado
+	 *                   and with so much within.
+	 * It reaches now
+	 *             its quiet end. */
+	g_free(vrq);
+}
+
+
+PurpleCertificate *
+purple_certificate_copy(PurpleCertificate *crt)
+{
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme, NULL);
+	g_return_val_if_fail(crt->scheme->copy_certificate, NULL);
+
+	return (crt->scheme->copy_certificate)(crt);
+}
+
+GList *
+purple_certificate_copy_list(GList *crt_list)
+{
+	GList *new, *l;
+
+	/* First, make a shallow copy of the list */
+	new = g_list_copy(crt_list);
+
+	/* Now go through and actually duplicate each certificate */
+	for (l = new; l; l = l->next) {
+		l->data = purple_certificate_copy(l->data);
+	}
+
+	return new;
+}
+
+void
+purple_certificate_destroy (PurpleCertificate *crt)
+{
+	PurpleCertificateScheme *scheme;
+	
+	if (NULL == crt) return;
+
+	scheme = crt->scheme;
+
+	(scheme->destroy_certificate)(crt);
+}
+
+void
+purple_certificate_destroy_list (GList * crt_list)
+{
+	PurpleCertificate *crt;
+	GList *l;
+
+	for (l=crt_list; l; l = l->next) {
+		crt = (PurpleCertificate *) l->data;
+		purple_certificate_destroy(crt);
+	}
+
+	g_list_free(crt_list);
+}
+
+gboolean
+purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer)
+{
+	PurpleCertificateScheme *scheme;
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(issuer, FALSE);
+
+	scheme = crt->scheme;
+	g_return_val_if_fail(scheme, FALSE);
+	/* We can't compare two certs of unrelated schemes, obviously */
+	g_return_val_if_fail(issuer->scheme == scheme, FALSE);
+
+	return (scheme->signed_by)(crt, issuer);
+}
+
+gboolean
+purple_certificate_check_signature_chain(GList *chain)
+{
+	GList *cur;
+	PurpleCertificate *crt, *issuer;
+	gchar *uid;
+
+	g_return_val_if_fail(chain, FALSE);
+
+	uid = purple_certificate_get_unique_id((PurpleCertificate *) chain->data);
+	purple_debug_info("certificate",
+			  "Checking signature chain for uid=%s\n",
+			  uid);
+	g_free(uid);
+	
+	/* If this is a single-certificate chain, say that it is valid */
+	if (chain->next == NULL) {
+		purple_debug_info("certificate",
+				  "...Singleton. We'll say it's valid.\n");
+		return TRUE;
+	}
+
+	/* Load crt with the first certificate */
+	crt = (PurpleCertificate *)(chain->data);
+	/* And start with the second certificate in the chain */
+	for ( cur = chain->next; cur; cur = cur->next ) {
+		
+		issuer = (PurpleCertificate *)(cur->data);
+		
+		/* Check the signature for this link */
+		if (! purple_certificate_signed_by(crt, issuer) ) {
+			uid = purple_certificate_get_unique_id(issuer);
+			purple_debug_info("certificate",
+					  "...Bad or missing signature by %s\nChain is INVALID\n",
+					  uid);
+			g_free(uid);
+		
+			return FALSE;
+		}
+
+		uid = purple_certificate_get_unique_id(issuer);
+		purple_debug_info("certificate",
+				  "...Good signature by %s\n",
+				  uid);
+		g_free(uid);
+		
+		/* The issuer is now the next crt whose signature is to be
+		   checked */
+		crt = issuer;
+	}
+
+	/* If control reaches this point, the chain is valid */
+	purple_debug_info("certificate", "Chain is VALID\n");
+	return TRUE;
+}
+
+PurpleCertificate *
+purple_certificate_import(PurpleCertificateScheme *scheme, const gchar *filename)
+{
+	g_return_val_if_fail(scheme, NULL);
+	g_return_val_if_fail(scheme->import_certificate, NULL);
+	g_return_val_if_fail(filename, NULL);
+
+	return (scheme->import_certificate)(filename);
+}
+
+gboolean
+purple_certificate_export(const gchar *filename, PurpleCertificate *crt)
+{
+	PurpleCertificateScheme *scheme;
+
+	g_return_val_if_fail(filename, FALSE);
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme, FALSE);
+
+	scheme = crt->scheme;
+	g_return_val_if_fail(scheme->export_certificate, FALSE);
+
+	return (scheme->export_certificate)(filename, crt);
+}
+
+GByteArray *
+purple_certificate_get_fingerprint_sha1(PurpleCertificate *crt)
+{
+	PurpleCertificateScheme *scheme;
+	GByteArray *fpr;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme, NULL);
+
+	scheme = crt->scheme;
+	
+	g_return_val_if_fail(scheme->get_fingerprint_sha1, NULL);
+
+	fpr = (scheme->get_fingerprint_sha1)(crt);
+
+	return fpr;
+}
+
+gchar *
+purple_certificate_get_unique_id(PurpleCertificate *crt)
+{
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme, NULL);
+	g_return_val_if_fail(crt->scheme->get_unique_id, NULL);
+
+	return (crt->scheme->get_unique_id)(crt);
+}
+
+gchar *
+purple_certificate_get_issuer_unique_id(PurpleCertificate *crt)
+{
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme, NULL);
+	g_return_val_if_fail(crt->scheme->get_issuer_unique_id, NULL);
+
+	return (crt->scheme->get_issuer_unique_id)(crt);
+}
+
+gchar *
+purple_certificate_get_subject_name(PurpleCertificate *crt)
+{
+	PurpleCertificateScheme *scheme;
+	gchar *subject_name;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme, NULL);
+
+	scheme = crt->scheme;
+
+	g_return_val_if_fail(scheme->get_subject_name, NULL);
+
+	subject_name = (scheme->get_subject_name)(crt);
+
+	return subject_name;
+}
+
+gboolean
+purple_certificate_check_subject_name(PurpleCertificate *crt, const gchar *name)
+{
+	PurpleCertificateScheme *scheme;
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme, FALSE);
+	g_return_val_if_fail(name, FALSE);
+
+	scheme = crt->scheme;
+
+	/* TODO: Instead of failing, maybe use get_subject_name and strcmp? */
+	g_return_val_if_fail(scheme->check_subject_name, FALSE);
+
+	return (scheme->check_subject_name)(crt, name);
+}
+
+gboolean
+purple_certificate_get_times(PurpleCertificate *crt, time_t *activation, time_t *expiration)
+{
+	PurpleCertificateScheme *scheme;
+
+	g_return_val_if_fail(crt, FALSE);
+
+	scheme = crt->scheme;
+	
+	g_return_val_if_fail(scheme, FALSE);
+
+	/* If both provided references are NULL, what are you doing calling
+	   this? */
+	g_return_val_if_fail( (activation != NULL) || (expiration != NULL), FALSE);
+
+	/* Throw the request on down to the certscheme */
+	return (scheme->get_times)(crt, activation, expiration);
+}
+
+
+gchar *
+purple_certificate_pool_mkpath(PurpleCertificatePool *pool, const gchar *id)
+{
+	gchar *path;
+	gchar *esc_scheme_name, *esc_name, *esc_id;
+	
+	g_return_val_if_fail(pool, NULL);
+	g_return_val_if_fail(pool->scheme_name, NULL);
+	g_return_val_if_fail(pool->name, NULL);
+
+	/* Escape all the elements for filesystem-friendliness */
+	esc_scheme_name = pool ? g_strdup(purple_escape_filename(pool->scheme_name)) : NULL;
+	esc_name = pool ? g_strdup(purple_escape_filename(pool->name)) : NULL;
+	esc_id = id ? g_strdup(purple_escape_filename(id)) : NULL;
+	
+	path = g_build_filename(purple_user_dir(),
+				"certificates", /* TODO: constantize this? */
+				esc_scheme_name,
+				esc_name,
+				esc_id,
+				NULL);
+
+	g_free(esc_scheme_name);
+	g_free(esc_name);
+	g_free(esc_id);
+	return path;
+}
+
+gboolean
+purple_certificate_pool_usable(PurpleCertificatePool *pool)
+{
+	g_return_val_if_fail(pool, FALSE);
+	g_return_val_if_fail(pool->scheme_name, FALSE);
+
+	/* Check that the pool's scheme is loaded */
+	if (purple_certificate_find_scheme(pool->scheme_name) == NULL) {
+		return FALSE;
+	}
+	
+	return TRUE;
+}
+
+PurpleCertificateScheme *
+purple_certificate_pool_get_scheme(PurpleCertificatePool *pool)
+{
+	g_return_val_if_fail(pool, NULL);
+	g_return_val_if_fail(pool->scheme_name, NULL);
+
+	return purple_certificate_find_scheme(pool->scheme_name);
+}
+
+gboolean
+purple_certificate_pool_contains(PurpleCertificatePool *pool, const gchar *id)
+{
+	g_return_val_if_fail(pool, FALSE);
+	g_return_val_if_fail(id, FALSE);
+	g_return_val_if_fail(pool->cert_in_pool, FALSE);
+
+	return (pool->cert_in_pool)(id);
+}
+
+PurpleCertificate *
+purple_certificate_pool_retrieve(PurpleCertificatePool *pool, const gchar *id)
+{
+	g_return_val_if_fail(pool, NULL);
+	g_return_val_if_fail(id, NULL);
+	g_return_val_if_fail(pool->get_cert, NULL);
+
+	return (pool->get_cert)(id);
+}
+
+gboolean
+purple_certificate_pool_store(PurpleCertificatePool *pool, const gchar *id, PurpleCertificate *crt)
+{
+	gboolean ret = FALSE;
+	
+	g_return_val_if_fail(pool, FALSE);
+	g_return_val_if_fail(id, FALSE);
+	g_return_val_if_fail(pool->put_cert, FALSE);
+
+	/* Whether crt->scheme matches find_scheme(pool->scheme_name) is not
+	   relevant... I think... */
+	g_return_val_if_fail(
+		g_ascii_strcasecmp(pool->scheme_name, crt->scheme->name) == 0,
+		FALSE);
+
+	ret = (pool->put_cert)(id, crt);
+
+	/* Signal that the certificate was stored if success*/
+	if (ret) {
+		purple_signal_emit(pool, "certificate-stored",
+				   pool, id);
+	}
+
+	return ret;
+}	
+
+gboolean
+purple_certificate_pool_delete(PurpleCertificatePool *pool, const gchar *id)
+{
+	gboolean ret = FALSE;
+	
+	g_return_val_if_fail(pool, FALSE);
+	g_return_val_if_fail(id, FALSE);
+	g_return_val_if_fail(pool->delete_cert, FALSE);
+
+	ret = (pool->delete_cert)(id);
+
+	/* Signal that the certificate was deleted if success */
+	if (ret) {
+		purple_signal_emit(pool, "certificate-deleted",
+				   pool, id);
+	}
+
+	return ret;
+}
+
+GList *
+purple_certificate_pool_get_idlist(PurpleCertificatePool *pool)
+{
+	g_return_val_if_fail(pool, NULL);
+	g_return_val_if_fail(pool->get_idlist, NULL);
+
+	return (pool->get_idlist)();
+}
+
+void
+purple_certificate_pool_destroy_idlist(GList *idlist)
+{
+	GList *l;
+	
+	/* Iterate through and free them strings */
+	for ( l = idlist; l; l = l->next ) {
+		g_free(l->data);
+	}
+
+	g_list_free(idlist);
+}
+
+
+/****************************************************************************/
+/* Builtin Verifiers, Pools, etc.                                           */
+/****************************************************************************/
+
+static void
+x509_singleuse_verify_cb (PurpleCertificateVerificationRequest *vrq, gint id)
+{
+	g_return_if_fail(vrq);
+
+	purple_debug_info("certificate/x509_singleuse",
+			  "VRQ on cert from %s gave %d\n",
+			  vrq->subject_name, id);
+
+	/* Signal what happened back to the caller */
+	if (1 == id) {		
+		/* Accepted! */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_VALID);
+	} else {
+		/* Not accepted */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+
+	}
+}
+
+static void
+x509_singleuse_start_verify (PurpleCertificateVerificationRequest *vrq)
+{
+	gchar *sha_asc;
+	GByteArray *sha_bin;
+	gchar *cn;
+	const gchar *cn_match;
+	gchar *primary, *secondary;
+	PurpleCertificate *crt = (PurpleCertificate *) vrq->cert_chain->data;
+
+	/* Pull out the SHA1 checksum */
+	sha_bin = purple_certificate_get_fingerprint_sha1(crt);
+	/* Now decode it for display */
+	sha_asc = purple_base16_encode_chunked(sha_bin->data,
+					       sha_bin->len);
+
+	/* Get the cert Common Name */
+	cn = purple_certificate_get_subject_name(crt);
+
+	/* Determine whether the name matches */
+	if (purple_certificate_check_subject_name(crt, vrq->subject_name)) {
+		cn_match = _("");
+	} else {
+		cn_match = _("(DOES NOT MATCH)");
+	}
+	
+	/* Make messages */
+	primary = g_strdup_printf(_("%s has presented the following certificate for just-this-once use:"), vrq->subject_name);
+	secondary = g_strdup_printf(_("Common name: %s %s\nFingerprint (SHA1): %s"), cn, cn_match, sha_asc);
+	
+	/* Make a semi-pretty display */
+	purple_request_accept_cancel(
+		vrq->cb_data, /* TODO: Find what the handle ought to be */
+		_("Single-use Certificate Verification"),
+		primary,
+		secondary,
+		1,            /* Accept by default */
+		NULL,         /* No account */
+		NULL,         /* No other user */
+		NULL,         /* No associated conversation */
+		vrq,
+		x509_singleuse_verify_cb,
+		x509_singleuse_verify_cb );
+	
+	/* Cleanup */
+	g_free(primary);
+	g_free(secondary);
+	g_free(sha_asc);
+	g_byte_array_free(sha_bin, TRUE);
+}
+
+static void
+x509_singleuse_destroy_request (PurpleCertificateVerificationRequest *vrq)
+{
+	/* I don't do anything! */
+}
+
+PurpleCertificateVerifier x509_singleuse = {
+	"x509",                         /* Scheme name */
+	"singleuse",                    /* Verifier name */
+	x509_singleuse_start_verify,    /* start_verification function */
+	x509_singleuse_destroy_request  /* Request cleanup operation */
+};
+
+
+
+/***** X.509 Certificate Authority pool, keyed by Distinguished Name *****/
+/* This is implemented in what may be the most inefficient and bugprone way
+   possible; however, future optimizations should not be difficult. */
+
+static PurpleCertificatePool x509_ca;
+
+/** Holds a key-value pair for quickish certificate lookup */
+typedef struct {
+	gchar *dn;
+	PurpleCertificate *crt;
+} x509_ca_element;
+
+static void
+x509_ca_element_free(x509_ca_element *el)
+{
+	if (NULL == el) return;
+
+	g_free(el->dn);
+	purple_certificate_destroy(el->crt);
+	g_free(el);
+}
+
+/** System directory to probe for CA certificates */
+/* This is set in the lazy_init function */
+static const gchar *x509_ca_syspath = NULL;
+
+/** A list of loaded CAs, populated from the above path whenever the lazy_init
+    happens. Contains pointers to x509_ca_elements */
+static GList *x509_ca_certs = NULL;
+
+/** Used for lazy initialization purposes. */
+static gboolean x509_ca_initialized = FALSE;
+
+/** Adds a certificate to the in-memory cache, doing nothing else */
+static gboolean
+x509_ca_quiet_put_cert(PurpleCertificate *crt)
+{
+	x509_ca_element *el;
+
+	/* lazy_init calls this function, so calling lazy_init here is a
+	   Bad Thing */
+	
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme, FALSE);
+	/* Make sure that this is some kind of X.509 certificate */
+	/* TODO: Perhaps just check crt->scheme->name instead? */
+	g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_ca.scheme_name), FALSE);
+	
+	el = g_new0(x509_ca_element, 1);
+	el->dn = purple_certificate_get_unique_id(crt);
+	el->crt = purple_certificate_copy(crt);
+	x509_ca_certs = g_list_prepend(x509_ca_certs, el);
+
+	return TRUE;
+}
+
+/* Since the libpurple CertificatePools get registered before plugins are
+   loaded, an X.509 Scheme is generally not available when x509_ca_init is
+   called, but x509_ca requires X.509 operations in order to properly load.
+
+   To solve this, I present the lazy_init function. It attempts to finish
+   initialization of the Pool, but it usually fails when it is called from
+   x509_ca_init. However, this is OK; initialization is then simply deferred
+   until someone tries to use functions from the pool. */
+static gboolean
+x509_ca_lazy_init(void)
+{
+	PurpleCertificateScheme *x509;
+	GDir *certdir;
+	const gchar *entry;
+	GPatternSpec *pempat;
+	
+	if (x509_ca_initialized) return TRUE;
+
+	/* Check that X.509 is registered */
+	x509 = purple_certificate_find_scheme(x509_ca.scheme_name);
+	if ( !x509 ) {
+		purple_debug_info("certificate/x509/ca",
+				  "Lazy init failed because an X.509 Scheme "
+				  "is not yet registered. Maybe it will be "
+				  "better later.\n");
+		return FALSE;
+	}
+
+	/* Attempt to point at the appropriate system path */
+	if (NULL == x509_ca_syspath) {
+		x509_ca_syspath = g_build_filename(DATADIR,
+						   "purple", "ca-certs", NULL);
+	}
+
+	/* Populate the certificates pool from the system path */
+	certdir = g_dir_open(x509_ca_syspath, 0, NULL);
+	g_return_val_if_fail(certdir, FALSE);
+
+	/* Use a glob to only read .pem files */
+	pempat = g_pattern_spec_new("*.pem");
+	
+	while ( (entry = g_dir_read_name(certdir)) ) {
+		gchar *fullpath;
+		PurpleCertificate *crt;
+
+		if ( !g_pattern_match_string(pempat, entry) ) {
+			continue;
+		}
+
+		fullpath = g_build_filename(x509_ca_syspath, entry, NULL);
+		
+		/* TODO: Respond to a failure in the following? */
+		crt = purple_certificate_import(x509, fullpath);
+
+		if (x509_ca_quiet_put_cert(crt)) {
+			purple_debug_info("certificate/x509/ca",
+					  "Loaded %s\n",
+					  fullpath);
+		} else {
+			purple_debug_error("certificate/x509/ca",
+					  "Failed to load %s\n",
+					  fullpath);
+		}
+
+		purple_certificate_destroy(crt);
+		g_free(fullpath);
+	}
+
+	g_pattern_spec_free(pempat);
+	g_dir_close(certdir);
+	
+	purple_debug_info("certificate/x509/ca",
+			  "Lazy init completed.\n");
+	x509_ca_initialized = TRUE;
+	return TRUE;
+}
+
+static gboolean
+x509_ca_init(void)
+{
+	/* Attempt to initialize now, but if it doesn't work, that's OK;
+	   it will get done later */
+	if ( ! x509_ca_lazy_init()) {
+		purple_debug_info("certificate/x509/ca",
+				  "Init failed, probably because a "
+				  "dependency is not yet registered. "
+				  "It has been deferred to later.\n");
+	}
+	
+	return TRUE;
+}
+
+static void
+x509_ca_uninit(void)
+{
+	GList *l;
+
+	for (l = x509_ca_certs; l; l = l->next) {
+		x509_ca_element *el = l->data;
+		x509_ca_element_free(el);
+	}
+	g_list_free(x509_ca_certs);
+	x509_ca_certs = NULL;
+	x509_ca_initialized = FALSE;
+}
+
+/** Look up a ca_element by dn */
+static x509_ca_element *
+x509_ca_locate_cert(GList *lst, const gchar *dn)
+{
+	GList *cur;
+
+	for (cur = lst; cur; cur = cur->next) {
+		x509_ca_element *el = cur->data;
+		/* TODO: Unsafe? */
+		if ( !strcmp(dn, el->dn) ) {
+			return el;
+		}
+	}
+	return NULL;
+}
+
+static gboolean
+x509_ca_cert_in_pool(const gchar *id)
+{
+	g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
+	g_return_val_if_fail(id, FALSE);
+
+	if (x509_ca_locate_cert(x509_ca_certs, id) != NULL) {
+		return TRUE;
+	} else {
+		return FALSE;
+	}
+
+	return FALSE;
+}
+
+static PurpleCertificate *
+x509_ca_get_cert(const gchar *id)
+{
+	PurpleCertificate *crt = NULL;
+	x509_ca_element *el;
+
+	g_return_val_if_fail(x509_ca_lazy_init(), NULL);
+	g_return_val_if_fail(id, NULL);
+
+	/* Search the memory-cached pool */
+	el = x509_ca_locate_cert(x509_ca_certs, id);
+
+	if (el != NULL) {
+		/* Make a copy of the memcached one for the function caller
+		   to play with */
+		crt = purple_certificate_copy(el->crt);
+	} else {
+		crt = NULL;
+	}
+	
+	return crt;
+}
+
+static gboolean
+x509_ca_put_cert(const gchar *id, PurpleCertificate *crt)
+{
+	gboolean ret = FALSE;
+	
+	g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
+
+	/* TODO: This is a quick way of doing this. At some point the change
+	   ought to be flushed to disk somehow. */
+	ret = x509_ca_quiet_put_cert(crt);
+
+	return ret;
+}
+
+static gboolean
+x509_ca_delete_cert(const gchar *id)
+{
+	x509_ca_element *el;
+	
+	g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
+	g_return_val_if_fail(id, FALSE);
+
+	/* Is the id even in the pool? */
+	el = x509_ca_locate_cert(x509_ca_certs, id);
+	if ( el == NULL ) {
+		purple_debug_warning("certificate/x509/ca",
+				     "Id %s wasn't in the pool\n",
+				     id);
+		return FALSE;
+	}
+
+	/* Unlink it from the memory cache and destroy it */
+	x509_ca_certs = g_list_remove(x509_ca_certs, el);
+	x509_ca_element_free(el);
+	
+	return TRUE;
+}
+
+static GList *
+x509_ca_get_idlist(void)
+{
+	GList *l, *idlist;
+	
+	g_return_val_if_fail(x509_ca_lazy_init(), NULL);
+
+	idlist = NULL;
+	for (l = x509_ca_certs; l; l = l->next) {
+		x509_ca_element *el = l->data;
+		idlist = g_list_prepend(idlist, g_strdup(el->dn));
+	}
+	
+	return idlist;
+}
+
+
+static PurpleCertificatePool x509_ca = {
+	"x509",                       /* Scheme name */
+	"ca",                         /* Pool name */
+	N_("Certificate Authorities"),/* User-friendly name */
+	NULL,                         /* Internal data */
+	x509_ca_init,                 /* init */
+	x509_ca_uninit,               /* uninit */
+	x509_ca_cert_in_pool,         /* Certificate exists? */
+	x509_ca_get_cert,             /* Cert retriever */
+	x509_ca_put_cert,             /* Cert writer */
+	x509_ca_delete_cert,          /* Cert remover */
+	x509_ca_get_idlist            /* idlist retriever */
+};
+
+
+
+/***** Cache of certificates given by TLS/SSL peers *****/
+static PurpleCertificatePool x509_tls_peers;
+
+static gboolean
+x509_tls_peers_init(void)
+{
+	gchar *poolpath;
+	int ret;
+	
+	/* Set up key cache here if it isn't already done */
+	poolpath = purple_certificate_pool_mkpath(&x509_tls_peers, NULL);
+	ret = purple_build_dir(poolpath, 0700); /* Make it this user only */
+
+	g_free(poolpath);
+
+	g_return_val_if_fail(ret == 0, FALSE);
+	return TRUE;
+}
+
+static gboolean
+x509_tls_peers_cert_in_pool(const gchar *id)
+{
+	gchar *keypath;
+	gboolean ret = FALSE;
+	
+	g_return_val_if_fail(id, FALSE);
+
+	keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
+
+	ret = g_file_test(keypath, G_FILE_TEST_IS_REGULAR);
+	
+	g_free(keypath);
+	return ret;
+}
+
+static PurpleCertificate *
+x509_tls_peers_get_cert(const gchar *id)
+{
+	PurpleCertificateScheme *x509;
+	PurpleCertificate *crt;
+	gchar *keypath;
+	
+	g_return_val_if_fail(id, NULL);
+
+	/* Is it in the pool? */
+	if ( !x509_tls_peers_cert_in_pool(id) ) {
+		return NULL;
+	}
+	
+	/* Look up the X.509 scheme */
+	x509 = purple_certificate_find_scheme("x509");
+	g_return_val_if_fail(x509, NULL);
+
+	/* Okay, now find and load that key */
+	keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
+	crt = purple_certificate_import(x509, keypath);
+
+	g_free(keypath);
+
+	return crt;
+}
+
+static gboolean
+x509_tls_peers_put_cert(const gchar *id, PurpleCertificate *crt)
+{
+	gboolean ret = FALSE;
+	gchar *keypath;
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme, FALSE);
+	/* Make sure that this is some kind of X.509 certificate */
+	/* TODO: Perhaps just check crt->scheme->name instead? */
+	g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_tls_peers.scheme_name), FALSE);
+
+	/* Work out the filename and export */
+	keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
+	ret = purple_certificate_export(keypath, crt);
+	
+	g_free(keypath);
+	return ret;
+}
+
+static gboolean
+x509_tls_peers_delete_cert(const gchar *id)
+{
+	gboolean ret = FALSE;
+	gchar *keypath;
+
+	g_return_val_if_fail(id, FALSE);
+
+	/* Is the id even in the pool? */
+	if (!x509_tls_peers_cert_in_pool(id)) {
+		purple_debug_warning("certificate/tls_peers",
+				     "Id %s wasn't in the pool\n",
+				     id);
+		return FALSE;
+	}
+
+	/* OK, so work out the keypath and delete the thing */
+	keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);	
+	if ( unlink(keypath) != 0 ) {
+		purple_debug_error("certificate/tls_peers",
+				   "Unlink of %s failed!\n",
+				   keypath);
+		ret = FALSE;
+	} else {
+		ret = TRUE;
+	}
+
+	g_free(keypath);
+	return ret;
+}
+
+static GList *
+x509_tls_peers_get_idlist(void)
+{
+	GList *idlist = NULL;
+	GDir *dir;
+	const gchar *entry;
+	gchar *poolpath;
+
+	/* Get a handle on the pool directory */
+	poolpath = purple_certificate_pool_mkpath(&x509_tls_peers, NULL);
+	dir = g_dir_open(poolpath,
+			 0,     /* No flags */
+			 NULL); /* Not interested in what the error is */
+	g_free(poolpath);
+
+	g_return_val_if_fail(dir, NULL);
+
+	/* Traverse the directory listing and create an idlist */
+	while ( (entry = g_dir_read_name(dir)) != NULL ) {
+		/* Unescape the filename */
+		const char *unescaped = purple_unescape_filename(entry);
+		
+		/* Copy the entry name into our list (GLib owns the original
+		   string) */
+		idlist = g_list_prepend(idlist, g_strdup(unescaped));
+	}
+
+	/* Release the directory */
+	g_dir_close(dir);
+	
+	return idlist;
+}
+
+static PurpleCertificatePool x509_tls_peers = {
+	"x509",                       /* Scheme name */
+	"tls_peers",                  /* Pool name */
+	N_("SSL Peers Cache"),        /* User-friendly name */
+	NULL,                         /* Internal data */
+	x509_tls_peers_init,          /* init */
+	NULL,                         /* uninit not required */
+	x509_tls_peers_cert_in_pool,  /* Certificate exists? */
+	x509_tls_peers_get_cert,      /* Cert retriever */
+	x509_tls_peers_put_cert,      /* Cert writer */
+	x509_tls_peers_delete_cert,   /* Cert remover */
+	x509_tls_peers_get_idlist     /* idlist retriever */
+};
+
+
+/***** A Verifier that uses the tls_peers cache and the CA pool to validate certificates *****/
+static PurpleCertificateVerifier x509_tls_cached;
+
+
+/* The following is several hacks piled together and needs to be fixed.
+ * It exists because show_cert (see its comments) needs the original reason
+ * given to user_auth in order to rebuild the dialog.
+ */
+/* TODO: This will cause a ua_ctx to become memleaked if the request(s) get
+   closed by handle or otherwise abnormally. */
+typedef struct {
+	PurpleCertificateVerificationRequest *vrq;
+	gchar *reason;
+} x509_tls_cached_ua_ctx;
+
+static x509_tls_cached_ua_ctx *
+x509_tls_cached_ua_ctx_new(PurpleCertificateVerificationRequest *vrq,
+			   const gchar *reason)
+{
+	x509_tls_cached_ua_ctx *c;
+
+	c = g_new0(x509_tls_cached_ua_ctx, 1);
+	c->vrq = vrq;
+	c->reason = g_strdup(reason);
+
+	return c;
+}
+
+
+static void
+x509_tls_cached_ua_ctx_free(x509_tls_cached_ua_ctx *c)
+{
+	g_return_if_fail(c);
+	g_free(c->reason);
+	g_free(c);
+}
+
+static void
+x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq,
+			  const gchar *reason);
+
+static void
+x509_tls_cached_show_cert(x509_tls_cached_ua_ctx *c, gint id)
+{
+	PurpleCertificate *disp_crt = c->vrq->cert_chain->data;
+	purple_certificate_display_x509(disp_crt);
+
+	/* Since clicking a button closes the request, show it again */
+	x509_tls_cached_user_auth(c->vrq, c->reason);
+
+	x509_tls_cached_ua_ctx_free(c);
+}
+
+static void
+x509_tls_cached_user_auth_cb (x509_tls_cached_ua_ctx *c, gint id)
+{
+	PurpleCertificateVerificationRequest *vrq;
+	PurpleCertificatePool *tls_peers;
+
+	g_return_if_fail(c);
+	g_return_if_fail(c->vrq);
+	
+	vrq = c->vrq;
+
+	x509_tls_cached_ua_ctx_free(c);
+
+	tls_peers = purple_certificate_find_pool("x509","tls_peers");
+
+	if (2 == id) {
+		gchar *cache_id = vrq->subject_name;
+		purple_debug_info("certificate/x509/tls_cached",
+				  "User ACCEPTED cert\nCaching first in chain for future use as %s...\n",
+				  cache_id);
+		
+		purple_certificate_pool_store(tls_peers, cache_id,
+					      vrq->cert_chain->data);
+
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_VALID);
+	} else {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "User REJECTED cert\n");
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+	}
+}
+
+/** Validates a certificate by asking the user
+ * @param reason    String to explain why the user needs to accept/refuse the
+ *                  certificate.
+ * @todo Needs a handle argument
+ */
+static void
+x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq,
+			  const gchar *reason)
+{
+	gchar *primary;
+
+	/* Make messages */
+	primary = g_strdup_printf(_("Accept certificate for %s?"),
+				  vrq->subject_name);
+		
+	/* Make a semi-pretty display */
+	purple_request_action(
+		vrq->cb_data, /* TODO: Find what the handle ought to be */
+		_("SSL Certificate Verification"),
+		primary,
+		reason,
+		2,            /* Accept by default */
+		NULL,         /* No account */
+		NULL,         /* No other user */
+		NULL,         /* No associated conversation */
+		x509_tls_cached_ua_ctx_new(vrq, reason),
+		3,            /* Number of actions */
+		_("Yes"), x509_tls_cached_user_auth_cb,
+		_("No"),  x509_tls_cached_user_auth_cb,
+		_("_View Certificate..."), x509_tls_cached_show_cert);
+	
+	/* Cleanup */
+	g_free(primary);
+}
+
+static void
+x509_tls_cached_peer_cert_changed(PurpleCertificateVerificationRequest *vrq)
+{
+	/* TODO: Prompt the user, etc. */
+
+	purple_debug_info("certificate/x509/tls_cached",
+			  "Certificate for %s does not match cached. "
+			  "Auto-rejecting!\n",
+			  vrq->subject_name);
+	
+	purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_INVALID);
+	return;
+}
+
+static void
+x509_tls_cached_cert_in_cache(PurpleCertificateVerificationRequest *vrq)
+{
+	/* TODO: Looking this up by name over and over is expensive.
+	   Fix, please! */
+	PurpleCertificatePool *tls_peers =
+		purple_certificate_find_pool(x509_tls_cached.scheme_name,
+					     "tls_peers");
+
+	/* The peer's certificate should be the first in the list */
+	PurpleCertificate *peer_crt =
+		(PurpleCertificate *) vrq->cert_chain->data;
+	
+	PurpleCertificate *cached_crt;
+	GByteArray *peer_fpr, *cached_fpr;
+
+	/* Load up the cached certificate */
+	cached_crt = purple_certificate_pool_retrieve(
+		tls_peers, vrq->subject_name);
+	g_assert(cached_crt);
+
+	/* Now get SHA1 sums for both and compare them */
+	/* TODO: This is not an elegant way to compare certs */
+	peer_fpr = purple_certificate_get_fingerprint_sha1(peer_crt);
+	cached_fpr = purple_certificate_get_fingerprint_sha1(cached_crt);
+	if (!memcmp(peer_fpr->data, cached_fpr->data, peer_fpr->len)) {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "Peer cert matched cached\n");
+		/* vrq is now finished */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_VALID);
+	} else {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "Peer cert did NOT match cached\n");
+		/* vrq now becomes the problem of cert_changed */
+		x509_tls_cached_peer_cert_changed(vrq);
+	}
+	
+	purple_certificate_destroy(cached_crt);
+	g_byte_array_free(peer_fpr, TRUE);
+	g_byte_array_free(cached_fpr, TRUE);
+}
+
+/* For when we've never communicated with this party before */
+static void
+x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq)
+{
+	PurpleCertificatePool *ca, *tls_peers;
+	PurpleCertificate *end_crt, *ca_crt, *peer_crt;
+	GList *chain = vrq->cert_chain;
+	GList *last;
+	gchar *ca_id;
+
+	peer_crt = (PurpleCertificate *) chain->data;
+
+	/* First, check that the hostname matches */
+	if ( ! purple_certificate_check_subject_name(peer_crt,
+						     vrq->subject_name) ) {
+		gchar *sn = purple_certificate_get_subject_name(peer_crt);
+		gchar *msg;
+		
+		purple_debug_info("certificate/x509/tls_cached",
+				  "Name mismatch: Certificate given for %s "
+				  "has a name of %s\n",
+				  vrq->subject_name, sn);
+
+		/* Prompt the user to authenticate the certificate */
+		/* TODO: Provide the user with more guidance about why he is
+		   being prompted */
+		/* vrq will be completed by user_auth */
+		msg = g_strdup_printf(_("The certificate presented by \"%s\" "
+					"claims to be from \"%s\" instead.  "
+					"This could mean that you are not "
+					"connecting to the service you "
+					"believe you are."),
+				      vrq->subject_name, sn);
+				      
+		x509_tls_cached_user_auth(vrq,msg);
+
+		g_free(sn);
+		g_free(msg);
+		return;
+	} /* if (name mismatch) */
+
+			
+	
+	/* Next, check that the certificate chain is valid */
+	if ( ! purple_certificate_check_signature_chain(chain) ) {
+		/* TODO: Tell the user where the chain broke? */
+		/* TODO: This error will hopelessly confuse any
+		   non-elite user. */
+		gchar *secondary;
+
+		secondary = g_strdup_printf(_("The certificate chain presented"
+					      " for %s is not valid."),
+					    vrq->subject_name);
+
+		/* TODO: Make this error either block the ensuing SSL
+		   connection error until the user dismisses this one, or
+		   stifle it. */
+		purple_notify_error(NULL, /* TODO: Probably wrong. */
+				    _("SSL Certificate Error"),
+				    _("Invalid certificate chain"),
+				    secondary );
+		g_free(secondary);
+
+		/* Okay, we're done here */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+	} /* if (signature chain not good) */
+
+	/* Next, attempt to verify the last certificate against a CA */
+	ca = purple_certificate_find_pool(x509_tls_cached.scheme_name, "ca");
+
+	/* If, for whatever reason, there is no Certificate Authority pool
+	   loaded, we will simply present it to the user for checking. */
+	if ( !ca ) {
+		purple_debug_error("certificate/x509/tls_cached",
+				   "No X.509 Certificate Authority pool "
+				   "could be found!\n");
+
+		/* vrq will be completed by user_auth */
+		x509_tls_cached_user_auth(vrq,_("You have no database of root "
+						"certificates, so this "
+						"certificate cannot be "
+						"validated."));
+		return;
+	}
+
+	last = g_list_last(chain);
+	end_crt = (PurpleCertificate *) last->data;
+
+	/* Attempt to look up the last certificate's issuer */
+	ca_id = purple_certificate_get_issuer_unique_id(end_crt);
+	purple_debug_info("certificate/x509/tls_cached",
+			  "Checking for a CA with DN=%s\n",
+			  ca_id);
+	if ( !purple_certificate_pool_contains(ca, ca_id) ) {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "Certificate Authority with DN='%s' not "
+				  "found. I'll prompt the user, I guess.\n",
+				  ca_id);
+		g_free(ca_id);
+		/* vrq will be completed by user_auth */
+		x509_tls_cached_user_auth(vrq,_("The root certificate this "
+						"one claims to be issued by "
+						"is unknown to Pidgin."));
+		return;
+	}
+
+	ca_crt = purple_certificate_pool_retrieve(ca, ca_id);
+	g_free(ca_id);
+	g_assert(ca_crt);
+	
+	/* Check the signature */
+	if ( !purple_certificate_signed_by(end_crt, ca_crt) ) {
+		/* TODO: If signed_by ever returns a reason, maybe mention
+		   that, too. */
+		/* TODO: Also mention the CA involved. While I could do this
+		   now, a full DN is a little much with which to assault the
+		   user's poor, leaky eyes. */
+		/* TODO: This error message makes my eyes cross, and I wrote it */
+		gchar * secondary =
+			g_strdup_printf(_("The certificate chain presented by "
+					  "%s does not have a valid digital "
+					  "signature from the Certificate "
+					  "Authority from which it claims to "
+					  "have a signature."),
+					vrq->subject_name);
+		
+		purple_notify_error(NULL, /* TODO: Probably wrong */
+				    _("SSL Certificate Error"),
+				    _("Invalid certificate authority"
+				      " signature"),
+				    secondary);
+		g_free(secondary);
+
+		/* Signal "bad cert" */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+		return;
+	} /* if (CA signature not good) */
+
+	/* If we reach this point, the certificate is good. */
+	/* Look up the local cache and store it there for future use */
+	tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,
+						 "tls_peers");
+
+	if (tls_peers) {
+		g_assert(purple_certificate_pool_store(tls_peers,
+						       vrq->subject_name,
+						       peer_crt) );
+	} else {
+		purple_debug_error("certificate/x509/tls_cached",
+				   "Unable to locate tls_peers certificate "
+				   "cache.\n");
+	}
+	
+	/* Whew! Done! */
+	purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID);
+}
+
+static void
+x509_tls_cached_start_verify(PurpleCertificateVerificationRequest *vrq)
+{
+	const gchar *tls_peers_name = "tls_peers"; /* Name of local cache */
+	PurpleCertificatePool *tls_peers;
+
+	g_return_if_fail(vrq);
+
+	purple_debug_info("certificate/x509/tls_cached",
+			  "Starting verify for %s\n",
+			  vrq->subject_name);
+	
+	tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,tls_peers_name);
+
+	/* TODO: This should probably just prompt the user instead of throwing
+	   an angry fit */
+	if (!tls_peers) {
+		purple_debug_error("certificate/x509/tls_cached",
+				   "Couldn't find local peers cache %s\nReturning INVALID to callback\n",
+				   tls_peers_name);
+
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+		return;
+	}
+	
+	/* Check if the peer has a certificate cached already */
+	purple_debug_info("certificate/x509/tls_cached",
+			  "Checking for cached cert...\n");
+	if (purple_certificate_pool_contains(tls_peers, vrq->subject_name)) {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "...Found cached cert\n");
+		/* vrq is now the responsibility of cert_in_cache */
+		x509_tls_cached_cert_in_cache(vrq);
+	} else {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "...Not in cache\n");
+		/* vrq now becomes the problem of unknown_peer */
+		x509_tls_cached_unknown_peer(vrq);
+	}
+}
+
+static void
+x509_tls_cached_destroy_request(PurpleCertificateVerificationRequest *vrq)
+{
+	g_return_if_fail(vrq);
+}
+
+static PurpleCertificateVerifier x509_tls_cached = {
+	"x509",                         /* Scheme name */
+	"tls_cached",                   /* Verifier name */
+	x509_tls_cached_start_verify,   /* Verification begin */
+	x509_tls_cached_destroy_request /* Request cleanup */
+};
+
+/****************************************************************************/
+/* Subsystem                                                                */
+/****************************************************************************/
+void
+purple_certificate_init(void)
+{
+	/* Register builtins */
+	purple_certificate_register_verifier(&x509_singleuse);
+	purple_certificate_register_pool(&x509_ca);
+	purple_certificate_register_pool(&x509_tls_peers);
+	purple_certificate_register_verifier(&x509_tls_cached);
+}
+
+void
+purple_certificate_uninit(void)
+{
+	GList *full_list, *l;
+
+	/* Unregister all Schemes */
+	full_list = g_list_copy(cert_schemes); /* Make a working copy */
+	for (l = full_list; l; l = l->next) {
+		purple_certificate_unregister_scheme(
+			(PurpleCertificateScheme *) l->data );
+	}
+	g_list_free(full_list);
+
+	/* Unregister all Verifiers */
+	full_list = g_list_copy(cert_verifiers); /* Make a working copy */
+	for (l = full_list; l; l = l->next) {
+		purple_certificate_unregister_verifier(
+			(PurpleCertificateVerifier *) l->data );
+	}
+	g_list_free(full_list);
+
+	/* Unregister all Pools */
+	full_list = g_list_copy(cert_pools); /* Make a working copy */
+	for (l = full_list; l; l = l->next) {
+		purple_certificate_unregister_pool(
+			(PurpleCertificatePool *) l->data );
+	}
+	g_list_free(full_list);
+}
+
+gpointer
+purple_certificate_get_handle(void)
+{
+	static gint handle;
+	return &handle;
+}
+
+PurpleCertificateScheme *
+purple_certificate_find_scheme(const gchar *name)
+{
+	PurpleCertificateScheme *scheme = NULL;
+	GList *l;
+
+	g_return_val_if_fail(name, NULL);
+
+	/* Traverse the list of registered schemes and locate the
+	   one whose name matches */
+	for(l = cert_schemes; l; l = l->next) {
+		scheme = (PurpleCertificateScheme *)(l->data);
+
+		/* Name matches? that's our man */
+		if(!g_ascii_strcasecmp(scheme->name, name))
+			return scheme;
+	}
+
+	purple_debug_warning("certificate",
+			     "CertificateScheme %s requested but not found.\n",
+			     name);
+
+	/* TODO: Signalling and such? */
+	
+	return NULL;
+}
+
+GList *
+purple_certificate_get_schemes(void)
+{
+	return cert_schemes;
+}
+
+gboolean
+purple_certificate_register_scheme(PurpleCertificateScheme *scheme)
+{
+	g_return_val_if_fail(scheme != NULL, FALSE);
+
+	/* Make sure no scheme is registered with the same name */
+	if (purple_certificate_find_scheme(scheme->name) != NULL) {
+		return FALSE;
+	}
+
+	/* Okay, we're golden. Register it. */
+	cert_schemes = g_list_prepend(cert_schemes, scheme);
+
+	/* TODO: Signalling and such? */
+
+	purple_debug_info("certificate",
+			  "CertificateScheme %s registered\n",
+			  scheme->name);
+	
+	return TRUE;
+}
+
+gboolean
+purple_certificate_unregister_scheme(PurpleCertificateScheme *scheme)
+{
+	if (NULL == scheme) {
+		purple_debug_warning("certificate",
+				     "Attempting to unregister NULL scheme\n");
+		return FALSE;
+	}
+
+	/* TODO: signalling? */
+
+	/* TODO: unregister all CertificateVerifiers for this scheme?*/
+	/* TODO: unregister all CertificatePools for this scheme? */
+	/* Neither of the above should be necessary, though */
+	cert_schemes = g_list_remove(cert_schemes, scheme);
+
+	purple_debug_info("certificate",
+			  "CertificateScheme %s unregistered\n",
+			  scheme->name);
+
+
+	return TRUE;
+}
+
+PurpleCertificateVerifier *
+purple_certificate_find_verifier(const gchar *scheme_name, const gchar *ver_name)
+{
+	PurpleCertificateVerifier *vr = NULL;
+	GList *l;
+
+	g_return_val_if_fail(scheme_name, NULL);
+	g_return_val_if_fail(ver_name, NULL);
+
+	/* Traverse the list of registered verifiers and locate the
+	   one whose name matches */
+	for(l = cert_verifiers; l; l = l->next) {
+		vr = (PurpleCertificateVerifier *)(l->data);
+
+		/* Scheme and name match? */
+		if(!g_ascii_strcasecmp(vr->scheme_name, scheme_name) &&
+		   !g_ascii_strcasecmp(vr->name, ver_name))
+			return vr;
+	}
+
+	purple_debug_warning("certificate",
+			     "CertificateVerifier %s, %s requested but not found.\n",
+			     scheme_name, ver_name);
+
+	/* TODO: Signalling and such? */
+	
+	return NULL;
+}
+
+
+GList *
+purple_certificate_get_verifiers(void)
+{
+	return cert_verifiers;
+}
+
+gboolean
+purple_certificate_register_verifier(PurpleCertificateVerifier *vr)
+{
+	g_return_val_if_fail(vr != NULL, FALSE);
+
+	/* Make sure no verifier is registered with the same scheme/name */
+	if (purple_certificate_find_verifier(vr->scheme_name, vr->name) != NULL) {
+		return FALSE;
+	}
+
+	/* Okay, we're golden. Register it. */
+	cert_verifiers = g_list_prepend(cert_verifiers, vr);
+
+	/* TODO: Signalling and such? */
+
+	purple_debug_info("certificate",
+			  "CertificateVerifier %s registered\n",
+			  vr->name);
+	return TRUE;
+}
+
+gboolean
+purple_certificate_unregister_verifier(PurpleCertificateVerifier *vr)
+{
+	if (NULL == vr) {
+		purple_debug_warning("certificate",
+				     "Attempting to unregister NULL verifier\n");
+		return FALSE;
+	}
+
+	/* TODO: signalling? */
+
+	cert_verifiers = g_list_remove(cert_verifiers, vr);
+
+
+	purple_debug_info("certificate",
+			  "CertificateVerifier %s unregistered\n",
+			  vr->name);
+
+	return TRUE;
+}
+
+PurpleCertificatePool *
+purple_certificate_find_pool(const gchar *scheme_name, const gchar *pool_name)
+{
+	PurpleCertificatePool *pool = NULL;
+	GList *l;
+
+	g_return_val_if_fail(scheme_name, NULL);
+	g_return_val_if_fail(pool_name, NULL);
+
+	/* Traverse the list of registered pools and locate the
+	   one whose name matches */
+	for(l = cert_pools; l; l = l->next) {
+		pool = (PurpleCertificatePool *)(l->data);
+
+		/* Scheme and name match? */
+		if(!g_ascii_strcasecmp(pool->scheme_name, scheme_name) &&
+		   !g_ascii_strcasecmp(pool->name, pool_name))
+			return pool;
+	}
+
+	purple_debug_warning("certificate",
+			     "CertificatePool %s, %s requested but not found.\n",
+			     scheme_name, pool_name);
+
+	/* TODO: Signalling and such? */
+	
+	return NULL;
+
+}
+
+GList *
+purple_certificate_get_pools(void)
+{
+	return cert_pools;
+}
+
+gboolean
+purple_certificate_register_pool(PurpleCertificatePool *pool)
+{
+	gboolean success = FALSE;
+	g_return_val_if_fail(pool, FALSE);
+	g_return_val_if_fail(pool->scheme_name, FALSE);
+	g_return_val_if_fail(pool->name, FALSE);
+	g_return_val_if_fail(pool->fullname, FALSE);
+
+	/* Make sure no pools are registered under this name */
+	if (purple_certificate_find_pool(pool->scheme_name, pool->name)) {
+		return FALSE;
+	}
+
+	/* Initialize the pool if needed */
+	if (pool->init) {
+		success = pool->init();
+	} else {
+		success = TRUE;
+	}
+	
+	if (success) {
+		/* Register the Pool */
+		cert_pools = g_list_prepend(cert_pools, pool);
+
+		/* TODO: Emit a signal that the pool got registered */
+
+		purple_signal_register(pool, /* Signals emitted from pool */
+				       "certificate-stored",
+				       purple_marshal_VOID__POINTER_POINTER,
+				       NULL, /* No callback return value */
+				       2,    /* Two non-data arguments */
+				       purple_value_new(PURPLE_TYPE_SUBTYPE,
+							PURPLE_SUBTYPE_CERTIFICATEPOOL),
+				       purple_value_new(PURPLE_TYPE_STRING));
+
+		purple_signal_register(pool, /* Signals emitted from pool */
+				       "certificate-deleted",
+				       purple_marshal_VOID__POINTER_POINTER,
+				       NULL, /* No callback return value */
+				       2,    /* Two non-data arguments */
+				       purple_value_new(PURPLE_TYPE_SUBTYPE,
+							PURPLE_SUBTYPE_CERTIFICATEPOOL),
+				       purple_value_new(PURPLE_TYPE_STRING));
+
+
+		purple_debug_info("certificate",
+			  "CertificatePool %s registered\n",
+			  pool->name);
+		return TRUE;
+	} else {
+		return FALSE;
+	}
+	
+	/* Control does not reach this point */
+}
+
+gboolean
+purple_certificate_unregister_pool(PurpleCertificatePool *pool)
+{
+	if (NULL == pool) {
+		purple_debug_warning("certificate",
+				     "Attempting to unregister NULL pool\n");
+		return FALSE;
+	}
+
+	/* Check that the pool is registered */
+	if (!g_list_find(cert_pools, pool)) {
+		purple_debug_warning("certificate",
+				     "Pool to unregister isn't registered!\n");
+
+		return FALSE;
+	}
+
+	/* Uninit the pool if needed */
+	if (pool->uninit) {
+		pool->uninit();
+	}
+
+	cert_pools = g_list_remove(cert_pools, pool);
+	
+	/* TODO: Signalling? */
+	purple_signal_unregister(pool, "certificate-stored");
+	purple_signal_unregister(pool, "certificate-deleted");
+		
+	purple_debug_info("certificate",
+			  "CertificatePool %s unregistered\n",
+			  pool->name);
+	return TRUE;
+}
+
+/****************************************************************************/
+/* Scheme-specific functions                                                */
+/****************************************************************************/
+
+void
+purple_certificate_display_x509(PurpleCertificate *crt)
+{
+	gchar *sha_asc;
+	GByteArray *sha_bin;
+	gchar *cn;
+	time_t activation, expiration;
+	/* Length of these buffers is dictated by 'man ctime_r' */
+	gchar activ_str[26], expir_str[26];
+	gchar *secondary;
+
+	/* Pull out the SHA1 checksum */
+	sha_bin = purple_certificate_get_fingerprint_sha1(crt);
+	/* Now decode it for display */
+	sha_asc = purple_base16_encode_chunked(sha_bin->data,
+					       sha_bin->len);
+
+	/* Get the cert Common Name */
+	/* TODO: Will break on CA certs */
+	cn = purple_certificate_get_subject_name(crt);
+
+	/* Get the certificate times */
+	/* TODO: Check the times against localtime */
+	/* TODO: errorcheck? */
+	g_assert(purple_certificate_get_times(crt, &activation, &expiration));
+	ctime_r(&activation, activ_str);
+	ctime_r(&expiration, expir_str);
+	
+	/* Make messages */
+	secondary = g_strdup_printf(_("Common name: %s\n\n"
+				      "Fingerprint (SHA1): %s\n\n"
+				      "Activation date: %s\n"
+				      "Expiration date: %s\n"),
+				    cn, sha_asc, activ_str, expir_str);
+	
+	/* Make a semi-pretty display */
+	purple_notify_info(
+		NULL,         /* TODO: Find what the handle ought to be */
+		_("Certificate Information"),
+		"",
+		secondary);
+		
+	/* Cleanup */
+	g_free(cn);
+	g_free(secondary);
+	g_free(sha_asc);
+	g_byte_array_free(sha_bin, TRUE);
+}
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/certificate.h	Tue Aug 28 01:14:08 2007 +0000
@@ -0,0 +1,779 @@
+/**
+ * @file certificate.h Public-Key Certificate API
+ * @ingroup core
+ */
+
+/*
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _PURPLE_CERTIFICATE_H
+#define _PURPLE_CERTIFICATE_H
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+typedef enum
+{
+	PURPLE_CERTIFICATE_INVALID = 0,
+	PURPLE_CERTIFICATE_VALID = 1
+} PurpleCertificateVerificationStatus;
+
+typedef struct _PurpleCertificate PurpleCertificate;
+typedef struct _PurpleCertificatePool PurpleCertificatePool;
+typedef struct _PurpleCertificateScheme PurpleCertificateScheme;
+typedef struct _PurpleCertificateVerifier PurpleCertificateVerifier;
+typedef struct _PurpleCertificateVerificationRequest PurpleCertificateVerificationRequest;
+
+/**
+ * Callback function for the results of a verification check
+ * @param st       Status code
+ * @param userdata User-defined data
+ */
+typedef void (*PurpleCertificateVerifiedCallback)
+		(PurpleCertificateVerificationStatus st,
+		 gpointer userdata);
+							  
+/** A certificate instance
+ *
+ *  An opaque data structure representing a single certificate under some
+ *  CertificateScheme
+ */
+struct _PurpleCertificate
+{
+	/** Scheme this certificate is under */
+	PurpleCertificateScheme * scheme;
+	/** Opaque pointer to internal data */
+	gpointer data;
+};
+
+/**
+ * Database for retrieval or storage of Certificates
+ *
+ * More or less a hash table; all lookups and writes are controlled by a string
+ * key.
+ */
+struct _PurpleCertificatePool
+{
+	/** Scheme this Pool operates for */
+	gchar *scheme_name;
+	/** Internal name to refer to the pool by */
+	gchar *name;
+
+	/** User-friendly name for this type
+	 *  ex: N_("SSL Servers")
+	 *  When this is displayed anywhere, it should be i18ned
+	 *  ex: _(pool->fullname)
+	 */
+	gchar *fullname;
+
+	/** Internal pool data */
+	gpointer data;
+	
+	/**
+	 * Set up the Pool's internal state
+	 *
+	 * Upon calling purple_certificate_register_pool() , this function will
+	 * be called. May be NULL.
+	 * @return TRUE if the initialization succeeded, otherwise FALSE
+	 */
+	gboolean (* init)(void);
+
+	/**
+	 * Uninit the Pool's internal state
+	 *
+	 * Will be called by purple_certificate_unregister_pool() . May be NULL
+	 */
+	void (* uninit)(void);
+
+	/** Check for presence of a certificate in the pool using unique ID */
+	gboolean (* cert_in_pool)(const gchar *id);
+	/** Retrieve a PurpleCertificate from the pool */
+	PurpleCertificate * (* get_cert)(const gchar *id);
+	/** Add a certificate to the pool. Must overwrite any other
+	 *  certificates sharing the same ID in the pool.
+	 *  @return TRUE if the operation succeeded, otherwise FALSE
+	 */
+	gboolean (* put_cert)(const gchar *id, PurpleCertificate *crt);
+	/** Delete a certificate from the pool */
+	gboolean (* delete_cert)(const gchar *id);
+
+	/** Returns a list of IDs stored in the pool */
+	GList * (* get_idlist)(void);
+};
+
+/** A certificate type
+ *
+ *  A CertificateScheme must implement all of the fields in the structure,
+ *  and register it using purple_certificate_register_scheme()
+ *
+ *  There may be only ONE CertificateScheme provided for each certificate
+ *  type, as specified by the "name" field.
+ */
+struct _PurpleCertificateScheme
+{
+	/** Name of the certificate type
+	 *  ex: "x509", "pgp", etc.
+	 *  This must be globally unique - you may not register more than one
+	 *  CertificateScheme of the same name at a time.
+	 */
+	gchar * name;
+
+	/** User-friendly name for this type
+	 *  ex: N_("X.509 Certificates")
+	 *  When this is displayed anywhere, it should be i18ned
+	 *  ex: _(scheme->fullname)
+	 */
+	gchar * fullname;
+
+	/** Imports a certificate from a file
+	 *
+	 *  @param filename   File to import the certificate from
+	 *  @return           Pointer to the newly allocated Certificate struct
+	 *                    or NULL on failure.
+	 */
+	PurpleCertificate * (* import_certificate)(const gchar * filename);
+
+	/**
+	 * Exports a certificate to a file
+	 *
+	 * @param filename    File to export the certificate to
+	 * @param crt         Certificate to export
+	 * @return TRUE if the export succeeded, otherwise FALSE
+	 * @see purple_certificate_export()
+	 */
+	gboolean (* export_certificate)(const gchar *filename, PurpleCertificate *crt);
+
+	/**
+	 * Duplicates a certificate
+	 *
+	 * Certificates are generally assumed to be read-only, so feel free to
+	 * do any sort of reference-counting magic you want here. If this ever
+	 * changes, please remember to change the magic accordingly.
+	 * @return Reference to the new copy
+	 */
+	PurpleCertificate * (* copy_certificate)(PurpleCertificate *crt);
+
+	/** Destroys and frees a Certificate structure
+	 *
+	 *  Destroys a Certificate's internal data structures and calls
+	 *  free(crt)
+	 *
+	 *  @param crt  Certificate instance to be destroyed. It WILL NOT be
+	 *              destroyed if it is not of the correct
+	 *              CertificateScheme. Can be NULL
+	 */
+	void (* destroy_certificate)(PurpleCertificate * crt);
+
+	/** Find whether "crt" has a valid signature from issuer "issuer"
+	 *  @see purple_certificate_signed_by() */
+	gboolean (*signed_by)(PurpleCertificate *crt, PurpleCertificate *issuer);
+	/**
+	 * Retrieves the certificate public key fingerprint using SHA1
+	 *
+	 * @param crt   Certificate instance
+	 * @return Binary representation of SHA1 hash - must be freed using
+	 *         g_byte_array_free()
+	 */
+	GByteArray * (* get_fingerprint_sha1)(PurpleCertificate *crt);
+
+	/**
+	 * Retrieves a unique certificate identifier
+	 *
+	 * @param crt   Certificate instance
+	 * @return Newly allocated string that can be used to uniquely
+	 *         identify the certificate.
+	 */
+	gchar * (* get_unique_id)(PurpleCertificate *crt);
+
+	/**
+	 * Retrieves a unique identifier for the certificate's issuer
+	 *
+	 * @param crt   Certificate instance
+	 * @return Newly allocated string that can be used to uniquely
+	 *         identify the issuer's certificate.
+	 */
+	gchar * (* get_issuer_unique_id)(PurpleCertificate *crt);
+
+	/**
+	 * Gets the certificate subject's name
+	 *
+	 * For X.509, this is the "Common Name" field, as we're only using it
+	 * for hostname verification at the moment
+	 *
+	 * @see purple_certificate_get_subject_name()
+	 *
+	 * @param crt   Certificate instance
+	 * @return Newly allocated string with the certificate subject.
+	 */
+	gchar * (* get_subject_name)(PurpleCertificate *crt);
+
+	/**
+	 * Check the subject name against that on the certificate
+	 * @see purple_certificate_check_subject_name()
+	 * @return TRUE if it is a match, else FALSE
+	 */
+	gboolean (* check_subject_name)(PurpleCertificate *crt, const gchar *name);
+
+	/** Retrieve the certificate activation/expiration times */
+	gboolean (* get_times)(PurpleCertificate *crt, time_t *activation, time_t *expiration);
+	
+	/* TODO: Fill out this structure */
+};
+
+/** A set of operations used to provide logic for verifying a Certificate's
+ *  authenticity.
+ *
+ * A Verifier provider must fill out these fields, then register it using
+ * purple_certificate_register_verifier()
+ *
+ * The (scheme_name, name) value must be unique for each Verifier - you may not
+ * register more than one Verifier of the same name for each Scheme
+ */
+struct _PurpleCertificateVerifier
+{
+	/** Name of the scheme this Verifier operates on
+	 *
+	 * The scheme will be looked up by name when a Request is generated
+	 * using this Verifier
+	 */
+	gchar *scheme_name;
+
+	/** Name of the Verifier - case insensitive */
+	gchar *name;
+	
+	/**
+	 * Start the verification process
+	 *
+	 * To be called from purple_certificate_verify once it has
+	 * constructed the request. This will use the information in the
+	 * given VerificationRequest to check the certificate and callback
+	 * the requester with the verification results.
+	 *
+	 * @param vrq      Request to process
+	 */
+	void (* start_verification)(PurpleCertificateVerificationRequest *vrq);
+
+	/**
+	 * Destroy a completed Request under this Verifier
+	 * The function pointed to here is only responsible for cleaning up
+	 * whatever PurpleCertificateVerificationRequest::data points to.
+	 * It should not call free(vrq)
+	 *
+	 * @param vrq       Request to destroy
+	 */
+	void (* destroy_request)(PurpleCertificateVerificationRequest *vrq);
+};
+
+/** Structure for a single certificate request
+ *
+ *  Useful for keeping track of the state of a verification that involves
+ *  several steps
+ */
+struct _PurpleCertificateVerificationRequest
+{
+	/** Reference to the verification logic used */
+	PurpleCertificateVerifier *verifier;
+	/** Reference to the scheme used.
+	 *
+	 * This is looked up from the Verifier when the Request is generated
+	 */
+	PurpleCertificateScheme *scheme;
+
+	/**
+	 * Name to check that the certificate is issued to
+	 *
+	 * For X.509 certificates, this is the Common Name
+	 */
+	gchar *subject_name;
+	
+	/** List of certificates in the chain to be verified (such as that returned by purple_ssl_get_peer_certificates )
+	 *
+	 * This is most relevant for X.509 certificates used in SSL sessions.
+	 * The list order should be: certificate, issuer, issuer's issuer, etc.
+	 */
+	GList *cert_chain;
+	
+	/** Internal data used by the Verifier code */
+	gpointer data;
+
+	/** Function to call with the verification result */
+	PurpleCertificateVerifiedCallback cb;
+	/** Data to pass to the post-verification callback */
+	gpointer cb_data;
+};
+
+/*****************************************************************************/
+/** @name Certificate Verification Functions                                 */
+/*****************************************************************************/
+/*@{*/
+
+/**
+ * Constructs a verification request and passed control to the specified Verifier
+ *
+ * It is possible that the callback will be called immediately upon calling
+ * this function. Plan accordingly.
+ *
+ * @param verifier      Verification logic to use.
+ *                      @see purple_certificate_find_verifier()
+ *
+ * @param subject_name  Name that should match the first certificate in the
+ *                      chain for the certificate to be valid. Will be strdup'd
+ *                      into the Request struct
+ *
+ * @param cert_chain    Certificate chain to check. If there is more than one
+ *                      certificate in the chain (X.509), the peer's
+ *                      certificate comes first, then the issuer/signer's
+ *                      certificate, etc. The whole list is duplicated into the
+ *                      Request struct.
+ *
+ * @param cb            Callback function to be called with whether the
+ *                      certificate was approved or not.
+ * @param cb_data       User-defined data for the above.
+ */
+void
+purple_certificate_verify (PurpleCertificateVerifier *verifier,
+			   const gchar *subject_name, GList *cert_chain,
+			   PurpleCertificateVerifiedCallback cb,
+			   gpointer cb_data);
+
+/**
+ * Completes and destroys a VerificationRequest
+ *
+ * @param vrq           Request to conclude
+ * @param st            Success/failure code to pass to the request's
+ *                      completion callback.
+ */
+void
+purple_certificate_verify_complete(PurpleCertificateVerificationRequest *vrq,
+				   PurpleCertificateVerificationStatus st);
+
+/*@}*/
+
+/*****************************************************************************/
+/** @name Certificate Functions                                              */
+/*****************************************************************************/
+/*@{*/
+
+/**
+ * Makes a duplicate of a certificate
+ *
+ * @param crt        Instance to duplicate
+ * @return Pointer to new instance
+ */
+PurpleCertificate *
+purple_certificate_copy(PurpleCertificate *crt);
+
+/**
+ * Duplicates an entire list of certificates
+ *
+ * @param crt_list   List to duplicate
+ * @return New list copy
+ */
+GList *
+purple_certificate_copy_list(GList *crt_list);
+
+/**
+ * Destroys and free()'s a Certificate
+ *
+ * @param crt        Instance to destroy. May be NULL.
+ */
+void
+purple_certificate_destroy (PurpleCertificate *crt);
+
+/**
+ * Destroy an entire list of Certificate instances and the containing list
+ *
+ * @param crt_list   List of certificates to destroy. May be NULL.
+ */
+void
+purple_certificate_destroy_list (GList * crt_list);
+
+/**
+ * Check whether 'crt' has a valid signature made by 'issuer'
+ *
+ * @param crt        Certificate instance to check signature of
+ * @param issuer     Certificate thought to have signed 'crt'
+ *
+ * @return TRUE if 'crt' has a valid signature made by 'issuer',
+ *         otherwise FALSE
+ * @TODO Find a way to give the reason (bad signature, not the issuer, etc.) 
+ */
+gboolean
+purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer);
+
+/**
+ * Check that a certificate chain is valid
+ *
+ * Uses purple_certificate_signed_by() to verify that each PurpleCertificate
+ * in the chain carries a valid signature from the next. A single-certificate
+ * chain is considered to be valid.
+ *
+ * @param chain      List of PurpleCertificate instances comprising the chain,
+ *                   in the order certificate, issuer, issuer's issuer, etc.
+ * @return TRUE if the chain is valid. See description.
+ * @TODO Specify which certificate in the chain caused a failure
+ */
+gboolean
+purple_certificate_check_signature_chain(GList *chain);
+
+/**
+ * Imports a PurpleCertificate from a file
+ *
+ * @param scheme      Scheme to import under
+ * @param filename    File path to import from
+ * @return Pointer to a new PurpleCertificate, or NULL on failure
+ */
+PurpleCertificate *
+purple_certificate_import(PurpleCertificateScheme *scheme, const gchar *filename);
+
+/**
+ * Exports a PurpleCertificate to a file
+ *
+ * @param filename    File to export the certificate to
+ * @param crt         Certificate to export
+ * @return TRUE if the export succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_export(const gchar *filename, PurpleCertificate *crt);
+
+
+/**
+ * Retrieves the certificate public key fingerprint using SHA1.
+ *
+ * @param crt        Certificate instance
+ * @return Binary representation of the hash. You are responsible for free()ing
+ *         this.
+ * @see purple_base16_encode_chunked()
+ */
+GByteArray *
+purple_certificate_get_fingerprint_sha1(PurpleCertificate *crt);
+
+/**
+ * Get a unique identifier for the certificate
+ *
+ * @param crt        Certificate instance
+ * @return String representing the certificate uniquely. Must be g_free()'ed
+ */
+gchar *
+purple_certificate_get_unique_id(PurpleCertificate *crt);
+
+/**
+ * Get a unique identifier for the certificate's issuer
+ *
+ * @param crt        Certificate instance
+ * @return String representing the certificate's issuer uniquely. Must be
+ *         g_free()'ed
+ */
+gchar *
+purple_certificate_get_issuer_unique_id(PurpleCertificate *crt);
+
+/**
+ * Gets the certificate subject's name
+ *
+ * For X.509, this is the "Common Name" field, as we're only using it
+ * for hostname verification at the moment
+ *
+ * @param crt   Certificate instance
+ * @return Newly allocated string with the certificate subject.
+ */
+gchar *
+purple_certificate_get_subject_name(PurpleCertificate *crt);
+
+/**
+ * Check the subject name against that on the certificate
+ * @param crt   Certificate instance
+ * @param name  Name to check. 
+ * @return TRUE if it is a match, else FALSE
+ */
+gboolean
+purple_certificate_check_subject_name(PurpleCertificate *crt, const gchar *name);
+
+/**
+ * Get the expiration/activation times.
+ *
+ * @param crt          Certificate instance
+ * @param activation   Reference to store the activation time at. May be NULL
+ *                     if you don't actually want it.
+ * @param expiration   Reference to store the expiration time at. May be NULL
+ *                     if you don't actually want it.
+ * @return TRUE if the requested values were obtained, otherwise FALSE.
+ */
+gboolean
+purple_certificate_get_times(PurpleCertificate *crt, time_t *activation, time_t *expiration);
+
+/*@}*/
+
+/*****************************************************************************/
+/** @name Certificate Pool Functions                                         */
+/*****************************************************************************/
+/*@{*/
+/**
+ * Helper function for generating file paths in ~/.purple/certificates for
+ * CertificatePools that use them.
+ *
+ * All components will be escaped for filesystem friendliness.
+ *
+ * @param pool   CertificatePool to build a path for
+ * @param id     Key to look up a Certificate by. May be NULL.
+ * @return A newly allocated path of the form
+ *         ~/.purple/certificates/scheme_name/pool_name/unique_id
+ */
+gchar *
+purple_certificate_pool_mkpath(PurpleCertificatePool *pool, const gchar *id);
+
+/**
+ * Determines whether a pool can be used.
+ *
+ * Checks whether the associated CertificateScheme is loaded.
+ *
+ * @param pool   Pool to check
+ *
+ * @return TRUE if the pool can be used, otherwise FALSE
+ */
+gboolean
+purple_certificate_pool_usable(PurpleCertificatePool *pool);
+
+/**
+ * Looks up the scheme the pool operates under
+ *
+ * @param pool   Pool to get the scheme of
+ *
+ * @return Pointer to the pool's scheme, or NULL if it isn't loaded.
+ * @see purple_certificate_pool_usable()
+ */
+PurpleCertificateScheme *
+purple_certificate_pool_get_scheme(PurpleCertificatePool *pool);
+
+/**
+ * Check for presence of an ID in a pool.
+ * @param pool   Pool to look in
+ * @param id     ID to look for
+ * @return TRUE if the ID is in the pool, else FALSE
+ */
+gboolean
+purple_certificate_pool_contains(PurpleCertificatePool *pool, const gchar *id);
+
+/**
+ * Retrieve a certificate from a pool.
+ * @param pool   Pool to fish in
+ * @param id     ID to look up
+ * @return Retrieved certificate, or NULL if it wasn't there
+ */
+PurpleCertificate *
+purple_certificate_pool_retrieve(PurpleCertificatePool *pool, const gchar *id);
+
+/**
+ * Add a certificate to a pool
+ *
+ * Any pre-existing certificate of the same ID will be overwritten.
+ *
+ * @param pool   Pool to add to
+ * @param id     ID to store the certificate with
+ * @param crt    Certificate to store
+ * @return TRUE if the operation succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_pool_store(PurpleCertificatePool *pool, const gchar *id, PurpleCertificate *crt);
+
+/**
+ * Remove a certificate from a pool
+ *
+ * @param pool   Pool to remove from
+ * @param id     ID to remove
+ * @return TRUE if the operation succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_pool_delete(PurpleCertificatePool *pool, const gchar *id);
+
+/**
+ * Get the list of IDs currently in the pool.
+ *
+ * @param pool   Pool to enumerate
+ * @return GList pointing to newly-allocated id strings. Free using
+ *         purple_certificate_pool_destroy_idlist()
+ */
+GList *
+purple_certificate_pool_get_idlist(PurpleCertificatePool *pool);
+
+/**
+ * Destroys the result given by purple_certificate_pool_get_idlist()
+ *
+ * @param idlist ID List to destroy
+ */
+void
+purple_certificate_pool_destroy_idlist(GList *idlist);
+
+/*@}*/
+
+/*****************************************************************************/
+/** @name Certificate Subsystem API                                          */
+/*****************************************************************************/
+/*@{*/
+
+/**
+ * Initialize the certificate system
+ */
+void
+purple_certificate_init(void);
+
+/**
+ * Un-initialize the certificate system
+ */
+void
+purple_certificate_uninit(void);
+
+/**
+ * Get the Certificate subsystem handle for signalling purposes
+ */
+gpointer
+purple_certificate_get_handle(void);
+
+/** Look up a registered CertificateScheme by name
+ * @param name   The scheme name. Case insensitive.
+ * @return Pointer to the located Scheme, or NULL if it isn't found.
+ */
+PurpleCertificateScheme *
+purple_certificate_find_scheme(const gchar *name);
+
+/**
+ * Get all registered CertificateSchemes
+ *
+ * @return GList pointing to all registered CertificateSchemes . This value
+ *         is owned by libpurple
+ */
+GList *
+purple_certificate_get_schemes(void);
+
+/** Register a CertificateScheme with libpurple
+ *
+ * No two schemes can be registered with the same name; this function enforces
+ * that.
+ *
+ * @param scheme  Pointer to the scheme to register.
+ * @return TRUE if the scheme was successfully added, otherwise FALSE
+ */
+gboolean
+purple_certificate_register_scheme(PurpleCertificateScheme *scheme);
+
+/** Unregister a CertificateScheme from libpurple
+ *
+ * @param scheme    Scheme to unregister.
+ *                  If the scheme is not registered, this is a no-op.
+ *
+ * @return TRUE if the unregister completed successfully
+ */
+gboolean
+purple_certificate_unregister_scheme(PurpleCertificateScheme *scheme);
+
+/** Look up a registered PurpleCertificateVerifier by scheme and name
+ * @param scheme_name  Scheme name. Case insensitive.
+ * @param ver_name     The verifier name. Case insensitive.
+ * @return Pointer to the located Verifier, or NULL if it isn't found.
+ */
+PurpleCertificateVerifier *
+purple_certificate_find_verifier(const gchar *scheme_name, const gchar *ver_name);
+
+/**
+ * Get the list of registered CertificateVerifiers
+ *
+ * @return GList of all registered PurpleCertificateVerifier. This value
+ *         is owned by libpurple
+ */
+GList *
+purple_certificate_get_verifiers(void);
+
+/**
+ * Register a CertificateVerifier with libpurple
+ *
+ * @param vr     Verifier to register.
+ * @return TRUE if register succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_register_verifier(PurpleCertificateVerifier *vr);
+
+/**
+ * Unregister a CertificateVerifier with libpurple
+ *
+ * @param vr     Verifier to unregister.
+ * @return TRUE if unregister succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_unregister_verifier(PurpleCertificateVerifier *vr);
+
+/** Look up a registered PurpleCertificatePool by scheme and name
+ * @param scheme_name  Scheme name. Case insensitive.
+ * @param pool_name    Pool name. Case insensitive.
+ * @return Pointer to the located Pool, or NULL if it isn't found.
+ */
+PurpleCertificatePool *
+purple_certificate_find_pool(const gchar *scheme_name, const gchar *pool_name);
+
+/**
+ * Get the list of registered Pools
+ *
+ * @return GList of all registered PurpleCertificatePool s. This value
+ *         is owned by libpurple
+ */
+GList *
+purple_certificate_get_pools(void);
+
+/**
+ * Register a CertificatePool with libpurple and call its init function
+ *
+ * @param pool   Pool to register.
+ * @return TRUE if the register succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_register_pool(PurpleCertificatePool *pool);
+
+/**
+ * Unregister a CertificatePool with libpurple and call its uninit function
+ *
+ * @param pool   Pool to unregister.
+ * @return TRUE if the unregister succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_unregister_pool(PurpleCertificatePool *pool);
+
+/*@}*/
+
+
+/**
+ * Displays a window showing X.509 certificate information
+ *
+ * @param crt    Certificate under an "x509" Scheme
+ * @TODO Will break on CA certs, as they have no Common Name
+ */
+void
+purple_certificate_display_x509(PurpleCertificate *crt);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _PURPLE_CERTIFICATE_H */
--- a/libpurple/core.c	Tue Aug 28 00:13:20 2007 +0000
+++ b/libpurple/core.c	Tue Aug 28 01:14:08 2007 +0000
@@ -24,6 +24,7 @@
  */
 #include "internal.h"
 #include "cipher.h"
+#include "certificate.h"
 #include "connection.h"
 #include "conversation.h"
 #include "core.h"
@@ -141,6 +142,7 @@
 	purple_accounts_init();
 	purple_savedstatuses_init();
 	purple_notify_init();
+	purple_certificate_init();
 	purple_connections_init();
 	purple_conversations_init();
 	purple_blist_init();
@@ -192,6 +194,7 @@
 	purple_notify_uninit();
 	purple_conversations_uninit();
 	purple_connections_uninit();
+	purple_certificate_uninit();
 	purple_buddy_icons_uninit();
 	purple_accounts_uninit();
 	purple_savedstatuses_uninit();
--- a/libpurple/plugins/ssl/ssl-gnutls.c	Tue Aug 28 00:13:20 2007 +0000
+++ b/libpurple/plugins/ssl/ssl-gnutls.c	Tue Aug 28 01:14:08 2007 +0000
@@ -21,15 +21,18 @@
  */
 #include "internal.h"
 #include "debug.h"
+#include "certificate.h"
 #include "plugin.h"
 #include "sslconn.h"
 #include "version.h"
+#include "util.h"
 
 #define SSL_GNUTLS_PLUGIN_ID "ssl-gnutls"
 
 #ifdef HAVE_GNUTLS
 
 #include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
 
 typedef struct
 {
@@ -44,9 +47,25 @@
 static void
 ssl_gnutls_init_gnutls(void)
 {
+	/* Configure GnuTLS to use glib memory management */
+	/* I expect that this isn't really necessary, but it may prevent
+	   some bugs */
+	/* TODO: It may be necessary to wrap this allocators for GnuTLS.
+	   If there are strange bugs, perhaps look here (yes, I am a
+	   hypocrite) */
+	gnutls_global_set_mem_functions(
+		(gnutls_alloc_function)   g_malloc0, /* malloc */
+		(gnutls_alloc_function)   g_malloc0, /* secure malloc */
+		NULL,      /* mem_is_secure */
+		(gnutls_realloc_function) g_realloc, /* realloc */
+		(gnutls_free_function)    g_free     /* free */
+		);
+	
 	gnutls_global_init();
 
 	gnutls_certificate_allocate_credentials(&xcred);
+
+	/* TODO: I can likely remove this */
 	gnutls_certificate_set_x509_trust_file(xcred, "ca.pem",
 		GNUTLS_X509_FMT_PEM);
 }
@@ -65,6 +84,25 @@
 	gnutls_certificate_free_credentials(xcred);
 }
 
+static void
+ssl_gnutls_verified_cb(PurpleCertificateVerificationStatus st,
+		       gpointer userdata)
+{
+	PurpleSslConnection *gsc = (PurpleSslConnection *) userdata;
+
+	if (st == PURPLE_CERTIFICATE_VALID) {
+		/* Certificate valid? Good! Do the connection! */
+		gsc->connect_cb(gsc->connect_cb_data, gsc, PURPLE_INPUT_READ);
+	} else {
+		/* Otherwise, signal an error */
+		if(gsc->error_cb != NULL)
+			gsc->error_cb(gsc, PURPLE_SSL_CERTIFICATE_INVALID,
+				      gsc->connect_cb_data);
+		purple_ssl_close(gsc);
+	}
+}
+
+
 
 static void ssl_gnutls_handshake_cb(gpointer data, gint source,
 		PurpleInputCondition cond)
@@ -73,7 +111,7 @@
 	PurpleSslGnutlsData *gnutls_data = PURPLE_SSL_GNUTLS_DATA(gsc);
 	ssize_t ret;
 
-	purple_debug_info("gnutls", "Handshaking\n");
+	purple_debug_info("gnutls", "Handshaking with %s\n", gsc->host);
 	ret = gnutls_handshake(gnutls_data->session);
 
 	if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
@@ -94,7 +132,117 @@
 	} else {
 		purple_debug_info("gnutls", "Handshake complete\n");
 
-		gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
+		/* TODO: Remove all this debugging babble */
+		/* Now we are cooking with gas! */
+		PurpleSslOps *ops = purple_ssl_get_ops();
+		GList * peers = ops->get_peer_certificates(gsc);
+		
+		PurpleCertificateScheme *x509 =
+			purple_certificate_find_scheme("x509");
+
+		GList * l;
+		for (l=peers; l; l = l->next) {
+			PurpleCertificate *crt = l->data;
+			GByteArray *z =
+				x509->get_fingerprint_sha1(crt);
+			gchar * fpr =
+				purple_base16_encode_chunked(z->data,
+							     z->len);
+
+			purple_debug_info("gnutls/x509",
+					  "Key print: %s\n",
+					  fpr);
+
+			/* Kill the cert! */
+			x509->destroy_certificate(crt);
+			
+			g_free(fpr);
+			g_byte_array_free(z, TRUE);
+		}
+		g_list_free(peers);
+		
+		{
+		  const gnutls_datum_t *cert_list;
+		  unsigned int cert_list_size = 0;
+		  gnutls_session_t session=gnutls_data->session;
+		  
+		  cert_list =
+		    gnutls_certificate_get_peers(session, &cert_list_size);
+		  
+		  purple_debug_info("gnutls",
+				    "Peer provided %d certs\n",
+				    cert_list_size);
+		  int i;
+		  for (i=0; i<cert_list_size; i++)
+		    {
+		      gchar fpr_bin[256];
+		      gsize fpr_bin_sz = sizeof(fpr_bin);
+		      gchar * fpr_asc = NULL;
+		      gchar tbuf[256];
+		      gsize tsz=sizeof(tbuf);
+		      gchar * tasc = NULL;
+		      gnutls_x509_crt_t cert;
+		      
+		      gnutls_x509_crt_init(&cert);
+		      gnutls_x509_crt_import (cert, &cert_list[i],
+					      GNUTLS_X509_FMT_DER);
+		      
+		      gnutls_x509_crt_get_fingerprint(cert, GNUTLS_MAC_SHA,
+						      fpr_bin, &fpr_bin_sz);
+		      
+		      fpr_asc =
+			purple_base16_encode_chunked(fpr_bin,fpr_bin_sz);
+		      
+		      purple_debug_info("gnutls", 
+					"Lvl %d SHA1 fingerprint: %s\n",
+					i, fpr_asc);
+		      
+		      tsz=sizeof(tbuf);
+		      gnutls_x509_crt_get_serial(cert,tbuf,&tsz);
+		      tasc=
+			purple_base16_encode_chunked(tbuf, tsz);
+		      purple_debug_info("gnutls",
+					"Serial: %s\n",
+					tasc);
+		      g_free(tasc);
+
+		      tsz=sizeof(tbuf);
+		      gnutls_x509_crt_get_dn (cert, tbuf, &tsz);
+		      purple_debug_info("gnutls",
+					"Cert DN: %s\n",
+					tbuf);
+		      tsz=sizeof(tbuf);
+		      gnutls_x509_crt_get_issuer_dn (cert, tbuf, &tsz);
+		      purple_debug_info("gnutls",
+					"Cert Issuer DN: %s\n",
+					tbuf);
+
+		      g_free(fpr_asc); fpr_asc = NULL;
+		      gnutls_x509_crt_deinit(cert);
+		    }
+		  
+		}
+
+		/* TODO: The following logic should really be in libpurple */
+		/* If a Verifier was given, hand control over to it */
+		if (gsc->verifier) {
+			GList *peers;
+			/* First, get the peer cert chain */
+			peers = purple_ssl_get_peer_certificates(gsc);
+
+			/* Now kick off the verification process */
+			purple_certificate_verify(gsc->verifier,
+						  gsc->host,
+						  peers,
+						  ssl_gnutls_verified_cb,
+						  gsc);
+
+			purple_certificate_destroy_list(peers);
+		} else {
+			/* Otherwise, just call the "connection complete"
+			   callback */
+			gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
+		}
 	}
 
 }
@@ -213,6 +361,559 @@
 	return s;
 }
 
+/* Forward declarations are fun! */
+static PurpleCertificate *
+x509_import_from_datum(const gnutls_datum_t dt, gnutls_x509_crt_fmt_t mode);
+
+static GList *
+ssl_gnutls_get_peer_certificates(PurpleSslConnection * gsc)
+{
+	PurpleSslGnutlsData *gnutls_data = PURPLE_SSL_GNUTLS_DATA(gsc);
+
+	/* List of Certificate instances to return */
+	GList * peer_certs = NULL;
+
+	/* List of raw certificates as given by GnuTLS */
+	const gnutls_datum_t *cert_list;
+	unsigned int cert_list_size = 0;
+
+	unsigned int i;
+	
+	/* This should never, ever happen. */
+	g_return_val_if_fail( gnutls_certificate_type_get (gnutls_data->session) == GNUTLS_CRT_X509, NULL);
+
+	/* Get the certificate list from GnuTLS */
+	/* TODO: I am _pretty sure_ this doesn't block or do other exciting things */
+	cert_list = gnutls_certificate_get_peers(gnutls_data->session,
+						 &cert_list_size);
+
+	/* Convert each certificate to a Certificate and append it to the list */
+	for (i = 0; i < cert_list_size; i++) {
+		PurpleCertificate * newcrt = x509_import_from_datum(cert_list[i],
+							      GNUTLS_X509_FMT_DER);
+		/* Append is somewhat inefficient on linked lists, but is easy
+		   to read. If someone complains, I'll change it.
+		   TODO: Is anyone complaining? (Maybe elb?) */
+		peer_certs = g_list_append(peer_certs, newcrt);
+	}
+
+	/* cert_list doesn't need free()-ing */
+
+	return peer_certs;
+}
+
+/************************************************************************/
+/* X.509 functionality                                                  */
+/************************************************************************/
+const gchar * SCHEME_NAME = "x509";
+
+static PurpleCertificateScheme x509_gnutls;
+
+/** Refcounted GnuTLS certificate data instance */
+typedef struct {
+	gint refcount;
+	gnutls_x509_crt_t crt;
+} x509_crtdata_t;
+
+/** Helper functions for reference counting */
+static x509_crtdata_t *
+x509_crtdata_addref(x509_crtdata_t *cd)
+{
+	(cd->refcount)++;
+	return cd;
+}
+
+static void
+x509_crtdata_delref(x509_crtdata_t *cd)
+{
+	g_assert(cd->refcount > 0);
+	
+	(cd->refcount)--;
+
+	/* If the refcount reaches zero, kill the structure */
+	if (cd->refcount == 0) {
+		purple_debug_info("gnutls/x509",
+				  "Freeing unused cert data at %p\n",
+				  cd);
+		/* Kill the internal data */
+		gnutls_x509_crt_deinit( cd->crt );
+		/* And kill the struct */
+		g_free( cd );
+	}
+}
+
+/** Helper macro to retrieve the GnuTLS crt_t from a PurpleCertificate */
+#define X509_GET_GNUTLS_DATA(pcrt) ( ((x509_crtdata_t *) (pcrt->data))->crt)
+
+/** Transforms a gnutls_datum_t containing an X.509 certificate into a Certificate instance under the x509_gnutls scheme
+ *
+ * @param dt   Datum to transform
+ * @param mode GnuTLS certificate format specifier (GNUTLS_X509_FMT_PEM for
+ *             reading from files, and GNUTLS_X509_FMT_DER for converting
+ *             "over the wire" certs for SSL)
+ *
+ * @return A newly allocated Certificate structure of the x509_gnutls scheme
+ */
+static PurpleCertificate *
+x509_import_from_datum(const gnutls_datum_t dt, gnutls_x509_crt_fmt_t mode)
+{
+	/* Internal certificate data structure */
+	x509_crtdata_t *certdat;
+	/* New certificate to return */
+	PurpleCertificate * crt;
+
+	/* Allocate and prepare the internal certificate data */
+	certdat = g_new0(x509_crtdata_t, 1);
+	gnutls_x509_crt_init(&(certdat->crt));
+	certdat->refcount = 0;
+	
+	/* Perform the actual certificate parse */
+	/* Yes, certdat->crt should be passed as-is */
+	gnutls_x509_crt_import(certdat->crt, &dt, mode);
+	
+	/* Allocate the certificate and load it with data */
+	crt = g_new0(PurpleCertificate, 1);
+	crt->scheme = &x509_gnutls;
+	crt->data = x509_crtdata_addref(certdat);
+
+	return crt;
+}
+
+/** Imports a PEM-formatted X.509 certificate from the specified file.
+ * @param filename Filename to import from. Format is PEM
+ *
+ * @return A newly allocated Certificate structure of the x509_gnutls scheme
+ */
+static PurpleCertificate *
+x509_import_from_file(const gchar * filename)
+{
+	PurpleCertificate *crt;  /* Certificate being constructed */
+	gchar *buf;        /* Used to load the raw file data */
+	gsize buf_sz;      /* Size of the above */
+	gnutls_datum_t dt; /* Struct to pass down to GnuTLS */
+
+	purple_debug_info("gnutls",
+			  "Attempting to load X.509 certificate from %s\n",
+			  filename);
+	
+	/* Next, we'll simply yank the entire contents of the file
+	   into memory */
+	/* TODO: Should I worry about very large files here? */
+	g_return_val_if_fail(
+		g_file_get_contents(filename,
+			    &buf,
+			    &buf_sz,
+			    NULL      /* No error checking for now */
+		),
+		NULL);
+	
+	/* Load the datum struct */
+	dt.data = (unsigned char *) buf;
+	dt.size = buf_sz;
+
+	/* Perform the conversion */
+	crt = x509_import_from_datum(dt,
+				     GNUTLS_X509_FMT_PEM); // files should be in PEM format
+	
+	/* Cleanup */
+	g_free(buf);
+
+	return crt;
+}
+
+/**
+ * Exports a PEM-formatted X.509 certificate to the specified file.
+ * @param filename Filename to export to. Format will be PEM
+ * @param crt      Certificate to export
+ *
+ * @return TRUE if success, otherwise FALSE
+ */
+static gboolean
+x509_export_certificate(const gchar *filename, PurpleCertificate *crt)
+{
+	gnutls_x509_crt_t crt_dat; /* GnuTLS cert struct */
+	int ret;
+	gchar * out_buf; /* Data to output */
+	size_t out_size; /* Output size */
+	gboolean success = FALSE;
+
+	/* Paranoia paranoia paranoia! */
+	g_return_val_if_fail(filename, FALSE);
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+	g_return_val_if_fail(crt->data, FALSE);
+
+	crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+	/* Obtain the output size required */
+	out_size = 0;
+	ret = gnutls_x509_crt_export(crt_dat, GNUTLS_X509_FMT_PEM,
+				     NULL, /* Provide no buffer yet */
+				     &out_size /* Put size here */
+		);
+	g_return_val_if_fail(ret == GNUTLS_E_SHORT_MEMORY_BUFFER, FALSE);
+
+	/* Now allocate a buffer and *really* export it */
+	out_buf = g_new0(gchar, out_size);
+	ret = gnutls_x509_crt_export(crt_dat, GNUTLS_X509_FMT_PEM,
+				     out_buf, /* Export to our new buffer */
+				     &out_size /* Put size here */
+		);
+	if (ret != 0) {
+		purple_debug_error("gnutls/x509",
+				   "Failed to export cert to buffer with code %d\n",
+				   ret);
+		g_free(out_buf);
+		return FALSE;
+	}
+
+	/* Write it out to an actual file */
+	success = purple_util_write_data_to_file_absolute(filename,
+							  out_buf, out_size);
+
+	
+	g_free(out_buf);
+	g_return_val_if_fail(success, FALSE);
+	return success;
+}
+
+static PurpleCertificate *
+x509_copy_certificate(PurpleCertificate *crt)
+{
+	x509_crtdata_t *crtdat;
+	PurpleCertificate *newcrt;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+	crtdat = (x509_crtdata_t *) crt->data;
+
+	newcrt = g_new0(PurpleCertificate, 1);
+	newcrt->scheme = &x509_gnutls;
+	newcrt->data = x509_crtdata_addref(crtdat);
+
+	return newcrt;
+}
+/** Frees a Certificate
+ *
+ *  Destroys a Certificate's internal data structures and frees the pointer
+ *  given.
+ *  @param crt  Certificate instance to be destroyed. It WILL NOT be destroyed
+ *              if it is not of the correct CertificateScheme. Can be NULL
+ *
+ */
+static void
+x509_destroy_certificate(PurpleCertificate * crt)
+{
+	if (NULL == crt) return;
+
+	/* Check that the scheme is x509_gnutls */
+	if ( crt->scheme != &x509_gnutls ) {
+		purple_debug_error("gnutls",
+				   "destroy_certificate attempted on certificate of wrong scheme (scheme was %s, expected %s)\n",
+				   crt->scheme->name,
+				   SCHEME_NAME);
+		return;
+	}
+
+	g_return_if_fail(crt->data != NULL);
+	g_return_if_fail(crt->scheme != NULL);
+
+	/* Use the reference counting system to free (or not) the
+	   underlying data */
+	x509_crtdata_delref((x509_crtdata_t *)crt->data);
+	
+	/* Kill the structure itself */
+	g_free(crt);
+}
+
+/** Determines whether one certificate has been issued and signed by another
+ *
+ * @param crt       Certificate to check the signature of
+ * @param issuer    Issuer's certificate
+ *
+ * @return TRUE if crt was signed and issued by issuer, otherwise FALSE
+ * @TODO  Modify this function to return a reason for invalidity?
+ */
+static gboolean
+x509_certificate_signed_by(PurpleCertificate * crt,
+			   PurpleCertificate * issuer)
+{
+	gnutls_x509_crt_t crt_dat;
+	gnutls_x509_crt_t issuer_dat;
+	unsigned int verify; /* used to store result from GnuTLS verifier */
+	int ret;
+	
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(issuer, FALSE);
+
+	/* Verify that both certs are the correct scheme */
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+	g_return_val_if_fail(issuer->scheme == &x509_gnutls, FALSE);
+
+	/* TODO: check for more nullness? */
+
+	crt_dat = X509_GET_GNUTLS_DATA(crt);
+	issuer_dat = X509_GET_GNUTLS_DATA(issuer);
+
+	/* First, let's check that crt.issuer is actually issuer */
+	ret = gnutls_x509_crt_check_issuer(crt_dat, issuer_dat);
+	if (ret <= 0) {
+
+		if (ret < 0) {
+			purple_debug_error("gnutls/x509",
+					   "GnuTLS error %d while checking certificate issuer match.",
+					   ret);
+		} else {
+			gchar *crt_id, *issuer_id, *crt_issuer_id;
+			crt_id = purple_certificate_get_unique_id(crt);
+			issuer_id = purple_certificate_get_unique_id(issuer);
+			crt_issuer_id =
+				purple_certificate_get_issuer_unique_id(crt);
+			purple_debug_info("gnutls/x509",
+					  "Certificate for %s claims to be "
+					  "issued by %s, but the certificate "
+					  "for %s does not match. A strcmp "
+					  "says %d\n",
+					  crt_id, crt_issuer_id, issuer_id,
+					  strcmp(crt_issuer_id, issuer_id));
+			g_free(crt_id);
+			g_free(issuer_id);
+			g_free(crt_issuer_id);
+		}
+
+		/* The issuer is not correct, or there were errors */
+		return FALSE;
+	}
+	
+	/* Now, check the signature */
+	/* The second argument is a ptr to an array of "trusted" issuer certs,
+	   but we're only using one trusted one */
+	ret = gnutls_x509_crt_verify(crt_dat, &issuer_dat, 1,
+				     /* Permit signings by X.509v1 certs
+					(Verisign and possibly others have
+					root certificates that predate the
+					current standard) */
+				     GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT,
+				     &verify);
+	
+	if (ret != 0) {
+		purple_debug_error("gnutls/x509",
+				   "Attempted certificate verification caused a GnuTLS error code %d. I will just say the signature is bad, but you should look into this.\n", ret);
+		return FALSE;
+	}
+
+	if (verify & GNUTLS_CERT_INVALID) {
+		/* Signature didn't check out, but at least
+		   there were no errors*/
+		gchar *crt_id = purple_certificate_get_unique_id(crt);
+		gchar *issuer_id = purple_certificate_get_issuer_unique_id(crt);
+		purple_debug_info("gnutls/x509",
+				  "Bad signature for %s on %s\n",
+				  issuer_id, crt_id);
+		g_free(crt_id);
+		g_free(issuer_id);
+		
+		return FALSE;
+	} /* if (ret, etc.) */
+
+	/* If we got here, the signature is good */
+	return TRUE;
+}
+
+static GByteArray *
+x509_sha1sum(PurpleCertificate *crt)
+{
+	size_t hashlen = 20; /* SHA1 hashes are 20 bytes */
+	size_t tmpsz = hashlen; /* Throw-away variable for GnuTLS to stomp on*/
+	gnutls_x509_crt_t crt_dat;
+	GByteArray *hash; /**< Final hash container */
+	guchar hashbuf[hashlen]; /**< Temporary buffer to contain hash */
+
+	g_return_val_if_fail(crt, NULL);
+
+	crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+	/* Extract the fingerprint */
+	g_return_val_if_fail(
+		0 == gnutls_x509_crt_get_fingerprint(crt_dat, GNUTLS_MAC_SHA,
+						     hashbuf, &tmpsz),
+		NULL);
+
+	/* This shouldn't happen */
+	g_return_val_if_fail(tmpsz == hashlen, NULL);
+	
+	/* Okay, now create and fill hash array */
+	hash = g_byte_array_new();
+	g_byte_array_append(hash, hashbuf, hashlen);
+
+	return hash;
+}
+
+static gchar *
+x509_cert_dn (PurpleCertificate *crt)
+{
+	gnutls_x509_crt_t cert_dat;
+	gchar *dn = NULL;
+	size_t dn_size;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+	cert_dat = X509_GET_GNUTLS_DATA(crt);
+
+	/* Figure out the length of the Distinguished Name */
+	/* Claim that the buffer is size 0 so GnuTLS just tells us how much
+	   space it needs */
+	dn_size = 0;
+	gnutls_x509_crt_get_dn(cert_dat, dn, &dn_size);
+
+	/* Now allocate and get the Distinguished Name */
+	dn = g_new0(gchar, dn_size);
+	if (0 != gnutls_x509_crt_get_dn(cert_dat, dn, &dn_size)) {
+		purple_debug_error("gnutls/x509",
+				   "Failed to get Distinguished Name\n");
+		g_free(dn);
+		return NULL;
+	}
+	
+	return dn;
+}
+
+static gchar *
+x509_issuer_dn (PurpleCertificate *crt)
+{
+	gnutls_x509_crt_t cert_dat;
+	gchar *dn = NULL;
+	size_t dn_size;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+	cert_dat = X509_GET_GNUTLS_DATA(crt);
+
+	/* Figure out the length of the Distinguished Name */
+	/* Claim that the buffer is size 0 so GnuTLS just tells us how much
+	   space it needs */
+	dn_size = 0;
+	gnutls_x509_crt_get_issuer_dn(cert_dat, dn, &dn_size);
+
+	/* Now allocate and get the Distinguished Name */
+	dn = g_new0(gchar, dn_size);
+	if (0 != gnutls_x509_crt_get_issuer_dn(cert_dat, dn, &dn_size)) {
+		purple_debug_error("gnutls/x509",
+				   "Failed to get issuer's Distinguished "
+				   "Name\n");
+		g_free(dn);
+		return NULL;
+	}
+	
+	return dn;
+}
+
+static gchar *
+x509_common_name (PurpleCertificate *crt)
+{
+	gnutls_x509_crt_t cert_dat;
+	gchar *cn = NULL;
+	size_t cn_size;
+	int ret;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+	cert_dat = X509_GET_GNUTLS_DATA(crt);
+
+	/* Figure out the length of the Common Name */
+	/* Claim that the buffer is size 0 so GnuTLS just tells us how much
+	   space it needs */
+	cn_size = 0;
+	gnutls_x509_crt_get_dn_by_oid(cert_dat,
+				      GNUTLS_OID_X520_COMMON_NAME,
+				      0, /* First CN found, please */
+				      0, /* Not in raw mode */
+				      cn, &cn_size);
+
+	/* Now allocate and get the Common Name */
+	cn = g_new0(gchar, cn_size);
+	ret = gnutls_x509_crt_get_dn_by_oid(cert_dat,
+					    GNUTLS_OID_X520_COMMON_NAME,
+					    0, /* First CN found, please */
+					    0, /* Not in raw mode */
+					    cn, &cn_size);
+	if (ret != 0) {
+		purple_debug_error("gnutls/x509",
+				   "Failed to get Common Name\n");
+		g_free(cn);
+		return NULL;
+	}
+
+	
+	return cn;
+}
+
+static gboolean
+x509_check_name (PurpleCertificate *crt, const gchar *name)
+{
+	gnutls_x509_crt_t crt_dat;
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+	g_return_val_if_fail(name, FALSE);
+
+	crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+	if (gnutls_x509_crt_check_hostname(crt_dat, name)) {
+		return TRUE;
+	} else {
+		return FALSE;
+	}
+}
+
+static gboolean
+x509_times (PurpleCertificate *crt, time_t *activation, time_t *expiration)
+{
+	gnutls_x509_crt_t crt_dat;
+	/* GnuTLS time functions return this on error */
+	const time_t errval = (time_t) (-1);
+
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+
+	crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+	if (activation) {
+		*activation = gnutls_x509_crt_get_activation_time(crt_dat);
+	}
+	if (expiration) {
+		*expiration = gnutls_x509_crt_get_expiration_time(crt_dat);
+	}
+
+	if (*activation == errval || *expiration == errval) {
+		return FALSE;
+	}
+	
+	return TRUE;
+}
+
+/* X.509 certificate operations provided by this plugin */
+static PurpleCertificateScheme x509_gnutls = {
+	"x509",                          /* Scheme name */
+	N_("X.509 Certificates"),        /* User-visible scheme name */
+	x509_import_from_file,           /* Certificate import function */
+	x509_export_certificate,         /* Certificate export function */
+	x509_copy_certificate,           /* Copy */
+	x509_destroy_certificate,        /* Destroy cert */
+	x509_certificate_signed_by,      /* Signature checker */
+	x509_sha1sum,                    /* SHA1 fingerprint */
+	x509_cert_dn,                    /* Unique ID */
+	x509_issuer_dn,                  /* Issuer Unique ID */
+	x509_common_name,                /* Subject name */
+	x509_check_name,                 /* Check subject name */
+	x509_times                       /* Activation/Expiration time */
+};
+
 static PurpleSslOps ssl_ops =
 {
 	ssl_gnutls_init,
@@ -221,11 +922,11 @@
 	ssl_gnutls_close,
 	ssl_gnutls_read,
 	ssl_gnutls_write,
+	ssl_gnutls_get_peer_certificates,
 
 	/* padding */
 	NULL,
 	NULL,
-	NULL,
 	NULL
 };
 
@@ -242,6 +943,9 @@
 	/* Init GNUTLS now so others can use it even if sslconn never does */
 	ssl_gnutls_init_gnutls();
 
+	/* Register that we're providing an X.509 CertScheme */
+	purple_certificate_register_scheme( &x509_gnutls );
+
 	return TRUE;
 #else
 	return FALSE;
@@ -255,6 +959,8 @@
 	if(purple_ssl_get_ops() == &ssl_ops) {
 		purple_ssl_set_ops(NULL);
 	}
+
+	purple_certificate_unregister_scheme( &x509_gnutls );
 #endif
 
 	return TRUE;
--- a/libpurple/plugins/ssl/ssl-nss.c	Tue Aug 28 00:13:20 2007 +0000
+++ b/libpurple/plugins/ssl/ssl-nss.c	Tue Aug 28 01:14:08 2007 +0000
@@ -21,6 +21,7 @@
  */
 #include "internal.h"
 #include "debug.h"
+#include "certificate.h"
 #include "plugin.h"
 #include "sslconn.h"
 #include "version.h"
@@ -360,6 +361,284 @@
 	return ret;
 }
 
+static GList *
+ssl_nss_peer_certs(PurpleSslConnection *gsc)
+{
+	PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc);
+	GList *chain = NULL;
+	CERTCertificate *cert;
+	void *pinArg;
+	SECStatus status;
+
+	/* TODO: this is a blind guess */
+	cert = SSL_PeerCertificate(nss_data->fd);
+
+	
+
+	return NULL;
+}
+
+/************************************************************************/
+/* X.509 functionality                                                  */
+/************************************************************************/
+static PurpleCertificateScheme x509_nss;
+
+/** Helpr macro to retrieve the NSS certdata from a PurpleCertificate */
+#define X509_NSS_DATA(pcrt) ( (CERTCertificate * ) (pcrt->data) )
+
+/** Imports a PEM-formatted X.509 certificate from the specified file.
+ * @param filename Filename to import from. Format is PEM
+ *
+ * @return A newly allocated Certificate structure of the x509_gnutls scheme
+ */
+static PurpleCertificate *
+x509_import_from_file(const gchar *filename)
+{
+	gchar *rawcert;
+	gsize len = 0;
+	CERTCertificate *crt_dat;
+	PurpleCertificate *crt;
+
+	g_return_val_if_fail(filename, NULL);
+
+	purple_debug_info("nss/x509",
+			  "Loading certificate from %s\n",
+			  filename);
+	
+	/* Load the raw data up */
+	g_return_val_if_fail(
+		g_file_get_contents(filename,
+				    &rawcert, &len,
+				    NULL ),
+		NULL);
+
+	/* Decode the certificate */
+	crt_dat = CERT_DecodeCertFromPackage(rawcert, len);
+	g_free(rawcert);
+
+	g_return_val_if_fail(crt_dat, NULL);
+	
+	crt = g_new0(PurpleCertificate, 1);
+	crt->scheme = &x509_nss;
+	crt->data = crt_dat;
+	
+	return crt;
+}
+
+/**
+ * Exports a PEM-formatted X.509 certificate to the specified file.
+ * @param filename Filename to export to. Format will be PEM
+ * @param crt      Certificate to export
+ *
+ * @return TRUE if success, otherwise FALSE
+ */
+static gboolean
+x509_export_certificate(const gchar *filename, PurpleCertificate *crt)
+{
+	/* TODO: WRITEME */
+	return FALSE;
+}
+
+static PurpleCertificate *
+x509_copy_certificate(PurpleCertificate *crt)
+{
+	CERTCertificate *crt_dat;
+	PurpleCertificate *newcrt;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_val_if_fail(crt_dat, NULL);
+
+	/* Create the certificate copy */
+	newcrt = g_new0(PurpleCertificate, 1);
+	newcrt->scheme = &x509_nss;
+	/* NSS does refcounting automatically */
+	newcrt->data = CERT_DupCertificate(crt_dat);
+	
+	return newcrt;
+}
+
+/** Frees a Certificate
+ *
+ *  Destroys a Certificate's internal data structures and frees the pointer
+ *  given.
+ *  @param crt  Certificate instance to be destroyed. It WILL NOT be destroyed
+ *              if it is not of the correct CertificateScheme. Can be NULL
+ *
+ */
+static void
+x509_destroy_certificate(PurpleCertificate * crt)
+{
+	CERTCertificate *crt_dat;
+
+	g_return_if_fail(crt);
+	g_return_if_fail(crt->scheme == &x509_nss);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_if_fail(crt_dat);
+
+	/* Finally we have the certificate. So let's kill it */
+	/* NSS does refcounting automatically */
+	CERT_DestroyCertificate(crt_dat);
+
+	/* Delete the PurpleCertificate as well */
+	g_free(crt);
+}
+
+/** Determines whether one certificate has been issued and signed by another
+ *
+ * @param crt       Certificate to check the signature of
+ * @param issuer    Issuer's certificate
+ *
+ * @return TRUE if crt was signed and issued by issuer, otherwise FALSE
+ * @TODO  Modify this function to return a reason for invalidity?
+ */
+static gboolean
+x509_certificate_signed_by(PurpleCertificate * crt,
+			   PurpleCertificate * issuer)
+{
+	return FALSE;
+}
+
+static GByteArray *
+x509_sha1sum(PurpleCertificate *crt)
+{
+	CERTCertificate *crt_dat;
+	size_t hashlen = 20; /* Size of an sha1sum */
+	GByteArray *sha1sum;
+	SECItem *derCert; /* DER representation of the cert */
+	SECStatus st;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_val_if_fail(crt_dat, NULL);
+
+	/* Get the certificate DER representation */
+	derCert = &(crt_dat->derCert);
+
+	/* Make a hash! */
+	sha1sum = g_byte_array_sized_new(hashlen);
+	st = PK11_HashBuf(SEC_OID_SHA1, sha1sum->data,
+			  derCert->data, derCert->len);
+
+	/* Check for errors */
+	if (st != SECSuccess) {
+		g_byte_array_free(sha1sum, TRUE);
+		purple_debug_error("nss/x509",
+				   "Error: hashing failed!\n");
+		return NULL;
+	}
+
+	return sha1sum;
+}
+
+static gchar *
+x509_common_name (PurpleCertificate *crt)
+{
+	CERTCertificate *crt_dat;
+	char *nss_cn;
+	gchar *ret_cn;
+	
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_val_if_fail(crt_dat, NULL);
+
+	/* Q:
+	   Why get a newly allocated string out of NSS, strdup it, and then
+	   return the new copy?
+
+	   A:
+	   The NSS LXR docs state that I should use the NSPR free functions on
+	   the strings that the NSS cert functions return. Since the libpurple
+	   API expects a g_free()-able string, we make our own copy and return
+	   that.
+
+	   NSPR is something of a prima donna. */
+
+	nss_cn = CERT_GetCommonName( &(crt_dat->subject) );
+	ret_cn = g_strdup(nss_cn);
+	PORT_Free(nss_cn);
+
+	return ret_cn;
+}
+
+static gboolean
+x509_check_name (PurpleCertificate *crt, const gchar *name)
+{
+	CERTCertificate *crt_dat;
+	SECStatus st;
+	
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_nss, FALSE);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_val_if_fail(crt_dat, FALSE);
+
+	st = CERT_VerifyCertName(crt_dat, name);
+
+	if (st == SECSuccess) {
+		return TRUE;
+	}
+	else if (st == SECFailure) {
+		return FALSE;
+	}
+	
+	/* If we get here...bad things! */
+	g_assert(FALSE);
+	return FALSE;
+}
+
+static gboolean
+x509_times (PurpleCertificate *crt, time_t *activation, time_t *expiration)
+{
+	CERTCertificate *crt_dat;
+	PRTime nss_activ, nss_expir;
+	
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_nss, FALSE);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_val_if_fail(crt_dat, FALSE);
+
+	/* Extract the times into ugly PRTime thingies */
+	/* TODO: Maybe this shouldn't throw an error? */
+	g_return_val_if_fail(
+		SECSuccess == CERT_GetCertTimes(crt_dat,
+						&nss_activ, &nss_expir),
+		FALSE);
+
+	if (activation) {
+		*activation = nss_activ;
+	}
+	if (expiration) {
+		*expiration = nss_expir;
+	}
+	
+	return TRUE;
+}
+
+static PurpleCertificateScheme x509_nss = {
+	"x509",                          /* Scheme name */
+	N_("X.509 Certificates"),        /* User-visible scheme name */
+	x509_import_from_file,           /* Certificate import function */
+	x509_export_certificate,         /* Certificate export function */
+	x509_copy_certificate,           /* Copy */
+	x509_destroy_certificate,        /* Destroy cert */
+	NULL,                            /* Signed-by */
+	x509_sha1sum,                    /* SHA1 fingerprint */
+	NULL,                            /* Unique ID */
+	NULL,                            /* Issuer Unique ID */
+	x509_common_name,                /* Subject name */
+	x509_check_name,                 /* Check subject name */
+	x509_times                       /* Activation/Expiration time */
+};
+
 static PurpleSslOps ssl_ops =
 {
 	ssl_nss_init,
@@ -368,11 +647,11 @@
 	ssl_nss_close,
 	ssl_nss_read,
 	ssl_nss_write,
+	ssl_nss_peer_certs,
 
 	/* padding */
 	NULL,
 	NULL,
-	NULL,
 	NULL
 };
 
@@ -390,6 +669,9 @@
 	/* Init NSS now, so others can use it even if sslconn never does */
 	ssl_nss_init_nss();
 
+	/* Register the X.509 functions we provide */
+	purple_certificate_register_scheme(&x509_nss);
+
 	return TRUE;
 #else
 	return FALSE;
@@ -403,6 +685,9 @@
 	if (purple_ssl_get_ops() == &ssl_ops) {
 		purple_ssl_set_ops(NULL);
 	}
+
+	/* Unregister our X.509 functions */
+	purple_certificate_unregister_scheme(&x509_nss);
 #endif
 
 	return TRUE;
--- a/libpurple/prefs.h	Tue Aug 28 00:13:20 2007 +0000
+++ b/libpurple/prefs.h	Tue Aug 28 01:14:08 2007 +0000
@@ -55,7 +55,9 @@
 #endif
 
 /**************************************************************************/
-/** @name Prefs API                                                       */
+/** @name Prefs API                                                       
+    Preferences are named according to a directory-like structure.        
+    Example: "/plugins/core/potato/is_from_idaho" (probably a boolean)    */
 /**************************************************************************/
 /*@{*/
 
--- a/libpurple/protocols/irc/irc.c	Tue Aug 28 00:13:20 2007 +0000
+++ b/libpurple/protocols/irc/irc.c	Tue Aug 28 01:14:08 2007 +0000
@@ -433,14 +433,7 @@
 
 	irc->gsc = NULL;
 
-	switch(error) {
-		case PURPLE_SSL_CONNECT_FAILED:
-			purple_connection_error(gc, _("Connection Failed"));
-			break;
-		case PURPLE_SSL_HANDSHAKE_FAILED:
-			purple_connection_error(gc, _("SSL Handshake Failed"));
-			break;
-	}
+	purple_connection_error(gc, purple_ssl_strerror(error));
 }
 
 static void irc_close(PurpleConnection *gc)
--- a/libpurple/protocols/jabber/jabber.c	Tue Aug 28 00:13:20 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Tue Aug 28 01:14:08 2007 +0000
@@ -494,29 +494,20 @@
 	js = gc->proto_data;
 	js->gsc = NULL;
 
-	switch(error) {
-		case PURPLE_SSL_CONNECT_FAILED:
-			purple_connection_error(gc, _("Connection Failed"));
-			break;
-		case PURPLE_SSL_HANDSHAKE_FAILED:
-			purple_connection_error(gc, _("SSL Handshake Failed"));
-			break;
-	}
+	purple_connection_error(gc, purple_ssl_strerror(error));
 }
 
 static void tls_init(JabberStream *js)
 {
 	purple_input_remove(js->gc->inpa);
 	js->gc->inpa = 0;
-	js->gsc = purple_ssl_connect_fd(js->gc->account, js->fd,
-			jabber_login_callback_ssl, jabber_ssl_connect_failure, js->gc);
+	js->gsc = purple_ssl_connect_with_host_fd(js->gc->account, js->fd,
+			jabber_login_callback_ssl, jabber_ssl_connect_failure, js->serverFQDN, js->gc);
 }
 
 static void jabber_login_connect(JabberStream *js, const char *fqdn, const char *host, int port)
 {
-#ifdef HAVE_CYRUS_SASL
 	js->serverFQDN = g_strdup(fqdn);
-#endif
 
 	if (purple_proxy_connect(js->gc, js->gc->account, host,
 			port, jabber_login_callback, js->gc) == NULL)
@@ -1025,9 +1016,9 @@
 		g_string_free(js->sasl_mechs, TRUE);
 	if(js->sasl_cb)
 		g_free(js->sasl_cb);
+#endif
 	if(js->serverFQDN)
 		g_free(js->serverFQDN);
-#endif
 	g_free(js->server_name);
 	g_free(js->gmail_last_time);
 	g_free(js->gmail_last_tid);
--- a/libpurple/protocols/jabber/jabber.h	Tue Aug 28 00:13:20 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Tue Aug 28 01:14:08 2007 +0000
@@ -136,6 +136,8 @@
 	char *gmail_last_time;
 	char *gmail_last_tid;
 
+    char *serverFQDN;
+
 	/* OK, this stays at the end of the struct, so plugins can depend
 	 * on the rest of the stuff being in the right place
 	 */
@@ -150,7 +152,6 @@
 	int sasl_state;
 	int sasl_maxbuf;
 	GString *sasl_mechs;
-	char *serverFQDN;
 
 	gboolean vcard_fetched;
 
--- a/libpurple/sslconn.c	Tue Aug 28 00:13:20 2007 +0000
+++ b/libpurple/sslconn.c	Tue Aug 28 01:14:08 2007 +0000
@@ -24,6 +24,7 @@
  */
 #include "internal.h"
 
+#include "certificate.h"
 #include "debug.h"
 #include "sslconn.h"
 
@@ -117,6 +118,9 @@
 	gsc->connect_cb      = func;
 	gsc->error_cb        = error_func;
 
+	/* TODO: Move this elsewhere */
+	gsc->verifier = purple_certificate_find_verifier("x509","tls_cached");
+
 	gsc->connect_data = purple_proxy_connect(NULL, account, host, port, purple_ssl_connect_cb, gsc);
 
 	if (gsc->connect_data == NULL)
@@ -151,10 +155,37 @@
 	gsc->inpa = purple_input_add(gsc->fd, PURPLE_INPUT_READ, recv_cb, gsc);
 }
 
+const gchar *
+purple_ssl_strerror(PurpleSslErrorType error)
+{
+	switch(error) {
+		case PURPLE_SSL_CONNECT_FAILED:
+			return _("SSL Connection Failed");
+		case PURPLE_SSL_HANDSHAKE_FAILED:
+			return _("SSL Handshake Failed");
+		case PURPLE_SSL_CERTIFICATE_INVALID:
+			return _("SSL peer presented an invalid certificate");
+		default:
+			purple_debug_warning("sslconn", "Unknown SSL error code %d\n", error);
+			return _("Unknown SSL error");
+	}
+}
+
 PurpleSslConnection *
 purple_ssl_connect_fd(PurpleAccount *account, int fd,
 					PurpleSslInputFunction func,
-					PurpleSslErrorFunction error_func, void *data)
+					PurpleSslErrorFunction error_func,
+                    void *data)
+{
+    return purple_ssl_connect_with_host_fd(account, fd, func, error_func, NULL, data);
+}
+
+PurpleSslConnection *
+purple_ssl_connect_with_host_fd(PurpleAccount *account, int fd,
+                      PurpleSslInputFunction func,
+                      PurpleSslErrorFunction error_func,
+                      const char *host,
+                      void *data)
 {
 	PurpleSslConnection *gsc;
 	PurpleSslOps *ops;
@@ -175,7 +206,13 @@
 	gsc->connect_cb      = func;
 	gsc->error_cb        = error_func;
 	gsc->fd              = fd;
+    if(host)
+        gsc->host            = g_strdup(host);
 
+	/* TODO: Move this elsewhere */
+	gsc->verifier = purple_certificate_find_verifier("x509","tls_cached");
+
+    
 	ops = purple_ssl_get_ops();
 	ops->connectfunc(gsc);
 
@@ -231,6 +268,17 @@
 	return (ops->write)(gsc, data, len);
 }
 
+GList *
+purple_ssl_get_peer_certificates(PurpleSslConnection *gsc)
+{
+	PurpleSslOps *ops;
+
+	g_return_val_if_fail(gsc != NULL, NULL);
+
+	ops = purple_ssl_get_ops();
+	return (ops->get_peer_certificates)(gsc);
+}
+
 void
 purple_ssl_set_ops(PurpleSslOps *ops)
 {
@@ -246,8 +294,10 @@
 void
 purple_ssl_init(void)
 {
-	/* This doesn't do anything at the moment. All the actual init work
-	 * is handled by purple_ssl_is_supported upon demand. */
+	/* Although purple_ssl_is_supported will do the initialization on
+	   command, SSL plugins tend to register CertificateSchemes as well
+	   as providing SSL ops. */
+	g_assert(ssl_init());
 }
 
 void
--- a/libpurple/sslconn.h	Tue Aug 28 00:13:20 2007 +0000
+++ b/libpurple/sslconn.h	Tue Aug 28 01:14:08 2007 +0000
@@ -25,6 +25,7 @@
 #ifndef _PURPLE_SSLCONN_H_
 #define _PURPLE_SSLCONN_H_
 
+#include "certificate.h"
 #include "proxy.h"
 
 #define PURPLE_SSL_DEFAULT_PORT 443
@@ -32,7 +33,8 @@
 typedef enum
 {
 	PURPLE_SSL_HANDSHAKE_FAILED = 1,
-	PURPLE_SSL_CONNECT_FAILED = 2
+	PURPLE_SSL_CONNECT_FAILED = 2,
+	PURPLE_SSL_CERTIFICATE_INVALID = 3
 } PurpleSslErrorType;
 
 typedef struct _PurpleSslConnection PurpleSslConnection;
@@ -69,6 +71,9 @@
 
 	/** Internal connection data managed by the SSL backend (GnuTLS/LibNSS/whatever) */
 	void *private_data;
+
+	/** Verifier to use in authenticating the peer */
+	PurpleCertificateVerifier *verifier;
 };
 
 /**
@@ -107,8 +112,17 @@
 	* @return	The number of bytes written (may be less than len) or <0 on error
 	*/
 	size_t (*write)(PurpleSslConnection *gsc, const void *data, size_t len);
-
-	void (*_purple_reserved1)(void);
+	/** Obtains the certificate chain provided by the peer
+	 *
+	 * @param gsc   Connection context
+	 * @return      A newly allocated list containing the certificates
+	 *              the peer provided.
+	 * @see PurpleCertificate
+	 * @todo        Decide whether the ordering of certificates in this
+	 *              list can be guaranteed.
+	 */
+	GList * (* get_peer_certificates)(PurpleSslConnection * gsc);
+	
 	void (*_purple_reserved2)(void);
 	void (*_purple_reserved3)(void);
 	void (*_purple_reserved4)(void);
@@ -131,6 +145,14 @@
 gboolean purple_ssl_is_supported(void);
 
 /**
+ * Returns a human-readable string for an SSL error
+ *
+ * @param error      Error code
+ * @return Human-readable error explanation
+ */
+const gchar * purple_ssl_strerror(PurpleSslErrorType error);
+
+/**
  * Makes a SSL connection to the specified host and port.  The caller
  * should keep track of the returned value and use it to cancel the
  * connection, if needed.
@@ -154,6 +176,7 @@
 
 /**
  * Makes a SSL connection using an already open file descriptor.
+ * DEPRECATED. Use purple_ssl_connect_with_host_fd instead.
  *
  * @param account    The account making the connection.
  * @param fd         The file descriptor.
@@ -166,7 +189,25 @@
 PurpleSslConnection *purple_ssl_connect_fd(PurpleAccount *account, int fd,
 									   PurpleSslInputFunction func,
 									   PurpleSslErrorFunction error_func,
-									   void *data);
+ 									   void *data);
+
+/**
+  * Makes a SSL connection using an already open file descriptor.
+  *
+  * @param account    The account making the connection.
+  * @param fd         The file descriptor.
+  * @param func       The SSL input handler function.
+  * @param error_func The SSL error handler function.
+  * @param host       The hostname of the other peer (to verify the CN)
+  * @param data       User-defined data.
+  *
+  * @return The SSL connection handle.
+  */
+PurpleSslConnection *purple_ssl_connect_with_host_fd(PurpleAccount *account, int fd,
+                                           PurpleSslInputFunction func,
+                                           PurpleSslErrorFunction error_func,
+                                           const char *host,
+                                           void *data);
 
 /**
  * Adds an input watcher for the specified SSL connection.
@@ -208,6 +249,16 @@
  */
 size_t purple_ssl_write(PurpleSslConnection *gsc, const void *buffer, size_t len);
 
+/**
+ * Obtains the peer's presented certificates
+ *
+ * @param gsc    The SSL connection handle
+ *
+ * @return The peer certificate chain, in the order of certificate, issuer,
+ *         issuer's issuer, etc. NULL if no certificates have been provided,
+ */
+GList * purple_ssl_get_peer_certificates(PurpleSslConnection *gsc);
+
 /*@}*/
 
 /**************************************************************************/
--- a/libpurple/util.c	Tue Aug 28 00:13:20 2007 +0000
+++ b/libpurple/util.c	Tue Aug 28 01:14:08 2007 +0000
@@ -2542,10 +2542,8 @@
 purple_util_write_data_to_file(const char *filename, const char *data, gssize size)
 {
 	const char *user_dir = purple_user_dir();
-	gchar *filename_temp, *filename_full;
-	FILE *file;
-	size_t real_size, byteswritten;
-	struct stat st;
+	gchar *filename_full;
+	gboolean ret = FALSE;
 
 	g_return_val_if_fail(user_dir != NULL, FALSE);
 
@@ -2564,6 +2562,25 @@
 	}
 
 	filename_full = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", user_dir, filename);
+
+	ret = purple_util_write_data_to_file_absolute(filename_full,
+						      data,size);
+
+	g_free(filename_full);
+	return ret;
+}
+
+gboolean
+purple_util_write_data_to_file_absolute(const char *filename_full, const char *data, size_t size)
+{
+	gchar *filename_temp;
+	FILE *file;
+	size_t real_size, byteswritten;
+	struct stat st;
+
+	purple_debug_info("util", "Writing file %s\n",
+					filename_full);
+
 	filename_temp = g_strdup_printf("%s.save", filename_full);
 
 	/* Remove an old temporary file, if one exists */
@@ -2571,8 +2588,9 @@
 	{
 		if (g_unlink(filename_temp) == -1)
 		{
-			purple_debug_error("util", "Error removing old file %s: %s\n",
-							 filename_temp, strerror(errno));
+			purple_debug_error("util", "Error removing old file "
+					   "%s: %s\n",
+					   filename_temp, strerror(errno));
 		}
 	}
 
@@ -2580,9 +2598,9 @@
 	file = g_fopen(filename_temp, "wb");
 	if (file == NULL)
 	{
-		purple_debug_error("util", "Error opening file %s for writing: %s\n",
-						 filename_temp, strerror(errno));
-		g_free(filename_full);
+		purple_debug_error("util", "Error opening file %s for "
+				   "writing: %s\n",
+				   filename_temp, strerror(errno));
 		g_free(filename_temp);
 		return FALSE;
 	}
@@ -2595,8 +2613,7 @@
 	if (fclose(file) != 0)
 	{
 		purple_debug_error("util", "Error closing file %s: %s\n",
-						 filename_temp, strerror(errno));
-		g_free(filename_full);
+				   filename_temp, strerror(errno));
 		g_free(filename_temp);
 		return FALSE;
 	}
@@ -2604,10 +2621,11 @@
 	/* Ensure the file is the correct size */
 	if (byteswritten != real_size)
 	{
-		purple_debug_error("util", "Error writing to file %s: Wrote %" G_GSIZE_FORMAT " bytes "
-						 "but should have written %" G_GSIZE_FORMAT "; is your disk full?\n",
-						 filename_temp, byteswritten, real_size);
-		g_free(filename_full);
+		purple_debug_error("util", "Error writing to file %s: Wrote %"
+				   G_GSIZE_FORMAT " bytes "
+				   "but should have written %" G_GSIZE_FORMAT
+				   "; is your disk full?\n",
+				   filename_temp, byteswritten, real_size);
 		g_free(filename_temp);
 		return FALSE;
 	}
@@ -2615,9 +2633,9 @@
 	if ((g_stat(filename_temp, &st) == -1) || (st.st_size != real_size))
 	{
 		purple_debug_error("util", "Error writing data to file %s: "
-						 "Incomplete file written; is your disk full?\n",
-						 filename_temp);
-		g_free(filename_full);
+				   "Incomplete file written; is your disk "
+				   "full?\n",
+				   filename_temp);
 		g_free(filename_temp);
 		return FALSE;
 	}
@@ -2635,10 +2653,10 @@
 	if (g_rename(filename_temp, filename_full) == -1)
 	{
 		purple_debug_error("util", "Error renaming %s to %s: %s\n",
-						 filename_temp, filename_full, strerror(errno));
+				   filename_temp, filename_full,
+				   strerror(errno));
 	}
 
-	g_free(filename_full);
 	g_free(filename_temp);
 
 	return TRUE;
--- a/libpurple/util.h	Tue Aug 28 00:13:20 2007 +0000
+++ b/libpurple/util.h	Tue Aug 28 01:14:08 2007 +0000
@@ -586,7 +586,27 @@
  * @return TRUE if the file was written successfully.  FALSE otherwise.
  */
 gboolean purple_util_write_data_to_file(const char *filename, const char *data,
-									  gssize size);
+									  size_t size);
+
+/**
+ * Write data to a file using the absolute path.
+ *
+ * This exists for Glib backwards compatibility reasons.
+ *
+ * @param filename_full Filename to write to
+ * @param data          A null-terminated string of data to write.
+ * @param size          The size of the data to save.  If data is
+ *                      null-terminated you can pass in -1.
+ *
+ * @return TRUE if the file was written successfully.  FALSE otherwise.
+ *
+ * @todo Remove this function (use g_file_set_contents instead) when 3.0.0
+ *       rolls around.
+ * @see purple_util_write_data_to_file()
+ *
+ */
+gboolean
+purple_util_write_data_to_file_absolute(const char *filename_full, const char *data, size_t size);
 
 /**
  * Read the contents of a given file and parse the results into an
--- a/libpurple/value.h	Tue Aug 28 00:13:20 2007 +0000
+++ b/libpurple/value.h	Tue Aug 28 01:14:08 2007 +0000
@@ -77,7 +77,8 @@
 	PURPLE_SUBTYPE_SAVEDSTATUS,
 	PURPLE_SUBTYPE_XMLNODE,
 	PURPLE_SUBTYPE_USERINFO,
-	PURPLE_SUBTYPE_STORED_IMAGE
+	PURPLE_SUBTYPE_STORED_IMAGE,
+	PURPLE_SUBTYPE_CERTIFICATEPOOL
 } PurpleSubType;
 
 /**
--- a/pidgin/Makefile.am	Tue Aug 28 00:13:20 2007 +0000
+++ b/pidgin/Makefile.am	Tue Aug 28 01:14:08 2007 +0000
@@ -81,6 +81,7 @@
 	gtkcellrendererprogress.c \
 	gtkcellview.c \
 	gtkcellviewmenuitem.c \
+	gtkcertmgr.c \
 	gtkconn.c \
 	gtkconv.c \
 	gtkdebug.c \
@@ -128,6 +129,7 @@
 	gtkcellviewmenuitem.h \
 	gtkcellview.h \
 	gtkcellviewmenuitem.h \
+	gtkcertmgr.h \
 	pidgincombobox.h \
 	gtkconn.h \
 	gtkconv.h \
--- a/pidgin/gtkblist.c	Tue Aug 28 00:13:20 2007 +0000
+++ b/pidgin/gtkblist.c	Tue Aug 28 01:14:08 2007 +0000
@@ -42,6 +42,7 @@
 #include "gtkaccount.h"
 #include "gtkblist.h"
 #include "gtkcellrendererexpander.h"
+#include "gtkcertmgr.h"
 #include "gtkconv.h"
 #include "gtkdebug.h"
 #include "gtkdialogs.h"
@@ -2882,6 +2883,7 @@
 	/* Tools */
 	{ N_("/_Tools"), NULL, NULL, 0, "<Branch>", NULL },
 	{ N_("/Tools/Buddy _Pounces"), NULL, pidgin_pounces_manager_show, 0, "<Item>", NULL },
+	{ N_("/Tools/_Certificates"), NULL, pidgin_certmgr_show, 0, "<Item>", NULL },
 	{ N_("/Tools/Plu_gins"), "<CTL>U", pidgin_plugin_dialog_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_PLUGINS },
 	{ N_("/Tools/Pr_eferences"), "<CTL>P", pidgin_prefs_show, 0, "<StockItem>", GTK_STOCK_PREFERENCES },
 	{ N_("/Tools/Pr_ivacy"), NULL, pidgin_privacy_dialog_show, 0, "<Item>", NULL },
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtkcertmgr.c	Tue Aug 28 01:14:08 2007 +0000
@@ -0,0 +1,685 @@
+/*
+ * @file gtkcertmgr.c GTK+ Certificate Manager API
+ * @ingroup pidgin
+ *
+ * pidgin
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <glib.h>
+
+#include "core.h"
+#include "internal.h"
+#include "pidgin.h"
+
+#include "certificate.h"
+#include "debug.h"
+#include "notify.h"
+#include "request.h"
+
+#include "gtkblist.h"
+#include "gtkutils.h"
+
+#include "gtkcertmgr.h"
+
+/*****************************************************************************
+ * X.509 tls_peers management interface                                      *
+ *****************************************************************************/
+
+typedef struct {
+	GtkWidget *mgmt_widget;
+	GtkTreeView *listview;
+	GtkTreeSelection *listselect;
+	GtkWidget *importbutton;
+	GtkWidget *exportbutton;
+	GtkWidget *infobutton;
+	GtkWidget *deletebutton;
+	PurpleCertificatePool *tls_peers;
+} tls_peers_mgmt_data;
+
+tls_peers_mgmt_data *tpm_dat = NULL;
+
+/* Columns
+   See http://developer.gnome.org/doc/API/2.0/gtk/TreeWidget.html */
+enum
+{
+	TPM_HOSTNAME_COLUMN,
+	TPM_N_COLUMNS
+};
+
+static void
+tls_peers_mgmt_destroy(GtkWidget *mgmt_widget, gpointer data)
+{
+	purple_debug_info("certmgr",
+			  "tls peers self-destructs\n");
+
+	purple_signals_disconnect_by_handle(tpm_dat);
+	purple_request_close_with_handle(tpm_dat);
+	g_free(tpm_dat); tpm_dat = NULL;
+}
+
+static void
+tls_peers_mgmt_repopulate_list(void)
+{
+	GtkTreeView *listview = tpm_dat->listview;
+	PurpleCertificatePool *tls_peers;
+	GList *idlist, *l;
+	
+	GtkListStore *store = GTK_LIST_STORE(
+		gtk_tree_view_get_model(GTK_TREE_VIEW(listview)));
+	
+	/* First, delete everything in the list */
+	gtk_list_store_clear(store);
+
+	/* Locate the "tls_peers" pool */
+	tls_peers = purple_certificate_find_pool("x509", "tls_peers");
+	g_return_if_fail(tls_peers);
+
+	/* Grab the loaded certificates */
+	idlist = purple_certificate_pool_get_idlist(tls_peers);
+
+	/* Populate the listview */
+	for (l = idlist; l; l = l->next) {
+		GtkTreeIter iter;
+		gtk_list_store_append(store, &iter);
+
+		gtk_list_store_set(GTK_LIST_STORE(store), &iter,
+				   TPM_HOSTNAME_COLUMN, l->data,
+				   -1);
+	}
+	purple_certificate_pool_destroy_idlist(idlist);
+}
+
+static void
+tls_peers_mgmt_mod_cb(PurpleCertificatePool *pool, const gchar *id, gpointer data)
+{
+	g_assert (pool == tpm_dat->tls_peers);
+
+	tls_peers_mgmt_repopulate_list();
+}
+
+static void
+tls_peers_mgmt_select_chg_cb(GtkTreeSelection *ignored, gpointer data)
+{
+	GtkTreeSelection *select = tpm_dat->listselect;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+
+	/* See if things are selected */
+	if (gtk_tree_selection_get_selected(select, &model, &iter)) {
+		/* Enable buttons if something is selected */
+		gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->exportbutton), TRUE);
+		gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->infobutton), TRUE);
+		gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->deletebutton), TRUE);
+	} else {
+		/* Otherwise, disable them */
+		gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->exportbutton), FALSE);
+		gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->infobutton), FALSE);
+		gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->deletebutton), FALSE);
+
+	}
+}
+
+static void
+tls_peers_mgmt_import_ok2_cb(gpointer data, const char *result)
+{
+	PurpleCertificate *crt = (PurpleCertificate *) data;
+	const char *id = result;
+
+	/* TODO: Perhaps prompt if you're overwriting a cert? */
+
+	/* Drop the certificate into the pool */
+	purple_certificate_pool_store(tpm_dat->tls_peers, id, crt);
+
+	/* And this certificate is not needed any more */
+	purple_certificate_destroy(crt);
+}
+
+static void
+tls_peers_mgmt_import_cancel2_cb(gpointer data, const char *result)
+{
+	PurpleCertificate *crt = (PurpleCertificate *) data;
+	purple_certificate_destroy(crt);
+}
+
+static void
+tls_peers_mgmt_import_ok_cb(gpointer data, const char *filename)
+{
+	PurpleCertificateScheme *x509;
+	PurpleCertificate *crt;
+
+	/* Load the scheme of our tls_peers pool (ought to be x509) */
+	x509 = purple_certificate_pool_get_scheme(tpm_dat->tls_peers);
+
+	/* Now load the certificate from disk */
+	crt = purple_certificate_import(x509, filename);
+
+	/* Did it work? */
+	if (crt != NULL) {
+		gchar *default_hostname;
+		/* Get name to add to pool as */
+		/* Make a guess about what the hostname should be */
+		 default_hostname = purple_certificate_get_subject_name(crt);
+		/* TODO: Find a way to make sure that crt gets destroyed
+		   if the window gets closed unusually, such as by handle
+		   deletion */
+		/* TODO: Display some more information on the certificate? */
+		purple_request_input(tpm_dat,
+				     _("Certificate Import"),
+				     _("Specify a hostname"),
+				     _("Type the host name this certificate is for."),
+				     default_hostname,
+				     FALSE, /* Not multiline */
+				     FALSE, /* Not masked? */
+				     NULL,  /* No hints? */
+				     _("OK"),
+				     G_CALLBACK(tls_peers_mgmt_import_ok2_cb),
+				     _("Cancel"),
+				     G_CALLBACK(tls_peers_mgmt_import_cancel2_cb),
+				     NULL, NULL, NULL, /* No account/who/conv*/
+				     crt    /* Pass cert instance to callback*/
+				     );
+		
+		g_free(default_hostname);
+	} else {
+		/* Errors! Oh no! */
+		/* TODO: Perhaps find a way to be specific about what just
+		   went wrong? */
+		gchar * secondary;
+
+		secondary = g_strdup_printf(_("File %s could not be imported.\nMake sure that the file is readable and in PEM format.\n"), filename);
+		purple_notify_error(NULL,
+				    _("Certificate Import Error"),
+				    _("X.509 certificate import failed"),
+				    secondary);
+		g_free(secondary);
+	}
+}
+
+static void
+tls_peers_mgmt_import_cb(GtkWidget *button, gpointer data)
+{
+	/* TODO: need to tell the user that we want a .PEM file! */
+	purple_request_file(tpm_dat,
+			    _("Select a PEM certificate"),
+			    "certificate.pem",
+			    FALSE, /* Not a save dialog */
+			    G_CALLBACK(tls_peers_mgmt_import_ok_cb),
+			    NULL,  /* Do nothing if cancelled */
+			    NULL, NULL, NULL, NULL );/* No account,conv,etc. */
+}
+
+static void
+tls_peers_mgmt_export_ok_cb(gpointer data, const char *filename)
+{
+	PurpleCertificate *crt = (PurpleCertificate *) data;
+
+	g_assert(filename);
+	
+	if (!purple_certificate_export(filename, crt)) {
+		/* Errors! Oh no! */
+		/* TODO: Perhaps find a way to be specific about what just
+		   went wrong? */
+		gchar * secondary;
+
+		secondary = g_strdup_printf(_("Export to file %s failed.\nCheck that you have write permission to the target path\n"), filename);
+		purple_notify_error(NULL,
+				    _("Certificate Export Error"),
+				    _("X.509 certificate export failed"),
+				    secondary);
+		g_free(secondary);
+	}
+
+	purple_certificate_destroy(crt);
+}
+
+static void
+tls_peers_mgmt_export_cancel_cb(gpointer data, const char *filename)
+{
+	PurpleCertificate *crt = (PurpleCertificate *) data;
+	/* Pressing cancel just frees the duplicated certificate */
+	purple_certificate_destroy(crt);
+}
+	
+static void
+tls_peers_mgmt_export_cb(GtkWidget *button, gpointer data)
+{
+	PurpleCertificate *crt;
+	GtkTreeSelection *select = tpm_dat->listselect;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	gchar *id;
+
+	/* See if things are selected */
+	if (!gtk_tree_selection_get_selected(select, &model, &iter)) {
+		purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
+				     "Export clicked with no selection?\n");
+		return;
+	}
+
+	/* Retrieve the selected hostname */
+	gtk_tree_model_get(model, &iter, TPM_HOSTNAME_COLUMN, &id, -1);
+
+	/* Extract the certificate from the pool now to make sure it doesn't
+	   get deleted out from under us */
+	crt = purple_certificate_pool_retrieve(tpm_dat->tls_peers, id);
+
+	if (NULL == crt) {
+		purple_debug_error("gtkcertmgr/tls_peers_mgmt",
+				   "Id %s was not in the peers cache?!\n",
+				   id);
+		g_free(id);
+		return;
+	}
+	g_free(id);
+
+	
+	/* TODO: inform user that it will be a PEM? */
+	purple_request_file(tpm_dat,
+			    _("PEM X.509 Certificate Export"),
+			    "certificate.pem",
+			    TRUE, /* Is a save dialog */
+			    G_CALLBACK(tls_peers_mgmt_export_ok_cb),
+			    G_CALLBACK(tls_peers_mgmt_export_cancel_cb),
+			    NULL, NULL, NULL, /* No account,conv,etc. */
+			    crt); /* Pass the certificate on to the callback */
+}
+
+static void
+tls_peers_mgmt_info_cb(GtkWidget *button, gpointer data)
+{
+	GtkTreeSelection *select = tpm_dat->listselect;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	gchar *id;
+	PurpleCertificate *crt;
+	gchar *subject;
+	GByteArray *fpr_sha1;
+	gchar *fpr_sha1_asc;
+	gchar *primary, *secondary;
+
+	/* See if things are selected */
+	if (!gtk_tree_selection_get_selected(select, &model, &iter)) {
+		purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
+				     "Info clicked with no selection?\n");
+		return;
+	}
+
+	/* Retrieve the selected hostname */
+	gtk_tree_model_get(model, &iter, TPM_HOSTNAME_COLUMN, &id, -1);
+
+	/* Now retrieve the certificate */
+	crt = purple_certificate_pool_retrieve(tpm_dat->tls_peers, id);
+	g_return_if_fail(crt);
+	
+	/* Build a notification thing */
+	/* TODO: This needs a better GUI, but a notification will do for now */
+	primary = g_strdup_printf(_("Certificate for %s"), id);
+
+	fpr_sha1 = purple_certificate_get_fingerprint_sha1(crt);
+	fpr_sha1_asc = purple_base16_encode_chunked(fpr_sha1->data,
+						    fpr_sha1->len);
+	subject = purple_certificate_get_subject_name(crt);
+
+	secondary = g_strdup_printf(_("Common name: %s\n\nSHA1 fingerprint:\n%s"), subject, fpr_sha1_asc);
+	
+	purple_notify_info(tpm_dat,
+			   _("SSL Host Certificate"),  primary, secondary );
+
+	g_free(primary);
+	g_free(secondary);
+	g_byte_array_free(fpr_sha1, TRUE);
+	g_free(fpr_sha1_asc);
+	g_free(subject);
+	g_free(id);
+	purple_certificate_destroy(crt);
+}
+
+static void
+tls_peers_mgmt_delete_confirm_cb(gchar *id, gint choice)
+{
+	if (1 == choice) {
+		/* Yes, delete was confirmed */
+		/* Now delete the thing */
+		if (!purple_certificate_pool_delete(tpm_dat->tls_peers, id)) {
+			purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
+					     "Deletion failed on id %s\n",
+					     id);
+		};
+	}
+
+	g_free(id);
+}
+	
+static void
+tls_peers_mgmt_delete_cb(GtkWidget *button, gpointer data)
+{
+	GtkTreeSelection *select = tpm_dat->listselect;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+
+	/* See if things are selected */
+	if (gtk_tree_selection_get_selected(select, &model, &iter)) {
+
+		gchar *id;
+		gchar *primary;
+
+		/* Retrieve the selected hostname */
+		gtk_tree_model_get(model, &iter, TPM_HOSTNAME_COLUMN, &id, -1);
+
+		/* Prompt to confirm deletion */
+		primary = g_strdup_printf(
+			_("Really delete certificate for %s?"), id );
+		
+		purple_request_yes_no(tpm_dat, _("Confirm certificate delete"),
+				      primary, NULL, /* Can this be NULL? */
+				      2, /* NO is default action */
+				      NULL, NULL, NULL,
+				      id, /* id ownership passed to callback */
+				      tls_peers_mgmt_delete_confirm_cb,
+				      tls_peers_mgmt_delete_confirm_cb );
+		
+		g_free(primary);
+				      
+	} else {
+		purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
+				     "Delete clicked with no selection?\n");
+		return;
+	}
+}
+
+static GtkWidget *
+tls_peers_mgmt_build(void)
+{
+	GtkWidget *bbox;
+	GtkListStore *store;
+
+	/* This block of variables will end up in tpm_dat */
+	GtkTreeView *listview;
+	GtkTreeSelection *select;
+	GtkWidget *importbutton;
+	GtkWidget *exportbutton;
+	GtkWidget *infobutton;
+	GtkWidget *deletebutton;
+	/** Element to return to the Certmgr window to put in the Notebook */
+	GtkWidget *mgmt_widget;
+
+	/* Create a struct to store context information about this window */
+	tpm_dat = g_new0(tls_peers_mgmt_data, 1);
+	
+	tpm_dat->mgmt_widget = mgmt_widget =
+		gtk_hbox_new(FALSE, /* Non-homogeneous */
+			     PIDGIN_HIG_BORDER);
+	gtk_widget_show(mgmt_widget);
+
+	/* Ensure that everything gets cleaned up when the dialog box
+	   is closed */
+	g_signal_connect(G_OBJECT(mgmt_widget), "destroy",
+			 G_CALLBACK(tls_peers_mgmt_destroy), NULL);
+
+	/* List view */
+	store = gtk_list_store_new(TPM_N_COLUMNS, G_TYPE_STRING);
+	
+	tpm_dat->listview = listview =
+		GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)));
+	
+	{
+		GtkCellRenderer *renderer;
+		GtkTreeViewColumn *column;
+
+		/* Set up the display columns */
+		renderer = gtk_cell_renderer_text_new();
+		column = gtk_tree_view_column_new_with_attributes(
+			"Hostname",
+			renderer,
+			"text", TPM_HOSTNAME_COLUMN,
+			NULL);
+		gtk_tree_view_append_column(GTK_TREE_VIEW(listview), column);
+	}
+	
+	/* Get the treeview selector into the struct */
+	tpm_dat->listselect = select =
+		gtk_tree_view_get_selection(GTK_TREE_VIEW(listview));
+	
+	/* Force the selection mode */
+	gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
+
+	/* Use a callback to enable/disable the buttons based on whether
+	   something is selected */
+	g_signal_connect(G_OBJECT(select), "changed",
+			 G_CALLBACK(tls_peers_mgmt_select_chg_cb), NULL);
+	
+	gtk_box_pack_start(GTK_BOX(mgmt_widget), GTK_WIDGET(listview),
+			   TRUE, TRUE, /* Take up lots of space */
+			   0); /* TODO: this padding is wrong */
+	gtk_widget_show(GTK_WIDGET(listview));
+
+	/* Fill the list for the first time */
+	tls_peers_mgmt_repopulate_list();
+	
+	/* Right-hand side controls box */
+	bbox = gtk_vbutton_box_new();
+	gtk_box_pack_end(GTK_BOX(mgmt_widget), bbox,
+			 FALSE, FALSE, /* Do not take up space */
+			 0); /* TODO: this padding is probably wrong */
+	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_START);
+	gtk_widget_show(bbox);
+
+	/* Import button */
+	/* TODO: This is the wrong stock button */
+	tpm_dat->importbutton = importbutton =
+		gtk_button_new_from_stock(GTK_STOCK_ADD);
+	gtk_box_pack_start(GTK_BOX(bbox), importbutton, FALSE, FALSE, 0);
+	gtk_widget_show(importbutton);
+	g_signal_connect(G_OBJECT(importbutton), "clicked",
+			 G_CALLBACK(tls_peers_mgmt_import_cb), NULL);
+
+
+	/* Export button */
+	/* TODO: This is the wrong stock button */
+	tpm_dat->exportbutton = exportbutton =
+		gtk_button_new_from_stock(GTK_STOCK_SAVE);
+	gtk_box_pack_start(GTK_BOX(bbox), exportbutton, FALSE, FALSE, 0);
+	gtk_widget_show(exportbutton);
+	g_signal_connect(G_OBJECT(exportbutton), "clicked",
+			 G_CALLBACK(tls_peers_mgmt_export_cb), NULL);
+
+
+	/* Info button */
+	tpm_dat->infobutton = infobutton =
+		gtk_button_new_from_stock(GTK_STOCK_INFO);
+	gtk_box_pack_start(GTK_BOX(bbox), infobutton, FALSE, FALSE, 0);
+	gtk_widget_show(infobutton);
+	g_signal_connect(G_OBJECT(infobutton), "clicked",
+			 G_CALLBACK(tls_peers_mgmt_info_cb), NULL);
+
+
+	/* Delete button */
+	tpm_dat->deletebutton = deletebutton =
+		gtk_button_new_from_stock(GTK_STOCK_DELETE);
+	gtk_box_pack_start(GTK_BOX(bbox), deletebutton, FALSE, FALSE, 0);
+	gtk_widget_show(deletebutton);
+	g_signal_connect(G_OBJECT(deletebutton), "clicked",
+			 G_CALLBACK(tls_peers_mgmt_delete_cb), NULL);
+
+	/* Call the "selection changed" callback, which will probably disable
+	   all the buttons since nothing is selected yet */
+	tls_peers_mgmt_select_chg_cb(select, NULL);
+
+	/* Bind us to the tls_peers pool */
+	tpm_dat->tls_peers = purple_certificate_find_pool("x509", "tls_peers");
+	
+	/**** libpurple signals ****/
+	/* Respond to certificate add/remove by just reloading everything */
+	purple_signal_connect(tpm_dat->tls_peers, "certificate-stored",
+			      tpm_dat, PURPLE_CALLBACK(tls_peers_mgmt_mod_cb),
+			      NULL);
+	purple_signal_connect(tpm_dat->tls_peers, "certificate-deleted",
+			      tpm_dat, PURPLE_CALLBACK(tls_peers_mgmt_mod_cb),
+			      NULL);
+	
+	return mgmt_widget;
+}
+
+PidginCertificateManager tls_peers_mgmt = {
+	tls_peers_mgmt_build, /* Widget creation function */
+	N_("SSL Servers")
+};
+
+/*****************************************************************************
+ * GTK+ main certificate manager                                             *
+ *****************************************************************************/
+typedef struct
+{
+	GtkWidget *window;
+	GtkWidget *notebook;
+
+	GtkWidget *closebutton;
+} CertMgrDialog;
+
+/* If a certificate manager window is open, this will point to it.
+   So if it is set, don't open another one! */
+CertMgrDialog *certmgr_dialog = NULL;
+
+static void
+certmgr_close_cb(GtkWidget *w, CertMgrDialog *dlg)
+{
+	/* TODO: Ignoring the arguments to this function may not be ideal,
+	   but there *should* only be "one dialog to rule them all" at a time*/
+	pidgin_certmgr_hide();
+}
+
+void
+pidgin_certmgr_show(void)
+{
+	CertMgrDialog *dlg;
+	GtkWidget *win;
+	GtkWidget *vbox;
+	GtkWidget *bbox;
+
+	/* Enumerate all the certificates on file */
+	{
+		GList *idlist, *poollist;
+
+		for ( poollist = purple_certificate_get_pools();
+		      poollist;
+		      poollist = poollist->next ) {
+			PurpleCertificatePool *pool = poollist->data;
+			GList *l;
+			
+			purple_debug_info("gtkcertmgr",
+					  "Pool %s found for scheme %s -"
+					  "Enumerating certificates:\n",
+					  pool->name, pool->scheme_name);
+
+			idlist = purple_certificate_pool_get_idlist(pool);
+
+			for (l=idlist; l; l = l->next) {
+				purple_debug_info("gtkcertmgr",
+						  "- %s\n",
+						  (gchar *) l->data);
+			} /* idlist */
+			purple_certificate_pool_destroy_idlist(idlist);
+		} /* poollist */
+	}
+
+	
+	/* If the manager is already open, bring it to the front */
+	if (certmgr_dialog != NULL) {
+		gtk_window_present(GTK_WINDOW(certmgr_dialog->window));
+		return;
+	}
+
+	/* Create the dialog, and set certmgr_dialog so we never create
+	   more than one at a time */
+	dlg = certmgr_dialog = g_new0(CertMgrDialog, 1);
+
+	win = dlg->window =
+		pidgin_create_window(_("Certificate Manager"),/* Title */
+				     PIDGIN_HIG_BORDER, /*Window border*/
+				     "certmgr",         /* Role */
+				     TRUE); /* Allow resizing */
+	g_signal_connect(G_OBJECT(win), "delete_event",
+			 G_CALLBACK(certmgr_close_cb), dlg);
+
+	
+	/* TODO: Retrieve the user-set window size and use it */
+	gtk_window_set_default_size(GTK_WINDOW(win), 400, 400);
+
+	/* Main vbox */
+	vbox = gtk_vbox_new( FALSE, PIDGIN_HIG_BORDER );
+	gtk_container_add(GTK_CONTAINER(win), vbox);
+	gtk_widget_show(vbox);
+
+	/* Notebook of various certificate managers */
+	dlg->notebook = gtk_notebook_new();
+	gtk_box_pack_start(GTK_BOX(vbox), dlg->notebook,
+			   TRUE, TRUE, /* Notebook should take extra space */
+			   0);
+	gtk_widget_show(dlg->notebook);
+
+	/* Box for the close button */
+	bbox = gtk_hbutton_box_new();
+	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
+	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
+	gtk_widget_show(bbox);
+
+	/* Close button */
+	dlg->closebutton = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
+	gtk_box_pack_start(GTK_BOX(bbox), dlg->closebutton, FALSE, FALSE, 0);
+	gtk_widget_show(dlg->closebutton);
+	g_signal_connect(G_OBJECT(dlg->closebutton), "clicked",
+			 G_CALLBACK(certmgr_close_cb), dlg);
+
+	/* Add the defined certificate managers */
+	/* TODO: Find a way of determining whether each is shown or not */
+	/* TODO: Implement this correctly */
+	gtk_notebook_append_page(GTK_NOTEBOOK (dlg->notebook),
+				 (tls_peers_mgmt.build)(),
+				 gtk_label_new(_(tls_peers_mgmt.label)) );
+
+	gtk_widget_show(win);
+}
+
+void
+pidgin_certmgr_hide(void)
+{
+	/* If it isn't open, do nothing */
+	if (certmgr_dialog == NULL) {
+		return;
+	}
+
+	purple_signals_disconnect_by_handle(certmgr_dialog);
+	purple_prefs_disconnect_by_handle(certmgr_dialog);
+
+	gtk_widget_destroy(certmgr_dialog->window);
+	g_free(certmgr_dialog);
+	certmgr_dialog = NULL;
+
+	/* If this was the only window left, quit */
+	if (PIDGIN_BLIST(purple_get_blist())->window == NULL &&
+		purple_connections_get_all() == NULL) {
+
+		purple_core_quit();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtkcertmgr.h	Tue Aug 28 01:14:08 2007 +0000
@@ -0,0 +1,62 @@
+/**
+ * @file gtkcertmgr.h GTK+ Certificate Manager API
+ * @ingroup pidgin
+ */
+/*
+ * pidgin
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef _PIDGINCERTMGR_H_
+#define _PIDGINCERTMGR_H_
+
+/**************************************************************************
+ * @name Structures                                                       *
+ **************************************************************************/
+typedef struct _PidginCertificateManager PidginCertificateManager;
+
+/**
+ * GTK+ Certificate Manager subwidget
+ */
+struct _PidginCertificateManager {
+	/** Create, configure, show, and return the management interface */
+	GtkWidget * (* build)(void);
+	/** Notebook label to use in the CertMgr dialog */
+	gchar *label;
+};
+
+/**************************************************************************/
+/** @name Certificate Manager API                                         */
+/**************************************************************************/
+/*@{*/
+/**
+ * Show the certificate manager window
+ */
+void pidgin_certmgr_show(void);
+
+/**
+ * Hide the certificate manager window
+ */
+void pidgin_certmgr_hide(void);
+
+/*@}*/
+
+#endif /* _PIDGINCERTMGR_H_ */
--- a/share/Makefile.am	Tue Aug 28 00:13:20 2007 +0000
+++ b/share/Makefile.am	Tue Aug 28 01:14:08 2007 +0000
@@ -1,4 +1,4 @@
 
-SUBDIRS = sounds
+SUBDIRS = sounds ca-certs
 
 EXTRA_DIST = Makefile.mingw
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/ca-certs/Equifax_Secure_CA.pem	Tue Aug 28 01:14:08 2007 +0000
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQG
+EwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1
+cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4
+MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgx
+LTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0
+eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2R
+FGiYCh7+2gRvE4RiIcPRfM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO
+/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuv
+K9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAGA1UdHwRp
+MGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEt
+MCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
+MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjAL
+BgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gjIBBPM5iQn9Qw
+HQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMBAf8w
+GgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GB
+AFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
+7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2u
+FHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
+-----END CERTIFICATE-----
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/ca-certs/Makefile.am	Tue Aug 28 01:14:08 2007 +0000
@@ -0,0 +1,8 @@
+cacertsdir =	$(datadir)/purple/ca-certs
+cacerts_DATA =	\
+		Equifax_Secure_CA.pem \
+		Verisign_RSA_Secure_Server_CA.pem
+
+EXTRA_DIST = \
+                $(cacerts_DATA)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/ca-certs/Verisign_RSA_Secure_Server_CA.pem	Tue Aug 28 01:14:08 2007 +0000
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzEL
+MAkGA1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMu
+MS4wLAYDVQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5MB4XDTk0MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVowXzELMAkGA1UE
+BhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD
+VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGb
+MA0GCSqGSIb3DQEBAQUAA4GJADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6O
+LDfO6zV4ZFQD5YRAUcm/jwjiioII0haGN1XpsSECrXZogZoFokvJSyVmIlZs
+iAeP94FZbYQHZXATcXY+m3dM41CJVphIuR2nKRoTLkoRWZweFdVJVCxzOmmC
+sZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZIhvcNAQECBQADfgBl3X7hsuyw
+4jrg7HFGmhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3YQO2WxZpO8ZECAyIUwxr
+l0nHPjXcbLm7qt9cuzovk2C2qUtN8iD3zV9/ZHuO3ABc1/p3yjkWWW8O6tO1
+g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA==
+-----END CERTIFICATE-----