Merge with trunk soc.2008.masterpassword

Wed, 01 May 2013 11:52:20 +0200

author
Tomasz Wasilczyk <tomkiewicz@cpw.pidgin.im>
date
Wed, 01 May 2013 11:52:20 +0200
branch
soc.2008.masterpassword
changeset 34173
16c7de07a9cf
parent 34172
db03a0efc8b4 (diff)
parent 33907
1046f312d35e (current diff)
child 34174
b9e24fbc6e29

Merge with trunk

libpurple/example/nullclient.c file | annotate | diff | comparison | revisions
libpurple/plugin.c file | annotate | diff | comparison | revisions
libpurple/plugin.h file | annotate | diff | comparison | revisions
--- a/.hgignore	Wed May 01 11:48:56 2013 +0200
+++ b/.hgignore	Wed May 01 11:52:20 2013 +0200
@@ -28,6 +28,7 @@
 .*\.pyo$
 .*\.rej$
 .*\.so$
+.*\.moc$
 Doxyfile(\.mingw)?$
 VERSION$
 aclocal.m4
--- a/COPYRIGHT	Wed May 01 11:48:56 2013 +0200
+++ b/COPYRIGHT	Wed May 01 11:52:20 2013 +0200
@@ -55,6 +55,7 @@
 Igor Belyi
 David Benjamin
 Brian Bernas
+Vivien Bernet-Rollande
 Paul Betts
 Runa Bhattacharjee
 Jonas Birmé
--- a/configure.ac	Wed May 01 11:48:56 2013 +0200
+++ b/configure.ac	Wed May 01 11:52:20 2013 +0200
@@ -109,6 +109,7 @@
 dnl Checks for programs.
 AC_PROG_CC
 AM_PROG_CC_C_O
+AC_PROG_CXX
 LT_PREREQ([2.2.6])
 LT_INIT([disable-static])
 LIBTOOL="$LIBTOOL --silent"
@@ -1478,6 +1479,7 @@
 	DEBUG_CFLAGS="-Wall $DEBUG_CFLAGS"
 	CFLAGS="-g $CFLAGS"
 fi
+DEBUG_CPPFLAGS=`echo "$DEBUG_CFLAGS" | $sedpath 's/-Wdeclaration-after-statement//' | $sedpath 's/-Wmissing-prototypes//' | $sedpath 's/-Waggregate-return//'`
 
 if test "x$SUNCC" = "xyes"; then
         CFLAGS="$CFLAGS -features=extensions" 
@@ -1533,6 +1535,151 @@
 fi
 
 dnl #######################################################################
+dnl # Check for Secret Service headers
+dnl #######################################################################
+
+AC_ARG_ENABLE(libsecret, [AC_HELP_STRING([--disable-libsecret], [enable Secret Service support])], enable_secret_service=no, enable_secret_service=yes)
+
+dnl Check for libsecret; if we don't have it, oh well
+if test "x$enable_secret_service" = "xyes" ; then
+	PKG_CHECK_MODULES(SECRETSERVICE, [libsecret-1], [
+		AC_SUBST(SECRETSERVICE_CFLAGS)
+		AC_SUBST(SECRETSERVICE_LIBS)
+		AC_DEFINE(HAVE_SECRETSERVICE, 1, [Define if we have Secret Service.])
+	])
+fi
+
+AM_CONDITIONAL(ENABLE_SECRETSERVICE, test "x$enable_secret_service" = "xyes")
+
+dnl #######################################################################
+dnl # Check for GNOME Keyring headers
+dnl #######################################################################
+
+AC_ARG_ENABLE(gnome-keyring, [AC_HELP_STRING([--disable-gnome-keyring], [enable GNOME Keyring support])], enable_gnome_keyring=no, enable_gnome_keyring=yes)
+
+if test "x$enable_gnome_keyring" = "xyes" ; then
+	PKG_CHECK_MODULES(GNOMEKEYRING, [gnome-keyring-1], [
+		AC_SUBST(GNOMEKEYRING_CFLAGS)
+		AC_SUBST(GNOMEKEYRING_LIBS)
+		AC_DEFINE(HAVE_GNOMEKEYRING, 1, [Define if we have GNOME Keyring.])
+	])
+fi
+
+AM_CONDITIONAL(ENABLE_GNOMEKEYRING, test "x$enable_gnome_keyring" = "xyes")
+
+dnl #######################################################################
+dnl # Check for KWallet headers
+dnl #######################################################################
+
+AC_ARG_ENABLE(kwallet, [AC_HELP_STRING([--disable-kwallet], [enable KWallet support])], enable_kwallet=no, enable_kwallet=yes)
+AC_ARG_WITH(kwallet-includes, [AC_HELP_STRING([--with-kwallet-includes=DIR], [compile the KWallet plugin against includes in DIR])], [ac_kwallet_includes="$withval"], [ac_kwallet_includes="no"])
+AC_ARG_WITH(kwallet-libs, [AC_HELP_STRING([--with-kwallet-libs=DIR], [compile the KWallet plugin against the KWallet libs in DIR])], [ac_kwallet_libs="$withval"], [ac_kwallet_libs="no"])
+
+if test "x$enable_kwallet" = "xyes"; then
+	KWALLET_CXXFLAGS=""
+	KWALLET_LIBS=""
+	if test -z "$with_kwallet_includes" || test -z "$with_kwallet_libs"; then
+		AC_CHECK_PROG(KDE4_CONFIG, kde4-config, kde4-config, no)
+		if test "x$KDE4_CONFIG" = "xno"; then
+			if test "x$force_deps" = "xyes"; then
+				AC_MSG_WARN([
+kde4-config not found. $KDE4_CONFIG
+Use --disable-kwallet if you do not need KWallet support.
+Use --with-kwallet-includes and --with-kwallet-libs to set up includes manually.
+])
+			enable_kwallet=no
+			fi
+		fi
+	fi
+fi
+
+if test "x$enable_kwallet" = "xyes"; then
+	AC_LANG_PUSH([C++])
+	CPPFLAGS_save="$CPPFLAGS"
+
+	if test "$ac_kwallet_includes" != "no"; then
+		KWALLET_CXXFLAGS="-I$ac_kwallet_includes"
+	elif test "x$KDE4_CONFIG" != "xno"; then
+		KWALLET_CXXFLAGS="-I`$KDE4_CONFIG --path include`"
+	fi
+	CPPFLAGS="$CPPFLAGS $KWALLET_CXXFLAGS"
+	AC_CHECK_HEADER([kwallet.h], , [enable_kwallet=no])
+
+	CPPFLAGS="$CPPFLAGS_save"
+	AC_LANG_POP
+fi
+
+if test "x$enable_kwallet" = "xyes"; then
+	dnl Ensure C++ compiler works
+	AC_CHECK_PROG(CXXTEST, [$CXX], [$CXX])
+	if test "x$CXXTEST" = "x"; then
+		if test "x$force_deps" = "xyes"; then
+			AC_MSG_WARN([
+A C++ compiler was not found.
+Use --disable-kwallet if you do not need KWallet support.
+])
+		enable_kwallet=no
+		fi
+	fi
+fi
+
+AC_LANG_PUSH([C++])
+CPPFLAGS_save="$CPPFLAGS"
+LDFLAGS_save="$LDFLAGS"
+if test "x$enable_kwallet" = "xyes"; then
+
+	PKG_CHECK_MODULES(QT4, [QtCore], [
+			AC_SUBST(QT4_CFLAGS)
+			AC_SUBST(QT4_LIBS)
+		], [
+			AC_MSG_RESULT(no)
+			AC_MSG_WARN([
+Qt4 development headers not found.
+Use --disable-kwallet if you do not need KWallet support.
+])
+			enable_kwallet=no
+		])
+fi
+
+if test "x$enable_kwallet" = "xyes"; then
+	AC_MSG_CHECKING([for metaobject compiler])
+	MOC=`$PKG_CONFIG --variable=moc_location QtCore`
+	AC_SUBST(MOC)
+	AC_MSG_RESULT([$MOC])
+
+
+	AC_MSG_CHECKING([for KWallet libraries])
+	if test "$ac_kwallet_libs" != "no"; then
+		KWALLET_LIBS="-L$ac_kwallet_libs -lkdeui"
+	elif test "x$KDE4_CONFIG" != "xno"; then
+		KWALLET_LIBS="-L`$KDE4_CONFIG --install lib`/kde4/devel -lkdeui"
+	else
+		KWALLET_LIBS="-lkdeui"
+	fi
+	KWALLET_LIBS="$KWALLET_LIBS"
+	CPPFLAGS="$CPPFLAGS $KWALLET_CXXFLAGS"
+	LDFLAGS="$LDFLAGS $KWALLET_LIBS $QT4_LIBS"
+	AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <kwallet.h>],
+		[KWallet::Wallet::LocalWallet();])], [AC_MSG_RESULT([yes])],
+		[if test "x$force_deps" = "xyes"; then
+AC_MSG_ERROR([
+KWallet development libraries not found.
+Use --disable-kwallet if you do not need KWallet support.
+])
+fi
+])
+
+fi
+CPPFLAGS="$CPPFLAGS_save"
+LDFLAGS="$LDFLAGS_save"
+AC_LANG_POP
+
+AC_SUBST(KWALLET_CXXFLAGS)
+AC_SUBST(KWALLET_LIBS)
+
+AM_CONDITIONAL(ENABLE_KWALLET, test "x$enable_kwallet" = "xyes")
+
+dnl #######################################################################
 dnl # Check for Python
 dnl #######################################################################
 
@@ -2428,6 +2575,7 @@
 	AC_DEFINE(DEBUG, 1, [Define if debugging is enabled.])
 fi
 
+AC_SUBST(DEBUG_CPPFLAGS)
 AC_SUBST(DEBUG_CFLAGS)
 AC_SUBST(LDADD)
 AC_SUBST(LIBS)
@@ -2706,6 +2854,7 @@
 		   libpurple/purple-3.pc
 		   libpurple/purple-3-uninstalled.pc
 		   libpurple/plugins/Makefile
+		   libpurple/plugins/keyrings/Makefile
 		   libpurple/plugins/mono/Makefile
 		   libpurple/plugins/mono/api/Makefile
 		   libpurple/plugins/mono/loader/Makefile
@@ -2789,6 +2938,10 @@
 echo Build with GtkSpell support... : $enable_gtkspell
 echo Build with GCR widgets........ : $enable_gcr
 echo
+echo Build with Secret Service..... : $enable_secret_service
+echo Build with GNOME Keyring...... : $enable_gnome_keyring
+echo Build with KWallet............ : $enable_kwallet
+echo
 echo Build with plugin support..... : $enable_plugins
 echo Build with Mono support....... : $enable_mono
 echo Build with Perl support....... : $enable_perl
--- a/finch/gntaccount.c	Wed May 01 11:48:56 2013 +0200
+++ b/finch/gntaccount.c	Wed May 01 11:52:20 2013 +0200
@@ -195,9 +195,9 @@
 			gnt_check_box_get_checked(GNT_CHECK_BOX(dialog->remember)));
 	value = gnt_entry_get_text(GNT_ENTRY(dialog->password));
 	if (value && *value)
-		purple_account_set_password(account, value);
+		purple_account_set_password(account, value, NULL, NULL);
 	else
-		purple_account_set_password(account, NULL);
+		purple_account_set_password(account, NULL, NULL, NULL);
 
 	/* Mail notification */
 	purple_account_set_check_mail(account,
@@ -534,7 +534,8 @@
 }
 
 static void
-edit_account(PurpleAccount *account)
+edit_account_continue(PurpleAccount *account, 
+	const gchar *password, GError *error, gpointer user_data)
 {
 	GntWidget *window, *hbox;
 	GntWidget *combo, *button, *entry;
@@ -617,7 +618,7 @@
 	gnt_box_add_widget(GNT_BOX(hbox), gnt_label_new(_("Password:")));
 	gnt_box_add_widget(GNT_BOX(hbox), entry);
 	if (account)
-		gnt_entry_set_text(GNT_ENTRY(entry), purple_account_get_password(account));
+		gnt_entry_set_text(GNT_ENTRY(entry), password);
 
 	hbox = gnt_hbox_new(TRUE);
 	gnt_box_set_pad(GNT_BOX(hbox), 0);
@@ -667,6 +668,12 @@
 }
 
 static void
+edit_account(PurpleAccount *account)
+{
+	purple_account_get_password(account, edit_account_continue, account);
+}
+
+static void
 add_account_cb(GntWidget *widget, gpointer null)
 {
 	edit_account(NULL);
--- a/finch/gntprefs.c	Wed May 01 11:48:56 2013 +0200
+++ b/finch/gntprefs.c	Wed May 01 11:52:20 2013 +0200
@@ -1,6 +1,7 @@
 /**
  * @file gntprefs.c GNT Preferences API
  * @ingroup finch
+ * @todo: add support for master password changing.
  */
 
 /* finch
@@ -195,6 +196,12 @@
 	{PURPLE_PREF_NONE, NULL, NULL, NULL},
 };
 
+static Prefs keyring[] =
+{
+	{PURPLE_PREF_STRING, "/purple/keyring/active", N_("Active keyring"), purple_keyring_get_options},
+	{PURPLE_PREF_NONE, NULL, NULL, NULL}
+};
+
 static Prefs idle[] =
 {
 	{PURPLE_PREF_STRING, "/purple/away/idle_reporting", N_("Report Idle time"), get_idle_options},
@@ -250,6 +257,7 @@
 
 	add_pref_group(fields, _("Buddy List"), blist);
 	add_pref_group(fields, _("Conversations"), convs);
+	add_pref_group(fields, _("Keyring"), keyring);
 	add_pref_group(fields, _("Logging"), logging);
 	add_pref_group(fields, _("Idle"), idle);
 
--- a/libpurple/Makefile.am	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/Makefile.am	Wed May 01 11:52:20 2013 +0200
@@ -53,6 +53,7 @@
 	http.c \
 	idle.c \
 	imgstore.c \
+	keyring.c \
 	log.c \
 	media/backend-fs2.c \
 	media/backend-iface.c \
@@ -122,6 +123,7 @@
 	http.h \
 	idle.h \
 	imgstore.h \
+	keyring.h \
 	log.h \
 	media.h \
 	media-gst.h \
--- a/libpurple/Makefile.mingw	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/Makefile.mingw	Wed May 01 11:52:20 2013 +0200
@@ -67,6 +67,7 @@
 			http.c \
 			idle.c \
 			imgstore.c \
+			keyring.c \
 			log.c \
 			media/candidate.c \
 			media/enum-types.c \
--- a/libpurple/account.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/account.c	Wed May 01 11:52:20 2013 +0200
@@ -28,6 +28,7 @@
 #include "core.h"
 #include "dbus-maybe.h"
 #include "debug.h"
+#include "keyring.h"
 #include "network.h"
 #include "notify.h"
 #include "pounce.h"
@@ -70,6 +71,12 @@
 	guint ref;
 } PurpleAccountRequestInfo;
 
+typedef struct
+{
+	PurpleCallback cb;
+	gpointer data;
+} PurpleCallbackBundle;
+
 static PurpleAccountUiOps *account_ui_ops = NULL;
 
 static GList   *accounts = NULL;
@@ -366,11 +373,33 @@
 	child = xmlnode_new_child(node, "name");
 	xmlnode_insert_data(child, purple_account_get_username(account), -1);
 
-	if (purple_account_get_remember_password(account) &&
-		((tmp = purple_account_get_password(account)) != NULL))
+	if (purple_account_get_remember_password(account))
 	{
-		child = xmlnode_new_child(node, "password");
-		xmlnode_insert_data(child, tmp, -1);
+		const char *keyring_id = NULL;
+		const char *mode = NULL;
+		char *data = NULL;
+		GError *error = NULL;
+		GDestroyNotify destroy = NULL;
+		gboolean exported = purple_keyring_export_password(account,
+			&keyring_id, &mode, &data, &error, &destroy);
+
+		if (error != NULL) {
+			purple_debug_error("account",
+				"Failed to export password for account %s: %s.\n",
+				purple_account_get_username(account),
+				error->message);
+		} else if (exported) {
+			child = xmlnode_new_child(node, "password");
+			if (keyring_id != NULL)
+				xmlnode_set_attrib(child, "keyring_id", keyring_id);
+			if (mode != NULL)
+				xmlnode_set_attrib(child, "mode", mode);
+			if (data != NULL)
+				xmlnode_insert_data(child, data, -1);
+
+			if (destroy != NULL)
+				destroy(data);
+		}
 	}
 
 	if ((tmp = purple_account_get_alias(account)) != NULL)
@@ -868,15 +897,6 @@
 	g_free(name);
 	g_free(protocol_id);
 
-	/* Read the password */
-	child = xmlnode_get_child(node, "password");
-	if ((child != NULL) && ((data = xmlnode_get_data(child)) != NULL))
-	{
-		purple_account_set_remember_password(ret, TRUE);
-		purple_account_set_password(ret, data);
-		g_free(data);
-	}
-
 	/* Read the alias */
 	child = xmlnode_get_child(node, "alias");
 	if ((child != NULL) && ((data = xmlnode_get_data(child)) != NULL))
@@ -940,6 +960,25 @@
 		parse_current_error(child, ret);
 	}
 
+	/* Read the password */
+	child = xmlnode_get_child(node, "password");
+	if (child != NULL)
+	{
+		const char *keyring_id = xmlnode_get_attrib(child, "keyring_id");
+		const char *mode = xmlnode_get_attrib(child, "mode");
+		gboolean result;
+
+		data = xmlnode_get_data(child);
+		result = purple_keyring_import_password(ret, keyring_id, mode, data, NULL);
+
+		if (result == TRUE || purple_keyring_get_inuse() == NULL) {
+			purple_account_set_remember_password(ret, TRUE);
+		} else {
+			purple_debug_error("account", "Failed to import password.\n");
+		} 
+		purple_str_wipe(data);
+	}
+
 	return ret;
 }
 
@@ -1059,7 +1098,7 @@
 
 	g_free(account->username);
 	g_free(account->alias);
-	g_free(account->password);
+	purple_str_wipe(account->password);
 	g_free(account->user_info);
 	g_free(account->buddy_icon_path);
 	g_free(account->protocol_id);
@@ -1107,6 +1146,15 @@
 	account->registration_cb_user_data = user_data;
 }
 
+static void
+purple_account_register_got_password_cb(PurpleAccount *account,
+	const gchar *password, GError *error, gpointer data)
+{
+	g_return_if_fail(account != NULL);
+
+	_purple_connection_new(account, TRUE, password);
+}
+
 void
 purple_account_register(PurpleAccount *account)
 {
@@ -1115,7 +1163,21 @@
 	purple_debug_info("account", "Registering account %s\n",
 					purple_account_get_username(account));
 
-	_purple_connection_new(account, TRUE, purple_account_get_password(account));
+	purple_keyring_get_password(account,
+		purple_account_register_got_password_cb, NULL);
+}
+
+static void
+purple_account_unregister_got_password_cb(PurpleAccount *account,
+	const gchar *password, GError *error, gpointer data)
+{
+	PurpleCallbackBundle *cbb = data;
+	PurpleAccountUnregistrationCb cb;
+
+	cb = (PurpleAccountUnregistrationCb)cbb->cb;
+	_purple_connection_new_unregister(account, password, cb, cbb->data);
+
+	g_free(cbb);
 }
 
 void
@@ -1130,12 +1192,19 @@
 void
 purple_account_unregister(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data)
 {
+	PurpleCallbackBundle *cbb;
+
 	g_return_if_fail(account != NULL);
 
 	purple_debug_info("account", "Unregistering account %s\n",
 					  purple_account_get_username(account));
 
-	_purple_connection_new_unregister(account, purple_account_get_password(account), cb, user_data);
+	cbb = g_new0(PurpleCallbackBundle, 1);
+	cbb->cb = PURPLE_CALLBACK(cb);
+	cbb->data = user_data;
+
+	purple_keyring_get_password(account,
+		purple_account_unregister_got_password_cb, cbb);
 }
 
 static void
@@ -1153,11 +1222,12 @@
 		return;
 	}
 
-	if(remember)
-		purple_account_set_remember_password(account, TRUE);
-
-	purple_account_set_password(account, entry);
-
+	if (!remember)
+		purple_keyring_set_password(account, NULL, NULL, NULL);
+
+	purple_account_set_remember_password(account, remember);
+
+	purple_account_set_password(account, entry, NULL, NULL);
 	_purple_connection_new(account, FALSE, entry);
 }
 
@@ -1210,11 +1280,27 @@
 	g_free(primary);
 }
 
+static void
+purple_account_connect_got_password_cb(PurpleAccount *account,
+	const gchar *password, GError *error, gpointer data)
+{
+	PurplePluginProtocolInfo *prpl_info = data;
+
+	if ((password == NULL || *password == '\0') &&
+		!(prpl_info->options & OPT_PROTO_NO_PASSWORD) &&
+		!(prpl_info->options & OPT_PROTO_PASSWORD_OPTIONAL))
+		purple_account_request_password(account,
+			G_CALLBACK(request_password_ok_cb),
+			G_CALLBACK(request_password_cancel_cb), account);
+	else
+		_purple_connection_new(account, FALSE, password);
+}
+
 void
 purple_account_connect(PurpleAccount *account)
 {
 	PurplePlugin *prpl;
-	const char *password, *username;
+	const char *username;
 	PurplePluginProtocolInfo *prpl_info;
 
 	g_return_if_fail(account != NULL);
@@ -1241,13 +1327,13 @@
 	purple_debug_info("account", "Connecting to account %s.\n", username);
 
 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
-	password = purple_account_get_password(account);
-	if ((password == NULL) &&
-		!(prpl_info->options & OPT_PROTO_NO_PASSWORD) &&
-		!(prpl_info->options & OPT_PROTO_PASSWORD_OPTIONAL))
-		purple_account_request_password(account, G_CALLBACK(request_password_ok_cb), G_CALLBACK(request_password_cancel_cb), account);
-	else
-		_purple_connection_new(account, FALSE, password);
+	if (account->password != NULL) {
+		purple_account_connect_got_password_cb(account,
+			account->password, NULL, prpl_info);
+	} else {
+		purple_keyring_get_password(account,
+			purple_account_connect_got_password_cb, prpl_info);
+	}
 }
 
 void
@@ -1267,8 +1353,6 @@
 
 	gc = purple_account_get_connection(account);
 	_purple_connection_destroy(gc);
-	if (!purple_account_get_remember_password(account))
-		purple_account_set_password(account, NULL);
 	purple_account_set_connection(account, NULL);
 
 	account->disconnecting = FALSE;
@@ -1626,15 +1710,27 @@
 		blist_ops->save_account(account);
 }
 
-void
-purple_account_set_password(PurpleAccount *account, const char *password)
+void 
+purple_account_set_password(PurpleAccount *account, const gchar *password,
+	PurpleKeyringSaveCallback cb, gpointer data)
 {
 	g_return_if_fail(account != NULL);
 
-	g_free(account->password);
+	purple_str_wipe(account->password);
 	account->password = g_strdup(password);
 
 	schedule_accounts_save();
+
+	if (!purple_account_get_remember_password(account)) {
+		purple_debug_info("account",
+			"Password for %s set, not sent to keyring.\n",
+			purple_account_get_username(account));
+
+		if (cb != NULL)
+			cb(account, NULL, data);
+	} else {
+		purple_keyring_set_password(account, password, cb, data);
+	}
 }
 
 void
@@ -2141,12 +2237,52 @@
 	return account->username;
 }
 
-const char *
-purple_account_get_password(const PurpleAccount *account)
+static void
+purple_account_get_password_got(PurpleAccount *account,
+	const gchar *password, GError *error, gpointer data)
 {
-	g_return_val_if_fail(account != NULL, NULL);
-
-	return account->password;
+	PurpleCallbackBundle *cbb = data;
+	PurpleKeyringReadCallback cb;
+
+	purple_debug_info("account",
+		"Read password for account %s from async keyring.\n",
+		purple_account_get_username(account));
+
+	purple_str_wipe(account->password);
+	account->password = g_strdup(password);
+
+	cb = (PurpleKeyringReadCallback)cbb->cb;
+	if (cb != NULL)
+		cb(account, password, error, cbb->data);
+
+	g_free(cbb);
+}
+
+void
+purple_account_get_password(PurpleAccount *account,
+	PurpleKeyringReadCallback cb, gpointer data)
+{
+	if (account == NULL) {
+		cb(NULL, NULL, NULL, data);
+		return;
+	}
+
+	if (account->password != NULL) {
+		purple_debug_info("account",
+			"Reading password for account %s from cache.\n",
+			purple_account_get_username(account));
+		cb(account, account->password, NULL, data);
+	} else {
+		PurpleCallbackBundle *cbb = g_new0(PurpleCallbackBundle, 1);
+		cbb->cb = PURPLE_CALLBACK(cb);
+		cbb->data = data;
+
+		purple_debug_info("account",
+			"Reading password for account %s from async keyring.\n",
+			purple_account_get_username(account));
+		purple_keyring_get_password(account, 
+			purple_account_get_password_got, cbb);
+	}
 }
 
 const char *
@@ -2661,7 +2797,7 @@
 	PurpleConnection *gc = purple_account_get_connection(account);
 	PurplePlugin *prpl = NULL;
 
-	purple_account_set_password(account, new_pw);
+	purple_account_set_password(account, new_pw, NULL, NULL);
 
 	if (gc != NULL)
 		prpl = purple_connection_get_prpl(gc);
