summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomasz Wasilczyk <tomkiewicz@cpw.pidgin.im>2013-05-06 16:02:57 +0200
committerTomasz Wasilczyk <tomkiewicz@cpw.pidgin.im>2013-05-06 16:02:57 +0200
commit60372ec98697473b607859a6d0d1d1bd335d660e (patch)
treef0208ff351d94afd47c75c349d3038f69e578786
parentb9d7a17f3d65e38799e03967bbb118bdb8abfab1 (diff)
parent888aafb15c057645a6b3bce5773ec53101ff42ce (diff)
downloadpidgin-60372ec98697473b607859a6d0d1d1bd335d660e.tar.gz
Merge from main
-rw-r--r--.hgignore1
-rw-r--r--COPYRIGHT1
-rw-r--r--configure.ac153
-rw-r--r--finch/gntaccount.c15
-rw-r--r--finch/gntprefs.c8
-rw-r--r--libpurple/Makefile.am2
-rw-r--r--libpurple/Makefile.mingw1
-rw-r--r--libpurple/account.c232
-rw-r--r--libpurple/account.h25
-rw-r--r--libpurple/connection.c9
-rw-r--r--libpurple/core.c6
-rw-r--r--libpurple/example/nullclient.c2
-rw-r--r--libpurple/keyring.c1316
-rw-r--r--libpurple/keyring.h522
-rw-r--r--libpurple/plugins/Makefile.am5
-rw-r--r--libpurple/plugins/Makefile.mingw4
-rw-r--r--libpurple/plugins/keyrings/Makefile.am75
-rw-r--r--libpurple/plugins/keyrings/Makefile.mingw81
-rw-r--r--libpurple/plugins/keyrings/gnomekeyring.c454
-rw-r--r--libpurple/plugins/keyrings/internalkeyring.c261
-rw-r--r--libpurple/plugins/keyrings/kwallet.cpp582
-rw-r--r--libpurple/plugins/keyrings/secretservice.c325
-rw-r--r--libpurple/plugins/keyrings/wincred.c320
-rw-r--r--libpurple/plugins/one_time_password.c2
-rw-r--r--libpurple/plugins/perl/common/Account.xs15
-rw-r--r--libpurple/plugins/perl/perl-handlers.c124
-rw-r--r--libpurple/plugins/perl/perl-handlers.h13
-rw-r--r--libpurple/protocols/gg/account.c8
-rw-r--r--libpurple/protocols/gg/gg.c3
-rw-r--r--libpurple/protocols/gg/oauth/oauth-purple.c8
-rw-r--r--libpurple/protocols/irc/msgs.c3
-rw-r--r--libpurple/protocols/jabber/auth.c6
-rw-r--r--libpurple/protocols/jabber/auth_cyrus.c12
-rw-r--r--libpurple/protocols/jabber/jabber.c6
-rw-r--r--libpurple/protocols/msn/session.c4
-rw-r--r--libpurple/protocols/mxit/actions.c7
-rw-r--r--libpurple/protocols/mxit/cipher.c4
-rw-r--r--libpurple/protocols/mxit/login.c2
-rw-r--r--libpurple/protocols/myspace/myspace.c10
-rw-r--r--libpurple/protocols/novell/novell.c4
-rw-r--r--libpurple/protocols/oscar/clientlogin.c2
-rw-r--r--libpurple/protocols/oscar/flap_connection.c2
-rw-r--r--libpurple/protocols/oscar/oscar.c2
-rw-r--r--libpurple/protocols/sametime/sametime.c2
-rw-r--r--libpurple/protocols/silc/silc.c4
-rw-r--r--libpurple/protocols/simple/simple.c2
-rw-r--r--libpurple/protocols/yahoo/libymsg.c6
-rw-r--r--libpurple/prpl.c7
-rw-r--r--libpurple/util.c35
-rw-r--r--libpurple/util.h27
-rw-r--r--pidgin/gtkaccount.c44
-rw-r--r--pidgin/gtkconn.c3
-rw-r--r--pidgin/gtkprefs.c298
53 files changed, 4882 insertions, 183 deletions
diff --git a/.hgignore b/.hgignore
index d049550677..d8707ec018 100644
--- a/.hgignore
+++ b/.hgignore
@@ -28,6 +28,7 @@ syntax: regexp
.*\.pyo$
.*\.rej$
.*\.so$
+.*\.moc$
Doxyfile(\.mingw)?$
VERSION$
aclocal.m4
diff --git a/COPYRIGHT b/COPYRIGHT
index 312da5fdf6..973050f385 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -55,6 +55,7 @@ Matthew W.S. Bell
Igor Belyi
David Benjamin
Brian Bernas
+Vivien Bernet-Rollande
Paul Betts
Runa Bhattacharjee
Jonas Birmé
diff --git a/configure.ac b/configure.ac
index 2475bd1517..50958fce85 100644
--- a/configure.ac
+++ b/configure.ac
@@ -109,6 +109,7 @@ AC_DEFINE_UNQUOTED(CONFIG_ARGS, "$ac_configure_args", [configure arguments])
dnl Checks for programs.
AC_PROG_CC
AM_PROG_CC_C_O
+AC_PROG_CXX
LT_PREREQ([2.2.6])
LT_INIT([disable-static])
LIBTOOL="$LIBTOOL --silent"
@@ -1478,6 +1479,7 @@ if test "x$GCC" = "xyes"; then
DEBUG_CFLAGS="-Wall $DEBUG_CFLAGS"
CFLAGS="-g $CFLAGS"
fi
+DEBUG_CPPFLAGS=`echo "$DEBUG_CFLAGS" | $sedpath 's/-Wdeclaration-after-statement//' | $sedpath 's/-Wmissing-prototypes//' | $sedpath 's/-Waggregate-return//'`
if test "x$SUNCC" = "xyes"; then
CFLAGS="$CFLAGS -features=extensions"
@@ -1533,6 +1535,151 @@ else
fi
dnl #######################################################################
+dnl # Check for Secret Service headers
+dnl #######################################################################
+
+AC_ARG_ENABLE(libsecret, [AC_HELP_STRING([--disable-libsecret], [enable Secret Service support])], enable_secret_service=no, enable_secret_service=yes)
+
+dnl Check for libsecret; if we don't have it, oh well
+if test "x$enable_secret_service" = "xyes" ; then
+ PKG_CHECK_MODULES(SECRETSERVICE, [libsecret-1], [
+ AC_SUBST(SECRETSERVICE_CFLAGS)
+ AC_SUBST(SECRETSERVICE_LIBS)
+ AC_DEFINE(HAVE_SECRETSERVICE, 1, [Define if we have Secret Service.])
+ ])
+fi
+
+AM_CONDITIONAL(ENABLE_SECRETSERVICE, test "x$enable_secret_service" = "xyes")
+
+dnl #######################################################################
+dnl # Check for GNOME Keyring headers
+dnl #######################################################################
+
+AC_ARG_ENABLE(gnome-keyring, [AC_HELP_STRING([--disable-gnome-keyring], [enable GNOME Keyring support])], enable_gnome_keyring=no, enable_gnome_keyring=yes)
+
+if test "x$enable_gnome_keyring" = "xyes" ; then
+ PKG_CHECK_MODULES(GNOMEKEYRING, [gnome-keyring-1], [
+ AC_SUBST(GNOMEKEYRING_CFLAGS)
+ AC_SUBST(GNOMEKEYRING_LIBS)
+ AC_DEFINE(HAVE_GNOMEKEYRING, 1, [Define if we have GNOME Keyring.])
+ ])
+fi
+
+AM_CONDITIONAL(ENABLE_GNOMEKEYRING, test "x$enable_gnome_keyring" = "xyes")
+
+dnl #######################################################################
+dnl # Check for KWallet headers
+dnl #######################################################################
+
+AC_ARG_ENABLE(kwallet, [AC_HELP_STRING([--disable-kwallet], [enable KWallet support])], enable_kwallet=no, enable_kwallet=yes)
+AC_ARG_WITH(kwallet-includes, [AC_HELP_STRING([--with-kwallet-includes=DIR], [compile the KWallet plugin against includes in DIR])], [ac_kwallet_includes="$withval"], [ac_kwallet_includes="no"])
+AC_ARG_WITH(kwallet-libs, [AC_HELP_STRING([--with-kwallet-libs=DIR], [compile the KWallet plugin against the KWallet libs in DIR])], [ac_kwallet_libs="$withval"], [ac_kwallet_libs="no"])
+
+if test "x$enable_kwallet" = "xyes"; then
+ KWALLET_CXXFLAGS=""
+ KWALLET_LIBS=""
+ if test -z "$with_kwallet_includes" || test -z "$with_kwallet_libs"; then
+ AC_CHECK_PROG(KDE4_CONFIG, kde4-config, kde4-config, no)
+ if test "x$KDE4_CONFIG" = "xno"; then
+ if test "x$force_deps" = "xyes"; then
+ AC_MSG_WARN([
+kde4-config not found. $KDE4_CONFIG
+Use --disable-kwallet if you do not need KWallet support.
+Use --with-kwallet-includes and --with-kwallet-libs to set up includes manually.
+])
+ enable_kwallet=no
+ fi
+ fi
+ fi
+fi
+
+if test "x$enable_kwallet" = "xyes"; then
+ AC_LANG_PUSH([C++])
+ CPPFLAGS_save="$CPPFLAGS"
+
+ if test "$ac_kwallet_includes" != "no"; then
+ KWALLET_CXXFLAGS="-I$ac_kwallet_includes"
+ elif test "x$KDE4_CONFIG" != "xno"; then
+ KWALLET_CXXFLAGS="-I`$KDE4_CONFIG --path include`"
+ fi
+ CPPFLAGS="$CPPFLAGS $KWALLET_CXXFLAGS"
+ AC_CHECK_HEADER([kwallet.h], , [enable_kwallet=no])
+
+ CPPFLAGS="$CPPFLAGS_save"
+ AC_LANG_POP
+fi
+
+if test "x$enable_kwallet" = "xyes"; then
+ dnl Ensure C++ compiler works
+ AC_CHECK_PROG(CXXTEST, [$CXX], [$CXX])
+ if test "x$CXXTEST" = "x"; then
+ if test "x$force_deps" = "xyes"; then
+ AC_MSG_WARN([
+A C++ compiler was not found.
+Use --disable-kwallet if you do not need KWallet support.
+])
+ enable_kwallet=no
+ fi
+ fi
+fi
+
+AC_LANG_PUSH([C++])
+CPPFLAGS_save="$CPPFLAGS"
+LDFLAGS_save="$LDFLAGS"
+if test "x$enable_kwallet" = "xyes"; then
+
+ PKG_CHECK_MODULES(QT4, [QtCore], [
+ AC_SUBST(QT4_CFLAGS)
+ AC_SUBST(QT4_LIBS)
+ ], [
+ AC_MSG_RESULT(no)
+ AC_MSG_WARN([
+Qt4 development headers not found.
+Use --disable-kwallet if you do not need KWallet support.
+])
+ enable_kwallet=no
+ ])
+fi
+
+if test "x$enable_kwallet" = "xyes"; then
+ AC_MSG_CHECKING([for metaobject compiler])
+ MOC=`$PKG_CONFIG --variable=moc_location QtCore`
+ AC_SUBST(MOC)
+ AC_MSG_RESULT([$MOC])
+
+
+ AC_MSG_CHECKING([for KWallet libraries])
+ if test "$ac_kwallet_libs" != "no"; then
+ KWALLET_LIBS="-L$ac_kwallet_libs -lkdeui"
+ elif test "x$KDE4_CONFIG" != "xno"; then
+ KWALLET_LIBS="-L`$KDE4_CONFIG --install lib`/kde4/devel -lkdeui"
+ else
+ KWALLET_LIBS="-lkdeui"
+ fi
+ KWALLET_LIBS="$KWALLET_LIBS"
+ CPPFLAGS="$CPPFLAGS $KWALLET_CXXFLAGS"
+ LDFLAGS="$LDFLAGS $KWALLET_LIBS $QT4_LIBS"
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <kwallet.h>],
+ [KWallet::Wallet::LocalWallet();])], [AC_MSG_RESULT([yes])],
+ [if test "x$force_deps" = "xyes"; then
+AC_MSG_ERROR([
+KWallet development libraries not found.
+Use --disable-kwallet if you do not need KWallet support.
+])
+fi
+])
+
+fi
+CPPFLAGS="$CPPFLAGS_save"
+LDFLAGS="$LDFLAGS_save"
+AC_LANG_POP
+
+AC_SUBST(KWALLET_CXXFLAGS)
+AC_SUBST(KWALLET_LIBS)
+
+AM_CONDITIONAL(ENABLE_KWALLET, test "x$enable_kwallet" = "xyes")
+
+dnl #######################################################################
dnl # Check for Python
dnl #######################################################################
@@ -2428,6 +2575,7 @@ if test "$ac_cv_cygwin" = yes ; then
AC_DEFINE(DEBUG, 1, [Define if debugging is enabled.])
fi
+AC_SUBST(DEBUG_CPPFLAGS)
AC_SUBST(DEBUG_CFLAGS)
AC_SUBST(LDADD)
AC_SUBST(LIBS)
@@ -2706,6 +2854,7 @@ AC_CONFIG_FILES([Makefile
libpurple/purple-3.pc
libpurple/purple-3-uninstalled.pc
libpurple/plugins/Makefile
+ libpurple/plugins/keyrings/Makefile
libpurple/plugins/mono/Makefile
libpurple/plugins/mono/api/Makefile
libpurple/plugins/mono/loader/Makefile
@@ -2789,6 +2938,10 @@ echo Use startup notification...... : $enable_startup_notification
echo Build with GtkSpell support... : $enable_gtkspell
echo Build with GCR widgets........ : $enable_gcr
echo
+echo Build with Secret Service..... : $enable_secret_service
+echo Build with GNOME Keyring...... : $enable_gnome_keyring
+echo Build with KWallet............ : $enable_kwallet
+echo
echo Build with plugin support..... : $enable_plugins
echo Build with Mono support....... : $enable_mono
echo Build with Perl support....... : $enable_perl
diff --git a/finch/gntaccount.c b/finch/gntaccount.c
index dc02aaae6c..fa66b04a97 100644
--- a/finch/gntaccount.c
+++ b/finch/gntaccount.c
@@ -195,9 +195,9 @@ save_account_cb(AccountEditDialog *dialog)
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 @@ prpl_changed_cb(GntWidget *combo, PurplePlugin *old, PurplePlugin *new, AccountE
}
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 @@ edit_account(PurpleAccount *account)
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 @@ edit_account(PurpleAccount *account)
}
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
index f1870976a9..166a1aa84d 100644
--- a/finch/gntprefs.c
+++ b/finch/gntprefs.c
@@ -1,6 +1,7 @@
/**
* @file gntprefs.c GNT Preferences API
* @ingroup finch
+ * @todo: add support for master password changing.
*/
/* finch
@@ -195,6 +196,12 @@ static Prefs logging[] =
{PURPLE_PREF_NONE, NULL, NULL, NULL},
};
+static Prefs keyring[] =
+{
+ {PURPLE_PREF_STRING, "/purple/keyring/active", N_("Active keyring"), purple_keyring_get_options},
+ {PURPLE_PREF_NONE, NULL, NULL, NULL}
+};
+
static Prefs idle[] =
{
{PURPLE_PREF_STRING, "/purple/away/idle_reporting", N_("Report Idle time"), get_idle_options},
@@ -250,6 +257,7 @@ void finch_prefs_show_all()
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);
diff --git a/libpurple/Makefile.am b/libpurple/Makefile.am
index 0e9b9a2984..8f2f5c4e68 100644
--- a/libpurple/Makefile.am
+++ b/libpurple/Makefile.am
@@ -53,6 +53,7 @@ purple_coresources = \
http.c \
idle.c \
imgstore.c \
+ keyring.c \
log.c \
media/backend-fs2.c \
media/backend-iface.c \
@@ -122,6 +123,7 @@ purple_coreheaders = \
http.h \
idle.h \
imgstore.h \
+ keyring.h \
log.h \
media.h \
media-gst.h \
diff --git a/libpurple/Makefile.mingw b/libpurple/Makefile.mingw
index a0fb6429ac..7062a3fc40 100644
--- a/libpurple/Makefile.mingw
+++ b/libpurple/Makefile.mingw
@@ -67,6 +67,7 @@ C_SRC = \
http.c \
idle.c \
imgstore.c \
+ keyring.c \
log.c \
media/candidate.c \
media/enum-types.c \
diff --git a/libpurple/account.c b/libpurple/account.c
index 8930e02378..53bc39fa11 100644
--- 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 @@ typedef struct
guint ref;
} PurpleAccountRequestInfo;
+typedef struct
+{
+ PurpleCallback cb;
+ gpointer data;
+} PurpleCallbackBundle;
+
static PurpleAccountUiOps *account_ui_ops = NULL;
static GList *accounts = NULL;
@@ -366,11 +373,33 @@ account_to_xmlnode(PurpleAccount *account)
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 @@ parse_account(xmlnode *node)
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_account(xmlnode *node)
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 @@ purple_account_destroy(PurpleAccount *account)
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 @@ purple_account_set_register_callback(PurpleAccount *account, PurpleAccountRegist
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_account_register(PurpleAccount *account)
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 @@ purple_account_register_completed(PurpleAccount *account, gboolean succeeded)
void
purple_account_unregister(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data)
{
+ PurpleCallbackBundle *cbb;
+
g_return_if_fail(account != NULL);
purple_debug_info("account", "Unregistering account %s\n",
purple_account_get_username(account));
- _purple_connection_new_unregister(account, purple_account_get_password(account), cb, user_data);
+ cbb = g_new0(PurpleCallbackBundle, 1);
+ cbb->cb = PURPLE_CALLBACK(cb);
+ cbb->data = user_data;
+
+ purple_keyring_get_password(account,
+ purple_account_unregister_got_password_cb, cbb);
}
static void
@@ -1153,11 +1222,12 @@ request_password_ok_cb(PurpleAccount *account, PurpleRequestFields *fields)
return;
}
- if(remember)
- purple_account_set_remember_password(account, TRUE);
+ if (!remember)
+ purple_keyring_set_password(account, NULL, NULL, NULL);
- 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 +1280,27 @@ purple_account_request_password(PurpleAccount *account, GCallback ok_cb,
g_free(primary);
}
+static void
+purple_account_connect_got_password_cb(PurpleAccount *account,
+ const gchar *password, GError *error, gpointer data)
+{
+ PurplePluginProtocolInfo *prpl_info = data;
+
+ if ((password == NULL || *password == '\0') &&
+ !(prpl_info->options & OPT_PROTO_NO_PASSWORD) &&
+ !(prpl_info->options & OPT_PROTO_PASSWORD_OPTIONAL))
+ purple_account_request_password(account,
+ G_CALLBACK(request_password_ok_cb),
+ G_CALLBACK(request_password_cancel_cb), account);
+ else
+ _purple_connection_new(account, FALSE, password);
+}
+
void
purple_account_connect(PurpleAccount *account)
{
PurplePlugin *prpl;
- const char *password, *username;
+ const char *username;
PurplePluginProtocolInfo *prpl_info;
g_return_if_fail(account != NULL);
@@ -1241,13 +1327,13 @@ purple_account_connect(PurpleAccount *account)
purple_debug_info("account", "Connecting to account %s.\n", username);
prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
- password = purple_account_get_password(account);
- if ((password == NULL) &&
- !(prpl_info->options & OPT_PROTO_NO_PASSWORD) &&
- !(prpl_info->options & OPT_PROTO_PASSWORD_OPTIONAL))
- purple_account_request_password(account, G_CALLBACK(request_password_ok_cb), G_CALLBACK(request_password_cancel_cb), account);
- else
- _purple_connection_new(account, FALSE, password);
+ if (account->password != NULL) {
+ purple_account_connect_got_password_cb(account,
+ account->password, NULL, prpl_info);
+ } else {
+ purple_keyring_get_password(account,
+ purple_account_connect_got_password_cb, prpl_info);
+ }
}
void
@@ -1267,8 +1353,6 @@ purple_account_disconnect(PurpleAccount *account)
gc = purple_account_get_connection(account);
_purple_connection_destroy(gc);
- if (!purple_account_get_remember_password(account))
- purple_account_set_password(account, NULL);
purple_account_set_connection(account, NULL);
account->disconnecting = FALSE;
@@ -1626,15 +1710,27 @@ purple_account_set_username(PurpleAccount *account, const char *username)
blist_ops->save_account(account);
}
-void
-purple_account_set_password(PurpleAccount *account, const char *password)
+void
+purple_account_set_password(PurpleAccount *account, const gchar *password,
+ PurpleKeyringSaveCallback cb, gpointer data)
{
g_return_if_fail(account != NULL);
- g_free(account->password);
+ purple_str_wipe(account->password);
account->password = g_strdup(password);
schedule_accounts_save();
+
+ if (!purple_account_get_remember_password(account)) {
+ purple_debug_info("account",
+ "Password for %s set, not sent to keyring.\n",
+ purple_account_get_username(account));
+
+ if (cb != NULL)
+ cb(account, NULL, data);
+ } else {
+ purple_keyring_set_password(account, password, cb, data);
+ }
}
void
@@ -2141,12 +2237,52 @@ purple_account_get_username(const PurpleAccount *account)
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);
+ PurpleCallbackBundle *cbb = data;
+ PurpleKeyringReadCallback cb;
+
+ purple_debug_info("account",
+ "Read password for account %s from async keyring.\n",
+ purple_account_get_username(account));
- return account->password;
+ purple_str_wipe(account->password);
+ account->password = g_strdup(password);
+
+ cb = (PurpleKeyringReadCallback)cbb->cb;
+ if (cb != NULL)
+ cb(account, password, error, cbb->data);
+
+ g_free(cbb);
+}
+
+void
+purple_account_get_password(PurpleAccount *account,
+ PurpleKeyringReadCallback cb, gpointer data)
+{
+ if (account == NULL) {
+ cb(NULL, NULL, NULL, data);
+ return;
+ }
+
+ if (account->password != NULL) {
+ purple_debug_info("account",
+ "Reading password for account %s from cache.\n",
+ purple_account_get_username(account));
+ cb(account, account->password, NULL, data);
+ } else {
+ PurpleCallbackBundle *cbb = g_new0(PurpleCallbackBundle, 1);
+ cbb->cb = PURPLE_CALLBACK(cb);
+ cbb->data = data;
+
+ purple_debug_info("account",
+ "Reading password for account %s from async keyring.\n",
+ purple_account_get_username(account));
+ purple_keyring_get_password(account,
+ purple_account_get_password_got, cbb);
+ }
}
const char *
@@ -2661,7 +2797,7 @@ purple_account_change_password(PurpleAccount *account, const char *orig_pw,
PurpleConnection *gc = purple_account_get_connection(account);
PurplePlugin *prpl = NULL;
- purple_account_set_password(account, new_pw);
+ purple_account_set_password(account, new_pw, NULL, NULL);
if (gc != NULL)
prpl = purple_connection_get_prpl(gc);
@@ -2768,6 +2904,14 @@ connection_error_cb(PurpleConnection *gc,
account, type, description);
}
+static void
+password_migration_cb(PurpleAccount *account)
+{
+ g_return_if_fail(account != NULL);
+
+ schedule_accounts_save();
+}
+
const PurpleConnectionErrorInfo *
purple_account_get_current_error(PurpleAccount *account)
{
@@ -2812,6 +2956,12 @@ purple_accounts_remove(PurpleAccount *account)
purple_signal_emit(purple_accounts_get_handle(), "account-removed", account);
}
+static void
+purple_accounts_delete_set(PurpleAccount *account, GError *error, gpointer data)
+{
+ purple_account_destroy(account);
+}
+
void
purple_accounts_delete(PurpleAccount *account)
{
@@ -2882,7 +3032,11 @@ purple_accounts_delete(PurpleAccount *account)
/* This will cause the deletion of an old buddy icon. */
purple_buddy_icons_set_account_icon(account, NULL, 0);
- purple_account_destroy(account);
+ /* This is async because we do not want the
+ * account being overwritten before we are done.
+ */
+ purple_keyring_set_password(account, NULL,
+ purple_accounts_delete_set, NULL);
}
void
@@ -3133,6 +3287,8 @@ purple_accounts_init(void)
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
index cb1cd85f48..564b32ba31 100644
--- a/libpurple/account.h
+++ b/libpurple/account.h
@@ -50,6 +50,7 @@ typedef void (*PurpleGetPublicAliasFailureCallback)(PurpleAccount *account, cons
#include "proxy.h"
#include "prpl.h"
#include "status.h"
+#include "keyring.h"
/**
* Account request types.
@@ -364,10 +365,18 @@ void purple_account_set_username(PurpleAccount *account, const char *username);
/**
* 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 @@ gboolean purple_account_is_disconnected(const PurpleAccount *account);
const char *purple_account_get_username(const PurpleAccount *account);
/**
- * Returns the account's password.
+ * Reads the password for the account.
*
- * @param account 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.
*
- * @return The password.
+ * @param account The account.
+ * @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/connection.c b/libpurple/connection.c
index 0baa31d677..08f97f565b 100644
--- a/libpurple/connection.c
+++ b/libpurple/connection.c
@@ -284,7 +284,7 @@ _purple_connection_destroy(PurpleConnection *gc)
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 @@ purple_connection_get_password(const PurpleConnection *gc)
{
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 @@ purple_connection_disconnect_cb(gpointer data)
{
PurpleAccount *account;
PurpleConnection *gc;
- char *password;
account = data;
gc = purple_account_get_connection(account);
@@ -520,11 +519,7 @@ purple_connection_disconnect_cb(gpointer data)
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
index 35cd236b97..d6047ce042 100644
--- 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_core_init(const char *ui)
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_core_init(const char *ui)
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 @@ purple_core_init(const char *ui)
/* 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_core_quit(void)
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
index 035c6b764a..34128a82b2 100644
--- a/libpurple/example/nullclient.c
+++ b/libpurple/example/nullclient.c
@@ -298,7 +298,7 @@ int main(int argc, char *argv[])
/* 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
index 0000000000..ebf6c48b0c
--- /dev/null
+++ b/libpurple/keyring.c
@@ -0,0 +1,1316 @@
+/**
+ * @file keyring.c Keyring API
+ * @ingroup core
+ */
+
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program ; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <glib.h>
+#include <string.h>
+#include "account.h"
+#include "keyring.h"
+#include "signals.h"
+#include "core.h"
+#include "debug.h"
+#include "internal.h"
+#include "dbus-maybe.h"
+
+struct _PurpleKeyring
+{
+ gchar *name;
+ gchar *id;
+ PurpleKeyringRead read_password;
+ PurpleKeyringSave save_password;
+ PurpleKeyringCancelRequests cancel_requests;
+ PurpleKeyringClose close_keyring;
+ PurpleKeyringChangeMaster change_master;
+ PurpleKeyringImportPassword import_password;
+ PurpleKeyringExportPassword export_password;
+};
+
+typedef struct
+{
+ GError *error;
+ PurpleKeyringSetInUseCallback cb;
+ gpointer cb_data;
+ PurpleKeyring *new;
+ PurpleKeyring *old;
+
+ /**
+ * We are done when finished is positive and read_outstanding is zero.
+ */
+ gboolean finished;
+ int read_outstanding;
+
+ gboolean abort;
+ gboolean force;
+ gboolean succeeded;
+} PurpleKeyringChangeTracker;
+
+typedef void (*PurpleKeyringDropCallback)(gpointer data);
+
+typedef struct
+{
+ PurpleKeyringDropCallback cb;
+ gpointer cb_data;
+
+ gboolean finished;
+ int drop_outstanding;
+} PurpleKeyringDropTracker;
+
+typedef struct
+{
+ PurpleKeyringSaveCallback cb;
+ gpointer cb_data;
+} PurpleKeyringSetPasswordData;
+
+typedef struct
+{
+ gchar *keyring_id;
+ gchar *mode;
+ gchar *data;
+} PurpleKeyringFailedImport;
+
+static void
+purple_keyring_change_tracker_free(PurpleKeyringChangeTracker *tracker)
+{
+ if (tracker->error)
+ g_error_free(tracker->error);
+ g_free(tracker);
+}
+
+static void
+purple_keyring_failed_import_free(PurpleKeyringFailedImport *import)
+{
+ g_return_if_fail(import != NULL);
+
+ g_free(import->keyring_id);
+ g_free(import->mode);
+ purple_str_wipe(import->data);
+ g_free(import);
+}
+
+static void
+purple_keyring_close(PurpleKeyring *keyring);
+
+static void
+purple_keyring_drop_passwords(PurpleKeyring *keyring,
+ PurpleKeyringDropCallback cb, gpointer data);
+
+/* A list of available keyrings */
+static GList *purple_keyring_keyrings = NULL;
+
+/* Keyring being used. */
+static PurpleKeyring *purple_keyring_inuse = NULL;
+
+/* Keyring id marked to use (may not be loadable). */
+static gchar *purple_keyring_to_use = NULL;
+
+static guint purple_keyring_pref_cbid = 0;
+static GList *purple_keyring_loaded_plugins = NULL;
+static PurpleKeyringChangeTracker *current_change_tracker = NULL;
+static gboolean purple_keyring_is_quitting = FALSE;
+static GHashTable *purple_keyring_failed_imports = NULL;
+
+static const gchar *
+purple_keyring_print_account(PurpleAccount *account)
+{
+ static gchar print_buff[100];
+
+ if (account == NULL) {
+ g_snprintf(print_buff, 100, "(null)");
+ return print_buff;
+ }
+
+ g_snprintf(print_buff, 100, "%s:%s",
+ purple_account_get_protocol_id(account),
+ purple_account_get_username(account));
+ return print_buff;
+}
+
+/**************************************************************************/
+/* Setting used keyrings */
+/**************************************************************************/
+
+PurpleKeyring *
+purple_keyring_find_keyring_by_id(const gchar *id)
+{
+ GList *it;
+
+ for (it = purple_keyring_keyrings; it != NULL; it = it->next) {
+ PurpleKeyring *keyring = it->data;
+ const gchar *curr_id = purple_keyring_get_id(keyring);
+
+ if (g_strcmp0(id, curr_id) == 0)
+ return keyring;
+ }
+
+ return NULL;
+}
+
+static void
+purple_keyring_pref_callback(const gchar *pref, PurplePrefType type,
+ gconstpointer id, gpointer data)
+{
+ PurpleKeyring *new_keyring;
+
+ g_return_if_fail(g_strcmp0(pref, "/purple/keyring/active") == 0);
+ g_return_if_fail(type == PURPLE_PREF_STRING);
+ g_return_if_fail(id != NULL);
+
+ new_keyring = purple_keyring_find_keyring_by_id(id);
+ g_return_if_fail(new_keyring != NULL);
+
+ purple_keyring_set_inuse(new_keyring, FALSE, NULL, NULL);
+}
+
+static void
+purple_keyring_pref_connect(void)
+{
+ g_return_if_fail(purple_keyring_pref_cbid == 0);
+
+ purple_keyring_pref_cbid = purple_prefs_connect_callback(NULL,
+ "/purple/keyring/active", purple_keyring_pref_callback, NULL);
+}
+
+static void
+purple_keyring_pref_disconnect(void)
+{
+ g_return_if_fail(purple_keyring_pref_cbid != 0);
+
+ purple_prefs_disconnect_callback(purple_keyring_pref_cbid);
+ purple_keyring_pref_cbid = 0;
+}
+
+PurpleKeyring *
+purple_keyring_get_inuse(void)
+{
+ return purple_keyring_inuse;
+}
+
+static void
+purple_keyring_set_inuse_drop_cb(gpointer _tracker)
+{
+ PurpleKeyringChangeTracker *tracker = _tracker;
+
+ g_return_if_fail(tracker != NULL);
+
+ if (tracker->succeeded) {
+ purple_keyring_close(tracker->old);
+
+ purple_debug_info("keyring", "Successfully changed keyring.\n");
+
+ purple_keyring_inuse = tracker->new;
+ current_change_tracker = NULL;
+
+ if (tracker->cb != NULL)
+ tracker->cb(NULL, tracker->cb_data);
+ purple_keyring_change_tracker_free(tracker);
+ return;
+ }
+
+ purple_debug_error("keyring", "Failed to change keyring, aborting.\n");
+
+ purple_keyring_close(tracker->new);
+
+ purple_keyring_pref_disconnect();
+ purple_prefs_set_string("/purple/keyring/active",
+ purple_keyring_get_id(tracker->old));
+ purple_keyring_pref_connect();
+
+ current_change_tracker = NULL;
+
+ if (tracker->error == NULL) {
+ tracker->error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_UNKNOWN,
+ "Unknown error has occured");
+ }
+
+ if (tracker->cb != NULL)
+ tracker->cb(tracker->error, tracker->cb_data);
+
+ purple_keyring_change_tracker_free(tracker);
+}
+
+static void
+purple_keyring_set_inuse_save_cb(PurpleAccount *account, GError *error,
+ gpointer _tracker)
+{
+ PurpleKeyringChangeTracker *tracker = _tracker;
+
+ g_return_if_fail(account != NULL);
+ g_return_if_fail(tracker != NULL);
+
+ tracker->read_outstanding--;
+
+ if (g_error_matches(error, PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOPASSWORD)) {
+ if (purple_debug_is_verbose()) {
+ purple_debug_misc("keyring", "No password found while "
+ "changing keyring for account %s: %s.\n",
+ purple_keyring_print_account(account),
+ error->message);
+ }
+ } else if (g_error_matches(error, PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_ACCESSDENIED)) {
+ purple_debug_info("keyring", "Access denied while changing "
+ "keyring for account %s: %s.\n",
+ purple_keyring_print_account(account), error->message);
+ tracker->abort = TRUE;
+ if (tracker->error != NULL)
+ g_error_free(tracker->error);
+ tracker->error = g_error_copy(error);
+ } else if (g_error_matches(error, PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_CANCELLED)) {
+ purple_debug_info("keyring", "Operation cancelled while "
+ "changing keyring for account %s: %s.\n",
+ purple_keyring_print_account(account), error->message);
+ tracker->abort = TRUE;
+ if (tracker->error == NULL)
+ tracker->error = g_error_copy(error);
+ } else if (g_error_matches(error, PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_BACKENDFAIL)) {
+ purple_debug_error("keyring", "Failed to communicate with "
+ "backend while changing keyring for account %s: %s. "
+ "Aborting changes.\n",
+ purple_keyring_print_account(account), error->message);
+ tracker->abort = TRUE;
+ if (tracker->error != NULL)
+ g_error_free(tracker->error);
+ tracker->error = g_error_copy(error);
+ } else if (error != NULL) {
+ purple_debug_error("keyring", "Unknown error while changing "
+ "keyring for account %s: %s. Aborting changes.\n",
+ purple_keyring_print_account(account), error->message);
+ tracker->abort = TRUE;
+ if (tracker->error == NULL)
+ tracker->error = g_error_copy(error);
+ }
+
+ purple_signal_emit(purple_keyring_get_handle(), "password-migration",
+ account);
+
+ if (!tracker->finished || tracker->read_outstanding > 0)
+ return;
+
+ /* This was the last one. */
+ if (tracker->abort && !tracker->force) {
+ tracker->succeeded = FALSE;
+ purple_keyring_drop_passwords(tracker->new,
+ purple_keyring_set_inuse_drop_cb, tracker);
+ } else {
+ tracker->succeeded = TRUE;
+ purple_keyring_drop_passwords(tracker->old,
+ purple_keyring_set_inuse_drop_cb, tracker);
+ }
+}
+
+static void
+purple_keyring_set_inuse_read_cb(PurpleAccount *account, const gchar *password,
+ GError *error, gpointer _tracker)
+{
+ PurpleKeyringChangeTracker *tracker = _tracker;
+ PurpleKeyringSave save_cb;
+
+ g_return_if_fail(account != NULL);
+ g_return_if_fail(tracker != NULL);
+
+ if (tracker->abort) {
+ purple_keyring_set_inuse_save_cb(account, NULL, tracker);
+ return;
+ }
+
+ if (error != NULL) {
+ if (tracker->force == TRUE || g_error_matches(error,
+ PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOPASSWORD)) {
+ /* Don't save password, and ignore it. */
+ } else {
+ tracker->abort = TRUE;
+ }
+ purple_keyring_set_inuse_save_cb(account, error, tracker);
+ return;
+ }
+
+ save_cb = purple_keyring_get_save_password(tracker->new);
+ g_assert(save_cb != NULL);
+
+ save_cb(account, password, purple_keyring_set_inuse_save_cb, tracker);
+}
+
+void
+purple_keyring_set_inuse(PurpleKeyring *newkeyring, gboolean force,
+ PurpleKeyringSetInUseCallback cb, gpointer data)
+{
+ PurpleKeyring *oldkeyring;
+ PurpleKeyringChangeTracker *tracker;
+ GList *it;
+ PurpleKeyringRead read_cb;
+
+ if (current_change_tracker != NULL) {
+ GError *error;
+ purple_debug_error("keyring", "There is password migration "
+ "session already running.\n");
+ if (cb == NULL)
+ return;
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_INTERNAL,
+ "There is a password migration session already running");
+ cb(error, data);
+ g_error_free(error);
+ return;
+ }
+
+ oldkeyring = purple_keyring_get_inuse();
+
+ if (oldkeyring == newkeyring) {
+ if (purple_debug_is_verbose()) {
+ purple_debug_misc("keyring",
+ "Old and new keyring are the same: %s.\n",
+ (newkeyring != NULL) ?
+ newkeyring->id : "(null)");
+ }
+ if (cb != NULL)
+ cb(NULL, data);
+ return;
+ }
+
+ purple_debug_info("keyring", "Attempting to set new keyring: %s.\n",
+ (newkeyring != NULL) ? newkeyring->id : "(null)");
+
+ if (oldkeyring == NULL) { /* No keyring was set before. */
+ if (purple_debug_is_verbose()) {
+ purple_debug_misc("keyring", "Setting keyring for the "
+ "first time: %s.\n", newkeyring->id);
+ }
+
+ purple_keyring_inuse = newkeyring;
+ g_assert(current_change_tracker == NULL);
+ if (cb != NULL)
+ cb(NULL, data);
+ return;
+ }
+
+ /* Starting a migration. */
+
+ read_cb = purple_keyring_get_read_password(oldkeyring);
+ g_assert(read_cb != NULL);
+
+ purple_debug_misc("keyring", "Starting migration from: %s.\n",
+ oldkeyring->id);
+
+ tracker = g_new0(PurpleKeyringChangeTracker, 1);
+ current_change_tracker = tracker;
+
+ tracker->cb = cb;
+ tracker->cb_data = data;
+ tracker->new = newkeyring;
+ tracker->old = oldkeyring;
+ tracker->force = force;
+
+ for (it = purple_accounts_get_all(); it != NULL; it = it->next) {
+ if (tracker->abort) {
+ tracker->finished = TRUE;
+ break;
+ }
+ tracker->read_outstanding++;
+
+ if (it->next == NULL)
+ tracker->finished = TRUE;
+
+ read_cb(it->data, purple_keyring_set_inuse_read_cb, tracker);
+ }
+}
+
+void
+purple_keyring_register(PurpleKeyring *keyring)
+{
+ const gchar *keyring_id;
+
+ g_return_if_fail(keyring != NULL);
+
+ keyring_id = purple_keyring_get_id(keyring);
+
+ purple_debug_info("keyring", "Registering keyring: %s\n",
+ keyring_id ? keyring_id : "(null)");
+
+ if (purple_keyring_get_id(keyring) == NULL ||
+ purple_keyring_get_name(keyring) == NULL ||
+ purple_keyring_get_read_password(keyring) == NULL ||
+ purple_keyring_get_save_password(keyring) == NULL) {
+ purple_debug_error("keyring", "Cannot register %s, some "
+ "required fields are missing.\n",
+ keyring_id ? keyring_id : "(null)");
+ return;
+ }
+
+ if (purple_keyring_find_keyring_by_id(keyring_id) != NULL) {
+ purple_debug_error("keyring",
+ "Keyring is already registered.\n");
+ return;
+ }
+
+ /* If this is the configured keyring, use it. */
+ if (purple_keyring_inuse == NULL &&
+ g_strcmp0(keyring_id, purple_keyring_to_use) == 0) {
+ purple_debug_info("keyring", "Keyring %s matches keyring to "
+ "use, using it.\n", keyring_id);
+ purple_keyring_set_inuse(keyring, TRUE, NULL, NULL);
+ }
+
+ PURPLE_DBUS_REGISTER_POINTER(keyring, PurpleKeyring);
+ purple_signal_emit(purple_keyring_get_handle(), "keyring-register",
+ keyring_id, keyring);
+ if (purple_debug_is_verbose()) {
+ purple_debug_info("keyring", "Registered keyring: %s.\n",
+ keyring_id);
+ }
+
+ purple_keyring_keyrings = g_list_prepend(purple_keyring_keyrings,
+ keyring);
+}
+
+void
+purple_keyring_unregister(PurpleKeyring *keyring)
+{
+ PurpleKeyring *inuse;
+ PurpleKeyring *fallback;
+ const gchar *keyring_id;
+
+ g_return_if_fail(keyring != NULL);
+
+ keyring_id = purple_keyring_get_id(keyring);
+
+ purple_debug_info("keyring", "Unregistering keyring: %s.\n",
+ keyring_id);
+
+ purple_signal_emit(purple_keyring_get_handle(), "keyring-unregister",
+ keyring_id, keyring);
+ PURPLE_DBUS_UNREGISTER_POINTER(keyring);
+
+ inuse = purple_keyring_get_inuse();
+ fallback = purple_keyring_find_keyring_by_id(PURPLE_DEFAULT_KEYRING);
+
+ if (inuse == keyring) {
+ if (inuse != fallback) {
+ purple_keyring_set_inuse(fallback, TRUE, NULL, NULL);
+ } else {
+ fallback = NULL;
+ purple_keyring_set_inuse(NULL, TRUE, NULL, NULL);
+ }
+ }
+
+ purple_keyring_keyrings = g_list_remove(purple_keyring_keyrings,
+ keyring);
+}
+
+GList *
+purple_keyring_get_options(void)
+{
+ GList *options = NULL;
+ GList *it;
+ static gchar currentDisabledName[40];
+
+ if (purple_keyring_get_inuse() == NULL && purple_keyring_to_use != NULL
+ && purple_keyring_to_use[0] != '\0') {
+ g_snprintf(currentDisabledName, sizeof(currentDisabledName),
+ _("%s (disabled)"), purple_keyring_to_use);
+
+ options = g_list_append(options, currentDisabledName);
+ options = g_list_append(options, purple_keyring_to_use);
+ }
+
+ for (it = purple_keyring_keyrings; it != NULL; it = it->next) {
+ PurpleKeyring *keyring = it->data;
+
+ options = g_list_append(options,
+ (gpointer)purple_keyring_get_name(keyring));
+ options = g_list_append(options,
+ (gpointer)purple_keyring_get_id(keyring));
+ }
+
+ return options;
+}
+
+
+/**************************************************************************/
+/* Keyring plugin wrappers */
+/**************************************************************************/
+
+static void
+purple_keyring_close(PurpleKeyring *keyring)
+{
+ PurpleKeyringClose close_cb;
+
+ g_return_if_fail(keyring != NULL);
+
+ close_cb = purple_keyring_get_close_keyring(keyring);
+
+ if (close_cb != NULL)
+ close_cb();
+}
+
+static void
+purple_keyring_drop_passwords_save_cb(PurpleAccount *account, GError *error,
+ gpointer _tracker)
+{
+ PurpleKeyringDropTracker *tracker = _tracker;
+
+ tracker->drop_outstanding--;
+
+ if (!tracker->finished || tracker->drop_outstanding > 0)
+ return;
+
+ if (tracker->cb)
+ tracker->cb(tracker->cb_data);
+ g_free(tracker);
+}
+
+static void
+purple_keyring_drop_passwords(PurpleKeyring *keyring,
+ PurpleKeyringDropCallback cb, gpointer data)
+{
+ GList *it;
+ PurpleKeyringSave save_cb;
+ PurpleKeyringDropTracker *tracker;
+
+ g_return_if_fail(keyring != NULL);
+
+ save_cb = purple_keyring_get_save_password(keyring);
+ g_assert(save_cb != NULL);
+
+ tracker = g_new0(PurpleKeyringDropTracker, 1);
+ tracker->cb = cb;
+ tracker->cb_data = data;
+
+ for (it = purple_accounts_get_all(); it != NULL; it = it->next) {
+ PurpleAccount *account = it->data;
+
+ tracker->drop_outstanding++;
+ if (it->next == NULL)
+ tracker->finished = TRUE;
+
+ save_cb(account, NULL, purple_keyring_drop_passwords_save_cb,
+ tracker);
+ }
+}
+
+gboolean
+purple_keyring_import_password(PurpleAccount *account, const gchar *keyring_id,
+ const gchar *mode, const gchar *data, GError **error)
+{
+ PurpleKeyring *keyring;
+ PurpleKeyring *inuse;
+ PurpleKeyringImportPassword import;
+
+ g_return_val_if_fail(account != NULL, FALSE);
+
+ if (keyring_id == NULL)
+ keyring_id = PURPLE_DEFAULT_KEYRING;
+
+ purple_debug_misc("keyring", "Importing password for account %s to "
+ "keyring %s.\n", purple_keyring_print_account(account),
+ keyring_id);
+
+ keyring = purple_keyring_find_keyring_by_id(keyring_id);
+ if (keyring == NULL) {
+ if (error != NULL) {
+ *error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_BACKENDFAIL,
+ "Specified keyring is not registered.");
+ }
+ purple_debug_warning("Keyring", "Specified keyring is not "
+ "registered, cannot import password info for account "
+ "%s.\n", purple_keyring_print_account(account));
+ return FALSE;
+ }
+
+ inuse = purple_keyring_get_inuse();
+ if (inuse == NULL) {
+ PurpleKeyringFailedImport *import;
+ if (error != NULL) {
+ *error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOKEYRING,
+ "No keyring loaded, cannot import password "
+ "info");
+ }
+ purple_debug_warning("Keyring",
+ "No keyring loaded, cannot import password info for "
+ "account %s.\n", purple_keyring_print_account(account));
+
+ import = g_new0(PurpleKeyringFailedImport, 1);
+ import->keyring_id = g_strdup(keyring_id);
+ import->mode = g_strdup(mode);
+ import->data = g_strdup(data);
+ g_hash_table_insert(purple_keyring_failed_imports, account,
+ import);
+ return FALSE;
+ }
+
+ if (inuse != keyring) {
+ if (error != NULL) {
+ *error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_INTERNAL,
+ "Specified keyring id does not match the "
+ "loaded one.");
+ }
+ purple_debug_error("keyring",
+ "Specified keyring %s is not currently used (%s). "
+ "Data will be lost.\n", keyring_id,
+ purple_keyring_get_id(inuse));
+ return FALSE;
+ }
+
+ import = purple_keyring_get_import_password(inuse);
+ if (import == NULL) {
+ if (purple_debug_is_verbose()) {
+ purple_debug_misc("Keyring", "Configured keyring "
+ "cannot import password info. This might be "
+ "normal.\n");
+ }
+ return TRUE;
+ }
+
+ return import(account, mode, data, error);
+}
+
+gboolean
+purple_keyring_export_password(PurpleAccount *account, const gchar **keyring_id,
+ const gchar **mode, gchar **data, GError **error,
+ GDestroyNotify *destroy)
+{
+ PurpleKeyring *inuse;
+ PurpleKeyringExportPassword export;
+
+ g_return_val_if_fail(account != NULL, FALSE);
+ g_return_val_if_fail(keyring_id != NULL, FALSE);
+ g_return_val_if_fail(mode != NULL, FALSE);
+ g_return_val_if_fail(data != NULL, FALSE);
+ g_return_val_if_fail(error != NULL, FALSE);
+
+ inuse = purple_keyring_get_inuse();
+
+ if (inuse == NULL) {
+ PurpleKeyringFailedImport *import = g_hash_table_lookup(
+ purple_keyring_failed_imports, account);
+
+ if (import == NULL) {
+ *error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOKEYRING,
+ "No keyring configured, cannot export password "
+ "info");
+ purple_debug_warning("keyring",
+ "No keyring configured, cannot export password "
+ "info.\n");
+ return FALSE;
+ } else {
+ purple_debug_info("keyring", "No keyring configured, "
+ "getting fallback export data for %s.\n",
+ purple_keyring_print_account(account));
+
+ *keyring_id = import->keyring_id;
+ *mode = import->mode;
+ *data = g_strdup(import->data);
+ *destroy = (GDestroyNotify)purple_str_wipe;
+ return TRUE;
+ }
+ }
+
+ if (purple_debug_is_verbose()) {
+ purple_debug_misc("keyring",
+ "Exporting password for account %s from keyring %s\n",
+ purple_keyring_print_account(account),
+ purple_keyring_get_id(inuse));
+ }
+
+ *keyring_id = purple_keyring_get_id(inuse);
+
+ export = purple_keyring_get_export_password(inuse);
+ if (export == NULL) {
+ if (purple_debug_is_verbose()) {
+ purple_debug_misc("Keyring", "Configured keyring "
+ "cannot export password info. This might be "
+ "normal.\n");
+ }
+ *mode = NULL;
+ *data = NULL;
+ *destroy = NULL;
+ return TRUE;
+ }
+
+ return export(account, mode, data, error, destroy);
+}
+
+void
+purple_keyring_get_password(PurpleAccount *account,
+ PurpleKeyringReadCallback cb, gpointer data)
+{
+ GError *error;
+ PurpleKeyring *inuse;
+ PurpleKeyringRead read_cb;
+
+ g_return_if_fail(account != NULL);
+
+ if (purple_keyring_is_quitting) {
+ purple_debug_error("keyring", "Cannot request a password while "
+ "quitting.\n");
+ if (cb == NULL)
+ return;
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_INTERNAL,
+ "Cannot request a password while quitting.");
+ cb(account, NULL, error, data);
+ g_error_free(error);
+ return;
+ }
+
+ inuse = purple_keyring_get_inuse();
+
+ if (inuse == NULL) {
+ purple_debug_error("keyring", "No keyring configured.\n");
+ if (cb == NULL)
+ return;
+
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOKEYRING,
+ "No keyring configured.");
+ cb(account, NULL, error, data);
+ g_error_free(error);
+ }
+
+ read_cb = purple_keyring_get_read_password(inuse);
+ g_assert(read_cb != NULL);
+
+ purple_debug_info("keyring", "Reading password for account %s...\n",
+ purple_keyring_print_account(account));
+ read_cb(account, cb, data);
+}
+
+static void
+purple_keyring_set_password_save_cb(PurpleAccount *account, GError *error,
+ gpointer _set_data)
+{
+ PurpleKeyringSetPasswordData *set_data = _set_data;
+
+ g_return_if_fail(account != NULL);
+ g_return_if_fail(set_data != NULL);
+
+ if (error == NULL && purple_debug_is_verbose()) {
+ purple_debug_misc("keyring", "Password for account %s "
+ "saved successfully.\n",
+ purple_keyring_print_account(account));
+ } else if (purple_debug_is_verbose()) {
+ purple_debug_warning("keyring", "Password for account %s "
+ "not saved successfully.\n",
+ purple_keyring_print_account(account));
+ }
+
+ if (error != NULL) {
+ purple_notify_error(NULL, _("Keyrings"),
+ _("Failed to save a password in keyring."),
+ error->message);
+ }
+
+ if (set_data->cb != NULL)
+ set_data->cb(account, error, set_data->cb_data);
+ g_free(set_data);
+}
+
+void
+purple_keyring_set_password(PurpleAccount *account, const gchar *password,
+ PurpleKeyringSaveCallback cb, gpointer data)
+{
+ GError *error;
+ PurpleKeyring *inuse;
+ PurpleKeyringSave save_cb;
+ PurpleKeyringSetPasswordData *set_data;
+
+ g_return_if_fail(account != NULL);
+
+ if (purple_keyring_is_quitting) {
+ purple_debug_error("keyring", "Cannot save a password while "
+ "quitting.\n");
+ if (cb == NULL)
+ return;
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_INTERNAL,
+ "Cannot save a password while quitting.");
+ cb(account, error, data);
+ g_error_free(error);
+ return;
+ }
+
+ if (current_change_tracker != NULL) {
+ purple_debug_error("keyring", "Cannot save a password during "
+ "password migration.\n");
+ if (cb == NULL)
+ return;
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_INTERNAL,
+ "Cannot save a password during password migration.");
+ cb(account, error, data);
+ g_error_free(error);
+ return;
+ }
+
+ inuse = purple_keyring_get_inuse();
+ if (inuse == NULL) {
+ if (cb == NULL)
+ return;
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOKEYRING,
+ "No keyring configured.");
+ cb(account, error, data);
+ g_error_free(error);
+ return;
+ }
+
+ save_cb = purple_keyring_get_save_password(inuse);
+ g_assert(save_cb != NULL);
+
+ set_data = g_new(PurpleKeyringSetPasswordData, 1);
+ set_data->cb = cb;
+ set_data->cb_data = data;
+ purple_debug_info("keyring", "%s password for account %s...\n",
+ (password ? "Saving" : "Removing"),
+ purple_keyring_print_account(account));
+ save_cb(account, password, purple_keyring_set_password_save_cb, set_data);
+}
+
+/* TODO: is it usable at all? */
+void
+purple_keyring_change_master(PurpleKeyringChangeMasterCallback cb,
+ gpointer data)
+{
+ GError *error;
+ PurpleKeyring *inuse;
+ PurpleKeyringChangeMaster change;
+
+ if (purple_keyring_is_quitting || current_change_tracker != NULL) {
+ purple_debug_error("keyring", "Cannot change a master password "
+ "at the moment.\n");
+ if (cb == NULL)
+ return;
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_INTERNAL,
+ "Cannot change a master password at the moment.");
+ cb(error, data);
+ g_error_free(error);
+ return;
+ }
+
+ inuse = purple_keyring_get_inuse();
+ if (inuse == NULL) {
+ purple_debug_error("keyring", "No keyring configured, cannot "
+ "change master password.\n");
+ if (cb == NULL)
+ return;
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOKEYRING,
+ "No keyring configured, cannot change master "
+ "password.");
+ cb(error, data);
+ g_error_free(error);
+ return;
+ }
+
+ change = purple_keyring_get_change_master(inuse);
+ if (change == NULL) {
+ purple_debug_error("keyring", "Keyring doesn't support master "
+ "passwords.\n");
+ if (cb == NULL)
+ return;
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_BACKENDFAIL,
+ "Keyring doesn't support master passwords.");
+ cb(error, data);
+ g_error_free(error);
+ return;
+ }
+
+ change(cb, data);
+}
+
+
+/**************************************************************************/
+/* PurpleKeyring accessors */
+/**************************************************************************/
+
+PurpleKeyring *
+purple_keyring_new(void)
+{
+ return g_new0(PurpleKeyring, 1);
+}
+
+void
+purple_keyring_free(PurpleKeyring *keyring)
+{
+ g_return_if_fail(keyring != NULL);
+
+ g_free(keyring->name);
+ g_free(keyring->id);
+ g_free(keyring);
+}
+
+const gchar *
+purple_keyring_get_name(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->name;
+}
+
+const gchar *
+purple_keyring_get_id(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->id;
+}
+
+PurpleKeyringRead
+purple_keyring_get_read_password(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->read_password;
+}
+
+PurpleKeyringSave
+purple_keyring_get_save_password(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->save_password;
+}
+
+PurpleKeyringCancelRequests
+purple_keyring_get_cancel_requests(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->cancel_requests;
+}
+
+PurpleKeyringClose
+purple_keyring_get_close_keyring(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->close_keyring;
+}
+
+PurpleKeyringChangeMaster
+purple_keyring_get_change_master(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->change_master;
+}
+
+PurpleKeyringImportPassword
+purple_keyring_get_import_password(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->import_password;
+}
+
+PurpleKeyringExportPassword
+purple_keyring_get_export_password(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->export_password;
+}
+
+void
+purple_keyring_set_name(PurpleKeyring *keyring, const gchar *name)
+{
+ g_return_if_fail(keyring != NULL);
+ g_return_if_fail(name != NULL);
+
+ g_free(keyring->name);
+ keyring->name = g_strdup(name);
+}
+
+void
+purple_keyring_set_id(PurpleKeyring *keyring, const gchar *id)
+{
+ g_return_if_fail(keyring != NULL);
+ g_return_if_fail(id != NULL);
+
+ g_free(keyring->id);
+ keyring->id = g_strdup(id);
+}
+
+void
+purple_keyring_set_read_password(PurpleKeyring *keyring,
+ PurpleKeyringRead read_cb)
+{
+ g_return_if_fail(keyring != NULL);
+ g_return_if_fail(read_cb != NULL);
+
+ keyring->read_password = read_cb;
+}
+
+void
+purple_keyring_set_save_password(PurpleKeyring *keyring,
+ PurpleKeyringSave save_cb)
+{
+ g_return_if_fail(keyring != NULL);
+ g_return_if_fail(save_cb != NULL);
+
+ keyring->save_password = save_cb;
+}
+
+void
+purple_keyring_set_cancel_requests(PurpleKeyring *keyring,
+ PurpleKeyringCancelRequests cancel_requests)
+{
+ g_return_if_fail(keyring != NULL);
+
+ keyring->cancel_requests = cancel_requests;
+}
+
+void
+purple_keyring_set_close_keyring(PurpleKeyring *keyring,
+ PurpleKeyringClose close_cb)
+{
+ g_return_if_fail(keyring != NULL);
+
+ keyring->close_keyring = close_cb;
+}
+
+void
+purple_keyring_set_change_master(PurpleKeyring *keyring,
+ PurpleKeyringChangeMaster change_master)
+{
+ g_return_if_fail(keyring != NULL);
+
+ keyring->change_master = change_master;
+}
+
+void
+purple_keyring_set_import_password(PurpleKeyring *keyring,
+ PurpleKeyringImportPassword import_password)
+{
+ g_return_if_fail(keyring != NULL);
+
+ keyring->import_password = import_password;
+}
+
+void
+purple_keyring_set_export_password(PurpleKeyring *keyring,
+ PurpleKeyringExportPassword export_password)
+{
+ g_return_if_fail(keyring != NULL);
+
+ keyring->export_password = export_password;
+}
+
+
+/**************************************************************************/
+/* Error Codes */
+/**************************************************************************/
+
+GQuark purple_keyring_error_domain(void)
+{
+ return g_quark_from_static_string("libpurple keyring");
+}
+
+/**************************************************************************/
+/* Keyring Subsystem */
+/**************************************************************************/
+
+static void purple_keyring_core_initialized_cb(void)
+{
+ if (purple_keyring_inuse == NULL) {
+ purple_notify_error(NULL, _("Keyrings"),
+ _("Failed to load selected keyring."),
+ _("Check your system configuration or select another "
+ "one in Preferences dialog."));
+ }
+}
+
+static void purple_keyring_core_quitting_cb()
+{
+ PurpleKeyringCancelRequests cancel;
+
+ if (current_change_tracker != NULL) {
+ PurpleKeyringChangeTracker *tracker = current_change_tracker;
+ tracker->abort = TRUE;
+ if (tracker->old) {
+ cancel = purple_keyring_get_cancel_requests(
+ tracker->old);
+ if (cancel)
+ cancel();
+ }
+ if (current_change_tracker == tracker && tracker->new) {
+ cancel = purple_keyring_get_cancel_requests(
+ tracker->new);
+ if (cancel)
+ cancel();
+ }
+ }
+
+ purple_keyring_is_quitting = TRUE;
+ if (purple_keyring_inuse != NULL) {
+ cancel = purple_keyring_get_cancel_requests(
+ purple_keyring_inuse);
+ if (cancel)
+ cancel();
+ }
+}
+
+void
+purple_keyring_init(void)
+{
+ const gchar *touse;
+ GList *it;
+
+ purple_keyring_keyrings = NULL;
+ purple_keyring_inuse = NULL;
+
+ purple_keyring_failed_imports = g_hash_table_new_full(g_direct_hash,
+ g_direct_equal, NULL,
+ (GDestroyNotify)purple_keyring_failed_import_free);
+
+ /* void keyring_register(const char *keyring_id,
+ * PurpleKeyring * keyring);
+ *
+ * A signal called when keyring is registered.
+ *
+ * @param keyring_id The keyring ID.
+ * @param keyring The keyring.
+ */
+ purple_signal_register(purple_keyring_get_handle(),
+ "keyring-register",
+ purple_marshal_VOID__POINTER_POINTER,
+ NULL, 2,
+ purple_value_new(PURPLE_TYPE_STRING),
+ purple_value_new(PURPLE_TYPE_BOXED, "PurpleKeyring *"));
+
+ /* void keyring_unregister(const char *keyring_id,
+ * PurpleKeyring * keyring);
+ *
+ * A signal called when keyring is unregistered.
+ *
+ * @param keyring_id The keyring ID.
+ * @param keyring The keyring.
+ */
+ purple_signal_register(purple_keyring_get_handle(),
+ "keyring-unregister",
+ purple_marshal_VOID__POINTER_POINTER,
+ NULL, 2,
+ purple_value_new(PURPLE_TYPE_STRING),
+ purple_value_new(PURPLE_TYPE_BOXED, "PurpleKeyring *"));
+
+ /* void password_migration(PurpleAccount* account);
+ *
+ * A signal called, when a password for the account was moved to another
+ * keyring.
+ *
+ * @param account The account.
+ */
+ purple_signal_register(purple_keyring_get_handle(),
+ "password-migration",
+ purple_marshal_VOID__POINTER,
+ NULL, 1,
+ purple_value_new(PURPLE_TYPE_BOXED, "PurpleAccount *"));
+
+ touse = purple_prefs_get_string("/purple/keyring/active");
+ if (touse == NULL) {
+ purple_prefs_add_none("/purple/keyring");
+ purple_prefs_add_string("/purple/keyring/active",
+ PURPLE_DEFAULT_KEYRING);
+ purple_keyring_to_use = g_strdup(PURPLE_DEFAULT_KEYRING);
+ } else
+ purple_keyring_to_use = g_strdup(touse);
+
+ purple_keyring_pref_connect();
+
+ for (it = purple_plugins_get_all(); it != NULL; it = it->next)
+ {
+ PurplePlugin *plugin = (PurplePlugin *)it->data;
+
+ if (plugin->info == NULL || plugin->info->id == NULL)
+ continue;
+ if (strncmp(plugin->info->id, "keyring-", 8) != 0)
+ continue;
+
+ if (purple_plugin_is_loaded(plugin))
+ continue;
+
+ if (purple_plugin_load(plugin))
+ {
+ purple_keyring_loaded_plugins = g_list_append(
+ purple_keyring_loaded_plugins, plugin);
+ }
+ }
+
+ if (purple_keyring_inuse == NULL)
+ purple_debug_error("keyring", "Selected keyring failed to load\n");
+
+ purple_signal_connect(purple_get_core(), "core-initialized",
+ purple_keyring_get_handle(),
+ PURPLE_CALLBACK(purple_keyring_core_initialized_cb), NULL);
+ purple_signal_connect(purple_get_core(), "quitting",
+ purple_keyring_get_handle(),
+ PURPLE_CALLBACK(purple_keyring_core_quitting_cb), NULL);
+}
+
+void
+purple_keyring_uninit(void)
+{
+ GList *it;
+
+ purple_keyring_pref_disconnect();
+
+ g_free(purple_keyring_to_use);
+ purple_keyring_inuse = NULL;
+
+ g_hash_table_destroy(purple_keyring_failed_imports);
+ purple_keyring_failed_imports = NULL;
+
+ for (it = g_list_first(purple_keyring_loaded_plugins); it != NULL;
+ it = g_list_next(it))
+ {
+ PurplePlugin *plugin = (PurplePlugin *)it->data;
+ if (g_list_find(purple_plugins_get_loaded(), plugin) == NULL)
+ continue;
+ purple_plugin_unload(plugin);
+ }
+ g_list_free(purple_keyring_loaded_plugins);
+ purple_keyring_loaded_plugins = NULL;
+
+ purple_signals_unregister_by_instance(purple_keyring_get_handle());
+ purple_signals_disconnect_by_handle(purple_keyring_get_handle());
+}
+
+void *
+purple_keyring_get_handle(void)
+{
+ static int handle;
+
+ return &handle;
+}
diff --git a/libpurple/keyring.h b/libpurple/keyring.h
new file mode 100644
index 0000000000..ea1d8aed4d
--- /dev/null
+++ b/libpurple/keyring.h
@@ -0,0 +1,522 @@
+/**
+ * @file keyring.h Keyring API
+ * @ingroup core
+ */
+
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _PURPLE_KEYRING_H_
+#define _PURPLE_KEYRING_H_
+
+#include "account.h"
+
+/**
+ * Default keyring ID.
+ */
+#define PURPLE_DEFAULT_KEYRING "keyring-internal"
+
+/**
+ * Keyring subsystem error domain.
+ */
+#define PURPLE_KEYRING_ERROR purple_keyring_error_domain()
+
+/**************************************************************************/
+/** @name Data structures and types */
+/**************************************************************************/
+/*@{*/
+
+typedef struct _PurpleKeyring PurpleKeyring;
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Callbacks for keyrings access functions */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Callback for once a password is read.
+ *
+ * If there was a problem, the password will be NULL, and the error set.
+ *
+ * @param account The account.
+ * @param password The password.
+ * @param error Error that may have occurred.
+ * @param data Data passed to the callback.
+ */
+typedef void (*PurpleKeyringReadCallback)(PurpleAccount *account,
+ const gchar *password, GError *error, gpointer data);
+
+/**
+ * Callback for once a password has been stored.
+ *
+ * If there was a problem, the error will be set.
+ *
+ * @param account The account.
+ * @param error Error that may have occurred.
+ * @param data Data passed to the callback.
+ */
+typedef void (*PurpleKeyringSaveCallback)(PurpleAccount *account, GError *error,
+ gpointer data);
+
+/**
+ * Callback for once the master password for a keyring has been changed.
+ *
+ * @param error Error that has occurred.
+ * @param data Data passed to the callback.
+ */
+typedef void (*PurpleKeyringChangeMasterCallback)(GError *error, gpointer data);
+
+/**
+ * Callback for when we change the keyring.
+ *
+ * @param error An error that might have occurred.
+ * @param data A pointer to user supplied data.
+ */
+typedef void (*PurpleKeyringSetInUseCallback)(GError *error, gpointer data);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Keyrings access functions */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Read the password for an account.
+ *
+ * @param account The account.
+ * @param cb A callback for once the password is found.
+ * @param data Data to be passed to the callback.
+ */
+typedef void (*PurpleKeyringRead)(PurpleAccount *account,
+ PurpleKeyringReadCallback cb, gpointer data);
+
+/**
+ * Store a password in the keyring.
+ *
+ * @param account The account.
+ * @param password The password to be stored. If the password is NULL, this
+ * means that the keyring should forget about that password.
+ * @param cb A callback for once the password is saved.
+ * @param data Data to be passed to the callback.
+ */
+typedef void (*PurpleKeyringSave)(PurpleAccount *account, const gchar *password,
+ PurpleKeyringSaveCallback cb, gpointer data);
+
+/**
+ * Cancel all running requests.
+ *
+ * After calling that, all queued requests should run their callbacks (most
+ * probably, with failure result).
+ */
+typedef void (*PurpleKeyringCancelRequests)(void);
+
+/**
+ * Close the keyring.
+ *
+ * This will be called so the keyring can do any cleanup it needs.
+ */
+typedef void (*PurpleKeyringClose)(void);
+
+/**
+ * Change the master password for the keyring.
+ *
+ * @param cb A callback for once the master password has been changed.
+ * @param data Data to be passed to the callback.
+ */
+typedef void (*PurpleKeyringChangeMaster)(PurpleKeyringChangeMasterCallback cb,
+ gpointer data);
+
+/**
+ * Import serialized (and maybe encrypted) password.
+ *
+ * This is not async because it is not meant to prompt for a master password and
+ * decrypt passwords.
+ *
+ * @param account The account.
+ * @param mode A keyring specific option that was stored. Can be NULL.
+ * @param data Data that was stored. Can be NULL.
+ *
+ * @return TRUE on success, FALSE on failure.
+ */
+typedef gboolean (*PurpleKeyringImportPassword)(PurpleAccount *account,
+ const gchar *mode, const gchar *data, GError **error);
+
+/**
+ * Export serialized (and maybe encrypted) password.
+ *
+ * @param account The account.
+ * @param mode An option field that can be used by the plugin. This is
+ * expected to be a static string.
+ * @param data The data to be stored in the XML node. This string will be
+ * freed using destroy() once not needed anymore.
+ * @param error Will be set if a problem occured.
+ * @param destroy A function to be called, if non NULL, to free data.
+ *
+ * @return TRUE on success, FALSE on failure.
+ */
+typedef gboolean (*PurpleKeyringExportPassword)(PurpleAccount *account,
+ const gchar **mode, gchar **data, GError **error,
+ GDestroyNotify *destroy);
+
+/*@}*/
+
+G_BEGIN_DECLS
+
+/**************************************************************************/
+/** @name Setting used keyrings */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Find a keyring by an id.
+ *
+ * @param id The id for the keyring.
+ *
+ * @return The keyring, or NULL if not found.
+ */
+PurpleKeyring *
+purple_keyring_find_keyring_by_id(const gchar *id);
+
+/**
+ * Get the keyring being used.
+ */
+PurpleKeyring *
+purple_keyring_get_inuse(void);
+
+/**
+ * Set the keyring to use. This function will move all passwords from
+ * the old keyring to the new one.
+ *
+ * If it fails, it will cancel all changes, close the new keyring, and notify
+ * the callback. If it succeeds, it will remove all passwords from the old safe
+ * and close that safe.
+ *
+ * @param newkeyring The new keyring to use.
+ * @param force FALSE if the change can be cancelled. If this is TRUE and
+ * an error occurs, data might be lost.
+ * @param cb A callback for once the change is complete.
+ * @param data Data to be passed to the callback.
+ */
+void
+purple_keyring_set_inuse(PurpleKeyring *newkeyring, gboolean force,
+ PurpleKeyringSetInUseCallback cb, gpointer data);
+
+/**
+ * Register a keyring plugin.
+ *
+ * @param keyring The keyring to register.
+ */
+void
+purple_keyring_register(PurpleKeyring *keyring);
+
+/**
+ * Unregister a keyring plugin.
+ *
+ * In case the keyring is in use, passwords will be moved to a fallback safe,
+ * and the keyring to unregister will be properly closed.
+ *
+ * @param keyring The keyring to unregister.
+ */
+void
+purple_keyring_unregister(PurpleKeyring *keyring);
+
+/**
+ * Returns a GList containing the IDs and names of the registered
+ * keyrings.
+ *
+ * @return The list of IDs and names.
+ */
+GList *
+purple_keyring_get_options(void);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Keyring plugin wrappers */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Import serialized (and maybe encrypted) password into current keyring.
+ *
+ * It's used by account.c while reading a password from xml.
+ *
+ * @param account The account.
+ * @param keyring_id The plugin ID that was stored in the xml file. Can be NULL.
+ * @param mode A keyring specific option that was stored. Can be NULL.
+ * @param data Data that was stored, can be NULL.
+ *
+ * @return TRUE if the input was accepted, FALSE otherwise.
+ */
+gboolean
+purple_keyring_import_password(PurpleAccount *account, const gchar *keyring_id,
+ const gchar *mode, const gchar *data, GError **error);
+
+/**
+ * Export serialized (and maybe encrypted) password out of current keyring.
+ *
+ * It's used by account.c while syncing accounts to xml.
+ *
+ * @param account The account for which we want the info.
+ * @param keyring_id The plugin id to be stored in the XML node. This will be
+ * NULL or a string that can be considered static.
+ * @param mode An option field that can be used by the plugin. This will
+ * be NULL or a string that can be considered static.
+ * @param data The data to be stored in the XML node. This string must be
+ * freed using destroy() once not needed anymore if it is not
+ * NULL.
+ * @param error Will be set if a problem occured.
+ * @param destroy A function to be called, if non NULL, to free data.
+ *
+ * @return TRUE if the info was exported successfully, FALSE otherwise.
+ */
+gboolean
+purple_keyring_export_password(PurpleAccount *account, const gchar **keyring_id,
+ const gchar **mode, gchar **data, GError **error,
+ GDestroyNotify *destroy);
+
+/**
+ * Read a password from the current keyring.
+ *
+ * @param account The account.
+ * @param cb A callback for once the password is read.
+ * @param data Data passed to the callback.
+ */
+void
+purple_keyring_get_password(PurpleAccount *account,
+ PurpleKeyringReadCallback cb, gpointer data);
+
+/**
+ * Save a password to the current keyring.
+ *
+ * @param account The account.
+ * @param password The password to save.
+ * @param cb A callback for once the password is saved.
+ * @param data Data to be passed to the callback.
+ */
+void
+purple_keyring_set_password(PurpleAccount *account, const gchar *password,
+ PurpleKeyringSaveCallback cb, gpointer data);
+
+/**
+ * Change the master password for a safe (if the safe supports it).
+ *
+ * @param cb A callback for once the master password has been changed.
+ * @param data Data to be passed to the callback.
+ *
+ * @todo Where is the master password string, that is being changed?
+ */
+void
+purple_keyring_change_master(PurpleKeyringChangeMasterCallback cb,
+ gpointer data);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name PurpleKeyring accessors */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates a new keyring wrapper.
+ */
+PurpleKeyring *
+purple_keyring_new(void);
+
+/**
+ * Frees all data allocated with purple_keyring_new.
+ *
+ * @param keyring Keyring wrapper struct.
+ */
+void
+purple_keyring_free(PurpleKeyring *keyring);
+
+/**
+ * Gets friendly user name.
+ *
+ * @param keyring The keyring.
+ * @return Friendly user name.
+ */
+const gchar *
+purple_keyring_get_name(const PurpleKeyring *keyring);
+
+/**
+ * Gets keyring ID.
+ *
+ * @param keyring The keyring.
+ * @return Keyring ID.
+ */
+const gchar *
+purple_keyring_get_id(const PurpleKeyring *keyring);
+
+PurpleKeyringRead
+purple_keyring_get_read_password(const PurpleKeyring *keyring);
+
+PurpleKeyringSave
+purple_keyring_get_save_password(const PurpleKeyring *keyring);
+
+PurpleKeyringCancelRequests
+purple_keyring_get_cancel_requests(const PurpleKeyring *keyring);
+
+PurpleKeyringClose
+purple_keyring_get_close_keyring(const PurpleKeyring *keyring);
+
+PurpleKeyringChangeMaster
+purple_keyring_get_change_master(const PurpleKeyring *keyring);
+
+PurpleKeyringImportPassword
+purple_keyring_get_import_password(const PurpleKeyring *keyring);
+
+PurpleKeyringExportPassword
+purple_keyring_get_export_password(const PurpleKeyring *keyring);
+
+/**
+ * Sets friendly user name.
+ *
+ * This field is required.
+ *
+ * @param keyring The keyring.
+ * @param name Friendly user name.
+ */
+void
+purple_keyring_set_name(PurpleKeyring *keyring, const gchar *name);
+
+/**
+ * Sets keyring ID.
+ *
+ * This field is required.
+ *
+ * @param keyring The keyring.
+ * @param name Keyring ID.
+ */
+void
+purple_keyring_set_id(PurpleKeyring *keyring, const gchar *id);
+
+/**
+ * Sets read password method.
+ *
+ * This field is required.
+ *
+ * @param keyring The keyring.
+ * @param read_cb Read password method.
+ */
+void
+purple_keyring_set_read_password(PurpleKeyring *keyring,
+ PurpleKeyringRead read_cb);
+
+/**
+ * Sets save password method.
+ *
+ * This field is required.
+ *
+ * @param keyring The keyring.
+ * @param save_cb Save password method.
+ */
+void
+purple_keyring_set_save_password(PurpleKeyring *keyring,
+ PurpleKeyringSave save_cb);
+
+void
+purple_keyring_set_cancel_requests(PurpleKeyring *keyring,
+ PurpleKeyringCancelRequests cancel_requests);
+
+void
+purple_keyring_set_close_keyring(PurpleKeyring *keyring,
+ PurpleKeyringClose close_cb);
+
+void
+purple_keyring_set_change_master(PurpleKeyring *keyring,
+ PurpleKeyringChangeMaster change_master);
+
+void
+purple_keyring_set_import_password(PurpleKeyring *keyring,
+ PurpleKeyringImportPassword import_password);
+
+void
+purple_keyring_set_export_password(PurpleKeyring *keyring,
+ PurpleKeyringExportPassword export_password);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Error Codes */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Gets keyring subsystem error domain.
+ *
+ * @return keyring subsystem error domain.
+ */
+GQuark
+purple_keyring_error_domain(void);
+
+/**
+ * Error codes for keyring subsystem.
+ */
+enum PurpleKeyringError
+{
+ PURPLE_KEYRING_ERROR_UNKNOWN = 0, /**< Unknown error. */
+
+ PURPLE_KEYRING_ERROR_NOKEYRING = 10, /**< No keyring configured. */
+ PURPLE_KEYRING_ERROR_INTERNAL, /**< Internal keyring system error. */
+ PURPLE_KEYRING_ERROR_BACKENDFAIL, /**< Failed to communicate with the backend or internal backend error. */
+
+ PURPLE_KEYRING_ERROR_NOPASSWORD = 20, /**< No password stored for the specified account. */
+ PURPLE_KEYRING_ERROR_ACCESSDENIED, /**< Access denied for the specified keyring or entry. */
+ PURPLE_KEYRING_ERROR_CANCELLED /**< Operation was cancelled. */
+};
+
+/*}@*/
+
+/**************************************************************************/
+/** @name Keyring Subsystem */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Initializes the keyring subsystem.
+ */
+void
+purple_keyring_init(void);
+
+/**
+ * Uninitializes the keyring subsystem.
+ */
+void
+purple_keyring_uninit(void);
+
+/**
+ * Returns the keyring subsystem handle.
+ *
+ * @return The keyring subsystem handle.
+ */
+void *
+purple_keyring_get_handle(void);
+
+/*}@*/
+
+G_END_DECLS
+
+#endif /* _PURPLE_KEYRING_H_ */
diff --git a/libpurple/plugins/Makefile.am b/libpurple/plugins/Makefile.am
index adc6209773..08b146424a 100644
--- 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 @@ SUBDIRS = \
$(MONO_DIR) \
$(PERL_DIR) \
ssl \
- $(TCL_DIR)
+ $(TCL_DIR) \
+ keyrings
plugindir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
diff --git a/libpurple/plugins/Makefile.mingw b/libpurple/plugins/Makefile.mingw
index a62253fedb..fbc70d533d 100644
--- a/libpurple/plugins/Makefile.mingw
+++ b/libpurple/plugins/Makefile.mingw
@@ -10,6 +10,7 @@ include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
PERL_PLUGIN := ./perl
TCL_PLUGIN := ./tcl
SSL_PLUGIN := ./ssl
+KEYRING_PLUGIN := ./keyrings
.SUFFIXES:
.SUFFIXES: .c .dll
@@ -48,11 +49,13 @@ all: $(PURPLE_DLL).a plugins
$(MAKE) -C $(PERL_PLUGIN) -f $(MINGW_MAKEFILE)
$(MAKE) -C $(TCL_PLUGIN) -f $(MINGW_MAKEFILE)
$(MAKE) -C $(SSL_PLUGIN) -f $(MINGW_MAKEFILE)
+ $(MAKE) -C $(KEYRING_PLUGIN) -f $(MINGW_MAKEFILE)
install: all $(PURPLE_INSTALL_PLUGINS_DIR)
$(MAKE) -C $(PERL_PLUGIN) -f $(MINGW_MAKEFILE) install
$(MAKE) -C $(TCL_PLUGIN) -f $(MINGW_MAKEFILE) install
$(MAKE) -C $(SSL_PLUGIN) -f $(MINGW_MAKEFILE) install
+ $(MAKE) -C $(KEYRING_PLUGIN) -f $(MINGW_MAKEFILE) install
cp *.dll $(PURPLE_INSTALL_PLUGINS_DIR)
%.dll: %.c $(PURPLE_CONFIG_H) $(PURPLE_VERSION_H)
@@ -78,5 +81,6 @@ clean:
$(MAKE) -C $(PERL_PLUGIN) -f $(MINGW_MAKEFILE) clean
$(MAKE) -C $(TCL_PLUGIN) -f $(MINGW_MAKEFILE) clean
$(MAKE) -C $(SSL_PLUGIN) -f $(MINGW_MAKEFILE) clean
+ $(MAKE) -C $(KEYRING_PLUGIN) -f $(MINGW_MAKEFILE) clean
include $(PIDGIN_COMMON_TARGETS)
diff --git a/libpurple/plugins/keyrings/Makefile.am b/libpurple/plugins/keyrings/Makefile.am
new file mode 100644
index 0000000000..b7db31f4b4
--- /dev/null
+++ b/libpurple/plugins/keyrings/Makefile.am
@@ -0,0 +1,75 @@
+EXTRA_DIST = \
+ Makefile.mingw
+CLEANFILES =
+
+plugindir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
+
+internalkeyring_la_CFLAGS = $(AM_CPPFLAGS)
+internalkeyring_la_LDFLAGS = -module -avoid-version
+internalkeyring_la_SOURCES = internalkeyring.c
+internalkeyring_la_LIBADD = $(GLIB_LIBS)
+
+if ENABLE_SECRETSERVICE
+
+secretservice_la_CFLAGS = $(AM_CPPFLAGS) $(SECRETSERVICE_CFLAGS)
+secretservice_la_LDFLAGS = -module -avoid-version
+secretservice_la_SOURCES = secretservice.c
+secretservice_la_LIBADD = $(GLIB_LIBS) $(SECRETSERVICE_LIBS)
+
+endif
+
+if ENABLE_GNOMEKEYRING
+
+gnomekeyring_la_CFLAGS = $(AM_CPPFLAGS) $(GNOMEKEYRING_CFLAGS)
+gnomekeyring_la_LDFLAGS = -module -avoid-version
+gnomekeyring_la_SOURCES = gnomekeyring.c
+gnomekeyring_la_LIBADD = $(GLIB_LIBS) $(GNOMEKEYRING_LIBS)
+
+endif
+
+if ENABLE_KWALLET
+
+kwallet_la_CXXFLAGS = $(KWALLET_CXXFLAGS) $(QT4_CFLAGS)
+kwallet_la_LDFLAGS = -module -avoid-version
+kwallet_la_SOURCES = kwallet.cpp
+kwallet_la_BUILTSOURCES = kwallet.moc
+kwallet_la_LIBADD = $(GLIB_LIBS) $(KWALLET_LIBS) $(QT4_LIBS)
+
+kwallet.cpp: kwallet.moc
+
+kwallet.moc:
+ $(AM_V_GEN)$(MOC) $(kwallet_la_CXXFLAGS) -i kwallet.cpp -o $@
+
+CLEANFILES += kwallet.moc
+
+endif
+
+if PLUGINS
+
+plugin_LTLIBRARIES = \
+ internalkeyring.la
+
+if ENABLE_SECRETSERVICE
+plugin_LTLIBRARIES += \
+ secretservice.la
+endif
+
+if ENABLE_GNOMEKEYRING
+plugin_LTLIBRARIES += \
+ gnomekeyring.la
+endif
+
+if ENABLE_KWALLET
+plugin_LTLIBRARIES += \
+ kwallet.la
+endif
+
+endif
+
+#XXX: that might be done better than adding DEBUG_CPPFLAGS to all objects (not only C++ ones)
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/libpurple \
+ -I$(top_builddir)/libpurple \
+ $(GLIB_CFLAGS) \
+ $(DEBUG_CPPFLAGS) \
+ $(PLUGIN_CFLAGS)
diff --git a/libpurple/plugins/keyrings/Makefile.mingw b/libpurple/plugins/keyrings/Makefile.mingw
new file mode 100644
index 0000000000..7574860b71
--- /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
index 0000000000..63c99533d5
--- /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
index 0000000000..24910d88c3
--- /dev/null
+++ b/libpurple/plugins/keyrings/internalkeyring.c
@@ -0,0 +1,261 @@
+/**
+ * @file internalkeyring.c internal keyring
+ * @ingroup plugins
+ */
+
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program ; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include "internal.h"
+#include "account.h"
+#include "debug.h"
+#include "keyring.h"
+#include "plugin.h"
+#include "version.h"
+
+#define INTERNALKEYRING_NAME N_("Internal keyring")
+#define INTERNALKEYRING_DESCRIPTION N_("This plugin provides the default" \
+ " password storage behaviour for libpurple. Password will be stored" \
+ " unencrypted.")
+#define INTERNALKEYRING_AUTHOR "Scrouaf (scrouaf[at]soc.pidgin.im)"
+#define INTERNALKEYRING_ID PURPLE_DEFAULT_KEYRING
+
+static gboolean internal_keyring_opened = FALSE;
+static GHashTable *internal_keyring_passwords = NULL;
+static PurpleKeyring *keyring_handler = NULL;
+
+/***********************************************/
+/* Keyring interface */
+/***********************************************/
+
+static void
+internal_keyring_open(void)
+{
+ if (internal_keyring_opened)
+ return;
+ internal_keyring_opened = TRUE;
+
+ internal_keyring_passwords = g_hash_table_new_full(g_direct_hash,
+ g_direct_equal, NULL, (GDestroyNotify)purple_str_wipe);
+}
+
+static void
+internal_keyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
+ gpointer data)
+{
+ const char *password;
+ GError *error;
+
+ internal_keyring_open();
+
+ password = g_hash_table_lookup(internal_keyring_passwords, account);
+
+ if (password != NULL) {
+ purple_debug_misc("keyring-internal",
+ "Got password for account %s (%s).\n",
+ purple_account_get_username(account),
+ purple_account_get_protocol_id(account));
+ if (cb != NULL)
+ cb(account, password, NULL, data);
+ } else {
+ if (purple_debug_is_verbose()) {
+ purple_debug_misc("keyring-internal",
+ "No password for account %s (%s).\n",
+ purple_account_get_username(account),
+ purple_account_get_protocol_id(account));
+ }
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOPASSWORD, "Password not found.");
+ if (cb != NULL)
+ cb(account, NULL, error, data);
+ g_error_free(error);
+ }
+}
+
+static void
+internal_keyring_save(PurpleAccount *account, const gchar *password,
+ PurpleKeyringSaveCallback cb, gpointer data)
+{
+ void *old_password;
+ internal_keyring_open();
+
+ old_password = g_hash_table_lookup(internal_keyring_passwords, account);
+
+ if (password == NULL)
+ g_hash_table_remove(internal_keyring_passwords, account);
+ else {
+ g_hash_table_replace(internal_keyring_passwords, account,
+ g_strdup(password));
+ }
+
+ if (!(password == NULL && old_password == NULL)) {
+ purple_debug_misc("keyring-internal",
+ "Password %s for account %s (%s).\n",
+ (password == NULL ? "removed" : (old_password == NULL ?
+ "saved" : "updated")),
+ purple_account_get_username(account),
+ purple_account_get_protocol_id(account));
+ } else if (purple_debug_is_verbose()) {
+ purple_debug_misc("keyring-internal",
+ "Password for account %s (%s) was already removed.\n",
+ purple_account_get_username(account),
+ purple_account_get_protocol_id(account));
+ }
+
+ if (cb != NULL)
+ cb(account, NULL, data);
+}
+
+static void
+internal_keyring_close(void)
+{
+ if (!internal_keyring_opened)
+ return;
+ internal_keyring_opened = FALSE;
+
+ g_hash_table_destroy(internal_keyring_passwords);
+ internal_keyring_passwords = NULL;
+}
+
+static gboolean
+internal_keyring_import_password(PurpleAccount *account, const char *mode,
+ const char *data, GError **error)
+{
+ g_return_val_if_fail(account != NULL, FALSE);
+ g_return_val_if_fail(data != NULL, FALSE);
+
+ internal_keyring_open();
+
+ if (mode == NULL)
+ mode = "cleartext";
+
+ if (g_strcmp0(mode, "cleartext") == 0) {
+ g_hash_table_replace(internal_keyring_passwords, account,
+ g_strdup(data));
+ return TRUE;
+ } else {
+ if (error != NULL) {
+ *error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_BACKENDFAIL,
+ "Invalid password storage mode");
+ }
+ return FALSE;
+ }
+}
+
+static gboolean
+internal_keyring_export_password(PurpleAccount *account, const char **mode,
+ char **data, GError **error, GDestroyNotify *destroy)
+{
+ gchar *password;
+
+ internal_keyring_open();
+
+ password = g_hash_table_lookup(internal_keyring_passwords, account);
+
+ if (password == NULL) {
+ return FALSE;
+ } else {
+ *mode = "cleartext";
+ *data = g_strdup(password);
+ *destroy = (GDestroyNotify)purple_str_wipe;
+ return TRUE;
+ }
+}
+
+/***********************************************/
+/* Plugin interface */
+/***********************************************/
+
+static gboolean
+internal_keyring_load(PurplePlugin *plugin)
+{
+ keyring_handler = purple_keyring_new();
+
+ purple_keyring_set_name(keyring_handler, INTERNALKEYRING_NAME);
+ purple_keyring_set_id(keyring_handler, INTERNALKEYRING_ID);
+ purple_keyring_set_read_password(keyring_handler,
+ internal_keyring_read);
+ purple_keyring_set_save_password(keyring_handler,
+ internal_keyring_save);
+ purple_keyring_set_close_keyring(keyring_handler,
+ internal_keyring_close);
+ purple_keyring_set_import_password(keyring_handler,
+ internal_keyring_import_password);
+ purple_keyring_set_export_password(keyring_handler,
+ internal_keyring_export_password);
+
+ purple_keyring_register(keyring_handler);
+
+ return TRUE;
+}
+
+static gboolean
+internal_keyring_unload(PurplePlugin *plugin)
+{
+ if (purple_keyring_get_inuse() == keyring_handler) {
+ purple_debug_warning("keyring-internal",
+ "keyring in use, cannot unload\n");
+ return FALSE;
+ }
+
+ internal_keyring_close();
+
+ purple_keyring_unregister(keyring_handler);
+ purple_keyring_free(keyring_handler);
+ keyring_handler = NULL;
+
+ return TRUE;
+}
+
+PurplePluginInfo plugininfo =
+{
+ PURPLE_PLUGIN_MAGIC, /* magic */
+ PURPLE_MAJOR_VERSION, /* major_version */
+ PURPLE_MINOR_VERSION, /* minor_version */
+ PURPLE_PLUGIN_STANDARD, /* type */
+ NULL, /* ui_requirement */
+ PURPLE_PLUGIN_FLAG_INVISIBLE, /* flags */
+ NULL, /* dependencies */
+ PURPLE_PRIORITY_DEFAULT, /* priority */
+ INTERNALKEYRING_ID, /* id */
+ INTERNALKEYRING_NAME, /* name */
+ DISPLAY_VERSION, /* version */
+ "Internal Keyring Plugin", /* summary */
+ INTERNALKEYRING_DESCRIPTION, /* description */
+ INTERNALKEYRING_AUTHOR, /* author */
+ PURPLE_WEBSITE, /* homepage */
+ internal_keyring_load, /* load */
+ internal_keyring_unload, /* unload */
+ NULL, /* destroy */
+ NULL, /* ui_info */
+ NULL, /* extra_info */
+ NULL, /* prefs_info */
+ NULL, /* actions */
+ NULL, NULL, NULL, NULL /* padding */
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+}
+
+PURPLE_INIT_PLUGIN(internal_keyring, init_plugin, plugininfo)
diff --git a/libpurple/plugins/keyrings/kwallet.cpp b/libpurple/plugins/keyrings/kwallet.cpp
new file mode 100644
index 0000000000..1d9c31b748
--- /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
index 0000000000..bdce53e224
--- /dev/null
+++ b/libpurple/plugins/keyrings/secretservice.c
@@ -0,0 +1,325 @@
+/* purple
+ * @file secretservice.c Secret Service password storage
+ * @ingroup plugins
+ * @todo rewrite it with Complete API
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program ; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include "internal.h"
+#include "account.h"
+#include "debug.h"
+#include "keyring.h"
+#include "plugin.h"
+#include "version.h"
+
+#include <libsecret/secret.h>
+
+#define SECRETSERVICE_NAME N_("Secret Service")
+#define SECRETSERVICE_ID "keyring-libsecret"
+
+static PurpleKeyring *keyring_handler = NULL;
+
+static const SecretSchema purple_schema = {
+ "im.pidgin.Purple", SECRET_SCHEMA_NONE,
+ {
+ {"user", SECRET_SCHEMA_ATTRIBUTE_STRING},
+ {"protocol", SECRET_SCHEMA_ATTRIBUTE_STRING},
+ {"NULL", 0}
+ },
+ /* Reserved fields */
+ 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+typedef struct _InfoStorage InfoStorage;
+
+struct _InfoStorage
+{
+ PurpleAccount *account;
+ gpointer cb;
+ gpointer user_data;
+};
+
+/***********************************************/
+/* Keyring interface */
+/***********************************************/
+static void
+ss_read_continue(GObject *object, GAsyncResult *result, gpointer data)
+{
+ InfoStorage *storage = data;
+ PurpleAccount *account = storage->account;
+ PurpleKeyringReadCallback cb = storage->cb;
+ char *password;
+ GError *error = NULL;
+
+ password = secret_password_lookup_finish(result, &error);
+
+ if (error != NULL) {
+ int code = error->code;
+
+ switch (code) {
+ case G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND:
+ case G_DBUS_ERROR_IO_ERROR:
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_BACKENDFAIL,
+ "Failed to communicate with Secret Service (account : %s).",
+ purple_account_get_username(account));
+ if (cb != NULL)
+ cb(account, NULL, error, storage->user_data);
+ g_error_free(error);
+ break;
+
+ default:
+ purple_debug_error("keyring-libsecret",
+ "Unknown error (account: %s (%s), domain: %s, code: %d): %s.\n",
+ purple_account_get_username(account),
+ purple_account_get_protocol_id(account),
+ g_quark_to_string(error->domain), code, error->message);
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_BACKENDFAIL,
+ "Unknown error (account : %s).",
+ purple_account_get_username(account));
+ if (cb != NULL)
+ cb(account, NULL, error, storage->user_data);
+ g_error_free(error);
+ break;
+ }
+
+ } else if (password == NULL) {
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOPASSWORD,
+ "No password found for account: %s",
+ purple_account_get_username(account));
+ if (cb != NULL)
+ cb(account, NULL, error, storage->user_data);
+ g_error_free(error);
+
+ } else {
+ if (cb != NULL)
+ cb(account, password, NULL, storage->user_data);
+ }
+
+ g_free(storage);
+}
+
+static void
+ss_read(PurpleAccount *account, PurpleKeyringReadCallback cb, gpointer data)
+{
+ InfoStorage *storage = g_new0(InfoStorage, 1);
+
+ storage->account = account;
+ storage->cb = cb;
+ storage->user_data = data;
+
+ secret_password_lookup(&purple_schema,
+ NULL, ss_read_continue, storage,
+ "user", purple_account_get_username(account),
+ "protocol", purple_account_get_protocol_id(account),
+ NULL);
+}
+
+static void
+ss_save_continue(GObject *object, GAsyncResult *result, gpointer data)
+{
+ InfoStorage *storage = data;
+ PurpleKeyringSaveCallback cb;
+ GError *error = NULL;
+ PurpleAccount *account;
+
+ account = storage->account;
+ cb = storage->cb;
+
+ secret_password_store_finish(result, &error);
+
+ if (error != NULL) {
+ int code = error->code;
+
+ switch (code) {
+ case G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND:
+ case G_DBUS_ERROR_IO_ERROR:
+ purple_debug_info("keyring-libsecret",
+ "Failed to communicate with Secret Service (account : %s (%s)).\n",
+ purple_account_get_username(account),
+ purple_account_get_protocol_id(account));
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_BACKENDFAIL,
+ "Failed to communicate with Secret Service (account : %s).",
+ purple_account_get_username(account));
+ if (cb != NULL)
+ cb(account, error, storage->user_data);
+ g_error_free(error);
+ break;
+
+ default:
+ purple_debug_error("keyring-libsecret",
+ "Unknown error (account: %s (%s), domain: %s, code: %d): %s.\n",
+ purple_account_get_username(account),
+ purple_account_get_protocol_id(account),
+ g_quark_to_string(error->domain), code, error->message);
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_BACKENDFAIL,
+ "Unknown error (account : %s).",
+ purple_account_get_username(account));
+ if (cb != NULL)
+ cb(account, error, storage->user_data);
+ g_error_free(error);
+ break;
+ }
+
+ } else {
+ purple_debug_info("keyring-libsecret", "Password for %s updated.\n",
+ purple_account_get_username(account));
+
+ if (cb != NULL)
+ cb(account, NULL, storage->user_data);
+ }
+
+ g_free(storage);
+}
+
+static void
+ss_save(PurpleAccount *account,
+ const gchar *password,
+ PurpleKeyringSaveCallback cb,
+ gpointer data)
+{
+ InfoStorage *storage = g_new0(InfoStorage, 1);
+
+ storage->account = account;
+ storage->cb = cb;
+ storage->user_data = data;
+
+ if (password != NULL && *password != '\0') {
+ const char *username = purple_account_get_username(account);
+ char *label;
+
+ purple_debug_info("keyring-libsecret",
+ "Updating password for account %s (%s).\n",
+ username, purple_account_get_protocol_id(account));
+
+ label = g_strdup_printf(_("Pidgin IM password for account %s"), username);
+ secret_password_store(&purple_schema, SECRET_COLLECTION_DEFAULT,
+ label, password,
+ NULL, ss_save_continue, storage,
+ "user", username,
+ "protocol", purple_account_get_protocol_id(account),
+ NULL);
+ g_free(label);
+
+ } else { /* password == NULL, delete password. */
+ purple_debug_info("keyring-libsecret",
+ "Forgetting password for account %s (%s).\n",
+ purple_account_get_username(account),
+ purple_account_get_protocol_id(account));
+
+ secret_password_clear(&purple_schema, NULL, ss_save_continue, storage,
+ "user", purple_account_get_username(account),
+ "protocol", purple_account_get_protocol_id(account),
+ NULL);
+ }
+}
+
+static void
+ss_close(void)
+{
+}
+
+static gboolean
+ss_init(void)
+{
+ keyring_handler = purple_keyring_new();
+
+ purple_keyring_set_name(keyring_handler, SECRETSERVICE_NAME);
+ purple_keyring_set_id(keyring_handler, SECRETSERVICE_ID);
+ purple_keyring_set_read_password(keyring_handler, ss_read);
+ purple_keyring_set_save_password(keyring_handler, ss_save);
+ purple_keyring_set_close_keyring(keyring_handler, ss_close);
+
+ purple_keyring_register(keyring_handler);
+
+ return TRUE;
+}
+
+static void
+ss_uninit(void)
+{
+ ss_close();
+ purple_keyring_unregister(keyring_handler);
+ purple_keyring_free(keyring_handler);
+ keyring_handler = NULL;
+}
+
+/***********************************************/
+/* Plugin interface */
+/***********************************************/
+
+static gboolean
+ss_load(PurplePlugin *plugin)
+{
+ return ss_init();
+}
+
+static gboolean
+ss_unload(PurplePlugin *plugin)
+{
+ if (purple_keyring_get_inuse() == keyring_handler)
+ return FALSE;
+
+ ss_uninit();
+
+ return TRUE;
+}
+
+PurplePluginInfo plugininfo =
+{
+ PURPLE_PLUGIN_MAGIC, /* magic */
+ PURPLE_MAJOR_VERSION, /* major_version */
+ PURPLE_MINOR_VERSION, /* minor_version */
+ PURPLE_PLUGIN_STANDARD, /* type */
+ NULL, /* ui_requirement */
+ PURPLE_PLUGIN_FLAG_INVISIBLE, /* flags */
+ NULL, /* dependencies */
+ PURPLE_PRIORITY_DEFAULT, /* priority */
+ SECRETSERVICE_ID, /* id */
+ SECRETSERVICE_NAME, /* name */
+ DISPLAY_VERSION, /* version */
+ "Secret Service Plugin", /* summary */
+ N_("This plugin will store passwords in Secret Service."), /* description */
+ "Elliott Sales de Andrade (qulogic[at]pidgin.im)", /* author */
+ PURPLE_WEBSITE, /* homepage */
+ ss_load, /* load */
+ ss_unload, /* unload */
+ NULL, /* destroy */
+ NULL, /* ui_info */
+ NULL, /* extra_info */
+ NULL, /* prefs_info */
+ NULL, /* actions */
+ NULL, /* padding... */
+ NULL,
+ NULL,
+ NULL,
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+}
+
+PURPLE_INIT_PLUGIN(secret_service, init_plugin, plugininfo)
+
diff --git a/libpurple/plugins/keyrings/wincred.c b/libpurple/plugins/keyrings/wincred.c
new file mode 100644
index 0000000000..6d17c2b96c
--- /dev/null
+++ b/libpurple/plugins/keyrings/wincred.c
@@ -0,0 +1,320 @@
+/**
+ * @file wincred.c Passwords storage using Windows credentials
+ * @ingroup plugins
+ */
+
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program ; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include "debug.h"
+#include "internal.h"
+#include "keyring.h"
+#include "plugin.h"
+#include "version.h"
+
+#include <wincred.h>
+
+#define WINCRED_NAME N_("Windows credentials")
+#define WINCRED_SUMMARY N_("Store passwords using Windows credentials")
+#define WINCRED_DESCRIPTION N_("This plugin stores passwords using Windows " \
+ "credentials.")
+#define WINCRED_AUTHOR "Tomek Wasilczyk (tomkiewicz@cpw.pidgin.im)"
+#define WINCRED_ID "keyring-wincred"
+
+#define WINCRED_MAX_TARGET_NAME 256
+
+static PurpleKeyring *keyring_handler = NULL;
+
+static gunichar2 *
+wincred_get_target_name(PurpleAccount *account)
+{
+ gchar target_name_utf8[WINCRED_MAX_TARGET_NAME];
+ gunichar2 *target_name_utf16;
+
+ g_return_val_if_fail(account != NULL, NULL);
+
+ g_snprintf(target_name_utf8, WINCRED_MAX_TARGET_NAME, "libpurple_%s_%s",
+ purple_account_get_protocol_id(account),
+ purple_account_get_username(account));
+
+ target_name_utf16 = g_utf8_to_utf16(target_name_utf8, -1,
+ NULL, NULL, NULL);
+
+ if (target_name_utf16 == NULL) {
+ purple_debug_fatal("keyring-wincred", "Couldn't convert target "
+ "name\n");
+ }
+
+ return target_name_utf16;
+}
+
+static void
+wincred_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
+ gpointer data)
+{
+ GError *error = NULL;
+ gunichar2 *target_name = NULL;
+ gchar *password;
+ PCREDENTIALW credential;
+
+ g_return_if_fail(account != NULL);
+
+ target_name = wincred_get_target_name(account);
+ g_return_if_fail(target_name != NULL);
+
+ if (!CredReadW(target_name, CRED_TYPE_GENERIC, 0, &credential)) {
+ DWORD error_code = GetLastError();
+
+ if (error_code == ERROR_NOT_FOUND) {
+ if (purple_debug_is_verbose()) {
+ purple_debug_misc("keyring-wincred",
+ "No password found for account %s\n",
+ purple_account_get_username(account));
+ }
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOPASSWORD,
+ "Password not found.");
+ } else if (error_code == ERROR_NO_SUCH_LOGON_SESSION) {
+ purple_debug_error("keyring-wincred",
+ "Cannot read password, no valid logon "
+ "session\n");
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_ACCESSDENIED,
+ "Cannot read password, no valid logon session");
+ } else {
+ purple_debug_error("keyring-wincred",
+ "Cannot read password, error %lx\n",
+ error_code);
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_BACKENDFAIL,
+ "Cannot read password, error %lx", error_code);
+ }
+
+ if (cb != NULL)
+ cb(account, NULL, error, data);
+ g_error_free(error);
+ return;
+ }
+
+ password = g_utf16_to_utf8((gunichar2*)credential->CredentialBlob,
+ credential->CredentialBlobSize / sizeof(gunichar2),
+ NULL, NULL, NULL);
+
+ memset(credential->CredentialBlob, 0, credential->CredentialBlobSize);
+ CredFree(credential);
+
+ if (password == NULL) {
+ purple_debug_error("keyring-wincred",
+ "Cannot convert password\n");
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_BACKENDFAIL,
+ "Cannot convert password");
+ } else {
+ purple_debug_misc("keyring-wincred",
+ "Got password for account %s.\n",
+ purple_account_get_username(account));
+ }
+
+ if (cb != NULL)
+ cb(account, password, error, data);
+ if (error != NULL)
+ g_error_free(error);
+
+ purple_str_wipe(password);
+}
+
+static void
+wincred_save(PurpleAccount *account, const gchar *password,
+ PurpleKeyringSaveCallback cb, gpointer data)
+{
+ GError *error = NULL;
+ gunichar2 *target_name = NULL;
+ gunichar2 *username_utf16 = NULL;
+ gunichar2 *password_utf16 = NULL;
+ CREDENTIALW credential;
+
+ g_return_if_fail(account != NULL);
+
+ target_name = wincred_get_target_name(account);
+ g_return_if_fail(target_name != NULL);
+
+ if (password == NULL)
+ {
+ if (CredDeleteW(target_name, CRED_TYPE_GENERIC, 0)) {
+ purple_debug_misc("keyring-wincred", "Password for "
+ "account %s removed\n",
+ purple_account_get_username(account));
+ } else {
+ DWORD error_code = GetLastError();
+
+ if (error_code == ERROR_NOT_FOUND) {
+ if (purple_debug_is_verbose()) {
+ purple_debug_misc("keyring-wincred",
+ "Password for account %s was already "
+ "removed.\n",
+ purple_account_get_username(account));
+ }
+ } else if (error_code == ERROR_NO_SUCH_LOGON_SESSION) {
+ purple_debug_error("keyring-wincred",
+ "Cannot remove password, no valid "
+ "logon session\n");
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_ACCESSDENIED,
+ "Cannot remove password, no valid "
+ "logon session");
+ } else {
+ purple_debug_error("keyring-wincred",
+ "Cannot remove password, error %lx\n",
+ error_code);
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_BACKENDFAIL,
+ "Cannot remove password, error %lx",
+ error_code);
+ }
+ }
+
+ if (cb != NULL)
+ cb(account, error, data);
+ if (error != NULL)
+ g_error_free(error);
+ return;
+ }
+
+ username_utf16 = g_utf8_to_utf16(purple_account_get_username(account),
+ -1, NULL, NULL, NULL);
+ password_utf16 = g_utf8_to_utf16(password, -1, NULL, NULL, NULL);
+
+ if (username_utf16 == NULL || password_utf16 == NULL) {
+ g_free(username_utf16);
+ purple_utf16_wipe(password_utf16);
+
+ purple_debug_fatal("keyring-wincred", "Couldn't convert "
+ "username or password\n");
+ g_return_if_reached();
+ }
+
+ memset(&credential, 0, sizeof(CREDENTIALW));
+ credential.Type = CRED_TYPE_GENERIC;
+ credential.TargetName = target_name;
+ credential.CredentialBlobSize = purple_utf16_size(password_utf16) - 2;
+ credential.CredentialBlob = (LPBYTE)password_utf16;
+ credential.Persist = CRED_PERSIST_LOCAL_MACHINE;
+ credential.UserName = username_utf16;
+
+ if (!CredWriteW(&credential, 0)) {
+ DWORD error_code = GetLastError();
+
+ if (error_code == ERROR_NO_SUCH_LOGON_SESSION) {
+ purple_debug_error("keyring-wincred",
+ "Cannot store password, no valid logon "
+ "session\n");
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_ACCESSDENIED,
+ "Cannot remove password, no valid logon "
+ "session");
+ } else {
+ purple_debug_error("keyring-wincred",
+ "Cannot store password, error %lx\n",
+ error_code);
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_BACKENDFAIL,
+ "Cannot store password, error %lx", error_code);
+ }
+ } else {
+ purple_debug_misc("keyring-wincred",
+ "Password updated for account %s.\n",
+ purple_account_get_username(account));
+ }
+
+ g_free(target_name);
+ g_free(username_utf16);
+ purple_utf16_wipe(password_utf16);
+
+ if (cb != NULL)
+ cb(account, error, data);
+ if (error != NULL)
+ g_error_free(error);
+}
+
+static gboolean
+wincred_load(PurplePlugin *plugin)
+{
+ keyring_handler = purple_keyring_new();
+
+ purple_keyring_set_name(keyring_handler, WINCRED_NAME);
+ purple_keyring_set_id(keyring_handler, WINCRED_ID);
+ purple_keyring_set_read_password(keyring_handler, wincred_read);
+ purple_keyring_set_save_password(keyring_handler, wincred_save);
+
+ purple_keyring_register(keyring_handler);
+
+ return TRUE;
+}
+
+static gboolean
+wincred_unload(PurplePlugin *plugin)
+{
+ if (purple_keyring_get_inuse() == keyring_handler) {
+ purple_debug_warning("keyring-wincred",
+ "keyring in use, cannot unload\n");
+ return FALSE;
+ }
+
+ purple_keyring_unregister(keyring_handler);
+ purple_keyring_free(keyring_handler);
+ keyring_handler = NULL;
+
+ return TRUE;
+}
+
+PurplePluginInfo plugininfo =
+{
+ PURPLE_PLUGIN_MAGIC, /* magic */
+ PURPLE_MAJOR_VERSION, /* major_version */
+ PURPLE_MINOR_VERSION, /* minor_version */
+ PURPLE_PLUGIN_STANDARD, /* type */
+ NULL, /* ui_requirement */
+ PURPLE_PLUGIN_FLAG_INVISIBLE, /* flags */
+ NULL, /* dependencies */
+ PURPLE_PRIORITY_DEFAULT, /* priority */
+ WINCRED_ID, /* id */
+ WINCRED_NAME, /* name */
+ DISPLAY_VERSION, /* version */
+ WINCRED_SUMMARY, /* summary */
+ WINCRED_DESCRIPTION, /* description */
+ WINCRED_AUTHOR, /* author */
+ PURPLE_WEBSITE, /* homepage */
+ wincred_load, /* load */
+ wincred_unload, /* unload */
+ NULL, /* destroy */
+ NULL, /* ui_info */
+ NULL, /* extra_info */
+ NULL, /* prefs_info */
+ NULL, /* actions */
+ NULL, NULL, NULL, NULL /* padding */
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+}
+
+PURPLE_INIT_PLUGIN(wincred_keyring, init_plugin, plugininfo)
diff --git a/libpurple/plugins/one_time_password.c b/libpurple/plugins/one_time_password.c
index caf29212a9..ed8fe64271 100644
--- a/libpurple/plugins/one_time_password.c
+++ b/libpurple/plugins/one_time_password.c
@@ -46,7 +46,7 @@ signed_on_cb(PurpleConnection *conn, void *data)
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
index 2a400d0379..46f597c9d1 100644
--- 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 @@ purple_account_set_username(account, username)
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 @@ const char *
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
index 384869458c..8f21357c46 100644
--- a/libpurple/plugins/perl/perl-handlers.c
+++ b/libpurple/plugins/perl/perl-handlers.c
@@ -845,3 +845,127 @@ void purple_perl_pref_cb_clear_for_plugin(PurplePlugin *plugin)
destroy_prefs_handler(handler);
}
}
+
+static void
+perl_account_save_cb(PurpleAccount *account, GError *error, gpointer data)
+{
+ int count;
+ SV *accountSV, *errorSV;
+ PurplePerlAccountPasswordHandler *handler = data;
+
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+
+ /* Push the account onto the perl stack */
+ accountSV = sv_2mortal(purple_perl_bless_object(account, "Purple::Account"));
+ XPUSHs(accountSV);
+
+ /* Push the error onto the perl stack */
+ errorSV = sv_2mortal(purple_perl_bless_object(account, "GLib::Error"));
+ XPUSHs(errorSV);
+
+ /* Push the data onto the perl stack */
+ XPUSHs((SV *)handler->data);
+
+ PUTBACK;
+ count = call_sv(handler->callback, G_EVAL | G_SCALAR);
+
+ if (count != 0)
+ croak("call_sv: Did not return the correct number of values.\n");
+
+ if (SvTRUE(ERRSV)) {
+ purple_debug_error("perl",
+ "Perl plugin command function exited abnormally: %s\n",
+ SvPVutf8_nolen(ERRSV));
+ }
+
+ SPAGAIN;
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ g_free(handler);
+}
+
+static void
+perl_account_read_cb(PurpleAccount *account, const gchar *password,
+ GError *error, gpointer data)
+{
+ int count;
+ SV *accountSV, *passwordSV, *errorSV;
+ PurplePerlAccountPasswordHandler *handler = data;
+
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+
+ /* Push the account onto the perl stack */
+ accountSV = sv_2mortal(purple_perl_bless_object(account, "Purple::Account"));
+ XPUSHs(accountSV);
+
+ /* Push the password onto the perl stack */
+ passwordSV = newSVpv(password, 0);
+ passwordSV = sv_2mortal(passwordSV);
+ XPUSHs(passwordSV);
+
+ /* Push the error onto the perl stack */
+ errorSV = sv_2mortal(purple_perl_bless_object(account, "GLib::Error"));
+ XPUSHs(errorSV);
+
+ /* Push the data onto the perl stack */
+ XPUSHs((SV *)handler->data);
+
+ PUTBACK;
+ count = call_sv(handler->callback, G_EVAL | G_SCALAR);
+
+ if (count != 0)
+ croak("call_sv: Did not return the correct number of values.\n");
+
+ if (SvTRUE(ERRSV)) {
+ purple_debug_error("perl",
+ "Perl plugin command function exited abnormally: %s\n",
+ SvPVutf8_nolen(ERRSV));
+ }
+
+ SPAGAIN;
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ g_free(handler);
+}
+
+void
+purple_perl_account_get_password(PurpleAccount *account, SV *func, SV *data)
+{
+ PurplePerlAccountPasswordHandler *handler;
+
+ handler = g_new0(PurplePerlAccountPasswordHandler, 1);
+ handler->callback = (func != NULL &&
+ func != &PL_sv_undef ? newSVsv(func) : NULL);
+ handler->data = (data != NULL &&
+ data != &PL_sv_undef ? newSVsv(data) : NULL);
+
+ purple_account_get_password(account, perl_account_read_cb, data);
+}
+
+void
+purple_perl_account_set_password(PurpleAccount *account, const char *password,
+ SV *func, SV *data)
+{
+ PurplePerlAccountPasswordHandler *handler;
+
+ handler = g_new0(PurplePerlAccountPasswordHandler, 1);
+ handler->callback = (func != NULL &&
+ func != &PL_sv_undef ? newSVsv(func) : NULL);
+ handler->data = (data != NULL &&
+ data != &PL_sv_undef ? newSVsv(data) : NULL);
+
+ purple_account_set_password(account, password, perl_account_save_cb, data);
+}
+
diff --git a/libpurple/plugins/perl/perl-handlers.h b/libpurple/plugins/perl/perl-handlers.h
index c0079d2fb9..6ceeb50c8e 100644
--- a/libpurple/plugins/perl/perl-handlers.h
+++ b/libpurple/plugins/perl/perl-handlers.h
@@ -48,6 +48,13 @@ typedef struct
} PurplePerlPrefsHandler;
+typedef struct
+{
+ SV *callback;
+ SV *data;
+
+} PurplePerlAccountPasswordHandler;
+
void purple_perl_plugin_action_cb(PurplePluginAction * gpa);
GList *purple_perl_plugin_actions(PurplePlugin *plugin, gpointer context);
@@ -82,4 +89,10 @@ guint purple_perl_prefs_connect_callback(PurplePlugin *plugin, const char *name,
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/protocols/gg/account.c b/libpurple/protocols/gg/account.c
index f88d3dac6c..b0736e2799 100644
--- a/libpurple/protocols/gg/account.c
+++ b/libpurple/protocols/gg/account.c
@@ -359,7 +359,8 @@ static void ggp_account_register_response(struct gg_http *h, gboolean success,
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 @@ static void ggp_account_chpass_dialog_ok(
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 @@ static void ggp_account_chpass_response(struct gg_http *h, gboolean success,
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
index a72dbcb807..98087588d5 100644
--- a/libpurple/protocols/gg/gg.c
+++ b/libpurple/protocols/gg/gg.c
@@ -896,7 +896,8 @@ static void ggp_login(PurpleAccount *account)
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
index 9e73f4c23b..07f47ea6dc 100644
--- a/libpurple/protocols/gg/oauth/oauth-purple.c
+++ b/libpurple/protocols/gg/oauth/oauth-purple.c
@@ -88,7 +88,7 @@ void ggp_oauth_request(PurpleConnection *gc, ggp_oauth_request_cb callback,
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 @@ static void ggp_oauth_request_token_got(PurpleUtilFetchUrlData *url_data,
"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 @@ static void ggp_oauth_authorization_done(PurpleUtilFetchUrlData *url_data,
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 @@ static void ggp_oauth_access_token_got(PurpleUtilFetchUrlData *url_data,
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
index 4845b3fcf3..d6e11cb51a 100644
--- a/libpurple/protocols/irc/msgs.c
+++ b/libpurple/protocols/irc/msgs.c
@@ -1471,7 +1471,8 @@ irc_sasl_cb_secret(sasl_conn_t *conn, void *ctx, int id, sasl_secret_t **secret)
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
index 7627511d12..7285108b11 100644
--- a/libpurple/protocols/jabber/auth.c
+++ b/libpurple/protocols/jabber/auth.c
@@ -110,7 +110,7 @@ auth_old_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
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 @@ static void auth_old_result_cb(JabberStream *js, const char *from,
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 @@ void jabber_auth_start_old(JabberStream *js)
* 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
index 157a61ae62..9a8fdca39c 100644
--- a/libpurple/protocols/jabber/auth_cyrus.c
+++ b/libpurple/protocols/jabber/auth_cyrus.c
@@ -91,12 +91,10 @@ static int jabber_sasl_cb_simple(void *ctx, int id, const char **res, unsigned *
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 @@ static void auth_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
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 @@ jabber_auth_start_cyrus(JabberStream *js, xmlnode **reply, char **error)
* 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 @@ jabber_sasl_cb_log(void *context, int level, const char *message)
static void
jabber_sasl_build_callbacks(JabberStream *js)
{
- PurpleAccount *account;
int id;
/* Set up our callbacks structure */
@@ -387,8 +384,7 @@ jabber_sasl_build_callbacks(JabberStream *js)
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
index d59d1fa95a..a7bcd10fb7 100644
--- a/libpurple/protocols/jabber/jabber.c
+++ b/libpurple/protocols/jabber/jabber.c
@@ -1263,7 +1263,7 @@ jabber_register_cb(JabberRegisterCBData *cbdata, PurpleRequestFields *fields)
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 @@ jabber_password_change_result_cb(JabberStream *js, const char *from,
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 @@ char *jabber_parse_error(JabberStream *js,
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
index 0665f91a32..1a0b6f15cd 100644
--- a/libpurple/protocols/msn/session.c
+++ b/libpurple/protocols/msn/session.c
@@ -385,7 +385,7 @@ msn_session_set_error(MsnSession *session, MsnErrorType error,
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 @@ msn_session_set_error(MsnSession *session, MsnErrorType error,
_("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
index 4bf77f3eb1..da87f40548 100644
--- a/libpurple/protocols/mxit/actions.c
+++ b/libpurple/protocols/mxit/actions.c
@@ -361,7 +361,7 @@ static void mxit_change_pin_cb( PurpleConnection* gc, PurpleRequestFields* field
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 @@ out:
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 @@ static void mxit_change_pin_action( PurplePluginAction* action )
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
index 856dfe36d4..97ba2609c2 100644
--- a/libpurple/protocols/mxit/cipher.c
+++ b/libpurple/protocols/mxit/cipher.c
@@ -79,7 +79,7 @@ static void padding_remove( GString* data )
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 @@ char* mxit_encrypt_password( struct MXitSession* session )
/* 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
index dfaef4b8bc..637d6c5241 100644
--- a/libpurple/protocols/mxit/login.c
+++ b/libpurple/protocols/mxit/login.c
@@ -287,7 +287,7 @@ static void mxit_cb_register_ok( PurpleConnection *gc, PurpleRequestFields *fiel
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
index 22b9327772..221dfc6440 100644
--- a/libpurple/protocols/myspace/myspace.c
+++ b/libpurple/protocols/myspace/myspace.c
@@ -704,7 +704,7 @@ msim_login_challenge(MsimSession *session, MsimMessage *msg)
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 @@ msim_error(MsimSession *session, MsimMessage *msg)
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 @@ msim_error(MsimSession *session, MsimMessage *msg)
"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 @@ msim_error(MsimSession *session, MsimMessage *msg)
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
index 4bf8037551..d0148c0cdb 100644
--- a/libpurple/protocols/novell/novell.c
+++ b/libpurple/protocols/novell/novell.c
@@ -132,7 +132,7 @@ _login_resp_cb(NMUser * user, NMERR_T ret_code,
* 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 @@ _evt_user_disconnect(NMUser * user, NMEvent * event)
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
index 62a6bc2bef..902185bd99 100644
--- a/libpurple/protocols/oscar/clientlogin.c
+++ b/libpurple/protocols/oscar/clientlogin.c
@@ -481,7 +481,7 @@ static gboolean parse_client_login_response(PurpleConnection *gc, const gchar *r
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
index a7441c69ed..a592a4e753 100644
--- a/libpurple/protocols/oscar/flap_connection.c
+++ b/libpurple/protocols/oscar/flap_connection.c
@@ -467,7 +467,7 @@ flap_connection_destroy_cb(gpointer data)
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
index 8c1df489d4..0606bc5c05 100644
--- a/libpurple/protocols/oscar/oscar.c
+++ b/libpurple/protocols/oscar/oscar.c
@@ -1077,7 +1077,7 @@ purple_parse_auth_resp(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
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
index 061ddadb15..3eed9d60df 100644
--- a/libpurple/protocols/sametime/sametime.c
+++ b/libpurple/protocols/sametime/sametime.c
@@ -3694,7 +3694,7 @@ static void mw_prpl_login(PurpleAccount *account) {
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
index f430081e99..a55bcf4923 100644
--- a/libpurple/protocols/silc/silc.c
+++ b/libpurple/protocols/silc/silc.c
@@ -481,7 +481,7 @@ static void silcpurple_got_password_cb(PurpleConnection *gc, PurpleRequestFields
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 @@ static void silcpurple_running(SilcClient client, void *context)
(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
index 034f133e22..10ff4be7da 100644
--- a/libpurple/protocols/simple/simple.c
+++ b/libpurple/protocols/simple/simple.c
@@ -1122,7 +1122,7 @@ gboolean process_register_response(struct simple_account_data *sip, struct sipms
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
index 34ac8e5baa..f3f92a5cc4 100644
--- a/libpurple/protocols/yahoo/libymsg.c
+++ b/libpurple/protocols/yahoo/libymsg.c
@@ -159,7 +159,7 @@ static void yahoo_process_status(PurpleConnection *gc, struct yahoo_packet *pkt)
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 @@ static void yahoo_auth16_stage1_cb(PurpleUtilFetchUrlData *url_data, gpointer us
/* 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 @@ static void yahoo_process_authresp(PurpleConnection *gc, struct yahoo_packet *pk
}
#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
index b366079d47..09edc76682 100644
--- a/libpurple/prpl.c
+++ b/libpurple/prpl.c
@@ -358,9 +358,10 @@ do_prpl_change_account_status(PurpleAccount *account,
{
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/util.c b/libpurple/util.c
index 5c18de6066..4cc7663749 100644
--- a/libpurple/util.c
+++ b/libpurple/util.c
@@ -3762,6 +3762,41 @@ purple_str_binary_to_ascii(const unsigned char *binary, guint len)
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
index 38c96dc041..5064468e32 100644
--- a/libpurple/util.h
+++ b/libpurple/util.h
@@ -1132,6 +1132,33 @@ char *purple_str_seconds_to_string(guint sec);
* @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
index 5f222fa99d..d5ddca759d 100644
--- a/pidgin/gtkaccount.c
+++ b/pidgin/gtkaccount.c
@@ -118,6 +118,7 @@ typedef struct
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 @@ add_login_options(AccountPrefsDialog *dialog, GtkWidget *parent)
/* 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 @@ account_win_destroy_cb(GtkWidget *w, GdkEvent *event,
purple_signals_disconnect_by_handle(dialog);
+ purple_str_wipe(dialog->password);
+
g_free(dialog);
return FALSE;
}
@@ -1418,6 +1422,7 @@ ok_account_prefs_cb(GtkWidget *w, AccountPrefsDialog *dialog)
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 @@ ok_account_prefs_cb(GtkWidget *w, AccountPrefsDialog *dialog)
/* 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 @@ ok_account_prefs_cb(GtkWidget *w, AccountPrefsDialog *dialog)
* 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 @@ static const GtkTargetEntry dnd_targets[] = {
{"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 @@ pidgin_account_dialog_show(PidginAccountDialogType type,
}
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 @@ pidgin_account_dialog_show(PidginAccountDialogType type,
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
index 70a60f0268..7e4b57c024 100644
--- a/pidgin/gtkconn.c
+++ b/pidgin/gtkconn.c
@@ -196,10 +196,7 @@ static void pidgin_connection_network_disconnected (void)
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
index 390dbe3078..9c4bccf991 100644
--- 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,9 @@ static GtkWidget *prefs_conv_variants_combo_box;
static GtkWidget *prefs_status_themes_combo_box;
static GtkWidget *prefs_smiley_themes_combo_box;
+/* Keyrings page */
+static gpointer keyring_page_pref_set_instance = NULL;
+
/* Sound theme specific */
static GtkWidget *sound_entry = NULL;
static int sound_row_sel = 0;
@@ -269,83 +273,105 @@ enum {
PREF_DROPDOWN_COUNT
};
-static void
-dropdown_set(GObject *w, const char *key)
+typedef struct
{
- const char *str_value;
- int int_value;
- gboolean bool_value;
PurplePrefType type;
+ union {
+ const char *string;
+ int integer;
+ gboolean boolean;
+ } value;
+} PidginPrefValue;
+
+typedef void (*PidginPrefsDropdownCallback)(GtkComboBox *combo_box,
+ PidginPrefValue value);
+
+static void
+dropdown_set(GtkComboBox *combo_box, gpointer _cb)
+{
+ PidginPrefsDropdownCallback cb = _cb;
GtkTreeIter iter;
GtkTreeModel *tree_model;
+ PidginPrefValue active;
- tree_model = gtk_combo_box_get_model(GTK_COMBO_BOX(w));
- if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(w), &iter))
+ tree_model = gtk_combo_box_get_model(combo_box);
+ if (!gtk_combo_box_get_active_iter(combo_box, &iter))
return;
+ active.type = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(combo_box),
+ "type"));
- 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);
+ 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)));
- purple_prefs_set_int(key, int_value);
+ 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)
+{
+ 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;
- 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;
+ GtkWidget *dropdown;
+ GtkWidget *label = NULL;
+ gchar *text;
GtkListStore *store = NULL;
GtkTreeIter iter;
GtkTreeIter active;
GtkCellRenderer *renderer;
+ gpointer current_active;
g_return_val_if_fail(menuitems != NULL, NULL);
- if (type == PURPLE_PREF_INT) {
+ if (initial.type == PURPLE_PREF_INT) {
store = gtk_list_store_new(PREF_DROPDOWN_COUNT, G_TYPE_STRING, G_TYPE_INT);
- stored_int = purple_prefs_get_int(key);
- } else if (type == PURPLE_PREF_STRING) {
+ } else if (initial.type == PURPLE_PREF_STRING) {
store = gtk_list_store_new(PREF_DROPDOWN_COUNT, G_TYPE_STRING, G_TYPE_STRING);
- stored_str = purple_prefs_get_string(key);
- } else if (type == PURPLE_PREF_BOOLEAN) {
+ } else if (initial.type == PURPLE_PREF_BOOLEAN) {
store = gtk_list_store_new(PREF_DROPDOWN_COUNT, G_TYPE_STRING, G_TYPE_BOOLEAN);
- stored_bool = purple_prefs_get_bool(key);
} else {
g_warn_if_reached();
return NULL;
}
dropdown = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
- g_object_set_data(G_OBJECT(dropdown), "type", GINT_TO_POINTER(type));
+ if (dropdown_out != NULL)
+ *dropdown_out = GTK_COMBO_BOX(dropdown);
+ g_object_set_data(G_OBJECT(dropdown), "type", GINT_TO_POINTER(initial.type));
while (menuitems != NULL && (text = (char *)menuitems->data) != NULL) {
+ int int_value = 0;
+ const char *str_value = NULL;
+ gboolean bool_value = FALSE;
+
menuitems = g_list_next(menuitems);
g_return_val_if_fail(menuitems != NULL, NULL);
@@ -354,30 +380,31 @@ pidgin_prefs_dropdown_from_list(GtkWidget *box, const gchar *title,
PREF_DROPDOWN_TEXT, text,
-1);
- if (type == PURPLE_PREF_INT) {
+ if (initial.type == PURPLE_PREF_INT) {
int_value = GPOINTER_TO_INT(menuitems->data);
gtk_list_store_set(store, &iter,
PREF_DROPDOWN_VALUE, int_value,
-1);
}
- else if (type == PURPLE_PREF_STRING) {
+ else if (initial.type == PURPLE_PREF_STRING) {
str_value = (const char *)menuitems->data;
gtk_list_store_set(store, &iter,
PREF_DROPDOWN_VALUE, str_value,
-1);
}
- else if (type == PURPLE_PREF_BOOLEAN) {
+ else if (initial.type == PURPLE_PREF_BOOLEAN) {
bool_value = (gboolean)GPOINTER_TO_INT(menuitems->data);
gtk_list_store_set(store, &iter,
PREF_DROPDOWN_VALUE, bool_value,
-1);
}
- if ((type == PURPLE_PREF_INT && stored_int == int_value) ||
- (type == PURPLE_PREF_STRING && stored_str != NULL &&
- !strcmp(stored_str, str_value)) ||
- (type == PURPLE_PREF_BOOLEAN &&
- (stored_bool == bool_value))) {
+ if ((initial.type == PURPLE_PREF_INT &&
+ initial.value.integer == int_value) ||
+ (initial.type == PURPLE_PREF_STRING &&
+ !g_strcmp0(initial.value.string, str_value)) ||
+ (initial.type == PURPLE_PREF_BOOLEAN &&
+ (initial.value.boolean == bool_value))) {
active = iter;
}
@@ -392,15 +419,65 @@ pidgin_prefs_dropdown_from_list(GtkWidget *box, const gchar *title,
NULL);
gtk_combo_box_set_active_iter(GTK_COMBO_BOX(dropdown), &active);
+ current_active = GINT_TO_POINTER(gtk_combo_box_get_active(GTK_COMBO_BOX(
+ dropdown)));
+ g_object_set_data(G_OBJECT(dropdown), "current_active", current_active);
+ g_object_set_data(G_OBJECT(dropdown), "previously_active", current_active);
g_signal_connect(G_OBJECT(dropdown), "changed",
- G_CALLBACK(dropdown_set), (char *)key);
+ G_CALLBACK(dropdown_set), cb);
pidgin_add_widget_to_vbox(GTK_BOX(box), title, NULL, dropdown, FALSE, &label);
return label;
}
+static void
+pidgin_prefs_dropdown_from_list_cb(GtkComboBox *combo_box,
+ PidginPrefValue value)
+{
+ const char *key;
+
+ key = g_object_get_data(G_OBJECT(combo_box), "key");
+
+ if (value.type == PURPLE_PREF_INT) {
+ purple_prefs_set_int(key, value.value.integer);
+ } else if (value.type == PURPLE_PREF_STRING) {
+ purple_prefs_set_string(key, value.value.string);
+ } else if (value.type == PURPLE_PREF_BOOLEAN) {
+ purple_prefs_set_bool(key, value.value.boolean);
+ } else {
+ g_return_if_reached();
+ }
+}
+
+GtkWidget *
+pidgin_prefs_dropdown_from_list(GtkWidget *box, const gchar *title,
+ PurplePrefType type, const char *key, GList *menuitems)
+{
+ PidginPrefValue initial;
+ GtkComboBox *dropdown = NULL;
+ GtkWidget *label;
+
+ initial.type = type;
+ if (type == PURPLE_PREF_INT) {
+ initial.value.integer = purple_prefs_get_int(key);
+ } else if (type == PURPLE_PREF_STRING) {
+ initial.value.string = purple_prefs_get_string(key);
+ } else if (type == PURPLE_PREF_BOOLEAN) {
+ initial.value.boolean = purple_prefs_get_bool(key);
+ } else {
+ g_return_val_if_reached(NULL);
+ }
+
+ label = pidgin_prefs_dropdown_from_list_with_cb(box, title, &dropdown,
+ menuitems, initial, pidgin_prefs_dropdown_from_list_cb);
+
+ g_object_set_data(G_OBJECT(dropdown), "key", (gpointer)key);
+
+ return label;
+}
+
GtkWidget *
pidgin_prefs_dropdown(GtkWidget *box, const gchar *title, PurplePrefType type,
const char *key, ...)
@@ -464,6 +541,8 @@ delete_prefs(GtkWidget *asdf, void *gdsa)
prefs_status_themes_combo_box = NULL;
prefs_smiley_themes_combo_box = NULL;
+ keyring_page_pref_set_instance = NULL;
+
sample_webview = NULL;
notebook_page = 0;
@@ -2507,6 +2586,116 @@ logging_page(void)
return ret;
}
+static void
+change_master_password_cb(GtkWidget *button, gpointer ptr)
+{
+ purple_keyring_change_master(NULL, NULL);
+}
+
+static void
+keyring_page_pref_set_inuse(GError *error, gpointer _combo_box)
+{
+ GtkComboBox *combo_box = _combo_box;
+ PurpleKeyring *in_use = purple_keyring_get_inuse();
+
+ if (keyring_page_pref_set_instance != combo_box) {
+ purple_debug_info("gtkprefs", "pref window already closed\n");
+ return;
+ }
+ keyring_page_pref_set_instance = NULL;
+
+ gtk_widget_set_sensitive(GTK_WIDGET(combo_box), TRUE);
+
+ if (error != NULL) {
+ pidgin_prefs_dropdown_revert_active(combo_box);
+ purple_notify_error(NULL, _("Keyring"),
+ _("Failed to set new keyring"), error->message);
+ return;
+ }
+
+ g_return_if_fail(in_use != NULL);
+ purple_prefs_set_string("/purple/keyring/active",
+ purple_keyring_get_id(in_use));
+}
+
+static void
+keyring_page_pref_changed(GtkComboBox *combo_box, PidginPrefValue value)
+{
+ const char *keyring_id;
+ PurpleKeyring *keyring;
+ GtkWidget *change_master_button;
+
+ g_return_if_fail(combo_box != NULL);
+ g_return_if_fail(value.type == PURPLE_PREF_STRING);
+
+ change_master_button = g_object_get_data(G_OBJECT(combo_box),
+ "change_master_button");
+
+ keyring_id = value.value.string;
+ keyring = purple_keyring_find_keyring_by_id(keyring_id);
+ if (keyring == NULL) {
+ pidgin_prefs_dropdown_revert_active(combo_box);
+ purple_notify_error(NULL, _("Keyring"),
+ _("Selected keyring is disabled"), NULL);
+ return;
+ }
+
+ gtk_widget_set_sensitive(GTK_WIDGET(combo_box), FALSE);
+
+ keyring_page_pref_set_instance = combo_box;
+ purple_keyring_set_inuse(keyring, FALSE, keyring_page_pref_set_inuse,
+ combo_box);
+
+ if (purple_keyring_get_change_master(keyring))
+ gtk_widget_set_sensitive(change_master_button, TRUE);
+ else
+ gtk_widget_set_sensitive(change_master_button, FALSE);
+}
+
+static GtkWidget *
+keyring_page(void)
+{
+ GtkWidget *ret;
+ GtkWidget *vbox;
+ GtkWidget *button;
+ GList *names;
+ const char *keyring_id;
+ PurpleKeyring *keyring;
+ PidginPrefValue initial;
+ GtkComboBox *dropdown = NULL;
+
+ keyring_id = purple_prefs_get_string("/purple/keyring/active");
+ keyring = purple_keyring_find_keyring_by_id(keyring_id);
+
+ ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
+ gtk_container_set_border_width(GTK_CONTAINER (ret), PIDGIN_HIG_BORDER);
+
+ /* Change master password */
+ button = gtk_button_new_with_mnemonic(_("_Change master password."));
+
+ /* Keyring selection */
+ vbox = pidgin_make_frame(ret, _("Keyring"));
+ names = purple_keyring_get_options();
+ initial.type = PURPLE_PREF_STRING;
+ initial.value.string = purple_prefs_get_string("/purple/keyring/active");
+ pidgin_prefs_dropdown_from_list_with_cb(vbox, _("Keyring:"), &dropdown,
+ names, initial, keyring_page_pref_changed);
+ g_object_set_data(G_OBJECT(dropdown), "change_master_button", button);
+ g_list_free(names);
+
+ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(change_master_password_cb), NULL);
+
+ if (keyring && purple_keyring_get_change_master(keyring))
+ gtk_widget_set_sensitive(button, TRUE);
+ else
+ gtk_widget_set_sensitive(button, FALSE);
+
+ gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 1);
+ gtk_widget_show_all(ret);
+
+ return ret;
+}
+
#ifndef _WIN32
static gint
sound_cmd_yeah(GtkEntry *entry, gpointer d)
@@ -3674,6 +3863,7 @@ prefs_notebook_init(void)
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++);