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