@@ -2768,6 +2904,14 @@
 	                   account, type, description);
 }
 
+static void
+password_migration_cb(PurpleAccount *account)
+{
+	g_return_if_fail(account != NULL);
+
+	schedule_accounts_save();
+}
+
 const PurpleConnectionErrorInfo *
 purple_account_get_current_error(PurpleAccount *account)
 {
@@ -2812,6 +2956,12 @@
 	purple_signal_emit(purple_accounts_get_handle(), "account-removed", account);
 }
 
+static void
+purple_accounts_delete_set(PurpleAccount *account, GError *error, gpointer data)
+{
+	purple_account_destroy(account);
+}
+
 void
 purple_accounts_delete(PurpleAccount *account)
 {
@@ -2882,7 +3032,11 @@
 	/* This will cause the deletion of an old buddy icon. */
 	purple_buddy_icons_set_account_icon(account, NULL, 0);
 
-	purple_account_destroy(account);
+	/* This is async because we do not want the
+	 * account being overwritten before we are done.
+	 */
+	purple_keyring_set_password(account, NULL,
+		purple_accounts_delete_set, NULL);
 }
 
 void
@@ -3133,6 +3287,8 @@
 	                      PURPLE_CALLBACK(signed_off_cb), NULL);
 	purple_signal_connect(conn_handle, "connection-error", handle,
 	                      PURPLE_CALLBACK(connection_error_cb), NULL);
+	purple_signal_connect(purple_keyring_get_handle(), "password-migration", handle,
+	                      PURPLE_CALLBACK(password_migration_cb), NULL);
 
 	load_accounts();
 
--- a/libpurple/account.h	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/account.h	Wed May 01 11:52:20 2013 +0200
@@ -50,6 +50,7 @@
 #include "proxy.h"
 #include "prpl.h"
 #include "status.h"
+#include "keyring.h"
 
 /**
  * Account request types.
@@ -364,10 +365,18 @@
 /**
  * Sets the account's password.
  *
+ * The password in the keyring might not be immediately updated, but the cached
+ * version will be, and it is therefore safe to read the password back before
+ * the callback has been triggered. One can also set a NULL callback if
+ * notification of saving to the keyring is not required.
+ *
  * @param account  The account.
  * @param password The password.
+ * @param cb       A callback for once the password is saved.
+ * @param data     A pointer to be passed to the callback.
  */
-void purple_account_set_password(PurpleAccount *account, const char *password);
+void purple_account_set_password(PurpleAccount *account, const gchar *password,
+	PurpleKeyringSaveCallback cb, gpointer data);
 
 /**
  * Sets the account's alias.
@@ -675,13 +684,19 @@
 const char *purple_account_get_username(const PurpleAccount *account);
 
 /**
- * Returns the account's password.
+ * Reads the password for the account.
+ *
+ * This is an asynchronous call, that will return the password in a callback
+ * once it has been read from the keyring. If the account is connected, and you
+ * require the password immediately, then consider using @ref
+ * purple_connection_get_password instead.
  *
  * @param account The account.
- *
- * @return The password.
+ * @param cb      The callback to give the password.
+ * @param data    A pointer passed to the callback.
  */
-const char *purple_account_get_password(const PurpleAccount *account);
+void purple_account_get_password(PurpleAccount *account,
+	PurpleKeyringReadCallback cb, gpointer data);
 
 /**
  * Returns the account's alias.
--- a/libpurple/connection.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/connection.c	Wed May 01 11:52:20 2013 +0200
@@ -284,7 +284,7 @@
 
 	purple_account_set_connection(account, NULL);
 
-	g_free(gc->password);
+	purple_str_wipe(gc->password);
 	g_free(gc->display_name);
 
 	if (gc->disconnect_timeout > 0)
@@ -458,7 +458,7 @@
 {
 	g_return_val_if_fail(gc != NULL, NULL);
 
-	return gc->password ? gc->password : purple_account_get_password(gc->account);
+	return gc->password;
 }
 
 const char *
@@ -512,7 +512,6 @@
 {
 	PurpleAccount *account;
 	PurpleConnection *gc;
-	char *password;
 
 	account = data;
 	gc = purple_account_get_connection(account);
@@ -520,11 +519,7 @@
 	if (gc != NULL)
 		gc->disconnect_timeout = 0;
 
-	password = g_strdup(purple_account_get_password(account));
 	purple_account_disconnect(account);
-	purple_account_set_password(account, password);
-	g_free(password);
-
 	return FALSE;
 }
 
--- a/libpurple/core.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/core.c	Wed May 01 11:52:20 2013 +0200
@@ -37,6 +37,7 @@
 #include "http.h"
 #include "idle.h"
 #include "imgstore.h"
+#include "keyring.h"
 #include "network.h"
 #include "notify.h"
 #include "plugin.h"
@@ -117,6 +118,7 @@
 		purple_value_new(PURPLE_TYPE_BOXED, "GHashTable *")); /* Parameters */
 
 	purple_signal_register(core, "quitting", purple_marshal_VOID, NULL, 0);
+	purple_signal_register(core, "core-initialized", purple_marshal_VOID, NULL, 0);
 
 	/* The prefs subsystem needs to be initialized before static protocols
 	 * for protocol prefs to work. */
@@ -150,6 +152,7 @@
 
 	purple_plugins_probe(G_MODULE_SUFFIX);
 
+	purple_keyring_init(); /* before accounts */
 	purple_theme_manager_init();
 
 	/* The buddy icon code uses the imgstore, so init it early. */
@@ -196,6 +199,8 @@
 	/* Load the buddy list after UI init */
 	purple_blist_boot();
 
+	purple_signal_emit(purple_get_core(), "core-initialized");
+
 	return TRUE;
 }
 
@@ -243,6 +248,7 @@
 	purple_savedstatuses_uninit();
 	purple_status_uninit();
 	purple_accounts_uninit();
+	purple_keyring_uninit(); /* after accounts */
 	purple_sound_uninit();
 	purple_theme_manager_uninit();
 	purple_xfers_uninit();
--- a/libpurple/example/nullclient.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/example/nullclient.c	Wed May 01 11:52:20 2013 +0200
@@ -298,7 +298,7 @@
 
 	/* Get the password for the account */
 	password = getpass("Password: ");
-	purple_account_set_password(account, password);
+	purple_account_set_password(account, password, NULL, NULL);
 
 	/* It's necessary to enable the account first. */
 	purple_account_set_enabled(account, UI_ID, TRUE);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/keyring.c	Wed May 01 11:52:20 2013 +0200
