summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomasz Wasilczyk <tomkiewicz@cpw.pidgin.im>2013-05-12 13:05:00 +0200
committerTomasz Wasilczyk <tomkiewicz@cpw.pidgin.im>2013-05-12 13:05:00 +0200
commitce2e03fea8a3be0b969a27f801ee94b9994a8def (patch)
tree94d69a99094d721e46a45f066aee7cdace164dcf
parent1efe22ca171a416c6a463410407dbbe2efee1321 (diff)
parentf1807cbcc85d7f36913597e31a8a741c7e37d0c2 (diff)
downloadpidgin-ce2e03fea8a3be0b969a27f801ee94b9994a8def.tar.gz
Merge with 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.c49
-rw-r--r--finch/gntprefs.h5
-rw-r--r--finch/gntui.c1
-rw-r--r--libpurple/Makefile.am4
-rw-r--r--libpurple/Makefile.mingw14
-rw-r--r--libpurple/account.c232
-rw-r--r--libpurple/account.h25
-rw-r--r--libpurple/cipher.c9
-rw-r--r--libpurple/ciphers/Makefile.am14
-rw-r--r--libpurple/ciphers/aes.c566
-rw-r--r--libpurple/ciphers/ciphers.h28
-rw-r--r--libpurple/ciphers/pbkdf2.c323
-rw-r--r--libpurple/connection.c9
-rw-r--r--libpurple/core.c6
-rw-r--r--libpurple/example/nullclient.c2
-rw-r--r--libpurple/keyring.c1356
-rw-r--r--libpurple/keyring.h548
-rw-r--r--libpurple/plugins/Makefile.am11
-rw-r--r--libpurple/plugins/Makefile.mingw4
-rw-r--r--libpurple/plugins/ciphertest.c339
-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.c327
-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/request.h2
-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.c445
-rw-r--r--pidgin/gtkutils.c12
63 files changed, 6504 insertions, 201 deletions
diff --git a/.hgignore b/.hgignore
index 554cbe0ca0..65601cd8d8 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 2748c8aeb3..da9bcdad32 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 f71ffbad6f..4341edfd9e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -110,6 +110,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"
@@ -1479,6 +1480,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"
@@ -1534,6 +1536,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 #######################################################################
@@ -2429,6 +2576,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)
@@ -2707,6 +2855,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
@@ -2790,6 +2939,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 c99d6b3302..7c396fff2d 100644
--- a/finch/gntprefs.c
+++ b/finch/gntprefs.c
@@ -41,6 +41,7 @@ static struct {
GList *freestrings; /* strings to be freed when the pref-window is closed */
gboolean showing;
GntWidget *window;
+ GntWidget *keyring_window;
} pref_request;
void finch_prefs_init()
@@ -195,6 +196,12 @@ 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},
@@ -246,10 +253,15 @@ void finch_prefs_show_all()
return;
}
+ if (pref_request.keyring_window != NULL)
+ purple_request_close(PURPLE_REQUEST_FIELDS,
+ pref_request.keyring_window);
+
fields = purple_request_fields_new();
add_pref_group(fields, _("Buddy List"), blist);
add_pref_group(fields, _("Conversations"), convs);
+ add_pref_group(fields, _("Keyring"), keyring);
add_pref_group(fields, _("Logging"), logging);
add_pref_group(fields, _("Idle"), idle);
@@ -260,3 +272,40 @@ void finch_prefs_show_all()
NULL);
}
+static void
+finch_prefs_keyring_save(void *data, PurpleRequestFields *fields)
+{
+ pref_request.keyring_window = NULL;
+
+ purple_keyring_apply_settings(NULL, fields);
+}
+
+static void
+finch_prefs_keyring_cancel(void)
+{
+ pref_request.keyring_window = NULL;
+}
+
+void finch_prefs_show_keyring(void)
+{
+ PurpleRequestFields *fields;
+
+ if (pref_request.keyring_window != NULL) {
+ gnt_window_present(pref_request.keyring_window);
+ return;
+ }
+
+ fields = purple_keyring_read_settings();
+ if (fields == NULL) {
+ purple_notify_info(NULL, _("Keyring settings"),
+ _("Selected keyring doesn't allow any configuration"),
+ NULL);
+ return;
+ }
+
+ pref_request.keyring_window = purple_request_fields(NULL,
+ _("Keyring settings"), NULL, NULL, fields,
+ _("Save"), G_CALLBACK(finch_prefs_keyring_save),
+ _("Cancel"), G_CALLBACK(finch_prefs_keyring_cancel),
+ NULL, NULL, NULL, NULL);
+}
diff --git a/finch/gntprefs.h b/finch/gntprefs.h
index b11a3141f9..88173e13a1 100644
--- a/finch/gntprefs.h
+++ b/finch/gntprefs.h
@@ -42,6 +42,11 @@ void finch_prefs_init(void);
void finch_prefs_show_all(void);
/**
+ * Show the preferences dialog for the selected keyring.
+ */
+void finch_prefs_show_keyring(void);
+
+/**
* You don't need to know about this.
*/
void finch_prefs_update_old(void);
diff --git a/finch/gntui.c b/finch/gntui.c
index bac9ec0912..833d464d7d 100644
--- a/finch/gntui.c
+++ b/finch/gntui.c
@@ -106,6 +106,7 @@ void gnt_ui_init()
gnt_register_action(_("Room List"), finch_roomlist_show_all);
gnt_register_action(_("Sounds"), finch_sounds_show_all);
gnt_register_action(_("Preferences"), finch_prefs_show_all);
+ gnt_register_action(_("Keyring settings"), finch_prefs_show_keyring);
gnt_register_action(_("Statuses"), finch_savedstatus_show_all);
#ifdef STANDALONE
diff --git a/libpurple/Makefile.am b/libpurple/Makefile.am
index 0e9b9a2984..3cd42b71ad 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 \
@@ -315,6 +317,8 @@ libpurple_la_LIBADD = \
$(GSTINTERFACES_LIBS) \
$(IDN_LIBS) \
$(JSON_LIBS) \
+ $(GNUTLS_LIBS) \
+ $(NSS_LIBS) \
ciphers/libpurple-ciphers.la \
-lm
diff --git a/libpurple/Makefile.mingw b/libpurple/Makefile.mingw
index a0fb6429ac..d226c6ae34 100644
--- a/libpurple/Makefile.mingw
+++ b/libpurple/Makefile.mingw
@@ -33,11 +33,14 @@ INCLUDE_PATHS += \
-I$(GTK_TOP)/include/glib-2.0 \
-I$(GTK_TOP)/lib/glib-2.0/include \
-I$(JSON_GLIB_TOP)/include/json-glib-1.0 \
- -I$(LIBXML2_TOP)/include/libxml2
+ -I$(LIBXML2_TOP)/include/libxml2 \
+ -I$(NSS_TOP)/include/nspr4 \
+ -I$(NSS_TOP)/include/nss3
LIB_PATHS += -L$(GTK_TOP)/lib \
-L$(JSON_GLIB_TOP)/lib \
- -L$(LIBXML2_TOP)/lib
+ -L$(LIBXML2_TOP)/lib \
+ -L$(NSS_TOP)/lib
##
## SOURCES, OBJECTS
@@ -49,10 +52,12 @@ C_SRC = \
buddyicon.c \
certificate.c \
cipher.c \
+ ciphers/aes.c \
ciphers/des.c \
ciphers/gchecksum.c \
ciphers/hmac.c \
ciphers/md4.c \
+ ciphers/pbkdf2.c \
ciphers/rc4.c \
circbuffer.c \
cmds.c \
@@ -67,6 +72,7 @@ C_SRC = \
http.c \
idle.c \
imgstore.c \
+ keyring.c \
log.c \
media/candidate.c \
media/enum-types.c \
@@ -127,7 +133,9 @@ LIBS = \
-lintl \
-lws2_32 \
-lxml2 \
- -ljson-glib-1.0
+ -ljson-glib-1.0 \
+ -lnss3 \
+ -lnspr4
include $(PIDGIN_COMMON_RULES)
diff --git a/libpurple/account.c b/libpurple/account.c
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/cipher.c b/libpurple/cipher.c
index 4dfc571ab3..1df779caab 100644
--- a/libpurple/cipher.c
+++ b/libpurple/cipher.c
@@ -248,14 +248,7 @@ purple_ciphers_init() {
purple_value_new(PURPLE_TYPE_SUBTYPE,
PURPLE_SUBTYPE_CIPHER));
- purple_ciphers_register_cipher("md5", purple_md5_cipher_get_ops());
- purple_ciphers_register_cipher("sha1", purple_sha1_cipher_get_ops());
- purple_ciphers_register_cipher("sha256", purple_sha256_cipher_get_ops());
- purple_ciphers_register_cipher("md4", purple_md4_cipher_get_ops());
- purple_ciphers_register_cipher("hmac", purple_hmac_cipher_get_ops());
- purple_ciphers_register_cipher("des", purple_des_cipher_get_ops());
- purple_ciphers_register_cipher("des3", purple_des3_cipher_get_ops());
- purple_ciphers_register_cipher("rc4", purple_rc4_cipher_get_ops());
+ purple_ciphers_register_all();
}
void
diff --git a/libpurple/ciphers/Makefile.am b/libpurple/ciphers/Makefile.am
index 67d887694e..c2c1e7544b 100644
--- a/libpurple/ciphers/Makefile.am
+++ b/libpurple/ciphers/Makefile.am
@@ -1,10 +1,20 @@
noinst_LTLIBRARIES=libpurple-ciphers.la
+# XXX: cipher.lo won't be updated after a change in cipher files
+
+if USE_NSS
+AES_SOURCE = aes.c
+endif
+if USE_GNUTLS
+AES_SOURCE = aes.c
+endif
libpurple_ciphers_la_SOURCES=\
+ $(AES_SOURCE) \
des.c \
gchecksum.c \
hmac.c \
md4.c \
+ pbkdf2.c \
rc4.c
noinst_HEADERS =\
@@ -16,4 +26,6 @@ AM_CPPFLAGS = \
$(INTGG_CFLAGS) \
$(AM_CFLAGS) \
$(GLIB_CFLAGS) \
- $(DEBUG_CFLAGS)
+ $(DEBUG_CFLAGS) \
+ $(GNUTLS_CFLAGS) \
+ $(NSS_CFLAGS)
diff --git a/libpurple/ciphers/aes.c b/libpurple/ciphers/aes.c
new file mode 100644
index 0000000000..ac340eedfd
--- /dev/null
+++ b/libpurple/ciphers/aes.c
@@ -0,0 +1,566 @@
+/*
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ *
+ * Written by Tomek Wasilczyk <tomkiewicz@cpw.pidgin.im>
+ */
+
+#include "internal.h"
+#include "cipher.h"
+#include "ciphers.h"
+#include "debug.h"
+
+#if defined(HAVE_GNUTLS)
+# define PURPLE_AES_USE_GNUTLS 1
+# include <gnutls/gnutls.h>
+# include <gnutls/crypto.h>
+#elif defined(HAVE_NSS)
+# define PURPLE_AES_USE_NSS 1
+# include <nss.h>
+# include <pk11pub.h>
+# include <prerror.h>
+#else
+# error "No GnuTLS or NSS support"
+#endif
+
+/* 128bit */
+#define PURPLE_AES_BLOCK_SIZE 16
+
+typedef struct
+{
+ guchar iv[PURPLE_AES_BLOCK_SIZE];
+ guchar key[32];
+ guint key_size;
+ gboolean failure;
+} AESContext;
+
+typedef gboolean (*purple_aes_crypt_func)(
+ const guchar *input, guchar *output, size_t len,
+ guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size);
+
+static void
+purple_aes_init(PurpleCipherContext *context, void *extra)
+{
+ AESContext *ctx_data;
+
+ ctx_data = g_new0(AESContext, 1);
+ purple_cipher_context_set_data(context, ctx_data);
+
+ purple_cipher_context_reset(context, extra);
+}
+
+static void
+purple_aes_uninit(PurpleCipherContext *context)
+{
+ AESContext *ctx_data;
+
+ purple_cipher_context_reset(context, NULL);
+
+ ctx_data = purple_cipher_context_get_data(context);
+ g_free(ctx_data);
+ purple_cipher_context_set_data(context, NULL);
+}
+
+static void
+purple_aes_reset(PurpleCipherContext *context, void *extra)
+{
+ AESContext *ctx_data = purple_cipher_context_get_data(context);
+
+ g_return_if_fail(ctx_data != NULL);
+
+ memset(ctx_data->iv, 0, sizeof(ctx_data->iv));
+ memset(ctx_data->key, 0, sizeof(ctx_data->key));
+ ctx_data->key_size = 32; /* 256bit */
+ ctx_data->failure = FALSE;
+}
+
+static void
+purple_aes_set_option(PurpleCipherContext *context, const gchar *name,
+ void *value)
+{
+ AESContext *ctx_data = purple_cipher_context_get_data(context);
+
+ purple_debug_error("cipher-aes", "set_option not supported\n");
+ ctx_data->failure = TRUE;
+}
+
+static void
+purple_aes_set_iv(PurpleCipherContext *context, guchar *iv, size_t len)
+{
+ AESContext *ctx_data = purple_cipher_context_get_data(context);
+
+ if ((len > 0 && iv == NULL) ||
+ (len != 0 && len != sizeof(ctx_data->iv))) {
+ purple_debug_error("cipher-aes", "invalid IV length\n");
+ ctx_data->failure = TRUE;
+ return;
+ }
+
+ if (len == 0)
+ memset(ctx_data->iv, 0, sizeof(ctx_data->iv));
+ else
+ memcpy(ctx_data->iv, iv, len);
+}
+
+static void
+purple_aes_set_key(PurpleCipherContext *context, const guchar *key, size_t len)
+{
+ AESContext *ctx_data = purple_cipher_context_get_data(context);
+
+ if ((len > 0 && key == NULL) ||
+ (len != 0 && len != 16 && len != 24 && len != 32)) {
+ purple_debug_error("cipher-aes", "invalid key length\n");
+ ctx_data->failure = TRUE;
+ return;
+ }
+
+ ctx_data->key_size = len;
+ memset(ctx_data->key, 0, sizeof(ctx_data->key));
+ if (len > 0)
+ memcpy(ctx_data->key, key, len);
+}
+
+static guchar *
+purple_aes_pad_pkcs7(const guchar input[], size_t in_len, size_t *out_len)
+{
+ int padding_len, total_len;
+ guchar *padded;
+
+ g_return_val_if_fail(input != NULL, NULL);
+ g_return_val_if_fail(out_len != NULL, NULL);
+
+ padding_len = PURPLE_AES_BLOCK_SIZE - (in_len % PURPLE_AES_BLOCK_SIZE);
+ total_len = in_len + padding_len;
+ g_assert((total_len % PURPLE_AES_BLOCK_SIZE) == 0);
+
+ padded = g_new(guchar, total_len);
+ *out_len = total_len;
+
+ memcpy(padded, input, in_len);
+ memset(padded + in_len, padding_len, padding_len);
+
+ return padded;
+}
+
+static ssize_t
+purple_aes_unpad_pkcs7(guchar input[], size_t in_len)
+{
+ int padding_len, i;
+ size_t out_len;
+
+ g_return_val_if_fail(input != NULL, -1);
+ g_return_val_if_fail(in_len > 0, -1);
+
+ padding_len = input[in_len - 1];
+ if (padding_len <= 0 || padding_len > PURPLE_AES_BLOCK_SIZE ||
+ padding_len > in_len) {
+ purple_debug_warning("cipher-aes",
+ "Invalid padding length: %d (total %d) - "
+ "most probably, the key was invalid\n",
+ padding_len, in_len);
+ return -1;
+ }
+
+ out_len = in_len - padding_len;
+ for (i = 0; i < padding_len; i++) {
+ if (input[out_len + i] != padding_len) {
+ purple_debug_warning("cipher-aes",
+ "Padding doesn't match at pos %d (found %02x, "
+ "expected %02x) - "
+ "most probably, the key was invalid\n",
+ i, input[out_len + i], padding_len);
+ return -1;
+ }
+ }
+
+ memset(input + out_len, 0, padding_len);
+ return out_len;
+}
+
+#ifdef PURPLE_AES_USE_GNUTLS
+
+static gnutls_cipher_hd_t
+purple_aes_crypt_gnutls_init(guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32],
+ guint key_size)
+{
+ gnutls_cipher_hd_t handle;
+ gnutls_cipher_algorithm_t algorithm;
+ gnutls_datum_t key_info, iv_info;
+ int ret;
+
+ if (key_size == 16)
+ algorithm = GNUTLS_CIPHER_AES_128_CBC;
+ else if (key_size == 24)
+ algorithm = GNUTLS_CIPHER_AES_192_CBC;
+ else if (key_size == 32)
+ algorithm = GNUTLS_CIPHER_AES_256_CBC;
+ else
+ g_return_val_if_reached(NULL);
+
+ key_info.data = key;
+ key_info.size = key_size;
+
+ iv_info.data = iv;
+ iv_info.size = PURPLE_AES_BLOCK_SIZE;
+
+ ret = gnutls_cipher_init(&handle, algorithm, &key_info, &iv_info);
+ if (ret != 0) {
+ purple_debug_error("cipher-aes",
+ "gnutls_cipher_init failed: %d\n", ret);
+ return NULL;
+ }
+
+ return handle;
+}
+
+static gboolean
+purple_aes_encrypt_gnutls(const guchar *input, guchar *output, size_t len,
+ guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size)
+{
+ gnutls_cipher_hd_t handle;
+ int ret;
+
+ handle = purple_aes_crypt_gnutls_init(iv, key, key_size);
+ if (handle == NULL)
+ return FALSE;
+
+ ret = gnutls_cipher_encrypt2(handle, input, len, output, len);
+ gnutls_cipher_deinit(handle);
+
+ if (ret != 0) {
+ purple_debug_error("cipher-aes",
+ "gnutls_cipher_encrypt2 failed: %d\n", ret);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+purple_aes_decrypt_gnutls(const guchar *input, guchar *output, size_t len,
+ guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size)
+{
+ gnutls_cipher_hd_t handle;
+ int ret;
+
+ handle = purple_aes_crypt_gnutls_init(iv, key, key_size);
+ if (handle == NULL)
+ return FALSE;
+
+ ret = gnutls_cipher_decrypt2(handle, input, len, output, len);
+ gnutls_cipher_deinit(handle);
+
+ if (ret != 0) {
+ purple_debug_error("cipher-aes",
+ "gnutls_cipher_decrypt2 failed: %d\n", ret);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#endif /* PURPLE_AES_USE_GNUTLS */
+
+#ifdef PURPLE_AES_USE_NSS
+
+typedef struct
+{
+ PK11SlotInfo *slot;
+ PK11SymKey *sym_key;
+ SECItem *sec_param;
+ PK11Context *enc_context;
+} purple_aes_encrypt_nss_context;
+
+static void
+purple_aes_encrypt_nss_context_cleanup(purple_aes_encrypt_nss_context *ctx)
+{
+ g_return_if_fail(ctx != NULL);
+
+ if (ctx->enc_context != NULL)
+ PK11_DestroyContext(ctx->enc_context, TRUE);
+ if (ctx->sec_param != NULL)
+ SECITEM_FreeItem(ctx->sec_param, TRUE);
+ if (ctx->sym_key != NULL)
+ PK11_FreeSymKey(ctx->sym_key);
+ if (ctx->slot != NULL)
+ PK11_FreeSlot(ctx->slot);
+
+ memset(ctx, 0, sizeof(purple_aes_encrypt_nss_context));
+}
+
+static gboolean
+purple_aes_crypt_nss(const guchar *input, guchar *output, size_t len,
+ guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size,
+ CK_ATTRIBUTE_TYPE operation)
+{
+ purple_aes_encrypt_nss_context ctx;
+ CK_MECHANISM_TYPE cipher_mech = CKM_AES_CBC;
+ SECItem key_item, iv_item;
+ SECStatus ret;
+ int outlen = 0;
+ unsigned int outlen_tmp = 0;
+
+ memset(&ctx, 0, sizeof(purple_aes_encrypt_nss_context));
+
+ if (NSS_NoDB_Init(NULL) != SECSuccess) {
+ purple_debug_error("cipher-aes",
+ "NSS_NoDB_Init failed: %d\n", PR_GetError());
+ return FALSE;
+ }
+
+ ctx.slot = PK11_GetBestSlot(cipher_mech, NULL);
+ if (ctx.slot == NULL) {
+ purple_debug_error("cipher-aes",
+ "PK11_GetBestSlot failed: %d\n", PR_GetError());
+ return FALSE;
+ }
+
+ key_item.type = siBuffer;
+ key_item.data = key;
+ key_item.len = key_size;
+ ctx.sym_key = PK11_ImportSymKey(ctx.slot, cipher_mech,
+ PK11_OriginUnwrap, CKA_ENCRYPT, &key_item, NULL);
+ if (ctx.sym_key == NULL) {
+ purple_debug_error("cipher-aes",
+ "PK11_ImportSymKey failed: %d\n", PR_GetError());
+ purple_aes_encrypt_nss_context_cleanup(&ctx);
+ return FALSE;
+ }
+
+ iv_item.type = siBuffer;
+ iv_item.data = iv;
+ iv_item.len = PURPLE_AES_BLOCK_SIZE;
+ ctx.sec_param = PK11_ParamFromIV(cipher_mech, &iv_item);
+ if (ctx.sec_param == NULL) {
+ purple_debug_error("cipher-aes",
+ "PK11_ParamFromIV failed: %d\n", PR_GetError());
+ purple_aes_encrypt_nss_context_cleanup(&ctx);
+ return FALSE;
+ }
+
+ ctx.enc_context = PK11_CreateContextBySymKey(cipher_mech, operation,
+ ctx.sym_key, ctx.sec_param);
+ if (ctx.enc_context == NULL) {
+ purple_debug_error("cipher-aes",
+ "PK11_CreateContextBySymKey failed: %d\n",
+ PR_GetError());
+ purple_aes_encrypt_nss_context_cleanup(&ctx);
+ return FALSE;
+ }
+
+ ret = PK11_CipherOp(ctx.enc_context, output, &outlen, len, input, len);
+ if (ret != SECSuccess) {
+ purple_debug_error("cipher-aes",
+ "PK11_CipherOp failed: %d\n", PR_GetError());
+ purple_aes_encrypt_nss_context_cleanup(&ctx);
+ return FALSE;
+ }
+
+ ret = PK11_DigestFinal(ctx.enc_context, output + outlen, &outlen_tmp,
+ len - outlen);
+ if (ret != SECSuccess) {
+ purple_debug_error("cipher-aes",
+ "PK11_DigestFinal failed: %d\n", PR_GetError());
+ purple_aes_encrypt_nss_context_cleanup(&ctx);
+ return FALSE;
+ }
+
+ purple_aes_encrypt_nss_context_cleanup(&ctx);
+
+ outlen += outlen_tmp;
+ if (outlen != len) {
+ purple_debug_error("cipher-aes",
+ "resulting length doesn't match: %d (expected: %d)\n",
+ outlen, len);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+purple_aes_encrypt_nss(const guchar *input, guchar *output, size_t len,
+ guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size)
+{
+ return purple_aes_crypt_nss(input, output, len, iv, key, key_size,
+ CKA_ENCRYPT);
+}
+
+static gboolean
+purple_aes_decrypt_nss(const guchar *input, guchar *output, size_t len,
+ guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size)
+{
+ return purple_aes_crypt_nss(input, output, len, iv, key, key_size,
+ CKA_DECRYPT);
+}
+
+#endif /* PURPLE_AES_USE_NSS */
+
+static ssize_t
+purple_aes_encrypt(PurpleCipherContext *context, const guchar input[],
+ size_t in_len, guchar output[], size_t out_size)
+{
+ AESContext *ctx_data = purple_cipher_context_get_data(context);
+ purple_aes_crypt_func encrypt_func;
+ guchar *input_padded;
+ size_t out_len = 0;
+ gboolean succ;
+
+ if (ctx_data->failure)
+ return -1;
+
+ input_padded = purple_aes_pad_pkcs7(input, in_len, &out_len);
+
+ if (out_len > out_size) {
+ purple_debug_error("cipher-aes", "Output buffer too small\n");
+ memset(input_padded, 0, out_len);
+ g_free(input_padded);
+ return -1;
+ }
+
+#if defined(PURPLE_AES_USE_GNUTLS)
+ encrypt_func = purple_aes_encrypt_gnutls;
+#elif defined(PURPLE_AES_USE_NSS)
+ encrypt_func = purple_aes_encrypt_nss;
+#else
+# error "No matching encrypt_func"
+#endif
+
+ succ = encrypt_func(input_padded, output, out_len, ctx_data->iv,
+ ctx_data->key, ctx_data->key_size);
+
+ memset(input_padded, 0, out_len);
+ g_free(input_padded);
+
+ if (!succ) {
+ memset(output, 0, out_len);
+ return -1;
+ }
+
+ return out_len;
+}
+
+static ssize_t
+purple_aes_decrypt(PurpleCipherContext *context, const guchar input[],
+ size_t in_len, guchar output[], size_t out_size)
+{
+ AESContext *ctx_data = purple_cipher_context_get_data(context);
+ purple_aes_crypt_func decrypt_func;
+ gboolean succ;
+ ssize_t out_len;
+
+ if (ctx_data->failure)
+ return -1;
+
+ if (in_len > out_size) {
+ purple_debug_error("cipher-aes", "Output buffer too small\n");
+ return -1;
+ }
+
+ if ((in_len % PURPLE_AES_BLOCK_SIZE) != 0 || in_len == 0) {
+ purple_debug_error("cipher-aes", "Malformed data\n");
+ return -1;
+ }
+
+#if defined(PURPLE_AES_USE_GNUTLS)
+ decrypt_func = purple_aes_decrypt_gnutls;
+#elif defined(PURPLE_AES_USE_NSS)
+ decrypt_func = purple_aes_decrypt_nss;
+#else
+# error "No matching encrypt_func"
+#endif
+
+ succ = decrypt_func(input, output, in_len, ctx_data->iv, ctx_data->key,
+ ctx_data->key_size);
+
+ if (!succ) {
+ memset(output, 0, in_len);
+ return -1;
+ }
+
+ out_len = purple_aes_unpad_pkcs7(output, in_len);
+ if (out_len < 0) {
+ memset(output, 0, in_len);
+ return -1;
+ }
+
+ return out_len;
+}
+
+static size_t
+purple_aes_get_key_size(PurpleCipherContext *context)
+{
+ AESContext *ctx_data = purple_cipher_context_get_data(context);
+
+ return ctx_data->key_size;
+}
+
+static void
+purple_aes_set_batch_mode(PurpleCipherContext *context,
+ PurpleCipherBatchMode mode)
+{
+ AESContext *ctx_data = purple_cipher_context_get_data(context);
+
+ if (mode == PURPLE_CIPHER_BATCH_MODE_CBC)
+ return;
+
+ purple_debug_error("cipher-aes", "unsupported batch mode\n");
+ ctx_data->failure = TRUE;
+}
+
+static PurpleCipherBatchMode
+purple_aes_get_batch_mode(PurpleCipherContext *context)
+{
+ return PURPLE_CIPHER_BATCH_MODE_CBC;
+}
+
+static size_t
+purple_aes_get_block_size(PurpleCipherContext *context)
+{
+ return PURPLE_AES_BLOCK_SIZE;
+}
+
+static PurpleCipherOps AESOps = {
+ purple_aes_set_option, /* set_option */
+ NULL, /* get_option */
+ purple_aes_init, /* init */
+ purple_aes_reset, /* reset */
+ NULL, /* reset_state */
+ purple_aes_uninit, /* uninit */
+ purple_aes_set_iv, /* set_iv */
+ NULL, /* append */
+ NULL, /* digest */
+ NULL, /* get_digest_size */
+ purple_aes_encrypt, /* encrypt */
+ purple_aes_decrypt, /* decrypt */
+ NULL, /* set_salt */
+ NULL, /* get_salt_size */
+ purple_aes_set_key, /* set_key */
+ purple_aes_get_key_size, /* get_key_size */
+ purple_aes_set_batch_mode, /* set_batch_mode */
+ purple_aes_get_batch_mode, /* get_batch_mode */
+ purple_aes_get_block_size, /* get_block_size */
+ NULL, NULL, NULL, NULL /* reserved */
+};
+
+PurpleCipherOps *
+purple_aes_cipher_get_ops(void) {
+ return &AESOps;
+}
diff --git a/libpurple/ciphers/ciphers.h b/libpurple/ciphers/ciphers.h
index 333beb2c05..f7bb5e2edf 100644
--- a/libpurple/ciphers/ciphers.h
+++ b/libpurple/ciphers/ciphers.h
@@ -19,6 +19,9 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
+/* aes.c */
+PurpleCipherOps * purple_aes_cipher_get_ops(void);
+
/* des.c */
PurpleCipherOps * purple_des_cipher_get_ops(void);
PurpleCipherOps * purple_des3_cipher_get_ops(void);
@@ -34,5 +37,30 @@ PurpleCipherOps * purple_hmac_cipher_get_ops(void);
/* md4.c */
PurpleCipherOps * purple_md4_cipher_get_ops(void);
+/* pbkdf2.c */
+PurpleCipherOps * purple_pbkdf2_cipher_get_ops(void);
+
/* rc4.c */
PurpleCipherOps * purple_rc4_cipher_get_ops(void);
+
+static inline void purple_ciphers_register_all(void)
+{
+#if defined(HAVE_GNUTLS) || defined(HAVE_NSS)
+ purple_ciphers_register_cipher("aes", purple_aes_cipher_get_ops());
+#endif
+
+ purple_ciphers_register_cipher("des", purple_des_cipher_get_ops());
+ purple_ciphers_register_cipher("des3", purple_des3_cipher_get_ops());
+
+ purple_ciphers_register_cipher("md5", purple_md5_cipher_get_ops());
+ purple_ciphers_register_cipher("sha1", purple_sha1_cipher_get_ops());
+ purple_ciphers_register_cipher("sha256", purple_sha256_cipher_get_ops());
+
+ purple_ciphers_register_cipher("hmac", purple_hmac_cipher_get_ops());
+
+ purple_ciphers_register_cipher("md4", purple_md4_cipher_get_ops());
+
+ purple_ciphers_register_cipher("pbkdf2", purple_pbkdf2_cipher_get_ops());
+
+ purple_ciphers_register_cipher("rc4", purple_rc4_cipher_get_ops());
+}
diff --git a/libpurple/ciphers/pbkdf2.c b/libpurple/ciphers/pbkdf2.c
new file mode 100644
index 0000000000..339ef5168b
--- /dev/null
+++ b/libpurple/ciphers/pbkdf2.c
@@ -0,0 +1,323 @@
+/*
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ *
+ * Written by Tomek Wasilczyk <tomkiewicz@cpw.pidgin.im>
+ */
+
+#include "internal.h"
+#include "cipher.h"
+#include "ciphers.h"
+#include "debug.h"
+
+/* 1024bit */
+#define PBKDF2_HASH_MAX_LEN 128
+
+typedef struct
+{
+ gchar *hash_func;
+ guint iter_count;
+ size_t out_len;
+
+ guchar *salt;
+ size_t salt_len;
+ guchar *passphrase;
+ size_t passphrase_len;
+} Pbkdf2Context;
+
+static void
+purple_pbkdf2_init(PurpleCipherContext *context, void *extra)
+{
+ Pbkdf2Context *ctx_data;
+
+ ctx_data = g_new0(Pbkdf2Context, 1);
+ purple_cipher_context_set_data(context, ctx_data);
+
+ purple_cipher_context_reset(context, extra);
+}
+
+static void
+purple_pbkdf2_uninit(PurpleCipherContext *context)
+{
+ Pbkdf2Context *ctx_data;
+
+ purple_cipher_context_reset(context, NULL);
+
+ ctx_data = purple_cipher_context_get_data(context);
+ g_free(ctx_data);
+ purple_cipher_context_set_data(context, NULL);
+}
+
+static void
+purple_pbkdf2_reset(PurpleCipherContext *context, void *extra)
+{
+ Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+
+ g_return_if_fail(ctx_data != NULL);
+
+ g_free(ctx_data->hash_func);
+ ctx_data->hash_func = NULL;
+ ctx_data->iter_count = 1;
+ ctx_data->out_len = 256;
+
+ purple_cipher_context_reset_state(context, extra);
+}
+
+static void
+purple_pbkdf2_reset_state(PurpleCipherContext *context, void *extra)
+{
+ Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+
+ g_return_if_fail(ctx_data != NULL);
+
+ purple_cipher_context_set_salt(context, NULL, 0);
+ purple_cipher_context_set_key(context, NULL, 0);
+}
+
+static void
+purple_pbkdf2_set_option(PurpleCipherContext *context, const gchar *name,
+ void *value)
+{
+ Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+
+ g_return_if_fail(ctx_data != NULL);
+
+ if (g_strcmp0(name, "hash") == 0) {
+ g_free(ctx_data->hash_func);
+ ctx_data->hash_func = g_strdup(value);
+ return;
+ }
+
+ if (g_strcmp0(name, "iter_count") == 0) {
+ ctx_data->iter_count = GPOINTER_TO_UINT(value);
+ return;
+ }
+
+ if (g_strcmp0(name, "out_len") == 0) {
+ ctx_data->out_len = GPOINTER_TO_UINT(value);
+ return;
+ }
+
+ purple_debug_warning("pbkdf2", "Unknown option: %s\n",
+ name ? name : "(null)");
+}
+
+static void *
+purple_pbkdf2_get_option(PurpleCipherContext *context, const gchar *name)
+{
+ Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+
+ g_return_val_if_fail(ctx_data != NULL, NULL);
+
+ if (g_strcmp0(name, "hash") == 0)
+ return ctx_data->hash_func;
+
+ if (g_strcmp0(name, "iter_count") == 0)
+ return GUINT_TO_POINTER(ctx_data->iter_count);
+
+ if (g_strcmp0(name, "out_len") == 0)
+ return GUINT_TO_POINTER(ctx_data->out_len);
+
+ purple_debug_warning("pbkdf2", "Unknown option: %s\n",
+ name ? name : "(null)");
+ return NULL;
+}
+
+static size_t
+purple_pbkdf2_get_digest_size(PurpleCipherContext *context)
+{
+ Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+
+ g_return_val_if_fail(ctx_data != NULL, 0);
+
+ return ctx_data->out_len;
+}
+
+static void
+purple_pbkdf2_set_salt(PurpleCipherContext *context, const guchar *salt, size_t len)
+{
+ Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+
+ g_return_if_fail(ctx_data != NULL);
+
+ g_free(ctx_data->salt);
+ ctx_data->salt = NULL;
+ ctx_data->salt_len = 0;
+
+ if (len == 0)
+ return;
+ g_return_if_fail(salt != NULL);
+
+ ctx_data->salt = g_memdup(salt, len);
+ ctx_data->salt_len = len;
+}
+
+static void
+purple_pbkdf2_set_key(PurpleCipherContext *context, const guchar *key,
+ size_t len)
+{
+ Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+
+ g_return_if_fail(ctx_data != NULL);
+
+ if (ctx_data->passphrase != NULL) {
+ memset(ctx_data->passphrase, 0, ctx_data->passphrase_len);
+ g_free(ctx_data->passphrase);
+ ctx_data->passphrase = NULL;
+ }
+ ctx_data->passphrase_len = 0;
+
+ if (len == 0)
+ return;
+ g_return_if_fail(key != NULL);
+
+ ctx_data->passphrase = g_memdup(key, len);
+ ctx_data->passphrase_len = len;
+}
+
+/* inspired by gnutls 3.1.10, pbkdf2-sha1.c */
+static gboolean
+purple_pbkdf2_digest(PurpleCipherContext *context, guchar digest[], size_t len)
+{
+ Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context);
+ guchar halfkey[PBKDF2_HASH_MAX_LEN], halfkey_hash[PBKDF2_HASH_MAX_LEN];
+ guint halfkey_len, halfkey_count, halfkey_pad, halfkey_no;
+ guchar *salt_ext;
+ size_t salt_ext_len;
+ guint iter_no;
+ PurpleCipherContext *hash;
+
+ g_return_val_if_fail(ctx_data != NULL, FALSE);
+ g_return_val_if_fail(digest != NULL, FALSE);
+ g_return_val_if_fail(len >= ctx_data->out_len, FALSE);
+
+ g_return_val_if_fail(ctx_data->hash_func != NULL, FALSE);
+ g_return_val_if_fail(ctx_data->iter_count > 0, FALSE);
+ g_return_val_if_fail(ctx_data->passphrase != NULL ||
+ ctx_data->passphrase_len == 0, FALSE);
+ g_return_val_if_fail(ctx_data->salt != NULL || ctx_data->salt_len == 0,
+ FALSE);
+ g_return_val_if_fail(ctx_data->out_len > 0, FALSE);
+ g_return_val_if_fail(ctx_data->out_len < 0xFFFFFFFFU, FALSE);
+
+ salt_ext_len = ctx_data->salt_len + 4;
+
+ hash = purple_cipher_context_new_by_name("hmac", NULL);
+ if (hash == NULL) {
+ purple_debug_error("pbkdf2", "Couldn't create new hmac "
+ "context\n");
+ return FALSE;
+ }
+ purple_cipher_context_set_option(hash, "hash",
+ (void*)ctx_data->hash_func);
+ purple_cipher_context_set_key(hash, (const guchar*)ctx_data->passphrase,
+ ctx_data->passphrase_len);
+
+ halfkey_len = purple_cipher_context_get_digest_size(hash);
+ if (halfkey_len <= 0 || halfkey_len > PBKDF2_HASH_MAX_LEN) {
+ purple_debug_error("pbkdf2", "Unsupported hash function: %s "
+ "(digest size: %d)\n",
+ ctx_data->hash_func ? ctx_data->hash_func : "(null)",
+ halfkey_len);
+ return FALSE;
+ }
+
+ halfkey_count = ((ctx_data->out_len - 1) / halfkey_len) + 1;
+ halfkey_pad = ctx_data->out_len - (halfkey_count - 1) * halfkey_len;
+
+ salt_ext = g_new(guchar, salt_ext_len);
+ memcpy(salt_ext, ctx_data->salt, ctx_data->salt_len);
+
+ for (halfkey_no = 1; halfkey_no <= halfkey_count; halfkey_no++) {
+ memset(halfkey, 0, halfkey_len);
+
+ for (iter_no = 1; iter_no <= ctx_data->iter_count; iter_no++) {
+ int i;
+
+ purple_cipher_context_reset_state(hash, NULL);
+
+ if (iter_no == 1) {
+ salt_ext[salt_ext_len - 4] =
+ (halfkey_no & 0xff000000) >> 24;
+ salt_ext[salt_ext_len - 3] =
+ (halfkey_no & 0x00ff0000) >> 16;
+ salt_ext[salt_ext_len - 2] =
+ (halfkey_no & 0x0000ff00) >> 8;
+ salt_ext[salt_ext_len - 1] =
+ (halfkey_no & 0x000000ff) >> 0;
+
+ purple_cipher_context_append(hash, salt_ext,
+ salt_ext_len);
+ }
+ else
+ purple_cipher_context_append(hash, halfkey_hash,
+ halfkey_len);
+
+ if (!purple_cipher_context_digest(hash, halfkey_hash,
+ halfkey_len)) {
+ purple_debug_error("pbkdf2",
+ "Couldn't retrieve a digest\n");
+ g_free(salt_ext);
+ purple_cipher_context_destroy(hash);
+ return FALSE;
+ }
+
+ for (i = 0; i < halfkey_len; i++)
+ halfkey[i] ^= halfkey_hash[i];
+ }
+
+ memcpy(digest + (halfkey_no - 1) * halfkey_len, halfkey,
+ (halfkey_no == halfkey_count) ? halfkey_pad :
+ halfkey_len);
+ }
+
+ g_free(salt_ext);
+ purple_cipher_context_destroy(hash);
+
+ return TRUE;
+}
+
+static PurpleCipherOps PBKDF2Ops = {
+ purple_pbkdf2_set_option, /* set_option */
+ purple_pbkdf2_get_option, /* get_option */
+ purple_pbkdf2_init, /* init */
+ purple_pbkdf2_reset, /* reset */
+ purple_pbkdf2_reset_state, /* reset_state */
+ purple_pbkdf2_uninit, /* uninit */
+ NULL, /* set_iv */
+ NULL, /* append */
+ purple_pbkdf2_digest, /* digest */
+ purple_pbkdf2_get_digest_size, /* get_digest_size */
+ NULL, /* encrypt */
+ NULL, /* decrypt */
+ purple_pbkdf2_set_salt, /* set_salt */
+ NULL, /* get_salt_size */
+ purple_pbkdf2_set_key, /* set_key */
+ NULL, /* get_key_size */
+ NULL, /* set_batch_mode */
+ NULL, /* get_batch_mode */
+ NULL, /* get_block_size */
+ NULL, NULL, NULL, NULL /* reserved */
+};
+
+PurpleCipherOps *
+purple_pbkdf2_cipher_get_ops(void) {
+ return &PBKDF2Ops;
+}
diff --git a/libpurple/connection.c b/libpurple/connection.c
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..e6c5e5b178
--- /dev/null
+++ b/libpurple/keyring.c
@@ -0,0 +1,1356 @@
+/**
+ * @file keyring.c Keyring API
+ * @ingroup core
+ */
+
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program ; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <glib.h>
+#include <string.h>
+#include "account.h"
+#include "keyring.h"
+#include "signals.h"
+#include "core.h"
+#include "debug.h"
+#include "internal.h"
+#include "dbus-maybe.h"
+
+struct _PurpleKeyring
+{
+ gchar *name;
+ gchar *id;
+ PurpleKeyringRead read_password;
+ PurpleKeyringSave save_password;
+ PurpleKeyringCancelRequests cancel_requests;
+ PurpleKeyringClose close_keyring;
+ PurpleKeyringImportPassword import_password;
+ PurpleKeyringExportPassword export_password;
+ PurpleKeyringReadSettings read_settings;
+ PurpleKeyringApplySettings apply_settings;
+
+ gboolean is_closing;
+ gboolean is_cancelling;
+ gboolean close_after_cancel;
+};
+
+typedef struct
+{
+ GError *error;
+ PurpleKeyringSetInUseCallback cb;
+ gpointer cb_data;
+ PurpleKeyring *new;
+ PurpleKeyring *old;
+
+ /**
+ * We are done when finished is positive and read_outstanding is zero.
+ */
+ gboolean finished;
+ int read_outstanding;
+
+ gboolean abort;
+ gboolean force;
+ gboolean succeeded;
+} PurpleKeyringChangeTracker;
+
+typedef void (*PurpleKeyringDropCallback)(gpointer data);
+
+typedef struct
+{
+ PurpleKeyringDropCallback cb;
+ gpointer cb_data;
+
+ gboolean finished;
+ int drop_outstanding;
+} PurpleKeyringDropTracker;
+
+typedef struct
+{
+ PurpleKeyringSaveCallback cb;
+ gpointer cb_data;
+} PurpleKeyringSetPasswordData;
+
+typedef struct
+{
+ gchar *keyring_id;
+ gchar *mode;
+ gchar *data;
+} PurpleKeyringFailedImport;
+
+static void
+purple_keyring_change_tracker_free(PurpleKeyringChangeTracker *tracker)
+{
+ if (tracker->error)
+ g_error_free(tracker->error);
+ g_free(tracker);
+}
+
+static void
+purple_keyring_failed_import_free(PurpleKeyringFailedImport *import)
+{
+ g_return_if_fail(import != NULL);
+
+ g_free(import->keyring_id);
+ g_free(import->mode);
+ purple_str_wipe(import->data);
+ g_free(import);
+}
+
+static void
+purple_keyring_close(PurpleKeyring *keyring);
+
+static void
+purple_keyring_drop_passwords(PurpleKeyring *keyring,
+ PurpleKeyringDropCallback cb, gpointer data);
+
+/* A list of available keyrings */
+static GList *purple_keyring_keyrings = NULL;
+
+/* Keyring being used. */
+static PurpleKeyring *purple_keyring_inuse = NULL;
+
+/* Keyring id marked to use (may not be loadable). */
+static gchar *purple_keyring_to_use = NULL;
+
+static guint purple_keyring_pref_cbid = 0;
+static GList *purple_keyring_loaded_plugins = NULL;
+static PurpleKeyringChangeTracker *current_change_tracker = NULL;
+static gboolean purple_keyring_is_quitting = FALSE;
+static GHashTable *purple_keyring_failed_imports = NULL;
+
+static const gchar *
+purple_keyring_print_account(PurpleAccount *account)
+{
+ static gchar print_buff[100];
+
+ if (account == NULL) {
+ g_snprintf(print_buff, 100, "(null)");
+ return print_buff;
+ }
+
+ g_snprintf(print_buff, 100, "%s:%s",
+ purple_account_get_protocol_id(account),
+ purple_account_get_username(account));
+ return print_buff;
+}
+
+/**************************************************************************/
+/* Setting used keyrings */
+/**************************************************************************/
+
+PurpleKeyring *
+purple_keyring_find_keyring_by_id(const gchar *id)
+{
+ GList *it;
+
+ for (it = purple_keyring_keyrings; it != NULL; it = it->next) {
+ PurpleKeyring *keyring = it->data;
+ const gchar *curr_id = purple_keyring_get_id(keyring);
+
+ if (g_strcmp0(id, curr_id) == 0)
+ return keyring;
+ }
+
+ return NULL;
+}
+
+static void
+purple_keyring_pref_callback(const gchar *pref, PurplePrefType type,
+ gconstpointer id, gpointer data)
+{
+ PurpleKeyring *new_keyring;
+
+ g_return_if_fail(g_strcmp0(pref, "/purple/keyring/active") == 0);
+ g_return_if_fail(type == PURPLE_PREF_STRING);
+ g_return_if_fail(id != NULL);
+
+ new_keyring = purple_keyring_find_keyring_by_id(id);
+ g_return_if_fail(new_keyring != NULL);
+
+ purple_keyring_set_inuse(new_keyring, FALSE, NULL, NULL);
+}
+
+static void
+purple_keyring_pref_connect(void)
+{
+ g_return_if_fail(purple_keyring_pref_cbid == 0);
+
+ purple_keyring_pref_cbid = purple_prefs_connect_callback(NULL,
+ "/purple/keyring/active", purple_keyring_pref_callback, NULL);
+}
+
+static void
+purple_keyring_pref_disconnect(void)
+{
+ g_return_if_fail(purple_keyring_pref_cbid != 0);
+
+ purple_prefs_disconnect_callback(purple_keyring_pref_cbid);
+ purple_keyring_pref_cbid = 0;
+}
+
+PurpleKeyring *
+purple_keyring_get_inuse(void)
+{
+ return purple_keyring_inuse;
+}
+
+static void
+purple_keyring_set_inuse_drop_cb(gpointer _tracker)
+{
+ PurpleKeyringChangeTracker *tracker = _tracker;
+
+ g_return_if_fail(tracker != NULL);
+
+ if (tracker->succeeded) {
+ purple_keyring_close(tracker->old);
+
+ purple_debug_info("keyring", "Successfully changed keyring.\n");
+
+ purple_keyring_inuse = tracker->new;
+ current_change_tracker = NULL;
+
+ if (tracker->cb != NULL)
+ tracker->cb(NULL, tracker->cb_data);
+ purple_keyring_change_tracker_free(tracker);
+ return;
+ }
+
+ purple_debug_error("keyring", "Failed to change keyring, aborting.\n");
+
+ purple_keyring_close(tracker->new);
+
+ purple_keyring_pref_disconnect();
+ purple_prefs_set_string("/purple/keyring/active",
+ purple_keyring_get_id(tracker->old));
+ purple_keyring_pref_connect();
+
+ current_change_tracker = NULL;
+
+ if (tracker->error == NULL) {
+ tracker->error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_UNKNOWN,
+ "Unknown error has occured");
+ }
+
+ if (tracker->cb != NULL)
+ tracker->cb(tracker->error, tracker->cb_data);
+
+ purple_keyring_change_tracker_free(tracker);
+}
+
+static void
+purple_keyring_set_inuse_save_cb(PurpleAccount *account, GError *error,
+ gpointer _tracker)
+{
+ PurpleKeyringChangeTracker *tracker = _tracker;
+
+ g_return_if_fail(account != NULL);
+ g_return_if_fail(tracker != NULL);
+
+ tracker->read_outstanding--;
+
+ if (g_error_matches(error, PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOPASSWORD)) {
+ if (purple_debug_is_verbose()) {
+ purple_debug_misc("keyring", "No password found while "
+ "changing keyring for account %s: %s.\n",
+ purple_keyring_print_account(account),
+ error->message);
+ }
+ } else if (g_error_matches(error, PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_ACCESSDENIED)) {
+ purple_debug_info("keyring", "Access denied while changing "
+ "keyring for account %s: %s.\n",
+ purple_keyring_print_account(account), error->message);
+ tracker->abort = TRUE;
+ if (tracker->error != NULL)
+ g_error_free(tracker->error);
+ tracker->error = g_error_copy(error);
+ } else if (g_error_matches(error, PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_CANCELLED)) {
+ purple_debug_info("keyring", "Operation cancelled while "
+ "changing keyring for account %s: %s.\n",
+ purple_keyring_print_account(account), error->message);
+ tracker->abort = TRUE;
+ if (tracker->error == NULL)
+ tracker->error = g_error_copy(error);
+ } else if (g_error_matches(error, PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_BACKENDFAIL)) {
+ purple_debug_error("keyring", "Failed to communicate with "
+ "backend while changing keyring for account %s: %s. "
+ "Aborting changes.\n",
+ purple_keyring_print_account(account), error->message);
+ tracker->abort = TRUE;
+ if (tracker->error != NULL)
+ g_error_free(tracker->error);
+ tracker->error = g_error_copy(error);
+ } else if (error != NULL) {
+ purple_debug_error("keyring", "Unknown error while changing "
+ "keyring for account %s: %s. Aborting changes.\n",
+ purple_keyring_print_account(account), error->message);
+ tracker->abort = TRUE;
+ if (tracker->error == NULL)
+ tracker->error = g_error_copy(error);
+ }
+
+ purple_signal_emit(purple_keyring_get_handle(), "password-migration",
+ account);
+
+ if (!tracker->finished || tracker->read_outstanding > 0)
+ return;
+
+ /* This was the last one. */
+ if (tracker->abort && !tracker->force) {
+ tracker->succeeded = FALSE;
+ purple_keyring_drop_passwords(tracker->new,
+ purple_keyring_set_inuse_drop_cb, tracker);
+ } else {
+ tracker->succeeded = TRUE;
+ purple_keyring_drop_passwords(tracker->old,
+ purple_keyring_set_inuse_drop_cb, tracker);
+ }
+}
+
+static void
+purple_keyring_set_inuse_read_cb(PurpleAccount *account, const gchar *password,
+ GError *error, gpointer _tracker)
+{
+ PurpleKeyringChangeTracker *tracker = _tracker;
+ PurpleKeyringSave save_cb;
+
+ g_return_if_fail(account != NULL);
+ g_return_if_fail(tracker != NULL);
+
+ if (tracker->abort) {
+ purple_keyring_set_inuse_save_cb(account, NULL, tracker);
+ return;
+ }
+
+ if (error != NULL) {
+ if (tracker->force == TRUE || g_error_matches(error,
+ PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOPASSWORD)) {
+ /* Don't save password, and ignore it. */
+ } else {
+ tracker->abort = TRUE;
+ }
+ purple_keyring_set_inuse_save_cb(account, error, tracker);
+ return;
+ }
+
+ save_cb = purple_keyring_get_save_password(tracker->new);
+ g_assert(save_cb != NULL);
+
+ save_cb(account, password, purple_keyring_set_inuse_save_cb, tracker);
+}
+
+void
+purple_keyring_set_inuse(PurpleKeyring *newkeyring, gboolean force,
+ PurpleKeyringSetInUseCallback cb, gpointer data)
+{
+ PurpleKeyring *oldkeyring;
+ PurpleKeyringChangeTracker *tracker;
+ GList *it;
+ PurpleKeyringRead read_cb;
+
+ if (current_change_tracker != NULL) {
+ GError *error;
+ purple_debug_error("keyring", "There is password migration "
+ "session already running.\n");
+ if (cb == NULL)
+ return;
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_INTERNAL,
+ "There is a password migration session already running");
+ cb(error, data);
+ g_error_free(error);
+ return;
+ }
+
+ oldkeyring = purple_keyring_get_inuse();
+
+ if (oldkeyring == newkeyring) {
+ if (purple_debug_is_verbose()) {
+ purple_debug_misc("keyring",
+ "Old and new keyring are the same: %s.\n",
+ (newkeyring != NULL) ?
+ newkeyring->id : "(null)");
+ }
+ if (cb != NULL)
+ cb(NULL, data);
+ return;
+ }
+
+ purple_debug_info("keyring", "Attempting to set new keyring: %s.\n",
+ (newkeyring != NULL) ? newkeyring->id : "(null)");
+
+ if (oldkeyring == NULL) { /* No keyring was set before. */
+ if (purple_debug_is_verbose()) {
+ purple_debug_misc("keyring", "Setting keyring for the "
+ "first time: %s.\n", newkeyring->id);
+ }
+
+ purple_keyring_inuse = newkeyring;
+ g_assert(current_change_tracker == NULL);
+ if (cb != NULL)
+ cb(NULL, data);
+ return;
+ }
+
+ /* Starting a migration. */
+
+ read_cb = purple_keyring_get_read_password(oldkeyring);
+ g_assert(read_cb != NULL);
+
+ purple_debug_misc("keyring", "Starting migration from: %s.\n",
+ oldkeyring->id);
+
+ tracker = g_new0(PurpleKeyringChangeTracker, 1);
+ current_change_tracker = tracker;
+
+ tracker->cb = cb;
+ tracker->cb_data = data;
+ tracker->new = newkeyring;
+ tracker->old = oldkeyring;
+ tracker->force = force;
+
+ for (it = purple_accounts_get_all(); it != NULL; it = it->next) {
+ if (tracker->abort) {
+ tracker->finished = TRUE;
+ break;
+ }
+ tracker->read_outstanding++;
+
+ if (it->next == NULL)
+ tracker->finished = TRUE;
+
+ read_cb(it->data, purple_keyring_set_inuse_read_cb, tracker);
+ }
+}
+
+void
+purple_keyring_register(PurpleKeyring *keyring)
+{
+ const gchar *keyring_id;
+
+ g_return_if_fail(keyring != NULL);
+
+ keyring_id = purple_keyring_get_id(keyring);
+
+ purple_debug_info("keyring", "Registering keyring: %s\n",
+ keyring_id ? keyring_id : "(null)");
+
+ if (purple_keyring_get_id(keyring) == NULL ||
+ purple_keyring_get_name(keyring) == NULL ||
+ purple_keyring_get_read_password(keyring) == NULL ||
+ purple_keyring_get_save_password(keyring) == NULL) {
+ purple_debug_error("keyring", "Cannot register %s, some "
+ "required fields are missing.\n",
+ keyring_id ? keyring_id : "(null)");
+ return;
+ }
+
+ if (purple_keyring_find_keyring_by_id(keyring_id) != NULL) {
+ purple_debug_error("keyring",
+ "Keyring is already registered.\n");
+ return;
+ }
+
+ /* If this is the configured keyring, use it. */
+ if (purple_keyring_inuse == NULL &&
+ g_strcmp0(keyring_id, purple_keyring_to_use) == 0) {
+ purple_debug_info("keyring", "Keyring %s matches keyring to "
+ "use, using it.\n", keyring_id);
+ purple_keyring_set_inuse(keyring, TRUE, NULL, NULL);
+ }
+
+ PURPLE_DBUS_REGISTER_POINTER(keyring, PurpleKeyring);
+ purple_signal_emit(purple_keyring_get_handle(), "keyring-register",
+ keyring_id, keyring);
+ if (purple_debug_is_verbose()) {
+ purple_debug_info("keyring", "Registered keyring: %s.\n",
+ keyring_id);
+ }
+
+ purple_keyring_keyrings = g_list_prepend(purple_keyring_keyrings,
+ keyring);
+}
+
+void
+purple_keyring_unregister(PurpleKeyring *keyring)
+{
+ PurpleKeyring *inuse;
+ PurpleKeyring *fallback;
+ const gchar *keyring_id;
+
+ g_return_if_fail(keyring != NULL);
+
+ keyring_id = purple_keyring_get_id(keyring);
+
+ purple_debug_info("keyring", "Unregistering keyring: %s.\n",
+ keyring_id);
+
+ purple_signal_emit(purple_keyring_get_handle(), "keyring-unregister",
+ keyring_id, keyring);
+ PURPLE_DBUS_UNREGISTER_POINTER(keyring);
+
+ inuse = purple_keyring_get_inuse();
+ fallback = purple_keyring_find_keyring_by_id(PURPLE_DEFAULT_KEYRING);
+
+ if (inuse == keyring) {
+ if (inuse != fallback) {
+ purple_keyring_set_inuse(fallback, TRUE, NULL, NULL);
+ } else {
+ fallback = NULL;
+ purple_keyring_set_inuse(NULL, TRUE, NULL, NULL);
+ }
+ }
+
+ purple_keyring_keyrings = g_list_remove(purple_keyring_keyrings,
+ keyring);
+}
+
+GList *
+purple_keyring_get_options(void)
+{
+ GList *options = NULL;
+ GList *it;
+ static gchar currentDisabledName[40];
+
+ if (purple_keyring_get_inuse() == NULL && purple_keyring_to_use != NULL
+ && purple_keyring_to_use[0] != '\0') {
+ g_snprintf(currentDisabledName, sizeof(currentDisabledName),
+ _("%s (disabled)"), purple_keyring_to_use);
+
+ options = g_list_append(options, currentDisabledName);
+ options = g_list_append(options, purple_keyring_to_use);
+ }
+
+ for (it = purple_keyring_keyrings; it != NULL; it = it->next) {
+ PurpleKeyring *keyring = it->data;
+
+ options = g_list_append(options,
+ (gpointer)purple_keyring_get_name(keyring));
+ options = g_list_append(options,
+ (gpointer)purple_keyring_get_id(keyring));
+ }
+
+ return options;
+}
+
+
+/**************************************************************************/
+/* Keyring plugin wrappers */
+/**************************************************************************/
+
+static void
+purple_keyring_close(PurpleKeyring *keyring)
+{
+ PurpleKeyringClose close_cb;
+
+ g_return_if_fail(keyring != NULL);
+
+ if (keyring->is_cancelling) {
+ keyring->close_after_cancel = TRUE;
+ return;
+ }
+ if (keyring->is_closing)
+ return;
+ keyring->is_closing = TRUE;
+
+ close_cb = purple_keyring_get_close_keyring(keyring);
+
+ if (close_cb != NULL)
+ close_cb();
+
+ keyring->is_closing = FALSE;
+}
+
+static void
+purple_keyring_cancel_requests(PurpleKeyring *keyring)
+{
+ PurpleKeyringCancelRequests cancel_cb;
+
+ g_return_if_fail(keyring != NULL);
+
+ if (keyring->is_cancelling)
+ return;
+ keyring->is_cancelling = TRUE;
+
+ cancel_cb = purple_keyring_get_cancel_requests(keyring);
+
+ if (cancel_cb != NULL)
+ cancel_cb();
+
+ keyring->is_cancelling = FALSE;
+
+ if (keyring->close_after_cancel) {
+ keyring->close_after_cancel = FALSE;
+ purple_keyring_close(keyring);
+ }
+}
+
+static void
+purple_keyring_drop_passwords_save_cb(PurpleAccount *account, GError *error,
+ gpointer _tracker)
+{
+ PurpleKeyringDropTracker *tracker = _tracker;
+
+ tracker->drop_outstanding--;
+
+ if (!tracker->finished || tracker->drop_outstanding > 0)
+ return;
+
+ if (tracker->cb)
+ tracker->cb(tracker->cb_data);
+ g_free(tracker);
+}
+
+static void
+purple_keyring_drop_passwords(PurpleKeyring *keyring,
+ PurpleKeyringDropCallback cb, gpointer data)
+{
+ GList *it;
+ PurpleKeyringSave save_cb;
+ PurpleKeyringDropTracker *tracker;
+
+ g_return_if_fail(keyring != NULL);
+
+ save_cb = purple_keyring_get_save_password(keyring);
+ g_assert(save_cb != NULL);
+
+ tracker = g_new0(PurpleKeyringDropTracker, 1);
+ tracker->cb = cb;
+ tracker->cb_data = data;
+
+ for (it = purple_accounts_get_all(); it != NULL; it = it->next) {
+ PurpleAccount *account = it->data;
+
+ tracker->drop_outstanding++;
+ if (it->next == NULL)
+ tracker->finished = TRUE;
+
+ save_cb(account, NULL, purple_keyring_drop_passwords_save_cb,
+ tracker);
+ }
+}
+
+gboolean
+purple_keyring_import_password(PurpleAccount *account, const gchar *keyring_id,
+ const gchar *mode, const gchar *data, GError **error)
+{
+ PurpleKeyring *keyring;
+ PurpleKeyring *inuse;
+ PurpleKeyringImportPassword import;
+
+ g_return_val_if_fail(account != NULL, FALSE);
+
+ if (keyring_id == NULL)
+ keyring_id = PURPLE_DEFAULT_KEYRING;
+
+ purple_debug_misc("keyring", "Importing password for account %s to "
+ "keyring %s.\n", purple_keyring_print_account(account),
+ keyring_id);
+
+ keyring = purple_keyring_find_keyring_by_id(keyring_id);
+ if (keyring == NULL) {
+ if (error != NULL) {
+ *error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_BACKENDFAIL,
+ "Specified keyring is not registered.");
+ }
+ purple_debug_warning("Keyring", "Specified keyring is not "
+ "registered, cannot import password info for account "
+ "%s.\n", purple_keyring_print_account(account));
+ return FALSE;
+ }
+
+ inuse = purple_keyring_get_inuse();
+ if (inuse == NULL) {
+ PurpleKeyringFailedImport *import;
+ if (error != NULL) {
+ *error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOKEYRING,
+ "No keyring loaded, cannot import password "
+ "info");
+ }
+ purple_debug_warning("Keyring",
+ "No keyring loaded, cannot import password info for "
+ "account %s.\n", purple_keyring_print_account(account));
+
+ import = g_new0(PurpleKeyringFailedImport, 1);
+ import->keyring_id = g_strdup(keyring_id);
+ import->mode = g_strdup(mode);
+ import->data = g_strdup(data);
+ g_hash_table_insert(purple_keyring_failed_imports, account,
+ import);
+ return FALSE;
+ }
+
+ if (inuse != keyring) {
+ if (error != NULL) {
+ *error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_INTERNAL,
+ "Specified keyring id does not match the "
+ "loaded one.");
+ }
+ purple_debug_error("keyring",
+ "Specified keyring %s is not currently used (%s). "
+ "Data will be lost.\n", keyring_id,
+ purple_keyring_get_id(inuse));
+ return FALSE;
+ }
+
+ import = purple_keyring_get_import_password(inuse);
+ if (import == NULL) {
+ if (purple_debug_is_verbose()) {
+ purple_debug_misc("Keyring", "Configured keyring "
+ "cannot import password info. This might be "
+ "normal.\n");
+ }
+ return TRUE;
+ }
+
+ return import(account, mode, data, error);
+}
+
+gboolean
+purple_keyring_export_password(PurpleAccount *account, const gchar **keyring_id,
+ const gchar **mode, gchar **data, GError **error,
+ GDestroyNotify *destroy)
+{
+ PurpleKeyring *inuse;
+ PurpleKeyringExportPassword export;
+
+ g_return_val_if_fail(account != NULL, FALSE);
+ g_return_val_if_fail(keyring_id != NULL, FALSE);
+ g_return_val_if_fail(mode != NULL, FALSE);
+ g_return_val_if_fail(data != NULL, FALSE);
+ g_return_val_if_fail(error != NULL, FALSE);
+
+ inuse = purple_keyring_get_inuse();
+
+ if (inuse == NULL) {
+ PurpleKeyringFailedImport *import = g_hash_table_lookup(
+ purple_keyring_failed_imports, account);
+
+ if (import == NULL) {
+ *error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOKEYRING,
+ "No keyring configured, cannot export password "
+ "info");
+ purple_debug_warning("keyring",
+ "No keyring configured, cannot export password "
+ "info.\n");
+ return FALSE;
+ } else {
+ purple_debug_info("keyring", "No keyring configured, "
+ "getting fallback export data for %s.\n",
+ purple_keyring_print_account(account));
+
+ *keyring_id = import->keyring_id;
+ *mode = import->mode;
+ *data = g_strdup(import->data);
+ *destroy = (GDestroyNotify)purple_str_wipe;
+ return TRUE;
+ }
+ }
+
+ if (purple_debug_is_verbose()) {
+ purple_debug_misc("keyring",
+ "Exporting password for account %s from keyring %s\n",
+ purple_keyring_print_account(account),
+ purple_keyring_get_id(inuse));
+ }
+
+ *keyring_id = purple_keyring_get_id(inuse);
+
+ export = purple_keyring_get_export_password(inuse);
+ if (export == NULL) {
+ if (purple_debug_is_verbose()) {
+ purple_debug_misc("Keyring", "Configured keyring "
+ "cannot export password info. This might be "
+ "normal.\n");
+ }
+ *mode = NULL;
+ *data = NULL;
+ *destroy = NULL;
+ return TRUE;
+ }
+
+ return export(account, mode, data, error, destroy);
+}
+
+void
+purple_keyring_get_password(PurpleAccount *account,
+ PurpleKeyringReadCallback cb, gpointer data)
+{
+ GError *error;
+ PurpleKeyring *inuse;
+ PurpleKeyringRead read_cb;
+
+ g_return_if_fail(account != NULL);
+
+ if (purple_keyring_is_quitting) {
+ purple_debug_error("keyring", "Cannot request a password while "
+ "quitting.\n");
+ if (cb == NULL)
+ return;
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_INTERNAL,
+ "Cannot request a password while quitting.");
+ cb(account, NULL, error, data);
+ g_error_free(error);
+ return;
+ }
+
+ inuse = purple_keyring_get_inuse();
+
+ if (inuse == NULL) {
+ purple_debug_error("keyring", "No keyring configured.\n");
+ if (cb == NULL)
+ return;
+
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOKEYRING,
+ "No keyring configured.");
+ cb(account, NULL, error, data);
+ g_error_free(error);
+ }
+
+ read_cb = purple_keyring_get_read_password(inuse);
+ g_assert(read_cb != NULL);
+
+ purple_debug_info("keyring", "Reading password for account %s...\n",
+ purple_keyring_print_account(account));
+ read_cb(account, cb, data);
+}
+
+static void
+purple_keyring_set_password_save_cb(PurpleAccount *account, GError *error,
+ gpointer _set_data)
+{
+ PurpleKeyringSetPasswordData *set_data = _set_data;
+
+ g_return_if_fail(account != NULL);
+ g_return_if_fail(set_data != NULL);
+
+ if (error == NULL && purple_debug_is_verbose()) {
+ purple_debug_misc("keyring", "Password for account %s "
+ "saved successfully.\n",
+ purple_keyring_print_account(account));
+ } else if (purple_debug_is_verbose()) {
+ purple_debug_warning("keyring", "Password for account %s "
+ "not saved successfully.\n",
+ purple_keyring_print_account(account));
+ }
+
+ if (error != NULL) {
+ purple_notify_error(NULL, _("Keyrings"),
+ _("Failed to save a password in keyring."),
+ error->message);
+ }
+
+ if (set_data->cb != NULL)
+ set_data->cb(account, error, set_data->cb_data);
+ g_free(set_data);
+}
+
+void
+purple_keyring_set_password(PurpleAccount *account, const gchar *password,
+ PurpleKeyringSaveCallback cb, gpointer data)
+{
+ GError *error;
+ PurpleKeyring *inuse;
+ PurpleKeyringSave save_cb;
+ PurpleKeyringSetPasswordData *set_data;
+
+ g_return_if_fail(account != NULL);
+
+ if (purple_keyring_is_quitting) {
+ purple_debug_error("keyring", "Cannot save a password while "
+ "quitting.\n");
+ if (cb == NULL)
+ return;
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_INTERNAL,
+ "Cannot save a password while quitting.");
+ cb(account, error, data);
+ g_error_free(error);
+ return;
+ }
+
+ if (current_change_tracker != NULL) {
+ purple_debug_error("keyring", "Cannot save a password during "
+ "password migration.\n");
+ if (cb == NULL)
+ return;
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_INTERNAL,
+ "Cannot save a password during password migration.");
+ cb(account, error, data);
+ g_error_free(error);
+ return;
+ }
+
+ inuse = purple_keyring_get_inuse();
+ if (inuse == NULL) {
+ if (cb == NULL)
+ return;
+ error = g_error_new(PURPLE_KEYRING_ERROR,
+ PURPLE_KEYRING_ERROR_NOKEYRING,
+ "No keyring configured.");
+ cb(account, error, data);
+ g_error_free(error);
+ return;
+ }
+
+ save_cb = purple_keyring_get_save_password(inuse);
+ g_assert(save_cb != NULL);
+
+ set_data = g_new(PurpleKeyringSetPasswordData, 1);
+ set_data->cb = cb;
+ set_data->cb_data = data;
+ purple_debug_info("keyring", "%s password for account %s...\n",
+ (password ? "Saving" : "Removing"),
+ purple_keyring_print_account(account));
+ save_cb(account, password, purple_keyring_set_password_save_cb, set_data);
+}
+
+PurpleRequestFields *
+purple_keyring_read_settings(void)
+{
+ PurpleKeyring *inuse;
+ PurpleKeyringReadSettings read_settings;
+
+ if (purple_keyring_is_quitting || current_change_tracker != NULL) {
+ purple_debug_error("keyring", "Cannot read settngs at the "
+ "moment.\n");
+ return NULL;
+ }
+
+ inuse = purple_keyring_get_inuse();
+ if (inuse == NULL) {
+ purple_debug_error("keyring", "No keyring in use.\n");
+ return NULL;
+ }
+
+ read_settings = purple_keyring_get_read_settings(inuse);
+ if (read_settings == NULL)
+ return NULL;
+ return read_settings();
+}
+
+gboolean
+purple_keyring_apply_settings(void *notify_handle, PurpleRequestFields *fields)
+{
+ PurpleKeyring *inuse;
+ PurpleKeyringApplySettings apply_settings;
+
+ g_return_val_if_fail(fields != NULL, FALSE);
+
+ if (purple_keyring_is_quitting || current_change_tracker != NULL) {
+ purple_debug_error("keyring", "Cannot apply settngs at the "
+ "moment.\n");
+ return FALSE;
+ }
+
+ inuse = purple_keyring_get_inuse();
+ if (inuse == NULL) {
+ purple_debug_error("keyring", "No keyring in use.\n");
+ return FALSE;
+ }
+
+ apply_settings = purple_keyring_get_apply_settings(inuse);
+ if (apply_settings == NULL) {
+ purple_debug_warning("keyring", "Applying settings not "
+ "supported.\n");
+ return FALSE;
+ }
+ return apply_settings(notify_handle, fields);
+}
+
+/**************************************************************************/
+/* PurpleKeyring accessors */
+/**************************************************************************/
+
+PurpleKeyring *
+purple_keyring_new(void)
+{
+ return g_new0(PurpleKeyring, 1);
+}
+
+void
+purple_keyring_free(PurpleKeyring *keyring)
+{
+ g_return_if_fail(keyring != NULL);
+
+ g_free(keyring->name);
+ g_free(keyring->id);
+ g_free(keyring);
+}
+
+const gchar *
+purple_keyring_get_name(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->name;
+}
+
+const gchar *
+purple_keyring_get_id(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->id;
+}
+
+PurpleKeyringRead
+purple_keyring_get_read_password(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->read_password;
+}
+
+PurpleKeyringSave
+purple_keyring_get_save_password(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->save_password;
+}
+
+PurpleKeyringCancelRequests
+purple_keyring_get_cancel_requests(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->cancel_requests;
+}
+
+PurpleKeyringClose
+purple_keyring_get_close_keyring(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->close_keyring;
+}
+
+PurpleKeyringImportPassword
+purple_keyring_get_import_password(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->import_password;
+}
+
+PurpleKeyringExportPassword
+purple_keyring_get_export_password(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->export_password;
+}
+
+PurpleKeyringReadSettings
+purple_keyring_get_read_settings(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->read_settings;
+}
+
+PurpleKeyringApplySettings
+purple_keyring_get_apply_settings(const PurpleKeyring *keyring)
+{
+ g_return_val_if_fail(keyring != NULL, NULL);
+
+ return keyring->apply_settings;
+}
+
+void
+purple_keyring_set_name(PurpleKeyring *keyring, const gchar *name)
+{
+ g_return_if_fail(keyring != NULL);
+ g_return_if_fail(name != NULL);
+
+ g_free(keyring->name);
+ keyring->name = g_strdup(name);
+}
+
+void
+purple_keyring_set_id(PurpleKeyring *keyring, const gchar *id)
+{
+ g_return_if_fail(keyring != NULL);
+ g_return_if_fail(id != NULL);
+
+ g_free(keyring->id);
+ keyring->id = g_strdup(id);
+}
+
+void
+purple_keyring_set_read_password(PurpleKeyring *keyring,
+ PurpleKeyringRead read_cb)
+{
+ g_return_if_fail(keyring != NULL);
+ g_return_if_fail(read_cb != NULL);
+
+ keyring->read_password = read_cb;
+}
+
+void
+purple_keyring_set_save_password(PurpleKeyring *keyring,
+ PurpleKeyringSave save_cb)
+{
+ g_return_if_fail(keyring != NULL);
+ g_return_if_fail(save_cb != NULL);
+
+ keyring->save_password = save_cb;
+}
+
+void
+purple_keyring_set_cancel_requests(PurpleKeyring *keyring,
+ PurpleKeyringCancelRequests cancel_requests)
+{
+ g_return_if_fail(keyring != NULL);
+
+ keyring->cancel_requests = cancel_requests;
+}
+
+void
+purple_keyring_set_close_keyring(PurpleKeyring *keyring,
+ PurpleKeyringClose close_cb)
+{
+ g_return_if_fail(keyring != NULL);
+
+ keyring->close_keyring = close_cb;
+}
+
+void
+purple_keyring_set_import_password(PurpleKeyring *keyring,
+ PurpleKeyringImportPassword import_password)
+{
+ g_return_if_fail(keyring != NULL);
+
+ keyring->import_password = import_password;
+}
+
+void
+purple_keyring_set_export_password(PurpleKeyring *keyring,
+ PurpleKeyringExportPassword export_password)
+{
+ g_return_if_fail(keyring != NULL);
+
+ keyring->export_password = export_password;
+}
+
+void
+purple_keyring_set_read_settings(PurpleKeyring *keyring,
+PurpleKeyringReadSettings read_settings)
+{
+ g_return_if_fail(keyring != NULL);
+
+ keyring->read_settings = read_settings;
+}
+
+void
+purple_keyring_set_apply_settings(PurpleKeyring *keyring,
+PurpleKeyringApplySettings apply_settings)
+{
+ g_return_if_fail(keyring != NULL);
+
+ keyring->apply_settings = apply_settings;
+}
+
+
+/**************************************************************************/
+/* Error Codes */
+/**************************************************************************/
+
+GQuark purple_keyring_error_domain(void)
+{
+ return g_quark_from_static_string("libpurple keyring");
+}
+
+/**************************************************************************/
+/* Keyring Subsystem */
+/**************************************************************************/
+
+static void purple_keyring_core_initialized_cb(void)
+{
+ if (purple_keyring_inuse == NULL) {
+ purple_notify_error(NULL, _("Keyrings"),
+ _("Failed to load selected keyring."),
+ _("Check your system configuration or select another "
+ "one in Preferences dialog."));
+ }
+}
+
+static void purple_keyring_core_quitting_cb()
+{
+ if (current_change_tracker != NULL) {
+ PurpleKeyringChangeTracker *tracker = current_change_tracker;
+ tracker->abort = TRUE;
+ if (tracker->old)
+ purple_keyring_cancel_requests(tracker->old);
+ if (current_change_tracker == tracker && tracker->new)
+ purple_keyring_cancel_requests(tracker->new);
+ }
+
+ purple_keyring_is_quitting = TRUE;
+ if (purple_keyring_inuse != NULL)
+ purple_keyring_cancel_requests(purple_keyring_inuse);
+}
+
+void
+purple_keyring_init(void)
+{
+ const gchar *touse;
+ GList *it;
+
+ purple_keyring_keyrings = NULL;
+ purple_keyring_inuse = NULL;
+
+ purple_keyring_failed_imports = g_hash_table_new_full(g_direct_hash,
+ g_direct_equal, NULL,
+ (GDestroyNotify)purple_keyring_failed_import_free);
+
+ /* void keyring_register(const char *keyring_id,
+ * PurpleKeyring * keyring);
+ *
+ * A signal called when keyring is registered.
+ *
+ * @param keyring_id The keyring ID.
+ * @param keyring The keyring.
+ */
+ purple_signal_register(purple_keyring_get_handle(),
+ "keyring-register",
+ purple_marshal_VOID__POINTER_POINTER,
+ NULL, 2,
+ purple_value_new(PURPLE_TYPE_STRING),
+ purple_value_new(PURPLE_TYPE_BOXED, "PurpleKeyring *"));
+
+ /* void keyring_unregister(const char *keyring_id,
+ * PurpleKeyring * keyring);
+ *
+ * A signal called when keyring is unregistered.
+ *
+ * @param keyring_id The keyring ID.
+ * @param keyring The keyring.
+ */
+ purple_signal_register(purple_keyring_get_handle(),
+ "keyring-unregister",
+ purple_marshal_VOID__POINTER_POINTER,
+ NULL, 2,
+ purple_value_new(PURPLE_TYPE_STRING),
+ purple_value_new(PURPLE_TYPE_BOXED, "PurpleKeyring *"));
+
+ /* void password_migration(PurpleAccount* account);
+ *
+ * A signal called, when a password for the account was moved to another
+ * keyring.
+ *
+ * @param account The account.
+ */
+ purple_signal_register(purple_keyring_get_handle(),
+ "password-migration",
+ purple_marshal_VOID__POINTER,
+ NULL, 1,
+ purple_value_new(PURPLE_TYPE_BOXED, "PurpleAccount *"));
+
+ touse = purple_prefs_get_string("/purple/keyring/active");
+ if (touse == NULL) {
+ purple_prefs_add_none("/purple/keyring");
+ purple_prefs_add_string("/purple/keyring/active",
+ PURPLE_DEFAULT_KEYRING);
+ purple_keyring_to_use = g_strdup(PURPLE_DEFAULT_KEYRING);
+ } else
+ purple_keyring_to_use = g_strdup(touse);
+
+ purple_keyring_pref_connect();
+
+ for (it = purple_plugins_get_all(); it != NULL; it = it->next)
+ {
+ PurplePlugin *plugin = (PurplePlugin *)it->data;
+
+ if (plugin->info == NULL || plugin->info->id == NULL)
+ continue;
+ if (strncmp(plugin->info->id, "keyring-", 8) != 0)
+ continue;
+
+ if (purple_plugin_is_loaded(plugin))
+ continue;
+
+ if (purple_plugin_load(plugin))
+ {
+ purple_keyring_loaded_plugins = g_list_append(
+ purple_keyring_loaded_plugins, plugin);
+ }
+ }
+
+ if (purple_keyring_inuse == NULL)
+ purple_debug_error("keyring", "Selected keyring failed to load\n");
+
+ purple_signal_connect(purple_get_core(), "core-initialized",
+ purple_keyring_get_handle(),
+ PURPLE_CALLBACK(purple_keyring_core_initialized_cb), NULL);
+ purple_signal_connect(purple_get_core(), "quitting",
+ purple_keyring_get_handle(),
+ PURPLE_CALLBACK(purple_keyring_core_quitting_cb), NULL);
+}
+
+void
+purple_keyring_uninit(void)
+{
+ GList *it;
+
+ purple_keyring_pref_disconnect();
+
+ g_free(purple_keyring_to_use);
+ purple_keyring_inuse = NULL;
+
+ g_hash_table_destroy(purple_keyring_failed_imports);
+ purple_keyring_failed_imports = NULL;
+
+ for (it = g_list_first(purple_keyring_loaded_plugins); it != NULL;
+ it = g_list_next(it))
+ {
+ PurplePlugin *plugin = (PurplePlugin *)it->data;
+ if (g_list_find(purple_plugins_get_loaded(), plugin) == NULL)
+ continue;
+ purple_plugin_unload(plugin);
+ }
+ g_list_free(purple_keyring_loaded_plugins);
+ purple_keyring_loaded_plugins = NULL;
+
+ purple_signals_unregister_by_instance(purple_keyring_get_handle());
+ purple_signals_disconnect_by_handle(purple_keyring_get_handle());
+}
+
+void *
+purple_keyring_get_handle(void)
+{
+ static int handle;
+
+ return &handle;
+}
diff --git a/libpurple/keyring.h b/libpurple/keyring.h
new file mode 100644
index 0000000000..9bed5fe5e6
--- /dev/null
+++ b/libpurple/keyring.h
@@ -0,0 +1,548 @@
+/**
+ * @file keyring.h Keyring API
+ * @ingroup core
+ */
+
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _PURPLE_KEYRING_H_
+#define _PURPLE_KEYRING_H_
+
+#include "account.h"
+#include "request.h"
+
+/**
+ * Default keyring ID.
+ */
+#define PURPLE_DEFAULT_KEYRING "keyring-internal"
+
+/**
+ * Keyring subsystem error domain.
+ */
+#define PURPLE_KEYRING_ERROR purple_keyring_error_domain()
+
+/**************************************************************************/
+/** @name Data structures and types */
+/**************************************************************************/
+/*@{*/
+
+typedef struct _PurpleKeyring PurpleKeyring;
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Callbacks for keyrings access functions */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Callback for once a password is read.
+ *
+ * If there was a problem, the password will be NULL, and the error set.
+ *
+ * @param account The account.
+ * @param password The password.
+ * @param error Error that may have occurred.
+ * @param data Data passed to the callback.
+ */
+typedef void (*PurpleKeyringReadCallback)(PurpleAccount *account,
+ const gchar *password, GError *error, gpointer data);
+
+/**
+ * Callback for once a password has been stored.
+ *
+ * If there was a problem, the error will be set.
+ *
+ * @param account The account.
+ * @param error Error that may have occurred.
+ * @param data Data passed to the callback.
+ */
+typedef void (*PurpleKeyringSaveCallback)(PurpleAccount *account, GError *error,
+ gpointer data);
+
+/**
+ * Callback for once the master password for a keyring has been changed.
+ *
+ * @param error Error that has occurred.
+ * @param data Data passed to the callback.
+ */
+typedef void (*PurpleKeyringChangeMasterCallback)(GError *error, gpointer data);
+
+/**
+ * Callback for when we change the keyring.
+ *
+ * @param error An error that might have occurred.
+ * @param data A pointer to user supplied data.
+ */
+typedef void (*PurpleKeyringSetInUseCallback)(GError *error, gpointer data);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Keyrings access functions */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Read the password for an account.
+ *
+ * @param account The account.
+ * @param cb A callback for once the password is found.
+ * @param data Data to be passed to the callback.
+ */
+typedef void (*PurpleKeyringRead)(PurpleAccount *account,
+ PurpleKeyringReadCallback cb, gpointer data);
+
+/**
+ * Store a password in the keyring.
+ *
+ * @param account The account.
+ * @param password The password to be stored. If the password is NULL, this
+ * means that the keyring should forget about that password.
+ * @param cb A callback for once the password is saved.
+ * @param data Data to be passed to the callback.
+ */
+typedef void (*PurpleKeyringSave)(PurpleAccount *account, const gchar *password,
+ PurpleKeyringSaveCallback cb, gpointer data);
+
+/**
+ * Cancel all running requests.
+ *
+ * After calling that, all queued requests should run their callbacks (most
+ * probably, with failure result).
+ */
+typedef void (*PurpleKeyringCancelRequests)(void);
+
+/**
+ * Close the keyring.
+ *
+ * This will be called so the keyring can do any cleanup it needs.
+ */
+typedef void (*PurpleKeyringClose)(void);
+
+/**
+ * Import serialized (and maybe encrypted) password.
+ *
+ * This is not async because it is not meant to prompt for a master password and
+ * decrypt passwords.
+ *
+ * @param account The account.
+ * @param mode A keyring specific option that was stored. Can be NULL.
+ * @param data Data that was stored. Can be NULL.
+ *
+ * @return TRUE on success, FALSE on failure.
+ */
+typedef gboolean (*PurpleKeyringImportPassword)(PurpleAccount *account,
+ const gchar *mode, const gchar *data, GError **error);
+
+/**
+ * Export serialized (and maybe encrypted) password.
+ *
+ * @param account The account.
+ * @param mode An option field that can be used by the plugin. This is
+ * expected to be a static string.
+ * @param data The data to be stored in the XML node. This string will be
+ * freed using destroy() once not needed anymore.
+ * @param error Will be set if a problem occured.
+ * @param destroy A function to be called, if non NULL, to free data.
+ *
+ * @return TRUE on success, FALSE on failure.
+ */
+typedef gboolean (*PurpleKeyringExportPassword)(PurpleAccount *account,
+ const gchar **mode, gchar **data, GError **error,
+ GDestroyNotify *destroy);
+
+/**
+ * Read keyring settings.
+ *
+ * @return New copy of current settings (must be free'd with
+ * purple_request_fields_destroy).
+ */
+typedef PurpleRequestFields * (*PurpleKeyringReadSettings)(void);
+
+/**
+ * Applies modified keyring settings.
+ *
+ * @param notify_handle A handle that can be passed to purple_notify_message.
+ * @param fields Modified settings (originally taken from
+ * PurpleKeyringReadSettings).
+ * @return TRUE, if succeeded, FALSE otherwise.
+ */
+typedef gboolean (*PurpleKeyringApplySettings)(void *notify_handle,
+ PurpleRequestFields *fields);
+
+/*@}*/
+
+G_BEGIN_DECLS
+
+/**************************************************************************/
+/** @name Setting used keyrings */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Find a keyring by an id.
+ *
+ * @param id The id for the keyring.
+ *
+ * @return The keyring, or NULL if not found.
+ */
+PurpleKeyring *
+purple_keyring_find_keyring_by_id(const gchar *id);
+
+/**
+ * Get the keyring being used.
+ */
+PurpleKeyring *
+purple_keyring_get_inuse(void);
+
+/**
+ * Set the keyring to use. This function will move all passwords from
+ * the old keyring to the new one.
+ *
+ * If it fails, it will cancel all changes, close the new keyring, and notify
+ * the callback. If it succeeds, it will remove all passwords from the old safe
+ * and close that safe.
+ *
+ * @param newkeyring The new keyring to use.
+ * @param force FALSE if the change can be cancelled. If this is TRUE and
+ * an error occurs, data might be lost.
+ * @param cb A callback for once the change is complete.
+ * @param data Data to be passed to the callback.
+ */
+void
+purple_keyring_set_inuse(PurpleKeyring *newkeyring, gboolean force,
+ PurpleKeyringSetInUseCallback cb, gpointer data);
+
+/**
+ * Register a keyring plugin.
+ *
+ * @param keyring The keyring to register.
+ */
+void
+purple_keyring_register(PurpleKeyring *keyring);
+
+/**
+ * Unregister a keyring plugin.
+ *
+ * In case the keyring is in use, passwords will be moved to a fallback safe,
+ * and the keyring to unregister will be properly closed.
+ *
+ * @param keyring The keyring to unregister.
+ */
+void
+purple_keyring_unregister(PurpleKeyring *keyring);
+
+/**
+ * Returns a GList containing the IDs and names of the registered
+ * keyrings.
+ *
+ * @return The list of IDs and names.
+ */
+GList *
+purple_keyring_get_options(void);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Keyring plugin wrappers */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Import serialized (and maybe encrypted) password into current keyring.
+ *
+ * It's used by account.c while reading a password from xml.
+ *
+ * @param account The account.
+ * @param keyring_id The plugin ID that was stored in the xml file. Can be NULL.
+ * @param mode A keyring specific option that was stored. Can be NULL.
+ * @param data Data that was stored, can be NULL.
+ *
+ * @return TRUE if the input was accepted, FALSE otherwise.
+ */
+gboolean
+purple_keyring_import_password(PurpleAccount *account, const gchar *keyring_id,
+ const gchar *mode, const gchar *data, GError **error);
+
+/**
+ * Export serialized (and maybe encrypted) password out of current keyring.
+ *
+ * It's used by account.c while syncing accounts to xml.
+ *
+ * @param account The account for which we want the info.
+ * @param keyring_id The plugin id to be stored in the XML node. This will be
+ * NULL or a string that can be considered static.
+ * @param mode An option field that can be used by the plugin. This will
+ * be NULL or a string that can be considered static.
+ * @param data The data to be stored in the XML node. This string must be
+ * freed using destroy() once not needed anymore if it is not
+ * NULL.
+ * @param error Will be set if a problem occured.
+ * @param destroy A function to be called, if non NULL, to free data.
+ *
+ * @return TRUE if the info was exported successfully, FALSE otherwise.
+ */
+gboolean
+purple_keyring_export_password(PurpleAccount *account, const gchar **keyring_id,
+ const gchar **mode, gchar **data, GError **error,
+ GDestroyNotify *destroy);
+
+/**
+ * Read a password from the current keyring.
+ *
+ * @param account The account.
+ * @param cb A callback for once the password is read.
+ * @param data Data passed to the callback.
+ */
+void
+purple_keyring_get_password(PurpleAccount *account,
+ PurpleKeyringReadCallback cb, gpointer data);
+
+/**
+ * Save a password to the current keyring.
+ *
+ * @param account The account.
+ * @param password The password to save.
+ * @param cb A callback for once the password is saved.
+ * @param data Data to be passed to the callback.
+ */
+void
+purple_keyring_set_password(PurpleAccount *account, const gchar *password,
+ PurpleKeyringSaveCallback cb, gpointer data);
+
+/**
+ * Reads settings from current keyring.
+ *
+ * @return New copy of current settings (must be free'd with
+ * purple_request_fields_destroy).
+ */
+PurpleRequestFields *
+purple_keyring_read_settings(void);
+
+/**
+ * Applies modified settings to current keyring.
+ *
+ * @param notify_handle A handle that can be passed to purple_notify_message.
+ * @param fields Modified settings (originally taken from
+ * PurpleKeyringReadSettings).
+ * @return TRUE, if succeeded, FALSE otherwise.
+ */
+gboolean
+purple_keyring_apply_settings(void *notify_handle, PurpleRequestFields *fields);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name PurpleKeyring accessors */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates a new keyring wrapper.
+ */
+PurpleKeyring *
+purple_keyring_new(void);
+
+/**
+ * Frees all data allocated with purple_keyring_new.
+ *
+ * @param keyring Keyring wrapper struct.
+ */
+void
+purple_keyring_free(PurpleKeyring *keyring);
+
+/**
+ * Gets friendly user name.
+ *
+ * @param keyring The keyring.
+ * @return Friendly user name.
+ */
+const gchar *
+purple_keyring_get_name(const PurpleKeyring *keyring);
+
+/**
+ * Gets keyring ID.
+ *
+ * @param keyring The keyring.
+ * @return Keyring ID.
+ */
+const gchar *
+purple_keyring_get_id(const PurpleKeyring *keyring);
+
+PurpleKeyringRead
+purple_keyring_get_read_password(const PurpleKeyring *keyring);
+
+PurpleKeyringSave
+purple_keyring_get_save_password(const PurpleKeyring *keyring);
+
+PurpleKeyringCancelRequests
+purple_keyring_get_cancel_requests(const PurpleKeyring *keyring);
+
+PurpleKeyringClose
+purple_keyring_get_close_keyring(const PurpleKeyring *keyring);
+
+PurpleKeyringImportPassword
+purple_keyring_get_import_password(const PurpleKeyring *keyring);
+
+PurpleKeyringExportPassword
+purple_keyring_get_export_password(const PurpleKeyring *keyring);
+
+PurpleKeyringReadSettings
+purple_keyring_get_read_settings(const PurpleKeyring *keyring);
+
+PurpleKeyringApplySettings
+purple_keyring_get_apply_settings(const PurpleKeyring *keyring);
+
+/**
+ * Sets friendly user name.
+ *
+ * This field is required.
+ *
+ * @param keyring The keyring.
+ * @param name Friendly user name.
+ */
+void
+purple_keyring_set_name(PurpleKeyring *keyring, const gchar *name);
+
+/**
+ * Sets keyring ID.
+ *
+ * This field is required.
+ *
+ * @param keyring The keyring.
+ * @param name Keyring ID.
+ */
+void
+purple_keyring_set_id(PurpleKeyring *keyring, const gchar *id);
+
+/**
+ * Sets read password method.
+ *
+ * This field is required.
+ *
+ * @param keyring The keyring.
+ * @param read_cb Read password method.
+ */
+void
+purple_keyring_set_read_password(PurpleKeyring *keyring,
+ PurpleKeyringRead read_cb);
+
+/**
+ * Sets save password method.
+ *
+ * This field is required.
+ *
+ * @param keyring The keyring.
+ * @param save_cb Save password method.
+ */
+void
+purple_keyring_set_save_password(PurpleKeyring *keyring,
+ PurpleKeyringSave save_cb);
+
+void
+purple_keyring_set_cancel_requests(PurpleKeyring *keyring,
+ PurpleKeyringCancelRequests cancel_requests);
+
+void
+purple_keyring_set_close_keyring(PurpleKeyring *keyring,
+ PurpleKeyringClose close_cb);
+
+void
+purple_keyring_set_import_password(PurpleKeyring *keyring,
+ PurpleKeyringImportPassword import_password);
+
+void
+purple_keyring_set_export_password(PurpleKeyring *keyring,
+ PurpleKeyringExportPassword export_password);
+
+void
+purple_keyring_set_read_settings(PurpleKeyring *keyring,
+PurpleKeyringReadSettings read_settings);
+
+void
+purple_keyring_set_apply_settings(PurpleKeyring *keyring,
+PurpleKeyringApplySettings apply_settings);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Error Codes */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Gets keyring subsystem error domain.
+ *
+ * @return keyring subsystem error domain.
+ */
+GQuark
+purple_keyring_error_domain(void);
+
+/**
+ * Error codes for keyring subsystem.
+ */
+enum PurpleKeyringError
+{
+ PURPLE_KEYRING_ERROR_UNKNOWN = 0, /**< Unknown error. */
+
+ PURPLE_KEYRING_ERROR_NOKEYRING = 10, /**< No keyring configured. */
+ PURPLE_KEYRING_ERROR_INTERNAL, /**< Internal keyring system error. */
+ PURPLE_KEYRING_ERROR_BACKENDFAIL, /**< Failed to communicate with the backend or internal backend error. */
+
+ PURPLE_KEYRING_ERROR_NOPASSWORD = 20, /**< No password stored for the specified account. */
+ PURPLE_KEYRING_ERROR_ACCESSDENIED, /**< Access denied for the specified keyring or entry. */
+ PURPLE_KEYRING_ERROR_CANCELLED /**< Operation was cancelled. */
+};
+
+/*}@*/
+
+/**************************************************************************/
+/** @name Keyring Subsystem */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Initializes the keyring subsystem.
+ */
+void
+purple_keyring_init(void);
+
+/**
+ * Uninitializes the keyring subsystem.
+ */
+void
+purple_keyring_uninit(void);
+
+/**
+ * Returns the keyring subsystem handle.
+ *
+ * @return The keyring subsystem handle.
+ */
+void *
+purple_keyring_get_handle(void);
+
+/*}@*/
+
+G_END_DECLS
+
+#endif /* _PURPLE_KEYRING_H_ */
diff --git a/libpurple/plugins/Makefile.am b/libpurple/plugins/Makefile.am
index adc6209773..cc3eae6a31 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)
@@ -146,7 +147,11 @@ AM_CPPFLAGS = \
$(DEBUG_CFLAGS) \
$(GLIB_CFLAGS) \
$(PLUGIN_CFLAGS) \
- $(DBUS_CFLAGS)
+ $(DBUS_CFLAGS) \
+ $(NSS_CFLAGS)
+
+PLUGIN_LIBS = \
+ $(NSS_LIBS)
#
# This part allows people to build their own plugins in here.
diff --git a/libpurple/plugins/Makefile.mingw b/libpurple/plugins/Makefile.mingw
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/ciphertest.c b/libpurple/plugins/ciphertest.c
index dd16e023b7..f99a27b527 100644
--- a/libpurple/plugins/ciphertest.c
+++ b/libpurple/plugins/ciphertest.c
@@ -231,6 +231,343 @@ cipher_test_digest(void)
}
/**************************************************************************
+ * PBKDF2 stuff
+ **************************************************************************/
+
+#ifdef HAVE_NSS
+# include <nss.h>
+# include <secmod.h>
+# include <pk11func.h>
+# include <prerror.h>
+# include <secerr.h>
+#endif
+
+typedef struct {
+ const gchar *hash;
+ const guint iter_count;
+ const gchar *passphrase;
+ const gchar *salt;
+ const guint out_len;
+ const gchar *answer;
+} pbkdf2_test;
+
+pbkdf2_test pbkdf2_tests[] = {
+ { "sha256", 1, "password", "salt", 32, "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"},
+ { "sha1", 1, "password", "salt", 32, "0c60c80f961f0e71f3a9b524af6012062fe037a6e0f0eb94fe8fc46bdc637164"},
+ { "sha1", 1000, "ala ma kota", "", 16, "924dba137b5bcf6d0de84998f3d8e1f9"},
+ { "sha1", 1, "", "", 32, "1e437a1c79d75be61e91141dae20affc4892cc99abcc3fe753887bccc8920176"},
+ { "sha256", 100, "some password", "and salt", 1, "c7"},
+ { "sha1", 10000, "pretty long password W Szczebrzeszynie chrzaszcz brzmi w trzcinie i Szczebrzeszyn z tego slynie", "Grzegorz Brzeczyszczykiewicz", 32, "8cb0cb164f2554733ae02f5751b0e84a88fb385446e85a3991bdcdf1ea11795c"},
+ { NULL, 0, NULL, NULL, 0, NULL}
+};
+
+#ifdef HAVE_NSS
+
+static gchar*
+cipher_pbkdf2_nss_sha1(const gchar *passphrase, const gchar *salt,
+ guint iter_count, guint out_len)
+{
+ PK11SlotInfo *slot;
+ SECAlgorithmID *algorithm = NULL;
+ PK11SymKey *symkey = NULL;
+ const SECItem *symkey_data = NULL;
+ SECItem salt_item, passphrase_item;
+ guchar *passphrase_buff, *salt_buff;
+ gchar *ret;
+
+ g_return_val_if_fail(passphrase != NULL, NULL);
+ g_return_val_if_fail(iter_count > 0, NULL);
+ g_return_val_if_fail(out_len > 0, NULL);
+
+ NSS_NoDB_Init(NULL);
+
+ slot = PK11_GetBestSlot(PK11_AlgtagToMechanism(SEC_OID_PKCS5_PBKDF2),
+ NULL);
+ if (slot == NULL) {
+ purple_debug_error("cipher-test", "NSS: couldn't get slot: "
+ "%d\n", PR_GetError());
+ return NULL;
+ }
+
+ salt_buff = (guchar*)g_strdup(salt ? salt : "");
+ salt_item.type = siBuffer;
+ salt_item.data = salt_buff;
+ salt_item.len = salt ? strlen(salt) : 0;
+
+ algorithm = PK11_CreatePBEV2AlgorithmID(SEC_OID_PKCS5_PBKDF2,
+ SEC_OID_AES_256_CBC, SEC_OID_HMAC_SHA1, out_len, iter_count,
+ &salt_item);
+ if (algorithm == NULL) {
+ purple_debug_error("cipher-test", "NSS: couldn't create "
+ "algorithm ID: %d\n", PR_GetError());
+ PK11_FreeSlot(slot);
+ g_free(salt_buff);
+ return NULL;
+ }
+
+ passphrase_buff = (guchar*)g_strdup(passphrase);
+ passphrase_item.type = siBuffer;
+ passphrase_item.data = passphrase_buff;
+ passphrase_item.len = strlen(passphrase);
+
+ symkey = PK11_PBEKeyGen(slot, algorithm, &passphrase_item, PR_FALSE,
+ NULL);
+ if (symkey == NULL) {
+ purple_debug_error("cipher-test", "NSS: Couldn't generate key: "
+ "%d\n", PR_GetError());
+ SECOID_DestroyAlgorithmID(algorithm, PR_TRUE);
+ PK11_FreeSlot(slot);
+ g_free(passphrase_buff);
+ g_free(salt_buff);
+ return NULL;
+ }
+
+ if (PK11_ExtractKeyValue(symkey) == SECSuccess)
+ symkey_data = PK11_GetKeyData(symkey);
+
+ if (symkey_data == NULL || symkey_data->data == NULL) {
+ purple_debug_error("cipher-test", "NSS: Couldn't extract key "
+ "value: %d\n", PR_GetError());
+ PK11_FreeSymKey(symkey);
+ SECOID_DestroyAlgorithmID(algorithm, PR_TRUE);
+ PK11_FreeSlot(slot);
+ g_free(passphrase_buff);
+ g_free(salt_buff);
+ return NULL;
+ }
+
+ if (symkey_data->len != out_len) {
+ purple_debug_error("cipher-test", "NSS: Invalid key length: %d "
+ "(should be %d)\n", symkey_data->len, out_len);
+ PK11_FreeSymKey(symkey);
+ SECOID_DestroyAlgorithmID(algorithm, PR_TRUE);
+ PK11_FreeSlot(slot);
+ g_free(passphrase_buff);
+ g_free(salt_buff);
+ return NULL;
+ }
+
+ ret = purple_base16_encode(symkey_data->data, symkey_data->len);
+
+ PK11_FreeSymKey(symkey);
+ SECOID_DestroyAlgorithmID(algorithm, PR_TRUE);
+ PK11_FreeSlot(slot);
+ g_free(passphrase_buff);
+ g_free(salt_buff);
+ return ret;
+}
+
+#endif /* HAVE_NSS */
+
+static void
+cipher_test_pbkdf2(void)
+{
+ PurpleCipherContext *context;
+ int i = 0;
+ gboolean fail = FALSE;
+
+ purple_debug_info("cipher-test", "Running PBKDF2 tests\n");
+
+ context = purple_cipher_context_new_by_name("pbkdf2", NULL);
+
+ while (!fail && pbkdf2_tests[i].answer) {
+ pbkdf2_test *test = &pbkdf2_tests[i];
+ gchar digest[2 * 32 + 1 + 10];
+ gchar *digest_nss = NULL;
+ gboolean ret, skip_nss = FALSE;
+
+ i++;
+
+ purple_debug_info("cipher-test", "Test %02d:\n", i);
+ purple_debug_info("cipher-test",
+ "\tTesting '%s' with salt:'%s' hash:%s iter_count:%d \n",
+ test->passphrase, test->salt, test->hash,
+ test->iter_count);
+
+ purple_cipher_context_set_option(context, "hash", (gpointer)test->hash);
+ purple_cipher_context_set_option(context, "iter_count", GUINT_TO_POINTER(test->iter_count));
+ purple_cipher_context_set_option(context, "out_len", GUINT_TO_POINTER(test->out_len));
+ purple_cipher_context_set_salt(context, (const guchar*)test->salt, test->salt ? strlen(test->salt): 0);
+ purple_cipher_context_set_key(context, (const guchar*)test->passphrase, strlen(test->passphrase));
+
+ ret = purple_cipher_context_digest_to_str(context, digest, sizeof(digest));
+ purple_cipher_context_reset(context, NULL);
+
+ if (!ret) {
+ purple_debug_info("cipher-test", "\tfailed\n");
+ fail = TRUE;
+ continue;
+ }
+
+ if (g_strcmp0(test->hash, "sha1") != 0)
+ skip_nss = TRUE;
+ if (test->out_len != 16 && test->out_len != 32)
+ skip_nss = TRUE;
+
+#ifdef HAVE_NSS
+ if (!skip_nss) {
+ digest_nss = cipher_pbkdf2_nss_sha1(test->passphrase,
+ test->salt, test->iter_count, test->out_len);
+ }
+#else
+ skip_nss = TRUE;
+#endif
+
+ if (!ret) {
+ purple_debug_info("cipher-test", "\tnss test failed\n");
+ fail = TRUE;
+ }
+
+ purple_debug_info("cipher-test", "\tGot: %s\n", digest);
+ if (digest_nss)
+ purple_debug_info("cipher-test", "\tGot from NSS: %s\n", digest_nss);
+ purple_debug_info("cipher-test", "\tWanted: %s\n", test->answer);
+
+ if (g_strcmp0(digest, test->answer) == 0 &&
+ (skip_nss || g_strcmp0(digest, digest_nss) == 0)) {
+ purple_debug_info("cipher-test", "\tTest OK\n");
+ }
+ else {
+ purple_debug_info("cipher-test", "\twrong answer\n");
+ fail = TRUE;
+ }
+ }
+
+ purple_cipher_context_destroy(context);
+
+ if (fail)
+ purple_debug_info("cipher-test", "PBKDF2 tests FAILED\n\n");
+ else
+ purple_debug_info("cipher-test", "PBKDF2 tests completed successfully\n\n");
+}
+
+/**************************************************************************
+ * AES stuff
+ **************************************************************************/
+
+typedef struct {
+ const gchar *iv;
+ const gchar *key;
+ const gchar *plaintext;
+ const gchar *cipher;
+} aes_test;
+
+aes_test aes_tests[] = {
+ { NULL, "000102030405060708090a0b0c0d0e0f", "plaintext", "152e5b950e5f28fafadee9e96fcc59c9" },
+ { NULL, "000102030405060708090a0b0c0d0e0f", NULL, "954f64f2e4e86e9eee82d20216684899" },
+ { "01010101010101010101010101010101", "000102030405060708090a0b0c0d0e0f", NULL, "35d14e6d3e3a279cf01e343e34e7ded3" },
+ { "01010101010101010101010101010101", "000102030405060708090a0b0c0d0e0f", "plaintext", "19d1996e8c098cf3c94bba5dcf5bc57e" },
+ { "01010101010101010101010101010101", "000102030405060708090a0b0c0d0e0f1011121314151617", "plaintext", "e8fba0deae94f63fe72ae9b8ef128aed" },
+ { "01010101010101010101010101010101", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "plaintext", "e2dc50f6c60b33ac3b5953b6503cb684" },
+ { "01010101010101010101010101010101", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "W Szczebrzeszynie chrzaszcz brzmi w trzcinie i Szczebrzeszyn z tego slynie", "8fcc068964e3505f0c2fac61c24592e5c8a9582d3a3264217cf605e9fd1cb056e679e159c4ac3110e8ce6c76c6630d42658c566ba3750c0e6da385b3a9baaa8b3a735b4c1ecaacf58037c8c281e523d2" },
+ { NULL, NULL, NULL, NULL }
+};
+
+static void
+cipher_test_aes(void)
+{
+ PurpleCipherContext *context;
+ int i = 0;
+ gboolean fail = FALSE;
+
+ purple_debug_info("cipher-test", "Running AES tests\n");
+
+ context = purple_cipher_context_new_by_name("aes", NULL);
+ if (context == NULL) {
+ purple_debug_error("cipher-test", "AES cipher not found\n");
+ fail = TRUE;
+ }
+
+ while (!fail && aes_tests[i].cipher) {
+ aes_test *test = &aes_tests[i];
+ gsize key_size;
+ guchar *key;
+ guchar cipher[1024], decipher[1024];
+ ssize_t cipher_len, decipher_len;
+ gchar *cipher_b16, *deciphered;
+
+ purple_debug_info("cipher-test", "Test %02d:\n", i);
+ purple_debug_info("cipher-test", "\tTesting '%s' (%dbit) \n",
+ test->plaintext ? test->plaintext : "(null)",
+ strlen(test->key) * 8 / 2);
+
+ i++;
+
+ purple_cipher_context_reset(context, NULL);
+
+ if (test->iv) {
+ gsize iv_size;
+ guchar *iv = purple_base16_decode(test->iv, &iv_size);
+ g_assert(iv != NULL);
+ purple_cipher_context_set_iv(context, iv, iv_size);
+ g_free(iv);
+ }
+
+ key = purple_base16_decode(test->key, &key_size);
+ g_assert(key != NULL);
+ purple_cipher_context_set_key(context, key, key_size);
+ g_free(key);
+
+ if (purple_cipher_context_get_key_size(context) != key_size) {
+ purple_debug_info("cipher-test", "\tinvalid key size\n");
+ fail = TRUE;
+ continue;
+ }
+
+ cipher_len = purple_cipher_context_encrypt(context,
+ (const guchar*)(test->plaintext ? test->plaintext : ""),
+ test->plaintext ? (strlen(test->plaintext) + 1) : 0,
+ cipher, sizeof(cipher));
+ if (cipher_len < 0) {
+ purple_debug_info("cipher-test", "\tencryption failed\n");
+ fail = TRUE;
+ continue;
+ }
+
+ cipher_b16 = purple_base16_encode(cipher, cipher_len);
+
+ purple_debug_info("cipher-test", "\tGot: %s\n", cipher_b16);
+ purple_debug_info("cipher-test", "\tWanted: %s\n", test->cipher);
+
+ if (g_strcmp0(cipher_b16, test->cipher) != 0) {
+ purple_debug_info("cipher-test",
+ "\tencrypted data doesn't match\n");
+ g_free(cipher_b16);
+ fail = TRUE;
+ continue;
+ }
+ g_free(cipher_b16);
+
+ decipher_len = purple_cipher_context_decrypt(context,
+ cipher, cipher_len, decipher, sizeof(decipher));
+ if (decipher_len < 0) {
+ purple_debug_info("cipher-test", "\tdecryption failed\n");
+ fail = TRUE;
+ continue;
+ }
+
+ deciphered = (decipher_len > 0) ? (gchar*)decipher : NULL;
+
+ if (g_strcmp0(deciphered, test->plaintext) != 0) {
+ purple_debug_info("cipher-test",
+ "\tdecrypted data doesn't match\n");
+ fail = TRUE;
+ continue;
+ }
+
+ purple_debug_info("cipher-test", "\tTest OK\n");
+ }
+
+ if (context != NULL)
+ purple_cipher_context_destroy(context);
+
+ if (fail)
+ purple_debug_info("cipher-test", "AES tests FAILED\n\n");
+ else
+ purple_debug_info("cipher-test", "AES tests completed successfully\n\n");
+}
+
+/**************************************************************************
* Plugin stuff
**************************************************************************/
static gboolean
@@ -238,6 +575,8 @@ plugin_load(PurplePlugin *plugin) {
cipher_test_md5();
cipher_test_sha1();
cipher_test_digest();
+ cipher_test_pbkdf2();
+ cipher_test_aes();
return TRUE;
}
diff --git a/libpurple/plugins/keyrings/Makefile.am b/libpurple/plugins/keyrings/Makefile.am
new file mode 100644
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..c9c516c10a
--- /dev/null
+++ b/libpurple/plugins/keyrings/internalkeyring.c
@@ -0,0 +1,327 @@
+/**
+ * @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;
+ }
+}
+
+static PurpleRequestFields *
+internal_keyring_read_settings(void)
+{
+ PurpleRequestFields *fields;
+ PurpleRequestFieldGroup *group;
+ PurpleRequestField *field;
+
+ fields = purple_request_fields_new();
+ group = purple_request_field_group_new(NULL);
+ purple_request_fields_add_group(fields, group);
+
+ field = purple_request_field_bool_new("encrypt",
+ _("Encrypt passwords"), FALSE);
+ purple_request_field_group_add_field(group, field);
+
+ group = purple_request_field_group_new(_("Master password"));
+ purple_request_fields_add_group(fields, group);
+
+ field = purple_request_field_string_new("passphrase1",
+ _("New passphrase:"), "", FALSE);
+ purple_request_field_string_set_masked(field, TRUE);
+ purple_request_field_group_add_field(group, field);
+
+ field = purple_request_field_string_new("passphrase2",
+ _("New passphrase (again):"), "", FALSE);
+ purple_request_field_string_set_masked(field, TRUE);
+ purple_request_field_group_add_field(group, field);
+
+ return fields;
+}
+
+static gboolean
+internal_keyring_apply_settings(void *notify_handle,
+ PurpleRequestFields *fields)
+{
+ const gchar *passphrase, *passphrase2;
+
+ passphrase = purple_request_fields_get_string(fields, "passphrase1");
+ if (g_strcmp0(passphrase, "") == 0)
+ passphrase = NULL;
+ passphrase2 = purple_request_fields_get_string(fields, "passphrase2");
+ if (g_strcmp0(passphrase2, "") == 0)
+ passphrase2 = NULL;
+
+ if (g_strcmp0(passphrase, passphrase2) != 0) {
+ purple_notify_error(notify_handle,
+ _("Internal keyring settings"),
+ _("Passphrases do not match"), NULL);
+ return FALSE;
+ }
+
+ if (purple_request_fields_get_bool(fields, "encrypt") && !passphrase) {
+ purple_notify_error(notify_handle,
+ _("Internal keyring settings"),
+ _("You have to set up a Master password, if you want "
+ "to enable encryption"), NULL);
+ return FALSE;
+ }
+
+ 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_set_read_settings(keyring_handler,
+ internal_keyring_read_settings);
+ purple_keyring_set_apply_settings(keyring_handler,
+ internal_keyring_apply_settings);
+
+ 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/request.h b/libpurple/request.h
index a8681afd49..f73dcbf9dd 100644
--- a/libpurple/request.h
+++ b/libpurple/request.h
@@ -30,6 +30,8 @@
#include <glib-object.h>
#include <glib.h>
+#include "certificate.h"
+
/**
* A request field.
*/
diff --git a/libpurple/util.c b/libpurple/util.c
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 3c52abb0fe..54db9424ab 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,14 @@ static GtkWidget *prefs_conv_variants_combo_box;
static GtkWidget *prefs_status_themes_combo_box;
static GtkWidget *prefs_smiley_themes_combo_box;
+/* Keyrings page */
+static GtkWidget *keyring_page_instance = NULL;
+static GtkComboBox *keyring_combo = NULL;
+static GtkBox *keyring_vbox = NULL;
+static PurpleRequestFields *keyring_settings = NULL;
+static GList *keyring_settings_fields = NULL;
+static GtkWidget *keyring_apply = NULL;
+
/* Sound theme specific */
static GtkWidget *sound_entry = NULL;
static int sound_row_sel = 0;
@@ -269,83 +278,105 @@ 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"));
+ g_object_set_data(G_OBJECT(combo_box), "previously_active",
+ g_object_get_data(G_OBJECT(combo_box), "current_active"));
+ g_object_set_data(G_OBJECT(combo_box), "current_active",
+ GINT_TO_POINTER(gtk_combo_box_get_active(combo_box)));
- if (type == PURPLE_PREF_INT) {
- gtk_tree_model_get(tree_model, &iter,
- PREF_DROPDOWN_VALUE, &int_value,
- -1);
-
- 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)
{
- GtkWidget *dropdown;
- GtkWidget *label = NULL;
- gchar *text;
- const char *stored_str = NULL;
- int stored_int = 0;
- gboolean stored_bool = FALSE;
- int int_value = 0;
- const char *str_value = NULL;
- gboolean bool_value = FALSE;
+ gint previously_active;
+
+ g_return_if_fail(combo_box != NULL);
+
+ previously_active = GPOINTER_TO_INT(g_object_get_data(
+ G_OBJECT(combo_box), "previously_active"));
+ g_object_set_data(G_OBJECT(combo_box), "current_active",
+ GINT_TO_POINTER(previously_active));
+
+ gtk_combo_box_set_active(combo_box, previously_active);
+}
+
+static GtkWidget *
+pidgin_prefs_dropdown_from_list_with_cb(GtkWidget *box, const gchar *title,
+ GtkComboBox **dropdown_out, GList *menuitems,
+ PidginPrefValue initial, PidginPrefsDropdownCallback cb)
+{
+ GtkWidget *dropdown;
+ GtkWidget *label = NULL;
+ gchar *text;
GtkListStore *store = NULL;
GtkTreeIter iter;
GtkTreeIter active;
GtkCellRenderer *renderer;
+ gpointer current_active;
g_return_val_if_fail(menuitems != NULL, NULL);
- if (type == PURPLE_PREF_INT) {
+ if (initial.type == PURPLE_PREF_INT) {
store = gtk_list_store_new(PREF_DROPDOWN_COUNT, G_TYPE_STRING, G_TYPE_INT);
- stored_int = purple_prefs_get_int(key);
- } else if (type == PURPLE_PREF_STRING) {
+ } else if (initial.type == PURPLE_PREF_STRING) {
store = gtk_list_store_new(PREF_DROPDOWN_COUNT, G_TYPE_STRING, G_TYPE_STRING);
- stored_str = purple_prefs_get_string(key);
- } else if (type == PURPLE_PREF_BOOLEAN) {
+ } else if (initial.type == PURPLE_PREF_BOOLEAN) {
store = gtk_list_store_new(PREF_DROPDOWN_COUNT, G_TYPE_STRING, G_TYPE_BOOLEAN);
- stored_bool = purple_prefs_get_bool(key);
} else {
g_warn_if_reached();
return NULL;
}
dropdown = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
- g_object_set_data(G_OBJECT(dropdown), "type", GINT_TO_POINTER(type));
+ if (dropdown_out != NULL)
+ *dropdown_out = GTK_COMBO_BOX(dropdown);
+ g_object_set_data(G_OBJECT(dropdown), "type", GINT_TO_POINTER(initial.type));
while (menuitems != NULL && (text = (char *)menuitems->data) != NULL) {
+ int int_value = 0;
+ const char *str_value = NULL;
+ gboolean bool_value = FALSE;
+
menuitems = g_list_next(menuitems);
g_return_val_if_fail(menuitems != NULL, NULL);
@@ -354,30 +385,31 @@ 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 +424,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, ...)
@@ -443,12 +525,16 @@ pidgin_prefs_dropdown(GtkWidget *box, const gchar *title, PurplePrefType type,
return dropdown;
}
+static void keyring_page_cleanup(void);
+
static void
delete_prefs(GtkWidget *asdf, void *gdsa)
{
/* Close any "select sound" request dialogs */
purple_request_close_with_handle(prefs);
+ purple_notify_close_with_handle(prefs);
+
/* Unregister callbacks. */
purple_prefs_disconnect_by_handle(prefs);
@@ -464,6 +550,8 @@ delete_prefs(GtkWidget *asdf, void *gdsa)
prefs_status_themes_combo_box = NULL;
prefs_smiley_themes_combo_box = NULL;
+ keyring_page_cleanup();
+
sample_webview = NULL;
notebook_page = 0;
@@ -2554,6 +2642,254 @@ logging_page(void)
return ret;
}
+/*** keyring page *******************************************************/
+
+static void
+keyring_page_settings_toggled(GtkToggleButton *togglebutton, gpointer _unused)
+{
+ PurpleRequestField *setting;
+
+ gtk_widget_set_sensitive(keyring_apply, TRUE);
+
+ setting = g_object_get_data(G_OBJECT(togglebutton), "setting");
+ purple_request_field_bool_set_value(setting,
+ gtk_toggle_button_get_active(togglebutton));
+}
+
+static void
+keyring_page_settings_string_changed(GtkWidget *widget, gpointer _unused)
+{
+ PurpleRequestField *setting;
+
+ gtk_widget_set_sensitive(keyring_apply, TRUE);
+
+ setting = g_object_get_data(G_OBJECT(widget), "setting");
+ purple_request_field_string_set_value(setting,
+ gtk_entry_get_text(GTK_ENTRY(widget)));
+}
+
+static GtkWidget *
+keyring_page_add_settings_field(GtkBox *vbox, PurpleRequestField *setting,
+ GtkSizeGroup *sg)
+{
+ GtkWidget *widget, *hbox;
+ PurpleRequestFieldType field_type;
+ const gchar *label;
+
+ label = purple_request_field_get_label(setting);
+
+ field_type = purple_request_field_get_type(setting);
+ if (field_type == PURPLE_REQUEST_FIELD_BOOLEAN) {
+ widget = gtk_check_button_new_with_label(label);
+ label = NULL;
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
+ purple_request_field_bool_get_default_value(setting));
+ g_signal_connect(G_OBJECT(widget), "toggled",
+ G_CALLBACK(keyring_page_settings_toggled), NULL);
+ } else if (field_type == PURPLE_REQUEST_FIELD_STRING) {
+ widget = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(widget),
+ purple_request_field_string_get_default_value(setting));
+ if (purple_request_field_string_is_masked(setting))
+ gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE);
+ g_signal_connect(G_OBJECT(widget), "changed",
+ G_CALLBACK(keyring_page_settings_string_changed), NULL);
+ } else {
+ purple_debug_error("gtkprefs", "Unsupported field type\n");
+ return NULL;
+ }
+
+ g_object_set_data(G_OBJECT(widget), "setting", setting);
+ hbox = pidgin_add_widget_to_vbox(vbox, label, sg, widget,
+ FALSE, NULL);
+ return ((void*)hbox == (void*)vbox) ? widget : hbox;
+}
+
+/* XXX: it could be available for all plugins, not keyrings only */
+static GList *
+keyring_page_add_settings(PurpleRequestFields *settings)
+{
+ GList *it, *groups, *added_fields;
+ GtkSizeGroup *sg;
+
+ added_fields = NULL;
+ groups = purple_request_fields_get_groups(settings);
+ for (it = g_list_first(groups); it != NULL; it = g_list_next(it)) {
+ GList *it2, *fields;
+ GtkBox *vbox;
+ PurpleRequestFieldGroup *group;
+ const gchar *group_title;
+
+ group = it->data;
+ group_title = purple_request_field_group_get_title(group);
+ if (group_title) {
+ vbox = GTK_BOX(pidgin_make_frame(
+ GTK_WIDGET(keyring_vbox), group_title));
+ added_fields = g_list_prepend(added_fields,
+ g_object_get_data(G_OBJECT(vbox), "main-vbox"));
+ } else
+ vbox = keyring_vbox;
+
+ sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+
+ fields = purple_request_field_group_get_fields(group);
+ for (it2 = g_list_first(fields); it2 != NULL;
+ it2 = g_list_next(it2)) {
+ GtkWidget *added = keyring_page_add_settings_field(vbox,
+ it2->data, sg);
+ if (added == NULL || vbox != keyring_vbox)
+ continue;
+ added_fields = g_list_prepend(added_fields, added);
+ }
+
+ g_object_unref(sg);
+ }
+
+ return added_fields;
+}
+
+static void
+keyring_page_settings_apply(GtkButton *button, gpointer _unused)
+{
+ if (!purple_keyring_apply_settings(prefs, keyring_settings))
+ return;
+
+ gtk_widget_set_sensitive(keyring_apply, FALSE);
+}
+
+static void
+keyring_page_update_settings()
+{
+ if (keyring_settings != NULL)
+ purple_request_fields_destroy(keyring_settings);
+ keyring_settings = purple_keyring_read_settings();
+ if (!keyring_settings)
+ return;
+
+ keyring_settings_fields = keyring_page_add_settings(keyring_settings);
+
+ keyring_apply = gtk_button_new_with_mnemonic(_("_Apply"));
+ gtk_box_pack_start(keyring_vbox, keyring_apply, FALSE, FALSE, 1);
+ gtk_widget_set_sensitive(keyring_apply, FALSE);
+ keyring_settings_fields = g_list_prepend(keyring_settings_fields,
+ keyring_apply);
+ g_signal_connect(G_OBJECT(keyring_apply), "clicked",
+ G_CALLBACK(keyring_page_settings_apply), NULL);
+
+ gtk_widget_show_all(keyring_page_instance);
+}
+
+static void
+keyring_page_pref_set_inuse(GError *error, gpointer _keyring_page_instance)
+{
+ PurpleKeyring *in_use = purple_keyring_get_inuse();
+
+ if (_keyring_page_instance != keyring_page_instance) {
+ purple_debug_info("gtkprefs", "pref window already closed\n");
+ return;
+ }
+
+ gtk_widget_set_sensitive(GTK_WIDGET(keyring_combo), TRUE);
+
+ if (error != NULL) {
+ pidgin_prefs_dropdown_revert_active(keyring_combo);
+ purple_notify_error(NULL, _("Keyring"),
+ _("Failed to set new keyring"), error->message);
+ return;
+ }
+
+ g_return_if_fail(in_use != NULL);
+ purple_prefs_set_string("/purple/keyring/active",
+ purple_keyring_get_id(in_use));
+
+ keyring_page_update_settings();
+}
+
+static void
+keyring_page_pref_changed(GtkComboBox *combo_box, PidginPrefValue value)
+{
+ const char *keyring_id;
+ PurpleKeyring *keyring;
+ GList *it;
+
+ g_return_if_fail(combo_box != NULL);
+ g_return_if_fail(value.type == PURPLE_PREF_STRING);
+
+ keyring_id = value.value.string;
+ keyring = purple_keyring_find_keyring_by_id(keyring_id);
+ if (keyring == NULL) {
+ pidgin_prefs_dropdown_revert_active(keyring_combo);
+ purple_notify_error(NULL, _("Keyring"),
+ _("Selected keyring is disabled"), NULL);
+ return;
+ }
+
+ gtk_widget_set_sensitive(GTK_WIDGET(combo_box), FALSE);
+
+ for (it = keyring_settings_fields; it != NULL; it = g_list_next(it))
+ {
+ GtkWidget *widget = it->data;
+ gtk_container_remove(
+ GTK_CONTAINER(gtk_widget_get_parent(widget)), widget);
+ }
+ gtk_widget_show_all(keyring_page_instance);
+ g_list_free(keyring_settings_fields);
+ keyring_settings_fields = NULL;
+ if (keyring_settings)
+ purple_request_fields_destroy(keyring_settings);
+ keyring_settings = NULL;
+
+ purple_keyring_set_inuse(keyring, FALSE, keyring_page_pref_set_inuse,
+ keyring_page_instance);
+}
+
+static void
+keyring_page_cleanup(void)
+{
+ keyring_page_instance = NULL;
+ keyring_combo = NULL;
+ keyring_vbox = NULL;
+ g_list_free(keyring_settings_fields);
+ keyring_settings_fields = NULL;
+ if (keyring_settings)
+ purple_request_fields_destroy(keyring_settings);
+ keyring_settings = NULL;
+ keyring_apply = NULL;
+}
+
+static GtkWidget *
+keyring_page(void)
+{
+ GList *names;
+ PidginPrefValue initial;
+
+ g_return_val_if_fail(keyring_page_instance == NULL,
+ keyring_page_instance);
+
+ keyring_page_instance = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
+ gtk_container_set_border_width(GTK_CONTAINER(keyring_page_instance),
+ PIDGIN_HIG_BORDER);
+
+ /* Keyring selection */
+ keyring_vbox = GTK_BOX(pidgin_make_frame(keyring_page_instance,
+ _("Keyring")));
+ names = purple_keyring_get_options();
+ initial.type = PURPLE_PREF_STRING;
+ initial.value.string = purple_prefs_get_string("/purple/keyring/active");
+ pidgin_prefs_dropdown_from_list_with_cb(GTK_WIDGET(keyring_vbox),
+ _("Keyring:"), &keyring_combo, names, initial,
+ keyring_page_pref_changed);
+ g_list_free(names);
+
+ keyring_page_update_settings();
+
+ gtk_widget_show_all(keyring_page_instance);
+
+ return keyring_page_instance;
+}
+
+/*** keyring page - end *************************************************/
+
#ifndef _WIN32
static gint
sound_cmd_yeah(GtkEntry *entry, gpointer d)
@@ -3721,6 +4057,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++);
diff --git a/pidgin/gtkutils.c b/pidgin/gtkutils.c
index 3372c9df3f..d98ebfe8e1 100644
--- a/pidgin/gtkutils.c
+++ b/pidgin/gtkutils.c
@@ -483,7 +483,7 @@ GtkWidget *pidgin_new_item_from_stock(GtkWidget *menu, const char *str, const ch
GtkWidget *
pidgin_make_frame(GtkWidget *parent, const char *title)
{
- GtkWidget *vbox, *label, *hbox;
+ GtkWidget *vbox, *vbox2, *label, *hbox;
char *labeltitle;
vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
@@ -509,11 +509,13 @@ pidgin_make_frame(GtkWidget *parent, const char *title)
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
gtk_widget_show(label);
- vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
- gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
- gtk_widget_show(vbox);
+ vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+ gtk_box_pack_start(GTK_BOX(hbox), vbox2, FALSE, FALSE, 0);
+ gtk_widget_show(vbox2);
+
+ g_object_set_data(G_OBJECT(vbox2), "main-vbox", vbox);
- return vbox;
+ return vbox2;
}
static gpointer