diff --git a/.hgignore b/.hgignore
--- a/.hgignore
+++ b/.hgignore
@@ -28,6 +28,7 @@
 .*\.pyo$
 .*\.rej$
 .*\.so$
+.*\.moc$
 Doxyfile(\.mingw)?$
 VERSION$
 aclocal.m4
diff --git a/COPYRIGHT b/COPYRIGHT
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -55,6 +55,7 @@
 Igor Belyi
 David Benjamin
 Brian Bernas
+Vivien Bernet-Rollande
 Paul Betts
 Runa Bhattacharjee
 Jonas Birmé
diff --git a/configure.ac b/configure.ac
--- a/configure.ac
+++ b/configure.ac
@@ -110,6 +110,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"
@@ -1479,6 +1480,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" 
@@ -1534,6 +1536,152 @@
 fi
 
 dnl #######################################################################
+dnl # Check for Secret Service headers
+dnl #######################################################################
+
+# disabled - see secretservice.c
+#AC_ARG_ENABLE(libsecret, [AC_HELP_STRING([--disable-libsecret], [enable Secret Service support])], enable_secret_service=no, enable_secret_service=yes)
+
+#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")
+AM_CONDITIONAL(ENABLE_SECRETSERVICE, test "x1" = "x2")
+
+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 #######################################################################
 
@@ -2429,6 +2577,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)
@@ -2707,6 +2856,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
@@ -2790,6 +2940,10 @@
 echo Build with GtkSpell support... : $enable_gtkspell
 echo Build with GCR widgets........ : $enable_gcr
 echo
+echo Build with GNOME Keyring...... : $enable_gnome_keyring
+echo Build with KWallet............ : $enable_kwallet
+#echo Build with Secret Service..... : $enable_secret_service
+echo
 echo Build with plugin support..... : $enable_plugins
 echo Build with Mono support....... : $enable_mono
 echo Build with Perl support....... : $enable_perl
diff --git a/finch/gntaccount.c b/finch/gntaccount.c
--- a/finch/gntaccount.c
+++ b/finch/gntaccount.c
@@ -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);
diff --git a/finch/gntprefs.c b/finch/gntprefs.c
--- a/finch/gntprefs.c
+++ b/finch/gntprefs.c
@@ -41,6 +41,7 @@
 	GList *freestrings;  /* strings to be freed when the pref-window is closed */
 	gboolean showing;
 	GntWidget *window;
+	GntWidget *keyring_window;
 } pref_request;
 
 void finch_prefs_init()
@@ -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},
@@ -246,10 +253,15 @@
 		return;
 	}
 
+	if (pref_request.keyring_window != NULL)
+		purple_request_close(PURPLE_REQUEST_FIELDS,
+			pref_request.keyring_window);
+
 	fields = purple_request_fields_new();
 
 	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);
 
@@ -260,3 +272,40 @@
 			NULL);
 }
 
+static void
+finch_prefs_keyring_save(void *data, PurpleRequestFields *fields)
+{
+	pref_request.keyring_window = NULL;
+
+	purple_keyring_apply_settings(NULL, fields);
+}
+
+static void
+finch_prefs_keyring_cancel(void)
+{
+	pref_request.keyring_window = NULL;
+}
+
+void finch_prefs_show_keyring(void)
+{
+	PurpleRequestFields *fields;
+
+	if (pref_request.keyring_window != NULL) {
+		gnt_window_present(pref_request.keyring_window);
+		return;
+	}
+
+	fields = purple_keyring_read_settings();
+	if (fields == NULL) {
+		purple_notify_info(NULL, _("Keyring settings"),
+			_("Selected keyring doesn't allow any configuration"),
+			NULL);
+		return;
+	}
+
+	pref_request.keyring_window = purple_request_fields(NULL,
+		_("Keyring settings"), NULL, NULL, fields,
+		_("Save"), G_CALLBACK(finch_prefs_keyring_save),
+		_("Cancel"), G_CALLBACK(finch_prefs_keyring_cancel),
+		NULL, NULL, NULL, NULL);
+}
diff --git a/finch/gntprefs.h b/finch/gntprefs.h
--- a/finch/gntprefs.h
+++ b/finch/gntprefs.h
@@ -42,6 +42,11 @@
 void finch_prefs_show_all(void);
 
 /**
+ * Show the preferences dialog for the selected keyring.
+ */
+void finch_prefs_show_keyring(void);
+
+/**
  * You don't need to know about this.
  */
 void finch_prefs_update_old(void);
diff --git a/finch/gntui.c b/finch/gntui.c
--- a/finch/gntui.c
+++ b/finch/gntui.c
@@ -106,6 +106,7 @@
 	gnt_register_action(_("Room List"), finch_roomlist_show_all);
 	gnt_register_action(_("Sounds"), finch_sounds_show_all);
 	gnt_register_action(_("Preferences"), finch_prefs_show_all);
+	gnt_register_action(_("Keyring settings"), finch_prefs_show_keyring);
 	gnt_register_action(_("Statuses"), finch_savedstatus_show_all);
 
 #ifdef STANDALONE
diff --git a/libpurple/Makefile.am b/libpurple/Makefile.am
--- a/libpurple/Makefile.am
+++ b/libpurple/Makefile.am
@@ -55,6 +55,7 @@
 	http.c \
 	idle.c \
 	imgstore.c \
+	keyring.c \
 	log.c \
 	media/backend-fs2.c \
 	media/backend-iface.c \
@@ -124,6 +125,7 @@
 	http.h \
 	idle.h \
 	imgstore.h \
+	keyring.h \
 	log.h \
 	media.h \
 	media-gst.h \
@@ -317,6 +319,8 @@
 	$(GSTINTERFACES_LIBS) \
 	$(IDN_LIBS) \
 	$(JSON_LIBS) \
+	$(GNUTLS_LIBS) \
+	$(NSS_LIBS) \
 	ciphers/libpurple-ciphers.la \
 	-lm
 
diff --git a/libpurple/Makefile.mingw b/libpurple/Makefile.mingw
--- a/libpurple/Makefile.mingw
+++ b/libpurple/Makefile.mingw
@@ -33,11 +33,14 @@
 			-I$(GTK_TOP)/include/glib-2.0 \
 			-I$(GTK_TOP)/lib/glib-2.0/include \
 			-I$(JSON_GLIB_TOP)/include/json-glib-1.0 \
-			-I$(LIBXML2_TOP)/include/libxml2
+			-I$(LIBXML2_TOP)/include/libxml2 \
+			-I$(NSS_TOP)/include/nspr4 \
+			-I$(NSS_TOP)/include/nss3
 
 LIB_PATHS +=		-L$(GTK_TOP)/lib \
 			-L$(JSON_GLIB_TOP)/lib \
-			-L$(LIBXML2_TOP)/lib
+			-L$(LIBXML2_TOP)/lib \
+			-L$(NSS_TOP)/lib
 
 ##
 ##  SOURCES, OBJECTS
@@ -49,10 +52,12 @@
 			buddyicon.c \
 			certificate.c \
 			cipher.c \
+			ciphers/aes.c \
 			ciphers/des.c \
 			ciphers/gchecksum.c \
 			ciphers/hmac.c \
 			ciphers/md4.c \
+			ciphers/pbkdf2.c \
 			ciphers/rc4.c \
 			circbuffer.c \
 			cmds.c \
@@ -67,6 +72,7 @@
 			http.c \
 			idle.c \
 			imgstore.c \
+			keyring.c \
 			log.c \
 			media/candidate.c \
 			media/enum-types.c \
@@ -127,7 +133,9 @@
 		-lintl \
 		-lws2_32 \
 		-lxml2 \
-		-ljson-glib-1.0
+		-ljson-glib-1.0 \
+		-lnss3 \
+		-lnspr4
 
 include $(PIDGIN_COMMON_RULES)
 
diff --git a/libpurple/account.c b/libpurple/account.c
--- a/libpurple/account.c
+++ b/libpurple/account.c
@@ -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,9 @@
 		return;
 	}
 
-	if(remember)
-		purple_account_set_remember_password(account, TRUE);
-
-	purple_account_set_password(account, entry);
-
+	purple_account_set_remember_password(account, remember);
+
+	purple_account_set_password(account, entry, NULL, NULL);
 	_purple_connection_new(account, FALSE, entry);
 }
 
@@ -1210,11 +1277,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 +1324,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 +1350,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 +1707,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 +2234,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 +2794,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 +2901,14 @@
 	                   account, type, description);
 }
 
+static void
+password_migration_cb(PurpleAccount *account)
+{
+	/* account may be NULL (means: all) */
+
+	schedule_accounts_save();
+}
+
 const PurpleConnectionErrorInfo *
 purple_account_get_current_error(PurpleAccount *account)
 {
@@ -2812,6 +2953,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 +3029,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 +3284,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();
 
diff --git a/libpurple/account.h b/libpurple/account.h
--- a/libpurple/account.h
+++ b/libpurple/account.h
@@ -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.
diff --git a/libpurple/cipher.c b/libpurple/cipher.c
--- a/libpurple/cipher.c
+++ b/libpurple/cipher.c
@@ -248,14 +248,7 @@
 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
 										PURPLE_SUBTYPE_CIPHER));
 
-	purple_ciphers_register_cipher("md5", purple_md5_cipher_get_ops());
-	purple_ciphers_register_cipher("sha1", purple_sha1_cipher_get_ops());
-	purple_ciphers_register_cipher("sha256", purple_sha256_cipher_get_ops());
-	purple_ciphers_register_cipher("md4", purple_md4_cipher_get_ops());
-	purple_ciphers_register_cipher("hmac", purple_hmac_cipher_get_ops());
-	purple_ciphers_register_cipher("des", purple_des_cipher_get_ops());
-	purple_ciphers_register_cipher("des3", purple_des3_cipher_get_ops());
-	purple_ciphers_register_cipher("rc4", purple_rc4_cipher_get_ops());
+	purple_ciphers_register_all();
 }
 
 void
diff --git a/libpurple/ciphers/Makefile.am b/libpurple/ciphers/Makefile.am
--- a/libpurple/ciphers/Makefile.am
+++ b/libpurple/ciphers/Makefile.am
@@ -1,10 +1,20 @@
 noinst_LTLIBRARIES=libpurple-ciphers.la
+# XXX: cipher.lo won't be updated after a change in cipher files
+
+if USE_NSS
+AES_SOURCE = aes.c
+endif
+if USE_GNUTLS
+AES_SOURCE = aes.c
+endif
 
 libpurple_ciphers_la_SOURCES=\
+	$(AES_SOURCE) \
 	des.c \
 	gchecksum.c \
 	hmac.c \
 	md4.c \
+	pbkdf2.c \
 	rc4.c
 
 noinst_HEADERS =\
@@ -16,4 +26,6 @@
 	$(INTGG_CFLAGS) \
 	$(AM_CFLAGS) \
 	$(GLIB_CFLAGS) \
