diff options
author | Tomasz Wasilczyk <tomkiewicz@cpw.pidgin.im> | 2013-05-14 23:22:32 +0200 |
---|---|---|
committer | Tomasz Wasilczyk <tomkiewicz@cpw.pidgin.im> | 2013-05-14 23:22:32 +0200 |
commit | 175d6b4cc7bf1e9b27370253796efe15ea4d23ce (patch) | |
tree | 1f28586b2bd271dc157834de44d5820be7df768e | |
parent | f0b38e92d01478f40f3ed77ab0fb250951027308 (diff) | |
parent | 9ddc77d4bffed1decf93659230e6835d169d3b9d (diff) | |
download | pidgin-175d6b4cc7bf1e9b27370253796efe15ea4d23ce.tar.gz |
Merge from main
64 files changed, 7255 insertions, 202 deletions
@@ -28,6 +28,7 @@ syntax: regexp .*\.pyo$ .*\.rej$ .*\.so$ +.*\.moc$ Doxyfile(\.mingw)?$ VERSION$ aclocal.m4 @@ -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..c3e84fcae2 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,152 @@ else fi dnl ####################################################################### +dnl # Check for Secret Service headers +dnl ####################################################################### + +# disabled - see secretservice.c +#AC_ARG_ENABLE(libsecret, [AC_HELP_STRING([--disable-libsecret], [enable Secret Service support])], enable_secret_service=no, enable_secret_service=yes) + +#if test "x$enable_secret_service" = "xyes" ; then +# PKG_CHECK_MODULES(SECRETSERVICE, [libsecret-1], [ +# AC_SUBST(SECRETSERVICE_CFLAGS) +# AC_SUBST(SECRETSERVICE_LIBS) +# AC_DEFINE(HAVE_SECRETSERVICE, 1, [Define if we have Secret Service.]) +# ]) +#fi + +#AM_CONDITIONAL(ENABLE_SECRETSERVICE, test "x$enable_secret_service" = "xyes") +AM_CONDITIONAL(ENABLE_SECRETSERVICE, test "x1" = "x2") + +dnl ####################################################################### +dnl # Check for GNOME Keyring headers +dnl ####################################################################### + +AC_ARG_ENABLE(gnome-keyring, [AC_HELP_STRING([--disable-gnome-keyring], [enable GNOME Keyring support])], enable_gnome_keyring=no, enable_gnome_keyring=yes) + +if test "x$enable_gnome_keyring" = "xyes" ; then + PKG_CHECK_MODULES(GNOMEKEYRING, [gnome-keyring-1], [ + AC_SUBST(GNOMEKEYRING_CFLAGS) + AC_SUBST(GNOMEKEYRING_LIBS) + AC_DEFINE(HAVE_GNOMEKEYRING, 1, [Define if we have GNOME Keyring.]) + ]) +fi + +AM_CONDITIONAL(ENABLE_GNOMEKEYRING, test "x$enable_gnome_keyring" = "xyes") + +dnl ####################################################################### +dnl # Check for KWallet headers +dnl ####################################################################### + +AC_ARG_ENABLE(kwallet, [AC_HELP_STRING([--disable-kwallet], [enable KWallet support])], enable_kwallet=no, enable_kwallet=yes) +AC_ARG_WITH(kwallet-includes, [AC_HELP_STRING([--with-kwallet-includes=DIR], [compile the KWallet plugin against includes in DIR])], [ac_kwallet_includes="$withval"], [ac_kwallet_includes="no"]) +AC_ARG_WITH(kwallet-libs, [AC_HELP_STRING([--with-kwallet-libs=DIR], [compile the KWallet plugin against the KWallet libs in DIR])], [ac_kwallet_libs="$withval"], [ac_kwallet_libs="no"]) + +if test "x$enable_kwallet" = "xyes"; then + KWALLET_CXXFLAGS="" + KWALLET_LIBS="" + if test -z "$with_kwallet_includes" || test -z "$with_kwallet_libs"; then + AC_CHECK_PROG(KDE4_CONFIG, kde4-config, kde4-config, no) + if test "x$KDE4_CONFIG" = "xno"; then + if test "x$force_deps" = "xyes"; then + AC_MSG_WARN([ +kde4-config not found. $KDE4_CONFIG +Use --disable-kwallet if you do not need KWallet support. +Use --with-kwallet-includes and --with-kwallet-libs to set up includes manually. +]) + enable_kwallet=no + fi + fi + fi +fi + +if test "x$enable_kwallet" = "xyes"; then + AC_LANG_PUSH([C++]) + CPPFLAGS_save="$CPPFLAGS" + + if test "$ac_kwallet_includes" != "no"; then + KWALLET_CXXFLAGS="-I$ac_kwallet_includes" + elif test "x$KDE4_CONFIG" != "xno"; then + KWALLET_CXXFLAGS="-I`$KDE4_CONFIG --path include`" + fi + CPPFLAGS="$CPPFLAGS $KWALLET_CXXFLAGS" + AC_CHECK_HEADER([kwallet.h], , [enable_kwallet=no]) + + CPPFLAGS="$CPPFLAGS_save" + AC_LANG_POP +fi + +if test "x$enable_kwallet" = "xyes"; then + dnl Ensure C++ compiler works + AC_CHECK_PROG(CXXTEST, [$CXX], [$CXX]) + if test "x$CXXTEST" = "x"; then + if test "x$force_deps" = "xyes"; then + AC_MSG_WARN([ +A C++ compiler was not found. +Use --disable-kwallet if you do not need KWallet support. +]) + enable_kwallet=no + fi + fi +fi + +AC_LANG_PUSH([C++]) +CPPFLAGS_save="$CPPFLAGS" +LDFLAGS_save="$LDFLAGS" +if test "x$enable_kwallet" = "xyes"; then + + PKG_CHECK_MODULES(QT4, [QtCore], [ + AC_SUBST(QT4_CFLAGS) + AC_SUBST(QT4_LIBS) + ], [ + AC_MSG_RESULT(no) + AC_MSG_WARN([ +Qt4 development headers not found. +Use --disable-kwallet if you do not need KWallet support. +]) + enable_kwallet=no + ]) +fi + +if test "x$enable_kwallet" = "xyes"; then + AC_MSG_CHECKING([for metaobject compiler]) + MOC=`$PKG_CONFIG --variable=moc_location QtCore` + AC_SUBST(MOC) + AC_MSG_RESULT([$MOC]) + + + AC_MSG_CHECKING([for KWallet libraries]) + if test "$ac_kwallet_libs" != "no"; then + KWALLET_LIBS="-L$ac_kwallet_libs -lkdeui" + elif test "x$KDE4_CONFIG" != "xno"; then + KWALLET_LIBS="-L`$KDE4_CONFIG --install lib`/kde4/devel -lkdeui" + else + KWALLET_LIBS="-lkdeui" + fi + KWALLET_LIBS="$KWALLET_LIBS" + CPPFLAGS="$CPPFLAGS $KWALLET_CXXFLAGS" + LDFLAGS="$LDFLAGS $KWALLET_LIBS $QT4_LIBS" + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <kwallet.h>], + [KWallet::Wallet::LocalWallet();])], [AC_MSG_RESULT([yes])], + [if test "x$force_deps" = "xyes"; then +AC_MSG_ERROR([ +KWallet development libraries not found. +Use --disable-kwallet if you do not need KWallet support. +]) +fi +]) + +fi +CPPFLAGS="$CPPFLAGS_save" +LDFLAGS="$LDFLAGS_save" +AC_LANG_POP + +AC_SUBST(KWALLET_CXXFLAGS) +AC_SUBST(KWALLET_LIBS) + +AM_CONDITIONAL(ENABLE_KWALLET, test "x$enable_kwallet" = "xyes") + +dnl ####################################################################### dnl # Check for Python dnl ####################################################################### @@ -2429,6 +2577,7 @@ 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 +2856,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 +2940,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 GNOME Keyring...... : $enable_gnome_keyring +echo Build with KWallet............ : $enable_kwallet +#echo Build with Secret Service..... : $enable_secret_service +echo echo Build with plugin support..... : $enable_plugins echo Build with Mono support....... : $enable_mono echo Build with Perl support....... : $enable_perl diff --git a/finch/gntaccount.c b/finch/gntaccount.c 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 ad6c174b30..7c98e380e4 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..2c90da9462 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,9 @@ request_password_ok_cb(PurpleAccount *account, PurpleRequestFields *fields) return; } - if(remember) - purple_account_set_remember_password(account, TRUE); - - purple_account_set_password(account, entry); + purple_account_set_remember_password(account, remember); + purple_account_set_password(account, entry, NULL, NULL); _purple_connection_new(account, FALSE, entry); } @@ -1210,11 +1277,27 @@ 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 +1324,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 +1350,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 +1707,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 +2234,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 +2794,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 +2901,14 @@ connection_error_cb(PurpleConnection *gc, account, type, description); } +static void +password_migration_cb(PurpleAccount *account) +{ + /* account may be NULL (means: all) */ + + schedule_accounts_save(); +} + const PurpleConnectionErrorInfo * purple_account_get_current_error(PurpleAccount *account) { @@ -2812,6 +2953,12 @@ purple_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 +3029,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 +3284,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..03bafb7840 --- /dev/null +++ b/libpurple/keyring.c @@ -0,0 +1,1357 @@ +/** + * @file keyring.c Keyring API + * @ingroup core + */ + +/* purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program ; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <glib.h> +#include <string.h> +#include "account.h" +#include "keyring.h" +#include "signals.h" +#include "core.h" +#include "debug.h" +#include "internal.h" +#include "dbus-maybe.h" + +struct _PurpleKeyring +{ + gchar *name; + gchar *id; + PurpleKeyringRead read_password; + PurpleKeyringSave save_password; + PurpleKeyringCancelRequests cancel_requests; + PurpleKeyringClose close_keyring; + PurpleKeyringImportPassword import_password; + PurpleKeyringExportPassword export_password; + PurpleKeyringReadSettings read_settings; + PurpleKeyringApplySettings apply_settings; + + gboolean is_closing; + gboolean is_cancelling; + gboolean close_after_cancel; +}; + +typedef struct +{ + GError *error; + PurpleKeyringSetInUseCallback cb; + gpointer cb_data; + PurpleKeyring *new; + PurpleKeyring *old; + + /** + * We are done when finished is positive and read_outstanding is zero. + */ + gboolean finished; + int read_outstanding; + + gboolean abort; + gboolean force; + gboolean succeeded; +} PurpleKeyringChangeTracker; + +typedef void (*PurpleKeyringDropCallback)(gpointer data); + +typedef struct +{ + PurpleKeyringDropCallback cb; + gpointer cb_data; + + gboolean finished; + int drop_outstanding; +} PurpleKeyringDropTracker; + +typedef struct +{ + PurpleKeyringSaveCallback cb; + gpointer cb_data; +} PurpleKeyringSetPasswordData; + +typedef struct +{ + gchar *keyring_id; + gchar *mode; + gchar *data; +} PurpleKeyringFailedImport; + +static void +purple_keyring_change_tracker_free(PurpleKeyringChangeTracker *tracker) +{ + if (tracker->error) + g_error_free(tracker->error); + g_free(tracker); +} + +static void +purple_keyring_failed_import_free(PurpleKeyringFailedImport *import) +{ + g_return_if_fail(import != NULL); + + g_free(import->keyring_id); + g_free(import->mode); + purple_str_wipe(import->data); + g_free(import); +} + +static void +purple_keyring_close(PurpleKeyring *keyring); + +static void +purple_keyring_drop_passwords(PurpleKeyring *keyring, + PurpleKeyringDropCallback cb, gpointer data); + +/* A list of available keyrings */ +static GList *purple_keyring_keyrings = NULL; + +/* Keyring being used. */ +static PurpleKeyring *purple_keyring_inuse = NULL; + +/* Keyring id marked to use (may not be loadable). */ +static gchar *purple_keyring_to_use = NULL; + +static guint purple_keyring_pref_cbid = 0; +static GList *purple_keyring_loaded_plugins = NULL; +static PurpleKeyringChangeTracker *current_change_tracker = NULL; +static gboolean purple_keyring_is_quitting = FALSE; +static GHashTable *purple_keyring_failed_imports = NULL; + +static const gchar * +purple_keyring_print_account(PurpleAccount *account) +{ + static gchar print_buff[100]; + + if (account == NULL) { + g_snprintf(print_buff, 100, "(null)"); + return print_buff; + } + + g_snprintf(print_buff, 100, "%s:%s", + purple_account_get_protocol_id(account), + purple_account_get_username(account)); + return print_buff; +} + +/**************************************************************************/ +/* Setting used keyrings */ +/**************************************************************************/ + +PurpleKeyring * +purple_keyring_find_keyring_by_id(const gchar *id) +{ + GList *it; + + for (it = purple_keyring_keyrings; it != NULL; it = it->next) { + PurpleKeyring *keyring = it->data; + const gchar *curr_id = purple_keyring_get_id(keyring); + + if (g_strcmp0(id, curr_id) == 0) + return keyring; + } + + return NULL; +} + +static void +purple_keyring_pref_callback(const gchar *pref, PurplePrefType type, + gconstpointer id, gpointer data) +{ + PurpleKeyring *new_keyring; + + g_return_if_fail(g_strcmp0(pref, "/purple/keyring/active") == 0); + g_return_if_fail(type == PURPLE_PREF_STRING); + g_return_if_fail(id != NULL); + + new_keyring = purple_keyring_find_keyring_by_id(id); + g_return_if_fail(new_keyring != NULL); + + purple_keyring_set_inuse(new_keyring, FALSE, NULL, NULL); +} + +static void +purple_keyring_pref_connect(void) +{ + g_return_if_fail(purple_keyring_pref_cbid == 0); + + purple_keyring_pref_cbid = purple_prefs_connect_callback(NULL, + "/purple/keyring/active", purple_keyring_pref_callback, NULL); +} + +static void +purple_keyring_pref_disconnect(void) +{ + g_return_if_fail(purple_keyring_pref_cbid != 0); + + purple_prefs_disconnect_callback(purple_keyring_pref_cbid); + purple_keyring_pref_cbid = 0; +} + +PurpleKeyring * +purple_keyring_get_inuse(void) +{ + return purple_keyring_inuse; +} + +static void +purple_keyring_set_inuse_drop_cb(gpointer _tracker) +{ + PurpleKeyringChangeTracker *tracker = _tracker; + + g_return_if_fail(tracker != NULL); + + if (tracker->succeeded) { + purple_keyring_close(tracker->old); + + purple_debug_info("keyring", "Successfully changed keyring.\n"); + + purple_keyring_inuse = tracker->new; + current_change_tracker = NULL; + + if (tracker->cb != NULL) + tracker->cb(NULL, tracker->cb_data); + purple_keyring_change_tracker_free(tracker); + return; + } + + purple_debug_error("keyring", "Failed to change keyring, aborting.\n"); + + purple_keyring_close(tracker->new); + + purple_keyring_pref_disconnect(); + purple_prefs_set_string("/purple/keyring/active", + purple_keyring_get_id(tracker->old)); + purple_keyring_pref_connect(); + + current_change_tracker = NULL; + + if (tracker->error == NULL) { + tracker->error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_UNKNOWN, + _("An unknown error has occured.")); + } + + if (tracker->cb != NULL) + tracker->cb(tracker->error, tracker->cb_data); + + purple_keyring_change_tracker_free(tracker); +} + +static void +purple_keyring_set_inuse_save_cb(PurpleAccount *account, GError *error, + gpointer _tracker) +{ + PurpleKeyringChangeTracker *tracker = _tracker; + + g_return_if_fail(account != NULL); + g_return_if_fail(tracker != NULL); + + tracker->read_outstanding--; + + if (g_error_matches(error, PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_NOPASSWORD)) { + if (purple_debug_is_verbose()) { + purple_debug_misc("keyring", "No password found while " + "changing keyring for account %s: %s.\n", + purple_keyring_print_account(account), + error->message); + } + } else if (g_error_matches(error, PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_ACCESSDENIED)) { + purple_debug_info("keyring", "Access denied while changing " + "keyring for account %s: %s.\n", + purple_keyring_print_account(account), error->message); + tracker->abort = TRUE; + if (tracker->error != NULL) + g_error_free(tracker->error); + tracker->error = g_error_copy(error); + } else if (g_error_matches(error, PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_CANCELLED)) { + purple_debug_info("keyring", "Operation cancelled while " + "changing keyring for account %s: %s.\n", + purple_keyring_print_account(account), error->message); + tracker->abort = TRUE; + if (tracker->error == NULL) + tracker->error = g_error_copy(error); + } else if (g_error_matches(error, PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_BACKENDFAIL)) { + purple_debug_error("keyring", "Failed to communicate with " + "backend while changing keyring for account %s: %s. " + "Aborting changes.\n", + purple_keyring_print_account(account), error->message); + tracker->abort = TRUE; + if (tracker->error != NULL) + g_error_free(tracker->error); + tracker->error = g_error_copy(error); + } else if (error != NULL) { + purple_debug_error("keyring", "Unknown error while changing " + "keyring for account %s: %s. Aborting changes.\n", + purple_keyring_print_account(account), error->message); + tracker->abort = TRUE; + if (tracker->error == NULL) + tracker->error = g_error_copy(error); + } + + purple_signal_emit(purple_keyring_get_handle(), "password-migration", + account); + + if (!tracker->finished || tracker->read_outstanding > 0) + return; + + /* This was the last one. */ + if (tracker->abort && !tracker->force) { + tracker->succeeded = FALSE; + purple_keyring_drop_passwords(tracker->new, + purple_keyring_set_inuse_drop_cb, tracker); + } else { + tracker->succeeded = TRUE; + purple_keyring_drop_passwords(tracker->old, + purple_keyring_set_inuse_drop_cb, tracker); + } +} + +static void +purple_keyring_set_inuse_read_cb(PurpleAccount *account, const gchar *password, + GError *error, gpointer _tracker) +{ + PurpleKeyringChangeTracker *tracker = _tracker; + PurpleKeyringSave save_cb; + + g_return_if_fail(account != NULL); + g_return_if_fail(tracker != NULL); + + if (tracker->abort) { + purple_keyring_set_inuse_save_cb(account, NULL, tracker); + return; + } + + if (error != NULL) { + if (tracker->force == TRUE || g_error_matches(error, + PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_NOPASSWORD)) { + /* Don't save password, and ignore it. */ + } else { + tracker->abort = TRUE; + } + purple_keyring_set_inuse_save_cb(account, error, tracker); + return; + } + + save_cb = purple_keyring_get_save_password(tracker->new); + g_assert(save_cb != NULL); + + save_cb(account, password, purple_keyring_set_inuse_save_cb, tracker); +} + +void +purple_keyring_set_inuse(PurpleKeyring *newkeyring, gboolean force, + PurpleKeyringSetInUseCallback cb, gpointer data) +{ + PurpleKeyring *oldkeyring; + PurpleKeyringChangeTracker *tracker; + GList *it; + PurpleKeyringRead read_cb; + + if (current_change_tracker != NULL) { + GError *error; + purple_debug_error("keyring", "There is password migration " + "session already running.\n"); + if (cb == NULL) + return; + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_INTERNAL, + _("There is a password migration session already " + "running.")); + cb(error, data); + g_error_free(error); + return; + } + + oldkeyring = purple_keyring_get_inuse(); + + if (oldkeyring == newkeyring) { + if (purple_debug_is_verbose()) { + purple_debug_misc("keyring", + "Old and new keyring are the same: %s.\n", + (newkeyring != NULL) ? + newkeyring->id : "(null)"); + } + if (cb != NULL) + cb(NULL, data); + return; + } + + purple_debug_info("keyring", "Attempting to set new keyring: %s.\n", + (newkeyring != NULL) ? newkeyring->id : "(null)"); + + if (oldkeyring == NULL) { /* No keyring was set before. */ + if (purple_debug_is_verbose()) { + purple_debug_misc("keyring", "Setting keyring for the " + "first time: %s.\n", newkeyring->id); + } + + purple_keyring_inuse = newkeyring; + g_assert(current_change_tracker == NULL); + if (cb != NULL) + cb(NULL, data); + return; + } + + /* Starting a migration. */ + + read_cb = purple_keyring_get_read_password(oldkeyring); + g_assert(read_cb != NULL); + + purple_debug_misc("keyring", "Starting migration from: %s.\n", + oldkeyring->id); + + tracker = g_new0(PurpleKeyringChangeTracker, 1); + current_change_tracker = tracker; + + tracker->cb = cb; + tracker->cb_data = data; + tracker->new = newkeyring; + tracker->old = oldkeyring; + tracker->force = force; + + for (it = purple_accounts_get_all(); it != NULL; it = it->next) { + if (tracker->abort) { + tracker->finished = TRUE; + break; + } + tracker->read_outstanding++; + + if (it->next == NULL) + tracker->finished = TRUE; + + read_cb(it->data, purple_keyring_set_inuse_read_cb, tracker); + } +} + +void +purple_keyring_register(PurpleKeyring *keyring) +{ + const gchar *keyring_id; + + g_return_if_fail(keyring != NULL); + + keyring_id = purple_keyring_get_id(keyring); + + purple_debug_info("keyring", "Registering keyring: %s\n", + keyring_id ? keyring_id : "(null)"); + + if (purple_keyring_get_id(keyring) == NULL || + purple_keyring_get_name(keyring) == NULL || + purple_keyring_get_read_password(keyring) == NULL || + purple_keyring_get_save_password(keyring) == NULL) { + purple_debug_error("keyring", "Cannot register %s, some " + "required fields are missing.\n", + keyring_id ? keyring_id : "(null)"); + return; + } + + if (purple_keyring_find_keyring_by_id(keyring_id) != NULL) { + purple_debug_error("keyring", + "Keyring is already registered.\n"); + return; + } + + /* If this is the configured keyring, use it. */ + if (purple_keyring_inuse == NULL && + g_strcmp0(keyring_id, purple_keyring_to_use) == 0) { + purple_debug_info("keyring", "Keyring %s matches keyring to " + "use, using it.\n", keyring_id); + purple_keyring_set_inuse(keyring, TRUE, NULL, NULL); + } + + PURPLE_DBUS_REGISTER_POINTER(keyring, PurpleKeyring); + purple_signal_emit(purple_keyring_get_handle(), "keyring-register", + keyring_id, keyring); + if (purple_debug_is_verbose()) { + purple_debug_info("keyring", "Registered keyring: %s.\n", + keyring_id); + } + + purple_keyring_keyrings = g_list_prepend(purple_keyring_keyrings, + keyring); +} + +void +purple_keyring_unregister(PurpleKeyring *keyring) +{ + PurpleKeyring *inuse; + PurpleKeyring *fallback; + const gchar *keyring_id; + + g_return_if_fail(keyring != NULL); + + keyring_id = purple_keyring_get_id(keyring); + + purple_debug_info("keyring", "Unregistering keyring: %s.\n", + keyring_id); + + purple_signal_emit(purple_keyring_get_handle(), "keyring-unregister", + keyring_id, keyring); + PURPLE_DBUS_UNREGISTER_POINTER(keyring); + + inuse = purple_keyring_get_inuse(); + fallback = purple_keyring_find_keyring_by_id(PURPLE_DEFAULT_KEYRING); + + if (inuse == keyring) { + if (inuse != fallback) { + purple_keyring_set_inuse(fallback, TRUE, NULL, NULL); + } else { + fallback = NULL; + purple_keyring_set_inuse(NULL, TRUE, NULL, NULL); + } + } + + purple_keyring_keyrings = g_list_remove(purple_keyring_keyrings, + keyring); +} + +GList * +purple_keyring_get_options(void) +{ + GList *options = NULL; + GList *it; + static gchar currentDisabledName[40]; + + if (purple_keyring_get_inuse() == NULL && purple_keyring_to_use != NULL + && purple_keyring_to_use[0] != '\0') { + g_snprintf(currentDisabledName, sizeof(currentDisabledName), + _("%s (disabled)"), purple_keyring_to_use); + + options = g_list_append(options, currentDisabledName); + options = g_list_append(options, purple_keyring_to_use); + } + + for (it = purple_keyring_keyrings; it != NULL; it = it->next) { + PurpleKeyring *keyring = it->data; + + options = g_list_append(options, + (gpointer)purple_keyring_get_name(keyring)); + options = g_list_append(options, + (gpointer)purple_keyring_get_id(keyring)); + } + + return options; +} + + +/**************************************************************************/ +/* Keyring plugin wrappers */ +/**************************************************************************/ + +static void +purple_keyring_close(PurpleKeyring *keyring) +{ + PurpleKeyringClose close_cb; + + g_return_if_fail(keyring != NULL); + + if (keyring->is_cancelling) { + keyring->close_after_cancel = TRUE; + return; + } + if (keyring->is_closing) + return; + keyring->is_closing = TRUE; + + close_cb = purple_keyring_get_close_keyring(keyring); + + if (close_cb != NULL) + close_cb(); + + keyring->is_closing = FALSE; +} + +static void +purple_keyring_cancel_requests(PurpleKeyring *keyring) +{ + PurpleKeyringCancelRequests cancel_cb; + + g_return_if_fail(keyring != NULL); + + if (keyring->is_cancelling) + return; + keyring->is_cancelling = TRUE; + + cancel_cb = purple_keyring_get_cancel_requests(keyring); + + if (cancel_cb != NULL) + cancel_cb(); + + keyring->is_cancelling = FALSE; + + if (keyring->close_after_cancel) { + keyring->close_after_cancel = FALSE; + purple_keyring_close(keyring); + } +} + +static void +purple_keyring_drop_passwords_save_cb(PurpleAccount *account, GError *error, + gpointer _tracker) +{ + PurpleKeyringDropTracker *tracker = _tracker; + + tracker->drop_outstanding--; + + if (!tracker->finished || tracker->drop_outstanding > 0) + return; + + if (tracker->cb) + tracker->cb(tracker->cb_data); + g_free(tracker); +} + +static void +purple_keyring_drop_passwords(PurpleKeyring *keyring, + PurpleKeyringDropCallback cb, gpointer data) +{ + GList *it; + PurpleKeyringSave save_cb; + PurpleKeyringDropTracker *tracker; + + g_return_if_fail(keyring != NULL); + + save_cb = purple_keyring_get_save_password(keyring); + g_assert(save_cb != NULL); + + tracker = g_new0(PurpleKeyringDropTracker, 1); + tracker->cb = cb; + tracker->cb_data = data; + + for (it = purple_accounts_get_all(); it != NULL; it = it->next) { + PurpleAccount *account = it->data; + + tracker->drop_outstanding++; + if (it->next == NULL) + tracker->finished = TRUE; + + save_cb(account, NULL, purple_keyring_drop_passwords_save_cb, + tracker); + } +} + +gboolean +purple_keyring_import_password(PurpleAccount *account, const gchar *keyring_id, + const gchar *mode, const gchar *data, GError **error) +{ + PurpleKeyring *keyring; + PurpleKeyring *inuse; + PurpleKeyringImportPassword import; + + g_return_val_if_fail(account != NULL, FALSE); + + if (keyring_id == NULL) + keyring_id = PURPLE_DEFAULT_KEYRING; + + purple_debug_misc("keyring", "Importing password for account %s to " + "keyring %s.\n", purple_keyring_print_account(account), + keyring_id); + + keyring = purple_keyring_find_keyring_by_id(keyring_id); + if (keyring == NULL) { + if (error != NULL) { + *error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_BACKENDFAIL, + _("Specified keyring is not registered.")); + } + purple_debug_warning("Keyring", "Specified keyring is not " + "registered, cannot import password info for account " + "%s.\n", purple_keyring_print_account(account)); + return FALSE; + } + + inuse = purple_keyring_get_inuse(); + if (inuse == NULL) { + PurpleKeyringFailedImport *import; + if (error != NULL) { + *error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_NOKEYRING, + _("No keyring loaded, cannot import password " + "info.")); + } + purple_debug_warning("Keyring", + "No keyring loaded, cannot import password info for " + "account %s.\n", purple_keyring_print_account(account)); + + import = g_new0(PurpleKeyringFailedImport, 1); + import->keyring_id = g_strdup(keyring_id); + import->mode = g_strdup(mode); + import->data = g_strdup(data); + g_hash_table_insert(purple_keyring_failed_imports, account, + import); + return FALSE; + } + + if (inuse != keyring) { + if (error != NULL) { + *error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_INTERNAL, + _("Specified keyring id does not match the " + "loaded one.")); + } + purple_debug_error("keyring", + "Specified keyring %s is not currently used (%s). " + "Data will be lost.\n", keyring_id, + purple_keyring_get_id(inuse)); + return FALSE; + } + + import = purple_keyring_get_import_password(inuse); + if (import == NULL) { + if (purple_debug_is_verbose()) { + purple_debug_misc("Keyring", "Configured keyring " + "cannot import password info. This might be " + "normal.\n"); + } + return TRUE; + } + + return import(account, mode, data, error); +} + +gboolean +purple_keyring_export_password(PurpleAccount *account, const gchar **keyring_id, + const gchar **mode, gchar **data, GError **error, + GDestroyNotify *destroy) +{ + PurpleKeyring *inuse; + PurpleKeyringExportPassword export; + + g_return_val_if_fail(account != NULL, FALSE); + g_return_val_if_fail(keyring_id != NULL, FALSE); + g_return_val_if_fail(mode != NULL, FALSE); + g_return_val_if_fail(data != NULL, FALSE); + g_return_val_if_fail(error != NULL, FALSE); + + inuse = purple_keyring_get_inuse(); + + if (inuse == NULL) { + PurpleKeyringFailedImport *import = g_hash_table_lookup( + purple_keyring_failed_imports, account); + + if (import == NULL) { + *error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_NOKEYRING, + _("No keyring configured, cannot export " + "password info.")); + purple_debug_warning("keyring", + "No keyring configured, cannot export password " + "info.\n"); + return FALSE; + } else { + purple_debug_info("keyring", "No keyring configured, " + "getting fallback export data for %s.\n", + purple_keyring_print_account(account)); + + *keyring_id = import->keyring_id; + *mode = import->mode; + *data = g_strdup(import->data); + *destroy = (GDestroyNotify)purple_str_wipe; + return TRUE; + } + } + + if (purple_debug_is_verbose()) { + purple_debug_misc("keyring", + "Exporting password for account %s from keyring %s\n", + purple_keyring_print_account(account), + purple_keyring_get_id(inuse)); + } + + *keyring_id = purple_keyring_get_id(inuse); + + export = purple_keyring_get_export_password(inuse); + if (export == NULL) { + if (purple_debug_is_verbose()) { + purple_debug_misc("Keyring", "Configured keyring " + "cannot export password info. This might be " + "normal.\n"); + } + *mode = NULL; + *data = NULL; + *destroy = NULL; + return TRUE; + } + + return export(account, mode, data, error, destroy); +} + +void +purple_keyring_get_password(PurpleAccount *account, + PurpleKeyringReadCallback cb, gpointer data) +{ + GError *error; + PurpleKeyring *inuse; + PurpleKeyringRead read_cb; + + g_return_if_fail(account != NULL); + + if (purple_keyring_is_quitting) { + purple_debug_error("keyring", "Cannot request a password while " + "quitting.\n"); + if (cb == NULL) + return; + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_INTERNAL, + _("Cannot request a password while quitting.")); + cb(account, NULL, error, data); + g_error_free(error); + return; + } + + inuse = purple_keyring_get_inuse(); + + if (inuse == NULL) { + purple_debug_error("keyring", "No keyring configured.\n"); + if (cb == NULL) + return; + + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_NOKEYRING, + _("No keyring configured.")); + cb(account, NULL, error, data); + g_error_free(error); + } + + read_cb = purple_keyring_get_read_password(inuse); + g_assert(read_cb != NULL); + + purple_debug_info("keyring", "Reading password for account %s...\n", + purple_keyring_print_account(account)); + read_cb(account, cb, data); +} + +static void +purple_keyring_set_password_save_cb(PurpleAccount *account, GError *error, + gpointer _set_data) +{ + PurpleKeyringSetPasswordData *set_data = _set_data; + + g_return_if_fail(account != NULL); + g_return_if_fail(set_data != NULL); + + if (error == NULL && purple_debug_is_verbose()) { + purple_debug_misc("keyring", "Password for account %s " + "saved successfully.\n", + purple_keyring_print_account(account)); + } else if (purple_debug_is_verbose()) { + purple_debug_warning("keyring", "Password for account %s " + "not saved successfully.\n", + purple_keyring_print_account(account)); + } + + if (error != NULL) { + purple_notify_error(NULL, _("Keyrings"), + _("Failed to save a password in keyring."), + error->message); + } + + if (set_data->cb != NULL) + set_data->cb(account, error, set_data->cb_data); + g_free(set_data); +} + +void +purple_keyring_set_password(PurpleAccount *account, const gchar *password, + PurpleKeyringSaveCallback cb, gpointer data) +{ + GError *error; + PurpleKeyring *inuse; + PurpleKeyringSave save_cb; + PurpleKeyringSetPasswordData *set_data; + + g_return_if_fail(account != NULL); + + if (purple_keyring_is_quitting) { + purple_debug_error("keyring", "Cannot save a password while " + "quitting.\n"); + if (cb == NULL) + return; + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_INTERNAL, + _("Cannot save a password while quitting.")); + cb(account, error, data); + g_error_free(error); + return; + } + + if (current_change_tracker != NULL) { + purple_debug_error("keyring", "Cannot save a password during " + "password migration.\n"); + if (cb == NULL) + return; + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_INTERNAL, + _("Cannot save a password during password migration.")); + cb(account, error, data); + g_error_free(error); + return; + } + + inuse = purple_keyring_get_inuse(); + if (inuse == NULL) { + if (cb == NULL) + return; + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_NOKEYRING, + _("No keyring configured.")); + cb(account, error, data); + g_error_free(error); + return; + } + + save_cb = purple_keyring_get_save_password(inuse); + g_assert(save_cb != NULL); + + set_data = g_new(PurpleKeyringSetPasswordData, 1); + set_data->cb = cb; + set_data->cb_data = data; + purple_debug_info("keyring", "%s password for account %s...\n", + (password ? "Saving" : "Removing"), + purple_keyring_print_account(account)); + save_cb(account, password, purple_keyring_set_password_save_cb, set_data); +} + +PurpleRequestFields * +purple_keyring_read_settings(void) +{ + PurpleKeyring *inuse; + PurpleKeyringReadSettings read_settings; + + if (purple_keyring_is_quitting || current_change_tracker != NULL) { + purple_debug_error("keyring", "Cannot read settngs at the " + "moment.\n"); + return NULL; + } + + inuse = purple_keyring_get_inuse(); + if (inuse == NULL) { + purple_debug_error("keyring", "No keyring in use.\n"); + return NULL; + } + + read_settings = purple_keyring_get_read_settings(inuse); + if (read_settings == NULL) + return NULL; + return read_settings(); +} + +gboolean +purple_keyring_apply_settings(void *notify_handle, PurpleRequestFields *fields) +{ + PurpleKeyring *inuse; + PurpleKeyringApplySettings apply_settings; + + g_return_val_if_fail(fields != NULL, FALSE); + + if (purple_keyring_is_quitting || current_change_tracker != NULL) { + purple_debug_error("keyring", "Cannot apply settngs at the " + "moment.\n"); + return FALSE; + } + + inuse = purple_keyring_get_inuse(); + if (inuse == NULL) { + purple_debug_error("keyring", "No keyring in use.\n"); + return FALSE; + } + + apply_settings = purple_keyring_get_apply_settings(inuse); + if (apply_settings == NULL) { + purple_debug_warning("keyring", "Applying settings not " + "supported.\n"); + return FALSE; + } + return apply_settings(notify_handle, fields); +} + +/**************************************************************************/ +/* PurpleKeyring accessors */ +/**************************************************************************/ + +PurpleKeyring * +purple_keyring_new(void) +{ + return g_new0(PurpleKeyring, 1); +} + +void +purple_keyring_free(PurpleKeyring *keyring) +{ + g_return_if_fail(keyring != NULL); + + g_free(keyring->name); + g_free(keyring->id); + g_free(keyring); +} + +const gchar * +purple_keyring_get_name(const PurpleKeyring *keyring) +{ + g_return_val_if_fail(keyring != NULL, NULL); + + return keyring->name; +} + +const gchar * +purple_keyring_get_id(const PurpleKeyring *keyring) +{ + g_return_val_if_fail(keyring != NULL, NULL); + + return keyring->id; +} + +PurpleKeyringRead +purple_keyring_get_read_password(const PurpleKeyring *keyring) +{ + g_return_val_if_fail(keyring != NULL, NULL); + + return keyring->read_password; +} + +PurpleKeyringSave +purple_keyring_get_save_password(const PurpleKeyring *keyring) +{ + g_return_val_if_fail(keyring != NULL, NULL); + + return keyring->save_password; +} + +PurpleKeyringCancelRequests +purple_keyring_get_cancel_requests(const PurpleKeyring *keyring) +{ + g_return_val_if_fail(keyring != NULL, NULL); + + return keyring->cancel_requests; +} + +PurpleKeyringClose +purple_keyring_get_close_keyring(const PurpleKeyring *keyring) +{ + g_return_val_if_fail(keyring != NULL, NULL); + + return keyring->close_keyring; +} + +PurpleKeyringImportPassword +purple_keyring_get_import_password(const PurpleKeyring *keyring) +{ + g_return_val_if_fail(keyring != NULL, NULL); + + return keyring->import_password; +} + +PurpleKeyringExportPassword +purple_keyring_get_export_password(const PurpleKeyring *keyring) +{ + g_return_val_if_fail(keyring != NULL, NULL); + + return keyring->export_password; +} + +PurpleKeyringReadSettings +purple_keyring_get_read_settings(const PurpleKeyring *keyring) +{ + g_return_val_if_fail(keyring != NULL, NULL); + + return keyring->read_settings; +} + +PurpleKeyringApplySettings +purple_keyring_get_apply_settings(const PurpleKeyring *keyring) +{ + g_return_val_if_fail(keyring != NULL, NULL); + + return keyring->apply_settings; +} + +void +purple_keyring_set_name(PurpleKeyring *keyring, const gchar *name) +{ + g_return_if_fail(keyring != NULL); + g_return_if_fail(name != NULL); + + g_free(keyring->name); + keyring->name = g_strdup(name); +} + +void +purple_keyring_set_id(PurpleKeyring *keyring, const gchar *id) +{ + g_return_if_fail(keyring != NULL); + g_return_if_fail(id != NULL); + + g_free(keyring->id); + keyring->id = g_strdup(id); +} + +void +purple_keyring_set_read_password(PurpleKeyring *keyring, + PurpleKeyringRead read_cb) +{ + g_return_if_fail(keyring != NULL); + g_return_if_fail(read_cb != NULL); + + keyring->read_password = read_cb; +} + +void +purple_keyring_set_save_password(PurpleKeyring *keyring, + PurpleKeyringSave save_cb) +{ + g_return_if_fail(keyring != NULL); + g_return_if_fail(save_cb != NULL); + + keyring->save_password = save_cb; +} + +void +purple_keyring_set_cancel_requests(PurpleKeyring *keyring, + PurpleKeyringCancelRequests cancel_requests) +{ + g_return_if_fail(keyring != NULL); + + keyring->cancel_requests = cancel_requests; +} + +void +purple_keyring_set_close_keyring(PurpleKeyring *keyring, + PurpleKeyringClose close_cb) +{ + g_return_if_fail(keyring != NULL); + + keyring->close_keyring = close_cb; +} + +void +purple_keyring_set_import_password(PurpleKeyring *keyring, + PurpleKeyringImportPassword import_password) +{ + g_return_if_fail(keyring != NULL); + + keyring->import_password = import_password; +} + +void +purple_keyring_set_export_password(PurpleKeyring *keyring, + PurpleKeyringExportPassword export_password) +{ + g_return_if_fail(keyring != NULL); + + keyring->export_password = export_password; +} + +void +purple_keyring_set_read_settings(PurpleKeyring *keyring, +PurpleKeyringReadSettings read_settings) +{ + g_return_if_fail(keyring != NULL); + + keyring->read_settings = read_settings; +} + +void +purple_keyring_set_apply_settings(PurpleKeyring *keyring, +PurpleKeyringApplySettings apply_settings) +{ + g_return_if_fail(keyring != NULL); + + keyring->apply_settings = apply_settings; +} + + +/**************************************************************************/ +/* Error Codes */ +/**************************************************************************/ + +GQuark purple_keyring_error_domain(void) +{ + return g_quark_from_static_string("libpurple keyring"); +} + +/**************************************************************************/ +/* Keyring Subsystem */ +/**************************************************************************/ + +static void purple_keyring_core_initialized_cb(void) +{ + if (purple_keyring_inuse == NULL) { + purple_notify_error(NULL, _("Keyrings"), + _("Failed to load selected keyring."), + _("Check your system configuration or select another " + "one in Preferences dialog.")); + } +} + +static void purple_keyring_core_quitting_cb() +{ + if (current_change_tracker != NULL) { + PurpleKeyringChangeTracker *tracker = current_change_tracker; + tracker->abort = TRUE; + if (tracker->old) + purple_keyring_cancel_requests(tracker->old); + if (current_change_tracker == tracker && tracker->new) + purple_keyring_cancel_requests(tracker->new); + } + + purple_keyring_is_quitting = TRUE; + if (purple_keyring_inuse != NULL) + purple_keyring_cancel_requests(purple_keyring_inuse); +} + +void +purple_keyring_init(void) +{ + const gchar *touse; + GList *it; + + purple_keyring_keyrings = NULL; + purple_keyring_inuse = NULL; + + purple_keyring_failed_imports = g_hash_table_new_full(g_direct_hash, + g_direct_equal, NULL, + (GDestroyNotify)purple_keyring_failed_import_free); + + /* void keyring_register(const char *keyring_id, + * PurpleKeyring * keyring); + * + * A signal called when keyring is registered. + * + * @param keyring_id The keyring ID. + * @param keyring The keyring. + */ + purple_signal_register(purple_keyring_get_handle(), + "keyring-register", + purple_marshal_VOID__POINTER_POINTER, + NULL, 2, + purple_value_new(PURPLE_TYPE_STRING), + purple_value_new(PURPLE_TYPE_BOXED, "PurpleKeyring *")); + + /* void keyring_unregister(const char *keyring_id, + * PurpleKeyring * keyring); + * + * A signal called when keyring is unregistered. + * + * @param keyring_id The keyring ID. + * @param keyring The keyring. + */ + purple_signal_register(purple_keyring_get_handle(), + "keyring-unregister", + purple_marshal_VOID__POINTER_POINTER, + NULL, 2, + purple_value_new(PURPLE_TYPE_STRING), + purple_value_new(PURPLE_TYPE_BOXED, "PurpleKeyring *")); + + /* void password_migration(PurpleAccount* account); + * + * A signal called, when a password for the account was moved to another + * keyring. + * + * @param account The account. + */ + purple_signal_register(purple_keyring_get_handle(), + "password-migration", + purple_marshal_VOID__POINTER, + NULL, 1, + purple_value_new(PURPLE_TYPE_BOXED, "PurpleAccount *")); + + touse = purple_prefs_get_string("/purple/keyring/active"); + if (touse == NULL) { + purple_prefs_add_none("/purple/keyring"); + purple_prefs_add_string("/purple/keyring/active", + PURPLE_DEFAULT_KEYRING); + purple_keyring_to_use = g_strdup(PURPLE_DEFAULT_KEYRING); + } else + purple_keyring_to_use = g_strdup(touse); + + purple_keyring_pref_connect(); + + for (it = purple_plugins_get_all(); it != NULL; it = it->next) + { + PurplePlugin *plugin = (PurplePlugin *)it->data; + + if (plugin->info == NULL || plugin->info->id == NULL) + continue; + if (strncmp(plugin->info->id, "keyring-", 8) != 0) + continue; + + if (purple_plugin_is_loaded(plugin)) + continue; + + if (purple_plugin_load(plugin)) + { + purple_keyring_loaded_plugins = g_list_append( + purple_keyring_loaded_plugins, plugin); + } + } + + if (purple_keyring_inuse == NULL) + purple_debug_error("keyring", "Selected keyring failed to load\n"); + + purple_signal_connect(purple_get_core(), "core-initialized", + purple_keyring_get_handle(), + PURPLE_CALLBACK(purple_keyring_core_initialized_cb), NULL); + purple_signal_connect(purple_get_core(), "quitting", + purple_keyring_get_handle(), + PURPLE_CALLBACK(purple_keyring_core_quitting_cb), NULL); +} + +void +purple_keyring_uninit(void) +{ + GList *it; + + purple_keyring_pref_disconnect(); + + g_free(purple_keyring_to_use); + purple_keyring_inuse = NULL; + + g_hash_table_destroy(purple_keyring_failed_imports); + purple_keyring_failed_imports = NULL; + + for (it = g_list_first(purple_keyring_loaded_plugins); it != NULL; + it = g_list_next(it)) + { + PurplePlugin *plugin = (PurplePlugin *)it->data; + if (g_list_find(purple_plugins_get_loaded(), plugin) == NULL) + continue; + purple_plugin_unload(plugin); + } + g_list_free(purple_keyring_loaded_plugins); + purple_keyring_loaded_plugins = NULL; + + purple_signals_unregister_by_instance(purple_keyring_get_handle()); + purple_signals_disconnect_by_handle(purple_keyring_get_handle()); +} + +void * +purple_keyring_get_handle(void) +{ + static int handle; + + return &handle; +} diff --git a/libpurple/keyring.h b/libpurple/keyring.h new file mode 100644 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 f4ef632c65..27de0e140e 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_at) $(MAKE) -C $(PERL_PLUGIN) -f $(MINGW_MAKEFILE) $(MAKE_at) $(MAKE) -C $(TCL_PLUGIN) -f $(MINGW_MAKEFILE) $(MAKE_at) $(MAKE) -C $(SSL_PLUGIN) -f $(MINGW_MAKEFILE) + $(MAKE) -C $(KEYRING_PLUGIN) -f $(MINGW_MAKEFILE) install: all $(PURPLE_INSTALL_PLUGINS_DIR) $(MAKE_at) $(MAKE) -C $(PERL_PLUGIN) -f $(MINGW_MAKEFILE) install $(MAKE_at) $(MAKE) -C $(TCL_PLUGIN) -f $(MINGW_MAKEFILE) install $(MAKE_at) $(MAKE) -C $(SSL_PLUGIN) -f $(MINGW_MAKEFILE) install + $(MAKE) -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_at) $(MAKE) -C $(PERL_PLUGIN) -f $(MINGW_MAKEFILE) clean $(MAKE_at) $(MAKE) -C $(TCL_PLUGIN) -f $(MINGW_MAKEFILE) clean $(MAKE_at) $(MAKE) -C $(SSL_PLUGIN) -f $(MINGW_MAKEFILE) clean + $(MAKE) -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..d734dc6b97 --- /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..7da6b79c35 --- /dev/null +++ b/libpurple/plugins/keyrings/internalkeyring.c @@ -0,0 +1,1037 @@ +/** + * @file internalkeyring.c internal keyring + * @ingroup plugins + */ + +/* purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program ; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" +#include "account.h" +#include "cipher.h" +#include "debug.h" +#include "keyring.h" +#include "plugin.h" +#include "version.h" + +#define INTKEYRING_NAME N_("Internal keyring") +#define INTKEYRING_DESCRIPTION N_("This plugin provides the default password " \ + "storage behaviour for libpurple.") +#define INTKEYRING_AUTHOR "Tomek Wasilczyk (tomkiewicz@cpw.pidgin.im)" +#define INTKEYRING_ID PURPLE_DEFAULT_KEYRING + +#define INTKEYRING_VERIFY_STR "[verification-string]" +#define INTKEYRING_PBKDF2_ITERATIONS 10000 +#define INTKEYRING_PBKDF2_ITERATIONS_MIN 1000 +#define INTKEYRING_PBKDF2_ITERATIONS_MAX 1000000000 +#define INTKEYRING_KEY_LEN (256/8) +#define INTKEYRING_ENCRYPT_BUFF_LEN 1000 +#define INTKEYRING_ENCRYPTED_MIN_LEN 50 +#define INTKEYRING_ENCRYPTION_METHOD "pbkdf2-sha256-aes256" + +#define INTKEYRING_PREFS "/plugins/keyrings/internal/" + +/* win32 build defines such macro to override read() routine */ +#undef read + +typedef struct +{ + enum + { + INTKEYRING_REQUEST_READ, + INTKEYRING_REQUEST_SAVE + } type; + PurpleAccount *account; + gchar *password; + union + { + PurpleKeyringReadCallback read; + PurpleKeyringSaveCallback save; + } cb; + gpointer cb_data; +} intkeyring_request; + +typedef struct +{ + guchar *data; + size_t len; +} intkeyring_buff_t; + +static intkeyring_buff_t *intkeyring_key; +static GHashTable *intkeyring_passwords = NULL; +static GHashTable *intkeyring_ciphertexts = NULL; + +static gboolean intkeyring_opened = FALSE; +static gboolean intkeyring_unlocked = FALSE; + +static GList *intkeyring_pending_requests = NULL; +static void *intkeyring_masterpw_uirequest = NULL; + +static PurpleKeyring *keyring_handler = NULL; + +static void +intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb, + gpointer data); +static void +intkeyring_save(PurpleAccount *account, const gchar *password, + PurpleKeyringSaveCallback cb, gpointer data); +static void +intkeyring_reencrypt_passwords(void); +static void +intkeyring_unlock(const gchar *message); + +static void +intkeyring_request_free(intkeyring_request *req) +{ + g_return_if_fail(req != NULL); + + purple_str_wipe(req->password); + g_free(req); +} + +static intkeyring_buff_t * +intkeyring_buff_new(guchar *data, size_t len) +{ + intkeyring_buff_t *ret = g_new(intkeyring_buff_t, 1); + + ret->data = data; + ret->len = len; + + return ret; +} + +static void +intkeyring_buff_free(intkeyring_buff_t *buff) +{ + if (buff == NULL) + return; + + memset(buff->data, 0, buff->len); + g_free(buff->data); + g_free(buff); +} + +static intkeyring_buff_t * +intkeyring_buff_from_base64(const gchar *base64) +{ + guchar *data; + gsize len; + + data = purple_base64_decode(base64, &len); + + return intkeyring_buff_new(data, len); +} + +/************************************************************************/ +/* Generic encryption stuff */ +/************************************************************************/ + +static intkeyring_buff_t * +intkeyring_derive_key(const gchar *passphrase, intkeyring_buff_t *salt) +{ + PurpleCipherContext *context; + gboolean succ; + intkeyring_buff_t *ret; + + g_return_val_if_fail(passphrase != NULL, NULL); + + context = purple_cipher_context_new_by_name("pbkdf2", NULL); + g_return_val_if_fail(context != NULL, NULL); + + purple_cipher_context_set_option(context, "hash", "sha256"); + purple_cipher_context_set_option(context, "iter_count", + GUINT_TO_POINTER(purple_prefs_get_int(INTKEYRING_PREFS + "pbkdf2_iterations"))); + purple_cipher_context_set_option(context, "out_len", GUINT_TO_POINTER( + INTKEYRING_KEY_LEN)); + purple_cipher_context_set_salt(context, salt->data, salt->len); + purple_cipher_context_set_key(context, (const guchar*)passphrase, + strlen(passphrase)); + + ret = intkeyring_buff_new(g_new(guchar, INTKEYRING_KEY_LEN), + INTKEYRING_KEY_LEN); + succ = purple_cipher_context_digest(context, ret->data, ret->len); + + purple_cipher_context_destroy(context); + + if (!succ) { + intkeyring_buff_free(ret); + return NULL; + } + + return ret; +} + +static intkeyring_buff_t * +intkeyring_gen_salt(size_t len) +{ + intkeyring_buff_t *ret; + size_t filled = 0; + + g_return_val_if_fail(len > 0, NULL); + + ret = intkeyring_buff_new(g_new(guchar, len), len); + + while (filled < len) { + guint32 r = g_random_int(); + int i; + + for (i = 0; i < 4; i++) { + ret->data[filled++] = r & 0xFF; + if (filled >= len) + break; + r >>= 8; + } + } + + return ret; +} + +/** + * Encrypts a plaintext using the specified key. + * + * Random IV will be generated and stored with ciphertext. + * + * Encryption scheme: + * [ IV ] ++ AES( [ plaintext ] ++ [ min length padding ] ++ + * [ control string ] ++ [ pkcs7 padding ] ) + * where: + * IV: Random, 128bit IV. + * plaintext: The plaintext. + * min length padding: The padding used to hide the rough length of short + * plaintexts, may have a length of 0. + * control string: Constant string, verifies corectness of decryption. + * pkcs7 padding: The padding used to determine total length of encrypted + * content (also provides some verification). + * + * @param key The AES key. + * @param str The NUL-terminated plaintext. + * @return The ciphertext with IV, encoded as base64. Must be g_free'd. + */ +static gchar * +intkeyring_encrypt(intkeyring_buff_t *key, const gchar *str) +{ + PurpleCipherContext *context; + intkeyring_buff_t *iv; + guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN]; + size_t plaintext_len, text_len, verify_len; + guchar encrypted_raw[INTKEYRING_ENCRYPT_BUFF_LEN]; + ssize_t encrypted_size; + + g_return_val_if_fail(key != NULL, NULL); + g_return_val_if_fail(str != NULL, NULL); + + text_len = strlen(str); + verify_len = strlen(INTKEYRING_VERIFY_STR); + plaintext_len = INTKEYRING_ENCRYPTED_MIN_LEN; + if (plaintext_len < text_len) + plaintext_len = text_len; + + g_return_val_if_fail(plaintext_len + verify_len <= sizeof(plaintext), + NULL); + + context = purple_cipher_context_new_by_name("aes", NULL); + g_return_val_if_fail(context != NULL, NULL); + + memset(plaintext, 0, plaintext_len); + memcpy(plaintext, str, text_len); + memcpy(plaintext + plaintext_len, INTKEYRING_VERIFY_STR, verify_len); + plaintext_len += verify_len; + + iv = intkeyring_gen_salt(purple_cipher_context_get_block_size(context)); + purple_cipher_context_set_iv(context, iv->data, iv->len); + purple_cipher_context_set_key(context, key->data, key->len); + purple_cipher_context_set_batch_mode(context, + PURPLE_CIPHER_BATCH_MODE_CBC); + + memcpy(encrypted_raw, iv->data, iv->len); + + encrypted_size = purple_cipher_context_encrypt(context, + plaintext, plaintext_len, encrypted_raw + iv->len, + sizeof(encrypted_raw) - iv->len); + encrypted_size += iv->len; + + memset(plaintext, 0, plaintext_len); + intkeyring_buff_free(iv); + purple_cipher_context_destroy(context); + + if (encrypted_size < 0) + return NULL; + + return purple_base64_encode(encrypted_raw, encrypted_size); + +} + +static gchar * +intkeyring_decrypt(intkeyring_buff_t *key, const gchar *str) +{ + PurpleCipherContext *context; + guchar *encrypted_raw; + gsize encrypted_size; + size_t iv_len, verify_len, text_len; + guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN]; + ssize_t plaintext_len; + gchar *ret; + + g_return_val_if_fail(key != NULL, NULL); + g_return_val_if_fail(str != NULL, NULL); + + context = purple_cipher_context_new_by_name("aes", NULL); + g_return_val_if_fail(context != NULL, NULL); + + encrypted_raw = purple_base64_decode(str, &encrypted_size); + g_return_val_if_fail(encrypted_raw != NULL, NULL); + + iv_len = purple_cipher_context_get_block_size(context); + if (encrypted_size < iv_len) { + g_free(encrypted_raw); + return NULL; + } + + purple_cipher_context_set_iv(context, encrypted_raw, iv_len); + purple_cipher_context_set_key(context, key->data, key->len); + purple_cipher_context_set_batch_mode(context, + PURPLE_CIPHER_BATCH_MODE_CBC); + + plaintext_len = purple_cipher_context_decrypt(context, + encrypted_raw + iv_len, encrypted_size - iv_len, + plaintext, sizeof(plaintext)); + + g_free(encrypted_raw); + purple_cipher_context_destroy(context); + + verify_len = strlen(INTKEYRING_VERIFY_STR); + if (plaintext_len < verify_len || strncmp( + (gchar*)plaintext + plaintext_len - verify_len, + INTKEYRING_VERIFY_STR, verify_len) != 0) { + purple_debug_warning("keyring-internal", + "Verification failed on decryption\n"); + memset(plaintext, 0, sizeof(plaintext)); + return NULL; + } + + text_len = plaintext_len - verify_len; + ret = g_new(gchar, text_len + 1); + memcpy(ret, plaintext, text_len); + memset(plaintext, 0, plaintext_len); + ret[text_len] = '\0'; + + return ret; +} + +/************************************************************************/ +/* Password encryption */ +/************************************************************************/ + +static gboolean +intkeyring_change_masterpw(const gchar *new_password) +{ + intkeyring_buff_t *salt, *key; + gchar *verifier = NULL, *salt_b64 = NULL; + int old_iter; + gboolean succ = TRUE;; + + g_return_val_if_fail(intkeyring_unlocked, FALSE); + + old_iter = purple_prefs_get_int(INTKEYRING_PREFS "pbkdf2_iterations"); + purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_iterations", + purple_prefs_get_int(INTKEYRING_PREFS + "pbkdf2_desired_iterations")); + + salt = intkeyring_gen_salt(32); + key = intkeyring_derive_key(new_password, salt); + + if (salt && key && key->len == INTKEYRING_KEY_LEN) { + /* In fact, verify str will be concatenated twice before + * encryption (it's used as a suffix in encryption routine), + * but it's not a problem. + */ + verifier = intkeyring_encrypt(key, INTKEYRING_VERIFY_STR); + salt_b64 = purple_base64_encode(salt->data, salt->len); + } + + if (!verifier || !salt_b64) { + purple_debug_error("keyring-internal", "Failed to change " + "master password\n"); + succ = FALSE; + purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_iterations", + old_iter); + } else { + purple_prefs_set_string(INTKEYRING_PREFS "pbkdf2_salt", + salt_b64); + purple_prefs_set_string(INTKEYRING_PREFS "key_verifier", + verifier); + + intkeyring_buff_free(intkeyring_key); + intkeyring_key = key; + key = NULL; + + intkeyring_reencrypt_passwords(); + + purple_signal_emit(purple_keyring_get_handle(), + "password-migration", NULL); + } + + g_free(salt_b64); + g_free(verifier); + intkeyring_buff_free(salt); + intkeyring_buff_free(key); + + return succ; +} + +static void +intkeyring_process_queue(void) +{ + GList *requests, *it; + gboolean open = intkeyring_unlocked; + + requests = g_list_first(intkeyring_pending_requests); + intkeyring_pending_requests = NULL; + + for (it = requests; it != NULL; it = g_list_next(it)) + { + intkeyring_request *req = it->data; + + if (open && req->type == INTKEYRING_REQUEST_READ) { + intkeyring_read(req->account, req->cb.read, + req->cb_data); + } else if (open && req->type == INTKEYRING_REQUEST_SAVE) { + intkeyring_save(req->account, req->password, + req->cb.save, req->cb_data); + } else if (open) + g_assert_not_reached(); + else if (req->cb.read != NULL /* || req->cb.write != NULL */ ) { + GError *error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_CANCELLED, + _("Operation cancelled.")); + if (req->type == INTKEYRING_REQUEST_READ) { + req->cb.read(req->account, NULL, error, + req->cb_data); + } else if (req->type == INTKEYRING_REQUEST_SAVE) + req->cb.save(req->account, error, req->cb_data); + else + g_assert_not_reached(); + g_error_free(error); + } + + intkeyring_request_free(req); + } + g_list_free(requests); +} + +static void +intkeyring_decrypt_password(PurpleAccount *account, const gchar *ciphertext) +{ + gchar *plaintext; + + plaintext = intkeyring_decrypt(intkeyring_key, ciphertext); + if (plaintext == NULL) { + purple_debug_warning("keyring-internal", + "Failed to decrypt a password\n"); + return; + } + + g_hash_table_replace(intkeyring_passwords, account, plaintext); +} + +static void +intkeyring_encrypt_password_if_needed(PurpleAccount *account) +{ + const gchar *plaintext; + gchar *ciphertext; + + ciphertext = g_hash_table_lookup(intkeyring_ciphertexts, account); + if (ciphertext != NULL) + return; + + plaintext = g_hash_table_lookup(intkeyring_passwords, account); + if (plaintext == NULL) + return; + + ciphertext = intkeyring_encrypt(intkeyring_key, plaintext); + g_return_if_fail(ciphertext != NULL); + + g_hash_table_replace(intkeyring_ciphertexts, account, ciphertext); +} + +static void +intkeyring_encrypt_passwords_if_needed_it(gpointer account, gpointer plaintext, + gpointer _unused) +{ + intkeyring_encrypt_password_if_needed(account); +} + +static void +intkeyring_reencrypt_passwords(void) +{ + g_hash_table_remove_all(intkeyring_ciphertexts); + g_hash_table_foreach(intkeyring_passwords, + intkeyring_encrypt_passwords_if_needed_it, NULL); +} + +static void +intkeyring_unlock_decrypt(gpointer account, gpointer ciphertext, + gpointer _unused) +{ + intkeyring_decrypt_password(account, ciphertext); +} + +/************************************************************************/ +/* Opening and unlocking keyring */ +/************************************************************************/ + +static void +intkeyring_unlock_ok(gpointer _unused, + PurpleRequestFields *fields) +{ + const gchar *masterpw; + gchar *verifier; + intkeyring_buff_t *salt, *key; + + intkeyring_masterpw_uirequest = NULL; + + if (g_strcmp0(purple_prefs_get_string(INTKEYRING_PREFS + "encryption_method"), INTKEYRING_ENCRYPTION_METHOD) != 0) { + purple_notify_error(NULL, + _("Unlocking internal keyring"), + _("Selected encryption method is not supported."), + _("Most probably, your passwords were encrypted with " + "newer Pidgin/libpurple version, please update.")); + return; + } + + masterpw = purple_request_fields_get_string(fields, "password"); + + if (masterpw == NULL || masterpw[0] == '\0') { + intkeyring_unlock(_("No password entered.")); + return; + } + + salt = intkeyring_buff_from_base64(purple_prefs_get_string( + INTKEYRING_PREFS "pbkdf2_salt")); + key = intkeyring_derive_key(masterpw, salt); + intkeyring_buff_free(salt); + + verifier = intkeyring_decrypt(key, purple_prefs_get_string( + INTKEYRING_PREFS "key_verifier")); + + if (g_strcmp0(verifier, INTKEYRING_VERIFY_STR) != 0) { + g_free(verifier); + intkeyring_buff_free(key); + intkeyring_unlock(_("Invalid master password entered, " + "try again.")); + return; + } + + g_free(verifier); + intkeyring_key = key; + intkeyring_unlocked = TRUE; + + g_hash_table_foreach(intkeyring_ciphertexts, + intkeyring_unlock_decrypt, NULL); + + intkeyring_process_queue(); +} + +static void +intkeyring_unlock_cancel(gpointer _unused, + PurpleRequestFields *fields) +{ + intkeyring_masterpw_uirequest = NULL; + intkeyring_process_queue(); +} + +static void +intkeyring_unlock(const gchar *message) +{ + PurpleRequestFields *fields; + PurpleRequestFieldGroup *group; + PurpleRequestField *field; + const gchar *primary_msg, *secondary_msg = NULL; + + if (intkeyring_unlocked || intkeyring_masterpw_uirequest != NULL) + return; + + if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) { + intkeyring_unlocked = TRUE; + intkeyring_process_queue(); + return; + } + + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + + field = purple_request_field_string_new("password", + _("Master password"), "", FALSE); + purple_request_field_string_set_masked(field, TRUE); + purple_request_field_group_add_field(group, field); + + primary_msg = _("Please, enter master password"); + if (message) { + secondary_msg = primary_msg; + primary_msg = message; + } + + intkeyring_masterpw_uirequest = purple_request_fields(NULL, + _("Unlocking internal keyring"), + primary_msg, secondary_msg, fields, + _("OK"), G_CALLBACK(intkeyring_unlock_ok), + _("Cancel"), G_CALLBACK(intkeyring_unlock_cancel), + NULL, NULL, NULL, NULL); +} + +static void +intkeyring_open(void) +{ + if (intkeyring_opened) + return; + intkeyring_opened = TRUE; + + intkeyring_passwords = g_hash_table_new_full(g_direct_hash, + g_direct_equal, NULL, (GDestroyNotify)purple_str_wipe); + intkeyring_ciphertexts = g_hash_table_new_full(g_direct_hash, + g_direct_equal, NULL, g_free); +} + +/************************************************************************/ +/* Keyring interface implementation */ +/************************************************************************/ + +static void +intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb, + gpointer data) +{ + const char *password; + GError *error; + + intkeyring_open(); + + if (!intkeyring_unlocked && g_hash_table_lookup(intkeyring_ciphertexts, + account) != NULL) + { + intkeyring_request *req = g_new0(intkeyring_request, 1); + + req->type = INTKEYRING_REQUEST_READ; + req->account = account; + req->cb.read = cb; + req->cb_data = data; + intkeyring_pending_requests = + g_list_append(intkeyring_pending_requests, req); + + intkeyring_unlock(NULL); + return; + } + + password = g_hash_table_lookup(intkeyring_passwords, account); + + if (password != NULL) { + purple_debug_misc("keyring-internal", + "Got password for account %s (%s).\n", + purple_account_get_username(account), + purple_account_get_protocol_id(account)); + if (cb != NULL) + cb(account, password, NULL, data); + } else { + if (purple_debug_is_verbose()) { + purple_debug_misc("keyring-internal", + "No password for account %s (%s).\n", + purple_account_get_username(account), + purple_account_get_protocol_id(account)); + } + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_NOPASSWORD, + _("Password not found.")); + if (cb != NULL) + cb(account, NULL, error, data); + g_error_free(error); + } +} + +static void +intkeyring_save(PurpleAccount *account, const gchar *password, + PurpleKeyringSaveCallback cb, gpointer data) +{ + void *old_password; + + intkeyring_open(); + + if (!intkeyring_unlocked) { + intkeyring_request *req; + + if (password == NULL) { + g_hash_table_remove(intkeyring_ciphertexts, account); + g_hash_table_remove(intkeyring_passwords, account); + if (cb) + cb(account, NULL, data); + return; + } + + req = g_new0(intkeyring_request, 1); + req->type = INTKEYRING_REQUEST_SAVE; + req->account = account; + req->password = g_strdup(password); + req->cb.save = cb; + req->cb_data = data; + intkeyring_pending_requests = + g_list_append(intkeyring_pending_requests, req); + + intkeyring_unlock(NULL); + return; + } + + g_hash_table_remove(intkeyring_ciphertexts, account); + + old_password = g_hash_table_lookup(intkeyring_passwords, account); + + if (password == NULL) + g_hash_table_remove(intkeyring_passwords, account); + else { + g_hash_table_replace(intkeyring_passwords, account, + g_strdup(password)); + } + + intkeyring_encrypt_password_if_needed(account); + + if (!(password == NULL && old_password == NULL)) { + purple_debug_misc("keyring-internal", + "Password %s for account %s (%s).\n", + (password == NULL ? "removed" : (old_password == NULL ? + "saved" : "updated")), + purple_account_get_username(account), + purple_account_get_protocol_id(account)); + } else if (purple_debug_is_verbose()) { + purple_debug_misc("keyring-internal", + "Password for account %s (%s) was already removed.\n", + purple_account_get_username(account), + purple_account_get_protocol_id(account)); + } + + if (cb != NULL) + cb(account, NULL, data); +} + +static void +intkeyring_close(void) +{ + if (!intkeyring_opened) + return; + intkeyring_opened = FALSE; + intkeyring_unlocked = FALSE; + + if (intkeyring_masterpw_uirequest) { + purple_request_close(PURPLE_REQUEST_FIELDS, + intkeyring_masterpw_uirequest); + } + g_warn_if_fail(intkeyring_masterpw_uirequest == NULL); + g_warn_if_fail(intkeyring_pending_requests == NULL); + + intkeyring_buff_free(intkeyring_key); + intkeyring_key = NULL; + g_hash_table_destroy(intkeyring_passwords); + intkeyring_passwords = NULL; + g_hash_table_destroy(intkeyring_ciphertexts); + intkeyring_ciphertexts = NULL; +} + +static gboolean +intkeyring_import_password(PurpleAccount *account, const char *mode, + const char *data, GError **error) +{ + g_return_val_if_fail(account != NULL, FALSE); + g_return_val_if_fail(data != NULL, FALSE); + + intkeyring_open(); + + if (mode == NULL) + mode = "cleartext"; + + if (g_strcmp0(mode, "cleartext") == 0) { + g_hash_table_replace(intkeyring_passwords, account, + g_strdup(data)); + return TRUE; + } else if (g_strcmp0(mode, "ciphertext") == 0) { + if (intkeyring_unlocked) + intkeyring_decrypt_password(account, data); + else { + g_hash_table_replace(intkeyring_ciphertexts, account, + g_strdup(data)); + } + return TRUE; + } else { + if (error != NULL) { + *error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_BACKENDFAIL, + _("Invalid password storage mode.")); + } + return FALSE; + } +} + +static gboolean +intkeyring_export_password(PurpleAccount *account, const char **mode, + char **data, GError **error, GDestroyNotify *destroy) +{ + gchar *ciphertext = NULL; + intkeyring_open(); + + if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) { + gchar *cleartext = g_hash_table_lookup(intkeyring_passwords, + account); + + if (cleartext == NULL) + return FALSE; + + *mode = "cleartext"; + *data = g_strdup(cleartext); + *destroy = (GDestroyNotify)purple_str_wipe; + return TRUE; + } + + ciphertext = g_strdup(g_hash_table_lookup(intkeyring_ciphertexts, + account)); + + if (ciphertext == NULL && intkeyring_unlocked) { + gchar *plaintext = g_hash_table_lookup(intkeyring_passwords, + account); + + if (plaintext == NULL) + return FALSE; + + purple_debug_warning("keyring-internal", "Encrypted password " + "is missing at export (it shouldn't happen)\n"); + ciphertext = intkeyring_encrypt(intkeyring_key, plaintext); + } + + if (ciphertext == NULL) + return FALSE; + + *mode = "ciphertext"; + *data = ciphertext; + *destroy = (GDestroyNotify)g_free; + return TRUE; +} + +static PurpleRequestFields * +intkeyring_read_settings(void) +{ + PurpleRequestFields *fields; + PurpleRequestFieldGroup *group; + PurpleRequestField *field; + + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + + field = purple_request_field_bool_new("encrypt_passwords", + _("Encrypt passwords"), purple_prefs_get_bool( + INTKEYRING_PREFS "encrypt_passwords")); + purple_request_field_group_add_field(group, field); + + group = purple_request_field_group_new(_("Master password")); + purple_request_fields_add_group(fields, group); + + field = purple_request_field_string_new("passphrase1", + _("New passphrase:"), "", FALSE); + purple_request_field_string_set_masked(field, TRUE); + purple_request_field_group_add_field(group, field); + + field = purple_request_field_string_new("passphrase2", + _("New passphrase (again):"), "", FALSE); + purple_request_field_string_set_masked(field, TRUE); + purple_request_field_group_add_field(group, field); + + group = purple_request_field_group_new(_("Advanced settings")); + purple_request_fields_add_group(fields, group); + + field = purple_request_field_int_new("pbkdf2_desired_iterations", + _("Number of PBKDF2 iterations:"), purple_prefs_get_int( + INTKEYRING_PREFS "pbkdf2_desired_iterations"), + INTKEYRING_PBKDF2_ITERATIONS_MIN, + INTKEYRING_PBKDF2_ITERATIONS_MAX); + purple_request_field_group_add_field(group, field); + + return fields; +} + +static gboolean +intkeyring_apply_settings(void *notify_handle, + PurpleRequestFields *fields) +{ + const gchar *passphrase, *passphrase2; + + intkeyring_unlock(_("You have to unlock the keyring first.")); + if (!intkeyring_unlocked) + return FALSE; + + passphrase = purple_request_fields_get_string(fields, "passphrase1"); + if (g_strcmp0(passphrase, "") == 0) + passphrase = NULL; + passphrase2 = purple_request_fields_get_string(fields, "passphrase2"); + if (g_strcmp0(passphrase2, "") == 0) + passphrase2 = NULL; + + if (g_strcmp0(passphrase, passphrase2) != 0) { + purple_notify_error(notify_handle, + _("Internal keyring settings"), + _("Passphrases do not match"), NULL); + return FALSE; + } + + if (purple_request_fields_get_bool(fields, "encrypt_passwords") && + !passphrase && !intkeyring_key) + { + purple_notify_error(notify_handle, + _("Internal keyring settings"), + _("You have to set up a Master password, if you want " + "to enable encryption"), NULL); + return FALSE; + } + + if (!purple_request_fields_get_bool(fields, "encrypt_passwords") && + passphrase) + { + purple_notify_error(notify_handle, + _("Internal keyring settings"), + _("You don't need any master password, if you won't " + "enable passwords encryption"), NULL); + return FALSE; + } + + purple_prefs_set_string(INTKEYRING_PREFS "encryption_method", + INTKEYRING_ENCRYPTION_METHOD); + + purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_desired_iterations", + purple_request_fields_get_integer(fields, + "pbkdf2_desired_iterations")); + + if (passphrase != NULL) { + if (!intkeyring_change_masterpw(passphrase)) + return FALSE; + } + + purple_prefs_set_bool(INTKEYRING_PREFS "encrypt_passwords", + purple_request_fields_get_bool(fields, "encrypt_passwords")); + + purple_signal_emit(purple_keyring_get_handle(), "password-migration", + NULL); + + + return TRUE; +} + +static gboolean +intkeyring_load(PurplePlugin *plugin) +{ + keyring_handler = purple_keyring_new(); + + purple_keyring_set_name(keyring_handler, INTKEYRING_NAME); + purple_keyring_set_id(keyring_handler, INTKEYRING_ID); + purple_keyring_set_read_password(keyring_handler, + intkeyring_read); + purple_keyring_set_save_password(keyring_handler, + intkeyring_save); + purple_keyring_set_close_keyring(keyring_handler, + intkeyring_close); + purple_keyring_set_import_password(keyring_handler, + intkeyring_import_password); + purple_keyring_set_export_password(keyring_handler, + intkeyring_export_password); + purple_keyring_set_read_settings(keyring_handler, + intkeyring_read_settings); + purple_keyring_set_apply_settings(keyring_handler, + intkeyring_apply_settings); + + purple_keyring_register(keyring_handler); + + return TRUE; +} + +static gboolean +intkeyring_unload(PurplePlugin *plugin) +{ + if (purple_keyring_get_inuse() == keyring_handler) { + purple_debug_warning("keyring-internal", + "keyring in use, cannot unload\n"); + return FALSE; + } + + intkeyring_close(); + + purple_keyring_unregister(keyring_handler); + purple_keyring_free(keyring_handler); + keyring_handler = NULL; + + if (intkeyring_key != NULL) { + purple_debug_warning("keyring-internal", "Master key should be " + "cleaned up at this point\n"); + intkeyring_buff_free(intkeyring_key); + intkeyring_key = NULL; + } + + return TRUE; +} + +PurplePluginInfo plugininfo = +{ + PURPLE_PLUGIN_MAGIC, /* magic */ + PURPLE_MAJOR_VERSION, /* major_version */ + PURPLE_MINOR_VERSION, /* minor_version */ + PURPLE_PLUGIN_STANDARD, /* type */ + NULL, /* ui_requirement */ + PURPLE_PLUGIN_FLAG_INVISIBLE, /* flags */ + NULL, /* dependencies */ + PURPLE_PRIORITY_DEFAULT, /* priority */ + INTKEYRING_ID, /* id */ + INTKEYRING_NAME, /* name */ + DISPLAY_VERSION, /* version */ + "Internal Keyring Plugin", /* summary */ + INTKEYRING_DESCRIPTION, /* description */ + INTKEYRING_AUTHOR, /* author */ + PURPLE_WEBSITE, /* homepage */ + intkeyring_load, /* load */ + intkeyring_unload, /* unload */ + NULL, /* destroy */ + NULL, /* ui_info */ + NULL, /* extra_info */ + NULL, /* prefs_info */ + NULL, /* actions */ + NULL, NULL, NULL, NULL /* padding */ +}; + +static void +init_plugin(PurplePlugin *plugin) +{ + purple_prefs_add_none("/plugins/keyrings"); + purple_prefs_add_none("/plugins/keyrings/internal"); + purple_prefs_add_bool(INTKEYRING_PREFS "encrypt_passwords", FALSE); + purple_prefs_add_string(INTKEYRING_PREFS "encryption_method", + INTKEYRING_ENCRYPTION_METHOD); + purple_prefs_add_int(INTKEYRING_PREFS "pbkdf2_desired_iterations", + INTKEYRING_PBKDF2_ITERATIONS); + purple_prefs_add_int(INTKEYRING_PREFS "pbkdf2_iterations", + INTKEYRING_PBKDF2_ITERATIONS); + purple_prefs_add_string(INTKEYRING_PREFS "pbkdf2_salt", ""); + purple_prefs_add_string(INTKEYRING_PREFS "key_verifier", ""); +} + +PURPLE_INIT_PLUGIN(internal_keyring, init_plugin, plugininfo) diff --git a/libpurple/plugins/keyrings/kwallet.cpp b/libpurple/plugins/keyrings/kwallet.cpp new file mode 100644 index 0000000000..86a26f0aa2 --- /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..53faa2810f --- /dev/null +++ b/libpurple/plugins/keyrings/secretservice.c @@ -0,0 +1,337 @@ +/* purple + * @file secretservice.c Secret Service password storage + * @ingroup plugins + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program ; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#error "This keyring needs some more work (see TODO)" + +/* TODO + * + * This keyring needs some more work, so it will be disabled until its quality + * was raised. Some of the pain points: + * - throws a lot of g_warnings + * - it doesn't notify about some backend faults (like access denied), some of + * them are not handled at all + * - it could use libsecret's Complete API + * - code formatting could be better + */ + +#include "internal.h" +#include "account.h" +#include "debug.h" +#include "keyring.h" +#include "plugin.h" +#include "version.h" + +#include <libsecret/secret.h> + +#define SECRETSERVICE_NAME N_("Secret Service") +#define SECRETSERVICE_ID "keyring-libsecret" + +static PurpleKeyring *keyring_handler = NULL; + +static const SecretSchema purple_schema = { + "im.pidgin.Purple", SECRET_SCHEMA_NONE, + { + {"user", SECRET_SCHEMA_ATTRIBUTE_STRING}, + {"protocol", SECRET_SCHEMA_ATTRIBUTE_STRING}, + {"NULL", 0} + }, + /* Reserved fields */ + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +typedef struct _InfoStorage InfoStorage; + +struct _InfoStorage +{ + PurpleAccount *account; + gpointer cb; + gpointer user_data; +}; + +/***********************************************/ +/* Keyring interface */ +/***********************************************/ +static void +ss_read_continue(GObject *object, GAsyncResult *result, gpointer data) +{ + InfoStorage *storage = data; + PurpleAccount *account = storage->account; + PurpleKeyringReadCallback cb = storage->cb; + char *password; + GError *error = NULL; + + password = secret_password_lookup_finish(result, &error); + + if (error != NULL) { + int code = error->code; + + switch (code) { + case G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND: + case G_DBUS_ERROR_IO_ERROR: + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_BACKENDFAIL, + "Failed to communicate with Secret Service (account : %s).", + purple_account_get_username(account)); + if (cb != NULL) + cb(account, NULL, error, storage->user_data); + g_error_free(error); + break; + + default: + purple_debug_error("keyring-libsecret", + "Unknown error (account: %s (%s), domain: %s, code: %d): %s.\n", + purple_account_get_username(account), + purple_account_get_protocol_id(account), + g_quark_to_string(error->domain), code, error->message); + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_BACKENDFAIL, + "Unknown error (account : %s).", + purple_account_get_username(account)); + if (cb != NULL) + cb(account, NULL, error, storage->user_data); + g_error_free(error); + break; + } + + } else if (password == NULL) { + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_NOPASSWORD, + "No password found for account: %s", + purple_account_get_username(account)); + if (cb != NULL) + cb(account, NULL, error, storage->user_data); + g_error_free(error); + + } else { + if (cb != NULL) + cb(account, password, NULL, storage->user_data); + } + + g_free(storage); +} + +static void +ss_read(PurpleAccount *account, PurpleKeyringReadCallback cb, gpointer data) +{ + InfoStorage *storage = g_new0(InfoStorage, 1); + + storage->account = account; + storage->cb = cb; + storage->user_data = data; + + secret_password_lookup(&purple_schema, + NULL, ss_read_continue, storage, + "user", purple_account_get_username(account), + "protocol", purple_account_get_protocol_id(account), + NULL); +} + +static void +ss_save_continue(GObject *object, GAsyncResult *result, gpointer data) +{ + InfoStorage *storage = data; + PurpleKeyringSaveCallback cb; + GError *error = NULL; + PurpleAccount *account; + + account = storage->account; + cb = storage->cb; + + secret_password_store_finish(result, &error); + + if (error != NULL) { + int code = error->code; + + switch (code) { + case G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND: + case G_DBUS_ERROR_IO_ERROR: + purple_debug_info("keyring-libsecret", + "Failed to communicate with Secret Service (account : %s (%s)).\n", + purple_account_get_username(account), + purple_account_get_protocol_id(account)); + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_BACKENDFAIL, + "Failed to communicate with Secret Service (account : %s).", + purple_account_get_username(account)); + if (cb != NULL) + cb(account, error, storage->user_data); + g_error_free(error); + break; + + default: + purple_debug_error("keyring-libsecret", + "Unknown error (account: %s (%s), domain: %s, code: %d): %s.\n", + purple_account_get_username(account), + purple_account_get_protocol_id(account), + g_quark_to_string(error->domain), code, error->message); + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_BACKENDFAIL, + "Unknown error (account : %s).", + purple_account_get_username(account)); + if (cb != NULL) + cb(account, error, storage->user_data); + g_error_free(error); + break; + } + + } else { + purple_debug_info("keyring-libsecret", "Password for %s updated.\n", + purple_account_get_username(account)); + + if (cb != NULL) + cb(account, NULL, storage->user_data); + } + + g_free(storage); +} + +static void +ss_save(PurpleAccount *account, + const gchar *password, + PurpleKeyringSaveCallback cb, + gpointer data) +{ + InfoStorage *storage = g_new0(InfoStorage, 1); + + storage->account = account; + storage->cb = cb; + storage->user_data = data; + + if (password != NULL && *password != '\0') { + const char *username = purple_account_get_username(account); + char *label; + + purple_debug_info("keyring-libsecret", + "Updating password for account %s (%s).\n", + username, purple_account_get_protocol_id(account)); + + label = g_strdup_printf(_("Pidgin IM password for account %s"), username); + secret_password_store(&purple_schema, SECRET_COLLECTION_DEFAULT, + label, password, + NULL, ss_save_continue, storage, + "user", username, + "protocol", purple_account_get_protocol_id(account), + NULL); + g_free(label); + + } else { /* password == NULL, delete password. */ + purple_debug_info("keyring-libsecret", + "Forgetting password for account %s (%s).\n", + purple_account_get_username(account), + purple_account_get_protocol_id(account)); + + secret_password_clear(&purple_schema, NULL, ss_save_continue, storage, + "user", purple_account_get_username(account), + "protocol", purple_account_get_protocol_id(account), + NULL); + } +} + +static void +ss_close(void) +{ +} + +static gboolean +ss_init(void) +{ + keyring_handler = purple_keyring_new(); + + purple_keyring_set_name(keyring_handler, SECRETSERVICE_NAME); + purple_keyring_set_id(keyring_handler, SECRETSERVICE_ID); + purple_keyring_set_read_password(keyring_handler, ss_read); + purple_keyring_set_save_password(keyring_handler, ss_save); + purple_keyring_set_close_keyring(keyring_handler, ss_close); + + purple_keyring_register(keyring_handler); + + return TRUE; +} + +static void +ss_uninit(void) +{ + ss_close(); + purple_keyring_unregister(keyring_handler); + purple_keyring_free(keyring_handler); + keyring_handler = NULL; +} + +/***********************************************/ +/* Plugin interface */ +/***********************************************/ + +static gboolean +ss_load(PurplePlugin *plugin) +{ + return ss_init(); +} + +static gboolean +ss_unload(PurplePlugin *plugin) +{ + if (purple_keyring_get_inuse() == keyring_handler) + return FALSE; + + ss_uninit(); + + return TRUE; +} + +PurplePluginInfo plugininfo = +{ + PURPLE_PLUGIN_MAGIC, /* magic */ + PURPLE_MAJOR_VERSION, /* major_version */ + PURPLE_MINOR_VERSION, /* minor_version */ + PURPLE_PLUGIN_STANDARD, /* type */ + NULL, /* ui_requirement */ + PURPLE_PLUGIN_FLAG_INVISIBLE, /* flags */ + NULL, /* dependencies */ + PURPLE_PRIORITY_DEFAULT, /* priority */ + SECRETSERVICE_ID, /* id */ + SECRETSERVICE_NAME, /* name */ + DISPLAY_VERSION, /* version */ + "Secret Service Plugin", /* summary */ + N_("This plugin will store passwords in Secret Service."), /* description */ + "Elliott Sales de Andrade (qulogic[at]pidgin.im)", /* author */ + PURPLE_WEBSITE, /* homepage */ + ss_load, /* load */ + ss_unload, /* unload */ + NULL, /* destroy */ + NULL, /* ui_info */ + NULL, /* extra_info */ + NULL, /* prefs_info */ + NULL, /* actions */ + NULL, /* padding... */ + NULL, + NULL, + NULL, +}; + +static void +init_plugin(PurplePlugin *plugin) +{ +} + +PURPLE_INIT_PLUGIN(secret_service, init_plugin, plugininfo) + diff --git a/libpurple/plugins/keyrings/wincred.c b/libpurple/plugins/keyrings/wincred.c new file mode 100644 index 0000000000..7386ea8fea --- /dev/null +++ b/libpurple/plugins/keyrings/wincred.c @@ -0,0 +1,321 @@ +/** + * @file wincred.c Passwords storage using Windows credentials + * @ingroup plugins + */ + +/* purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program ; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "debug.h" +#include "internal.h" +#include "keyring.h" +#include "plugin.h" +#include "version.h" + +#include <wincred.h> + +#define WINCRED_NAME N_("Windows credentials") +#define WINCRED_SUMMARY N_("Store passwords using Windows credentials") +#define WINCRED_DESCRIPTION N_("This plugin stores passwords using Windows " \ + "credentials.") +#define WINCRED_AUTHOR "Tomek Wasilczyk (tomkiewicz@cpw.pidgin.im)" +#define WINCRED_ID "keyring-wincred" + +#define WINCRED_MAX_TARGET_NAME 256 + +static PurpleKeyring *keyring_handler = NULL; + +static gunichar2 * +wincred_get_target_name(PurpleAccount *account) +{ + gchar target_name_utf8[WINCRED_MAX_TARGET_NAME]; + gunichar2 *target_name_utf16; + + g_return_val_if_fail(account != NULL, NULL); + + g_snprintf(target_name_utf8, WINCRED_MAX_TARGET_NAME, "libpurple_%s_%s", + purple_account_get_protocol_id(account), + purple_account_get_username(account)); + + target_name_utf16 = g_utf8_to_utf16(target_name_utf8, -1, + NULL, NULL, NULL); + + if (target_name_utf16 == NULL) { + purple_debug_fatal("keyring-wincred", "Couldn't convert target " + "name\n"); + } + + return target_name_utf16; +} + +static void +wincred_read(PurpleAccount *account, PurpleKeyringReadCallback cb, + gpointer data) +{ + GError *error = NULL; + gunichar2 *target_name = NULL; + gchar *password; + PCREDENTIALW credential; + + g_return_if_fail(account != NULL); + + target_name = wincred_get_target_name(account); + g_return_if_fail(target_name != NULL); + + if (!CredReadW(target_name, CRED_TYPE_GENERIC, 0, &credential)) { + DWORD error_code = GetLastError(); + + if (error_code == ERROR_NOT_FOUND) { + if (purple_debug_is_verbose()) { + purple_debug_misc("keyring-wincred", + "No password found for account %s\n", + purple_account_get_username(account)); + } + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_NOPASSWORD, + _("Password not found.")); + } else if (error_code == ERROR_NO_SUCH_LOGON_SESSION) { + purple_debug_error("keyring-wincred", + "Cannot read password, no valid logon " + "session\n"); + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_ACCESSDENIED, + _("Cannot read password, no valid logon " + "session.")); + } else { + purple_debug_error("keyring-wincred", + "Cannot read password, error %lx\n", + error_code); + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_BACKENDFAIL, + _("Cannot read password (error %lx)."), error_code); + } + + if (cb != NULL) + cb(account, NULL, error, data); + g_error_free(error); + return; + } + + password = g_utf16_to_utf8((gunichar2*)credential->CredentialBlob, + credential->CredentialBlobSize / sizeof(gunichar2), + NULL, NULL, NULL); + + memset(credential->CredentialBlob, 0, credential->CredentialBlobSize); + CredFree(credential); + + if (password == NULL) { + purple_debug_error("keyring-wincred", + "Cannot convert password\n"); + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_BACKENDFAIL, + _("Cannot read password (unicode error).")); + } else { + purple_debug_misc("keyring-wincred", + _("Got password for account %s.\n"), + purple_account_get_username(account)); + } + + if (cb != NULL) + cb(account, password, error, data); + if (error != NULL) + g_error_free(error); + + purple_str_wipe(password); +} + +static void +wincred_save(PurpleAccount *account, const gchar *password, + PurpleKeyringSaveCallback cb, gpointer data) +{ + GError *error = NULL; + gunichar2 *target_name = NULL; + gunichar2 *username_utf16 = NULL; + gunichar2 *password_utf16 = NULL; + CREDENTIALW credential; + + g_return_if_fail(account != NULL); + + target_name = wincred_get_target_name(account); + g_return_if_fail(target_name != NULL); + + if (password == NULL) + { + if (CredDeleteW(target_name, CRED_TYPE_GENERIC, 0)) { + purple_debug_misc("keyring-wincred", "Password for " + "account %s removed\n", + purple_account_get_username(account)); + } else { + DWORD error_code = GetLastError(); + + if (error_code == ERROR_NOT_FOUND) { + if (purple_debug_is_verbose()) { + purple_debug_misc("keyring-wincred", + "Password for account %s was already " + "removed.\n", + purple_account_get_username(account)); + } + } else if (error_code == ERROR_NO_SUCH_LOGON_SESSION) { + purple_debug_error("keyring-wincred", + "Cannot remove password, no valid " + "logon session\n"); + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_ACCESSDENIED, + _("Cannot remove password, no valid " + "logon session.")); + } else { + purple_debug_error("keyring-wincred", + "Cannot remove password, error %lx\n", + error_code); + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_BACKENDFAIL, + _("Cannot remove password (error %lx)."), + error_code); + } + } + + if (cb != NULL) + cb(account, error, data); + if (error != NULL) + g_error_free(error); + return; + } + + username_utf16 = g_utf8_to_utf16(purple_account_get_username(account), + -1, NULL, NULL, NULL); + password_utf16 = g_utf8_to_utf16(password, -1, NULL, NULL, NULL); + + if (username_utf16 == NULL || password_utf16 == NULL) { + g_free(username_utf16); + purple_utf16_wipe(password_utf16); + + purple_debug_fatal("keyring-wincred", "Couldn't convert " + "username or password\n"); + g_return_if_reached(); + } + + memset(&credential, 0, sizeof(CREDENTIALW)); + credential.Type = CRED_TYPE_GENERIC; + credential.TargetName = target_name; + credential.CredentialBlobSize = purple_utf16_size(password_utf16) - 2; + credential.CredentialBlob = (LPBYTE)password_utf16; + credential.Persist = CRED_PERSIST_LOCAL_MACHINE; + credential.UserName = username_utf16; + + if (!CredWriteW(&credential, 0)) { + DWORD error_code = GetLastError(); + + if (error_code == ERROR_NO_SUCH_LOGON_SESSION) { + purple_debug_error("keyring-wincred", + "Cannot store password, no valid logon " + "session\n"); + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_ACCESSDENIED, + _("Cannot remove password, no valid logon " + "session.")); + } else { + purple_debug_error("keyring-wincred", + "Cannot store password, error %lx\n", + error_code); + error = g_error_new(PURPLE_KEYRING_ERROR, + PURPLE_KEYRING_ERROR_BACKENDFAIL, + _("Cannot store password (error %lx)."), error_code); + } + } else { + purple_debug_misc("keyring-wincred", + "Password updated for account %s.\n", + purple_account_get_username(account)); + } + + g_free(target_name); + g_free(username_utf16); + purple_utf16_wipe(password_utf16); + + if (cb != NULL) + cb(account, error, data); + if (error != NULL) + g_error_free(error); +} + +static gboolean +wincred_load(PurplePlugin *plugin) +{ + keyring_handler = purple_keyring_new(); + + purple_keyring_set_name(keyring_handler, WINCRED_NAME); + purple_keyring_set_id(keyring_handler, WINCRED_ID); + purple_keyring_set_read_password(keyring_handler, wincred_read); + purple_keyring_set_save_password(keyring_handler, wincred_save); + + purple_keyring_register(keyring_handler); + + return TRUE; +} + +static gboolean +wincred_unload(PurplePlugin *plugin) +{ + if (purple_keyring_get_inuse() == keyring_handler) { + purple_debug_warning("keyring-wincred", + "keyring in use, cannot unload\n"); + return FALSE; + } + + purple_keyring_unregister(keyring_handler); + purple_keyring_free(keyring_handler); + keyring_handler = NULL; + + return TRUE; +} + +PurplePluginInfo plugininfo = +{ + PURPLE_PLUGIN_MAGIC, /* magic */ + PURPLE_MAJOR_VERSION, /* major_version */ + PURPLE_MINOR_VERSION, /* minor_version */ + PURPLE_PLUGIN_STANDARD, /* type */ + NULL, /* ui_requirement */ + PURPLE_PLUGIN_FLAG_INVISIBLE, /* flags */ + NULL, /* dependencies */ + PURPLE_PRIORITY_DEFAULT, /* priority */ + WINCRED_ID, /* id */ + WINCRED_NAME, /* name */ + DISPLAY_VERSION, /* version */ + WINCRED_SUMMARY, /* summary */ + WINCRED_DESCRIPTION, /* description */ + WINCRED_AUTHOR, /* author */ + PURPLE_WEBSITE, /* homepage */ + wincred_load, /* load */ + wincred_unload, /* unload */ + NULL, /* destroy */ + NULL, /* ui_info */ + NULL, /* extra_info */ + NULL, /* prefs_info */ + NULL, /* actions */ + NULL, NULL, NULL, NULL /* padding */ +}; + +static void +init_plugin(PurplePlugin *plugin) +{ +} + +PURPLE_INIT_PLUGIN(wincred_keyring, init_plugin, plugininfo) diff --git a/libpurple/plugins/one_time_password.c b/libpurple/plugins/one_time_password.c 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..b83f4e9089 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..1e8eb5830d 100644 --- a/libpurple/plugins/perl/perl-handlers.c +++ b/libpurple/plugins/perl/perl-handlers.c @@ -4,6 +4,12 @@ #include "debug.h" #include "signals.h" +typedef struct +{ + SV *callback; + SV *data; +} PurplePerlAccountPasswordHandler; + extern PerlInterpreter *my_perl; static GSList *cmd_handlers = NULL; static GSList *signal_handlers = NULL; @@ -845,3 +851,115 @@ 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 _handler) +{ + PurplePerlAccountPasswordHandler *handler = _handler; + SV *accountSV, *errorSV; + + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + + accountSV = sv_2mortal(purple_perl_bless_object(account, + "Purple::Account")); + XPUSHs(accountSV); + + errorSV = sv_2mortal(purple_perl_bless_object(error, "GLib::Error")); + XPUSHs(errorSV); + + XPUSHs((SV *)handler->data); + + PUTBACK; + call_sv(handler->callback, G_EVAL | G_SCALAR); + SPAGAIN; + + if (SvTRUE(ERRSV)) { + purple_debug_error("perl", "Perl plugin command function " + "exited abnormally: %s\n", SvPVutf8_nolen(ERRSV)); + } + + PUTBACK; + FREETMPS; + LEAVE; + + g_free(handler); +} + +static void +perl_account_read_cb(PurpleAccount *account, const gchar *password, + GError *error, gpointer _handler) +{ + PurplePerlAccountPasswordHandler *handler = _handler; + SV *accountSV, *passwordSV, *errorSV; + + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + + accountSV = sv_2mortal(purple_perl_bless_object(account, + "Purple::Account")); + XPUSHs(accountSV); + + passwordSV = sv_2mortal(newSVpv(password, 0)); + XPUSHs(passwordSV); + + errorSV = sv_2mortal(purple_perl_bless_object(error, "GLib::Error")); + XPUSHs(errorSV); + + XPUSHs((SV *)handler->data); + + PUTBACK; + call_sv(handler->callback, G_EVAL | G_SCALAR); + SPAGAIN; + + if (SvTRUE(ERRSV)) { + purple_debug_error("perl", "Perl plugin command function " + "exited abnormally: %s\n", SvPVutf8_nolen(ERRSV)); + } + + PUTBACK; + FREETMPS; + LEAVE; + + g_free(handler); +} + +void +purple_perl_account_get_password(PurpleAccount *account, SV *func, SV *data) +{ + PurplePerlAccountPasswordHandler *handler; + + if (func == &PL_sv_undef) + func = NULL; + if (data == &PL_sv_undef) + data = NULL; + + handler = g_new0(PurplePerlAccountPasswordHandler, 1); + handler->callback = (func != NULL ? newSVsv(func) : NULL); + handler->data = (data != NULL ? newSVsv(data) : NULL); + + purple_account_get_password(account, perl_account_read_cb, handler); +} + +void +purple_perl_account_set_password(PurpleAccount *account, const gchar *password, + SV *func, SV *data) +{ + PurplePerlAccountPasswordHandler *handler; + + if (func == &PL_sv_undef) + func = NULL; + if (data == &PL_sv_undef) + data = NULL; + + handler = g_new0(PurplePerlAccountPasswordHandler, 1); + handler->callback = (func != NULL ? newSVsv(func) : NULL); + handler->data = (data != NULL ? newSVsv(data) : NULL); + + purple_account_set_password(account, password, perl_account_save_cb, + handler); +} diff --git a/libpurple/plugins/perl/perl-handlers.h b/libpurple/plugins/perl/perl-handlers.h index c0079d2fb9..0484025d48 100644 --- a/libpurple/plugins/perl/perl-handlers.h +++ b/libpurple/plugins/perl/perl-handlers.h @@ -82,4 +82,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/plugins/perl/scripts/account.pl b/libpurple/plugins/perl/scripts/account.pl index e56ee28d21..dcf1249f20 100644 --- a/libpurple/plugins/perl/scripts/account.pl +++ b/libpurple/plugins/perl/scripts/account.pl @@ -34,6 +34,37 @@ sub plugin_init { return %PLUGIN_INFO; } +sub set_password_cb +{ + my $account = shift; + my $error = shift; + my $data = shift; + + if ($error) { + Purple::Debug::warning($MODULE_NAME, "Failed to set password " . + "for $account\n"); + return; + } + + Purple::Debug::misc($MODULE_NAME, "Password for $account was set\n"); +} + +sub get_password_cb +{ + my $account = shift; + my $password = shift; + my $error = shift; + my $data = shift; + + if ($error) { + Purple::Debug::warning($MODULE_NAME, "Failed to get password for $account\n"); + return; + } + + Purple::Debug::misc($MODULE_NAME, "Got password for $account\n"); + + $account->set_password($password, \&set_password_cb); +} # This is the sub defined in %PLUGIN_INFO to be called when the plugin is loaded # Note: The plugin has a reference to itself on top of the argument stack. @@ -101,6 +132,8 @@ sub plugin_load { $account->set_status("available", TRUE); $account->connect(); + $account->get_password(\&get_password_cb); + print "\n\n"; Purple::Debug::info($MODULE_NAME, "plugin_load() - Testing $MODULE_NAME Completed.\n"); print "\n\n" . "#" x 80 . "\n\n"; diff --git a/libpurple/protocols/gg/account.c b/libpurple/protocols/gg/account.c 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..f3d7f38c73 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")); - - if (type == PURPLE_PREF_INT) { - gtk_tree_model_get(tree_model, &iter, - PREF_DROPDOWN_VALUE, &int_value, - -1); + g_object_set_data(G_OBJECT(combo_box), "previously_active", + g_object_get_data(G_OBJECT(combo_box), "current_active")); + g_object_set_data(G_OBJECT(combo_box), "current_active", + GINT_TO_POINTER(gtk_combo_box_get_active(combo_box))); - purple_prefs_set_int(key, int_value); + if (active.type == PURPLE_PREF_INT) { + gtk_tree_model_get(tree_model, &iter, PREF_DROPDOWN_VALUE, + &active.value.integer, -1); } - else if (type == PURPLE_PREF_STRING) { - gtk_tree_model_get(tree_model, &iter, - PREF_DROPDOWN_VALUE, &str_value, - -1); - - purple_prefs_set_string(key, str_value); + else if (active.type == PURPLE_PREF_STRING) { + gtk_tree_model_get(tree_model, &iter, PREF_DROPDOWN_VALUE, + &active.value.string, -1); } - else if (type == PURPLE_PREF_BOOLEAN) { - gtk_tree_model_get(tree_model, &iter, - PREF_DROPDOWN_VALUE, &bool_value, - -1); - - purple_prefs_set_bool(key, bool_value); + else if (active.type == PURPLE_PREF_BOOLEAN) { + gtk_tree_model_get(tree_model, &iter, PREF_DROPDOWN_VALUE, + &active.value.boolean, -1); } + + cb(combo_box, active); } -GtkWidget * -pidgin_prefs_dropdown_from_list(GtkWidget *box, const gchar *title, - PurplePrefType type, const char *key, GList *menuitems) +static void pidgin_prefs_dropdown_revert_active(GtkComboBox *combo_box) { - 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,262 @@ logging_page(void) return ret; } +/*** keyring page *******************************************************/ + +static void +keyring_page_settings_changed(GtkWidget *widget, gpointer _setting) +{ + PurpleRequestField *setting = _setting; + PurpleRequestFieldType field_type; + + gtk_widget_set_sensitive(keyring_apply, TRUE); + + field_type = purple_request_field_get_type(setting); + + if (field_type == PURPLE_REQUEST_FIELD_BOOLEAN) { + purple_request_field_bool_set_value(setting, + gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(widget))); + } else if (field_type == PURPLE_REQUEST_FIELD_STRING) { + purple_request_field_string_set_value(setting, + gtk_entry_get_text(GTK_ENTRY(widget))); + } else if (field_type == PURPLE_REQUEST_FIELD_INTEGER) { + purple_request_field_int_set_value(setting, + gtk_spin_button_get_value_as_int( + GTK_SPIN_BUTTON(widget))); + } else + g_return_if_reached(); +} + +static GtkWidget * +keyring_page_add_settings_field(GtkBox *vbox, PurpleRequestField *setting, + GtkSizeGroup *sg) +{ + GtkWidget *widget, *hbox; + PurpleRequestFieldType field_type; + const gchar *label; + + label = purple_request_field_get_label(setting); + + field_type = purple_request_field_get_type(setting); + if (field_type == PURPLE_REQUEST_FIELD_BOOLEAN) { + widget = gtk_check_button_new_with_label(label); + label = NULL; + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), + purple_request_field_bool_get_value(setting)); + g_signal_connect(G_OBJECT(widget), "toggled", + G_CALLBACK(keyring_page_settings_changed), setting); + } else if (field_type == PURPLE_REQUEST_FIELD_STRING) { + widget = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(widget), + purple_request_field_string_get_value(setting)); + if (purple_request_field_string_is_masked(setting)) + gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE); + g_signal_connect(G_OBJECT(widget), "changed", + G_CALLBACK(keyring_page_settings_changed), setting); + } else if (field_type == PURPLE_REQUEST_FIELD_INTEGER) { + widget = gtk_spin_button_new_with_range( + purple_request_field_int_get_lower_bound(setting), + purple_request_field_int_get_upper_bound(setting), 1); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), + purple_request_field_int_get_value(setting)); + g_signal_connect(G_OBJECT(widget), "value-changed", + G_CALLBACK(keyring_page_settings_changed), setting); + } else { + purple_debug_error("gtkprefs", "Unsupported field type\n"); + return NULL; + } + + hbox = pidgin_add_widget_to_vbox(vbox, label, sg, widget, + FALSE, NULL); + return ((void*)hbox == (void*)vbox) ? widget : hbox; +} + +/* XXX: it could be available for all plugins, not keyrings only */ +static GList * +keyring_page_add_settings(PurpleRequestFields *settings) +{ + GList *it, *groups, *added_fields; + GtkSizeGroup *sg; + + sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + + added_fields = NULL; + groups = purple_request_fields_get_groups(settings); + for (it = g_list_first(groups); it != NULL; it = g_list_next(it)) { + GList *it2, *fields; + GtkBox *vbox; + PurpleRequestFieldGroup *group; + const gchar *group_title; + + group = it->data; + group_title = purple_request_field_group_get_title(group); + if (group_title) { + vbox = GTK_BOX(pidgin_make_frame( + GTK_WIDGET(keyring_vbox), group_title)); + added_fields = g_list_prepend(added_fields, + g_object_get_data(G_OBJECT(vbox), "main-vbox")); + } else + vbox = keyring_vbox; + + fields = purple_request_field_group_get_fields(group); + for (it2 = g_list_first(fields); it2 != NULL; + it2 = g_list_next(it2)) { + GtkWidget *added = keyring_page_add_settings_field(vbox, + it2->data, sg); + if (added == NULL || vbox != keyring_vbox) + continue; + added_fields = g_list_prepend(added_fields, added); + } + } + + g_object_unref(sg); + + return added_fields; +} + +static void +keyring_page_settings_apply(GtkButton *button, gpointer _unused) +{ + if (!purple_keyring_apply_settings(prefs, keyring_settings)) + return; + + gtk_widget_set_sensitive(keyring_apply, FALSE); +} + +static void +keyring_page_update_settings() +{ + if (keyring_settings != NULL) + purple_request_fields_destroy(keyring_settings); + keyring_settings = purple_keyring_read_settings(); + if (!keyring_settings) + return; + + keyring_settings_fields = keyring_page_add_settings(keyring_settings); + + keyring_apply = gtk_button_new_with_mnemonic(_("_Apply")); + gtk_box_pack_start(keyring_vbox, keyring_apply, FALSE, FALSE, 1); + gtk_widget_set_sensitive(keyring_apply, FALSE); + keyring_settings_fields = g_list_prepend(keyring_settings_fields, + keyring_apply); + g_signal_connect(G_OBJECT(keyring_apply), "clicked", + G_CALLBACK(keyring_page_settings_apply), NULL); + + gtk_widget_show_all(keyring_page_instance); +} + +static void +keyring_page_pref_set_inuse(GError *error, gpointer _keyring_page_instance) +{ + PurpleKeyring *in_use = purple_keyring_get_inuse(); + + if (_keyring_page_instance != keyring_page_instance) { + purple_debug_info("gtkprefs", "pref window already closed\n"); + return; + } + + gtk_widget_set_sensitive(GTK_WIDGET(keyring_combo), TRUE); + + if (error != NULL) { + pidgin_prefs_dropdown_revert_active(keyring_combo); + purple_notify_error(NULL, _("Keyring"), + _("Failed to set new keyring"), error->message); + return; + } + + g_return_if_fail(in_use != NULL); + purple_prefs_set_string("/purple/keyring/active", + purple_keyring_get_id(in_use)); + + keyring_page_update_settings(); +} + +static void +keyring_page_pref_changed(GtkComboBox *combo_box, PidginPrefValue value) +{ + const char *keyring_id; + PurpleKeyring *keyring; + GList *it; + + g_return_if_fail(combo_box != NULL); + g_return_if_fail(value.type == PURPLE_PREF_STRING); + + keyring_id = value.value.string; + keyring = purple_keyring_find_keyring_by_id(keyring_id); + if (keyring == NULL) { + pidgin_prefs_dropdown_revert_active(keyring_combo); + purple_notify_error(NULL, _("Keyring"), + _("Selected keyring is disabled"), NULL); + return; + } + + gtk_widget_set_sensitive(GTK_WIDGET(combo_box), FALSE); + + for (it = keyring_settings_fields; it != NULL; it = g_list_next(it)) + { + GtkWidget *widget = it->data; + gtk_container_remove( + GTK_CONTAINER(gtk_widget_get_parent(widget)), widget); + } + gtk_widget_show_all(keyring_page_instance); + g_list_free(keyring_settings_fields); + keyring_settings_fields = NULL; + if (keyring_settings) + purple_request_fields_destroy(keyring_settings); + keyring_settings = NULL; + + purple_keyring_set_inuse(keyring, FALSE, keyring_page_pref_set_inuse, + keyring_page_instance); +} + +static void +keyring_page_cleanup(void) +{ + keyring_page_instance = NULL; + keyring_combo = NULL; + keyring_vbox = NULL; + g_list_free(keyring_settings_fields); + keyring_settings_fields = NULL; + if (keyring_settings) + purple_request_fields_destroy(keyring_settings); + keyring_settings = NULL; + keyring_apply = NULL; +} + +static GtkWidget * +keyring_page(void) +{ + GList *names; + PidginPrefValue initial; + + g_return_val_if_fail(keyring_page_instance == NULL, + keyring_page_instance); + + keyring_page_instance = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); + gtk_container_set_border_width(GTK_CONTAINER(keyring_page_instance), + PIDGIN_HIG_BORDER); + + /* Keyring selection */ + keyring_vbox = GTK_BOX(pidgin_make_frame(keyring_page_instance, + _("Keyring"))); + names = purple_keyring_get_options(); + initial.type = PURPLE_PREF_STRING; + initial.value.string = purple_prefs_get_string("/purple/keyring/active"); + pidgin_prefs_dropdown_from_list_with_cb(GTK_WIDGET(keyring_vbox), + _("Keyring:"), &keyring_combo, names, initial, + keyring_page_pref_changed); + g_list_free(names); + + keyring_page_update_settings(); + + gtk_widget_show_all(keyring_page_instance); + + return keyring_page_instance; +} + +/*** keyring page - end *************************************************/ + #ifndef _WIN32 static gint sound_cmd_yeah(GtkEntry *entry, gpointer d) @@ -3721,6 +4065,7 @@ prefs_notebook_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 |