@@ -0,0 +1,1316 @@
+/**
+ * @file keyring.c Keyring 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <glib.h>
+#include <string.h>
+#include "account.h"
+#include "keyring.h"
+#include "signals.h"
+#include "core.h"
+#include "debug.h"
+#include "internal.h"
+#include "dbus-maybe.h"
+
+struct _PurpleKeyring
+{
+	gchar *name;
+	gchar *id;
+	PurpleKeyringRead read_password;
+	PurpleKeyringSave save_password;
+	PurpleKeyringCancelRequests cancel_requests;
+	PurpleKeyringClose close_keyring;
+	PurpleKeyringChangeMaster change_master;
+	PurpleKeyringImportPassword import_password;
+	PurpleKeyringExportPassword export_password;
+};
+
+typedef struct
+{
+	GError *error;
+	PurpleKeyringSetInUseCallback cb;
+	gpointer cb_data;
+	PurpleKeyring *new;
+	PurpleKeyring *old;
+
+	/**
+	 * We are done when finished is positive and read_outstanding is zero.
+	 */
+	gboolean finished;
+	int read_outstanding;
+
+	gboolean abort;
+	gboolean force;
+	gboolean succeeded;
+} PurpleKeyringChangeTracker;
+
+typedef void (*PurpleKeyringDropCallback)(gpointer data);
+
+typedef struct
+{
+	PurpleKeyringDropCallback cb;
+	gpointer cb_data;
+
+	gboolean finished;
+	int drop_outstanding;
+} PurpleKeyringDropTracker;
+
+typedef struct
+{
+	PurpleKeyringSaveCallback cb;
+	gpointer cb_data;
+} PurpleKeyringSetPasswordData;
+
+typedef struct
+{
+	gchar *keyring_id;
+	gchar *mode;
+	gchar *data;
+} PurpleKeyringFailedImport;
+
+static void
+purple_keyring_change_tracker_free(PurpleKeyringChangeTracker *tracker)
+{
+	if (tracker->error)
+		g_error_free(tracker->error);
+	g_free(tracker);
+}
+
+static void
+purple_keyring_failed_import_free(PurpleKeyringFailedImport *import)
+{
+	g_return_if_fail(import != NULL);
+
+	g_free(import->keyring_id);
+	g_free(import->mode);
+	purple_str_wipe(import->data);
+	g_free(import);
+}
+
+static void
+purple_keyring_close(PurpleKeyring *keyring);
+
+static void
+purple_keyring_drop_passwords(PurpleKeyring *keyring,
+	PurpleKeyringDropCallback cb, gpointer data);
+
+/* A list of available keyrings */
+static GList *purple_keyring_keyrings = NULL;
+
+/* Keyring being used. */
+static PurpleKeyring *purple_keyring_inuse = NULL;
+
+/* Keyring id marked to use (may not be loadable). */
+static gchar *purple_keyring_to_use = NULL;
+
+static guint purple_keyring_pref_cbid = 0;
+static GList *purple_keyring_loaded_plugins = NULL;
+static PurpleKeyringChangeTracker *current_change_tracker = NULL;
+static gboolean purple_keyring_is_quitting = FALSE;
+static GHashTable *purple_keyring_failed_imports = NULL;
+
+static const gchar *
+purple_keyring_print_account(PurpleAccount *account)
+{
+	static gchar print_buff[100];
+
+	if (account == NULL) {
+		g_snprintf(print_buff, 100, "(null)");
+		return print_buff;
+	}
+
+	g_snprintf(print_buff, 100, "%s:%s",
+		purple_account_get_protocol_id(account),
+		purple_account_get_username(account));
+	return print_buff;
+}
+
+/**************************************************************************/
+/* Setting used keyrings                                                  */
+/**************************************************************************/
+
+PurpleKeyring *
+purple_keyring_find_keyring_by_id(const gchar *id)
+{
+	GList *it;
+
+	for (it = purple_keyring_keyrings; it != NULL; it = it->next) {
+		PurpleKeyring *keyring = it->data;
+		const gchar *curr_id = purple_keyring_get_id(keyring);
+
+		if (g_strcmp0(id, curr_id) == 0)
+			return keyring;
+	}
+
+	return NULL;
+}
+
+static void
+purple_keyring_pref_callback(const gchar *pref, PurplePrefType type,
+	gconstpointer id, gpointer data)
+{
+	PurpleKeyring *new_keyring;
+
+	g_return_if_fail(g_strcmp0(pref, "/purple/keyring/active") == 0);
+	g_return_if_fail(type == PURPLE_PREF_STRING);
+	g_return_if_fail(id != NULL);
+
+	new_keyring = purple_keyring_find_keyring_by_id(id);
+	g_return_if_fail(new_keyring != NULL);
+
+	purple_keyring_set_inuse(new_keyring, FALSE, NULL, NULL);
+}
+
+static void
+purple_keyring_pref_connect(void)
+{
+	g_return_if_fail(purple_keyring_pref_cbid == 0);
+
+	purple_keyring_pref_cbid = purple_prefs_connect_callback(NULL,
+		"/purple/keyring/active", purple_keyring_pref_callback, NULL);
+}
+
+static void
+purple_keyring_pref_disconnect(void)
+{
+	g_return_if_fail(purple_keyring_pref_cbid != 0);
+
+	purple_prefs_disconnect_callback(purple_keyring_pref_cbid);
+	purple_keyring_pref_cbid = 0;
+}
+
+PurpleKeyring *
+purple_keyring_get_inuse(void)
+{
+	return purple_keyring_inuse;
+}
+
+static void
+purple_keyring_set_inuse_drop_cb(gpointer _tracker)
+{
+	PurpleKeyringChangeTracker *tracker = _tracker;
+
+	g_return_if_fail(tracker != NULL);
+
+	if (tracker->succeeded) {
+		purple_keyring_close(tracker->old);
+
+		purple_debug_info("keyring", "Successfully changed keyring.\n");
+
+		purple_keyring_inuse = tracker->new;
+		current_change_tracker = NULL;
+
+		if (tracker->cb != NULL)
+			tracker->cb(NULL, tracker->cb_data);
+		purple_keyring_change_tracker_free(tracker);
+		return;
+	}
+
+	purple_debug_error("keyring", "Failed to change keyring, aborting.\n");
+
+	purple_keyring_close(tracker->new);
+
+	purple_keyring_pref_disconnect();
+	purple_prefs_set_string("/purple/keyring/active",
+		purple_keyring_get_id(tracker->old));
+	purple_keyring_pref_connect();
+
+	current_change_tracker = NULL;
+
+	if (tracker->error == NULL) {
+		tracker->error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_UNKNOWN,
+			"Unknown error has occured");
+	}
+
+	if (tracker->cb != NULL)
+		tracker->cb(tracker->error, tracker->cb_data);
+
+	purple_keyring_change_tracker_free(tracker);
+}
+
+static void
+purple_keyring_set_inuse_save_cb(PurpleAccount *account, GError *error,
+	gpointer _tracker)
+{
+	PurpleKeyringChangeTracker *tracker = _tracker;
+
+	g_return_if_fail(account != NULL);
+	g_return_if_fail(tracker != NULL);
+
+	tracker->read_outstanding--;
+
+	if (g_error_matches(error, PURPLE_KEYRING_ERROR,
+		PURPLE_KEYRING_ERROR_NOPASSWORD)) {
+		if (purple_debug_is_verbose()) {
+			purple_debug_misc("keyring", "No password found while "
+				"changing keyring for account %s: %s.\n",
+				purple_keyring_print_account(account),
+				error->message);
+		}
+	} else if (g_error_matches(error, PURPLE_KEYRING_ERROR,
+		PURPLE_KEYRING_ERROR_ACCESSDENIED)) {
+		purple_debug_info("keyring", "Access denied while changing "
+			"keyring for account %s: %s.\n",
+			purple_keyring_print_account(account), error->message);
+		tracker->abort = TRUE;
+		if (tracker->error != NULL)
+			g_error_free(tracker->error);
+		tracker->error = g_error_copy(error);
+	} else if (g_error_matches(error, PURPLE_KEYRING_ERROR,
+		PURPLE_KEYRING_ERROR_CANCELLED)) {
+		purple_debug_info("keyring", "Operation cancelled while "
+			"changing keyring for account %s: %s.\n",
+			purple_keyring_print_account(account), error->message);
+		tracker->abort = TRUE;
+		if (tracker->error == NULL)
+			tracker->error = g_error_copy(error);
+	} else if (g_error_matches(error, PURPLE_KEYRING_ERROR,
+		PURPLE_KEYRING_ERROR_BACKENDFAIL)) {
+		purple_debug_error("keyring", "Failed to communicate with "
+			"backend while changing keyring for account %s: %s. "
+			"Aborting changes.\n",
+			purple_keyring_print_account(account), error->message);
+		tracker->abort = TRUE;
+		if (tracker->error != NULL)
+			g_error_free(tracker->error);
+		tracker->error = g_error_copy(error);
+	} else if (error != NULL) {
+		purple_debug_error("keyring", "Unknown error while changing "
+			"keyring for account %s: %s. Aborting changes.\n",
+			purple_keyring_print_account(account), error->message);
+		tracker->abort = TRUE;
+		if (tracker->error == NULL)
+			tracker->error = g_error_copy(error);
+	}
+
+	purple_signal_emit(purple_keyring_get_handle(), "password-migration",
+		account);
+
+	if (!tracker->finished || tracker->read_outstanding > 0)
+		return;
+
+	/* This was the last one. */
+	if (tracker->abort && !tracker->force) {
+		tracker->succeeded = FALSE;
+		purple_keyring_drop_passwords(tracker->new,
+			purple_keyring_set_inuse_drop_cb, tracker);
+	} else {
+		tracker->succeeded = TRUE;
+		purple_keyring_drop_passwords(tracker->old,
+			purple_keyring_set_inuse_drop_cb, tracker);
+	}
+}
+
+static void
+purple_keyring_set_inuse_read_cb(PurpleAccount *account, const gchar *password,
+	GError *error, gpointer _tracker)
+{
+	PurpleKeyringChangeTracker *tracker = _tracker;
+	PurpleKeyringSave save_cb;
+
+	g_return_if_fail(account != NULL);
+	g_return_if_fail(tracker != NULL);
+
+	if (tracker->abort) {
+		purple_keyring_set_inuse_save_cb(account, NULL, tracker);
+		return;
+	}
+
+	if (error != NULL) {
+		if (tracker->force == TRUE || g_error_matches(error,
+			PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_NOPASSWORD)) {
+			/* Don't save password, and ignore it. */
+		} else {
+			tracker->abort = TRUE;
+		}
+		purple_keyring_set_inuse_save_cb(account, error, tracker);
+		return;
+	}
+
+	save_cb = purple_keyring_get_save_password(tracker->new);
+	g_assert(save_cb != NULL);
+
+	save_cb(account, password, purple_keyring_set_inuse_save_cb, tracker);
+}
+
+void
+purple_keyring_set_inuse(PurpleKeyring *newkeyring, gboolean force,
+	PurpleKeyringSetInUseCallback cb, gpointer data)
+{
+	PurpleKeyring *oldkeyring;
+	PurpleKeyringChangeTracker *tracker;
+	GList *it;
+	PurpleKeyringRead read_cb;
+
+	if (current_change_tracker != NULL) {
+		GError *error;
+		purple_debug_error("keyring", "There is password migration "
+			"session already running.\n");
+		if (cb == NULL)
+			return;
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_INTERNAL,
+			"There is a password migration session already running");
+		cb(error, data);
+		g_error_free(error);
+		return;
+	}
+
+	oldkeyring = purple_keyring_get_inuse();
+
+	if (oldkeyring == newkeyring) {
+		if (purple_debug_is_verbose()) {
+			purple_debug_misc("keyring",
+				"Old and new keyring are the same: %s.\n",
+				(newkeyring != NULL) ?
+					newkeyring->id : "(null)");
+		}
+		if (cb != NULL)
+			cb(NULL, data);
+		return;
+	}
+
+	purple_debug_info("keyring", "Attempting to set new keyring: %s.\n",
+		(newkeyring != NULL) ? newkeyring->id : "(null)");
+
+	if (oldkeyring == NULL) { /* No keyring was set before. */
+		if (purple_debug_is_verbose()) {
+			purple_debug_misc("keyring", "Setting keyring for the "
+				"first time: %s.\n", newkeyring->id);
+		}
+
+		purple_keyring_inuse = newkeyring;
+		g_assert(current_change_tracker == NULL);
+		if (cb != NULL)
+			cb(NULL, data);
+		return;
+	}
+
+	/* Starting a migration. */
+
+	read_cb = purple_keyring_get_read_password(oldkeyring);
+	g_assert(read_cb != NULL);
+
+	purple_debug_misc("keyring", "Starting migration from: %s.\n",
+		oldkeyring->id);
+
+	tracker = g_new0(PurpleKeyringChangeTracker, 1);
+	current_change_tracker = tracker;
+
+	tracker->cb = cb;
+	tracker->cb_data = data;
+	tracker->new = newkeyring;
+	tracker->old = oldkeyring;
+	tracker->force = force;
+
+	for (it = purple_accounts_get_all(); it != NULL; it = it->next) {
+		if (tracker->abort) {
+			tracker->finished = TRUE;
+			break;
+		}
+		tracker->read_outstanding++;
+
+		if (it->next == NULL)
+			tracker->finished = TRUE;
+
+		read_cb(it->data, purple_keyring_set_inuse_read_cb, tracker);
+	}
+}
+
+void
+purple_keyring_register(PurpleKeyring *keyring)
+{
+	const gchar *keyring_id;
+
+	g_return_if_fail(keyring != NULL);
+
+	keyring_id = purple_keyring_get_id(keyring);
+
+	purple_debug_info("keyring", "Registering keyring: %s\n",
+		keyring_id ? keyring_id : "(null)");
+
+	if (purple_keyring_get_id(keyring) == NULL ||
+		purple_keyring_get_name(keyring) == NULL ||
+		purple_keyring_get_read_password(keyring) == NULL ||
+		purple_keyring_get_save_password(keyring) == NULL) {
+		purple_debug_error("keyring", "Cannot register %s, some "
+			"required fields are missing.\n",
+			keyring_id ? keyring_id : "(null)");
+		return;
+	}
+
+	if (purple_keyring_find_keyring_by_id(keyring_id) != NULL) {
+		purple_debug_error("keyring",
+			"Keyring is already registered.\n");
+		return;
+	}
+
+	/* If this is the configured keyring, use it. */
+	if (purple_keyring_inuse == NULL &&
+		g_strcmp0(keyring_id, purple_keyring_to_use) == 0) {
+		purple_debug_info("keyring", "Keyring %s matches keyring to "
+			"use, using it.\n", keyring_id);
+		purple_keyring_set_inuse(keyring, TRUE, NULL, NULL);
+	}
+
+	PURPLE_DBUS_REGISTER_POINTER(keyring, PurpleKeyring);
+	purple_signal_emit(purple_keyring_get_handle(), "keyring-register",
+		keyring_id, keyring);
+	if (purple_debug_is_verbose()) {
+		purple_debug_info("keyring", "Registered keyring: %s.\n",
+			keyring_id);
+	}
+
+	purple_keyring_keyrings = g_list_prepend(purple_keyring_keyrings,
+		keyring);
+}
+
+void
+purple_keyring_unregister(PurpleKeyring *keyring)
+{
+	PurpleKeyring *inuse;
+	PurpleKeyring *fallback;
+	const gchar *keyring_id;
+
+	g_return_if_fail(keyring != NULL);
+
+	keyring_id = purple_keyring_get_id(keyring);
+
+	purple_debug_info("keyring", "Unregistering keyring: %s.\n",
+		keyring_id);
+
+	purple_signal_emit(purple_keyring_get_handle(), "keyring-unregister",
+		keyring_id, keyring);
+	PURPLE_DBUS_UNREGISTER_POINTER(keyring);
+
+	inuse = purple_keyring_get_inuse();
+	fallback = purple_keyring_find_keyring_by_id(PURPLE_DEFAULT_KEYRING);
+
+	if (inuse == keyring) {
+		if (inuse != fallback) {
+			purple_keyring_set_inuse(fallback, TRUE, NULL, NULL);
+		} else {
+			fallback = NULL;
+			purple_keyring_set_inuse(NULL, TRUE, NULL, NULL);
+		}
+	}
+
+	purple_keyring_keyrings = g_list_remove(purple_keyring_keyrings,
+		keyring);
+}
+
+GList *
+purple_keyring_get_options(void)
+{
+	GList *options = NULL;
+	GList *it;
+	static gchar currentDisabledName[40];
+
+	if (purple_keyring_get_inuse() == NULL && purple_keyring_to_use != NULL
+		&& purple_keyring_to_use[0] != '\0') {
+		g_snprintf(currentDisabledName, sizeof(currentDisabledName),
+			_("%s (disabled)"), purple_keyring_to_use);
+
+		options = g_list_append(options, currentDisabledName);
+		options = g_list_append(options, purple_keyring_to_use);
+	}
+
+	for (it = purple_keyring_keyrings; it != NULL; it = it->next) {
+		PurpleKeyring *keyring = it->data;
+
+		options = g_list_append(options,
+			(gpointer)purple_keyring_get_name(keyring));
+		options = g_list_append(options,
+			(gpointer)purple_keyring_get_id(keyring));
+	}
+
+	return options;
+}
+
+
+/**************************************************************************/
+/* Keyring plugin wrappers                                                */
+/**************************************************************************/
+
+static void
+purple_keyring_close(PurpleKeyring *keyring)
+{
+	PurpleKeyringClose close_cb;
+
+	g_return_if_fail(keyring != NULL);
+
+	close_cb = purple_keyring_get_close_keyring(keyring);
+
+	if (close_cb != NULL)
+		close_cb();
+}
+
+static void
+purple_keyring_drop_passwords_save_cb(PurpleAccount *account, GError *error,
+	gpointer _tracker)
+{
+	PurpleKeyringDropTracker *tracker = _tracker;
+
+	tracker->drop_outstanding--;
+
+	if (!tracker->finished || tracker->drop_outstanding > 0)
+		return;
+
+	if (tracker->cb)
+		tracker->cb(tracker->cb_data);
+	g_free(tracker);
+}
+
+static void
+purple_keyring_drop_passwords(PurpleKeyring *keyring,
+	PurpleKeyringDropCallback cb, gpointer data)
+{
+	GList *it;
+	PurpleKeyringSave save_cb;
+	PurpleKeyringDropTracker *tracker;
+
+	g_return_if_fail(keyring != NULL);
+
+	save_cb = purple_keyring_get_save_password(keyring);
+	g_assert(save_cb != NULL);
+
+	tracker = g_new0(PurpleKeyringDropTracker, 1);
+	tracker->cb = cb;
+	tracker->cb_data = data;
+
+	for (it = purple_accounts_get_all(); it != NULL; it = it->next) {
+		PurpleAccount *account = it->data;
+
+		tracker->drop_outstanding++;
+		if (it->next == NULL)
+			tracker->finished = TRUE;
+
+		save_cb(account, NULL, purple_keyring_drop_passwords_save_cb,
+			tracker);
+	}
+}
+
+gboolean
+purple_keyring_import_password(PurpleAccount *account, const gchar *keyring_id,
+	const gchar *mode, const gchar *data, GError **error)
+{
+	PurpleKeyring *keyring;
+	PurpleKeyring *inuse;
+	PurpleKeyringImportPassword import;
+
+	g_return_val_if_fail(account != NULL, FALSE);
+
+	if (keyring_id == NULL)
+		keyring_id = PURPLE_DEFAULT_KEYRING;
+
+	purple_debug_misc("keyring", "Importing password for account %s to "
+		"keyring %s.\n", purple_keyring_print_account(account),
+		keyring_id);
+
+	keyring = purple_keyring_find_keyring_by_id(keyring_id);
+	if (keyring == NULL) {
+		if (error != NULL) {
+			*error = g_error_new(PURPLE_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_BACKENDFAIL,
+				"Specified keyring is not registered.");
+		}
+		purple_debug_warning("Keyring", "Specified keyring is not "
+			"registered, cannot import password info for account "
+			"%s.\n", purple_keyring_print_account(account));
+		return FALSE;
+	}
+
+	inuse = purple_keyring_get_inuse();
+	if (inuse == NULL) {
+		PurpleKeyringFailedImport *import;
+		if (error != NULL) {
+			*error = g_error_new(PURPLE_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_NOKEYRING,
+				"No keyring loaded, cannot import password "
+				"info");
+		}
+		purple_debug_warning("Keyring",
+			"No keyring loaded, cannot import password info for "
+			"account %s.\n", purple_keyring_print_account(account));
+
+		import = g_new0(PurpleKeyringFailedImport, 1);
+		import->keyring_id = g_strdup(keyring_id);
+		import->mode = g_strdup(mode);
+		import->data = g_strdup(data);
+		g_hash_table_insert(purple_keyring_failed_imports, account,
+			import);
+		return FALSE;
+	}
+
+	if (inuse != keyring) {
+		if (error != NULL) {
+			*error = g_error_new(PURPLE_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_INTERNAL,
+				"Specified keyring id does not match the "
+				"loaded one.");
+		}
+		purple_debug_error("keyring",
+			"Specified keyring %s is not currently used (%s). "
+			"Data will be lost.\n", keyring_id,
+			purple_keyring_get_id(inuse));
+		return FALSE;
+	}
+
+	import = purple_keyring_get_import_password(inuse);
+	if (import == NULL) {
+		if (purple_debug_is_verbose()) {
+			purple_debug_misc("Keyring", "Configured keyring "
+				"cannot import password info. This might be "
+				"normal.\n");
+		}
+		return TRUE;
+	}
+
+	return import(account, mode, data, error);
+}
+
+gboolean
+purple_keyring_export_password(PurpleAccount *account, const gchar **keyring_id,
+	const gchar **mode, gchar **data, GError **error,
+	GDestroyNotify *destroy)
+{
+	PurpleKeyring *inuse;
+	PurpleKeyringExportPassword export;
+
+	g_return_val_if_fail(account != NULL, FALSE);
+	g_return_val_if_fail(keyring_id != NULL, FALSE);
+	g_return_val_if_fail(mode != NULL, FALSE);
+	g_return_val_if_fail(data != NULL, FALSE);
+	g_return_val_if_fail(error != NULL, FALSE);
+
+	inuse = purple_keyring_get_inuse();
+
+	if (inuse == NULL) {
+		PurpleKeyringFailedImport *import = g_hash_table_lookup(
+			purple_keyring_failed_imports, account);
+
+		if (import == NULL) {
+			*error = g_error_new(PURPLE_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_NOKEYRING,
+				"No keyring configured, cannot export password "
+				"info");
+			purple_debug_warning("keyring",
+				"No keyring configured, cannot export password "
+				"info.\n");
+			return FALSE;
+		} else {
+			purple_debug_info("keyring", "No keyring configured, "
+				"getting fallback export data for %s.\n",
+				purple_keyring_print_account(account));
+
+			*keyring_id = import->keyring_id;
+			*mode = import->mode;
+			*data = g_strdup(import->data);
+			*destroy = (GDestroyNotify)purple_str_wipe;
+			return TRUE;
+		}
+	}
+
+	if (purple_debug_is_verbose()) {
+		purple_debug_misc("keyring",
+			"Exporting password for account %s from keyring %s\n",
+			purple_keyring_print_account(account),
+			purple_keyring_get_id(inuse));
+	}
+
+	*keyring_id = purple_keyring_get_id(inuse);
+
+	export = purple_keyring_get_export_password(inuse);
+	if (export == NULL) {
+		if (purple_debug_is_verbose()) {
+			purple_debug_misc("Keyring", "Configured keyring "
+				"cannot export password info. This might be "
+				"normal.\n");
+		}
+		*mode = NULL;
+		*data = NULL;
+		*destroy = NULL;
+		return TRUE;
+	}
+
+	return export(account, mode, data, error, destroy);
+}
+
+void
+purple_keyring_get_password(PurpleAccount *account,
+	PurpleKeyringReadCallback cb, gpointer data)
+{
+	GError *error;
+	PurpleKeyring *inuse;
+	PurpleKeyringRead read_cb;
+
+	g_return_if_fail(account != NULL);
+
+	if (purple_keyring_is_quitting) {
+		purple_debug_error("keyring", "Cannot request a password while "
+			"quitting.\n");
+		if (cb == NULL)
+			return;
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_INTERNAL,
+			"Cannot request a password while quitting.");
+		cb(account, NULL, error, data);
+		g_error_free(error);
+		return;
+	}
+
+	inuse = purple_keyring_get_inuse();
+
+	if (inuse == NULL) {
+		purple_debug_error("keyring", "No keyring configured.\n");
+		if (cb == NULL)
+			return;
+
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_NOKEYRING,
+			"No keyring configured.");
+		cb(account, NULL, error, data);
+		g_error_free(error);
+	}
+
+	read_cb = purple_keyring_get_read_password(inuse);
+	g_assert(read_cb != NULL);
+
+	purple_debug_info("keyring", "Reading password for account %s...\n",
+		purple_keyring_print_account(account));
+	read_cb(account, cb, data);
+}
+
+static void
+purple_keyring_set_password_save_cb(PurpleAccount *account, GError *error,
+	gpointer _set_data)
+{
+	PurpleKeyringSetPasswordData *set_data = _set_data;
+
+	g_return_if_fail(account != NULL);
+	g_return_if_fail(set_data != NULL);
+
+	if (error == NULL && purple_debug_is_verbose()) {
+		purple_debug_misc("keyring", "Password for account %s "
+			"saved successfully.\n",
+			purple_keyring_print_account(account));
+	} else if (purple_debug_is_verbose()) {
+		purple_debug_warning("keyring", "Password for account %s "
+			"not saved successfully.\n",
+			purple_keyring_print_account(account));
+	}
+
+	if (error != NULL) {
+		purple_notify_error(NULL, _("Keyrings"),
+			_("Failed to save a password in keyring."),
+			error->message);
+	}
+
+	if (set_data->cb != NULL)
+		set_data->cb(account, error, set_data->cb_data);
+	g_free(set_data);
+}
+
+void
+purple_keyring_set_password(PurpleAccount *account, const gchar *password,
+	PurpleKeyringSaveCallback cb, gpointer data)
+{
+	GError *error;
+	PurpleKeyring *inuse;
+	PurpleKeyringSave save_cb;
+	PurpleKeyringSetPasswordData *set_data;
+
+	g_return_if_fail(account != NULL);
+
+	if (purple_keyring_is_quitting) {
+		purple_debug_error("keyring", "Cannot save a password while "
+			"quitting.\n");
+		if (cb == NULL)
+			return;
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_INTERNAL,
+			"Cannot save a password while quitting.");
+		cb(account, error, data);
+		g_error_free(error);
+		return;
+	}
+
+	if (current_change_tracker != NULL) {
+		purple_debug_error("keyring", "Cannot save a password during "
+			"password migration.\n");
+		if (cb == NULL)
+			return;
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_INTERNAL,
+			"Cannot save a password during password migration.");
+		cb(account, error, data);
+		g_error_free(error);
+		return;
+	}
+
+	inuse = purple_keyring_get_inuse();
+	if (inuse == NULL) {
+		if (cb == NULL)
+			return;
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_NOKEYRING,
+			"No keyring configured.");
+		cb(account, error, data);
+		g_error_free(error);
+		return;
+	}
+
+	save_cb = purple_keyring_get_save_password(inuse);
+	g_assert(save_cb != NULL);
+
+	set_data = g_new(PurpleKeyringSetPasswordData, 1);
+	set_data->cb = cb;
+	set_data->cb_data = data;
+	purple_debug_info("keyring", "%s password for account %s...\n",
+		(password ? "Saving" : "Removing"),
+		purple_keyring_print_account(account));
+	save_cb(account, password, purple_keyring_set_password_save_cb, set_data);
+}
+
+/* TODO: is it usable at all? */
+void
+purple_keyring_change_master(PurpleKeyringChangeMasterCallback cb,
+	gpointer data)
+{
+	GError *error;
+	PurpleKeyring *inuse;
+	PurpleKeyringChangeMaster change;
+
+	if (purple_keyring_is_quitting || current_change_tracker != NULL) {
+		purple_debug_error("keyring", "Cannot change a master password "
+			"at the moment.\n");
+		if (cb == NULL)
+			return;
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_INTERNAL,
+			"Cannot change a master password at the moment.");
+		cb(error, data);
+		g_error_free(error);
+		return;
+	}
+
+	inuse = purple_keyring_get_inuse();
+	if (inuse == NULL) {
+		purple_debug_error("keyring", "No keyring configured, cannot "
+			"change master password.\n");
+		if (cb == NULL)
+			return;
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_NOKEYRING,
+			"No keyring configured, cannot change master "
+			"password.");
+		cb(error, data);
+		g_error_free(error);
+		return;
+	}
+
+	change = purple_keyring_get_change_master(inuse);
+	if (change == NULL) {
+		purple_debug_error("keyring", "Keyring doesn't support master "
+			"passwords.\n");
+		if (cb == NULL)
+			return;
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_BACKENDFAIL,
+			"Keyring doesn't support master passwords.");
+		cb(error, data);
+		g_error_free(error);
+		return;
+	}
+
+	change(cb, data);
+}
+
+
+/**************************************************************************/
+/* PurpleKeyring accessors                                                */
+/**************************************************************************/
+
+PurpleKeyring *
+purple_keyring_new(void)
+{
+	return g_new0(PurpleKeyring, 1);
+}
+
+void
+purple_keyring_free(PurpleKeyring *keyring)
+{
+	g_return_if_fail(keyring != NULL);
+
+	g_free(keyring->name);
+	g_free(keyring->id);
+	g_free(keyring);
+}
+
+const gchar *
+purple_keyring_get_name(const PurpleKeyring *keyring)
+{
+	g_return_val_if_fail(keyring != NULL, NULL);
+
+	return keyring->name;
+}
+
+const gchar *
+purple_keyring_get_id(const PurpleKeyring *keyring)
+{
+	g_return_val_if_fail(keyring != NULL, NULL);
+
+	return keyring->id;
+}
+
+PurpleKeyringRead
+purple_keyring_get_read_password(const PurpleKeyring *keyring)
+{
+	g_return_val_if_fail(keyring != NULL, NULL);
+
+	return keyring->read_password;
+}
+
+PurpleKeyringSave
+purple_keyring_get_save_password(const PurpleKeyring *keyring)
+{
+	g_return_val_if_fail(keyring != NULL, NULL);
+
+	return keyring->save_password;
+}
+
+PurpleKeyringCancelRequests
+purple_keyring_get_cancel_requests(const PurpleKeyring *keyring)
+{
+	g_return_val_if_fail(keyring != NULL, NULL);
+
+	return keyring->cancel_requests;
+}
+
+PurpleKeyringClose
+purple_keyring_get_close_keyring(const PurpleKeyring *keyring)
+{
+	g_return_val_if_fail(keyring != NULL, NULL);
+
+	return keyring->close_keyring;
+}
+
+PurpleKeyringChangeMaster
+purple_keyring_get_change_master(const PurpleKeyring *keyring)
+{
+	g_return_val_if_fail(keyring != NULL, NULL);
+
+	return keyring->change_master;
+}
+
+PurpleKeyringImportPassword
+purple_keyring_get_import_password(const PurpleKeyring *keyring)
+{
+	g_return_val_if_fail(keyring != NULL, NULL);
+
+	return keyring->import_password;
+}
+
+PurpleKeyringExportPassword
+purple_keyring_get_export_password(const PurpleKeyring *keyring)
+{
+	g_return_val_if_fail(keyring != NULL, NULL);
+
+	return keyring->export_password;
+}
+
+void
+purple_keyring_set_name(PurpleKeyring *keyring, const gchar *name)
+{
+	g_return_if_fail(keyring != NULL);
+	g_return_if_fail(name != NULL);
+
+	g_free(keyring->name);
+	keyring->name = g_strdup(name);
+}
+
+void
+purple_keyring_set_id(PurpleKeyring *keyring, const gchar *id)
+{
+	g_return_if_fail(keyring != NULL);
+	g_return_if_fail(id != NULL);
+
+	g_free(keyring->id);
+	keyring->id = g_strdup(id);
+}
+
+void
+purple_keyring_set_read_password(PurpleKeyring *keyring,
+	PurpleKeyringRead read_cb)
+{
+	g_return_if_fail(keyring != NULL);
+	g_return_if_fail(read_cb != NULL);
+
+	keyring->read_password = read_cb;
+}
+
+void
+purple_keyring_set_save_password(PurpleKeyring *keyring,
+	PurpleKeyringSave save_cb)
+{
+	g_return_if_fail(keyring != NULL);
+	g_return_if_fail(save_cb != NULL);
+
+	keyring->save_password = save_cb;
+}
+
+void
+purple_keyring_set_cancel_requests(PurpleKeyring *keyring,
+	PurpleKeyringCancelRequests cancel_requests)
+{
+	g_return_if_fail(keyring != NULL);
+
+	keyring->cancel_requests = cancel_requests;
+}
+
+void
+purple_keyring_set_close_keyring(PurpleKeyring *keyring,
+	PurpleKeyringClose close_cb)
+{
+	g_return_if_fail(keyring != NULL);
+
+	keyring->close_keyring = close_cb;
+}
+
+void
+purple_keyring_set_change_master(PurpleKeyring *keyring,
+	PurpleKeyringChangeMaster change_master)
+{
+	g_return_if_fail(keyring != NULL);
+
+	keyring->change_master = change_master;
+}
+
+void
+purple_keyring_set_import_password(PurpleKeyring *keyring,
+	PurpleKeyringImportPassword import_password)
+{
+	g_return_if_fail(keyring != NULL);
+
+	keyring->import_password = import_password;
+}
+
+void
+purple_keyring_set_export_password(PurpleKeyring *keyring,
+	PurpleKeyringExportPassword export_password)
+{
+	g_return_if_fail(keyring != NULL);
+
+	keyring->export_password = export_password;
+}
+
+
+/**************************************************************************/
+/* Error Codes                                                            */
+/**************************************************************************/
+
+GQuark purple_keyring_error_domain(void)
+{
+	return g_quark_from_static_string("libpurple keyring");
+}
+
+/**************************************************************************/
+/* Keyring Subsystem                                                      */
+/**************************************************************************/
+
+static void purple_keyring_core_initialized_cb(void)
+{
+	if (purple_keyring_inuse == NULL) {
+		purple_notify_error(NULL, _("Keyrings"),
+			_("Failed to load selected keyring."),
+			_("Check your system configuration or select another "
+			"one in Preferences dialog."));
+	}
+}
+
+static void purple_keyring_core_quitting_cb()
+{
+	PurpleKeyringCancelRequests cancel;
+
+	if (current_change_tracker != NULL) {
+		PurpleKeyringChangeTracker *tracker = current_change_tracker;
+		tracker->abort = TRUE;
+		if (tracker->old) {
+			cancel = purple_keyring_get_cancel_requests(
+				tracker->old);
+			if (cancel)
+				cancel();
+		}
+		if (current_change_tracker == tracker && tracker->new) {
+			cancel = purple_keyring_get_cancel_requests(
+				tracker->new);
+			if (cancel)
+				cancel();
+		}
+	}
+
+	purple_keyring_is_quitting = TRUE;
+	if (purple_keyring_inuse != NULL) {
+		cancel = purple_keyring_get_cancel_requests(
+			purple_keyring_inuse);
+		if (cancel)
+			cancel();
+	}
+}
+
+void
+purple_keyring_init(void)
+{
+	const gchar *touse;
+	GList *it;
+
+	purple_keyring_keyrings = NULL;
+	purple_keyring_inuse = NULL;
+
+	purple_keyring_failed_imports = g_hash_table_new_full(g_direct_hash,
+		g_direct_equal, NULL,
+		(GDestroyNotify)purple_keyring_failed_import_free);
+
+	/* void keyring_register(const char *keyring_id,
+	 *      PurpleKeyring * keyring);
+	 *
+	 * A signal called when keyring is registered.
+	 *
+	 * @param keyring_id The keyring ID.
+	 * @param keyring    The keyring.
+	 */
+	purple_signal_register(purple_keyring_get_handle(),
+		"keyring-register",
+		purple_marshal_VOID__POINTER_POINTER,
+		NULL, 2,
+		purple_value_new(PURPLE_TYPE_STRING),
+		purple_value_new(PURPLE_TYPE_BOXED, "PurpleKeyring *"));
+
+	/* void keyring_unregister(const char *keyring_id,
+	 *      PurpleKeyring * keyring);
+	 *
+	 * A signal called when keyring is unregistered.
+	 *
+	 * @param keyring_id The keyring ID.
+	 * @param keyring    The keyring.
+	 */
+	purple_signal_register(purple_keyring_get_handle(),
+		"keyring-unregister",
+		purple_marshal_VOID__POINTER_POINTER,
+		NULL, 2,
+		purple_value_new(PURPLE_TYPE_STRING),
+		purple_value_new(PURPLE_TYPE_BOXED, "PurpleKeyring *"));
+
+	/* void password_migration(PurpleAccount* account);
+	 *
+	 * A signal called, when a password for the account was moved to another
+	 * keyring.
+	 *
+	 * @param account The account.
+	 */
+	purple_signal_register(purple_keyring_get_handle(),
+		"password-migration",
+		purple_marshal_VOID__POINTER,
+		NULL, 1,
+		purple_value_new(PURPLE_TYPE_BOXED, "PurpleAccount *"));
+
+	touse = purple_prefs_get_string("/purple/keyring/active");
+	if (touse == NULL) {
+		purple_prefs_add_none("/purple/keyring");
+		purple_prefs_add_string("/purple/keyring/active",
+			PURPLE_DEFAULT_KEYRING);
+		purple_keyring_to_use = g_strdup(PURPLE_DEFAULT_KEYRING);
+	} else
+		purple_keyring_to_use = g_strdup(touse);
+
+	purple_keyring_pref_connect();
+
+	for (it = purple_plugins_get_all(); it != NULL; it = it->next)
+	{
+		PurplePlugin *plugin = (PurplePlugin *)it->data;
+
+		if (plugin->info == NULL || plugin->info->id == NULL)
+			continue;
+		if (strncmp(plugin->info->id, "keyring-", 8) != 0)
+			continue;
+
+		if (purple_plugin_is_loaded(plugin))
+			continue;
+
+		if (purple_plugin_load(plugin))
+		{
+			purple_keyring_loaded_plugins = g_list_append(
+				purple_keyring_loaded_plugins, plugin);
+		}
+	}
+
+	if (purple_keyring_inuse == NULL)
+		purple_debug_error("keyring", "Selected keyring failed to load\n");
+
+	purple_signal_connect(purple_get_core(), "core-initialized",
+		purple_keyring_get_handle(),
+		PURPLE_CALLBACK(purple_keyring_core_initialized_cb), NULL);
+	purple_signal_connect(purple_get_core(), "quitting",
+		purple_keyring_get_handle(),
+		PURPLE_CALLBACK(purple_keyring_core_quitting_cb), NULL);
+}
+
+void
+purple_keyring_uninit(void)
+{
+	GList *it;
+
+	purple_keyring_pref_disconnect();
+
+	g_free(purple_keyring_to_use);
+	purple_keyring_inuse = NULL;
+
+	g_hash_table_destroy(purple_keyring_failed_imports);
+	purple_keyring_failed_imports = NULL;
+
+	for (it = g_list_first(purple_keyring_loaded_plugins); it != NULL;
+		it = g_list_next(it))
+	{
+		PurplePlugin *plugin = (PurplePlugin *)it->data;
+		if (g_list_find(purple_plugins_get_loaded(), plugin) == NULL)
+			continue;
+		purple_plugin_unload(plugin);
+	}
+	g_list_free(purple_keyring_loaded_plugins);
+	purple_keyring_loaded_plugins = NULL;
+
+	purple_signals_unregister_by_instance(purple_keyring_get_handle());
+	purple_signals_disconnect_by_handle(purple_keyring_get_handle());
+}
+
+void *
+purple_keyring_get_handle(void)
+{
+	static int handle;
+
+	return &handle;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/keyring.h	Wed May 01 11:52:20 2013 +0200
@@ -0,0 +1,522 @@
+/**
+ * @file keyring.h Keyring 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _PURPLE_KEYRING_H_
+#define _PURPLE_KEYRING_H_
+
+#include "account.h"
+
+/**
+ * Default keyring ID.
+ */
+#define PURPLE_DEFAULT_KEYRING "keyring-internal"
+
+/**
+ * Keyring subsystem error domain.
+ */
+#define PURPLE_KEYRING_ERROR purple_keyring_error_domain()
+
+/**************************************************************************/
+/** @name Data structures and types                                       */
+/**************************************************************************/
+/*@{*/
+
+typedef struct _PurpleKeyring PurpleKeyring;
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Callbacks for keyrings access functions                         */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Callback for once a password is read.
+ *
+ * If there was a problem, the password will be NULL, and the error set.
+ *
+ * @param account  The account.
+ * @param password The password.
+ * @param error    Error that may have occurred.
+ * @param data     Data passed to the callback.
+ */
+typedef void (*PurpleKeyringReadCallback)(PurpleAccount *account,
+	const gchar *password, GError *error, gpointer data);
+
+/**
+ * Callback for once a password has been stored.
+ *
+ * If there was a problem, the error will be set.
+ *
+ * @param account The account.
+ * @param error   Error that may have occurred.
+ * @param data    Data passed to the callback.
+ */
+typedef void (*PurpleKeyringSaveCallback)(PurpleAccount *account, GError *error,
+	gpointer data);
+
+/**
+ * Callback for once the master password for a keyring has been changed.
+ *
+ * @param error  Error that has occurred.
+ * @param data   Data passed to the callback.
+ */
+typedef void (*PurpleKeyringChangeMasterCallback)(GError *error, gpointer data);
+
+/**
+ * Callback for when we change the keyring.
+ *
+ * @param error   An error that might have occurred.
+ * @param data    A pointer to user supplied data.
+ */
+typedef void (*PurpleKeyringSetInUseCallback)(GError *error, gpointer data);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Keyrings access functions                                       */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Read the password for an account.
+ *
+ * @param account The account.
+ * @param cb      A callback for once the password is found.
+ * @param data    Data to be passed to the callback.
+ */
+typedef void (*PurpleKeyringRead)(PurpleAccount *account,
+	PurpleKeyringReadCallback cb, gpointer data);
+
+/**
+ * Store a password in the keyring.
+ *
+ * @param account  The account.
+ * @param password The password to be stored. If the password is NULL, this
+ *                 means that the keyring should forget about that password.
+ * @param cb       A callback for once the password is saved.
+ * @param data     Data to be passed to the callback.
+ */
+typedef void (*PurpleKeyringSave)(PurpleAccount *account, const gchar *password,
+	PurpleKeyringSaveCallback cb, gpointer data);
+
+/**
+ * Cancel all running requests.
+ *
+ * After calling that, all queued requests should run their callbacks (most
+ * probably, with failure result).
+ */
+typedef void (*PurpleKeyringCancelRequests)(void);
+
+/**
+ * Close the keyring.
+ *
+ * This will be called so the keyring can do any cleanup it needs.
+ */
+typedef void (*PurpleKeyringClose)(void);
+
+/**
+ * Change the master password for the keyring.
+ *
+ * @param cb    A callback for once the master password has been changed.
+ * @param data  Data to be passed to the callback.
+ */
+typedef void (*PurpleKeyringChangeMaster)(PurpleKeyringChangeMasterCallback cb,
+	gpointer data);
+
+/**
+ * Import serialized (and maybe encrypted) password.
+ *
+ * This is not async because it is not meant to prompt for a master password and
+ * decrypt passwords.
+ *
+ * @param account The account.
+ * @param mode    A keyring specific option that was stored. Can be NULL.
+ * @param data    Data that was stored. Can be NULL.
+ *
+ * @return TRUE on success, FALSE on failure.
+ */
+typedef gboolean (*PurpleKeyringImportPassword)(PurpleAccount *account,
+	const gchar *mode, const gchar *data, GError **error);
+
+/**
+ * Export serialized (and maybe encrypted) password.
+ *
+ * @param account The account.
+ * @param mode    An option field that can be used by the plugin. This is
+ *                expected to be a static string.
+ * @param data    The data to be stored in the XML node. This string will be
+ *                freed using destroy() once not needed anymore.
+ * @param error   Will be set if a problem occured.
+ * @param destroy A function to be called, if non NULL, to free data.
+ *
+ * @return TRUE on success, FALSE on failure.
+ */
+typedef gboolean (*PurpleKeyringExportPassword)(PurpleAccount *account,
+	const gchar **mode, gchar **data, GError **error,
+	GDestroyNotify *destroy);
+
+/*@}*/
+
+G_BEGIN_DECLS
+
+/**************************************************************************/
+/** @name Setting used keyrings                                           */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Find a keyring by an id.
+ *
+ * @param id The id for the keyring.
+ *
+ * @return The keyring, or NULL if not found.
+ */
+PurpleKeyring *
+purple_keyring_find_keyring_by_id(const gchar *id);
+
+/**
+ * Get the keyring being used.
+ */
+PurpleKeyring *
+purple_keyring_get_inuse(void);
+
+/**
+ * Set the keyring to use. This function will move all passwords from
+ * the old keyring to the new one.
+ *
+ * If it fails, it will cancel all changes, close the new keyring, and notify
+ * the callback. If it succeeds, it will remove all passwords from the old safe
+ * and close that safe.
+ *
+ * @param newkeyring The new keyring to use.
+ * @param force      FALSE if the change can be cancelled. If this is TRUE and
+ *                   an error occurs, data might be lost.
+ * @param cb         A callback for once the change is complete.
+ * @param data       Data to be passed to the callback.
+ */
+void
+purple_keyring_set_inuse(PurpleKeyring *newkeyring, gboolean force,
+	PurpleKeyringSetInUseCallback cb, gpointer data);
+
+/**
+ * Register a keyring plugin.
+ *
+ * @param keyring The keyring to register.
+ */
+void
+purple_keyring_register(PurpleKeyring *keyring);
+
+/**
+ * Unregister a keyring plugin.
+ *
+ * In case the keyring is in use, passwords will be moved to a fallback safe,
+ * and the keyring to unregister will be properly closed.
+ *
+ * @param keyring The keyring to unregister.
+ */
+void
+purple_keyring_unregister(PurpleKeyring *keyring);
+
+/**
+ * Returns a GList containing the IDs and names of the registered
+ * keyrings.
+ *
+ * @return The list of IDs and names.
+ */
+GList *
+purple_keyring_get_options(void);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Keyring plugin wrappers                                         */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Import serialized (and maybe encrypted) password into current keyring.
+ *
+ * It's used by account.c while reading a password from xml.
+ *
+ * @param account    The account.
+ * @param keyring_id The plugin ID that was stored in the xml file. Can be NULL.
+ * @param mode       A keyring specific option that was stored. Can be NULL.
+ * @param data       Data that was stored, can be NULL.
+ *
+ * @return TRUE if the input was accepted, FALSE otherwise.
+ */
+gboolean
+purple_keyring_import_password(PurpleAccount *account, const gchar *keyring_id,
+	const gchar *mode, const gchar *data, GError **error);
+
+/**
+ * Export serialized (and maybe encrypted) password out of current keyring.
+ *
+ * It's used by account.c while syncing accounts to xml.
+ *
+ * @param account    The account for which we want the info.
+ * @param keyring_id The plugin id to be stored in the XML node. This will be
+ *                   NULL or a string that can be considered static.
+ * @param mode       An option field that can be used by the plugin. This will
+ *                   be NULL or a string that can be considered static.
+ * @param data       The data to be stored in the XML node. This string must be
+ *                   freed using destroy() once not needed anymore if it is not
+ *                   NULL.
+ * @param error      Will be set if a problem occured.
+ * @param destroy    A function to be called, if non NULL, to free data.
+ *
+ * @return TRUE if the info was exported successfully, FALSE otherwise.
+ */
+gboolean
+purple_keyring_export_password(PurpleAccount *account, const gchar **keyring_id,
+	const gchar **mode, gchar **data, GError **error,
+	GDestroyNotify *destroy);
+
+/**
+ * Read a password from the current keyring.
+ *
+ * @param account The account.
+ * @param cb      A callback for once the password is read.
+ * @param data    Data passed to the callback.
+ */
+void
+purple_keyring_get_password(PurpleAccount *account,
+	PurpleKeyringReadCallback cb, gpointer data);
+
+/**
+ * Save a password to the current keyring.
+ *
+ * @param account  The account.
+ * @param password The password to save.
+ * @param cb       A callback for once the password is saved.
+ * @param data     Data to be passed to the callback.
+ */
+void
+purple_keyring_set_password(PurpleAccount *account, const gchar *password,
+	PurpleKeyringSaveCallback cb, gpointer data);
+
+/**
+ * Change the master password for a safe (if the safe supports it).
+ *
+ * @param cb   A callback for once the master password has been changed.
+ * @param data Data to be passed to the callback.
+ *
+ * @todo Where is the master password string, that is being changed?
+ */
+void
+purple_keyring_change_master(PurpleKeyringChangeMasterCallback cb,
+	gpointer data);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name PurpleKeyring accessors                                         */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates a new keyring wrapper.
+ */
+PurpleKeyring *
+purple_keyring_new(void);
+
+/**
+ * Frees all data allocated with purple_keyring_new.
+ *
+ * @param keyring Keyring wrapper struct.
+ */
+void
+purple_keyring_free(PurpleKeyring *keyring);
+
+/**
+ * Gets friendly user name.
+ *
+ * @param keyring The keyring.
+ * @return Friendly user name.
+ */
+const gchar *
+purple_keyring_get_name(const PurpleKeyring *keyring);
+
+/**
+ * Gets keyring ID.
+ *
+ * @param keyring The keyring.
+ * @return Keyring ID.
+ */
+const gchar *
+purple_keyring_get_id(const PurpleKeyring *keyring);
+
+PurpleKeyringRead
+purple_keyring_get_read_password(const PurpleKeyring *keyring);
+
+PurpleKeyringSave
+purple_keyring_get_save_password(const PurpleKeyring *keyring);
+
+PurpleKeyringCancelRequests
+purple_keyring_get_cancel_requests(const PurpleKeyring *keyring);
+
+PurpleKeyringClose
+purple_keyring_get_close_keyring(const PurpleKeyring *keyring);
+
+PurpleKeyringChangeMaster
+purple_keyring_get_change_master(const PurpleKeyring *keyring);
+
+PurpleKeyringImportPassword
+purple_keyring_get_import_password(const PurpleKeyring *keyring);
+
+PurpleKeyringExportPassword
+purple_keyring_get_export_password(const PurpleKeyring *keyring);
+
+/**
+ * Sets friendly user name.
+ *
+ * This field is required.
+ *
+ * @param keyring The keyring.
+ * @param name    Friendly user name.
+ */
+void
+purple_keyring_set_name(PurpleKeyring *keyring, const gchar *name);
+
+/**
+ * Sets keyring ID.
+ *
+ * This field is required.
+ *
+ * @param keyring The keyring.
+ * @param name    Keyring ID.
+ */
+void
+purple_keyring_set_id(PurpleKeyring *keyring, const gchar *id);
+
+/**
+ * Sets read password method.
+ *
+ * This field is required.
+ *
+ * @param keyring The keyring.
+ * @param read_cb Read password method.
+ */
+void
+purple_keyring_set_read_password(PurpleKeyring *keyring,
+	PurpleKeyringRead read_cb);
+
+/**
+ * Sets save password method.
+ *
+ * This field is required.
+ *
+ * @param keyring The keyring.
+ * @param save_cb Save password method.
+ */
+void
+purple_keyring_set_save_password(PurpleKeyring *keyring,
+	PurpleKeyringSave save_cb);
+
+void
+purple_keyring_set_cancel_requests(PurpleKeyring *keyring,
+	PurpleKeyringCancelRequests cancel_requests);
+
+void
+purple_keyring_set_close_keyring(PurpleKeyring *keyring,
+	PurpleKeyringClose close_cb);
+
+void
+purple_keyring_set_change_master(PurpleKeyring *keyring,
+	PurpleKeyringChangeMaster change_master);
+
+void
+purple_keyring_set_import_password(PurpleKeyring *keyring,
+	PurpleKeyringImportPassword import_password);
+
+void
+purple_keyring_set_export_password(PurpleKeyring *keyring,
+	PurpleKeyringExportPassword export_password);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Error Codes                                                     */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Gets keyring subsystem error domain.
+ *
+ * @return keyring subsystem error domain.
+ */
+GQuark
+purple_keyring_error_domain(void);
+
+/**
+ * Error codes for keyring subsystem.
+ */
+enum PurpleKeyringError
+{
+	PURPLE_KEYRING_ERROR_UNKNOWN = 0,     /**< Unknown error. */
+
+	PURPLE_KEYRING_ERROR_NOKEYRING = 10,  /**< No keyring configured. */
+	PURPLE_KEYRING_ERROR_INTERNAL,        /**< Internal keyring system error. */
+	PURPLE_KEYRING_ERROR_BACKENDFAIL,     /**< Failed to communicate with the backend or internal backend error. */
+
+	PURPLE_KEYRING_ERROR_NOPASSWORD = 20, /**< No password stored for the specified account. */
+	PURPLE_KEYRING_ERROR_ACCESSDENIED,    /**< Access denied for the specified keyring or entry. */
+	PURPLE_KEYRING_ERROR_CANCELLED        /**< Operation was cancelled. */
+};
+
+/*}@*/
+
+/**************************************************************************/
+/** @name Keyring Subsystem                                               */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Initializes the keyring subsystem.
+ */
+void
+purple_keyring_init(void);
+
+/**
+ * Uninitializes the keyring subsystem.
+ */
+void
+purple_keyring_uninit(void);
+
+/**
+ * Returns the keyring subsystem handle.
+ *
+ * @return The keyring subsystem handle.
+ */
+void *
+purple_keyring_get_handle(void);
+
+/*}@*/
+
+G_END_DECLS
+
+#endif /* _PURPLE_KEYRING_H_ */
--- a/libpurple/plugins/Makefile.am	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/plugins/Makefile.am	Wed May 01 11:52:20 2013 +0200
@@ -1,4 +1,4 @@
-DIST_SUBDIRS = mono perl ssl tcl
+DIST_SUBDIRS = mono perl ssl tcl keyrings
 
 if USE_PERL
 PERL_DIR = perl
@@ -20,7 +20,8 @@
 	$(MONO_DIR) \
 	$(PERL_DIR) \
 	ssl \
-	$(TCL_DIR)
+	$(TCL_DIR) \
+	keyrings
 
 plugindir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
 
--- a/libpurple/plugins/Makefile.mingw	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/plugins/Makefile.mingw	Wed May 01 11:52:20 2013 +0200
@@ -10,6 +10,7 @@
 PERL_PLUGIN := ./perl
 TCL_PLUGIN := ./tcl
 SSL_PLUGIN := ./ssl
+KEYRING_PLUGIN := ./keyrings
 
 .SUFFIXES:
 .SUFFIXES: .c .dll
@@ -48,11 +49,13 @@
 	$(MAKE) -C $(PERL_PLUGIN) -f $(MINGW_MAKEFILE)
 	$(MAKE) -C $(TCL_PLUGIN) -f $(MINGW_MAKEFILE)
 	$(MAKE) -C $(SSL_PLUGIN) -f $(MINGW_MAKEFILE)
+	$(MAKE) -C $(KEYRING_PLUGIN) -f $(MINGW_MAKEFILE)
 
 install: all $(PURPLE_INSTALL_PLUGINS_DIR)
 	$(MAKE) -C $(PERL_PLUGIN) -f $(MINGW_MAKEFILE) install
 	$(MAKE) -C $(TCL_PLUGIN) -f $(MINGW_MAKEFILE) install
 	$(MAKE) -C $(SSL_PLUGIN) -f $(MINGW_MAKEFILE) install
+	$(MAKE) -C $(KEYRING_PLUGIN) -f $(MINGW_MAKEFILE) install
 	cp *.dll $(PURPLE_INSTALL_PLUGINS_DIR)
 
 %.dll: %.c $(PURPLE_CONFIG_H) $(PURPLE_VERSION_H)
@@ -78,5 +81,6 @@
 	$(MAKE) -C $(PERL_PLUGIN) -f $(MINGW_MAKEFILE) clean
 	$(MAKE) -C $(TCL_PLUGIN) -f $(MINGW_MAKEFILE) clean
 	$(MAKE) -C $(SSL_PLUGIN) -f $(MINGW_MAKEFILE) clean
+	$(MAKE) -C $(KEYRING_PLUGIN) -f $(MINGW_MAKEFILE) clean
 
 include $(PIDGIN_COMMON_TARGETS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/plugins/keyrings/Makefile.am	Wed May 01 11:52:20 2013 +0200
@@ -0,0 +1,75 @@
+EXTRA_DIST = \
+		Makefile.mingw
+CLEANFILES =
+
+plugindir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
+
+internalkeyring_la_CFLAGS  = $(AM_CPPFLAGS)
+internalkeyring_la_LDFLAGS = -module -avoid-version
+internalkeyring_la_SOURCES = internalkeyring.c
+internalkeyring_la_LIBADD  = $(GLIB_LIBS)
+
+if ENABLE_SECRETSERVICE
+
+secretservice_la_CFLAGS  = $(AM_CPPFLAGS) $(SECRETSERVICE_CFLAGS)
+secretservice_la_LDFLAGS = -module -avoid-version
+secretservice_la_SOURCES = secretservice.c
+secretservice_la_LIBADD  = $(GLIB_LIBS) $(SECRETSERVICE_LIBS)
+
+endif
+
+if ENABLE_GNOMEKEYRING
+
+gnomekeyring_la_CFLAGS  = $(AM_CPPFLAGS) $(GNOMEKEYRING_CFLAGS)
+gnomekeyring_la_LDFLAGS = -module -avoid-version
+gnomekeyring_la_SOURCES = gnomekeyring.c
+gnomekeyring_la_LIBADD  = $(GLIB_LIBS) $(GNOMEKEYRING_LIBS)
+
+endif
+
+if ENABLE_KWALLET
+
+kwallet_la_CXXFLAGS  = $(KWALLET_CXXFLAGS) $(QT4_CFLAGS)
+kwallet_la_LDFLAGS = -module -avoid-version
+kwallet_la_SOURCES = kwallet.cpp
+kwallet_la_BUILTSOURCES = kwallet.moc
+kwallet_la_LIBADD  = $(GLIB_LIBS) $(KWALLET_LIBS) $(QT4_LIBS)
+
+kwallet.cpp: kwallet.moc
+
+kwallet.moc:
+	$(AM_V_GEN)$(MOC) $(kwallet_la_CXXFLAGS) -i kwallet.cpp -o $@
+
+CLEANFILES += kwallet.moc
+
+endif
+
+if PLUGINS
+
+plugin_LTLIBRARIES = \
+	internalkeyring.la
+
+if ENABLE_SECRETSERVICE
+plugin_LTLIBRARIES += \
+	secretservice.la
+endif
+
+if ENABLE_GNOMEKEYRING
+plugin_LTLIBRARIES += \
+	gnomekeyring.la
+endif
+
+if ENABLE_KWALLET
+plugin_LTLIBRARIES += \
+	kwallet.la
+endif
+
+endif
+
+#XXX: that might be done better than adding DEBUG_CPPFLAGS to all objects (not only C++ ones)
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/libpurple \
+	-I$(top_builddir)/libpurple \
+	$(GLIB_CFLAGS) \
+	$(DEBUG_CPPFLAGS) \
+	$(PLUGIN_CFLAGS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/plugins/keyrings/Makefile.mingw	Wed May 01 11:52:20 2013 +0200
@@ -0,0 +1,81 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for keyring plugins.
+#
+
+PIDGIN_TREE_TOP := ../../..
+include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+
+##
+## VARIABLE DEFINITIONS
+##
+TARGET_INTERNAL = internalkeyring
+TARGET_WINCRED = wincred
+
+##
+## INCLUDE PATHS
+##
+INCLUDE_PATHS += \
+	-I. \
+	-I$(GTK_TOP)/include \
+	-I$(GTK_TOP)/include/glib-2.0 \
+	-I$(GTK_TOP)/lib/glib-2.0/include \
+	-I$(PURPLE_TOP) \
+	-I$(PURPLE_TOP)/win32 \
+	-I$(PIDGIN_TREE_TOP)
+
+LIB_PATHS += \
+	-L$(GTK_TOP)/lib \
+	-L$(PURPLE_TOP)
+
+##
+##  SOURCES, OBJECTS
+##
+C_SRC_INTERNAL = internalkeyring.c
+OBJECTS_INTERNAL = $(C_SRC_INTERNAL:%.c=%.o)
+
+C_SRC_WINCRED = wincred.c
+OBJECTS_WINCRED = $(C_SRC_WINCRED:%.c=%.o)
+
+##
+## LIBRARIES
+##
+LIBS =	\
+			-lglib-2.0 \
+			-lws2_32 \
+			-lintl \
+			-lpurple
+
+include $(PIDGIN_COMMON_RULES)
+
+##
+## TARGET DEFINITIONS
+##
+.PHONY: all install clean
+
+all: $(TARGET_INTERNAL).dll $(TARGET_WINCRED).dll
+
+install: all $(PURPLE_INSTALL_PLUGINS_DIR) $(PURPLE_INSTALL_DIR)
+	cp $(TARGET_INTERNAL).dll $(PURPLE_INSTALL_PLUGINS_DIR)
+	cp $(TARGET_WINCRED).dll $(PURPLE_INSTALL_PLUGINS_DIR)
+
+$(OBJECTS_INTERNAL): $(PURPLE_CONFIG_H)
+
+##
+## BUILD DLL
+##
+$(TARGET_INTERNAL).dll: $(PURPLE_DLL) $(OBJECTS_INTERNAL)
+	$(CC) -shared $(OBJECTS_INTERNAL) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET_INTERNAL).dll
+
+$(TARGET_WINCRED).dll: $(PURPLE_DLL) $(OBJECTS_WINCRED)
+	$(CC) -shared $(OBJECTS_WINCRED) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET_WINCRED).dll
+
+##
+## CLEAN RULES
+##
+clean:
+	rm -f $(OBJECTS_INTERNAL) $(TARGET_INTERNAL).dll
+	rm -f $(OBJECTS_WINCRED) $(TARGET_WINCRED).dll
+
+include $(PIDGIN_COMMON_TARGETS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/plugins/keyrings/gnomekeyring.c	Wed May 01 11:52:20 2013 +0200
@@ -0,0 +1,454 @@
+/**
+ * @file gnomekeyring.c Gnome keyring password storage
+ * @ingroup plugins
+ */
+
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include "internal.h"
+#include "account.h"
+#include "debug.h"
+#include "keyring.h"
+#include "plugin.h"
+#include "version.h"
+
+#include <gnome-keyring.h>
+#include <gnome-keyring-memory.h>
+
+#define GNOMEKEYRING_NAME        N_("GNOME Keyring")
+#define GNOMEKEYRING_DESCRIPTION N_("This plugin will store passwords in " \
+	"GNOME Keyring.")
+#define GNOMEKEYRING_AUTHOR      "Tomek Wasilczyk (tomkiewicz@cpw.pidgin.im)"
+#define GNOMEKEYRING_ID          "keyring-gnomekeyring"
+
+static PurpleKeyring *keyring_handler = NULL;
+static GList *request_queue = NULL;
+static gpointer current_request = NULL;
+
+typedef struct
+{
+	enum
+	{
+		GNOMEKEYRING_REQUEST_READ,
+		GNOMEKEYRING_REQUEST_SAVE
+	} type;
+	PurpleAccount *account;
+	gchar *password;
+	union
+	{
+		PurpleKeyringReadCallback read;
+		PurpleKeyringSaveCallback save;
+	} cb;
+	gpointer cb_data;
+	gboolean handled;
+} gnomekeyring_request;
+
+static void gnomekeyring_cancel_queue(void);
+static void gnomekeyring_process_queue(void);
+
+static void gnomekeyring_request_free(gnomekeyring_request *req)
+{
+	if (req->password != NULL) {
+		memset(req->password, 0, strlen(req->password));
+		gnome_keyring_memory_free(req->password);
+	}
+	g_free(req);
+}
+
+static void
+gnomekeyring_enqueue(gnomekeyring_request *req)
+{
+	request_queue = g_list_append(request_queue, req);
+	gnomekeyring_process_queue();
+}
+
+static void
+gnomekeyring_read_cb(GnomeKeyringResult result, const char *password,
+	gpointer _req)
+{
+	gnomekeyring_request *req = _req;
+	PurpleAccount *account;
+	GError *error = NULL;
+
+	g_return_if_fail(req != NULL);
+
+	current_request = NULL;
+	account = req->account;
+
+	if (result == GNOME_KEYRING_RESULT_OK) {
+		error = NULL;
+	} else if (result == GNOME_KEYRING_RESULT_NO_MATCH) {
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_NOPASSWORD,
+			"No password found for account");
+	} else if (result == GNOME_KEYRING_RESULT_DENIED ||
+		result == GNOME_KEYRING_RESULT_CANCELLED) {
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_ACCESSDENIED,
+			"Access denied");
+		gnomekeyring_cancel_queue();
+	} else if (result == GNOME_KEYRING_RESULT_NO_KEYRING_DAEMON ||
+		GNOME_KEYRING_RESULT_IO_ERROR) {
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_BACKENDFAIL,
+			"Communication with GNOME Keyring failed");
+	} else {
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_BACKENDFAIL,
+			"Unknown error (code: %d)", result);
+	}
+
+	if (error == NULL && password == NULL) {
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_BACKENDFAIL,
+			"Unknown error (password empty)");
+	}
+
+	if (error == NULL) {
+		purple_debug_misc("keyring-gnome",
+			"Got password for account %s (%s).\n",
+			purple_account_get_username(account),
+			purple_account_get_protocol_id(account));
+	} else if (result == GNOME_KEYRING_RESULT_NO_MATCH) {
+		if (purple_debug_is_verbose()) {
+			purple_debug_info("keyring-gnome",
+				"Password for account %s (%s) isn't stored.\n",
+				purple_account_get_username(account),
+				purple_account_get_protocol_id(account));
+		}
+	} else {
+		password = NULL;
+		purple_debug_warning("keyring-gnome", "Failed to read "
+			"password for account %s (%s), code: %d.\n",
+			purple_account_get_username(account),
+			purple_account_get_protocol_id(account),
+			result);
+	}
+
+	if (req->cb.read != NULL)
+		req->cb.read(account, password, error, req->cb_data);
+	req->handled = TRUE;
+
+	if (error)
+		g_error_free(error);
+
+	gnomekeyring_process_queue();
+}
+
+static void
+gnomekeyring_save_cb(GnomeKeyringResult result, gpointer _req)
+{
+	gnomekeyring_request *req = _req;
+	PurpleAccount *account;
+	GError *error = NULL;
+	gboolean already_removed = FALSE;
+
+	g_return_if_fail(req != NULL);
+
+	current_request = NULL;
+	account = req->account;
+
+	if (result == GNOME_KEYRING_RESULT_OK) {
+		error = NULL;
+	} else if (result == GNOME_KEYRING_RESULT_NO_MATCH &&
+		req->password == NULL) {
+		error = NULL;
+		already_removed = TRUE;
+	} else if (result == GNOME_KEYRING_RESULT_DENIED ||
+		result == GNOME_KEYRING_RESULT_CANCELLED) {
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_ACCESSDENIED,
+			"Access denied");
+		gnomekeyring_cancel_queue();
+	} else if (result == GNOME_KEYRING_RESULT_NO_KEYRING_DAEMON ||
+		GNOME_KEYRING_RESULT_IO_ERROR) {
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_BACKENDFAIL,
+			"Communication with GNOME Keyring failed");
+	} else {
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_BACKENDFAIL,
+			"Unknown error (code: %d)", result);
+	}
+
+	if (already_removed) {
+		/* no operation */
+	} else if (error == NULL) {
+		purple_debug_misc("keyring-gnome",
+			"Password %s for account %s (%s).\n",
+			req->password ? "saved" : "removed",
+			purple_account_get_username(account),
+			purple_account_get_protocol_id(account));
+	} else {
+		purple_debug_warning("keyring-gnome", "Failed updating "
+			"password for account %s (%s), code: %d.\n",
+			purple_account_get_username(account),
+			purple_account_get_protocol_id(account),
+			result);
+	}
+
+	if (req->cb.save != NULL)
+		req->cb.save(account, error, req->cb_data);
+	req->handled = TRUE;
+
+	if (error)
+		g_error_free(error);
+
+	gnomekeyring_process_queue();
+}
+
+static void
+gnomekeyring_request_cancel(gpointer _req)
+{
+	gnomekeyring_request *req = _req;
+	PurpleAccount *account;
+	GError *error;
+
+	g_return_if_fail(req != NULL);
+
+	if (req->handled) {
+		gnomekeyring_request_free(req);
+		return;
+	}
+
+	purple_debug_warning("keyring-gnome",
+		"operation cancelled (%d %s:%s)\n", req->type,
+		purple_account_get_protocol_id(req->account),
+		purple_account_get_username(req->account));
+
+	account = req->account;
+	error = g_error_new(PURPLE_KEYRING_ERROR,
+		PURPLE_KEYRING_ERROR_CANCELLED,
+		"Operation cancelled");
+	if (req->type == GNOMEKEYRING_REQUEST_READ && req->cb.read)
+		req->cb.read(account, NULL, error, req->cb_data);
+	if (req->type == GNOMEKEYRING_REQUEST_SAVE && req->cb.save)
+		req->cb.save(account, error, req->cb_data);
+	g_error_free(error);
+
+	gnomekeyring_request_free(req);
+	gnomekeyring_process_queue();
+}
+
+static void
+gnomekeyring_cancel_queue(void)
+{
+	GList *cancel_list = request_queue;
+
+	if (request_queue == NULL)
+		return;
+
+	purple_debug_info("gnome-keyring", "cancelling all pending requests\n");
+	request_queue = NULL;
+
+	g_list_free_full(cancel_list, gnomekeyring_request_cancel);
+}
+
+static void
+gnomekeyring_process_queue(void)
+{
+	gnomekeyring_request *req;
+	PurpleAccount *account;
+	GList *first;
+
+	if (request_queue == NULL)
+		return;
+
+	if (current_request) {
+		if (purple_debug_is_verbose())
+			purple_debug_misc("keyring-gnome", "busy...\n");
+		return;
+	}
+
+	first = g_list_first(request_queue);
+	req = first->data;
+	request_queue = g_list_delete_link(request_queue, first);
+	account = req->account;
+
+	if (purple_debug_is_verbose()) {
+		purple_debug_misc("keyring-gnome",
+			"%s password for account %s (%s)\n",
+			req->type == GNOMEKEYRING_REQUEST_READ ? "reading" :
+			(req->password == NULL ? "removing" : "updating"),
+			purple_account_get_username(account),
+			purple_account_get_protocol_id(account));
+	}
+
+	if (req->type == GNOMEKEYRING_REQUEST_READ) {
+		current_request = gnome_keyring_find_password(
+			GNOME_KEYRING_NETWORK_PASSWORD, gnomekeyring_read_cb,
+			req, gnomekeyring_request_cancel,
+			"user", purple_account_get_username(account),
+			"protocol", purple_account_get_protocol_id(account),
+			NULL);
+	} else if (req->type == GNOMEKEYRING_REQUEST_SAVE &&
+		req->password != NULL) {
+		gchar *display_name = g_strdup_printf(
+			_("Pidgin IM password for account %s"),
+			purple_account_get_username(account));
+		current_request = gnome_keyring_store_password(
+			GNOME_KEYRING_NETWORK_PASSWORD, GNOME_KEYRING_DEFAULT,
+			display_name, req->password, gnomekeyring_save_cb, req,
+			gnomekeyring_request_cancel,
+			"user", purple_account_get_username(account),
+			"protocol", purple_account_get_protocol_id(account),
+			NULL);
+		g_free(display_name);
+	} else if (req->type == GNOMEKEYRING_REQUEST_SAVE &&
+		req->password == NULL) {
+		current_request = gnome_keyring_delete_password(
+			GNOME_KEYRING_NETWORK_PASSWORD, gnomekeyring_save_cb,
+			req, gnomekeyring_request_cancel,
+			"user", purple_account_get_username(account),
+			"protocol", purple_account_get_protocol_id(account),
+			NULL);
+	} else {
+		g_return_if_reached();
+	}
+}
+
+static void
+gnomekeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
+	gpointer data)
+{
+	gnomekeyring_request *req;
+
+	g_return_if_fail(account != NULL);
+
+	req = g_new0(gnomekeyring_request, 1);
+	req->type = GNOMEKEYRING_REQUEST_READ;
+	req->account = account;
+	req->cb.read = cb;
+	req->cb_data = data;
+
+	gnomekeyring_enqueue(req);
+}
+
+static void
+gnomekeyring_save(PurpleAccount *account, const gchar *password,
+	PurpleKeyringSaveCallback cb, gpointer data)
+{
+	gnomekeyring_request *req;
+
+	g_return_if_fail(account != NULL);
+
+	req = g_new0(gnomekeyring_request, 1);
+	req->type = GNOMEKEYRING_REQUEST_SAVE;
+	req->account = account;
+	req->password = gnome_keyring_memory_strdup(password);
+	req->cb.save = cb;
+	req->cb_data = data;
+
+	gnomekeyring_enqueue(req);
+}
+
+static void
+gnomekeyring_cancel(void)
+{
+	gnomekeyring_cancel_queue();
+	if (current_request) {
+		gnome_keyring_cancel_request(current_request);
+		while (g_main_iteration(FALSE));
+	}
+}
+
+static void
+gnomekeyring_close(void)
+{
+	gnomekeyring_cancel();
+}
+
+static gboolean
+gnomekeyring_load(PurplePlugin *plugin)
+{
+	if (!gnome_keyring_is_available()) {
+		purple_debug_info("keyring-gnome", "GNOME Keyring service is "
+			"disabled\n");
+		return FALSE;
+	}
+
+	keyring_handler = purple_keyring_new();
+
+	purple_keyring_set_name(keyring_handler, GNOMEKEYRING_NAME);
+	purple_keyring_set_id(keyring_handler, GNOMEKEYRING_ID);
+	purple_keyring_set_read_password(keyring_handler, gnomekeyring_read);
+	purple_keyring_set_save_password(keyring_handler, gnomekeyring_save);
+	purple_keyring_set_cancel_requests(keyring_handler,
+		gnomekeyring_cancel);
+	purple_keyring_set_close_keyring(keyring_handler, gnomekeyring_close);
+
+	purple_keyring_register(keyring_handler);
+
+	return TRUE;
+}
+
+static gboolean
+gnomekeyring_unload(PurplePlugin *plugin)
+{
+	if (purple_keyring_get_inuse() == keyring_handler) {
+		purple_debug_warning("keyring-gnome",
+			"keyring in use, cannot unload\n");
+		return FALSE;
+	}
+
+	gnomekeyring_close();
+
+	purple_keyring_unregister(keyring_handler);
+	purple_keyring_free(keyring_handler);
+	keyring_handler = NULL;
+
+	return TRUE;
+}
+
+PurplePluginInfo plugininfo =
+{
+	PURPLE_PLUGIN_MAGIC,		/* magic */
+	PURPLE_MAJOR_VERSION,		/* major_version */
+	PURPLE_MINOR_VERSION,		/* minor_version */
+	PURPLE_PLUGIN_STANDARD,		/* type */
+	NULL,				/* ui_requirement */
+	PURPLE_PLUGIN_FLAG_INVISIBLE,	/* flags */
+	NULL,				/* dependencies */
+	PURPLE_PRIORITY_DEFAULT,	/* priority */
+	GNOMEKEYRING_ID,		/* id */
+	GNOMEKEYRING_NAME,		/* name */
+	DISPLAY_VERSION,		/* version */
+	"GNOME Keyring Plugin",		/* summary */
+	GNOMEKEYRING_DESCRIPTION,	/* description */
+	GNOMEKEYRING_AUTHOR,		/* author */
+	PURPLE_WEBSITE,			/* homepage */
+	gnomekeyring_load,		/* load */
+	gnomekeyring_unload,		/* unload */
+	NULL,				/* destroy */
+	NULL,				/* ui_info */
+	NULL,				/* extra_info */
+	NULL,				/* prefs_info */
+	NULL,				/* actions */
+	NULL, NULL, NULL, NULL		/* padding */
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+}
+
+PURPLE_INIT_PLUGIN(gnome_keyring, init_plugin, plugininfo)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/plugins/keyrings/internalkeyring.c	Wed May 01 11:52:20 2013 +0200
@@ -0,0 +1,261 @@
+/**
+ * @file internalkeyring.c internal keyring
+ * @ingroup plugins
+ */
+
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include "internal.h"
+#include "account.h"
+#include "debug.h"
+#include "keyring.h"
+#include "plugin.h"
+#include "version.h"
+
+#define INTERNALKEYRING_NAME        N_("Internal keyring")
+#define INTERNALKEYRING_DESCRIPTION N_("This plugin provides the default" \
+	" password storage behaviour for libpurple. Password will be stored" \
+	" unencrypted.")
+#define INTERNALKEYRING_AUTHOR      "Scrouaf (scrouaf[at]soc.pidgin.im)"
+#define INTERNALKEYRING_ID          PURPLE_DEFAULT_KEYRING
+
+static gboolean internal_keyring_opened = FALSE;
+static GHashTable *internal_keyring_passwords = NULL;
+static PurpleKeyring *keyring_handler = NULL;
+
+/***********************************************/
+/*     Keyring interface                       */
+/***********************************************/
+
+static void
+internal_keyring_open(void)
+{
+	if (internal_keyring_opened)
+		return;
+	internal_keyring_opened = TRUE;
+
+	internal_keyring_passwords = g_hash_table_new_full(g_direct_hash,
+		g_direct_equal, NULL, (GDestroyNotify)purple_str_wipe);
+}
+
+static void
+internal_keyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
+	gpointer data)
+{
+	const char *password;
+	GError *error;
+
+	internal_keyring_open();
+
+	password = g_hash_table_lookup(internal_keyring_passwords, account);
+
+	if (password != NULL) {
+		purple_debug_misc("keyring-internal",
+			"Got password for account %s (%s).\n",
+			purple_account_get_username(account),
+			purple_account_get_protocol_id(account));
+		if (cb != NULL)
+			cb(account, password, NULL, data);
+	} else {
+		if (purple_debug_is_verbose()) {
+			purple_debug_misc("keyring-internal",
+				"No password for account %s (%s).\n",
+				purple_account_get_username(account),
+				purple_account_get_protocol_id(account));
+		}
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_NOPASSWORD, "Password not found.");
+		if (cb != NULL)
+			cb(account, NULL, error, data);
+		g_error_free(error);
+	}
+}
+
+static void
+internal_keyring_save(PurpleAccount *account, const gchar *password,
+	PurpleKeyringSaveCallback cb, gpointer data)
+{
+	void *old_password;
+	internal_keyring_open();
+
+	old_password = g_hash_table_lookup(internal_keyring_passwords, account);
+
+	if (password == NULL)
+		g_hash_table_remove(internal_keyring_passwords, account);
+	else {
+		g_hash_table_replace(internal_keyring_passwords, account,
+			g_strdup(password));
+	}
+
+	if (!(password == NULL && old_password == NULL)) {
+		purple_debug_misc("keyring-internal",
+			"Password %s for account %s (%s).\n",
+			(password == NULL ? "removed" : (old_password == NULL ?
+				"saved" : "updated")),
+			purple_account_get_username(account),
+			purple_account_get_protocol_id(account));
+	} else if (purple_debug_is_verbose()) {
+		purple_debug_misc("keyring-internal",
+			"Password for account %s (%s) was already removed.\n",
+			purple_account_get_username(account),
+			purple_account_get_protocol_id(account));
+	}
+
+	if (cb != NULL)
+		cb(account, NULL, data);
+}
+
+static void
+internal_keyring_close(void)
+{
+	if (!internal_keyring_opened)
+		return;
+	internal_keyring_opened = FALSE;
+
+	g_hash_table_destroy(internal_keyring_passwords);
+	internal_keyring_passwords = NULL;
+}
+
+static gboolean
+internal_keyring_import_password(PurpleAccount *account, const char *mode,
+	const char *data, GError **error)
+{
+	g_return_val_if_fail(account != NULL, FALSE);
+	g_return_val_if_fail(data != NULL, FALSE);
+
+	internal_keyring_open();
+
+	if (mode == NULL)
+		mode = "cleartext";
+
+	if (g_strcmp0(mode, "cleartext") == 0) {
+		g_hash_table_replace(internal_keyring_passwords, account,
+			g_strdup(data));
+		return TRUE;
+	} else {
+		if (error != NULL) {
+			*error = g_error_new(PURPLE_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_BACKENDFAIL,
+				"Invalid password storage mode");
+		}
+		return FALSE;
+	}
+}
+
+static gboolean
+internal_keyring_export_password(PurpleAccount *account, const char **mode,
+	char **data, GError **error, GDestroyNotify *destroy)
+{
+	gchar *password;
+
+	internal_keyring_open();
+
+	password = g_hash_table_lookup(internal_keyring_passwords, account);
+
+	if (password == NULL) {
+		return FALSE;
+	} else {
+		*mode = "cleartext";
+		*data = g_strdup(password);
+		*destroy = (GDestroyNotify)purple_str_wipe;
+		return TRUE;
+	}
+}
+
+/***********************************************/
+/*     Plugin interface                        */
+/***********************************************/
+
+static gboolean
+internal_keyring_load(PurplePlugin *plugin)
+{
+	keyring_handler = purple_keyring_new();
+
+	purple_keyring_set_name(keyring_handler, INTERNALKEYRING_NAME);
+	purple_keyring_set_id(keyring_handler, INTERNALKEYRING_ID);
+	purple_keyring_set_read_password(keyring_handler,
+		internal_keyring_read);
+	purple_keyring_set_save_password(keyring_handler,
+		internal_keyring_save);
+	purple_keyring_set_close_keyring(keyring_handler,
+		internal_keyring_close);
+	purple_keyring_set_import_password(keyring_handler,
+		internal_keyring_import_password);
+	purple_keyring_set_export_password(keyring_handler,
+		internal_keyring_export_password);
+
+	purple_keyring_register(keyring_handler);
+
+	return TRUE;
+}
+
+static gboolean
+internal_keyring_unload(PurplePlugin *plugin)
+{
+	if (purple_keyring_get_inuse() == keyring_handler) {
+		purple_debug_warning("keyring-internal",
+			"keyring in use, cannot unload\n");
+		return FALSE;
+	}
+
+	internal_keyring_close();
+
+	purple_keyring_unregister(keyring_handler);
+	purple_keyring_free(keyring_handler);
+	keyring_handler = NULL;
+
+	return TRUE;
+}
+
+PurplePluginInfo plugininfo =
+{
+	PURPLE_PLUGIN_MAGIC,		/* magic */
+	PURPLE_MAJOR_VERSION,		/* major_version */
+	PURPLE_MINOR_VERSION,		/* minor_version */
+	PURPLE_PLUGIN_STANDARD,		/* type */
+	NULL,				/* ui_requirement */
+	PURPLE_PLUGIN_FLAG_INVISIBLE,	/* flags */
+	NULL,				/* dependencies */
+	PURPLE_PRIORITY_DEFAULT,	/* priority */
+	INTERNALKEYRING_ID,		/* id */
+	INTERNALKEYRING_NAME,		/* name */
+	DISPLAY_VERSION,		/* version */
+	"Internal Keyring Plugin",	/* summary */
+	INTERNALKEYRING_DESCRIPTION,	/* description */
+	INTERNALKEYRING_AUTHOR,		/* author */
+	PURPLE_WEBSITE,			/* homepage */
+	internal_keyring_load,		/* load */
+	internal_keyring_unload,	/* unload */
+	NULL,				/* destroy */
+	NULL,				/* ui_info */
+	NULL,				/* extra_info */
+	NULL,				/* prefs_info */
+	NULL,				/* actions */
+	NULL, NULL, NULL, NULL		/* padding */
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+}
+
+PURPLE_INIT_PLUGIN(internal_keyring, init_plugin, plugininfo)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/plugins/keyrings/kwallet.cpp	Wed May 01 11:52:20 2013 +0200
@@ -0,0 +1,582 @@
+/**
+ * @file kwallet.cpp KWallet password storage
+ * @ingroup plugins
+ */
+
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include "internal.h"
+#include "account.h"
+#include "core.h"
+#include "debug.h"
+#include "keyring.h"
+#include "plugin.h"
+#include "version.h"
+
+#include <QQueue>
+#include <QCoreApplication>
+#include <kwallet.h>
+
+#define KWALLET_NAME        N_("KWallet")
+#define KWALLET_DESCRIPTION N_("This plugin will store passwords in KWallet.")
+#define KWALLET_AUTHOR      "QuLogic (qulogic[at]pidgin.im)"
+#define KWALLET_ID          "keyring-kwallet"
+
+#define KWALLET_WALLET_NAME KWallet::Wallet::NetworkWallet()
+#define KWALLET_APP_NAME "Libpurple"
+#define KWALLET_FOLDER_NAME "libpurple"
+
+PurpleKeyring *keyring_handler = NULL;
+QCoreApplication *qCoreApp = NULL;
+
+namespace KWalletPlugin {
+
+class request
+{
+	public:
+		virtual ~request();
+		virtual void detailedAbort(enum PurpleKeyringError error) = 0;
+		void abort();
+		virtual void execute(KWallet::Wallet *wallet) = 0;
+
+	protected:
+		gpointer data;
+		PurpleAccount *account;
+		QString password;
+		bool noPassword;
+};
+
+class engine : private QObject, private QQueue<request*>
+{
+	Q_OBJECT
+
+	public:
+		engine();
+		~engine();
+		void queue(request *req);
+		void abortAll();
+		static engine *instance(bool create);
+		static void closeInstance(void);
+
+	private slots:
+		void walletOpened(bool opened);
+		void walletClosed();
+
+	private:
+		static engine *pinstance;
+
+		bool connected;
+		bool failed;
+		bool closing;
+		bool externallyClosed;
+		bool busy;
+		bool closeAfterBusy;
+
+		KWallet::Wallet *wallet;
+
+		void reopenWallet();
+		void executeRequests();
+};
+
+class save_request : public request
+{
+	public:
+		save_request(PurpleAccount *account, const char *password,
+			PurpleKeyringSaveCallback cb, void *data);
+		void detailedAbort(enum PurpleKeyringError error);
+		void execute(KWallet::Wallet *wallet);
+
+	private:
+		PurpleKeyringSaveCallback callback;
+};
+
+class read_request : public request
+{
+	public:
+		read_request(PurpleAccount *account,
+			PurpleKeyringReadCallback cb, void *data);
+		void detailedAbort(enum PurpleKeyringError error);
+		void execute(KWallet::Wallet *wallet);
+
+	private:
+		PurpleKeyringReadCallback callback;
+};
+
+}
+
+static gboolean
+kwallet_is_enabled(void)
+{
+	return KWallet::Wallet::isEnabled() ? TRUE : FALSE;
+}
+
+KWalletPlugin::engine *KWalletPlugin::engine::pinstance = NULL;
+
+KWalletPlugin::request::~request()
+{
+}
+
+void
+KWalletPlugin::request::abort()
+{
+	detailedAbort(PURPLE_KEYRING_ERROR_CANCELLED);
+}
+
+KWalletPlugin::engine::engine()
+{
+	connected = false;
+	failed = false;
+	closing = false;
+	externallyClosed = false;
+	wallet = NULL;
+	busy = false;
+	closeAfterBusy = false;
+
+	reopenWallet();
+}
+
+void
+KWalletPlugin::engine::reopenWallet()
+{
+	if (closing) {
+		purple_debug_error("keyring-kwallet",
+			"wallet is closing right now\n");
+		failed = true;
+		return;
+	}
+
+	connected = false;
+	failed = false;
+	externallyClosed = false;
+
+	wallet = KWallet::Wallet::openWallet(KWALLET_WALLET_NAME, 0,
+		KWallet::Wallet::Asynchronous);
+	if (wallet == NULL) {
+		failed = true;
+		purple_debug_error("keyring-kwallet",
+			"failed opening a wallet\n");
+		return;
+	}
+
+	failed |= !connect(wallet, SIGNAL(walletClosed()),
+		SLOT(walletClosed()));
+	failed |= !connect(wallet, SIGNAL(walletOpened(bool)),
+		SLOT(walletOpened(bool)));
+	if (failed) {
+		purple_debug_error("keyring-kwallet",
+			"failed connecting to wallet signal\n");
+	}
+}
+
+KWalletPlugin::engine::~engine()
+{
+	closing = true;
+
+	abortAll();
+
+	delete wallet;
+
+	if (pinstance == this)
+		pinstance = NULL;
+}
+
+void
+KWalletPlugin::engine::abortAll()
+{
+	int abortedCount = 0;
+
+	while (!isEmpty()) {
+		request *req = dequeue();
+		req->abort();
+		delete req;
+		abortedCount++;
+	}
+
+	if (abortedCount > 0) {
+		purple_debug_info("keyring-kwallet", "aborted requests: %d\n",
+			abortedCount);
+	}
+}
+
+KWalletPlugin::engine *
+KWalletPlugin::engine::instance(bool create)
+{
+	if (pinstance == NULL && create)
+		pinstance = new engine;
+	return pinstance;
+}
+
+void
+KWalletPlugin::engine::closeInstance(void)
+{
+	if (pinstance == NULL)
+		return;
+	if (pinstance->closing)
+		return;
+	if (pinstance->busy) {
+		purple_debug_misc("keyring-kwallet",
+			"current instance is busy, will be freed later\n");
+		pinstance->closeAfterBusy = true;
+	} else
+		delete pinstance;
+	pinstance = NULL;
+}
+
+void
+KWalletPlugin::engine::walletOpened(bool opened)
+{
+	connected = opened;
+
+	if (!opened) {
+		purple_debug_warning("keyring-kwallet",
+			"failed to open a wallet\n");
+		delete this;
+		return;
+	}
+
+	if (!wallet->hasFolder(KWALLET_FOLDER_NAME)) {
+		if (!wallet->createFolder(KWALLET_FOLDER_NAME)) {
+			purple_debug_error("keyring-kwallet",
+				"couldn't create \"" KWALLET_FOLDER_NAME
+				"\" folder in wallet\n");
+			failed = true;
+		}
+	}
+	if (!failed)
+		wallet->setFolder(KWALLET_FOLDER_NAME);
+
+	executeRequests();
+}
+
+void
+KWalletPlugin::engine::walletClosed()
+{
+	if (!closing) {
+		purple_debug_info("keyring-kwallet",
+			"wallet was externally closed\n");
+		externallyClosed = true;
+		delete wallet;
+		wallet = NULL;
+	}
+}
+
+void
+KWalletPlugin::engine::queue(request *req)
+{
+	enqueue(req);
+	executeRequests();
+}
+
+void
+KWalletPlugin::engine::executeRequests()
+{
+	if (closing || busy)
+		return;
+	busy = true;
+	if (externallyClosed) {
+		reopenWallet();
+	} else if (connected || failed) {
+		while (!isEmpty()) {
+			request *req = dequeue();
+			if (connected)
+				req->execute(wallet);
+			else
+				req->abort();
+			delete req;
+		}
+	} else if (purple_debug_is_verbose()) {
+		purple_debug_misc("keyring-kwallet", "not yet connected\n");
+	}
+	busy = false;
+	if (closeAfterBusy) {
+		purple_debug_misc("keyring-kwallet",
+			"instance freed after being busy\n");
+		delete this;
+	}
+}
+
+KWalletPlugin::save_request::save_request(PurpleAccount *acc, const char *pw,
+	PurpleKeyringSaveCallback cb, void *userdata)
+{
+	account = acc;
+	data = userdata;
+	callback = cb;
+	password = QString(pw);
+	noPassword = (pw == NULL);
+}
+
+KWalletPlugin::read_request::read_request(PurpleAccount *acc,
+	PurpleKeyringReadCallback cb, void *userdata)
+{
+	account = acc;
+	data = userdata;
+	callback = cb;
+	password = QString();
+}
+
+void
+KWalletPlugin::save_request::detailedAbort(enum PurpleKeyringError error)
+{
+	GError *gerror;
+	if (callback == NULL)
+		return;
+
+	gerror = g_error_new(PURPLE_KEYRING_ERROR, error,
+		"Failed to save password");
+	callback(account, gerror, data);
+	g_error_free(gerror);
+}
+
+void
+KWalletPlugin::read_request::detailedAbort(enum PurpleKeyringError error)
+{
+	GError *gerror;
+	if (callback == NULL)
+		return;
+
+	gerror = g_error_new(PURPLE_KEYRING_ERROR, error,
+		"Failed to read password");
+	callback(account, NULL, gerror, data);
+	g_error_free(gerror);
+}
+
+static QString
+kwallet_account_key(PurpleAccount *account)
+{
+	return QString(purple_account_get_protocol_id(account)) + ":" +
+		purple_account_get_username(account);
+}
+
+void
+KWalletPlugin::read_request::execute(KWallet::Wallet *wallet)
+{
+	int result;
+
+	g_return_if_fail(wallet != NULL);
+
+	result = wallet->readPassword(kwallet_account_key(account), password);
+
+	if (result != 0) {
+		purple_debug_warning("keyring-kwallet",
+			"failed to read password, result was %d\n", result);
+		abort();
+		return;
+	}
+
+	purple_debug_misc("keyring-kwallet",
+		"Got password for account %s (%s).\n",
+		purple_account_get_username(account),
+		purple_account_get_protocol_id(account));
+
+	if (callback != NULL)
+		callback(account, password.toUtf8().constData(), NULL, data);
+}
+
+void
+KWalletPlugin::save_request::execute(KWallet::Wallet *wallet)
+{
+	int result;
+
+	g_return_if_fail(wallet != NULL);
+
+	if (noPassword)
+		result = wallet->removeEntry(kwallet_account_key(account));
+	else {
+		result = wallet->writePassword(kwallet_account_key(account),
+			password);
+	}
+
+	if (result != 0) {
+		purple_debug_warning("keyring-kwallet",
+			"failed to write password, result was %d\n", result);
+		abort();
+		return;
+	}
+
+	purple_debug_misc("keyring-kwallet",
+		"Password %s for account %s (%s).\n",
+		(noPassword ? "removed" : "saved"),
+		purple_account_get_username(account),
+		purple_account_get_protocol_id(account));
+
+	if (callback != NULL)
+		callback(account, NULL, data);
+}
+
+extern "C"
+{
+
+static void
+kwallet_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
+	gpointer data)
+{
+	KWalletPlugin::read_request *req =
+		new KWalletPlugin::read_request(account, cb, data);
+
+	if (KWallet::Wallet::keyDoesNotExist(KWALLET_WALLET_NAME,
+		KWALLET_FOLDER_NAME, kwallet_account_key(account))) {
+		req->detailedAbort(PURPLE_KEYRING_ERROR_NOPASSWORD);
+		delete req;
+	}
+	else
+		KWalletPlugin::engine::instance(true)->queue(req);
+}
+
+static void
+kwallet_save(PurpleAccount *account, const char *password,
+	PurpleKeyringSaveCallback cb, gpointer data)
+{
+	if (password == NULL && KWallet::Wallet::keyDoesNotExist(
+		KWALLET_WALLET_NAME, KWALLET_FOLDER_NAME,
+		kwallet_account_key(account))) {
+		if (cb != NULL)
+			cb(account, NULL, data);
+	}
+	else
+		KWalletPlugin::engine::instance(true)->queue(
+			new KWalletPlugin::save_request(account, password, cb,
+			data));
+}
+
+static void
+kwallet_cancel(void)
+{
+	KWalletPlugin::engine *instance =
+		KWalletPlugin::engine::instance(false);
+	if (instance)
+		instance->abortAll();
+}
+
+static void *
+kwallet_get_handle(void)
+{
+	static int handle;
+
+	return &handle;
+}
+
+static const char *kwallet_get_ui_name(void)
+{
+	GHashTable *ui_info;
+	const char *ui_name = NULL;
+
+	ui_info = purple_core_get_ui_info();
+	if (ui_info != NULL)
+		ui_name = (const char*)g_hash_table_lookup(ui_info, "name");
+	if (ui_name == NULL)
+		ui_name = KWALLET_APP_NAME;
+
+	return ui_name;
+}
+
+static gboolean
+kwallet_load(PurplePlugin *plugin)
+{
+	if (!qCoreApp) {
+		int argc = 0;
+		qCoreApp = new QCoreApplication(argc, NULL);
+		qCoreApp->setApplicationName(kwallet_get_ui_name());
+	}
+
+	if (!kwallet_is_enabled()) {
+		purple_debug_info("keyring-kwallet",
+			"KWallet service is disabled\n");
+		return FALSE;
+	}
+
+	keyring_handler = purple_keyring_new();
+
+	purple_keyring_set_name(keyring_handler, KWALLET_NAME);
+	purple_keyring_set_id(keyring_handler, KWALLET_ID);
+	purple_keyring_set_read_password(keyring_handler, kwallet_read);
+	purple_keyring_set_save_password(keyring_handler, kwallet_save);
+	purple_keyring_set_cancel_requests(keyring_handler, kwallet_cancel);
+	purple_keyring_set_close_keyring(keyring_handler,
+		KWalletPlugin::engine::closeInstance);
+
+	purple_keyring_register(keyring_handler);
+
+	return TRUE;
+}
+
+static gboolean
+kwallet_unload(PurplePlugin *plugin)
+{
+	if (purple_keyring_get_inuse() == keyring_handler) {
+		purple_debug_warning("keyring-kwallet",
+			"keyring in use, cannot unload\n");
+		return FALSE;
+	}
+
+	purple_signals_disconnect_by_handle(kwallet_get_handle());
+
+	KWalletPlugin::engine::closeInstance();
+
+	purple_keyring_unregister(keyring_handler);
+	purple_keyring_free(keyring_handler);
+	keyring_handler = NULL;
+
+	if (qCoreApp) {
+		delete qCoreApp;
+		qCoreApp = NULL;
+	}
+
+	return TRUE;
+}
+
+PurplePluginInfo plugininfo =
+{
+	PURPLE_PLUGIN_MAGIC,			/* magic */
+	PURPLE_MAJOR_VERSION,			/* major_version */
+	PURPLE_MINOR_VERSION,			/* minor_version */
+	PURPLE_PLUGIN_STANDARD,			/* type */
+	NULL,					/* ui_requirement */
+	PURPLE_PLUGIN_FLAG_INVISIBLE,		/* flags */
+	NULL,					/* dependencies */
+	PURPLE_PRIORITY_DEFAULT,		/* priority */
+	(char*)KWALLET_ID,			/* id */
+	(char*)KWALLET_NAME,			/* name */
+	(char*)DISPLAY_VERSION,			/* version */
+	(char*)"KWallet Keyring Plugin",	/* summary */
+	(char*)KWALLET_DESCRIPTION,		/* description */
+	(char*)KWALLET_AUTHOR,			/* author */
+	(char*)PURPLE_WEBSITE,			/* homepage */
+	kwallet_load,				/* load */
+	kwallet_unload,				/* unload */
+	NULL,					/* destroy */
+	NULL,					/* ui_info */
+	NULL,					/* extra_info */
+	NULL,					/* prefs_info */
+	NULL,					/* actions */
+	NULL, NULL, NULL, NULL			/* padding */
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+}
+
+PURPLE_INIT_PLUGIN(kwallet_keyring, init_plugin, plugininfo)
+
+} /* extern "C" */
+
+#include "kwallet.moc"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/plugins/keyrings/secretservice.c	Wed May 01 11:52:20 2013 +0200
@@ -0,0 +1,325 @@
+/* purple
+ * @file secretservice.c Secret Service password storage
+ * @ingroup plugins
+ * @todo rewrite it with Complete API
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "internal.h"
+#include "account.h"
+#include "debug.h"
+#include "keyring.h"
+#include "plugin.h"
+#include "version.h"
+
+#include <libsecret/secret.h>
+
+#define SECRETSERVICE_NAME        N_("Secret Service")
+#define SECRETSERVICE_ID          "keyring-libsecret"
+
+static PurpleKeyring *keyring_handler = NULL;
+
+static const SecretSchema purple_schema = {
+	"im.pidgin.Purple", SECRET_SCHEMA_NONE,
+	{
+		{"user", SECRET_SCHEMA_ATTRIBUTE_STRING},
+		{"protocol", SECRET_SCHEMA_ATTRIBUTE_STRING},
+		{"NULL", 0}
+	},
+	/* Reserved fields */
+	0, 0, 0, 0, 0, 0, 0, 0
+};
+
+typedef struct _InfoStorage InfoStorage;
+
+struct _InfoStorage
+{
+	PurpleAccount *account;
+	gpointer cb;
+	gpointer user_data;
+};
+
+/***********************************************/
+/*     Keyring interface                       */
+/***********************************************/
+static void
+ss_read_continue(GObject *object, GAsyncResult *result, gpointer data)
+{
+	InfoStorage *storage = data;
+	PurpleAccount *account = storage->account;
+	PurpleKeyringReadCallback cb = storage->cb;
+	char *password;
+	GError *error = NULL;
+
+	password = secret_password_lookup_finish(result, &error);
+
+	if (error != NULL) {
+		int code = error->code;
+
+		switch (code) {
+			case G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND:
+			case G_DBUS_ERROR_IO_ERROR:
+				error = g_error_new(PURPLE_KEYRING_ERROR,
+				                    PURPLE_KEYRING_ERROR_BACKENDFAIL,
+				                    "Failed to communicate with Secret Service (account : %s).",
+				                    purple_account_get_username(account));
+				if (cb != NULL)
+					cb(account, NULL, error, storage->user_data);
+				g_error_free(error);
+				break;
+
+			default:
+				purple_debug_error("keyring-libsecret",
+				                  "Unknown error (account: %s (%s), domain: %s, code: %d): %s.\n",
+				                  purple_account_get_username(account),
+				                  purple_account_get_protocol_id(account),
+				                  g_quark_to_string(error->domain), code, error->message);
+				error = g_error_new(PURPLE_KEYRING_ERROR,
+				                    PURPLE_KEYRING_ERROR_BACKENDFAIL,
+				                    "Unknown error (account : %s).",
+				                    purple_account_get_username(account));
+				if (cb != NULL)
+					cb(account, NULL, error, storage->user_data);
+				g_error_free(error);
+				break;
+		}
+
+	} else if (password == NULL) {
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+		                    PURPLE_KEYRING_ERROR_NOPASSWORD,
+		                    "No password found for account: %s",
+		                    purple_account_get_username(account));
+		if (cb != NULL)
+			cb(account, NULL, error, storage->user_data);
+		g_error_free(error);
+
+	} else {
+		if (cb != NULL)
+			cb(account, password, NULL, storage->user_data);
+	}
+
+	g_free(storage);
+}
+
+static void
+ss_read(PurpleAccount *account, PurpleKeyringReadCallback cb, gpointer data)
+{
+	InfoStorage *storage = g_new0(InfoStorage, 1);
+
+	storage->account = account;
+	storage->cb = cb;
+	storage->user_data = data;
+
+	secret_password_lookup(&purple_schema,
+	                       NULL, ss_read_continue, storage,
+	                       "user", purple_account_get_username(account),
+	                       "protocol", purple_account_get_protocol_id(account),
+	                       NULL);
+}
+
+static void
+ss_save_continue(GObject *object, GAsyncResult *result, gpointer data)
+{
+	InfoStorage *storage = data;
+	PurpleKeyringSaveCallback cb;
+	GError *error = NULL;
+	PurpleAccount *account;
+
+	account = storage->account;
+	cb = storage->cb;
+
+	secret_password_store_finish(result, &error);
+
+	if (error != NULL) {
+		int code = error->code;
+
+		switch (code) {
+			case G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND:
+			case G_DBUS_ERROR_IO_ERROR:
+				purple_debug_info("keyring-libsecret",
+				                  "Failed to communicate with Secret Service (account : %s (%s)).\n",
+				                  purple_account_get_username(account),
+				                  purple_account_get_protocol_id(account));
+				error = g_error_new(PURPLE_KEYRING_ERROR,
+				                    PURPLE_KEYRING_ERROR_BACKENDFAIL,
+				                    "Failed to communicate with Secret Service (account : %s).",
+				                    purple_account_get_username(account));
+				if (cb != NULL)
+					cb(account, error, storage->user_data);
+				g_error_free(error);
+				break;
+
+			default:
+				purple_debug_error("keyring-libsecret",
+				                  "Unknown error (account: %s (%s), domain: %s, code: %d): %s.\n",
+				                  purple_account_get_username(account),
+				                  purple_account_get_protocol_id(account),
+				                  g_quark_to_string(error->domain), code, error->message);
+				error = g_error_new(PURPLE_KEYRING_ERROR,
+				                    PURPLE_KEYRING_ERROR_BACKENDFAIL,
+				                    "Unknown error (account : %s).",
+				                    purple_account_get_username(account));
+				if (cb != NULL)
+					cb(account, error, storage->user_data);
+				g_error_free(error);
+				break;
+		}
+
+	} else {
+		purple_debug_info("keyring-libsecret", "Password for %s updated.\n",
+			purple_account_get_username(account));
+
+		if (cb != NULL)
+			cb(account, NULL, storage->user_data);
+	}
+
+	g_free(storage);
+}
+
+static void
+ss_save(PurpleAccount *account,
+         const gchar *password,
+         PurpleKeyringSaveCallback cb,
+         gpointer data)
+{
+	InfoStorage *storage = g_new0(InfoStorage, 1);
+
+	storage->account = account;
+	storage->cb = cb;
+	storage->user_data = data;
+
+	if (password != NULL && *password != '\0') {
+		const char *username = purple_account_get_username(account);
+		char *label;
+
+		purple_debug_info("keyring-libsecret",
+			"Updating password for account %s (%s).\n",
+			username, purple_account_get_protocol_id(account));
+
+		label = g_strdup_printf(_("Pidgin IM password for account %s"), username);
+		secret_password_store(&purple_schema, SECRET_COLLECTION_DEFAULT,
+		                      label, password,
+		                      NULL, ss_save_continue, storage,
+		                      "user", username,
+		                      "protocol", purple_account_get_protocol_id(account),
+		                      NULL);
+		g_free(label);
+
+	} else {	/* password == NULL, delete password. */
+		purple_debug_info("keyring-libsecret",
+			"Forgetting password for account %s (%s).\n",
+			purple_account_get_username(account),
+			purple_account_get_protocol_id(account));
+
+		secret_password_clear(&purple_schema, NULL, ss_save_continue, storage,
+		                      "user", purple_account_get_username(account),
+		                      "protocol", purple_account_get_protocol_id(account),
+		                      NULL);
+	}
+}
+
+static void
+ss_close(void)
+{
+}
+
+static gboolean
+ss_init(void)
+{
+	keyring_handler = purple_keyring_new();
+
+	purple_keyring_set_name(keyring_handler, SECRETSERVICE_NAME);
+	purple_keyring_set_id(keyring_handler, SECRETSERVICE_ID);
+	purple_keyring_set_read_password(keyring_handler, ss_read);
+	purple_keyring_set_save_password(keyring_handler, ss_save);
+	purple_keyring_set_close_keyring(keyring_handler, ss_close);
+
+	purple_keyring_register(keyring_handler);
+
+	return TRUE;
+}
+
+static void
+ss_uninit(void)
+{
+	ss_close();
+	purple_keyring_unregister(keyring_handler);
+	purple_keyring_free(keyring_handler);
+	keyring_handler = NULL;
+}
+
+/***********************************************/
+/*     Plugin interface                        */
+/***********************************************/
+
+static gboolean
+ss_load(PurplePlugin *plugin)
+{
+	return ss_init();
+}
+
+static gboolean
+ss_unload(PurplePlugin *plugin)
+{
+	if (purple_keyring_get_inuse() == keyring_handler)
+		return FALSE;
+
+	ss_uninit();
+
+	return TRUE;
+}
+
+PurplePluginInfo plugininfo =
+{
+	PURPLE_PLUGIN_MAGIC,		/* magic */
+	PURPLE_MAJOR_VERSION,		/* major_version */
+	PURPLE_MINOR_VERSION,		/* minor_version */
+	PURPLE_PLUGIN_STANDARD,		/* type */
+	NULL,						/* ui_requirement */
+	PURPLE_PLUGIN_FLAG_INVISIBLE,	/* flags */
+	NULL,						/* dependencies */
+	PURPLE_PRIORITY_DEFAULT,	/* priority */
+	SECRETSERVICE_ID,			/* id */
+	SECRETSERVICE_NAME,			/* name */
+	DISPLAY_VERSION,			/* version */
+	"Secret Service Plugin",		/* summary */
+	N_("This plugin will store passwords in Secret Service."),	/* description */
+	"Elliott Sales de Andrade (qulogic[at]pidgin.im)",		/* author */
+	PURPLE_WEBSITE,				/* homepage */
+	ss_load,					/* load */
+	ss_unload,					/* unload */
+	NULL,						/* destroy */
+	NULL,						/* ui_info */
+	NULL,						/* extra_info */
+	NULL,						/* prefs_info */
+	NULL,						/* actions */
+	NULL,						/* padding... */
+	NULL,
+	NULL,
+	NULL,
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+}
+
+PURPLE_INIT_PLUGIN(secret_service, init_plugin, plugininfo)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/plugins/keyrings/wincred.c	Wed May 01 11:52:20 2013 +0200
@@ -0,0 +1,320 @@
+/**
+ * @file wincred.c Passwords storage using Windows credentials
+ * @ingroup plugins
+ */
+
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include "debug.h"
+#include "internal.h"
+#include "keyring.h"
+#include "plugin.h"
+#include "version.h"
+
+#include <wincred.h>
+
+#define WINCRED_NAME        N_("Windows credentials")
+#define WINCRED_SUMMARY     N_("Store passwords using Windows credentials")
+#define WINCRED_DESCRIPTION N_("This plugin stores passwords using Windows " \
+	"credentials.")
+#define WINCRED_AUTHOR      "Tomek Wasilczyk (tomkiewicz@cpw.pidgin.im)"
+#define WINCRED_ID          "keyring-wincred"
+
+#define WINCRED_MAX_TARGET_NAME 256
+
+static PurpleKeyring *keyring_handler = NULL;
+
+static gunichar2 *
+wincred_get_target_name(PurpleAccount *account)
+{
+	gchar target_name_utf8[WINCRED_MAX_TARGET_NAME];
+	gunichar2 *target_name_utf16;
+
+	g_return_val_if_fail(account != NULL, NULL);
+
+	g_snprintf(target_name_utf8, WINCRED_MAX_TARGET_NAME, "libpurple_%s_%s",
+		purple_account_get_protocol_id(account),
+		purple_account_get_username(account));
+
+	target_name_utf16 = g_utf8_to_utf16(target_name_utf8, -1,
+		NULL, NULL, NULL);
+
+	if (target_name_utf16 == NULL) {
+		purple_debug_fatal("keyring-wincred", "Couldn't convert target "
+			"name\n");
+	}
+
+	return target_name_utf16;
+}
+
+static void
+wincred_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
+	gpointer data)
+{
+	GError *error = NULL;
+	gunichar2 *target_name = NULL;
+	gchar *password;
+	PCREDENTIALW credential;
+
+	g_return_if_fail(account != NULL);
+
+	target_name = wincred_get_target_name(account);
+	g_return_if_fail(target_name != NULL);
+
+	if (!CredReadW(target_name, CRED_TYPE_GENERIC, 0, &credential)) {
+		DWORD error_code = GetLastError();
+
+		if (error_code == ERROR_NOT_FOUND) {
+			if (purple_debug_is_verbose()) {
+				purple_debug_misc("keyring-wincred",
+					"No password found for account %s\n",
+					purple_account_get_username(account));
+			}
+			error = g_error_new(PURPLE_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_NOPASSWORD,
+				"Password not found.");
+		} else if (error_code == ERROR_NO_SUCH_LOGON_SESSION) {
+			purple_debug_error("keyring-wincred",
+				"Cannot read password, no valid logon "
+				"session\n");
+			error = g_error_new(PURPLE_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_ACCESSDENIED,
+				"Cannot read password, no valid logon session");
+		} else {
+			purple_debug_error("keyring-wincred",
+				"Cannot read password, error %lx\n",
+				error_code);
+			error = g_error_new(PURPLE_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_BACKENDFAIL,
+				"Cannot read password, error %lx", error_code);
+		}
+
+		if (cb != NULL)
+			cb(account, NULL, error, data);
+		g_error_free(error);
+		return;
+	}
+
+	password = g_utf16_to_utf8((gunichar2*)credential->CredentialBlob,
+		credential->CredentialBlobSize / sizeof(gunichar2),
+		NULL, NULL, NULL);
+
+	memset(credential->CredentialBlob, 0, credential->CredentialBlobSize);
+	CredFree(credential);
+
+	if (password == NULL) {
+		purple_debug_error("keyring-wincred",
+			"Cannot convert password\n");
+		error = g_error_new(PURPLE_KEYRING_ERROR,
+			PURPLE_KEYRING_ERROR_BACKENDFAIL,
+			"Cannot convert password");
+	} else {
+		purple_debug_misc("keyring-wincred",
+			"Got password for account %s.\n",
+			purple_account_get_username(account));
+	}
+
+	if (cb != NULL)
+		cb(account, password, error, data);
+	if (error != NULL)
+		g_error_free(error);
+
+	purple_str_wipe(password);
+}
+
+static void
+wincred_save(PurpleAccount *account, const gchar *password,
+	PurpleKeyringSaveCallback cb, gpointer data)
+{
+	GError *error = NULL;
+	gunichar2 *target_name = NULL;
+	gunichar2 *username_utf16 = NULL;
+	gunichar2 *password_utf16 = NULL;
+	CREDENTIALW credential;
+
+	g_return_if_fail(account != NULL);
+
+	target_name = wincred_get_target_name(account);
+	g_return_if_fail(target_name != NULL);
+
+	if (password == NULL)
+	{
+		if (CredDeleteW(target_name, CRED_TYPE_GENERIC, 0)) {
+			purple_debug_misc("keyring-wincred", "Password for "
+				"account %s removed\n",
+				purple_account_get_username(account));
+		} else {
+			DWORD error_code = GetLastError();
+
+			if (error_code == ERROR_NOT_FOUND) {
+				if (purple_debug_is_verbose()) {
+					purple_debug_misc("keyring-wincred",
+					"Password for account %s was already "
+					"removed.\n",
+					purple_account_get_username(account));
+				}
+			} else if (error_code == ERROR_NO_SUCH_LOGON_SESSION) {
+				purple_debug_error("keyring-wincred",
+					"Cannot remove password, no valid "
+					"logon session\n");
+				error = g_error_new(PURPLE_KEYRING_ERROR,
+					PURPLE_KEYRING_ERROR_ACCESSDENIED,
+					"Cannot remove password, no valid "
+					"logon session");
+			} else {
+				purple_debug_error("keyring-wincred",
+					"Cannot remove password, error %lx\n",
+					error_code);
+				error = g_error_new(PURPLE_KEYRING_ERROR,
+					PURPLE_KEYRING_ERROR_BACKENDFAIL,
+					"Cannot remove password, error %lx",
+					error_code);
+			}
+		}
+
+		if (cb != NULL)
+			cb(account, error, data);
+		if (error != NULL)
+			g_error_free(error);
+		return;
+	}
+
+	username_utf16 = g_utf8_to_utf16(purple_account_get_username(account),
+		-1, NULL, NULL, NULL);
+	password_utf16 = g_utf8_to_utf16(password, -1, NULL, NULL, NULL);
+
+	if (username_utf16 == NULL || password_utf16 == NULL) {
+		g_free(username_utf16);
+		purple_utf16_wipe(password_utf16);
+
+		purple_debug_fatal("keyring-wincred", "Couldn't convert "
+			"username or password\n");
+		g_return_if_reached();
+	}
+
+	memset(&credential, 0, sizeof(CREDENTIALW));
+	credential.Type = CRED_TYPE_GENERIC;
+	credential.TargetName = target_name;
+	credential.CredentialBlobSize = purple_utf16_size(password_utf16) - 2;
+	credential.CredentialBlob = (LPBYTE)password_utf16;
+	credential.Persist = CRED_PERSIST_LOCAL_MACHINE;
+	credential.UserName = username_utf16;
+
+	if (!CredWriteW(&credential, 0)) {
+		DWORD error_code = GetLastError();
+
+		if (error_code == ERROR_NO_SUCH_LOGON_SESSION) {
+			purple_debug_error("keyring-wincred",
+				"Cannot store password, no valid logon "
+				"session\n");
+			error = g_error_new(PURPLE_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_ACCESSDENIED,
+				"Cannot remove password, no valid logon "
+				"session");
+		} else {
+			purple_debug_error("keyring-wincred",
+				"Cannot store password, error %lx\n",
+				error_code);
+			error = g_error_new(PURPLE_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_BACKENDFAIL,
+				"Cannot store password, error %lx", error_code);
+		}
+	} else {
+		purple_debug_misc("keyring-wincred",
+			"Password updated for account %s.\n",
+			purple_account_get_username(account));
+	}
+
+	g_free(target_name);
+	g_free(username_utf16);
+	purple_utf16_wipe(password_utf16);
+
+	if (cb != NULL)
+		cb(account, error, data);
+	if (error != NULL)
+		g_error_free(error);
+}
+
+static gboolean
+wincred_load(PurplePlugin *plugin)
+{
+	keyring_handler = purple_keyring_new();
+
+	purple_keyring_set_name(keyring_handler, WINCRED_NAME);
+	purple_keyring_set_id(keyring_handler, WINCRED_ID);
+	purple_keyring_set_read_password(keyring_handler, wincred_read);
+	purple_keyring_set_save_password(keyring_handler, wincred_save);
+
+	purple_keyring_register(keyring_handler);
+
+	return TRUE;
+}
+
+static gboolean
+wincred_unload(PurplePlugin *plugin)
+{
+	if (purple_keyring_get_inuse() == keyring_handler) {
+		purple_debug_warning("keyring-wincred",
+			"keyring in use, cannot unload\n");
+		return FALSE;
+	}
+
+	purple_keyring_unregister(keyring_handler);
+	purple_keyring_free(keyring_handler);
+	keyring_handler = NULL;
+
+	return TRUE;
+}
+
+PurplePluginInfo plugininfo =
+{
+	PURPLE_PLUGIN_MAGIC,		/* magic */
+	PURPLE_MAJOR_VERSION,		/* major_version */
+	PURPLE_MINOR_VERSION,		/* minor_version */
+	PURPLE_PLUGIN_STANDARD,		/* type */
+	NULL,				/* ui_requirement */
+	PURPLE_PLUGIN_FLAG_INVISIBLE,	/* flags */
+	NULL,				/* dependencies */
+	PURPLE_PRIORITY_DEFAULT,	/* priority */
+	WINCRED_ID,			/* id */
+	WINCRED_NAME,			/* name */
+	DISPLAY_VERSION,		/* version */
+	WINCRED_SUMMARY,		/* summary */
+	WINCRED_DESCRIPTION,		/* description */
+	WINCRED_AUTHOR,			/* author */
+	PURPLE_WEBSITE,			/* homepage */
+	wincred_load,			/* load */
+	wincred_unload,			/* unload */
+	NULL,				/* destroy */
+	NULL,				/* ui_info */
+	NULL,				/* extra_info */
+	NULL,				/* prefs_info */
+	NULL,				/* actions */
+	NULL, NULL, NULL, NULL		/* padding */
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+}
+
+PURPLE_INIT_PLUGIN(wincred_keyring, init_plugin, plugininfo)
--- a/libpurple/plugins/one_time_password.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/plugins/one_time_password.c	Wed May 01 11:52:20 2013 +0200
@@ -46,7 +46,7 @@
 					  purple_account_get_username(account),
 					  purple_account_get_protocol_name(account));
 