-	$(DEBUG_CFLAGS)
+	$(DEBUG_CFLAGS) \
+	$(GNUTLS_CFLAGS) \
+	$(NSS_CFLAGS)
diff --git a/libpurple/ciphers/aes.c b/libpurple/ciphers/aes.c
new file mode 100644
--- /dev/null
+++ b/libpurple/ciphers/aes.c
@@ -0,0 +1,566 @@
+/*
+ * 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
+ *
+ * Written by Tomek Wasilczyk <tomkiewicz@cpw.pidgin.im>
+ */
+
+#include "internal.h"
+#include "cipher.h"
+#include "ciphers.h"
+#include "debug.h"
+
+#if defined(HAVE_GNUTLS)
+#  define PURPLE_AES_USE_GNUTLS 1
+#  include <gnutls/gnutls.h>
+#  include <gnutls/crypto.h>
+#elif defined(HAVE_NSS)
+#  define PURPLE_AES_USE_NSS 1
+#  include <nss.h>
+#  include <pk11pub.h>
+#  include <prerror.h>
+#else
+#  error "No GnuTLS or NSS support"
+#endif
+
+/* 128bit */
+#define PURPLE_AES_BLOCK_SIZE 16
+
+typedef struct
+{
+	guchar iv[PURPLE_AES_BLOCK_SIZE];
+	guchar key[32];
+	guint key_size;
+	gboolean failure;
+} AESContext;
+
+typedef gboolean (*purple_aes_crypt_func)(
+	const guchar *input, guchar *output, size_t len,
+	guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size);
+
+static void
+purple_aes_init(PurpleCipherContext *context, void *extra)
+{
+	AESContext *ctx_data;
+
+	ctx_data = g_new0(AESContext, 1);
+	purple_cipher_context_set_data(context, ctx_data);
+
+	purple_cipher_context_reset(context, extra);
+}
+
+static void
+purple_aes_uninit(PurpleCipherContext *context)
+{
+	AESContext *ctx_data;
+
+	purple_cipher_context_reset(context, NULL);
+
+	ctx_data = purple_cipher_context_get_data(context);
+	g_free(ctx_data);
+	purple_cipher_context_set_data(context, NULL);
+}
+
+static void
+purple_aes_reset(PurpleCipherContext *context, void *extra)
+{
+	AESContext *ctx_data = purple_cipher_context_get_data(context);
+
+	g_return_if_fail(ctx_data != NULL);
+
+	memset(ctx_data->iv, 0, sizeof(ctx_data->iv));
+	memset(ctx_data->key, 0, sizeof(ctx_data->key));
+	ctx_data->key_size = 32; /* 256bit */
+	ctx_data->failure = FALSE;
+}
+
+static void
+purple_aes_set_option(PurpleCipherContext *context, const gchar *name,
+	void *value)
+{
+	AESContext *ctx_data = purple_cipher_context_get_data(context);
+
+	purple_debug_error("cipher-aes", "set_option not supported\n");
+	ctx_data->failure = TRUE;
+}
+
+static void
+purple_aes_set_iv(PurpleCipherContext *context, guchar *iv, size_t len)
+{
+	AESContext *ctx_data = purple_cipher_context_get_data(context);
+
+	if ((len > 0 && iv == NULL) ||
+		(len != 0 && len != sizeof(ctx_data->iv))) {
+		purple_debug_error("cipher-aes", "invalid IV length\n");
+		ctx_data->failure = TRUE;
+		return;
+	}
+
+	if (len == 0)
+		memset(ctx_data->iv, 0, sizeof(ctx_data->iv));
+	else
+		memcpy(ctx_data->iv, iv, len);
+}
+
+static void
+purple_aes_set_key(PurpleCipherContext *context, const guchar *key, size_t len)
+{
+	AESContext *ctx_data = purple_cipher_context_get_data(context);
+
+	if ((len > 0 && key == NULL) ||
+		(len != 0 && len != 16 && len != 24 && len != 32)) {
+		purple_debug_error("cipher-aes", "invalid key length\n");
+		ctx_data->failure = TRUE;
+		return;
+	}
+
+	ctx_data->key_size = len;
+	memset(ctx_data->key, 0, sizeof(ctx_data->key));
+	if (len > 0)
+		memcpy(ctx_data->key, key, len);
+}
+
+static guchar *
+purple_aes_pad_pkcs7(const guchar input[], size_t in_len, size_t *out_len)
+{
+	int padding_len, total_len;
+	guchar *padded;
+
+	g_return_val_if_fail(input != NULL, NULL);
+	g_return_val_if_fail(out_len != NULL, NULL);
+
+	padding_len = PURPLE_AES_BLOCK_SIZE - (in_len % PURPLE_AES_BLOCK_SIZE);
+	total_len = in_len + padding_len;
+	g_assert((total_len % PURPLE_AES_BLOCK_SIZE) == 0);
+
+	padded = g_new(guchar, total_len);
+	*out_len = total_len;
+
+	memcpy(padded, input, in_len);
+	memset(padded + in_len, padding_len, padding_len);
+
+	return padded;
+}
+
+static ssize_t
+purple_aes_unpad_pkcs7(guchar input[], size_t in_len)
+{
+	int padding_len, i;
+	size_t out_len;
+
+	g_return_val_if_fail(input != NULL, -1);
+	g_return_val_if_fail(in_len > 0, -1);
+
+	padding_len = input[in_len - 1];
+	if (padding_len <= 0 || padding_len > PURPLE_AES_BLOCK_SIZE ||
+		padding_len > in_len) {
+		purple_debug_warning("cipher-aes",
+			"Invalid padding length: %d (total %d) - "
+			"most probably, the key was invalid\n",
+			padding_len, in_len);
+		return -1;
+	}
+
+	out_len = in_len - padding_len;
+	for (i = 0; i < padding_len; i++) {
+		if (input[out_len + i] != padding_len) {
+			purple_debug_warning("cipher-aes",
+				"Padding doesn't match at pos %d (found %02x, "
+				"expected %02x) - "
+				"most probably, the key was invalid\n",
+				i, input[out_len + i], padding_len);
+			return -1;
+		}
+	}
+
+	memset(input + out_len, 0, padding_len);
+	return out_len;
+}
+
+#ifdef PURPLE_AES_USE_GNUTLS
+
+static gnutls_cipher_hd_t
+purple_aes_crypt_gnutls_init(guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32],
+	guint key_size)
+{
+	gnutls_cipher_hd_t handle;
+	gnutls_cipher_algorithm_t algorithm;
+	gnutls_datum_t key_info, iv_info;
+	int ret;
+
+	if (key_size == 16)
+		algorithm = GNUTLS_CIPHER_AES_128_CBC;
+	else if (key_size == 24)
+		algorithm = GNUTLS_CIPHER_AES_192_CBC;
+	else if (key_size == 32)
+		algorithm = GNUTLS_CIPHER_AES_256_CBC;
+	else
+		g_return_val_if_reached(NULL);
+
+	key_info.data = key;
+	key_info.size = key_size;
+
+	iv_info.data = iv;
+	iv_info.size = PURPLE_AES_BLOCK_SIZE;
+
+	ret = gnutls_cipher_init(&handle, algorithm, &key_info, &iv_info);
+	if (ret != 0) {
+		purple_debug_error("cipher-aes",
+			"gnutls_cipher_init failed: %d\n", ret);
+		return NULL;
+	}
+
+	return handle;
+}
+
+static gboolean
+purple_aes_encrypt_gnutls(const guchar *input, guchar *output, size_t len,
+	guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size)
+{
+	gnutls_cipher_hd_t handle;
+	int ret;
+
+	handle = purple_aes_crypt_gnutls_init(iv, key, key_size);
+	if (handle == NULL)
+		return FALSE;
+
+	ret = gnutls_cipher_encrypt2(handle, input, len, output, len);
+	gnutls_cipher_deinit(handle);
+
+	if (ret != 0) {
+		purple_debug_error("cipher-aes",
+			"gnutls_cipher_encrypt2 failed: %d\n", ret);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+purple_aes_decrypt_gnutls(const guchar *input, guchar *output, size_t len,
+	guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size)
+{
+	gnutls_cipher_hd_t handle;
+	int ret;
+
+	handle = purple_aes_crypt_gnutls_init(iv, key, key_size);
+	if (handle == NULL)
+		return FALSE;
+
+	ret = gnutls_cipher_decrypt2(handle, input, len, output, len);
+	gnutls_cipher_deinit(handle);
+
+	if (ret != 0) {
+		purple_debug_error("cipher-aes",
+			"gnutls_cipher_decrypt2 failed: %d\n", ret);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+#endif /* PURPLE_AES_USE_GNUTLS */
+
+#ifdef PURPLE_AES_USE_NSS
+
+typedef struct
+{
+	PK11SlotInfo *slot;
+	PK11SymKey *sym_key;
+	SECItem *sec_param;
+	PK11Context *enc_context;
+} purple_aes_encrypt_nss_context;
+
+static void
+purple_aes_encrypt_nss_context_cleanup(purple_aes_encrypt_nss_context *ctx)
+{
+	g_return_if_fail(ctx != NULL);
+
+	if (ctx->enc_context != NULL)
+		PK11_DestroyContext(ctx->enc_context, TRUE);
+	if (ctx->sec_param != NULL)
+		SECITEM_FreeItem(ctx->sec_param, TRUE);
+	if (ctx->sym_key != NULL)
+		PK11_FreeSymKey(ctx->sym_key);
+	if (ctx->slot != NULL)
+		PK11_FreeSlot(ctx->slot);
+
+	memset(ctx, 0, sizeof(purple_aes_encrypt_nss_context));
+}
+
+static gboolean
+purple_aes_crypt_nss(const guchar *input, guchar *output, size_t len,
+	guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size,
+	CK_ATTRIBUTE_TYPE operation)
+{
+	purple_aes_encrypt_nss_context ctx;
+	CK_MECHANISM_TYPE cipher_mech = CKM_AES_CBC;
+	SECItem key_item, iv_item;
+	SECStatus ret;
+	int outlen = 0;
+	unsigned int outlen_tmp = 0;
+
+	memset(&ctx, 0, sizeof(purple_aes_encrypt_nss_context));
+
+	if (NSS_NoDB_Init(NULL) != SECSuccess) {
+		purple_debug_error("cipher-aes",
+			"NSS_NoDB_Init failed: %d\n", PR_GetError());
+		return FALSE;
+	}
+
+	ctx.slot = PK11_GetBestSlot(cipher_mech, NULL);
+	if (ctx.slot == NULL) {
+		purple_debug_error("cipher-aes",
+			"PK11_GetBestSlot failed: %d\n", PR_GetError());
+		return FALSE;
+	}
+
+	key_item.type = siBuffer;
+	key_item.data = key;
+	key_item.len = key_size;
+	ctx.sym_key = PK11_ImportSymKey(ctx.slot, cipher_mech,
+		PK11_OriginUnwrap, CKA_ENCRYPT, &key_item, NULL);
+	if (ctx.sym_key == NULL) {
+		purple_debug_error("cipher-aes",
+			"PK11_ImportSymKey failed: %d\n", PR_GetError());
+		purple_aes_encrypt_nss_context_cleanup(&ctx);
+		return FALSE;
+	}
+
+	iv_item.type = siBuffer;
+	iv_item.data = iv;
+	iv_item.len = PURPLE_AES_BLOCK_SIZE;
+	ctx.sec_param = PK11_ParamFromIV(cipher_mech, &iv_item);
+	if (ctx.sec_param == NULL) {
+		purple_debug_error("cipher-aes",
+			"PK11_ParamFromIV failed: %d\n", PR_GetError());
+		purple_aes_encrypt_nss_context_cleanup(&ctx);
+		return FALSE;
+	}
+
+	ctx.enc_context = PK11_CreateContextBySymKey(cipher_mech, operation,
+		ctx.sym_key, ctx.sec_param);
+	if (ctx.enc_context == NULL) {
+		purple_debug_error("cipher-aes",
+			"PK11_CreateContextBySymKey failed: %d\n",
+				PR_GetError());
+		purple_aes_encrypt_nss_context_cleanup(&ctx);
+		return FALSE;
+	}
+
+	ret = PK11_CipherOp(ctx.enc_context, output, &outlen, len, input, len);
+	if (ret != SECSuccess) {
+		purple_debug_error("cipher-aes",
+			"PK11_CipherOp failed: %d\n", PR_GetError());
+		purple_aes_encrypt_nss_context_cleanup(&ctx);
+		return FALSE;
+	}
+
+	ret = PK11_DigestFinal(ctx.enc_context, output + outlen, &outlen_tmp,
+		len - outlen);
+	if (ret != SECSuccess) {
+		purple_debug_error("cipher-aes",
+			"PK11_DigestFinal failed: %d\n", PR_GetError());
+		purple_aes_encrypt_nss_context_cleanup(&ctx);
+		return FALSE;
+	}
+
+	purple_aes_encrypt_nss_context_cleanup(&ctx);
+
+	outlen += outlen_tmp;
+	if (outlen != len) {
+		purple_debug_error("cipher-aes",
+			"resulting length doesn't match: %d (expected: %d)\n",
+			outlen, len);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+purple_aes_encrypt_nss(const guchar *input, guchar *output, size_t len,
+	guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size)
+{
+	return purple_aes_crypt_nss(input, output, len, iv, key, key_size,
+		CKA_ENCRYPT);
+}
+
+static gboolean
+purple_aes_decrypt_nss(const guchar *input, guchar *output, size_t len,
+	guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size)
+{
+	return purple_aes_crypt_nss(input, output, len, iv, key, key_size,
+		CKA_DECRYPT);
+}
+
+#endif /* PURPLE_AES_USE_NSS */
+
+static ssize_t
+purple_aes_encrypt(PurpleCipherContext *context, const guchar input[],
+	size_t in_len, guchar output[], size_t out_size)
+{
+	AESContext *ctx_data = purple_cipher_context_get_data(context);
+	purple_aes_crypt_func encrypt_func;
+	guchar *input_padded;
+	size_t out_len = 0;
+	gboolean succ;
+
+	if (ctx_data->failure)
+		return -1;
+
+	input_padded = purple_aes_pad_pkcs7(input, in_len, &out_len);
+
+	if (out_len > out_size) {
+		purple_debug_error("cipher-aes", "Output buffer too small\n");
+		memset(input_padded, 0, out_len);
+		g_free(input_padded);
+		return -1;
+	}
+
+#if defined(PURPLE_AES_USE_GNUTLS)
+	encrypt_func = purple_aes_encrypt_gnutls;
+#elif defined(PURPLE_AES_USE_NSS)
+	encrypt_func = purple_aes_encrypt_nss;
+#else
+#  error "No matching encrypt_func"
+#endif
+
+	succ = encrypt_func(input_padded, output, out_len, ctx_data->iv,
+		ctx_data->key, ctx_data->key_size);
+
+	memset(input_padded, 0, out_len);
+	g_free(input_padded);
+
+	if (!succ) {
+		memset(output, 0, out_len);
+		return -1;
+	}
+
+	return out_len;
+}
+
+static ssize_t
+purple_aes_decrypt(PurpleCipherContext *context, const guchar input[],
+	size_t in_len, guchar output[], size_t out_size)
+{
+	AESContext *ctx_data = purple_cipher_context_get_data(context);
+	purple_aes_crypt_func decrypt_func;
+	gboolean succ;
+	ssize_t out_len;
+
+	if (ctx_data->failure)
+		return -1;
+
+	if (in_len > out_size) {
+		purple_debug_error("cipher-aes", "Output buffer too small\n");
+		return -1;
+	}
+
+	if ((in_len % PURPLE_AES_BLOCK_SIZE) != 0 || in_len == 0) {
+		purple_debug_error("cipher-aes", "Malformed data\n");
+		return -1;
+	}
+
+#if defined(PURPLE_AES_USE_GNUTLS)
+	decrypt_func = purple_aes_decrypt_gnutls;
+#elif defined(PURPLE_AES_USE_NSS)
+	decrypt_func = purple_aes_decrypt_nss;
+#else
+#  error "No matching encrypt_func"
+#endif
+
+	succ = decrypt_func(input, output, in_len, ctx_data->iv, ctx_data->key,
+		ctx_data->key_size);
+
+	if (!succ) {
+		memset(output, 0, in_len);
+		return -1;
+	}
+
+	out_len = purple_aes_unpad_pkcs7(output, in_len);
+	if (out_len < 0) {
+		memset(output, 0, in_len);
+		return -1;
+	}
+
+	return out_len;
+}
+
+static size_t
+purple_aes_get_key_size(PurpleCipherContext *context)
+{
+	AESContext *ctx_data = purple_cipher_context_get_data(context);
+
+	return ctx_data->key_size;
+}
+
+static void
+purple_aes_set_batch_mode(PurpleCipherContext *context,
+	PurpleCipherBatchMode mode)
+{
+	AESContext *ctx_data = purple_cipher_context_get_data(context);
+
+	if (mode == PURPLE_CIPHER_BATCH_MODE_CBC)
+		return;
+
+	purple_debug_error("cipher-aes", "unsupported batch mode\n");
+	ctx_data->failure = TRUE;
+}
+
+static PurpleCipherBatchMode
+purple_aes_get_batch_mode(PurpleCipherContext *context)
+{
+	return PURPLE_CIPHER_BATCH_MODE_CBC;
+}
+
+static size_t
+purple_aes_get_block_size(PurpleCipherContext *context)
+{
+	return PURPLE_AES_BLOCK_SIZE;
+}
+
+static PurpleCipherOps AESOps = {
+	purple_aes_set_option,     /* set_option */
+	NULL,                      /* get_option */
+	purple_aes_init,           /* init */
+	purple_aes_reset,          /* reset */
+	NULL,                      /* reset_state */
+	purple_aes_uninit,         /* uninit */
+	purple_aes_set_iv,         /* set_iv */
+	NULL,                      /* append */
+	NULL,                      /* digest */
+	NULL,                      /* get_digest_size */
+	purple_aes_encrypt,        /* encrypt */
+	purple_aes_decrypt,        /* decrypt */
+	NULL,                      /* set_salt */
+	NULL,                      /* get_salt_size */
+	purple_aes_set_key,        /* set_key */
+	purple_aes_get_key_size,   /* get_key_size */
+	purple_aes_set_batch_mode, /* set_batch_mode */
+	purple_aes_get_batch_mode, /* get_batch_mode */
+	purple_aes_get_block_size, /* get_block_size */
+	NULL, NULL, NULL, NULL     /* reserved */
+};
+
+PurpleCipherOps *
+purple_aes_cipher_get_ops(void) {
+	return &AESOps;
+}
diff --git a/libpurple/ciphers/ciphers.h b/libpurple/ciphers/ciphers.h
--- a/libpurple/ciphers/ciphers.h
+++ b/libpurple/ciphers/ciphers.h
@@ -19,6 +19,9 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
  */
 
+/* aes.c */
+PurpleCipherOps * purple_aes_cipher_get_ops(void);
+
 /* des.c */
 PurpleCipherOps * purple_des_cipher_get_ops(void);
 PurpleCipherOps * purple_des3_cipher_get_ops(void);
@@ -34,5 +37,30 @@
 /* md4.c */
 PurpleCipherOps * purple_md4_cipher_get_ops(void);
 
+/* pbkdf2.c */
+PurpleCipherOps * purple_pbkdf2_cipher_get_ops(void);
+
 /* rc4.c */
 PurpleCipherOps * purple_rc4_cipher_get_ops(void);
+
+static inline void purple_ciphers_register_all(void)
+{
+#if defined(HAVE_GNUTLS) || defined(HAVE_NSS)
+	purple_ciphers_register_cipher("aes", purple_aes_cipher_get_ops());
+#endif
+
+	purple_ciphers_register_cipher("des", purple_des_cipher_get_ops());
+	purple_ciphers_register_cipher("des3", purple_des3_cipher_get_ops());
+
+	purple_ciphers_register_cipher("md5", purple_md5_cipher_get_ops());
+	purple_ciphers_register_cipher("sha1", purple_sha1_cipher_get_ops());
+	purple_ciphers_register_cipher("sha256", purple_sha256_cipher_get_ops());
+
+	purple_ciphers_register_cipher("hmac", purple_hmac_cipher_get_ops());
+
+	purple_ciphers_register_cipher("md4", purple_md4_cipher_get_ops());
+
+	purple_ciphers_register_cipher("pbkdf2", purple_pbkdf2_cipher_get_ops());
+
+	purple_ciphers_register_cipher("rc4", purple_rc4_cipher_get_ops());
+}
diff --git a/libpurple/ciphers/pbkdf2.c b/libpurple/ciphers/pbkdf2.c
new file mode 100644
--- /dev/null
+++ b/libpurple/ciphers/pbkdf2.c
@@ -0,0 +1,323 @@
+/*
+ * 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
+ *
+ * Written by Tomek Wasilczyk <tomkiewicz@cpw.pidgin.im>
+ */
+
+#include "internal.h"
+#include "cipher.h"
+#include "ciphers.h"
+#include "debug.h"
+
+/* 1024bit */
+#define PBKDF2_HASH_MAX_LEN 128
+
+typedef struct
+{
+	gchar *hash_func;
+	guint iter_count;
+	size_t out_len;
+
+	guchar *salt;
+	size_t salt_len;
+	guchar *passphrase;
+	size_t passphrase_len;
+} Pbkdf2Context;
+
+static void
+purple_pbkdf2_init(PurpleCipherContext *context, void *extra)
+{
+	Pbkdf2Context *ctx_data;
+
+	ctx_data = g_new0(Pbkdf2Context, 1);
+	purple_cipher_context_set_data(context, ctx_data);
+
+	purple_cipher_context_reset(context, extra);
+}
+
+static void
+purple_pbkdf2_uninit(PurpleCipherContext *context)
+{
+	Pbkdf2Context *ctx_data;
+
+	purple_cipher_context_reset(context, NULL);
+
+	ctx_data = purple_cipher_context_get_data(context);
+	g_free(ctx_data);
+	purple_cipher_context_set_data(context, NULL);
+}
+
+static void
+purple_pbkdf2_reset(PurpleCipherContext *context, void *extra)
+{
+	Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+
+	g_return_if_fail(ctx_data != NULL);
+
+	g_free(ctx_data->hash_func);
+	ctx_data->hash_func = NULL;
+	ctx_data->iter_count = 1;
+	ctx_data->out_len = 256;
+
+	purple_cipher_context_reset_state(context, extra);
+}
+
+static void
+purple_pbkdf2_reset_state(PurpleCipherContext *context, void *extra)
+{
+	Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+
+	g_return_if_fail(ctx_data != NULL);
+
+	purple_cipher_context_set_salt(context, NULL, 0);
+	purple_cipher_context_set_key(context, NULL, 0);
+}
+
+static void
+purple_pbkdf2_set_option(PurpleCipherContext *context, const gchar *name,
+	void *value)
+{
+	Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+
+	g_return_if_fail(ctx_data != NULL);
+
+	if (g_strcmp0(name, "hash") == 0) {
+		g_free(ctx_data->hash_func);
+		ctx_data->hash_func = g_strdup(value);
+		return;
+	}
+
+	if (g_strcmp0(name, "iter_count") == 0) {
+		ctx_data->iter_count = GPOINTER_TO_UINT(value);
+		return;
+	}
+
+	if (g_strcmp0(name, "out_len") == 0) {
+		ctx_data->out_len = GPOINTER_TO_UINT(value);
+		return;
+	}
+
+	purple_debug_warning("pbkdf2", "Unknown option: %s\n",
+		name ? name : "(null)");
+}
+
+static void *
+purple_pbkdf2_get_option(PurpleCipherContext *context, const gchar *name)
+{
+	Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+
+	g_return_val_if_fail(ctx_data != NULL, NULL);
+
+	if (g_strcmp0(name, "hash") == 0)
+		return ctx_data->hash_func;
+
+	if (g_strcmp0(name, "iter_count") == 0)
+		return GUINT_TO_POINTER(ctx_data->iter_count);
+
+	if (g_strcmp0(name, "out_len") == 0)
+		return GUINT_TO_POINTER(ctx_data->out_len);
+
+	purple_debug_warning("pbkdf2", "Unknown option: %s\n",
+		name ? name : "(null)");
+	return NULL;
+}
+
+static size_t
+purple_pbkdf2_get_digest_size(PurpleCipherContext *context)
+{
+	Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+
+	g_return_val_if_fail(ctx_data != NULL, 0);
+
+	return ctx_data->out_len;
+}
+
+static void
+purple_pbkdf2_set_salt(PurpleCipherContext *context, const guchar *salt, size_t len)
+{
+	Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+
+	g_return_if_fail(ctx_data != NULL);
+
+	g_free(ctx_data->salt);
+	ctx_data->salt = NULL;
+	ctx_data->salt_len = 0;
+
+	if (len == 0)
+		return;
+	g_return_if_fail(salt != NULL);
+
+	ctx_data->salt = g_memdup(salt, len);
+	ctx_data->salt_len = len;
+}
+
+static void
+purple_pbkdf2_set_key(PurpleCipherContext *context, const guchar *key,
+	size_t len)
+{
+	Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+
+	g_return_if_fail(ctx_data != NULL);
+
+	if (ctx_data->passphrase != NULL) {
+		memset(ctx_data->passphrase, 0, ctx_data->passphrase_len);
+		g_free(ctx_data->passphrase);
+		ctx_data->passphrase = NULL;
+	}
+	ctx_data->passphrase_len = 0;
+
+	if (len == 0)
+		return;
+	g_return_if_fail(key != NULL);
+
+	ctx_data->passphrase = g_memdup(key, len);
+	ctx_data->passphrase_len = len;
+}
+
+/* inspired by gnutls 3.1.10, pbkdf2-sha1.c */
+static gboolean
+purple_pbkdf2_digest(PurpleCipherContext *context, guchar digest[], size_t len)
+{
+	Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+	guchar halfkey[PBKDF2_HASH_MAX_LEN], halfkey_hash[PBKDF2_HASH_MAX_LEN];
+	guint halfkey_len, halfkey_count, halfkey_pad, halfkey_no;
+	guchar *salt_ext;
+	size_t salt_ext_len;
+	guint iter_no;
+	PurpleCipherContext *hash;
+
+	g_return_val_if_fail(ctx_data != NULL, FALSE);
+	g_return_val_if_fail(digest != NULL, FALSE);
+	g_return_val_if_fail(len >= ctx_data->out_len, FALSE);
+
+	g_return_val_if_fail(ctx_data->hash_func != NULL, FALSE);
+	g_return_val_if_fail(ctx_data->iter_count > 0, FALSE);
+	g_return_val_if_fail(ctx_data->passphrase != NULL ||
+		ctx_data->passphrase_len == 0, FALSE);
+	g_return_val_if_fail(ctx_data->salt != NULL || ctx_data->salt_len == 0,
+		FALSE);
+	g_return_val_if_fail(ctx_data->out_len > 0, FALSE);
+	g_return_val_if_fail(ctx_data->out_len < 0xFFFFFFFFU, FALSE);
+
+	salt_ext_len = ctx_data->salt_len + 4;
+
+	hash = purple_cipher_context_new_by_name("hmac", NULL);
+	if (hash == NULL) {
+		purple_debug_error("pbkdf2", "Couldn't create new hmac "
+			"context\n");
+		return FALSE;
+	}
+	purple_cipher_context_set_option(hash, "hash",
+		(void*)ctx_data->hash_func);
+	purple_cipher_context_set_key(hash, (const guchar*)ctx_data->passphrase,
+		ctx_data->passphrase_len);
+
+	halfkey_len = purple_cipher_context_get_digest_size(hash);
+	if (halfkey_len <= 0 || halfkey_len > PBKDF2_HASH_MAX_LEN) {
+		purple_debug_error("pbkdf2", "Unsupported hash function: %s "
+			"(digest size: %d)\n",
+			ctx_data->hash_func ? ctx_data->hash_func : "(null)",
+			halfkey_len);
+		return FALSE;
+	}
+
+	halfkey_count = ((ctx_data->out_len - 1) / halfkey_len) + 1;
+	halfkey_pad = ctx_data->out_len - (halfkey_count - 1) * halfkey_len;
+
+	salt_ext = g_new(guchar, salt_ext_len);
+	memcpy(salt_ext, ctx_data->salt, ctx_data->salt_len);
+
+	for (halfkey_no = 1; halfkey_no <= halfkey_count; halfkey_no++) {
+		memset(halfkey, 0, halfkey_len);
+
+		for (iter_no = 1; iter_no <= ctx_data->iter_count; iter_no++) {
+			int i;
+
+			purple_cipher_context_reset_state(hash, NULL);
+
+			if (iter_no == 1) {
+				salt_ext[salt_ext_len - 4] =
+					(halfkey_no & 0xff000000) >> 24;
+				salt_ext[salt_ext_len - 3] =
+					(halfkey_no & 0x00ff0000) >> 16;
+				salt_ext[salt_ext_len - 2] =
+					(halfkey_no & 0x0000ff00) >> 8;
+				salt_ext[salt_ext_len - 1] =
+					(halfkey_no & 0x000000ff) >> 0;
+
+				purple_cipher_context_append(hash, salt_ext,
+					salt_ext_len);
+			}
+			else
+				purple_cipher_context_append(hash, halfkey_hash,
+					halfkey_len);
+
+			if (!purple_cipher_context_digest(hash, halfkey_hash,
+				halfkey_len)) {
+				purple_debug_error("pbkdf2",
+					"Couldn't retrieve a digest\n");
+				g_free(salt_ext);
+				purple_cipher_context_destroy(hash);
+				return FALSE;
+			}
+
+			for (i = 0; i < halfkey_len; i++)
+				halfkey[i] ^= halfkey_hash[i];
+		}
+
+		memcpy(digest + (halfkey_no - 1) * halfkey_len, halfkey,
+			(halfkey_no == halfkey_count) ? halfkey_pad :
+				halfkey_len);
+	}
+
+	g_free(salt_ext);
+	purple_cipher_context_destroy(hash);
+
+	return TRUE;
+}
+
+static PurpleCipherOps PBKDF2Ops = {
+	purple_pbkdf2_set_option,      /* set_option */
+	purple_pbkdf2_get_option,      /* get_option */
+	purple_pbkdf2_init,            /* init */
+	purple_pbkdf2_reset,           /* reset */
+	purple_pbkdf2_reset_state,     /* reset_state */
+	purple_pbkdf2_uninit,          /* uninit */
+	NULL,                          /* set_iv */
+	NULL,                          /* append */
+	purple_pbkdf2_digest,          /* digest */
+	purple_pbkdf2_get_digest_size, /* get_digest_size */
+	NULL,                          /* encrypt */
+	NULL,                          /* decrypt */
+	purple_pbkdf2_set_salt,        /* set_salt */
+	NULL,                          /* get_salt_size */
+	purple_pbkdf2_set_key,         /* set_key */
+	NULL,                          /* get_key_size */
+	NULL,                          /* set_batch_mode */
+	NULL,                          /* get_batch_mode */
+	NULL,                          /* get_block_size */
+	NULL, NULL, NULL, NULL         /* reserved */
+};
+
+PurpleCipherOps *
+purple_pbkdf2_cipher_get_ops(void) {
+	return &PBKDF2Ops;
+}
diff --git a/libpurple/connection.c b/libpurple/connection.c
--- a/libpurple/connection.c
+++ b/libpurple/connection.c
@@ -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;
 }
 
diff --git a/libpurple/core.c b/libpurple/core.c
--- a/libpurple/core.c
+++ b/libpurple/core.c
@@ -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();
diff --git a/libpurple/example/nullclient.c b/libpurple/example/nullclient.c
--- a/libpurple/example/nullclient.c
+++ b/libpurple/example/nullclient.c
@@ -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);
diff --git a/libpurple/keyring.c b/libpurple/keyring.c
new file mode 100644
--- /dev/null
+++ b/libpurple/keyring.c
@@ -0,0 +1,1357 @@
+/**
+ * @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;
+	PurpleKeyringImportPassword import_password;
+	PurpleKeyringExportPassword export_password;
+	PurpleKeyringReadSettings read_settings;
+	PurpleKeyringApplySettings apply_settings;
+
+	gboolean is_closing;
+	gboolean is_cancelling;
+	gboolean close_after_cancel;
+};
+
+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,
+			_("An 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);
+
+	if (keyring->is_cancelling) {
+		keyring->close_after_cancel = TRUE;
+		return;
+	}
+	if (keyring->is_closing)
+		return;
+	keyring->is_closing = TRUE;
+
+	close_cb = purple_keyring_get_close_keyring(keyring);
+
+	if (close_cb != NULL)
+		close_cb();
+
+	keyring->is_closing = FALSE;
+}
+
+static void
+purple_keyring_cancel_requests(PurpleKeyring *keyring)
+{
+	PurpleKeyringCancelRequests cancel_cb;
+
+	g_return_if_fail(keyring != NULL);
+
+	if (keyring->is_cancelling)
+		return;
+	keyring->is_cancelling = TRUE;
+
+	cancel_cb = purple_keyring_get_cancel_requests(keyring);
+
+	if (cancel_cb != NULL)
+		cancel_cb();
+
+	keyring->is_cancelling = FALSE;
+
+	if (keyring->close_after_cancel) {
+		keyring->close_after_cancel = FALSE;
+		purple_keyring_close(keyring);
+	}
+}
+
+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);
+}
+
+PurpleRequestFields *
+purple_keyring_read_settings(void)
+{
+	PurpleKeyring *inuse;
+	PurpleKeyringReadSettings read_settings;
+
+	if (purple_keyring_is_quitting || current_change_tracker != NULL) {
+		purple_debug_error("keyring", "Cannot read settngs at the "
+			"moment.\n");
+		return NULL;
+	}
+
+	inuse = purple_keyring_get_inuse();
+	if (inuse == NULL) {
+		purple_debug_error("keyring", "No keyring in use.\n");
+		return NULL;
+	}
+
+	read_settings = purple_keyring_get_read_settings(inuse);
+	if (read_settings == NULL)
+		return NULL;
+	return read_settings();
+}
+
+gboolean
+purple_keyring_apply_settings(void *notify_handle, PurpleRequestFields *fields)
+{
+	PurpleKeyring *inuse;
+	PurpleKeyringApplySettings apply_settings;
+
+	g_return_val_if_fail(fields != NULL, FALSE);
+
+	if (purple_keyring_is_quitting || current_change_tracker != NULL) {
+		purple_debug_error("keyring", "Cannot apply settngs at the "
+			"moment.\n");
+		return FALSE;
+	}
+
+	inuse = purple_keyring_get_inuse();
+	if (inuse == NULL) {
+		purple_debug_error("keyring", "No keyring in use.\n");
+		return FALSE;
+	}
+
+	apply_settings = purple_keyring_get_apply_settings(inuse);
+	if (apply_settings == NULL) {
+		purple_debug_warning("keyring", "Applying settings not "
+			"supported.\n");
+		return FALSE;
+	}
+	return apply_settings(notify_handle, fields);
+}
+
+/**************************************************************************/
+/* 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;
+}
+
+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;
+}
+
+PurpleKeyringReadSettings
+purple_keyring_get_read_settings(const PurpleKeyring *keyring)
+{
+	g_return_val_if_fail(keyring != NULL, NULL);
+
+	return keyring->read_settings;
+}
+
+PurpleKeyringApplySettings
+purple_keyring_get_apply_settings(const PurpleKeyring *keyring)
+{
+	g_return_val_if_fail(keyring != NULL, NULL);
+
+	return keyring->apply_settings;
+}
+
+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_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;
+}
+
+void
+purple_keyring_set_read_settings(PurpleKeyring *keyring,
+PurpleKeyringReadSettings read_settings)
+{
+	g_return_if_fail(keyring != NULL);
+
+	keyring->read_settings = read_settings;
+}
+
+void
+purple_keyring_set_apply_settings(PurpleKeyring *keyring,
+PurpleKeyringApplySettings apply_settings)
+{
+	g_return_if_fail(keyring != NULL);
+
+	keyring->apply_settings = apply_settings;
+}
+
+
+/**************************************************************************/
+/* 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()
+{
+	if (current_change_tracker != NULL) {
+		PurpleKeyringChangeTracker *tracker = current_change_tracker;
+		tracker->abort = TRUE;
+		if (tracker->old)
+			purple_keyring_cancel_requests(tracker->old);
+		if (current_change_tracker == tracker && tracker->new)
+			purple_keyring_cancel_requests(tracker->new);
+	}
+
+	purple_keyring_is_quitting = TRUE;
+	if (purple_keyring_inuse != NULL)
+		purple_keyring_cancel_requests(purple_keyring_inuse);
+}
+
+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;
+}
diff --git a/libpurple/keyring.h b/libpurple/keyring.h
new file mode 100644
--- /dev/null
+++ b/libpurple/keyring.h
@@ -0,0 +1,548 @@
+/**
+ * @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"
+#include "request.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);
+
+/**
+ * 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);
+
+/**
+ * Read keyring settings.
+ *
+ * @return New copy of current settings (must be free'd with
+ *         purple_request_fields_destroy).
+ */
+typedef PurpleRequestFields * (*PurpleKeyringReadSettings)(void);
+
+/**
+ * Applies modified keyring settings.
+ *
+ * @param notify_handle A handle that can be passed to purple_notify_message.
+ * @param fields        Modified settings (originally taken from
+ *                      PurpleKeyringReadSettings).
+ * @return TRUE, if succeeded, FALSE otherwise.
+ */
+typedef gboolean (*PurpleKeyringApplySettings)(void *notify_handle,
+	PurpleRequestFields *fields);
+
+/*@}*/
+
+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);
+
+/**
+ * Reads settings from current keyring.
+ *
+ * @return New copy of current settings (must be free'd with
+ *         purple_request_fields_destroy).
+ */
+PurpleRequestFields *
+purple_keyring_read_settings(void);
+
+/**
+ * Applies modified settings to current keyring.
+ *
+ * @param notify_handle A handle that can be passed to purple_notify_message.
+ * @param fields        Modified settings (originally taken from
+ *                      PurpleKeyringReadSettings).
+ * @return TRUE, if succeeded, FALSE otherwise.
+ */
+gboolean
+purple_keyring_apply_settings(void *notify_handle, PurpleRequestFields *fields);
+
+/*@}*/
+
+/**************************************************************************/
+/** @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);
+
+PurpleKeyringImportPassword
+purple_keyring_get_import_password(const PurpleKeyring *keyring);
+
+PurpleKeyringExportPassword
+purple_keyring_get_export_password(const PurpleKeyring *keyring);
+
+PurpleKeyringReadSettings
+purple_keyring_get_read_settings(const PurpleKeyring *keyring);
+
+PurpleKeyringApplySettings
+purple_keyring_get_apply_settings(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_import_password(PurpleKeyring *keyring,
+	PurpleKeyringImportPassword import_password);
+
+void
+purple_keyring_set_export_password(PurpleKeyring *keyring,
+	PurpleKeyringExportPassword export_password);
+
+void
+purple_keyring_set_read_settings(PurpleKeyring *keyring,
+PurpleKeyringReadSettings read_settings);
+
+void
+purple_keyring_set_apply_settings(PurpleKeyring *keyring,
+PurpleKeyringApplySettings apply_settings);
+
+/*@}*/
+
+/**************************************************************************/
+/** @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_ */
diff --git a/libpurple/plugins/Makefile.am b/libpurple/plugins/Makefile.am
--- a/libpurple/plugins/Makefile.am
+++ b/libpurple/plugins/Makefile.am
@@ -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)
 
@@ -146,7 +147,11 @@
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(PLUGIN_CFLAGS) \
-	$(DBUS_CFLAGS)
+	$(DBUS_CFLAGS) \
+	$(NSS_CFLAGS)
+
+PLUGIN_LIBS = \
+	$(NSS_LIBS)
 
 #
 # This part allows people to build their own plugins in here.
diff --git a/libpurple/plugins/Makefile.mingw b/libpurple/plugins/Makefile.mingw
--- a/libpurple/plugins/Makefile.mingw
+++ b/libpurple/plugins/Makefile.mingw
@@ -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_at) $(MAKE) -C $(PERL_PLUGIN) -f $(MINGW_MAKEFILE)
 	$(MAKE_at) $(MAKE) -C $(TCL_PLUGIN) -f $(MINGW_MAKEFILE)
 	$(MAKE_at) $(MAKE) -C $(SSL_PLUGIN) -f $(MINGW_MAKEFILE)
+	$(MAKE_at) $(MAKE) -C $(KEYRING_PLUGIN) -f $(MINGW_MAKEFILE)
 
 install: all $(PURPLE_INSTALL_PLUGINS_DIR)
 	$(MAKE_at) $(MAKE) -C $(PERL_PLUGIN) -f $(MINGW_MAKEFILE) install
 	$(MAKE_at) $(MAKE) -C $(TCL_PLUGIN) -f $(MINGW_MAKEFILE) install
 	$(MAKE_at) $(MAKE) -C $(SSL_PLUGIN) -f $(MINGW_MAKEFILE) install
+	$(MAKE_at) $(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_at) $(MAKE) -C $(PERL_PLUGIN) -f $(MINGW_MAKEFILE) clean
 	$(MAKE_at) $(MAKE) -C $(TCL_PLUGIN) -f $(MINGW_MAKEFILE) clean
 	$(MAKE_at) $(MAKE) -C $(SSL_PLUGIN) -f $(MINGW_MAKEFILE) clean
+	$(MAKE_at) $(MAKE) -C $(KEYRING_PLUGIN) -f $(MINGW_MAKEFILE) clean
 
 include $(PIDGIN_COMMON_TARGETS)
diff --git a/libpurple/plugins/ciphertest.c b/libpurple/plugins/ciphertest.c
--- a/libpurple/plugins/ciphertest.c
+++ b/libpurple/plugins/ciphertest.c
@@ -231,6 +231,343 @@
 }
 
 /**************************************************************************
+ * PBKDF2 stuff
+ **************************************************************************/
+
+#ifdef HAVE_NSS
+#  include <nss.h>
+#  include <secmod.h>
+#  include <pk11func.h>
+#  include <prerror.h>
+#  include <secerr.h>
+#endif
+
+typedef struct {
+	const gchar *hash;
+	const guint iter_count;
+	const gchar *passphrase;
+	const gchar *salt;
+	const guint out_len;
+	const gchar *answer;
+} pbkdf2_test;
+
+pbkdf2_test pbkdf2_tests[] = {
+	{ "sha256", 1, "password", "salt", 32, "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"},
+	{ "sha1", 1, "password", "salt", 32, "0c60c80f961f0e71f3a9b524af6012062fe037a6e0f0eb94fe8fc46bdc637164"},
+	{ "sha1", 1000, "ala ma kota", "", 16, "924dba137b5bcf6d0de84998f3d8e1f9"},
+	{ "sha1", 1, "", "", 32, "1e437a1c79d75be61e91141dae20affc4892cc99abcc3fe753887bccc8920176"},
+	{ "sha256", 100, "some password", "and salt", 1, "c7"},
+	{ "sha1", 10000, "pretty long password W Szczebrzeszynie chrzaszcz brzmi w trzcinie i Szczebrzeszyn z tego slynie", "Grzegorz Brzeczyszczykiewicz", 32, "8cb0cb164f2554733ae02f5751b0e84a88fb385446e85a3991bdcdf1ea11795c"},
+	{ NULL, 0, NULL, NULL, 0, NULL}
+};
+
+#ifdef HAVE_NSS
+
+static gchar*
+cipher_pbkdf2_nss_sha1(const gchar *passphrase, const gchar *salt,
+	guint iter_count, guint out_len)
+{
+	PK11SlotInfo *slot;
+	SECAlgorithmID *algorithm = NULL;
+	PK11SymKey *symkey = NULL;
+	const SECItem *symkey_data = NULL;
+	SECItem salt_item, passphrase_item;
+	guchar *passphrase_buff, *salt_buff;
+	gchar *ret;
+
+	g_return_val_if_fail(passphrase != NULL, NULL);
+	g_return_val_if_fail(iter_count > 0, NULL);
+	g_return_val_if_fail(out_len > 0, NULL);
+
+	NSS_NoDB_Init(NULL);
+
+	slot = PK11_GetBestSlot(PK11_AlgtagToMechanism(SEC_OID_PKCS5_PBKDF2),
+		NULL);
+	if (slot == NULL) {
+		purple_debug_error("cipher-test", "NSS: couldn't get slot: "
+			"%d\n", PR_GetError());
+		return NULL;
+	}
+
+	salt_buff = (guchar*)g_strdup(salt ? salt : "");
+	salt_item.type = siBuffer;
+	salt_item.data = salt_buff;
+	salt_item.len = salt ? strlen(salt) : 0;
+
+	algorithm = PK11_CreatePBEV2AlgorithmID(SEC_OID_PKCS5_PBKDF2,
+		SEC_OID_AES_256_CBC, SEC_OID_HMAC_SHA1, out_len, iter_count,
+		&salt_item);
+	if (algorithm == NULL) {
+		purple_debug_error("cipher-test", "NSS: couldn't create "
+			"algorithm ID: %d\n", PR_GetError());
+		PK11_FreeSlot(slot);
+		g_free(salt_buff);
+		return NULL;
+	}
+
+	passphrase_buff = (guchar*)g_strdup(passphrase);
+	passphrase_item.type = siBuffer;
+	passphrase_item.data = passphrase_buff;
+	passphrase_item.len = strlen(passphrase);
+
+	symkey = PK11_PBEKeyGen(slot, algorithm, &passphrase_item, PR_FALSE,
+		NULL);
+	if (symkey == NULL) {
+		purple_debug_error("cipher-test", "NSS: Couldn't generate key: "
+			"%d\n", PR_GetError());
+		SECOID_DestroyAlgorithmID(algorithm, PR_TRUE);
+		PK11_FreeSlot(slot);
+		g_free(passphrase_buff);
+		g_free(salt_buff);
+		return NULL;
+	}
+
+	if (PK11_ExtractKeyValue(symkey) == SECSuccess)
+		symkey_data = PK11_GetKeyData(symkey);
+
+	if (symkey_data == NULL || symkey_data->data == NULL) {
+		purple_debug_error("cipher-test", "NSS: Couldn't extract key "
+			"value: %d\n", PR_GetError());
+		PK11_FreeSymKey(symkey);
+		SECOID_DestroyAlgorithmID(algorithm, PR_TRUE);
+		PK11_FreeSlot(slot);
+		g_free(passphrase_buff);
+		g_free(salt_buff);
+		return NULL;
+	}
+
+	if (symkey_data->len != out_len) {
+		purple_debug_error("cipher-test", "NSS: Invalid key length: %d "
+			"(should be %d)\n", symkey_data->len, out_len);
+		PK11_FreeSymKey(symkey);
+		SECOID_DestroyAlgorithmID(algorithm, PR_TRUE);
+		PK11_FreeSlot(slot);
+		g_free(passphrase_buff);
+		g_free(salt_buff);
+		return NULL;
+	}
+
+	ret = purple_base16_encode(symkey_data->data, symkey_data->len);
+
+	PK11_FreeSymKey(symkey);
+	SECOID_DestroyAlgorithmID(algorithm, PR_TRUE);
+	PK11_FreeSlot(slot);
+	g_free(passphrase_buff);
+	g_free(salt_buff);
+	return ret;
+}
+
+#endif /* HAVE_NSS */
+
+static void
+cipher_test_pbkdf2(void)
+{
+	PurpleCipherContext *context;
+	int i = 0;
+	gboolean fail = FALSE;
+
+	purple_debug_info("cipher-test", "Running PBKDF2 tests\n");
+
+	context = purple_cipher_context_new_by_name("pbkdf2", NULL);
+
+	while (!fail && pbkdf2_tests[i].answer) {
+		pbkdf2_test *test = &pbkdf2_tests[i];
+		gchar digest[2 * 32 + 1 + 10];
+		gchar *digest_nss = NULL;
+		gboolean ret, skip_nss = FALSE;
+
+		i++;
+
+		purple_debug_info("cipher-test", "Test %02d:\n", i);
+		purple_debug_info("cipher-test",
+			"\tTesting '%s' with salt:'%s' hash:%s iter_count:%d \n",
+			test->passphrase, test->salt, test->hash,
+			test->iter_count);
+
+		purple_cipher_context_set_option(context, "hash", (gpointer)test->hash);
+		purple_cipher_context_set_option(context, "iter_count", GUINT_TO_POINTER(test->iter_count));
+		purple_cipher_context_set_option(context, "out_len", GUINT_TO_POINTER(test->out_len));
+		purple_cipher_context_set_salt(context, (const guchar*)test->salt, test->salt ? strlen(test->salt): 0);
+		purple_cipher_context_set_key(context, (const guchar*)test->passphrase, strlen(test->passphrase));
+
+		ret = purple_cipher_context_digest_to_str(context, digest, sizeof(digest));
+		purple_cipher_context_reset(context, NULL);
+
+		if (!ret) {
+			purple_debug_info("cipher-test", "\tfailed\n");
+			fail = TRUE;
+			continue;
+		}
+
+		if (g_strcmp0(test->hash, "sha1") != 0)
+			skip_nss = TRUE;
+		if (test->out_len != 16 && test->out_len != 32)
+			skip_nss = TRUE;
+
+#ifdef HAVE_NSS
+		if (!skip_nss) {
+			digest_nss = cipher_pbkdf2_nss_sha1(test->passphrase,
+				test->salt, test->iter_count, test->out_len);
+		}
+#else
+		skip_nss = TRUE;
+#endif
+
+		if (!ret) {
+			purple_debug_info("cipher-test", "\tnss test failed\n");
+			fail = TRUE;
+		}
+
+		purple_debug_info("cipher-test", "\tGot:          %s\n", digest);
+		if (digest_nss)
+			purple_debug_info("cipher-test", "\tGot from NSS: %s\n", digest_nss);
+		purple_debug_info("cipher-test", "\tWanted:       %s\n", test->answer);
+
+		if (g_strcmp0(digest, test->answer) == 0 &&
+			(skip_nss || g_strcmp0(digest, digest_nss) == 0)) {
+			purple_debug_info("cipher-test", "\tTest OK\n");
+		}
+		else {
+			purple_debug_info("cipher-test", "\twrong answer\n");
+			fail = TRUE;
+		}
+	}
+
+	purple_cipher_context_destroy(context);
+
+	if (fail)
+		purple_debug_info("cipher-test", "PBKDF2 tests FAILED\n\n");
+	else
+		purple_debug_info("cipher-test", "PBKDF2 tests completed successfully\n\n");
+}
+
+/**************************************************************************
+ * AES stuff
+ **************************************************************************/
+
+typedef struct {
+	const gchar *iv;
+	const gchar *key;
+	const gchar *plaintext;
+	const gchar *cipher;
+} aes_test;
+
+aes_test aes_tests[] = {
+	{ NULL, "000102030405060708090a0b0c0d0e0f", "plaintext", "152e5b950e5f28fafadee9e96fcc59c9" },
+	{ NULL, "000102030405060708090a0b0c0d0e0f", NULL, "954f64f2e4e86e9eee82d20216684899" },
+	{ "01010101010101010101010101010101", "000102030405060708090a0b0c0d0e0f", NULL, "35d14e6d3e3a279cf01e343e34e7ded3" },
+	{ "01010101010101010101010101010101", "000102030405060708090a0b0c0d0e0f", "plaintext", "19d1996e8c098cf3c94bba5dcf5bc57e" },
+	{ "01010101010101010101010101010101", "000102030405060708090a0b0c0d0e0f1011121314151617", "plaintext", "e8fba0deae94f63fe72ae9b8ef128aed" },
+	{ "01010101010101010101010101010101", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "plaintext", "e2dc50f6c60b33ac3b5953b6503cb684" },
+	{ "01010101010101010101010101010101", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "W Szczebrzeszynie chrzaszcz brzmi w trzcinie i Szczebrzeszyn z tego slynie", "8fcc068964e3505f0c2fac61c24592e5c8a9582d3a3264217cf605e9fd1cb056e679e159c4ac3110e8ce6c76c6630d42658c566ba3750c0e6da385b3a9baaa8b3a735b4c1ecaacf58037c8c281e523d2" },
+	{ NULL, NULL, NULL, NULL }
+};
+
+static void
+cipher_test_aes(void)
+{
+	PurpleCipherContext *context;
+	int i = 0;
+	gboolean fail = FALSE;
+
+	purple_debug_info("cipher-test", "Running AES tests\n");
+
+	context = purple_cipher_context_new_by_name("aes", NULL);
+	if (context == NULL) {
+		purple_debug_error("cipher-test", "AES cipher not found\n");
+		fail = TRUE;
+	}
+
+	while (!fail && aes_tests[i].cipher) {
+		aes_test *test = &aes_tests[i];
+		gsize key_size;
+		guchar *key;
+		guchar cipher[1024], decipher[1024];
+		ssize_t cipher_len, decipher_len;
+		gchar *cipher_b16, *deciphered;
+
+		purple_debug_info("cipher-test", "Test %02d:\n", i);
+		purple_debug_info("cipher-test", "\tTesting '%s' (%dbit) \n",
+			test->plaintext ? test->plaintext : "(null)",
+			strlen(test->key) * 8 / 2);
+
+		i++;
+
+		purple_cipher_context_reset(context, NULL);
+
+		if (test->iv) {
+			gsize iv_size;
+			guchar *iv = purple_base16_decode(test->iv, &iv_size);
+			g_assert(iv != NULL);
+			purple_cipher_context_set_iv(context, iv, iv_size);
+			g_free(iv);
+		}
+
+		key = purple_base16_decode(test->key, &key_size);
+		g_assert(key != NULL);
+		purple_cipher_context_set_key(context, key, key_size);
+		g_free(key);
+
+		if (purple_cipher_context_get_key_size(context) != key_size) {
+			purple_debug_info("cipher-test", "\tinvalid key size\n");
+			fail = TRUE;
+			continue;
+		}
+
+		cipher_len = purple_cipher_context_encrypt(context,
+			(const guchar*)(test->plaintext ? test->plaintext : ""),
+			test->plaintext ? (strlen(test->plaintext) + 1) : 0,
+			cipher, sizeof(cipher));
+		if (cipher_len < 0) {
+			purple_debug_info("cipher-test", "\tencryption failed\n");
+			fail = TRUE;
+			continue;
+		}
+
+		cipher_b16 = purple_base16_encode(cipher, cipher_len);
+
+		purple_debug_info("cipher-test", "\tGot:          %s\n", cipher_b16);
+		purple_debug_info("cipher-test", "\tWanted:       %s\n", test->cipher);
+
+		if (g_strcmp0(cipher_b16, test->cipher) != 0) {
+			purple_debug_info("cipher-test",
+				"\tencrypted data doesn't match\n");
+			g_free(cipher_b16);
+			fail = TRUE;
+			continue;
+		}
+		g_free(cipher_b16);
+
+		decipher_len = purple_cipher_context_decrypt(context,
+			cipher, cipher_len, decipher, sizeof(decipher));
+		if (decipher_len < 0) {
+			purple_debug_info("cipher-test", "\tdecryption failed\n");
+			fail = TRUE;
+			continue;
+		}
+
+		deciphered = (decipher_len > 0) ? (gchar*)decipher : NULL;
+
+		if (g_strcmp0(deciphered, test->plaintext) != 0) {
+			purple_debug_info("cipher-test",
+				"\tdecrypted data doesn't match\n");
+			fail = TRUE;
+			continue;
+		}
+
+		purple_debug_info("cipher-test", "\tTest OK\n");
+	}
+
+	if (context != NULL)
+		purple_cipher_context_destroy(context);
+
+	if (fail)
+		purple_debug_info("cipher-test", "AES tests FAILED\n\n");
+	else
+		purple_debug_info("cipher-test", "AES tests completed successfully\n\n");
+}
+
+/**************************************************************************
  * Plugin stuff
  **************************************************************************/
 static gboolean
@@ -238,6 +575,8 @@
 	cipher_test_md5();
 	cipher_test_sha1();
 	cipher_test_digest();
+	cipher_test_pbkdf2();
+	cipher_test_aes();
 
 	return TRUE;
 }
diff --git a/libpurple/plugins/keyrings/Makefile.am b/libpurple/plugins/keyrings/Makefile.am
new file mode 100644
--- /dev/null
+++ b/libpurple/plugins/keyrings/Makefile.am
@@ -0,0 +1,76 @@
+EXTRA_DIST = \
+		Makefile.mingw \
+		wincred.c
+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)
diff --git a/libpurple/plugins/keyrings/Makefile.mingw b/libpurple/plugins/keyrings/Makefile.mingw
new file mode 100644
--- /dev/null
+++ b/libpurple/plugins/keyrings/Makefile.mingw
@@ -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)
diff --git a/libpurple/plugins/keyrings/gnomekeyring.c b/libpurple/plugins/keyrings/gnomekeyring.c
new file mode 100644
--- /dev/null
+++ b/libpurple/plugins/keyrings/gnomekeyring.c
@@ -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)
diff --git a/libpurple/plugins/keyrings/internalkeyring.c b/libpurple/plugins/keyrings/internalkeyring.c
new file mode 100644
--- /dev/null
+++ b/libpurple/plugins/keyrings/internalkeyring.c
@@ -0,0 +1,1043 @@
+/**
+ * @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 "cipher.h"
+#include "debug.h"
+#include "keyring.h"
+#include "plugin.h"
+#include "version.h"
+
+#define INTKEYRING_NAME N_("Internal keyring")
+#define INTKEYRING_DESCRIPTION N_("This plugin provides the default password " \
+	"storage behaviour for libpurple.")
+#define INTKEYRING_AUTHOR "Tomek Wasilczyk (tomkiewicz@cpw.pidgin.im)"
+#define INTKEYRING_ID PURPLE_DEFAULT_KEYRING
+
+#define INTKEYRING_VERIFY_STR "[verification-string]"
+#define INTKEYRING_PBKDF2_ITERATIONS 10000
+#define INTKEYRING_PBKDF2_ITERATIONS_MIN 1000
+#define INTKEYRING_PBKDF2_ITERATIONS_MAX 1000000000
+#define INTKEYRING_KEY_LEN (256/8)
+#define INTKEYRING_ENCRYPT_BUFF_LEN 1000
+#define INTKEYRING_ENCRYPTED_MIN_LEN 50
+#define INTKEYRING_ENCRYPTION_METHOD "pbkdf2-sha256-aes256"
+
+#define INTKEYRING_PREFS "/plugins/keyrings/internal/"
+
+/* win32 build defines such macro to override read() routine */
+#undef read
+
+typedef struct
+{
+	enum
+	{
+		INTKEYRING_REQUEST_READ,
+		INTKEYRING_REQUEST_SAVE
+	} type;
+	PurpleAccount *account;
+	gchar *password;
+	union
+	{
+		PurpleKeyringReadCallback read;
+		PurpleKeyringSaveCallback save;
+	} cb;
+	gpointer cb_data;
+} intkeyring_request;
+
+typedef struct
+{
+	guchar *data;
+	size_t len;
+} intkeyring_buff_t;
+
+static intkeyring_buff_t *intkeyring_key;
+static GHashTable *intkeyring_passwords = NULL;
+static GHashTable *intkeyring_ciphertexts = NULL;
+
+static gboolean intkeyring_opened = FALSE;
+static gboolean intkeyring_unlocked = FALSE;
+
+static GList *intkeyring_pending_requests = NULL;
+static void *intkeyring_masterpw_uirequest = NULL;
+
+static PurpleKeyring *keyring_handler = NULL;
+
+static void
+intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
+	gpointer data);
+static void
+intkeyring_save(PurpleAccount *account, const gchar *password,
+	PurpleKeyringSaveCallback cb, gpointer data);
+static void
+intkeyring_reencrypt_passwords(void);
+static void
+intkeyring_unlock(const gchar *message);
+
+static void
+intkeyring_request_free(intkeyring_request *req)
+{
+	g_return_if_fail(req != NULL);
+
+	purple_str_wipe(req->password);
+	g_free(req);
+}
+
+static intkeyring_buff_t *
+intkeyring_buff_new(guchar *data, size_t len)
+{
+	intkeyring_buff_t *ret = g_new(intkeyring_buff_t, 1);
+
+	ret->data = data;
+	ret->len = len;
+
+	return ret;
+}
+
+static void
+intkeyring_buff_free(intkeyring_buff_t *buff)
+{
+	if (buff == NULL)
+		return;
+
+	memset(buff->data, 0, buff->len);
+	g_free(buff->data);
+	g_free(buff);
+}
+
+static intkeyring_buff_t *
+intkeyring_buff_from_base64(const gchar *base64)
+{
+	guchar *data;
+	gsize len;
+
+	data = purple_base64_decode(base64, &len);
+
+	return intkeyring_buff_new(data, len);
+}
+
+/************************************************************************/
+/* Generic encryption stuff                                             */
+/************************************************************************/
+
+static intkeyring_buff_t *
+intkeyring_derive_key(const gchar *passphrase, intkeyring_buff_t *salt)
+{
+	PurpleCipherContext *context;
+	gboolean succ;
+	intkeyring_buff_t *ret;
+
+	g_return_val_if_fail(passphrase != NULL, NULL);
+
+	context = purple_cipher_context_new_by_name("pbkdf2", NULL);
+	g_return_val_if_fail(context != NULL, NULL);
+
+	purple_cipher_context_set_option(context, "hash", "sha256");
+	purple_cipher_context_set_option(context, "iter_count",
+		GUINT_TO_POINTER(purple_prefs_get_int(INTKEYRING_PREFS
+		"pbkdf2_iterations")));
+	purple_cipher_context_set_option(context, "out_len", GUINT_TO_POINTER(
+		INTKEYRING_KEY_LEN));
+	purple_cipher_context_set_salt(context, salt->data, salt->len);
+	purple_cipher_context_set_key(context, (const guchar*)passphrase,
+		strlen(passphrase));
+
+	ret = intkeyring_buff_new(g_new(guchar, INTKEYRING_KEY_LEN),
+		INTKEYRING_KEY_LEN);
+	succ = purple_cipher_context_digest(context, ret->data, ret->len);
+
+	purple_cipher_context_destroy(context);
+
+	if (!succ) {
+		intkeyring_buff_free(ret);
+		return NULL;
+	}
+
+	return ret;
+}
+
+static intkeyring_buff_t *
+intkeyring_gen_salt(size_t len)
+{
+	intkeyring_buff_t *ret;
+	size_t filled = 0;
+
+	g_return_val_if_fail(len > 0, NULL);
+
+	ret = intkeyring_buff_new(g_new(guchar, len), len);
+
+	while (filled < len) {
+		guint32 r = g_random_int();
+		int i;
+
+		for (i = 0; i < 4; i++) {
+			ret->data[filled++] = r & 0xFF;
+			if (filled >= len)
+				break;
+			r >>= 8;
+		}
+	}
+
+	return ret;
+}
+
+/**
+ * Encrypts a plaintext using the specified key.
+ *
+ * Random IV will be generated and stored with ciphertext.
+ *
+ * Encryption scheme:
+ * [ IV ] ++ AES( [ plaintext ] ++ [ min length padding ] ++
+ *                [ control string ] ++ [ pkcs7 padding ] )
+ * where:
+ *  IV:                 Random, 128bit IV.
+ *  plaintext:          The plaintext.
+ *  min length padding: The padding used to hide the rough length of short
+ *                      plaintexts, may have a length of 0.
+ *  control string:     Constant string, verifies corectness of decryption.
+ *  pkcs7 padding:      The padding used to determine total length of encrypted
+ *                      content (also provides some verification).
+ *
+ * @param key The AES key.
+ * @param str The NUL-terminated plaintext.
+ * @return    The ciphertext with IV, encoded as base64. Must be g_free'd.
+ */
+static gchar *
+intkeyring_encrypt(intkeyring_buff_t *key, const gchar *str)
+{
+	PurpleCipherContext *context;
+	intkeyring_buff_t *iv;
+	guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN];
+	size_t plaintext_len, text_len, verify_len;
+	guchar encrypted_raw[INTKEYRING_ENCRYPT_BUFF_LEN];
+	ssize_t encrypted_size;
+
+	g_return_val_if_fail(key != NULL, NULL);
+	g_return_val_if_fail(str != NULL, NULL);
+
+	text_len = strlen(str);
+	verify_len = strlen(INTKEYRING_VERIFY_STR);
+	plaintext_len = INTKEYRING_ENCRYPTED_MIN_LEN;
+	if (plaintext_len < text_len)
+		plaintext_len = text_len;
+
+	g_return_val_if_fail(plaintext_len + verify_len <= sizeof(plaintext),
+		NULL);
+
+	context = purple_cipher_context_new_by_name("aes", NULL);
+	g_return_val_if_fail(context != NULL, NULL);
+
+	memset(plaintext, 0, plaintext_len);
+	memcpy(plaintext, str, text_len);
+	memcpy(plaintext + plaintext_len, INTKEYRING_VERIFY_STR, verify_len);
+	plaintext_len += verify_len;
+
+	iv = intkeyring_gen_salt(purple_cipher_context_get_block_size(context));
+	purple_cipher_context_set_iv(context, iv->data, iv->len);
+	purple_cipher_context_set_key(context, key->data, key->len);
+	purple_cipher_context_set_batch_mode(context,
+		PURPLE_CIPHER_BATCH_MODE_CBC);
+
+	memcpy(encrypted_raw, iv->data, iv->len);
+
+	encrypted_size = purple_cipher_context_encrypt(context,
+		plaintext, plaintext_len, encrypted_raw + iv->len,
+		sizeof(encrypted_raw) - iv->len);
+	encrypted_size += iv->len;
+
+	memset(plaintext, 0, plaintext_len);
+	intkeyring_buff_free(iv);
+	purple_cipher_context_destroy(context);
+
+	if (encrypted_size < 0)
+		return NULL;
+
+	return purple_base64_encode(encrypted_raw, encrypted_size);
+
+}
+
+static gchar *
+intkeyring_decrypt(intkeyring_buff_t *key, const gchar *str)
+{
+	PurpleCipherContext *context;
+	guchar *encrypted_raw;
+	gsize encrypted_size;
+	size_t iv_len, verify_len, text_len;
+	guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN];
+	const gchar *verify_str = NULL;
+	ssize_t plaintext_len;
+	gchar *ret;
+
+	g_return_val_if_fail(key != NULL, NULL);
+	g_return_val_if_fail(str != NULL, NULL);
+
+	context = purple_cipher_context_new_by_name("aes", NULL);
+	g_return_val_if_fail(context != NULL, NULL);
+
+	encrypted_raw = purple_base64_decode(str, &encrypted_size);
+	g_return_val_if_fail(encrypted_raw != NULL, NULL);
+
+	iv_len = purple_cipher_context_get_block_size(context);
+	if (encrypted_size < iv_len) {
+		g_free(encrypted_raw);
+		return NULL;
+	}
+
+	purple_cipher_context_set_iv(context, encrypted_raw, iv_len);
+	purple_cipher_context_set_key(context, key->data, key->len);
+	purple_cipher_context_set_batch_mode(context,
+		PURPLE_CIPHER_BATCH_MODE_CBC);
+
+	plaintext_len = purple_cipher_context_decrypt(context,
+		encrypted_raw + iv_len, encrypted_size - iv_len,
+		plaintext, sizeof(plaintext));
+
+	g_free(encrypted_raw);
+	purple_cipher_context_destroy(context);
+
+	verify_len = strlen(INTKEYRING_VERIFY_STR);
+	/* Don't remove the len > 0 check! */
+	if (plaintext_len > 0 && plaintext_len > verify_len &&
+		plaintext[plaintext_len] == '\0')
+	{
+		verify_str = (gchar*)plaintext + plaintext_len - verify_len;
+	}
+
+	if (g_strcmp0(verify_str, INTKEYRING_VERIFY_STR) != 0) {
+		purple_debug_warning("keyring-internal",
+			"Verification failed on decryption\n");
+		memset(plaintext, 0, sizeof(plaintext));
+		return NULL;
+	}
+
+	text_len = plaintext_len - verify_len;
+	ret = g_new(gchar, text_len + 1);
+	memcpy(ret, plaintext, text_len);
+	memset(plaintext, 0, plaintext_len);
+	ret[text_len] = '\0';
+
+	return ret;
+}
+
+/************************************************************************/
+/* Password encryption                                                  */
+/************************************************************************/
+
+static gboolean
+intkeyring_change_masterpw(const gchar *new_password)
+{
+	intkeyring_buff_t *salt, *key;
+	gchar *verifier = NULL, *salt_b64 = NULL;
+	int old_iter;
+	gboolean succ = TRUE;;
+
+	g_return_val_if_fail(intkeyring_unlocked, FALSE);
+
+	old_iter = purple_prefs_get_int(INTKEYRING_PREFS "pbkdf2_iterations");
+	purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_iterations",
+		purple_prefs_get_int(INTKEYRING_PREFS
+			"pbkdf2_desired_iterations"));
+
+	salt = intkeyring_gen_salt(32);
+	key = intkeyring_derive_key(new_password, salt);
+
+	if (salt && key && key->len == INTKEYRING_KEY_LEN) {
+		/* In fact, verify str will be concatenated twice before
+		 * encryption (it's used as a suffix in encryption routine),
+		 * but it's not a problem.
+		 */
+		verifier = intkeyring_encrypt(key, INTKEYRING_VERIFY_STR);
+		salt_b64 = purple_base64_encode(salt->data, salt->len);
+	}
+
+	if (!verifier || !salt_b64) {
+		purple_debug_error("keyring-internal", "Failed to change "
+			"master password\n");
+		succ = FALSE;
+		purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_iterations",
+			old_iter);
+	} else {
+		purple_prefs_set_string(INTKEYRING_PREFS "pbkdf2_salt",
+			salt_b64);
+		purple_prefs_set_string(INTKEYRING_PREFS "key_verifier",
+			verifier);
+
+		intkeyring_buff_free(intkeyring_key);
+		intkeyring_key = key;
+		key = NULL;
+
+		intkeyring_reencrypt_passwords();
+
+		purple_signal_emit(purple_keyring_get_handle(),
+			"password-migration", NULL);
+	}
+
+	g_free(salt_b64);
+	g_free(verifier);
+	intkeyring_buff_free(salt);
+	intkeyring_buff_free(key);
+
+	return succ;
+}
+
+static void
+intkeyring_process_queue(void)
+{
+	GList *requests, *it;
+	gboolean open = intkeyring_unlocked;
+
+	requests = g_list_first(intkeyring_pending_requests);
+	intkeyring_pending_requests = NULL;
+
+	for (it = requests; it != NULL; it = g_list_next(it))
+	{
+		intkeyring_request *req = it->data;
+
+		if (open && req->type == INTKEYRING_REQUEST_READ) {
+			intkeyring_read(req->account, req->cb.read,
+				req->cb_data);
+		} else if (open && req->type == INTKEYRING_REQUEST_SAVE) {
+			intkeyring_save(req->account, req->password,
+				req->cb.save, req->cb_data);
+		} else if (open)
+			g_assert_not_reached();
+		else if (req->cb.read != NULL /* || req->cb.write != NULL */ ) {
+			GError *error = g_error_new(PURPLE_KEYRING_ERROR,
+				PURPLE_KEYRING_ERROR_CANCELLED,
+				_("Operation cancelled."));
+			if (req->type == INTKEYRING_REQUEST_READ) {
+				req->cb.read(req->account, NULL, error,
+					req->cb_data);
+			} else if (req->type == INTKEYRING_REQUEST_SAVE)
+				req->cb.save(req->account, error, req->cb_data);
+			else
+				g_assert_not_reached();
+			g_error_free(error);
+		}
+
+		intkeyring_request_free(req);
+	}
+	g_list_free(requests);
+}
+
+static void
+intkeyring_decrypt_password(PurpleAccount *account, const gchar *ciphertext)
+{
+	gchar *plaintext;
+
+	plaintext = intkeyring_decrypt(intkeyring_key, ciphertext);
+	if (plaintext == NULL) {
+		purple_debug_warning("keyring-internal",
+			"Failed to decrypt a password\n");
+		return;
+	}
+
+	g_hash_table_replace(intkeyring_passwords, account, plaintext);
+}
+
+static void
+intkeyring_encrypt_password_if_needed(PurpleAccount *account)
+{
+	const gchar *plaintext;
+	gchar *ciphertext;
+
+	ciphertext = g_hash_table_lookup(intkeyring_ciphertexts, account);
+	if (ciphertext != NULL)
+		return;
+
+	plaintext = g_hash_table_lookup(intkeyring_passwords, account);
+	if (plaintext == NULL)
+		return;
+
+	ciphertext = intkeyring_encrypt(intkeyring_key, plaintext);
+	g_return_if_fail(ciphertext != NULL);
+
+	g_hash_table_replace(intkeyring_ciphertexts, account, ciphertext);
+}
+
+static void
+intkeyring_encrypt_passwords_if_needed_it(gpointer account, gpointer plaintext,
+	gpointer _unused)
+{
+	intkeyring_encrypt_password_if_needed(account);
+}
+
+static void
+intkeyring_reencrypt_passwords(void)
+{
+	g_hash_table_remove_all(intkeyring_ciphertexts);
+	g_hash_table_foreach(intkeyring_passwords,
+		intkeyring_encrypt_passwords_if_needed_it, NULL);
+}
+
+static void
+intkeyring_unlock_decrypt(gpointer account, gpointer ciphertext,
+	gpointer _unused)
+{
+	intkeyring_decrypt_password(account, ciphertext);
+}
+
+/************************************************************************/
+/* Opening and unlocking keyring                                        */
+/************************************************************************/
+
+static void
+intkeyring_unlock_ok(gpointer _unused,
+	PurpleRequestFields *fields)
+{
+	const gchar *masterpw;
+	gchar *verifier;
+	intkeyring_buff_t *salt, *key;
+
+	intkeyring_masterpw_uirequest = NULL;
+
+	if (g_strcmp0(purple_prefs_get_string(INTKEYRING_PREFS
+		"encryption_method"), INTKEYRING_ENCRYPTION_METHOD) != 0) {
+		purple_notify_error(NULL,
+			_("Unlocking internal keyring"),
+			_("Selected encryption method is not supported."),
+			_("Most probably, your passwords were encrypted with "
+			"newer Pidgin/libpurple version, please update."));
+		return;
+	}
+
+	masterpw = purple_request_fields_get_string(fields, "password");
+
+	if (masterpw == NULL || masterpw[0] == '\0') {
+		intkeyring_unlock(_("No password entered."));
+		return;
+	}
+
+	salt = intkeyring_buff_from_base64(purple_prefs_get_string(
+		INTKEYRING_PREFS "pbkdf2_salt"));
+	key = intkeyring_derive_key(masterpw, salt);
+	intkeyring_buff_free(salt);
+
+	verifier = intkeyring_decrypt(key, purple_prefs_get_string(
+		INTKEYRING_PREFS "key_verifier"));
+
+	if (g_strcmp0(verifier, INTKEYRING_VERIFY_STR) != 0) {
+		g_free(verifier);
+		intkeyring_buff_free(key);
+		intkeyring_unlock(_("Invalid master password entered, "
+			"try again."));
+		return;
+	}
+
+	g_free(verifier);
+	intkeyring_key = key;
+	intkeyring_unlocked = TRUE;
+
+	g_hash_table_foreach(intkeyring_ciphertexts,
+		intkeyring_unlock_decrypt, NULL);
+
+	intkeyring_process_queue();
+}
+
+static void
+intkeyring_unlock_cancel(gpointer _unused,
+	PurpleRequestFields *fields)
+{
+	intkeyring_masterpw_uirequest = NULL;
+	intkeyring_process_queue();
+}
+
+static void
+intkeyring_unlock(const gchar *message)
+{
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *group;
+	PurpleRequestField *field;
+	const gchar *primary_msg, *secondary_msg = NULL;
+
+	if (intkeyring_unlocked || intkeyring_masterpw_uirequest != NULL)
+		return;
+
+	if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) {
+		intkeyring_unlocked = TRUE;
+		intkeyring_process_queue();
+		return;
+	}
+
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new(NULL);
+	purple_request_fields_add_group(fields, group);
+
+	field = purple_request_field_string_new("password",
+		_("Master password"), "", FALSE);
+	purple_request_field_string_set_masked(field, TRUE);
+	purple_request_field_group_add_field(group, field);
+
+	primary_msg = _("Please, enter master password");
+	if (message) {
+		secondary_msg = primary_msg;
+		primary_msg = message;
+	}
+
+	intkeyring_masterpw_uirequest = purple_request_fields(NULL,
+		_("Unlocking internal keyring"),
+		primary_msg, secondary_msg, fields,
+		_("OK"), G_CALLBACK(intkeyring_unlock_ok),
+		_("Cancel"), G_CALLBACK(intkeyring_unlock_cancel),
+		NULL, NULL, NULL, NULL);
+}
+
+static void
+intkeyring_open(void)
+{
+	if (intkeyring_opened)
+		return;
+	intkeyring_opened = TRUE;
+
+	intkeyring_passwords = g_hash_table_new_full(g_direct_hash,
+		g_direct_equal, NULL, (GDestroyNotify)purple_str_wipe);
+	intkeyring_ciphertexts = g_hash_table_new_full(g_direct_hash,
+		g_direct_equal, NULL, g_free);
+}
+
+/************************************************************************/
+/* Keyring interface implementation                                     */
+/************************************************************************/
+
+static void
+intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
+	gpointer data)
+{
+	const char *password;
+	GError *error;
+
+	intkeyring_open();
+
+	if (!intkeyring_unlocked && g_hash_table_lookup(intkeyring_ciphertexts,
+		account) != NULL)
+	{
+		intkeyring_request *req = g_new0(intkeyring_request, 1);
+
+		req->type = INTKEYRING_REQUEST_READ;
+		req->account = account;
+		req->cb.read = cb;
+		req->cb_data = data;
+		intkeyring_pending_requests =
+			g_list_append(intkeyring_pending_requests, req);
+
+		intkeyring_unlock(NULL);
+		return;
+	}
+
+	password = g_hash_table_lookup(intkeyring_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
+intkeyring_save(PurpleAccount *account, const gchar *password,
+	PurpleKeyringSaveCallback cb, gpointer data)
+{
+	void *old_password;
+
+	intkeyring_open();
+
+	if (!intkeyring_unlocked) {
+		intkeyring_request *req;
+
+		if (password == NULL) {
+			g_hash_table_remove(intkeyring_ciphertexts, account);
+			g_hash_table_remove(intkeyring_passwords, account);
+			if (cb)
+				cb(account, NULL, data);
+			return;
+		}
+
+		req = g_new0(intkeyring_request, 1);
+		req->type = INTKEYRING_REQUEST_SAVE;
+		req->account = account;
+		req->password = g_strdup(password);
+		req->cb.save = cb;
+		req->cb_data = data;
+		intkeyring_pending_requests =
+			g_list_append(intkeyring_pending_requests, req);
+
+		intkeyring_unlock(NULL);
+		return;
+	}
+
+	g_hash_table_remove(intkeyring_ciphertexts, account);
+
+	old_password = g_hash_table_lookup(intkeyring_passwords, account);
+
+	if (password == NULL)
+		g_hash_table_remove(intkeyring_passwords, account);
+	else {
+		g_hash_table_replace(intkeyring_passwords, account,
+			g_strdup(password));
+	}
+
+	intkeyring_encrypt_password_if_needed(account);
+
+	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
+intkeyring_close(void)
+{
+	if (!intkeyring_opened)
+		return;
+	intkeyring_opened = FALSE;
+	intkeyring_unlocked = FALSE;
+
+	if (intkeyring_masterpw_uirequest) {
+		purple_request_close(PURPLE_REQUEST_FIELDS,
+			intkeyring_masterpw_uirequest);
+	}
+	g_warn_if_fail(intkeyring_masterpw_uirequest == NULL);
+	g_warn_if_fail(intkeyring_pending_requests == NULL);
+
+	intkeyring_buff_free(intkeyring_key);
+	intkeyring_key = NULL;
+	g_hash_table_destroy(intkeyring_passwords);
+	intkeyring_passwords = NULL;
+	g_hash_table_destroy(intkeyring_ciphertexts);
+	intkeyring_ciphertexts = NULL;
+}
+
+static gboolean
+intkeyring_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);
+
+	intkeyring_open();
+
+	if (mode == NULL)
+		mode = "cleartext";
+
+	if (g_strcmp0(mode, "cleartext") == 0) {
+		g_hash_table_replace(intkeyring_passwords, account,
+			g_strdup(data));
+		return TRUE;
+	} else if (g_strcmp0(mode, "ciphertext") == 0) {
+		if (intkeyring_unlocked)
+			intkeyring_decrypt_password(account, data);
+		else {
+			g_hash_table_replace(intkeyring_ciphertexts, 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
+intkeyring_export_password(PurpleAccount *account, const char **mode,
+	char **data, GError **error, GDestroyNotify *destroy)
+{
+	gchar *ciphertext = NULL;
+	intkeyring_open();
+
+	if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) {
+		gchar *cleartext = g_hash_table_lookup(intkeyring_passwords,
+			account);
+
+		if (cleartext == NULL)
+			return FALSE;
+
+		*mode = "cleartext";
+		*data = g_strdup(cleartext);
+		*destroy = (GDestroyNotify)purple_str_wipe;
+		return TRUE;
+	}
+
+	ciphertext = g_strdup(g_hash_table_lookup(intkeyring_ciphertexts,
+		account));
+
+	if (ciphertext == NULL && intkeyring_unlocked) {
+		gchar *plaintext = g_hash_table_lookup(intkeyring_passwords,
+			account);
+
+		if (plaintext == NULL)
+			return FALSE;
+
+		purple_debug_warning("keyring-internal", "Encrypted password "
+			"is missing at export (it shouldn't happen)\n");
+		ciphertext = intkeyring_encrypt(intkeyring_key, plaintext);
+	}
+
+	if (ciphertext == NULL)
+		return FALSE;
+
+	*mode = "ciphertext";
+	*data = ciphertext;
+	*destroy = (GDestroyNotify)g_free;
+	return TRUE;
+}
+
+static PurpleRequestFields *
+intkeyring_read_settings(void)
+{
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *group;
+	PurpleRequestField *field;
+
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new(NULL);
+	purple_request_fields_add_group(fields, group);
+
+	field = purple_request_field_bool_new("encrypt_passwords",
+		_("Encrypt passwords"), purple_prefs_get_bool(
+			INTKEYRING_PREFS "encrypt_passwords"));
+	purple_request_field_group_add_field(group, field);
+
+	group = purple_request_field_group_new(_("Master password"));
+	purple_request_fields_add_group(fields, group);
+
+	field = purple_request_field_string_new("passphrase1",
+		_("New passphrase:"), "", FALSE);
+	purple_request_field_string_set_masked(field, TRUE);
+	purple_request_field_group_add_field(group, field);
+
+	field = purple_request_field_string_new("passphrase2",
+		_("New passphrase (again):"), "", FALSE);
+	purple_request_field_string_set_masked(field, TRUE);
+	purple_request_field_group_add_field(group, field);
+
+	group = purple_request_field_group_new(_("Advanced settings"));
+	purple_request_fields_add_group(fields, group);
+
+	field = purple_request_field_int_new("pbkdf2_desired_iterations",
+		_("Number of PBKDF2 iterations:"), purple_prefs_get_int(
+			INTKEYRING_PREFS "pbkdf2_desired_iterations"),
+		INTKEYRING_PBKDF2_ITERATIONS_MIN,
+		INTKEYRING_PBKDF2_ITERATIONS_MAX);
+	purple_request_field_group_add_field(group, field);
+
+	return fields;
+}
+
+static gboolean
+intkeyring_apply_settings(void *notify_handle,
+	PurpleRequestFields *fields)
+{
+	const gchar *passphrase, *passphrase2;
+
+	intkeyring_unlock(_("You have to unlock the keyring first."));
+	if (!intkeyring_unlocked)
+		return FALSE;
+
+	passphrase = purple_request_fields_get_string(fields, "passphrase1");
+	if (g_strcmp0(passphrase, "") == 0)
+		passphrase = NULL;
+	passphrase2 = purple_request_fields_get_string(fields, "passphrase2");
+	if (g_strcmp0(passphrase2, "") == 0)
+		passphrase2 = NULL;
+
+	if (g_strcmp0(passphrase, passphrase2) != 0) {
+		purple_notify_error(notify_handle,
+			_("Internal keyring settings"),
+			_("Passphrases do not match"), NULL);
+		return FALSE;
+	}
+
+	if (purple_request_fields_get_bool(fields, "encrypt_passwords") &&
+		!passphrase && !intkeyring_key)
+	{
+		purple_notify_error(notify_handle,
+			_("Internal keyring settings"),
+			_("You have to set up a Master password, if you want "
+			"to enable encryption"), NULL);
+		return FALSE;
+	}
+
+	if (!purple_request_fields_get_bool(fields, "encrypt_passwords") &&
+		passphrase)
+	{
+		purple_notify_error(notify_handle,
+			_("Internal keyring settings"),
+			_("You don't need any master password, if you won't "
+			"enable passwords encryption"), NULL);
+		return FALSE;
+	}
+
+	purple_prefs_set_string(INTKEYRING_PREFS "encryption_method",
+		INTKEYRING_ENCRYPTION_METHOD);
+
+	purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_desired_iterations",
+		purple_request_fields_get_integer(fields,
+			"pbkdf2_desired_iterations"));
+
+	if (passphrase != NULL) {
+		if (!intkeyring_change_masterpw(passphrase))
+			return FALSE;
+	}
+
+	purple_prefs_set_bool(INTKEYRING_PREFS "encrypt_passwords",
+		purple_request_fields_get_bool(fields, "encrypt_passwords"));
+
+	purple_signal_emit(purple_keyring_get_handle(), "password-migration",
+		NULL);
+
+
+	return TRUE;
+}
+
+static gboolean
+intkeyring_load(PurplePlugin *plugin)
+{
+	keyring_handler = purple_keyring_new();
+
+	purple_keyring_set_name(keyring_handler, INTKEYRING_NAME);
+	purple_keyring_set_id(keyring_handler, INTKEYRING_ID);
+	purple_keyring_set_read_password(keyring_handler,
+		intkeyring_read);
+	purple_keyring_set_save_password(keyring_handler,
+		intkeyring_save);
+	purple_keyring_set_close_keyring(keyring_handler,
+		intkeyring_close);
+	purple_keyring_set_import_password(keyring_handler,
+		intkeyring_import_password);
+	purple_keyring_set_export_password(keyring_handler,
+		intkeyring_export_password);
+	purple_keyring_set_read_settings(keyring_handler,
+		intkeyring_read_settings);
+	purple_keyring_set_apply_settings(keyring_handler,
+		intkeyring_apply_settings);
+
+	purple_keyring_register(keyring_handler);
+
+	return TRUE;
+}
+
+static gboolean
+intkeyring_unload(PurplePlugin *plugin)
+{
+	if (purple_keyring_get_inuse() == keyring_handler) {
+		purple_debug_warning("keyring-internal",
+			"keyring in use, cannot unload\n");
+		return FALSE;
+	}
+
+	intkeyring_close();
+
+	purple_keyring_unregister(keyring_handler);
+	purple_keyring_free(keyring_handler);
+	keyring_handler = NULL;
+
+	if (intkeyring_key != NULL) {
+		purple_debug_warning("keyring-internal", "Master key should be "
+			"cleaned up at this point\n");
+		intkeyring_buff_free(intkeyring_key);
+		intkeyring_key = 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 */
+	INTKEYRING_ID,			/* id */
+	INTKEYRING_NAME,		/* name */
+	DISPLAY_VERSION,		/* version */
+	"Internal Keyring Plugin",	/* summary */
+	INTKEYRING_DESCRIPTION,		/* description */
+	INTKEYRING_AUTHOR,		/* author */
+	PURPLE_WEBSITE,			/* homepage */
+	intkeyring_load,		/* load */
+	intkeyring_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_prefs_add_none("/plugins/keyrings");
+	purple_prefs_add_none("/plugins/keyrings/internal");
+	purple_prefs_add_bool(INTKEYRING_PREFS "encrypt_passwords", FALSE);
+	purple_prefs_add_string(INTKEYRING_PREFS "encryption_method",
+		INTKEYRING_ENCRYPTION_METHOD);
+	purple_prefs_add_int(INTKEYRING_PREFS "pbkdf2_desired_iterations",
+		INTKEYRING_PBKDF2_ITERATIONS);
+	purple_prefs_add_int(INTKEYRING_PREFS "pbkdf2_iterations",
+		INTKEYRING_PBKDF2_ITERATIONS);
+	purple_prefs_add_string(INTKEYRING_PREFS "pbkdf2_salt", "");
+	purple_prefs_add_string(INTKEYRING_PREFS "key_verifier", "");
+}
+
+PURPLE_INIT_PLUGIN(internal_keyring, init_plugin, plugininfo)
diff --git a/libpurple/plugins/keyrings/kwallet.cpp b/libpurple/plugins/keyrings/kwallet.cpp
new file mode 100644
--- /dev/null
+++ b/libpurple/plugins/keyrings/kwallet.cpp
@@ -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 */
+	KWALLET_ID,				/* id */
+	KWALLET_NAME,				/* name */
+	DISPLAY_VERSION,			/* version */
+	"KWallet Keyring Plugin",		/* summary */
+	KWALLET_DESCRIPTION,			/* description */
+	KWALLET_AUTHOR,				/* author */
+	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"
diff --git a/libpurple/plugins/keyrings/secretservice.c b/libpurple/plugins/keyrings/secretservice.c
new file mode 100644
--- /dev/null
+++ b/libpurple/plugins/keyrings/secretservice.c
@@ -0,0 +1,337 @@
+/* purple
+ * @file secretservice.c Secret Service password storage
+ * @ingroup plugins
+ *
+ * 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
+ */
+
+#error "This keyring needs some more work (see TODO)"
+
+/* TODO
+ *
+ * This keyring needs some more work, so it will be disabled until its quality
+ * was raised. Some of the pain points:
+ *  - throws a lot of g_warnings
+ *  - it doesn't notify about some backend faults (like access denied), some of
+ *    them are not handled at all
+ *  - it could use libsecret's Complete API
+ *  - code formatting could be better
+ */
+
+#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)
+
diff --git a/libpurple/plugins/keyrings/wincred.c b/libpurple/plugins/keyrings/wincred.c
new file mode 100644
--- /dev/null
+++ b/libpurple/plugins/keyrings/wincred.c
@@ -0,0 +1,321 @@
+/**
+ * @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 read password (unicode error)."));
+	} 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)
diff --git a/libpurple/plugins/one_time_password.c b/libpurple/plugins/one_time_password.c
--- a/libpurple/plugins/one_time_password.c
+++ b/libpurple/plugins/one_time_password.c
@@ -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 ? */
 		}
 	}
diff --git a/libpurple/plugins/perl/common/Account.xs b/libpurple/plugins/perl/common/Account.xs
--- a/libpurple/plugins/perl/common/Account.xs
+++ b/libpurple/plugins/perl/common/Account.xs
@@ -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)
diff --git a/libpurple/plugins/perl/perl-handlers.c b/libpurple/plugins/perl/perl-handlers.c
--- a/libpurple/plugins/perl/perl-handlers.c
+++ b/libpurple/plugins/perl/perl-handlers.c
@@ -4,6 +4,12 @@
 #include "debug.h"
 #include "signals.h"
 
+typedef struct
+{
+	SV *callback;
+	SV *data;
+} PurplePerlAccountPasswordHandler;
+
 extern PerlInterpreter *my_perl;
 static GSList *cmd_handlers = NULL;
 static GSList *signal_handlers = NULL;
@@ -845,3 +851,115 @@
 			destroy_prefs_handler(handler);
 	}
 }