-			purple_account_set_password(account, NULL);
+			purple_account_set_password(account, NULL, NULL, NULL);
 			/* TODO: Do we need to somehow clear conn->password ? */
 		}
 	}
--- a/libpurple/plugins/perl/common/Account.xs	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/plugins/perl/common/Account.xs	Wed May 01 11:52:20 2013 +0200
@@ -1,4 +1,5 @@
 #include "module.h"
+#include "../perl-handlers.h"
 
 MODULE = Purple::Account  PACKAGE = Purple::Account  PREFIX = purple_account_
 PROTOTYPES: ENABLE
@@ -44,9 +45,13 @@
     const char * username
 
 void
-purple_account_set_password(account, password)
+purple_account_set_password(account, password, func, data = 0)
     Purple::Account account
     const char * password
+    SV *func
+    SV *data
+CODE:
+	purple_perl_account_set_password(account, password, func, data);
 
 void
 purple_account_set_alias(account, alias)
@@ -130,9 +135,13 @@
 purple_account_get_username(account)
     Purple::Account account
 
-const char *
-purple_account_get_password(account)
+void
+purple_account_get_password(account, func, data = 0)
     Purple::Account account
+    SV *func
+    SV *data
+CODE:
+	purple_perl_account_get_password(account, func, data);
 
 const char *
 purple_account_get_alias(account)