+
+static void
+perl_account_save_cb(PurpleAccount *account, GError *error, gpointer _handler)
+{
+	PurplePerlAccountPasswordHandler *handler = _handler;
+	SV *accountSV, *errorSV;
+
+	dSP;
+	ENTER;
+	SAVETMPS;
+	PUSHMARK(SP);
+
+	accountSV = sv_2mortal(purple_perl_bless_object(account,
+		"Purple::Account"));
+	XPUSHs(accountSV);
+
+	errorSV = sv_2mortal(purple_perl_bless_object(error, "GLib::Error"));
+	XPUSHs(errorSV);
+
+	XPUSHs((SV *)handler->data);
+
+	PUTBACK;
+	call_sv(handler->callback, G_EVAL | G_SCALAR);
+	SPAGAIN;
+
+	if (SvTRUE(ERRSV)) {
+		purple_debug_error("perl", "Perl plugin command function "
+			"exited abnormally: %s\n", SvPVutf8_nolen(ERRSV));
+	}
+
+	PUTBACK;
+	FREETMPS;
+	LEAVE;
+
+	g_free(handler);
+}
+
+static void
+perl_account_read_cb(PurpleAccount *account, const gchar *password,
+	GError *error, gpointer _handler)
+{
+	PurplePerlAccountPasswordHandler *handler = _handler;
+	SV *accountSV, *passwordSV, *errorSV;
+
+	dSP;
+	ENTER;
+	SAVETMPS;
+	PUSHMARK(SP);
+
+	accountSV = sv_2mortal(purple_perl_bless_object(account,
+		"Purple::Account"));
+	XPUSHs(accountSV);
+
+	passwordSV = sv_2mortal(newSVpv(password, 0));
+	XPUSHs(passwordSV);
+
+	errorSV = sv_2mortal(purple_perl_bless_object(error, "GLib::Error"));
+	XPUSHs(errorSV);
+
+	XPUSHs((SV *)handler->data);
+
+	PUTBACK;
+	call_sv(handler->callback, G_EVAL | G_SCALAR);
+	SPAGAIN;
+
+	if (SvTRUE(ERRSV)) {
+		purple_debug_error("perl", "Perl plugin command function "
+			"exited abnormally: %s\n", SvPVutf8_nolen(ERRSV));
+	}
+
+	PUTBACK;
+	FREETMPS;
+	LEAVE;
+
+	g_free(handler);
+}
+
+void
+purple_perl_account_get_password(PurpleAccount *account, SV *func, SV *data)
+{
+	PurplePerlAccountPasswordHandler *handler;
+
+	if (func == &PL_sv_undef)
+		func = NULL;
+	if (data == &PL_sv_undef)
+		data = NULL;
+
+	handler = g_new0(PurplePerlAccountPasswordHandler, 1);
+	handler->callback = (func != NULL ? newSVsv(func) : NULL);
+	handler->data = (data != NULL ? newSVsv(data) : NULL);
+
+	purple_account_get_password(account, perl_account_read_cb, handler);
+}
+
+void
+purple_perl_account_set_password(PurpleAccount *account, const gchar *password,
+	SV *func, SV *data)
+{
+	PurplePerlAccountPasswordHandler *handler;
+
+	if (func == &PL_sv_undef)
+		func = NULL;
+	if (data == &PL_sv_undef)
+		data = NULL;
+
+	handler = g_new0(PurplePerlAccountPasswordHandler, 1);
+	handler->callback = (func != NULL ? newSVsv(func) : NULL);
+	handler->data = (data != NULL ? newSVsv(data) : NULL);
+
+	purple_account_set_password(account, password, perl_account_save_cb,
+		handler);
+}
diff --git a/libpurple/plugins/perl/perl-handlers.h b/libpurple/plugins/perl/perl-handlers.h
--- a/libpurple/plugins/perl/perl-handlers.h
+++ b/libpurple/plugins/perl/perl-handlers.h
@@ -82,4 +82,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_ */
diff --git a/libpurple/plugins/perl/scripts/account.pl b/libpurple/plugins/perl/scripts/account.pl
--- a/libpurple/plugins/perl/scripts/account.pl
+++ b/libpurple/plugins/perl/scripts/account.pl
@@ -34,6 +34,37 @@
 	return %PLUGIN_INFO;
 }
 
+sub set_password_cb
+{
+	my $account = shift;
+	my $error = shift;
+	my $data = shift;
+
+	if ($error) {
+		Purple::Debug::warning($MODULE_NAME, "Failed to set password " .
+			"for $account\n");
+		return;
+	}
+
+	Purple::Debug::misc($MODULE_NAME, "Password for $account was set\n");
+}
+
+sub get_password_cb
+{
+	my $account = shift;
+	my $password = shift;
+	my $error = shift;
+	my $data = shift;
+
+	if ($error) {
+		Purple::Debug::warning($MODULE_NAME, "Failed to get password for $account\n");
+		return;
+	}
+
+	Purple::Debug::misc($MODULE_NAME, "Got password for $account\n");
+
+	$account->set_password($password, \&set_password_cb);
+}
 
 # This is the sub defined in %PLUGIN_INFO to be called when the plugin is loaded
 #	Note: The plugin has a reference to itself on top of the argument stack.
@@ -101,6 +132,8 @@
 	$account->set_status("available", TRUE);
 	$account->connect();
 
+	$account->get_password(\&get_password_cb);
+
 	print "\n\n";
 	Purple::Debug::info($MODULE_NAME, "plugin_load() - Testing $MODULE_NAME Completed.\n");
 	print "\n\n" . "#" x 80 . "\n\n";