--- a/libpurple/plugins/perl/perl-handlers.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/plugins/perl/perl-handlers.c	Wed May 01 11:52:20 2013 +0200
@@ -845,3 +845,127 @@
 			destroy_prefs_handler(handler);
 	}
 }
+
+static void
+perl_account_save_cb(PurpleAccount *account, GError *error, gpointer data)
+{
+	int count;
+	SV *accountSV, *errorSV;
+	PurplePerlAccountPasswordHandler *handler = data;
+
+	dSP;
+	ENTER;
+	SAVETMPS;
+	PUSHMARK(SP);
+
+	/* Push the account onto the perl stack */
+	accountSV = sv_2mortal(purple_perl_bless_object(account, "Purple::Account"));
+	XPUSHs(accountSV);
+
+	/* Push the error onto the perl stack */
+	errorSV = sv_2mortal(purple_perl_bless_object(account, "GLib::Error"));
+	XPUSHs(errorSV);
+
+	/* Push the data onto the perl stack */
+	XPUSHs((SV *)handler->data);
+
+	PUTBACK;
+	count = call_sv(handler->callback, G_EVAL | G_SCALAR);
+
+	if (count != 0)
+		croak("call_sv: Did not return the correct number of values.\n");
+
+	if (SvTRUE(ERRSV)) {
+		purple_debug_error("perl",
+		                 "Perl plugin command function exited abnormally: %s\n",
+		                 SvPVutf8_nolen(ERRSV));
+	}
+
+	SPAGAIN;
+
+	PUTBACK;
+	FREETMPS;
+	LEAVE;
+
+	g_free(handler);
+}
+
+static void
+perl_account_read_cb(PurpleAccount *account, const gchar *password,
+                     GError *error, gpointer data)
+{
+	int count;
+	SV *accountSV, *passwordSV, *errorSV;
+	PurplePerlAccountPasswordHandler *handler = data;
+
+	dSP;
+	ENTER;
+	SAVETMPS;
+	PUSHMARK(SP);
+
+	/* Push the account onto the perl stack */
+	accountSV = sv_2mortal(purple_perl_bless_object(account, "Purple::Account"));
+	XPUSHs(accountSV);
+
+	/* Push the password onto the perl stack */
+	passwordSV = newSVpv(password, 0);
+	passwordSV = sv_2mortal(passwordSV);
+	XPUSHs(passwordSV);
+
+	/* Push the error onto the perl stack */
+	errorSV = sv_2mortal(purple_perl_bless_object(account, "GLib::Error"));
+	XPUSHs(errorSV);
+
+	/* Push the data onto the perl stack */
+	XPUSHs((SV *)handler->data);
+
+	PUTBACK;
+	count = call_sv(handler->callback, G_EVAL | G_SCALAR);
+
+	if (count != 0)
+		croak("call_sv: Did not return the correct number of values.\n");
+
+	if (SvTRUE(ERRSV)) {
+		purple_debug_error("perl",
+		                 "Perl plugin command function exited abnormally: %s\n",
+		                 SvPVutf8_nolen(ERRSV));
+	}
+
+	SPAGAIN;
+
+	PUTBACK;
+	FREETMPS;
+	LEAVE;
+
+	g_free(handler);
+}
+
+void
+purple_perl_account_get_password(PurpleAccount *account, SV *func, SV *data)
+{
+	PurplePerlAccountPasswordHandler *handler;
+
+	handler = g_new0(PurplePerlAccountPasswordHandler, 1);
+	handler->callback = (func != NULL &&
+	                     func != &PL_sv_undef ? newSVsv(func) : NULL);
+	handler->data     = (data != NULL &&
+	                     data != &PL_sv_undef ? newSVsv(data) : NULL);
+
+	purple_account_get_password(account, perl_account_read_cb, data);
+}
+
+void
+purple_perl_account_set_password(PurpleAccount *account, const char *password,
+                                 SV *func, SV *data)
+{
+	PurplePerlAccountPasswordHandler *handler;
+
+	handler = g_new0(PurplePerlAccountPasswordHandler, 1);
+	handler->callback = (func != NULL &&
+	                     func != &PL_sv_undef ? newSVsv(func) : NULL);
+	handler->data     = (data != NULL &&
+	                     data != &PL_sv_undef ? newSVsv(data) : NULL);
+
+	purple_account_set_password(account, password, perl_account_save_cb, data);
+}
+
--- a/libpurple/plugins/perl/perl-handlers.h	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/plugins/perl/perl-handlers.h	Wed May 01 11:52:20 2013 +0200
@@ -48,6 +48,13 @@
 
 } PurplePerlPrefsHandler;
 