diff --git a/libpurple/protocols/gg/account.c b/libpurple/protocols/gg/account.c
--- a/libpurple/protocols/gg/account.c
+++ b/libpurple/protocols/gg/account.c
@@ -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);
diff --git a/libpurple/protocols/gg/gg.c b/libpurple/protocols/gg/gg.c
--- a/libpurple/protocols/gg/gg.c
+++ b/libpurple/protocols/gg/gg.c
@@ -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,
diff --git a/libpurple/protocols/gg/oauth/oauth-purple.c b/libpurple/protocols/gg/oauth/oauth-purple.c
--- a/libpurple/protocols/gg/oauth/oauth-purple.c
+++ b/libpurple/protocols/gg/oauth/oauth-purple.c
@@ -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);
 	}
diff --git a/libpurple/protocols/irc/msgs.c b/libpurple/protocols/irc/msgs.c
--- a/libpurple/protocols/irc/msgs.c
+++ b/libpurple/protocols/irc/msgs.c
@@ -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;
diff --git a/libpurple/protocols/jabber/auth.c b/libpurple/protocols/jabber/auth.c
--- a/libpurple/protocols/jabber/auth.c
+++ b/libpurple/protocols/jabber/auth.c
@@ -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;
 	}
diff --git a/libpurple/protocols/jabber/auth_cyrus.c b/libpurple/protocols/jabber/auth_cyrus.c
--- a/libpurple/protocols/jabber/auth_cyrus.c
+++ b/libpurple/protocols/jabber/auth_cyrus.c
@@ -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;
diff --git a/libpurple/protocols/jabber/jabber.c b/libpurple/protocols/jabber/jabber.c
--- a/libpurple/protocols/jabber/jabber.c
+++ b/libpurple/protocols/jabber/jabber.c
@@ -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");
diff --git a/libpurple/protocols/msn/session.c b/libpurple/protocols/msn/session.c
--- a/libpurple/protocols/msn/session.c
+++ b/libpurple/protocols/msn/session.c
@@ -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;
diff --git a/libpurple/protocols/mxit/actions.c b/libpurple/protocols/mxit/actions.c
--- a/libpurple/protocols/mxit/actions.c
+++ b/libpurple/protocols/mxit/actions.c
@@ -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 );
 
diff --git a/libpurple/protocols/mxit/cipher.c b/libpurple/protocols/mxit/cipher.c
--- a/libpurple/protocols/mxit/cipher.c
+++ b/libpurple/protocols/mxit/cipher.c
@@ -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) */
diff --git a/libpurple/protocols/mxit/login.c b/libpurple/protocols/mxit/login.c
--- a/libpurple/protocols/mxit/login.c
+++ b/libpurple/protocols/mxit/login.c
@@ -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 {
diff --git a/libpurple/protocols/myspace/myspace.c b/libpurple/protocols/myspace/myspace.c
--- a/libpurple/protocols/myspace/myspace.c
+++ b/libpurple/protocols/myspace/myspace.c
@@ -704,7 +704,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);
 
@@ -1835,9 +1835,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 "
@@ -1845,7 +1845,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. */
@@ -1860,7 +1860,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);
diff --git a/libpurple/protocols/novell/novell.c b/libpurple/protocols/novell/novell.c
--- a/libpurple/protocols/novell/novell.c
+++ b/libpurple/protocols/novell/novell.c
@@ -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"));
diff --git a/libpurple/protocols/oscar/clientlogin.c b/libpurple/protocols/oscar/clientlogin.c
--- a/libpurple/protocols/oscar/clientlogin.c
+++ b/libpurple/protocols/oscar/clientlogin.c
@@ -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"));
diff --git a/libpurple/protocols/oscar/flap_connection.c b/libpurple/protocols/oscar/flap_connection.c
--- a/libpurple/protocols/oscar/flap_connection.c
+++ b/libpurple/protocols/oscar/flap_connection.c
@@ -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)
diff --git a/libpurple/protocols/oscar/oscar.c b/libpurple/protocols/oscar/oscar.c
--- a/libpurple/protocols/oscar/oscar.c
+++ b/libpurple/protocols/oscar/oscar.c
@@ -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:
diff --git a/libpurple/protocols/sametime/sametime.c b/libpurple/protocols/sametime/sametime.c
--- a/libpurple/protocols/sametime/sametime.c
+++ b/libpurple/protocols/sametime/sametime.c
@@ -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);
diff --git a/libpurple/protocols/silc/silc.c b/libpurple/protocols/silc/silc.c
--- a/libpurple/protocols/silc/silc.c
+++ b/libpurple/protocols/silc/silc.c
@@ -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;
diff --git a/libpurple/protocols/simple/simple.c b/libpurple/protocols/simple/simple.c
--- a/libpurple/protocols/simple/simple.c
+++ b/libpurple/protocols/simple/simple.c
@@ -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"));
diff --git a/libpurple/protocols/yahoo/libymsg.c b/libpurple/protocols/yahoo/libymsg.c
--- a/libpurple/protocols/yahoo/libymsg.c
+++ b/libpurple/protocols/yahoo/libymsg.c
@@ -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;
diff --git a/libpurple/prpl.c b/libpurple/prpl.c
--- a/libpurple/prpl.c
+++ b/libpurple/prpl.c
@@ -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;
 	}
 