+typedef struct
+{
+	SV *callback;
+	SV *data;
+
+} PurplePerlAccountPasswordHandler;
+
 void purple_perl_plugin_action_cb(PurplePluginAction * gpa);
 GList *purple_perl_plugin_actions(PurplePlugin *plugin, gpointer context);
 
@@ -82,4 +89,10 @@
 void purple_perl_prefs_disconnect_callback(guint callback_id);
 void purple_perl_pref_cb_clear_for_plugin(PurplePlugin *plugin);
 
+void
+purple_perl_account_get_password(PurpleAccount *account, SV *func, SV *data);
+void
+purple_perl_account_set_password(PurpleAccount *account, const char *password,
+                                 SV *func, SV *data);
+
 #endif /* _PURPLE_PERL_HANDLERS_H_ */
--- a/libpurple/protocols/gg/account.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/gg/account.c	Wed May 01 11:52:20 2013 +0200
@@ -359,7 +359,8 @@
 	purple_account_set_username(account, ggp_uin_to_str(uin));
 	purple_account_set_remember_password(account,
 		register_data->password_remember);
-	purple_account_set_password(account, register_data->password);
+	purple_account_set_password(account, register_data->password,
+		NULL, NULL);
 	
 	tmp = g_strdup_printf(_("Your new GG number: %u."), uin);
 	purple_notify_info(account, GGP_ACCOUNT_REGISTER_TITLE,
@@ -554,7 +555,7 @@
 	g_assert(chpass_data->token_value != NULL);
 
 	if (g_utf8_collate(chpass_data->password_current,
-		purple_account_get_password(account)) != 0)
+		purple_connection_get_password(chpass_data->gc)) != 0)
 	{
 		g_free(chpass_data->password_current);
 		chpass_data->password_current = NULL;
@@ -636,7 +637,8 @@
 	purple_debug_info("gg", "ggp_account_chpass_response: "
 		"password changed\n");
 	
-	purple_account_set_password(account, chpass_data->password_new);
+	purple_account_set_password(account, chpass_data->password_new,
+		NULL, NULL);
 
 	purple_notify_info(account, GGP_ACCOUNT_CHPASS_TITLE,
 		_("Your password has been changed."), NULL);
--- a/libpurple/protocols/gg/gg.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/gg/gg.c	Wed May 01 11:52:20 2013 +0200
@@ -896,7 +896,8 @@
 	ggp_status_setup(gc);
 	
 	glp->uin = ggp_str_to_uin(purple_account_get_username(account));
-	glp->password = ggp_convert_to_cp1250(purple_account_get_password(account));
+	glp->password =
+		ggp_convert_to_cp1250(purple_connection_get_password(gc));
 
 	if (glp->uin == 0) {
 		purple_connection_error(gc,
--- a/libpurple/protocols/gg/oauth/oauth-purple.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/gg/oauth/oauth-purple.c	Wed May 01 11:52:20 2013 +0200
@@ -88,7 +88,7 @@
 
 	auth = gg_oauth_generate_header(method, url,
 		purple_account_get_username(account),
-		purple_account_get_password(account), NULL, NULL);
+		purple_connection_get_password(gc), NULL, NULL);
 	request = g_strdup_printf(
 		"POST /request_token HTTP/1.1\r\n"
 		"Host: api.gadu-gadu.pl\r\n"
@@ -165,7 +165,7 @@
 		"callback_url=http://www.mojageneracja.pl&request_token=%s&"
 		"uin=%s&password=%s", data->token,
 		purple_account_get_username(account),
-		purple_account_get_password(account));
+		purple_connection_get_password(data->gc));
 	request = g_strdup_printf(
 		"POST /authorize HTTP/1.1\r\n"
 		"Host: login.gadu-gadu.pl\r\n"
@@ -206,7 +206,7 @@
 
 	auth = gg_oauth_generate_header("POST", url,
 		purple_account_get_username(account),
-		purple_account_get_password(account),
+		purple_connection_get_password(data->gc),
 		data->token, data->token_secret);
 
 	request = g_strdup_printf(
@@ -267,7 +267,7 @@
 		auth = gg_oauth_generate_header(
 			data->sign_method, data->sign_url,
 			purple_account_get_username(account),
-			purple_account_get_password(account),
+			purple_connection_get_password(data->gc),
 			token, token_secret);
 		data->callback(data->gc, auth, data->user_data);
 	}
--- a/libpurple/protocols/irc/msgs.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/irc/msgs.c	Wed May 01 11:52:20 2013 +0200
@@ -1471,7 +1471,8 @@
 	const char *pw;
 	size_t len;
 
-	pw = purple_account_get_password(irc->account);
+	pw = purple_connection_get_password(purple_account_get_connection(
+		irc->account));
 
 	if (!conn || !secret || id != SASL_CB_PASS)
 		return SASL_BADPARAM;
--- a/libpurple/protocols/jabber/auth.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/jabber/auth.c	Wed May 01 11:52:20 2013 +0200
@@ -110,7 +110,7 @@
 	if (remember)
 		purple_account_set_remember_password(account, TRUE);
 
-	purple_account_set_password(account, entry);
+	purple_account_set_password(account, entry, NULL, NULL);
 
 	/* Restart our connection */
 	jabber_auth_start_old(js);
@@ -228,7 +228,7 @@
 			reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
 			/* Clear the pasword if it isn't being saved */
 			if (!purple_account_get_remember_password(account))
-				purple_account_set_password(account, NULL);
+				purple_account_set_password(account, NULL, NULL, NULL);
 		}
 
 		purple_connection_error(js->gc, reason, msg);
@@ -372,7 +372,7 @@
 	 * password prompting here
 	 */
 
-	if (!purple_account_get_password(account)) {
+	if (!purple_connection_get_password(js->gc)) {
 		purple_account_request_password(account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc);
 		return;
 	}
--- a/libpurple/protocols/jabber/auth_cyrus.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/jabber/auth_cyrus.c	Wed May 01 11:52:20 2013 +0200
@@ -91,12 +91,10 @@
 static int jabber_sasl_cb_secret(sasl_conn_t *conn, void *ctx, int id, sasl_secret_t **secret)
 {
 	JabberStream *js = ctx;
-	PurpleAccount *account;
 	const char *pw;
 	size_t len;
 
-	account = purple_connection_get_account(js->gc);
-	pw = purple_account_get_password(account);
+	pw = purple_connection_get_password(js->gc);
 
 	if (!conn || !secret || id != SASL_CB_PASS)
 		return SASL_BADPARAM;
@@ -154,7 +152,7 @@
 	if (remember)
 		purple_account_set_remember_password(account, TRUE);
 
-	purple_account_set_password(account, entry);
+	purple_account_set_password(account, entry, NULL, NULL);
 
 	/* Rebuild our callbacks as we now have a password to offer */
 	jabber_sasl_build_callbacks(js);
@@ -249,7 +247,7 @@
 				 * to get one
 				 */
 
-				if (!purple_account_get_password(account)) {
+				if (!purple_connection_get_password(js->gc)) {
 					purple_account_request_password(account, G_CALLBACK(auth_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc);
 					return JABBER_SASL_STATE_CONTINUE;
 
@@ -364,7 +362,6 @@
 static void
 jabber_sasl_build_callbacks(JabberStream *js)
 {
-	PurpleAccount *account;
 	int id;
 
 	/* Set up our callbacks structure */
@@ -387,8 +384,7 @@
 	js->sasl_cb[id].context = (void *)js;
 	id++;
 
-	account = purple_connection_get_account(js->gc);
-	if (purple_account_get_password(account) != NULL ) {
+	if (purple_connection_get_password(js->gc) != NULL) {
 		js->sasl_cb[id].id = SASL_CB_PASS;
 		js->sasl_cb[id].proc = (void *)jabber_sasl_cb_secret;
 		js->sasl_cb[id].context = (void *)js;
--- a/libpurple/protocols/jabber/jabber.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/jabber/jabber.c	Wed May 01 11:52:20 2013 +0200
@@ -1263,7 +1263,7 @@
 					cbdata->js->user->node = g_strdup(value);
 				}
 				if(cbdata->js->registration && !strcmp(id, "password"))
-					purple_account_set_password(purple_connection_get_account(cbdata->js->gc), value);
+					purple_account_set_password(purple_connection_get_account(cbdata->js->gc), value, NULL, NULL);
 			}
 		}
 	}
@@ -2485,7 +2485,7 @@
 		purple_notify_info(js->gc, _("Password Changed"), _("Password Changed"),
 				_("Your password has been changed."));
 
-		purple_account_set_password(purple_connection_get_account(js->gc), (char *)data);
+		purple_account_set_password(purple_connection_get_account(js->gc), (const char *)data, NULL, NULL);
 	} else {
 		char *msg = jabber_parse_error(js, packet, NULL);
 
@@ -2741,7 +2741,7 @@
 			SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED);
 			/* Clear the pasword if it isn't being saved */
 			if (!purple_account_get_remember_password(purple_connection_get_account(js->gc)))
-				purple_account_set_password(purple_connection_get_account(js->gc), NULL);
+				purple_account_set_password(purple_connection_get_account(js->gc), NULL, NULL, NULL);
 			text = _("Not Authorized");
 		} else if(xmlnode_get_child(packet, "temporary-auth-failure")) {
 			text = _("Temporary Authentication Failure");
--- a/libpurple/protocols/msn/session.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/msn/session.c	Wed May 01 11:52:20 2013 +0200
@@ -385,7 +385,7 @@
 			reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE;
 			msg = g_strdup(_("You have signed on from another location"));
 			if (!purple_account_get_remember_password(session->account))
-				purple_account_set_password(session->account, NULL);
+				purple_account_set_password(session->account, NULL, NULL, NULL);
 			break;
 		case MSN_ERROR_SERV_UNAVAILABLE:
 			reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
@@ -405,7 +405,7 @@
 								  _("Unknown error") : info);
 			/* Clear the password if it isn't being saved */
 			if (!purple_account_get_remember_password(session->account))
-				purple_account_set_password(session->account, NULL);
+				purple_account_set_password(session->account, NULL, NULL, NULL);
 			break;
 		case MSN_ERROR_BAD_BLIST:
 			reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
--- a/libpurple/protocols/mxit/actions.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/mxit/actions.c	Wed May 01 11:52:20 2013 +0200
@@ -361,7 +361,7 @@
 out:
 	if ( !err ) {
 		/* update PIN in account */
-		purple_account_set_password( session->acc, pin );
+		purple_account_set_password( session->acc, pin, NULL, NULL );
 
 		/* update session object */
 		g_free( session->encpwd );
@@ -385,7 +385,6 @@
 static void mxit_change_pin_action( PurplePluginAction* action )
 {
 	PurpleConnection*			gc		= (PurpleConnection*) action->context;
-	struct MXitSession*			session	= purple_connection_get_protocol_data( gc );
 
 	PurpleRequestFields*		fields	= NULL;
 	PurpleRequestFieldGroup*	group	= NULL;
@@ -398,12 +397,12 @@
 	purple_request_fields_add_group( fields, group );
 
 	/* pin */
-	field = purple_request_field_string_new( "pin", _( "PIN" ), purple_account_get_password( session->acc ), FALSE );
+	field = purple_request_field_string_new( "pin", _( "PIN" ), purple_connection_get_password( gc ), FALSE );
 	purple_request_field_string_set_masked( field, TRUE );
 	purple_request_field_group_add_field( group, field );
 
 	/* verify pin */
-	field = purple_request_field_string_new( "pin2", _( "Verify PIN" ), purple_account_get_password( session->acc ), FALSE );
+	field = purple_request_field_string_new( "pin2", _( "Verify PIN" ), purple_connection_get_password( gc ), FALSE );
 	purple_request_field_string_set_masked( field, TRUE );
 	purple_request_field_group_add_field( group, field );
 
--- a/libpurple/protocols/mxit/cipher.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/mxit/cipher.c	Wed May 01 11:52:20 2013 +0200
@@ -79,7 +79,7 @@
 static char* transport_layer_key( struct MXitSession* session )
 {
 	static char	key[16 + 1];
-	const char*	password		= purple_account_get_password( session->acc );
+	const char*	password		= purple_connection_get_password( session->con );
 	int			passlen			= strlen( password );
 
 	/* initialize with initial key */
@@ -123,7 +123,7 @@
 
 	/* build the secret data to be encrypted: SECRET_HEADER + password */
 	pass = g_string_new( SECRET_HEADER );
-	g_string_append( pass, purple_account_get_password( session->acc) );
+	g_string_append( pass, purple_connection_get_password( session->con ) );
 	padding_add( pass );		/* add ISO10126 padding */
 
 	/* now encrypt the secret. we encrypt each block separately (ECB mode) */
--- a/libpurple/protocols/mxit/login.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/mxit/login.c	Wed May 01 11:52:20 2013 +0200
@@ -287,7 +287,7 @@
 
 out:
 	if ( !err ) {
-		purple_account_set_password( session->acc, session->profile->pin );
+		purple_account_set_password( session->acc, session->profile->pin, NULL, NULL );
 		mxit_login_connect( session );
 	}
 	else {
--- a/libpurple/protocols/myspace/myspace.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/myspace/myspace.c	Wed May 01 11:52:20 2013 +0200
@@ -705,7 +705,7 @@
 	purple_connection_update_progress(session->gc, _("Logging in"), 2, 4);
 
 	response_len = 0;
-	response = msim_compute_login_response(nc, purple_account_get_username(account), purple_account_get_password(account), &response_len);
+	response = msim_compute_login_response(nc, purple_account_get_username(account), purple_connection_get_password(session->gc), &response_len);
 
 	g_free(nc);
 
@@ -1836,9 +1836,9 @@
 			case MSIM_ERROR_INCORRECT_PASSWORD: /* Incorrect password */
 				reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
 				if (!purple_account_get_remember_password(session->account))
-					purple_account_set_password(session->account, NULL);
+					purple_account_set_password(session->account, NULL, NULL, NULL);
 #ifdef MSIM_MAX_PASSWORD_LENGTH
-				if (purple_account_get_password(session->account) && (strlen(purple_account_get_password(session->account)) > MSIM_MAX_PASSWORD_LENGTH)) {
+				if (purple_connection_get_password(session->gc) && (strlen(purple_connection_get_password(session->gc)) > MSIM_MAX_PASSWORD_LENGTH)) {
 					gchar *suggestion;
 
 					suggestion = g_strdup_printf(_("%s Your password is "
@@ -1846,7 +1846,7 @@
 							"maximum length of %d.  Please shorten your "
 							"password at http://profileedit.myspace.com/index.cfm?fuseaction=accountSettings.changePassword and try again."),
 							full_errmsg,
-							(gsize)strlen(purple_account_get_password(session->account)),
+							(gsize)strlen(purple_connection_get_password(session->gc)),
 							MSIM_MAX_PASSWORD_LENGTH);
 
 					/* Replace full_errmsg. */
@@ -1861,7 +1861,7 @@
 			case MSIM_ERROR_LOGGED_IN_ELSEWHERE: /* Logged in elsewhere */
 				reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE;
 				if (!purple_account_get_remember_password(session->account))
-					purple_account_set_password(session->account, NULL);
+					purple_account_set_password(session->account, NULL, NULL, NULL);
 				break;
 		}
 		purple_connection_error(session->gc, reason, full_errmsg);
--- a/libpurple/protocols/novell/novell.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/novell/novell.c	Wed May 01 11:52:20 2013 +0200
@@ -132,7 +132,7 @@
 				 * password was invalid.
 				 */
 				if (!purple_account_get_remember_password(purple_connection_get_account(gc)))
-					purple_account_set_password(purple_connection_get_account(gc), NULL);
+					purple_account_set_password(purple_connection_get_account(gc), NULL, NULL, NULL);
 				reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
 				break;
 			default:
@@ -2031,7 +2031,7 @@
 	if (gc)
 	{
 		if (!purple_account_get_remember_password(account))
-			purple_account_set_password(account, NULL);
+			purple_account_set_password(account, NULL, NULL, NULL);
 		purple_connection_error(gc,
 			PURPLE_CONNECTION_ERROR_NAME_IN_USE,
 			_("You have signed on from another location"));
--- a/libpurple/protocols/oscar/clientlogin.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/oscar/clientlogin.c	Wed May 01 11:52:20 2013 +0200
@@ -481,7 +481,7 @@
 		if (status_code == 330 && status_detail_code == 3011) {
 			PurpleAccount *account = purple_connection_get_account(gc);
 			if (!purple_account_get_remember_password(account))
-				purple_account_set_password(account, NULL);
+				purple_account_set_password(account, NULL, NULL, NULL);
 			purple_connection_error(gc,
 					PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
 					_("Incorrect password"));
--- a/libpurple/protocols/oscar/flap_connection.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/oscar/flap_connection.c	Wed May 01 11:52:20 2013 +0200
@@ -467,7 +467,7 @@
 			reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE;
 			tmp = g_strdup(_("You have signed on from another location"));
 			if (!purple_account_get_remember_password(account))
-				purple_account_set_password(account, NULL);
+				purple_account_set_password(account, NULL, NULL, NULL);
 		} else if (conn->disconnect_reason == OSCAR_DISCONNECT_REMOTE_CLOSED)
 			tmp = g_strdup(_("Server closed the connection"));
 		else if (conn->disconnect_reason == OSCAR_DISCONNECT_LOST_CONNECTION)
--- a/libpurple/protocols/oscar/oscar.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/oscar/oscar.c	Wed May 01 11:52:20 2013 +0200
@@ -1077,7 +1077,7 @@
 		case 0x05:
 			/* Incorrect password */
 			if (!purple_account_get_remember_password(account))
-				purple_account_set_password(account, NULL);
+				purple_account_set_password(account, NULL, NULL, NULL);
 			purple_connection_error(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Incorrect password"));
 			break;
 		case 0x11:
--- a/libpurple/protocols/sametime/sametime.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/sametime/sametime.c	Wed May 01 11:52:20 2013 +0200
@@ -3694,7 +3694,7 @@
     return;
   }
 
-  pass = g_strdup(purple_account_get_password(account));
+  pass = g_strdup(purple_connection_get_password(gc));
   port = purple_account_get_int(account, MW_KEY_PORT, MW_PLUGIN_DEFAULT_PORT);
 
   DEBUG_INFO("user: '%s'\n", user);
--- a/libpurple/protocols/silc/silc.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/silc/silc.c	Wed May 01 11:52:20 2013 +0200
@@ -481,7 +481,7 @@
 	if (remember)
 		purple_account_set_remember_password(account, TRUE);
 
-	purple_account_set_password(account, password);
+	purple_account_set_password(account, password, NULL, NULL);
 
 	/* Load SILC key pair */
 	g_snprintf(pkd, sizeof(pkd), "%s" G_DIR_SEPARATOR_S "public_key.pub", silcpurple_silcdir());
@@ -530,7 +530,7 @@
 				(char *)purple_account_get_string(account, "private-key", prd),
 				(purple_connection_get_password(gc) == NULL) ? "" : purple_connection_get_password(gc),
 				&sg->public_key, &sg->private_key)) {
-		if (!purple_account_get_password(account)) {
+		if (!purple_connection_get_password(gc)) {
 			purple_account_request_password(account, G_CALLBACK(silcpurple_got_password_cb),
 											G_CALLBACK(silcpurple_no_password_cb), gc);
 			return;
--- a/libpurple/protocols/simple/simple.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/simple/simple.c	Wed May 01 11:52:20 2013 +0200
@@ -1122,7 +1122,7 @@
 				purple_debug_info("simple", "REGISTER retries %d\n", sip->registrar.retries);
 				if(sip->registrar.retries > SIMPLE_REGISTER_RETRY_MAX) {
 					if (!purple_account_get_remember_password(purple_connection_get_account(sip->gc)))
-						purple_account_set_password(purple_connection_get_account(sip->gc), NULL);
+						purple_account_set_password(purple_connection_get_account(sip->gc), NULL, NULL, NULL);
 					purple_connection_error(sip->gc,
 						PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
 						_("Incorrect password"));
--- a/libpurple/protocols/yahoo/libymsg.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/protocols/yahoo/libymsg.c	Wed May 01 11:52:20 2013 +0200
@@ -159,7 +159,7 @@
 
 	if (pkt->service == YAHOO_SERVICE_LOGOFF && pkt->status == -1) {
 		if (!purple_account_get_remember_password(account))
-			purple_account_set_password(account, NULL);
+			purple_account_set_password(account, NULL, NULL, NULL);
 		purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NAME_IN_USE,
 			_("You have signed on from another location"));
 		return;
@@ -1940,7 +1940,7 @@
 					/* Password incorrect */
 					/* Set password to NULL. Avoids account locking. Brings dialog to enter password if clicked on Re-enable account */
 					if (!purple_account_get_remember_password(account))
-						purple_account_set_password(account, NULL);
+						purple_account_set_password(account, NULL, NULL, NULL);
 					error_reason = g_strdup(_("Incorrect password"));
 					error = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
 					break;
@@ -2232,7 +2232,7 @@
 		}
 #endif /* TRY_WEBMESSENGER_LOGIN */
 		if (!purple_account_get_remember_password(account))
-			purple_account_set_password(account, NULL);
+			purple_account_set_password(account, NULL, NULL, NULL);
 
 		msg = g_strdup(_("Invalid username or password"));
 		reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
--- a/libpurple/prpl.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/prpl.c	Wed May 01 11:52:20 2013 +0200
@@ -358,9 +358,10 @@
 	{
 		if (!purple_account_is_disconnected(account))
 			purple_account_disconnect(account);
-		/* Clear out the unsaved password if we're already disconnected and we switch to offline status */
-		else if (!purple_account_get_remember_password(account))
-			purple_account_set_password(account, NULL);
+		/* Clear out the unsaved password if we switch to offline status */
+		if (!purple_account_get_remember_password(account))
+			purple_account_set_password(account, NULL, NULL, NULL);
+
 		return;
 	}
 
--- a/libpurple/util.c	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/util.c	Wed May 01 11:52:20 2013 +0200
@@ -3762,6 +3762,41 @@
 	return g_string_free(ret, FALSE);
 }
 
+size_t
+purple_utf16_size(const gunichar2 *str)
+{
+	/* UTF16 cannot contain two consequent NUL bytes starting at even
+	 * position - see Unicode standards Chapter 3.9 D91 or RFC2781
+	 * Chapter 2.
+	 */
+
+	size_t i = 0;
+
+	g_return_val_if_fail(str != NULL, 0);
+
+	while (str[i++]);
+
+	return i * sizeof(gunichar2);
+}
+
+void
+purple_str_wipe(gchar *str)
+{
+	if (str == NULL)
+		return;
+	memset(str, 0, strlen(str));
+	g_free(str);
+}
+
+void
+purple_utf16_wipe(gunichar2 *str)
+{
+	if (str == NULL)
+		return;
+	memset(str, 0, purple_utf16_size(str));
+	g_free(str);
+}
+
 /**************************************************************************
  * URI/URL Functions
  **************************************************************************/
--- a/libpurple/util.h	Wed May 01 11:48:56 2013 +0200
+++ b/libpurple/util.h	Wed May 01 11:52:20 2013 +0200
@@ -1132,6 +1132,33 @@
  * @return A newly allocated ASCIIZ string.
  */
 char *purple_str_binary_to_ascii(const unsigned char *binary, guint len);
+
+/**
+ * Calculates UTF-16 string size (in bytes).
+ *
+ * @param str String to check.
+ * @return    Number of bytes (including NUL character) that string occupies.
+ */
+size_t purple_utf16_size(const gunichar2 *str);
+
+/**
+ * Fills a NUL-terminated string with zeros and frees it.
+ *
+ * It should be used to free sensitive data, like passwords.
+ *
+ * @param str A NUL-terminated string to free, or a NULL-pointer.
+ */
+void purple_str_wipe(gchar *str);
+
+/**
+ * Fills a NUL-terminated UTF-16 string with zeros and frees it.
+ *
+ * It should be used to free sensitive data, like passwords.
+ *
+ * @param str A NUL-terminated string to free, or a NULL-pointer.
+ */
+void purple_utf16_wipe(gunichar2 *str);
+
 /*@}*/
 
 
--- a/pidgin/gtkaccount.c	Wed May 01 11:48:56 2013 +0200
+++ b/pidgin/gtkaccount.c	Wed May 01 11:52:20 2013 +0200
@@ -118,6 +118,7 @@
 	GtkWidget *login_frame;
 	GtkWidget *protocol_menu;
 	GtkWidget *password_box;
+	gchar *password;
 	GtkWidget *username_entry;
 #if GTK_CHECK_VERSION(3,0,0)
 	GdkRGBA username_entry_hint_color;
@@ -732,10 +733,11 @@
 
 	/* Set the fields. */
 	if (dialog->account != NULL) {
-		if (purple_account_get_password(dialog->account) &&
-		    purple_account_get_remember_password(dialog->account))
+		if (dialog->password && purple_account_get_remember_password(
+			dialog->account)) {
 			gtk_entry_set_text(GTK_ENTRY(dialog->password_entry),
-							   purple_account_get_password(dialog->account));
+				dialog->password);
+		}
 
 		gtk_toggle_button_set_active(
 				GTK_TOGGLE_BUTTON(dialog->remember_pass_check),
@@ -1381,6 +1383,8 @@
 
 	purple_signals_disconnect_by_handle(dialog);
 
+	purple_str_wipe(dialog->password);
+
 	g_free(dialog);
 	return FALSE;
 }
@@ -1418,6 +1422,7 @@
 	char *tmp;
 	gboolean new_acct = FALSE, icon_change = FALSE;
 	PurpleAccount *account;
+	gboolean remember;
 
 	/* Build the username string. */
 	username = g_strdup(gtk_entry_get_text(GTK_ENTRY(dialog->username_entry)));
@@ -1523,9 +1528,12 @@
 
 
 	/* Remember Password */
-	purple_account_set_remember_password(account,
-			gtk_toggle_button_get_active(
-					GTK_TOGGLE_BUTTON(dialog->remember_pass_check)));
+	remember = gtk_toggle_button_get_active(
+		GTK_TOGGLE_BUTTON(dialog->remember_pass_check));
+	if(!remember)
+		purple_keyring_set_password(account, NULL, NULL, NULL);
+
+	purple_account_set_remember_password(account, remember);
 
 	/* Check Mail */
 	if (dialog->prpl_info && dialog->prpl_info->options & OPT_PROTO_MAIL_CHECK)
@@ -1543,9 +1551,9 @@
 	 * don't want to prompt them.
 	 */
 	if ((purple_account_get_remember_password(account) || new_acct) && (*value != '\0'))
-		purple_account_set_password(account, value);
+		purple_account_set_password(account, value, NULL, NULL);
 	else
-		purple_account_set_password(account, NULL);
+		purple_account_set_password(account, NULL, NULL, NULL);
 
 	purple_account_set_username(account, username);
 	g_free(username);
@@ -1687,10 +1695,11 @@
 	{"STRING", 0, 2}
 };
 
-void
-pidgin_account_dialog_show(PidginAccountDialogType type,
-							 PurpleAccount *account)
+static void
+pidgin_account_dialog_show_continue(PurpleAccount *account,
+	const gchar *password, GError *error, gpointer _type)
 {
+	PidginAccountDialogType type = GPOINTER_TO_INT(_type);
 	AccountPrefsDialog *dialog;
 	GtkWidget *win;
 	GtkWidget *main_vbox;
@@ -1714,8 +1723,9 @@
 	}
 
 	dialog->account = account;
-	dialog->type    = type;
-	dialog->sg      = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+	dialog->password = g_strdup(password);
+	dialog->type = type;
+	dialog->sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
 
 	if (dialog->account == NULL) {
 		/* Select the first prpl in the list*/
@@ -1810,6 +1820,14 @@
 		gtk_widget_grab_focus(dialog->protocol_menu);
 }
 
+void
+pidgin_account_dialog_show(PidginAccountDialogType type, PurpleAccount *account)
+{
+	/* this is to make sure the password will be cached */
+	purple_account_get_password(account,
+		pidgin_account_dialog_show_continue, GINT_TO_POINTER(type));
+}
+
 /**************************************************************************
  * Accounts Dialog
  **************************************************************************/
--- a/pidgin/gtkconn.c	Wed May 01 11:48:56 2013 +0200
+++ b/pidgin/gtkconn.c	Wed May 01 11:52:20 2013 +0200
@@ -196,10 +196,7 @@
 	while (l) {
 		PurpleAccount *a = (PurpleAccount*)l->data;
 		if (!purple_account_is_disconnected(a)) {
-			char *password = g_strdup(purple_account_get_password(a));
 			purple_account_disconnect(a);
-			purple_account_set_password(a, password);
-			g_free(password);
 		}
 		l = l->next;
 	}
--- a/pidgin/gtkprefs.c	Wed May 01 11:48:56 2013 +0200
+++ b/pidgin/gtkprefs.c	Wed May 01 11:52:20 2013 +0200
@@ -43,6 +43,7 @@
 #include "upnp.h"
 #include "util.h"
 #include "network.h"
+#include "keyring.h"
 
 #include "gtkblist.h"
 #include "gtkconv.h"
@@ -110,6 +111,9 @@
 static GtkWidget *prefs_status_themes_combo_box;
 static GtkWidget *prefs_smiley_themes_combo_box;
 
+/* Keyrings page */
+static gpointer keyring_page_pref_set_instance = NULL;
+
 /* Sound theme specific */
 static GtkWidget *sound_entry = NULL;
 static int sound_row_sel = 0;
@@ -269,83 +273,105 @@
 	PREF_DROPDOWN_COUNT
 };
 
-static void
-dropdown_set(GObject *w, const char *key)
+typedef struct
 {
-	const char *str_value;
-	int int_value;
-	gboolean bool_value;
 	PurplePrefType type;
+	union {
+		const char *string;
+		int integer;
+		gboolean boolean;
+	} value;
+} PidginPrefValue;
+
+typedef void (*PidginPrefsDropdownCallback)(GtkComboBox *combo_box,
+	PidginPrefValue value);
+
+static void
+dropdown_set(GtkComboBox *combo_box, gpointer _cb)
+{
+	PidginPrefsDropdownCallback cb = _cb;
 	GtkTreeIter iter;
 	GtkTreeModel *tree_model;
-
-	tree_model = gtk_combo_box_get_model(GTK_COMBO_BOX(w));
-	if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(w), &iter))
+	PidginPrefValue active;
+
+	tree_model = gtk_combo_box_get_model(combo_box);
+	if (!gtk_combo_box_get_active_iter(combo_box, &iter))
 		return;
-
-	type = GPOINTER_TO_INT(g_object_get_data(w, "type"));
-
-	if (type == PURPLE_PREF_INT) {
-		gtk_tree_model_get(tree_model, &iter,
-		                   PREF_DROPDOWN_VALUE, &int_value,
-		                   -1);
-
-		purple_prefs_set_int(key, int_value);
+	active.type = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(combo_box),
+		"type"));
+
+	g_object_set_data(G_OBJECT(combo_box), "previously_active",
+		g_object_get_data(G_OBJECT(combo_box), "current_active"));
+	g_object_set_data(G_OBJECT(combo_box), "current_active",
+		GINT_TO_POINTER(gtk_combo_box_get_active(combo_box)));
+
+	if (active.type == PURPLE_PREF_INT) {
+		gtk_tree_model_get(tree_model, &iter, PREF_DROPDOWN_VALUE,
+			&active.value.integer, -1);
 	}
-	else if (type == PURPLE_PREF_STRING) {
-		gtk_tree_model_get(tree_model, &iter,
-		                   PREF_DROPDOWN_VALUE, &str_value,
-		                   -1);
-
-		purple_prefs_set_string(key, str_value);
+	else if (active.type == PURPLE_PREF_STRING) {
+		gtk_tree_model_get(tree_model, &iter, PREF_DROPDOWN_VALUE,
+			&active.value.string, -1);
+	}
+	else if (active.type == PURPLE_PREF_BOOLEAN) {
+		gtk_tree_model_get(tree_model, &iter, PREF_DROPDOWN_VALUE,
+			&active.value.boolean, -1);
 	}
-	else if (type == PURPLE_PREF_BOOLEAN) {
-		gtk_tree_model_get(tree_model, &iter,
-		                   PREF_DROPDOWN_VALUE, &bool_value,
-		                   -1);
-
-		purple_prefs_set_bool(key, bool_value);
-	}
+
+	cb(combo_box, active);
 }
 
-GtkWidget *
-pidgin_prefs_dropdown_from_list(GtkWidget *box, const gchar *title,
-		PurplePrefType type, const char *key, GList *menuitems)
+static void pidgin_prefs_dropdown_revert_active(GtkComboBox *combo_box)
 {
-	GtkWidget  *dropdown;
-	GtkWidget  *label = NULL;
-	gchar      *text;
-	const char *stored_str = NULL;
-	int         stored_int = 0;
-	gboolean    stored_bool = FALSE;
-	int         int_value  = 0;
-	const char *str_value  = NULL;
-	gboolean    bool_value = FALSE;
+	gint previously_active;
+
+	g_return_if_fail(combo_box != NULL);
+
+	previously_active = GPOINTER_TO_INT(g_object_get_data(
+		G_OBJECT(combo_box), "previously_active"));
+	g_object_set_data(G_OBJECT(combo_box), "current_active",
+		GINT_TO_POINTER(previously_active));
+
+	gtk_combo_box_set_active(combo_box, previously_active);
+}
+
+static GtkWidget *
+pidgin_prefs_dropdown_from_list_with_cb(GtkWidget *box, const gchar *title,
+	GtkComboBox **dropdown_out, GList *menuitems,
+	PidginPrefValue initial, PidginPrefsDropdownCallback cb)
+{
+	GtkWidget *dropdown;
+	GtkWidget *label = NULL;
+	gchar *text;
 	GtkListStore *store = NULL;
 	GtkTreeIter iter;
 	GtkTreeIter active;
 	GtkCellRenderer *renderer;
+	gpointer current_active;
 
 	g_return_val_if_fail(menuitems != NULL, NULL);
 
-	if (type == PURPLE_PREF_INT) {
+	if (initial.type == PURPLE_PREF_INT) {
 		store = gtk_list_store_new(PREF_DROPDOWN_COUNT, G_TYPE_STRING, G_TYPE_INT);
-		stored_int = purple_prefs_get_int(key);
-	} else if (type == PURPLE_PREF_STRING) {
+	} else if (initial.type == PURPLE_PREF_STRING) {
 		store = gtk_list_store_new(PREF_DROPDOWN_COUNT, G_TYPE_STRING, G_TYPE_STRING);
-		stored_str = purple_prefs_get_string(key);
-	} else if (type == PURPLE_PREF_BOOLEAN) {
+	} else if (initial.type == PURPLE_PREF_BOOLEAN) {
 		store = gtk_list_store_new(PREF_DROPDOWN_COUNT, G_TYPE_STRING, G_TYPE_BOOLEAN);
-		stored_bool = purple_prefs_get_bool(key);
 	} else {
 		g_warn_if_reached();
 		return NULL;
 	}
 
 	dropdown = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
-	g_object_set_data(G_OBJECT(dropdown), "type", GINT_TO_POINTER(type));
+	if (dropdown_out != NULL)
+		*dropdown_out = GTK_COMBO_BOX(dropdown);
+	g_object_set_data(G_OBJECT(dropdown), "type", GINT_TO_POINTER(initial.type));
 
 	while (menuitems != NULL && (text = (char *)menuitems->data) != NULL) {
+		int int_value  = 0;
+		const char *str_value  = NULL;
+		gboolean bool_value = FALSE;
+
 		menuitems = g_list_next(menuitems);
 		g_return_val_if_fail(menuitems != NULL, NULL);
 
@@ -354,30 +380,31 @@
 		                   PREF_DROPDOWN_TEXT, text,
 		                   -1);
 
-		if (type == PURPLE_PREF_INT) {
+		if (initial.type == PURPLE_PREF_INT) {
 			int_value = GPOINTER_TO_INT(menuitems->data);
 			gtk_list_store_set(store, &iter,
 			                   PREF_DROPDOWN_VALUE, int_value,
 			                   -1);
 		}
-		else if (type == PURPLE_PREF_STRING) {
+		else if (initial.type == PURPLE_PREF_STRING) {
 			str_value = (const char *)menuitems->data;
 			gtk_list_store_set(store, &iter,
 			                   PREF_DROPDOWN_VALUE, str_value,
 			                   -1);
 		}
-		else if (type == PURPLE_PREF_BOOLEAN) {
+		else if (initial.type == PURPLE_PREF_BOOLEAN) {
 			bool_value = (gboolean)GPOINTER_TO_INT(menuitems->data);
 			gtk_list_store_set(store, &iter,
 			                   PREF_DROPDOWN_VALUE, bool_value,
 			                   -1);
 		}
 
-		if ((type == PURPLE_PREF_INT && stored_int == int_value) ||
-			(type == PURPLE_PREF_STRING && stored_str != NULL &&
-			 !strcmp(stored_str, str_value)) ||
-			(type == PURPLE_PREF_BOOLEAN &&
-			 (stored_bool == bool_value))) {
+		if ((initial.type == PURPLE_PREF_INT &&
+			initial.value.integer == int_value) ||
+			(initial.type == PURPLE_PREF_STRING &&
+			!g_strcmp0(initial.value.string, str_value)) ||
+			(initial.type == PURPLE_PREF_BOOLEAN &&
+			(initial.value.boolean == bool_value))) {
 
 			active = iter;
 		}
@@ -392,15 +419,65 @@
 	                               NULL);
 
 	gtk_combo_box_set_active_iter(GTK_COMBO_BOX(dropdown), &active);
+	current_active = GINT_TO_POINTER(gtk_combo_box_get_active(GTK_COMBO_BOX(
+		dropdown)));
+	g_object_set_data(G_OBJECT(dropdown), "current_active", current_active);
+	g_object_set_data(G_OBJECT(dropdown), "previously_active", current_active);
 
 	g_signal_connect(G_OBJECT(dropdown), "changed",
-	                 G_CALLBACK(dropdown_set), (char *)key);
+		G_CALLBACK(dropdown_set), cb);
 
 	pidgin_add_widget_to_vbox(GTK_BOX(box), title, NULL, dropdown, FALSE, &label);
 
 	return label;
 }
 
+static void
+pidgin_prefs_dropdown_from_list_cb(GtkComboBox *combo_box,
+	PidginPrefValue value)
+{
+	const char *key;
+
+	key = g_object_get_data(G_OBJECT(combo_box), "key");
+
+	if (value.type == PURPLE_PREF_INT) {
+		purple_prefs_set_int(key, value.value.integer);
+	} else if (value.type == PURPLE_PREF_STRING) {
+		purple_prefs_set_string(key, value.value.string);
+	} else if (value.type == PURPLE_PREF_BOOLEAN) {
+		purple_prefs_set_bool(key, value.value.boolean);
+	} else {
+		g_return_if_reached();
+	}
+}
+
+GtkWidget *
+pidgin_prefs_dropdown_from_list(GtkWidget *box, const gchar *title,
+		PurplePrefType type, const char *key, GList *menuitems)
+{
+	PidginPrefValue initial;
+	GtkComboBox *dropdown = NULL;
+	GtkWidget *label;
+
+	initial.type = type;
+	if (type == PURPLE_PREF_INT) {
+		initial.value.integer = purple_prefs_get_int(key);
+	} else if (type == PURPLE_PREF_STRING) {
+		initial.value.string = purple_prefs_get_string(key);
+	} else if (type == PURPLE_PREF_BOOLEAN) {
+		initial.value.boolean = purple_prefs_get_bool(key);
+	} else {
+		g_return_val_if_reached(NULL);
+	}
+
+	label = pidgin_prefs_dropdown_from_list_with_cb(box, title, &dropdown,
+		menuitems, initial, pidgin_prefs_dropdown_from_list_cb);
+
+	g_object_set_data(G_OBJECT(dropdown), "key", (gpointer)key);
+
+	return label;
+}
+
 GtkWidget *
 pidgin_prefs_dropdown(GtkWidget *box, const gchar *title, PurplePrefType type,
 			   const char *key, ...)
@@ -464,6 +541,8 @@
 	prefs_status_themes_combo_box = NULL;
 	prefs_smiley_themes_combo_box = NULL;
 
+	keyring_page_pref_set_instance = NULL;
+
 	sample_webview = NULL;
 
 	notebook_page = 0;
@@ -2507,6 +2586,116 @@
 	return ret;
 }
 
+static void
+change_master_password_cb(GtkWidget *button, gpointer ptr)
+{
+	purple_keyring_change_master(NULL, NULL);
+}
+
+static void
+keyring_page_pref_set_inuse(GError *error, gpointer _combo_box)
+{
+	GtkComboBox *combo_box = _combo_box;
+	PurpleKeyring *in_use = purple_keyring_get_inuse();
+
+	if (keyring_page_pref_set_instance != combo_box) {
+		purple_debug_info("gtkprefs", "pref window already closed\n");
+		return;
+	}
+	keyring_page_pref_set_instance = NULL;
+
+	gtk_widget_set_sensitive(GTK_WIDGET(combo_box), TRUE);
+
+	if (error != NULL) {
+		pidgin_prefs_dropdown_revert_active(combo_box);
+		purple_notify_error(NULL, _("Keyring"),
+			_("Failed to set new keyring"), error->message);
+		return;
+	}
+
+	g_return_if_fail(in_use != NULL);
+	purple_prefs_set_string("/purple/keyring/active",
+		purple_keyring_get_id(in_use));
+}
+
+static void
+keyring_page_pref_changed(GtkComboBox *combo_box, PidginPrefValue value)
+{
+	const char *keyring_id;
+	PurpleKeyring *keyring;
+	GtkWidget *change_master_button;
+
+	g_return_if_fail(combo_box != NULL);
+	g_return_if_fail(value.type == PURPLE_PREF_STRING);
+
+	change_master_button = g_object_get_data(G_OBJECT(combo_box),
+		"change_master_button");
+
+	keyring_id = value.value.string;
+	keyring = purple_keyring_find_keyring_by_id(keyring_id);
+	if (keyring == NULL) {
+		pidgin_prefs_dropdown_revert_active(combo_box);
+		purple_notify_error(NULL, _("Keyring"),
+			_("Selected keyring is disabled"), NULL);
+		return;
+	}
+
+	gtk_widget_set_sensitive(GTK_WIDGET(combo_box), FALSE);
+
+	keyring_page_pref_set_instance = combo_box;
+	purple_keyring_set_inuse(keyring, FALSE, keyring_page_pref_set_inuse,
+		combo_box);
+
+	if (purple_keyring_get_change_master(keyring))
+		gtk_widget_set_sensitive(change_master_button, TRUE);
+	else
+		gtk_widget_set_sensitive(change_master_button, FALSE);
+}
+
+static GtkWidget *
+keyring_page(void)
+{
+	GtkWidget *ret;
+	GtkWidget *vbox;
+	GtkWidget *button;
+	GList *names;
+	const char *keyring_id;
+	PurpleKeyring *keyring;
+	PidginPrefValue initial;
+	GtkComboBox *dropdown = NULL;
+
+	keyring_id = purple_prefs_get_string("/purple/keyring/active");
+	keyring = purple_keyring_find_keyring_by_id(keyring_id);
+
+	ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
+	gtk_container_set_border_width(GTK_CONTAINER (ret), PIDGIN_HIG_BORDER);
+
+	/* Change master password */
+	button = gtk_button_new_with_mnemonic(_("_Change master password."));
+
+	/* Keyring selection */
+	vbox = pidgin_make_frame(ret, _("Keyring"));
+	names = purple_keyring_get_options();
+	initial.type = PURPLE_PREF_STRING;
+	initial.value.string = purple_prefs_get_string("/purple/keyring/active");
+	pidgin_prefs_dropdown_from_list_with_cb(vbox, _("Keyring:"), &dropdown,
+		names, initial, keyring_page_pref_changed);
+	g_object_set_data(G_OBJECT(dropdown), "change_master_button", button);
+	g_list_free(names);
+
+	g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(change_master_password_cb), NULL);
+
+	if (keyring && purple_keyring_get_change_master(keyring))
+		gtk_widget_set_sensitive(button, TRUE);
+	else
+		gtk_widget_set_sensitive(button, FALSE);
+
+	gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 1);
+	gtk_widget_show_all(ret);
+
+	return ret;
+}
+
 #ifndef _WIN32
 static gint
 sound_cmd_yeah(GtkEntry *entry, gpointer d)
@@ -3674,6 +3863,7 @@
 	prefs_notebook_add_page(_("Logging"), logging_page(), notebook_page++);
 	prefs_notebook_add_page(_("Network"), network_page(), notebook_page++);
 	prefs_notebook_add_page(_("Proxy"), proxy_page(), notebook_page++);
+	prefs_notebook_add_page(_("Password Storage"), keyring_page(), notebook_page++);
 
 	prefs_notebook_add_page(_("Sounds"), sound_page(), notebook_page++);
 	prefs_notebook_add_page(_("Status / Idle"), away_page(), notebook_page++);

mercurial