diff --git a/libpurple/request.h b/libpurple/request.h
--- a/libpurple/request.h
+++ b/libpurple/request.h
@@ -30,6 +30,8 @@
 #include <glib-object.h>
 #include <glib.h>
 
+#include "certificate.h"
+
 /**
  * A request field.
  */
diff --git a/libpurple/util.c b/libpurple/util.c
--- a/libpurple/util.c
+++ b/libpurple/util.c
@@ -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
  **************************************************************************/
diff --git a/libpurple/util.h b/libpurple/util.h
--- a/libpurple/util.h
+++ b/libpurple/util.h
@@ -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);
+
 /*@}*/
 
 
diff --git a/pidgin/gtkaccount.c b/pidgin/gtkaccount.c
--- a/pidgin/gtkaccount.c
+++ b/pidgin/gtkaccount.c
@@ -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
  **************************************************************************/
diff --git a/pidgin/gtkconn.c b/pidgin/gtkconn.c
--- a/pidgin/gtkconn.c
+++ b/pidgin/gtkconn.c
@@ -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;
 	}
diff --git a/pidgin/gtkprefs.c b/pidgin/gtkprefs.c
--- a/pidgin/gtkprefs.c
+++ b/pidgin/gtkprefs.c
@@ -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,14 @@
 static GtkWidget *prefs_status_themes_combo_box;
 static GtkWidget *prefs_smiley_themes_combo_box;
 
+/* Keyrings page */
+static GtkWidget *keyring_page_instance = NULL;
+static GtkComboBox *keyring_combo = NULL;
+static GtkBox *keyring_vbox = NULL;
+static PurpleRequestFields *keyring_settings = NULL;
+static GList *keyring_settings_fields = NULL;
+static GtkWidget *keyring_apply = NULL;
+
 /* Sound theme specific */
 static GtkWidget *sound_entry = NULL;
 static int sound_row_sel = 0;
@@ -269,83 +278,105 @@
 	PREF_DROPDOWN_COUNT
 };
 
+typedef struct
+{
+	PurplePrefType type;
+	union {
+		const char *string;
+		int integer;
+		gboolean boolean;
+	} value;
+} PidginPrefValue;
+
+typedef void (*PidginPrefsDropdownCallback)(GtkComboBox *combo_box,
+	PidginPrefValue value);
+
 static void
-dropdown_set(GObject *w, const char *key)
+dropdown_set(GtkComboBox *combo_box, gpointer _cb)
 {
-	const char *str_value;
-	int int_value;
-	gboolean bool_value;
-	PurplePrefType type;
+	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 (type == PURPLE_PREF_BOOLEAN) {
-		gtk_tree_model_get(tree_model, &iter,
-		                   PREF_DROPDOWN_VALUE, &bool_value,
-		                   -1);
-
-		purple_prefs_set_bool(key, bool_value);
+	else if (active.type == PURPLE_PREF_BOOLEAN) {
+		gtk_tree_model_get(tree_model, &iter, PREF_DROPDOWN_VALUE,
+			&active.value.boolean, -1);
 	}
+
+	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 +385,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 +424,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, ...)
@@ -443,12 +525,16 @@
 	return dropdown;
 }
 
+static void keyring_page_cleanup(void);
+
 static void
 delete_prefs(GtkWidget *asdf, void *gdsa)
 {
 	/* Close any "select sound" request dialogs */
 	purple_request_close_with_handle(prefs);
 
+	purple_notify_close_with_handle(prefs);
+
 	/* Unregister callbacks. */
 	purple_prefs_disconnect_by_handle(prefs);
 
@@ -464,6 +550,8 @@
 	prefs_status_themes_combo_box = NULL;
 	prefs_smiley_themes_combo_box = NULL;
 
+	keyring_page_cleanup();
+
 	sample_webview = NULL;
 
 	notebook_page = 0;
@@ -2554,6 +2642,262 @@
 	return ret;
 }
 
+/*** keyring page *******************************************************/
+
+static void
+keyring_page_settings_changed(GtkWidget *widget, gpointer _setting)
+{
+	PurpleRequestField *setting = _setting;
+	PurpleRequestFieldType field_type;
+
+	gtk_widget_set_sensitive(keyring_apply, TRUE);
+
+	field_type = purple_request_field_get_type(setting);
+
+	if (field_type == PURPLE_REQUEST_FIELD_BOOLEAN) {
+		purple_request_field_bool_set_value(setting,
+			gtk_toggle_button_get_active(
+				GTK_TOGGLE_BUTTON(widget)));
+	} else if (field_type == PURPLE_REQUEST_FIELD_STRING) {
+		purple_request_field_string_set_value(setting,
+			gtk_entry_get_text(GTK_ENTRY(widget)));
+	} else if (field_type == PURPLE_REQUEST_FIELD_INTEGER) {
+		purple_request_field_int_set_value(setting,
+			gtk_spin_button_get_value_as_int(
+				GTK_SPIN_BUTTON(widget)));
+	} else
+		g_return_if_reached();
+}
+
+static GtkWidget *
+keyring_page_add_settings_field(GtkBox *vbox, PurpleRequestField *setting,
+	GtkSizeGroup *sg)
+{
+	GtkWidget *widget, *hbox;
+	PurpleRequestFieldType field_type;
+	const gchar *label;
+
+	label = purple_request_field_get_label(setting);
+
+	field_type = purple_request_field_get_type(setting);
+	if (field_type == PURPLE_REQUEST_FIELD_BOOLEAN) {
+		widget = gtk_check_button_new_with_label(label);
+		label = NULL;
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
+			purple_request_field_bool_get_value(setting));
+		g_signal_connect(G_OBJECT(widget), "toggled",
+			G_CALLBACK(keyring_page_settings_changed), setting);
+	} else if (field_type == PURPLE_REQUEST_FIELD_STRING) {
+		widget = gtk_entry_new();
+		gtk_entry_set_text(GTK_ENTRY(widget),
+			purple_request_field_string_get_value(setting));
+		if (purple_request_field_string_is_masked(setting))
+			gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE);
+		g_signal_connect(G_OBJECT(widget), "changed",
+			G_CALLBACK(keyring_page_settings_changed), setting);
+	} else if (field_type == PURPLE_REQUEST_FIELD_INTEGER) {
+		widget = gtk_spin_button_new_with_range(
+			purple_request_field_int_get_lower_bound(setting),
+			purple_request_field_int_get_upper_bound(setting), 1);
+		gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget),
+			purple_request_field_int_get_value(setting));
+		g_signal_connect(G_OBJECT(widget), "value-changed",
+			G_CALLBACK(keyring_page_settings_changed), setting);
+	} else {
+		purple_debug_error("gtkprefs", "Unsupported field type\n");
+		return NULL;
+	}
+
+	hbox = pidgin_add_widget_to_vbox(vbox, label, sg, widget,
+		FALSE, NULL);
+	return ((void*)hbox == (void*)vbox) ? widget : hbox;
+}
+
+/* XXX: it could be available for all plugins, not keyrings only */
+static GList *
+keyring_page_add_settings(PurpleRequestFields *settings)
+{
+	GList *it, *groups, *added_fields;
+	GtkSizeGroup *sg;
+
+	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+
+	added_fields = NULL;
+	groups = purple_request_fields_get_groups(settings);
+	for (it = g_list_first(groups); it != NULL; it = g_list_next(it)) {
+		GList *it2, *fields;
+		GtkBox *vbox;
+		PurpleRequestFieldGroup *group;
+		const gchar *group_title;
+
+		group = it->data;
+		group_title = purple_request_field_group_get_title(group);
+		if (group_title) {
+			vbox = GTK_BOX(pidgin_make_frame(
+				GTK_WIDGET(keyring_vbox), group_title));
+			added_fields = g_list_prepend(added_fields,
+				g_object_get_data(G_OBJECT(vbox), "main-vbox"));
+		} else
+			vbox = keyring_vbox;
+
+		fields = purple_request_field_group_get_fields(group);
+		for (it2 = g_list_first(fields); it2 != NULL;
+			it2 = g_list_next(it2)) {
+			GtkWidget *added = keyring_page_add_settings_field(vbox,
+				it2->data, sg);
+			if (added == NULL || vbox != keyring_vbox)
+				continue;
+			added_fields = g_list_prepend(added_fields, added);
+		}
+	}
+
+	g_object_unref(sg);
+
+	return added_fields;
+}
+
+static void
+keyring_page_settings_apply(GtkButton *button, gpointer _unused)
+{
+	if (!purple_keyring_apply_settings(prefs, keyring_settings))
+		return;
+
+	gtk_widget_set_sensitive(keyring_apply, FALSE);
+}
+
+static void
+keyring_page_update_settings()
+{
+	if (keyring_settings != NULL)
+		purple_request_fields_destroy(keyring_settings);
+	keyring_settings = purple_keyring_read_settings();
+	if (!keyring_settings)
+		return;
+
+	keyring_settings_fields = keyring_page_add_settings(keyring_settings);
+
+	keyring_apply = gtk_button_new_with_mnemonic(_("_Apply"));
+	gtk_box_pack_start(keyring_vbox, keyring_apply, FALSE, FALSE, 1);
+	gtk_widget_set_sensitive(keyring_apply, FALSE);
+	keyring_settings_fields = g_list_prepend(keyring_settings_fields,
+		keyring_apply);
+	g_signal_connect(G_OBJECT(keyring_apply), "clicked",
+		G_CALLBACK(keyring_page_settings_apply), NULL);
+
+	gtk_widget_show_all(keyring_page_instance);
+}
+
+static void
+keyring_page_pref_set_inuse(GError *error, gpointer _keyring_page_instance)
+{
+	PurpleKeyring *in_use = purple_keyring_get_inuse();
+
+	if (_keyring_page_instance != keyring_page_instance) {
+		purple_debug_info("gtkprefs", "pref window already closed\n");
+		return;
+	}
+
+	gtk_widget_set_sensitive(GTK_WIDGET(keyring_combo), TRUE);
+
+	if (error != NULL) {
+		pidgin_prefs_dropdown_revert_active(keyring_combo);
+		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));
+
+	keyring_page_update_settings();
+}
+
+static void
+keyring_page_pref_changed(GtkComboBox *combo_box, PidginPrefValue value)
+{
+	const char *keyring_id;
+	PurpleKeyring *keyring;
+	GList *it;
+
+	g_return_if_fail(combo_box != NULL);
+	g_return_if_fail(value.type == PURPLE_PREF_STRING);
+
+	keyring_id = value.value.string;
+	keyring = purple_keyring_find_keyring_by_id(keyring_id);
+	if (keyring == NULL) {
+		pidgin_prefs_dropdown_revert_active(keyring_combo);
+		purple_notify_error(NULL, _("Keyring"),
+			_("Selected keyring is disabled"), NULL);
+		return;
+	}
+
+	gtk_widget_set_sensitive(GTK_WIDGET(combo_box), FALSE);
+
+	for (it = keyring_settings_fields; it != NULL; it = g_list_next(it))
+	{
+		GtkWidget *widget = it->data;
+		gtk_container_remove(
+			GTK_CONTAINER(gtk_widget_get_parent(widget)), widget);
+	}
+	gtk_widget_show_all(keyring_page_instance);
+	g_list_free(keyring_settings_fields);
+	keyring_settings_fields = NULL;
+	if (keyring_settings)
+		purple_request_fields_destroy(keyring_settings);
+	keyring_settings = NULL;
+
+	purple_keyring_set_inuse(keyring, FALSE, keyring_page_pref_set_inuse,
+		keyring_page_instance);
+}
+
+static void
+keyring_page_cleanup(void)
+{
+	keyring_page_instance = NULL;
+	keyring_combo = NULL;
+	keyring_vbox = NULL;
+	g_list_free(keyring_settings_fields);
+	keyring_settings_fields = NULL;
+	if (keyring_settings)
+		purple_request_fields_destroy(keyring_settings);
+	keyring_settings = NULL;
+	keyring_apply = NULL;
+}
+
+static GtkWidget *
+keyring_page(void)
+{
+	GList *names;
+	PidginPrefValue initial;
+
+	g_return_val_if_fail(keyring_page_instance == NULL,
+		keyring_page_instance);
+
+	keyring_page_instance = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
+	gtk_container_set_border_width(GTK_CONTAINER(keyring_page_instance),
+		PIDGIN_HIG_BORDER);
+
+	/* Keyring selection */
+	keyring_vbox = GTK_BOX(pidgin_make_frame(keyring_page_instance,
+		_("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(GTK_WIDGET(keyring_vbox),
+		_("Keyring:"), &keyring_combo, names, initial,
+		keyring_page_pref_changed);
+	g_list_free(names);
+
+	keyring_page_update_settings();
+
+	gtk_widget_show_all(keyring_page_instance);
+
+	return keyring_page_instance;
+}
+
+/*** keyring page - end *************************************************/
+
 #ifndef _WIN32
 static gint
 sound_cmd_yeah(GtkEntry *entry, gpointer d)
@@ -3721,6 +4065,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++);
diff --git a/pidgin/gtkutils.c b/pidgin/gtkutils.c
--- a/pidgin/gtkutils.c
+++ b/pidgin/gtkutils.c
@@ -483,7 +483,7 @@
 GtkWidget *
 pidgin_make_frame(GtkWidget *parent, const char *title)
 {
-	GtkWidget *vbox, *label, *hbox;
+	GtkWidget *vbox, *vbox2, *label, *hbox;
 	char *labeltitle;
 
 	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
@@ -509,11 +509,13 @@
 	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
 	gtk_widget_show(label);
 
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
-	gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
-	gtk_widget_show(vbox);
-
-	return vbox;
+	vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	gtk_box_pack_start(GTK_BOX(hbox), vbox2, FALSE, FALSE, 0);
+	gtk_widget_show(vbox2);
+
+	g_object_set_data(G_OBJECT(vbox2), "main-vbox", vbox);
+
+	return vbox2;
 }
 
 static gpointer
diff --git a/po/POTFILES.in b/po/POTFILES.in
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -49,6 +49,7 @@
 libpurple/dnsquery.c
 libpurple/ft.c
 libpurple/gconf/purple.schemas.in
+libpurple/keyring.c
 libpurple/log.c
 libpurple/media/backend-fs2.c
 libpurple/plugin.c
@@ -61,6 +62,11 @@
 libpurple/plugins/ipc-test-client.c
 libpurple/plugins/ipc-test-server.c
 libpurple/plugins/joinpart.c
+libpurple/plugins/keyrings/gnomekeyring.c
+libpurple/plugins/keyrings/internalkeyring.c
+libpurple/plugins/keyrings/kwallet.cpp
+libpurple/plugins/keyrings/secretservice.c
+libpurple/plugins/keyrings/wincred.c
 libpurple/plugins/log_reader.c
 libpurple/plugins/mono/loader/mono.c
 libpurple/plugins/newline.c
