# HG changeset patch # User Paul Aurich # Date 1262981509 0 # Node ID 3a43e48c870e8d8bfc537e3cea8f24dab44b4b96 # Parent 711b828d5173c8da365892184a775198c52f4894# Parent 10ec0fbfc790c76ef077d46bafdabc3f5692685d propagate from branch 'im.pidgin.pidgin.2.6.5' (head 794204a24ba7bf26cdb07ae71b9573b09927d641) to branch 'im.pidgin.pidgin' (head 037ff1c0c37fb6717fbc6fe2c658d6f9dda4e148) diff -r 10ec0fbfc790 -r 3a43e48c870e COPYRIGHT --- a/COPYRIGHT Fri Jan 08 19:01:39 2010 +0000 +++ b/COPYRIGHT Fri Jan 08 20:11:49 2010 +0000 @@ -154,6 +154,7 @@ David Fiander Rob Flynn Rob Foehl (rwf) +Chris Foote Alan Ford Nathan Fredrickson Chris J. Friesen diff -r 10ec0fbfc790 -r 3a43e48c870e ChangeLog --- a/ChangeLog Fri Jan 08 19:01:39 2010 +0000 +++ b/ChangeLog Fri Jan 08 20:11:49 2010 +0000 @@ -1,5 +1,25 @@ +Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul -Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.6.6 (??/??/20??): + Gadu-Gadu: + * Fix display of avatars after a server-side change. (Krzysztof + Klinikowski) + + MSN: + * File transfer requests will no longer cause a crash if you delete the + file before the other side accepts. + * Recieved files will no longer hold an extra lock after completion, + meaning they can be moved or deleted without complaints from your OS. + * Buddies who sign in from a second location will no longer cause an + unnecessary chat window to open. + * Support setting an animated GIF as a buddy icon. + + XMPP: + * Added support for the SCRAM-SHA-1 SASL mechanism. This is only + available when built without Cyrus SASL support. + * When getting info on a domain-only (server) JID, show uptime + (when given by the result of the "last query") and don't show status as + offline. version 2.6.5 (01/08/2010): libpurple: diff -r 10ec0fbfc790 -r 3a43e48c870e ChangeLog.API --- a/ChangeLog.API Fri Jan 08 19:01:39 2010 +0000 +++ b/ChangeLog.API Fri Jan 08 20:11:49 2010 +0000 @@ -1,5 +1,20 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.6.6 (??/??/2010): + libpurple: + Changed: + * purple_xfer_cancel_local is now called instead of + purple_xfer_request_denied if an error is found when selecting + a file to send. Request denied is still used when a receive + request is not allowed. + Perl: + Changed: + * Corrected the package names for the PurpleProxyType and + PurpleLogReadFlags enums to have the correct number of colons + (from Purple::ProxyType:::: to Purple::ProxyType:: + and Purple::Log:ReadFlags:::: to + Purple::Log::ReadFlags::) (Chris Foote) + version 2.6.5 (01/08/2010): No changes diff -r 10ec0fbfc790 -r 3a43e48c870e ChangeLog.win32 --- a/ChangeLog.win32 Fri Jan 08 19:01:39 2010 +0000 +++ b/ChangeLog.win32 Fri Jan 08 20:11:49 2010 +0000 @@ -1,3 +1,7 @@ + +version 2.6.6 (??/??/2010): + * Installer translations for: Norwegian nynorsk + version 2.6.5 (01/08/2010): * No changes diff -r 10ec0fbfc790 -r 3a43e48c870e NEWS --- a/NEWS Fri Jan 08 19:01:39 2010 +0000 +++ b/NEWS Fri Jan 08 20:11:49 2010 +0000 @@ -2,6 +2,8 @@ Our development blog is available at: http://planet.pidgin.im +2.6.6 (??/??/2010): + 2.6.5 (01/08/2010): Paul: This release fixes a pretty serious bug in the MSN code, so we're releasing this build a little earlier than planned with only major diff -r 10ec0fbfc790 -r 3a43e48c870e configure.ac --- a/configure.ac Fri Jan 08 19:01:39 2010 +0000 +++ b/configure.ac Fri Jan 08 20:11:49 2010 +0000 @@ -47,7 +47,7 @@ m4_define([purple_major_version], [2]) m4_define([purple_minor_version], [6]) m4_define([purple_micro_version], [5]) -m4_define([purple_version_suffix], []) +m4_define([purple_version_suffix], [devel]) m4_define([purple_version], [purple_major_version.purple_minor_version.purple_micro_version]) m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix])) @@ -56,7 +56,7 @@ m4_define([gnt_major_version], [2]) m4_define([gnt_minor_version], [6]) m4_define([gnt_micro_version], [5]) -m4_define([gnt_version_suffix], []) +m4_define([gnt_version_suffix], [devel]) m4_define([gnt_version], [gnt_major_version.gnt_minor_version.gnt_micro_version]) m4_define([gnt_display_version], gnt_version[]m4_ifdef([gnt_version_suffix],[gnt_version_suffix])) @@ -812,6 +812,10 @@ fi AM_CONDITIONAL(USE_VV, test "x$enable_gstreamer" != "xno" -a "x$enable_gstinterfaces" != "xno" -a "x$enable_farsight" != "xno") +dnl ####################################################################### +dnl # Check for Internationalized Domain Name support +dnl ####################################################################### + AC_ARG_ENABLE(idn, [AC_HELP_STRING([--disable-idn], [compile without IDN support])], [enable_idn="$enableval" force_idn=$enableval], [enable_idn="yes" force_idn=no]) @@ -2267,11 +2271,15 @@ AC_ARG_ENABLE(cyrus-sasl, AC_HELP_STRING([--enable-cyrus-sasl], [enable Cyrus SASL support for jabberd]), enable_cyrus_sasl=$enableval, enable_cyrus_sasl=no) if test "x$enable_cyrus_sasl" = "xyes" ; then AC_CHECK_LIB(sasl2, sasl_client_init, [ + AM_CONDITIONAL(USE_CYRUS_SASL, true) AC_DEFINE(HAVE_CYRUS_SASL, [1], [Define to 1 if Cyrus SASL is present]) SASL_LIBS=-"lsasl2" ], [ + AM_CONDITIONAL(USE_CYRUS_SASL, false) AC_ERROR(Cyrus SASL library not found) ]) +else + AM_CONDITIONAL(USE_CYRUS_SASL, false) fi dnl ####################################################################### diff -r 10ec0fbfc790 -r 3a43e48c870e finch/finch.c --- a/finch/finch.c Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/finch.c Fri Jan 08 20:11:49 2010 +0000 @@ -19,8 +19,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include #include "finch.h" -#include #include "account.h" #include "conversation.h" diff -r 10ec0fbfc790 -r 3a43e48c870e finch/gntaccount.c --- a/finch/gntaccount.c Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/gntaccount.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,6 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include + #include #include #include @@ -36,7 +38,6 @@ #include #include "finch.h" -#include #include #include diff -r 10ec0fbfc790 -r 3a43e48c870e finch/gntblist.c --- a/finch/gntblist.c Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/gntblist.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,8 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include #include "finch.h" -#include #include #include diff -r 10ec0fbfc790 -r 3a43e48c870e finch/gntcertmgr.c --- a/finch/gntcertmgr.c Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/gntcertmgr.c Fri Jan 08 20:11:49 2010 +0000 @@ -25,8 +25,8 @@ * */ +#include #include "finch.h" -#include #include "certificate.h" #include "debug.h" diff -r 10ec0fbfc790 -r 3a43e48c870e finch/gntconn.c --- a/finch/gntconn.c Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/gntconn.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,8 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include #include "finch.h" -#include #include "account.h" #include "core.h" diff -r 10ec0fbfc790 -r 3a43e48c870e finch/gntconv.c --- a/finch/gntconv.c Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/gntconv.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,10 +23,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include +#include #include "finch.h" -#include #include #include diff -r 10ec0fbfc790 -r 3a43e48c870e finch/gntdebug.c --- a/finch/gntdebug.c Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/gntdebug.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,6 +23,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ + +#include + #include #include #include @@ -35,7 +38,6 @@ #include "gntdebug.h" #include "finch.h" -#include #include "notify.h" #include "util.h" diff -r 10ec0fbfc790 -r 3a43e48c870e finch/gntft.c --- a/finch/gntft.c Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/gntft.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,8 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include #include "finch.h" -#include #include #include diff -r 10ec0fbfc790 -r 3a43e48c870e finch/gntlog.c --- a/finch/gntlog.c Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/gntlog.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,8 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include #include "finch.h" -#include #include #include diff -r 10ec0fbfc790 -r 3a43e48c870e finch/gntmedia.c --- a/finch/gntmedia.c Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/gntmedia.c Fri Jan 08 20:11:49 2010 +0000 @@ -24,8 +24,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include #include "finch.h" -#include #include "gntconv.h" #include "gntmedia.h" diff -r 10ec0fbfc790 -r 3a43e48c870e finch/gntnotify.c --- a/finch/gntnotify.c Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/gntnotify.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,6 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include + #include #include #include @@ -32,7 +34,6 @@ #include #include "finch.h" -#include #include diff -r 10ec0fbfc790 -r 3a43e48c870e finch/gntplugin.c --- a/finch/gntplugin.c Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/gntplugin.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,6 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include + #include #include #include @@ -32,7 +34,6 @@ #include #include "finch.h" -#include #include "debug.h" #include "notify.h" diff -r 10ec0fbfc790 -r 3a43e48c870e finch/gntpounce.c --- a/finch/gntpounce.c Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/gntpounce.c Fri Jan 08 20:11:49 2010 +0000 @@ -24,6 +24,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ +#include + #include #include #include @@ -36,7 +38,6 @@ #include #include "finch.h" -#include #include "account.h" #include "conversation.h" diff -r 10ec0fbfc790 -r 3a43e48c870e finch/libgnt/gntkeys.h --- a/finch/libgnt/gntkeys.h Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/libgnt/gntkeys.h Fri Jan 08 20:11:49 2010 +0000 @@ -65,7 +65,7 @@ #define GNT_KEY_BACKSPACE SAFE(key_backspace) #define GNT_KEY_DEL SAFE(key_dc) #define GNT_KEY_INS SAFE(key_ic) -#define GNT_KEY_BACK_TAB SAFE(back_tab) +#define GNT_KEY_BACK_TAB (back_tab ? back_tab : SAFE(key_btab)) #define GNT_KEY_CTRL_A "\001" #define GNT_KEY_CTRL_B "\002" diff -r 10ec0fbfc790 -r 3a43e48c870e finch/libgnt/gntutils.c --- a/finch/libgnt/gntutils.c Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/libgnt/gntutils.c Fri Jan 08 20:11:49 2010 +0000 @@ -20,6 +20,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include "config.h" + #include "gntinternal.h" #undef GNT_LOG_DOMAIN #define GNT_LOG_DOMAIN "Utils" @@ -35,8 +37,6 @@ #include "gntutils.h" #include "gntwindow.h" -#include "config.h" - #include #include #include @@ -46,8 +46,6 @@ #include #endif -#include "config.h" - void gnt_util_get_text_bound(const char *text, int *width, int *height) { const char *s = text, *last; diff -r 10ec0fbfc790 -r 3a43e48c870e finch/libgnt/wms/s.c --- a/finch/libgnt/wms/s.c Fri Jan 08 19:01:39 2010 +0000 +++ b/finch/libgnt/wms/s.c Fri Jan 08 20:11:49 2010 +0000 @@ -1,8 +1,8 @@ +#include "internal.h" + #include #include -#include "internal.h" - #include "gnt.h" #include "gntbox.h" #include "gntmenu.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/account.c --- a/libpurple/account.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/account.c Fri Jan 08 20:11:49 2010 +0000 @@ -2315,7 +2315,7 @@ gc = purple_account_get_connection(account); if (gc != NULL) - prpl = purple_connection_get_prpl(gc); + prpl = purple_connection_get_prpl(gc); if (prpl != NULL) prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); @@ -2332,7 +2332,7 @@ PurplePlugin *prpl = NULL; if (gc != NULL) - prpl = purple_connection_get_prpl(gc); + prpl = purple_connection_get_prpl(gc); if (prpl != NULL) prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); @@ -2371,7 +2371,7 @@ PurplePlugin *prpl = NULL; if (gc != NULL) - prpl = purple_connection_get_prpl(gc); + prpl = purple_connection_get_prpl(gc); if (prpl != NULL) prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); @@ -2388,7 +2388,7 @@ PurplePlugin *prpl = NULL; if (gc != NULL) - prpl = purple_connection_get_prpl(gc); + prpl = purple_connection_get_prpl(gc); if (prpl != NULL) prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); @@ -2416,7 +2416,7 @@ PurplePlugin *prpl = NULL; if (gc != NULL) - prpl = purple_connection_get_prpl(gc); + prpl = purple_connection_get_prpl(gc); if (prpl != NULL) prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); @@ -2436,7 +2436,7 @@ purple_account_set_password(account, new_pw); if (gc != NULL) - prpl = purple_connection_get_prpl(gc); + prpl = purple_connection_get_prpl(gc); if (prpl != NULL) prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/conversation.h --- a/libpurple/conversation.h Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/conversation.h Fri Jan 08 20:11:49 2010 +0000 @@ -368,7 +368,8 @@ * @param type The type of conversation. * @param account The account opening the conversation window on the purple * user's end. - * @param name The name of the conversation. + * @param name The name of the conversation. For PURPLE_CONV_TYPE_IM, + * this is the name of the buddy. * * @return The new conversation. */ @@ -1025,7 +1026,8 @@ GList *purple_conv_chat_set_users(PurpleConvChat *chat, GList *users); /** - * Returns a list of users in the chat room. + * Returns a list of users in the chat room. The members of the list + * are PurpleConvChatBuddy objects. * * @param chat The chat. * diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/dbus-server.c --- a/libpurple/dbus-server.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/dbus-server.c Fri Jan 08 20:11:49 2010 +0000 @@ -601,7 +601,6 @@ { static DBusObjectPathVTable vtable = {NULL, &purple_dbus_dispatch, NULL, NULL, NULL, NULL}; DBusError error; - int result; dbus_error_init(&error); purple_dbus_connection = dbus_bus_get(DBUS_BUS_STARTER, &error); @@ -625,16 +624,15 @@ return; } - dbus_request_name_reply = - result = dbus_bus_request_name(purple_dbus_connection, + dbus_request_name_reply = dbus_bus_request_name(purple_dbus_connection, DBUS_SERVICE_PURPLE, 0, &error); if (dbus_error_is_set(&error)) { dbus_connection_unref(purple_dbus_connection); - dbus_error_free(&error); purple_dbus_connection = NULL; init_error = g_strdup_printf(N_("Failed to get serv name: %s"), error.name); + dbus_error_free(&error); return; } diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/ft.c --- a/libpurple/ft.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/ft.c Fri Jan 08 20:11:49 2010 +0000 @@ -69,6 +69,30 @@ g_free(priv); } +static const gchar * +purple_xfer_status_type_to_string(PurpleXferStatusType type) +{ + static const struct { + PurpleXferStatusType type; + const char *name; + } type_names[] = { + { PURPLE_XFER_STATUS_UNKNOWN, "unknown" }, + { PURPLE_XFER_STATUS_NOT_STARTED, "not started" }, + { PURPLE_XFER_STATUS_ACCEPTED, "accepted" }, + { PURPLE_XFER_STATUS_STARTED, "started" }, + { PURPLE_XFER_STATUS_DONE, "done" }, + { PURPLE_XFER_STATUS_CANCEL_LOCAL, "cancelled locally" }, + { PURPLE_XFER_STATUS_CANCEL_REMOTE, "cancelled remotely" } + }; + int i; + + for (i = 0; i < G_N_ELEMENTS(type_names); ++i) + if (type_names[i].type == type) + return type_names[i].name; + + return "invalid state"; +} + GList * purple_xfers_get_all() { @@ -109,6 +133,10 @@ ui_ops->new_xfer(xfer); xfers = g_list_prepend(xfers, xfer); + + if (purple_debug_is_verbose()) + purple_debug_info("xfer", "new %p [%d]\n", xfer, xfer->ref); + return xfer; } @@ -119,6 +147,9 @@ g_return_if_fail(xfer != NULL); + if (purple_debug_is_verbose()) + purple_debug_info("xfer", "destroyed %p [%d]\n", xfer, xfer->ref); + /* Close the file browser, if it's open */ purple_request_close_with_handle(xfer); @@ -148,6 +179,9 @@ g_return_if_fail(xfer != NULL); xfer->ref++; + + if (purple_debug_is_verbose()) + purple_debug_info("xfer", "ref'd %p [%d]\n", xfer, xfer->ref); } void @@ -158,6 +192,9 @@ xfer->ref--; + if (purple_debug_is_verbose()) + purple_debug_info("xfer", "unref'd %p [%d]\n", xfer, xfer->ref); + if (xfer->ref == 0) purple_xfer_destroy(xfer); } @@ -167,6 +204,11 @@ { g_return_if_fail(xfer != NULL); + if (purple_debug_is_verbose()) + purple_debug_info("xfer", "Changing status of xfer %p from %s to %s\n", + xfer, purple_xfer_status_type_to_string(xfer->status), + purple_xfer_status_type_to_string(status)); + if (xfer->status == status) return; @@ -268,14 +310,16 @@ purple_xfer_choose_file_ok_cb(void *user_data, const char *filename) { PurpleXfer *xfer; + PurpleXferType type; struct stat st; gchar *dir; xfer = (PurpleXfer *)user_data; + type = purple_xfer_get_type(xfer); if (g_stat(filename, &st) != 0) { /* File not found. */ - if (purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE) { + if (type == PURPLE_XFER_RECEIVE) { #ifndef _WIN32 int mode = W_OK; #else @@ -297,29 +341,26 @@ } else { purple_xfer_show_file_error(xfer, filename); - purple_xfer_request_denied(xfer); + purple_xfer_cancel_local(xfer); } } - else if ((purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) && - (st.st_size == 0)) { + else if ((type == PURPLE_XFER_SEND) && (st.st_size == 0)) { purple_notify_error(NULL, NULL, _("Cannot send a file of 0 bytes."), NULL); - purple_xfer_request_denied(xfer); + purple_xfer_cancel_local(xfer); } - else if ((purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) && - S_ISDIR(st.st_mode)) { + else if ((type == PURPLE_XFER_SEND) && S_ISDIR(st.st_mode)) { /* * XXX - Sending a directory should be valid for some protocols. */ purple_notify_error(NULL, NULL, _("Cannot send a directory."), NULL); - purple_xfer_request_denied(xfer); + purple_xfer_cancel_local(xfer); } - else if ((purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE) && - S_ISDIR(st.st_mode)) { + else if ((type == PURPLE_XFER_RECEIVE) && S_ISDIR(st.st_mode)) { char *msg, *utf8; utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); msg = g_strdup_printf( @@ -329,6 +370,23 @@ g_free(msg); purple_xfer_request_denied(xfer); } + else if (type == PURPLE_XFER_SEND) { +#ifndef _WIN32 + int mode = R_OK; +#else + int mode = F_OK; +#endif + + if (g_access(filename, mode) == 0) { + purple_xfer_request_accepted(xfer, filename); + } else { + purple_xfer_ref(xfer); + purple_notify_message( + NULL, PURPLE_NOTIFY_MSG_ERROR, NULL, + _("File is not readable."), NULL, + (PurpleNotifyCloseCallback)purple_xfer_choose_file, xfer); + } + } else { purple_xfer_request_accepted(xfer, filename); } @@ -342,7 +400,11 @@ PurpleXfer *xfer = (PurpleXfer *)user_data; purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_LOCAL); - purple_xfer_request_denied(xfer); + if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) + purple_xfer_cancel_local(xfer); + else + purple_xfer_request_denied(xfer); + purple_xfer_unref(xfer); } static int @@ -506,6 +568,8 @@ type = purple_xfer_get_type(xfer); account = purple_xfer_get_account(xfer); + purple_debug_misc("xfer", "request accepted for %p\n", xfer); + if (!filename && type == PURPLE_XFER_RECEIVE) { xfer->status = PURPLE_XFER_STATUS_ACCEPTED; xfer->ops.init(xfer); @@ -583,6 +647,8 @@ { g_return_if_fail(xfer != NULL); + purple_debug_misc("xfer", "xfer %p denied\n", xfer); + if (xfer->ops.request_denied != NULL) xfer->ops.request_denied(xfer); @@ -1111,6 +1177,8 @@ purple_input_remove(xfer->watcher); xfer->watcher = 0; + + purple_debug_misc("xfer", "prpl is ready on ft %p, waiting for UI\n", xfer); return; } } @@ -1173,8 +1241,12 @@ priv = g_hash_table_lookup(xfers_data, xfer); priv->ready |= PURPLE_XFER_READY_UI; - if (0 == (priv->ready & PURPLE_XFER_READY_PRPL)) + if (0 == (priv->ready & PURPLE_XFER_READY_PRPL)) { + purple_debug_misc("xfer", "UI is ready on ft %p, waiting for prpl\n", xfer); return; + } + + purple_debug_misc("xfer", "UI (and prpl) ready on ft %p, so proceeding\n", xfer); type = purple_xfer_get_type(xfer); if (type == PURPLE_XFER_SEND) @@ -1201,8 +1273,12 @@ priv->ready |= PURPLE_XFER_READY_PRPL; /* I don't think fwrite/fread are ever *not* ready */ - if (xfer->dest_fp == NULL && 0 == (priv->ready & PURPLE_XFER_READY_UI)) + if (xfer->dest_fp == NULL && 0 == (priv->ready & PURPLE_XFER_READY_UI)) { + purple_debug_misc("xfer", "prpl is ready on ft %p, waiting for UI\n", xfer); return; + } + + purple_debug_misc("xfer", "Prpl (and UI) ready on ft %p, so proceeding\n", xfer); priv->ready = PURPLE_XFER_READY_NONE; diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/media.h --- a/libpurple/media.h Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/media.h Fri Jan 08 20:11:49 2010 +0000 @@ -75,7 +75,7 @@ PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION = 1 << 3, PURPLE_MEDIA_CAPS_AUDIO_VIDEO = 1 << 4, PURPLE_MEDIA_CAPS_MODIFY_SESSION = 1 << 5, - PURPLE_MEDIA_CAPS_CHANGE_DIRECTION = 1 << 6, + PURPLE_MEDIA_CAPS_CHANGE_DIRECTION = 1 << 6 } PurpleMediaCaps; /** Media session types */ @@ -93,7 +93,7 @@ typedef enum { PURPLE_MEDIA_STATE_NEW = 0, PURPLE_MEDIA_STATE_CONNECTED, - PURPLE_MEDIA_STATE_END, + PURPLE_MEDIA_STATE_END } PurpleMediaState; /** Media info types */ @@ -106,7 +106,7 @@ PURPLE_MEDIA_INFO_PAUSE, PURPLE_MEDIA_INFO_UNPAUSE, PURPLE_MEDIA_INFO_HOLD, - PURPLE_MEDIA_INFO_UNHOLD, + PURPLE_MEDIA_INFO_UNHOLD } PurpleMediaInfoType; typedef enum { @@ -114,18 +114,18 @@ PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX, PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX, PURPLE_MEDIA_CANDIDATE_TYPE_RELAY, - PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST, + PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST } PurpleMediaCandidateType; typedef enum { PURPLE_MEDIA_COMPONENT_NONE = 0, PURPLE_MEDIA_COMPONENT_RTP = 1, - PURPLE_MEDIA_COMPONENT_RTCP = 2, + PURPLE_MEDIA_COMPONENT_RTCP = 2 } PurpleMediaComponentType; typedef enum { PURPLE_MEDIA_NETWORK_PROTOCOL_UDP, - PURPLE_MEDIA_NETWORK_PROTOCOL_TCP, + PURPLE_MEDIA_NETWORK_PROTOCOL_TCP } PurpleMediaNetworkProtocol; #include "signals.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/plugins/perl/common/Log.xs --- a/libpurple/plugins/perl/common/Log.xs Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/plugins/perl/common/Log.xs Fri Jan 08 20:11:49 2010 +0000 @@ -6,7 +6,7 @@ BOOT: { HV *type_stash = gv_stashpv("Purple::Log::Type", 1); - HV *flags_stash = gv_stashpv("Purple::Log:ReadFlags::", 1); + HV *flags_stash = gv_stashpv("Purple::Log::ReadFlags", 1); static const constiv *civ, type_const_iv[] = { #define const_iv(name) {#name, (IV)PURPLE_LOG_##name} diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/plugins/perl/common/Proxy.xs --- a/libpurple/plugins/perl/common/Proxy.xs Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/plugins/perl/common/Proxy.xs Fri Jan 08 20:11:49 2010 +0000 @@ -5,7 +5,7 @@ BOOT: { - HV *stash = gv_stashpv("Purple::ProxyType::", 1); + HV *stash = gv_stashpv("Purple::ProxyType", 1); static const constiv *civ, const_iv[] = { #define const_iv(name) {#name, (IV)PURPLE_PROXY_##name} diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/plugins/signals-test.c --- a/libpurple/plugins/signals-test.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/plugins/signals-test.c Fri Jan 08 20:11:49 2010 +0000 @@ -547,6 +547,26 @@ purple_debug_misc("signals test", "quitting ()\n"); } +static void +printhash(gpointer key, gpointer value, gpointer data) +{ + char *a = (char *)key; + char *b = (char *)value; + GString *str = (GString *)data; + g_string_append_printf(str, " [%s] = [%s]\n", a, b ? b : "(null)"); +} + +static gboolean +uri_handler(const char *proto, const char *cmd, GHashTable *params) +{ + GString *str = g_string_new("\n{\n"); + g_hash_table_foreach(params, printhash, str); + g_string_append_c(str, '}'); + purple_debug_misc("signals test", "uri handler (%s, %s, %s)\n", proto, cmd, str->str); + g_string_free(str, TRUE); + return FALSE; +} + /************************************************************************** * File transfer signal callbacks **************************************************************************/ @@ -820,6 +840,8 @@ /* Core signals */ purple_signal_connect(core_handle, "quitting", plugin, PURPLE_CALLBACK(quitting_cb), NULL); + purple_signal_connect(core_handle, "uri-handler", + plugin, PURPLE_CALLBACK(uri_handler), NULL); /* File transfer signals */ purple_signal_connect(ft_handle, "file-recv-accept", diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/bonjour/bonjour.c --- a/libpurple/protocols/bonjour/bonjour.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Fri Jan 08 20:11:49 2010 +0000 @@ -392,11 +392,8 @@ purple_notify_user_info_add_pair(user_info, _("XMPP Account"), bb->jid); } -static void -bonjour_group_buddy(PurpleConnection *connection, const char *who, const char *old_group, const char *new_group) -{ +static void bonjour_do_group_change(PurpleBuddy *buddy, const char *new_group) { PurpleBlistNodeFlags oldflags; - PurpleBuddy *buddy = purple_find_buddy(connection->account, who); if (buddy == NULL) return; @@ -404,13 +401,38 @@ oldflags = purple_blist_node_get_flags((PurpleBlistNode *)buddy); /* If we're moving them out of the bonjour group, make them persistent */ - if (strcmp(new_group, BONJOUR_GROUP_NAME) == 0) + if (purple_strequal(new_group, BONJOUR_GROUP_NAME)) purple_blist_node_set_flags((PurpleBlistNode *)buddy, oldflags | PURPLE_BLIST_NODE_FLAG_NO_SAVE); else purple_blist_node_set_flags((PurpleBlistNode *)buddy, oldflags ^ PURPLE_BLIST_NODE_FLAG_NO_SAVE); } +static void +bonjour_group_buddy(PurpleConnection *connection, const char *who, const char *old_group, const char *new_group) +{ + PurpleBuddy *buddy = purple_find_buddy(connection->account, who); + + bonjour_do_group_change(buddy, new_group); + +} + +static void +bonjour_rename_group(PurpleConnection *connection, const char *old_name, PurpleGroup *group, GList *moved_buddies) +{ + GList *cur; + const char *new_group; + PurpleBuddy *buddy; + + new_group = purple_group_get_name(group); + + for (cur = moved_buddies; cur; cur = cur->next) { + buddy = cur->data; + bonjour_do_group_change(buddy, new_group); + } + +} + static gboolean bonjour_can_receive_file(PurpleConnection *connection, const char *who) { @@ -478,7 +500,7 @@ NULL, /* get_cb_away */ NULL, /* alias_buddy */ bonjour_group_buddy, /* group_buddy */ - NULL, /* rename_group */ + bonjour_rename_group, /* rename_group */ NULL, /* buddy_free */ bonjour_convo_closed, /* convo_closed */ NULL, /* normalize */ diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/gg/gg.c --- a/libpurple/protocols/gg/gg.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/gg/gg.c Fri Jan 08 20:11:49 2010 +0000 @@ -947,7 +947,7 @@ if (xmlnode_avatar == NULL) goto out; - xmlnode_bigavatar = xmlnode_get_child(xmlnode_avatar, "bigAvatar"); + xmlnode_bigavatar = xmlnode_get_child(xmlnode_avatar, "originBigAvatar"); if (xmlnode_bigavatar == NULL) goto out; diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/gg/lib/common.c --- a/libpurple/protocols/gg/lib/common.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/gg/lib/common.c Fri Jan 08 20:11:49 2010 +0000 @@ -19,6 +19,8 @@ * USA. */ +#include "libgadu.h" + #ifndef _WIN32 #include #include @@ -41,8 +43,6 @@ #include #include -#include "libgadu.h" - FILE *gg_debug_file = NULL; #ifndef GG_DEBUG_DISABLE diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/gg/lib/dcc.c --- a/libpurple/protocols/gg/lib/dcc.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/gg/lib/dcc.c Fri Jan 08 20:11:49 2010 +0000 @@ -19,6 +19,8 @@ * USA. */ +#include "libgadu.h" + #include #include #ifndef _WIN32 @@ -41,8 +43,6 @@ #include #include "compat.h" -#include "libgadu.h" - #ifndef GG_DEBUG_DISABLE /* * gg_dcc_debug_data() // funkcja wewntrzna diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/gg/lib/events.c --- a/libpurple/protocols/gg/lib/events.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/gg/lib/events.c Fri Jan 08 20:11:49 2010 +0000 @@ -20,6 +20,8 @@ * USA. */ +#include "libgadu.h" + #include #ifndef _WIN32 #include @@ -46,7 +48,6 @@ #endif #include "compat.h" -#include "libgadu.h" /* * gg_event_free() diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/gg/lib/http.c --- a/libpurple/protocols/gg/lib/http.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/gg/lib/http.c Fri Jan 08 20:11:49 2010 +0000 @@ -18,6 +18,8 @@ * USA. */ +#include "libgadu.h" + #include #ifndef _WIN32 #include @@ -43,7 +45,6 @@ #include #include "compat.h" -#include "libgadu.h" /* * gg_http_connect() // funkcja pomocnicza diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/gg/lib/libgadu.c --- a/libpurple/protocols/gg/lib/libgadu.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/gg/lib/libgadu.c Fri Jan 08 20:11:49 2010 +0000 @@ -21,6 +21,8 @@ * USA. */ +#include "libgadu.h" + #include #ifndef _WIN32 #include @@ -57,7 +59,6 @@ #endif #include "compat.h" -#include "libgadu.h" int gg_debug_level = 0; void (*gg_debug_handler)(int level, const char *format, va_list ap) = NULL; diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/gg/lib/pubdir.c --- a/libpurple/protocols/gg/lib/pubdir.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/gg/lib/pubdir.c Fri Jan 08 20:11:49 2010 +0000 @@ -19,6 +19,8 @@ * USA. */ +#include "libgadu.h" + #include #include #include @@ -27,8 +29,6 @@ #include #include -#include "libgadu.h" - /* * gg_register3() * diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/gg/lib/pubdir50.c --- a/libpurple/protocols/gg/lib/pubdir50.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/gg/lib/pubdir50.c Fri Jan 08 20:11:49 2010 +0000 @@ -18,13 +18,13 @@ * USA. */ +#include "libgadu.h" + #include #include #include #include -#include "libgadu.h" - /* * gg_pubdir50_new() * diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/Makefile.am --- a/libpurple/protocols/jabber/Makefile.am Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/Makefile.am Fri Jan 08 20:11:49 2010 +0000 @@ -10,6 +10,10 @@ adhoccommands.h \ auth.c \ auth.h \ + auth_digest_md5.c \ + auth_plain.c \ + auth_scram.c \ + auth_scram.h \ buddy.c \ buddy.h \ bosh.c \ @@ -78,6 +82,10 @@ libxmpp_la_LDFLAGS = -module -avoid-version +if USE_CYRUS_SASL +JABBERSOURCES += auth_cyrus.c +endif + if STATIC_JABBER st = -DPURPLE_STATIC_PRPL diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/Makefile.mingw --- a/libpurple/protocols/jabber/Makefile.mingw Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/Makefile.mingw Fri Jan 08 20:11:49 2010 +0000 @@ -45,6 +45,10 @@ C_SRC = \ adhoccommands.c \ auth.c \ + auth_cyrus.c \ + auth_digest_md5.c \ + auth_plain.c \ + auth_scram.c \ buddy.c \ bosh.c \ caps.c \ diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/auth.c --- a/libpurple/protocols/jabber/auth.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/auth.c Fri Jan 08 20:11:49 2010 +0000 @@ -39,6 +39,8 @@ #include "iq.h" #include "notify.h" +static GSList *auth_mechs = NULL; + static void auth_old_result_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *packet, gpointer data); @@ -46,8 +48,11 @@ gboolean jabber_process_starttls(JabberStream *js, xmlnode *packet) { + PurpleAccount *account; xmlnode *starttls; + account = purple_connection_get_account(js->gc); + if((starttls = xmlnode_get_child(packet, "starttls"))) { if(purple_ssl_is_supported()) { jabber_send_raw(js, @@ -58,7 +63,7 @@ PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("Server requires TLS/SSL, but no TLS/SSL support was found.")); return TRUE; - } else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { + } else if(purple_account_get_bool(account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("You require encryption, but no TLS/SSL support was found.")); @@ -71,418 +76,96 @@ static void finish_plaintext_authentication(JabberStream *js) { - if(js->auth_type == JABBER_AUTH_PLAIN) { - xmlnode *auth; - GString *response; - gchar *enc_out; - - auth = xmlnode_new("auth"); - xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl"); - - xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth"); - xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true"); - - response = g_string_new(""); - response = g_string_append_len(response, "\0", 1); - response = g_string_append(response, js->user->node); - response = g_string_append_len(response, "\0", 1); - response = g_string_append(response, - purple_connection_get_password(js->gc)); - - enc_out = purple_base64_encode((guchar *)response->str, response->len); + JabberIq *iq; + xmlnode *query, *x; - xmlnode_set_attrib(auth, "mechanism", "PLAIN"); - xmlnode_insert_data(auth, enc_out, -1); - g_free(enc_out); - g_string_free(response, TRUE); - - jabber_send(js, auth); - xmlnode_free(auth); - } else if(js->auth_type == JABBER_AUTH_IQ_AUTH) { - JabberIq *iq; - xmlnode *query, *x; - - iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth"); - query = xmlnode_get_child(iq->node, "query"); - x = xmlnode_new_child(query, "username"); - xmlnode_insert_data(x, js->user->node, -1); - x = xmlnode_new_child(query, "resource"); - xmlnode_insert_data(x, js->user->resource, -1); - x = xmlnode_new_child(query, "password"); - xmlnode_insert_data(x, purple_connection_get_password(js->gc), -1); - jabber_iq_set_callback(iq, auth_old_result_cb, NULL); - jabber_iq_send(iq); - } + iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth"); + query = xmlnode_get_child(iq->node, "query"); + x = xmlnode_new_child(query, "username"); + xmlnode_insert_data(x, js->user->node, -1); + x = xmlnode_new_child(query, "resource"); + xmlnode_insert_data(x, js->user->resource, -1); + x = xmlnode_new_child(query, "password"); + xmlnode_insert_data(x, purple_connection_get_password(js->gc), -1); + jabber_iq_set_callback(iq, auth_old_result_cb, NULL); + jabber_iq_send(iq); } static void allow_plaintext_auth(PurpleAccount *account) { + PurpleConnection *gc; + JabberStream *js; + purple_account_set_bool(account, "auth_plain_in_clear", TRUE); - finish_plaintext_authentication(account->gc->proto_data); + gc = purple_account_get_connection(account); + js = purple_connection_get_protocol_data(gc); + + finish_plaintext_authentication(js); } static void disallow_plaintext_auth(PurpleAccount *account) { - purple_connection_error_reason(account->gc, + purple_connection_error_reason(purple_account_get_connection(account), PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, _("Server requires plaintext authentication over an unencrypted stream")); } #ifdef HAVE_CYRUS_SASL - -static void jabber_auth_start_cyrus(JabberStream *); -static void jabber_sasl_build_callbacks(JabberStream *); - -/* Callbacks for Cyrus SASL */ - -static int jabber_sasl_cb_realm(void *ctx, int id, const char **avail, const char **result) -{ - JabberStream *js = (JabberStream *)ctx; - - if (id != SASL_CB_GETREALM || !result) return SASL_BADPARAM; - - *result = js->user->domain; - - return SASL_OK; -} - -static int jabber_sasl_cb_simple(void *ctx, int id, const char **res, unsigned *len) +static void +auth_old_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields) { - JabberStream *js = (JabberStream *)ctx; - - switch(id) { - case SASL_CB_AUTHNAME: - *res = js->user->node; - break; - case SASL_CB_USER: - *res = ""; - break; - default: - return SASL_BADPARAM; - } - if (len) *len = strlen((char *)*res); - return SASL_OK; -} - -static int jabber_sasl_cb_secret(sasl_conn_t *conn, void *ctx, int id, sasl_secret_t **secret) -{ - JabberStream *js = (JabberStream *)ctx; - const char *pw = purple_account_get_password(js->gc->account); - size_t len; - static sasl_secret_t *x = NULL; - - if (!conn || !secret || id != SASL_CB_PASS) - return SASL_BADPARAM; - - len = strlen(pw); - x = (sasl_secret_t *) realloc(x, sizeof(sasl_secret_t) + len); - - if (!x) - return SASL_NOMEM; - - x->len = len; - strcpy((char*)x->data, pw); - - *secret = x; - return SASL_OK; -} - -static void allow_cyrus_plaintext_auth(PurpleAccount *account) -{ - purple_account_set_bool(account, "auth_plain_in_clear", TRUE); - - jabber_auth_start_cyrus(account->gc->proto_data); -} - -static gboolean auth_pass_generic(JabberStream *js, PurpleRequestFields *fields) -{ + PurpleAccount *account; + JabberStream *js; const char *entry; gboolean remember; + /* The password prompt dialog doesn't get disposed if the account disconnects */ + if (!PURPLE_CONNECTION_IS_VALID(gc)) + return; + + account = purple_connection_get_account(gc); + js = purple_connection_get_protocol_data(gc); + entry = purple_request_fields_get_string(fields, "password"); remember = purple_request_fields_get_bool(fields, "remember"); if (!entry || !*entry) { - purple_notify_error(js->gc->account, NULL, _("Password is required to sign on."), NULL); - return FALSE; + purple_notify_error(account, NULL, _("Password is required to sign on."), NULL); + return; } if (remember) - purple_account_set_remember_password(js->gc->account, TRUE); - - purple_account_set_password(js->gc->account, entry); - - return TRUE; -} - -static void auth_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields) -{ - JabberStream *js; - - /* The password prompt dialog doesn't get disposed if the account disconnects */ - if (!PURPLE_CONNECTION_IS_VALID(conn)) - return; - - js = conn->proto_data; - - if (!auth_pass_generic(js, fields)) - return; + purple_account_set_remember_password(account, TRUE); - /* Rebuild our callbacks as we now have a password to offer */ - jabber_sasl_build_callbacks(js); - - /* Restart our connection */ - jabber_auth_start_cyrus(js); -} - -static void -auth_old_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields) -{ - JabberStream *js; - - /* The password prompt dialog doesn't get disposed if the account disconnects */ - if (!PURPLE_CONNECTION_IS_VALID(conn)) - return; - - js = conn->proto_data; - - if (!auth_pass_generic(js, fields)) - return; + purple_account_set_password(account, entry); /* Restart our connection */ jabber_auth_start_old(js); } - static void -auth_no_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields) +auth_no_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields) { - JabberStream *js; - /* The password prompt dialog doesn't get disposed if the account disconnects */ - if (!PURPLE_CONNECTION_IS_VALID(conn)) + if (!PURPLE_CONNECTION_IS_VALID(gc)) return; - js = conn->proto_data; - /* Disable the account as the user has canceled connecting */ - purple_account_set_enabled(conn->account, purple_core_get_ui(), FALSE); + purple_account_set_enabled(purple_connection_get_account(gc), purple_core_get_ui(), FALSE); } - -static void jabber_auth_start_cyrus(JabberStream *js) -{ - const char *clientout = NULL; - char *enc_out; - unsigned coutlen = 0; - xmlnode *auth; - sasl_security_properties_t secprops; - gboolean again; - gboolean plaintext = TRUE; - - /* Set up security properties and options */ - secprops.min_ssf = 0; - secprops.security_flags = SASL_SEC_NOANONYMOUS; - - if (!jabber_stream_is_ssl(js)) { - secprops.max_ssf = -1; - secprops.maxbufsize = 4096; - plaintext = purple_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE); - if (!plaintext) - secprops.security_flags |= SASL_SEC_NOPLAINTEXT; - } else { - secprops.max_ssf = 0; - secprops.maxbufsize = 0; - plaintext = TRUE; - } - secprops.property_names = 0; - secprops.property_values = 0; - - do { - again = FALSE; - - js->sasl_state = sasl_client_new("xmpp", js->serverFQDN, NULL, NULL, js->sasl_cb, 0, &js->sasl); - if (js->sasl_state==SASL_OK) { - sasl_setprop(js->sasl, SASL_SEC_PROPS, &secprops); - purple_debug_info("sasl", "Mechs found: %s\n", js->sasl_mechs->str); - js->sasl_state = sasl_client_start(js->sasl, js->sasl_mechs->str, NULL, &clientout, &coutlen, &js->current_mech); - } - switch (js->sasl_state) { - /* Success */ - case SASL_OK: - case SASL_CONTINUE: - break; - case SASL_NOMECH: - /* No mechanisms have offered to help */ - - /* Firstly, if we don't have a password try - * to get one - */ - - if (!purple_account_get_password(js->gc->account)) { - purple_account_request_password(js->gc->account, G_CALLBACK(auth_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc); - return; - - /* If we've got a password, but aren't sending - * it in plaintext, see if we can turn on - * plaintext auth - */ - } else if (!plaintext) { - char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"), - js->gc->account->username); - purple_request_yes_no(js->gc, _("Plaintext Authentication"), - _("Plaintext Authentication"), - msg, - 1, js->gc->account, NULL, NULL, js->gc->account, - allow_cyrus_plaintext_auth, - disallow_plaintext_auth); - g_free(msg); - return; - - } else { - /* We have no mechs which can work. - * Try falling back on the old jabber:iq:auth method. We get here if the server supports - * one or more sasl mechs, we are compiled with cyrus-sasl support, but we support or can connect with none of - * the offerred mechs. jabberd 2.0 w/ SASL and Apple's iChat Server 10.5 both handle and expect - * jabber:iq:auth in this situation. iChat Server in particular offers SASL GSSAPI by default, which is often - * not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails. - * - * Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However, - * I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms. - * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers - * which would connect without issue otherwise. -evands - */ - js->auth_type = JABBER_AUTH_IQ_AUTH; - jabber_auth_start_old(js); - return; - } - /* not reached */ - break; - - /* Fatal errors. Give up and go home */ - case SASL_BADPARAM: - case SASL_NOMEM: - break; - - /* For everything else, fail the mechanism and try again */ - default: - purple_debug_info("sasl", "sasl_state is %d, failing the mech and trying again\n", js->sasl_state); - - /* - * DAA: is this right? - * The manpage says that "mech" will contain the chosen mechanism on success. - * Presumably, if we get here that isn't the case and we shouldn't try again? - * I suspect that this never happens. - */ - /* - * SXW: Yes, this is right. What this handles is the situation where a - * mechanism, say GSSAPI, is tried. If that mechanism fails, it may be - * due to mechanism specific issues, so we want to try one of the other - * supported mechanisms. This code handles that case - */ - if (js->current_mech && *js->current_mech) { - char *pos; - if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) { - g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech)); - } - /* Remove space which separated this mech from the next */ - if ((js->sasl_mechs->str)[0] == ' ') { - g_string_erase(js->sasl_mechs, 0, 1); - } - again = TRUE; - } - - sasl_dispose(&js->sasl); - } - } while (again); - - if (js->sasl_state == SASL_CONTINUE || js->sasl_state == SASL_OK) { - auth = xmlnode_new("auth"); - xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl"); - xmlnode_set_attrib(auth, "mechanism", js->current_mech); - - xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth"); - xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true"); - - if (clientout) { - if (coutlen == 0) { - xmlnode_insert_data(auth, "=", -1); - } else { - enc_out = purple_base64_encode((unsigned char*)clientout, coutlen); - xmlnode_insert_data(auth, enc_out, -1); - g_free(enc_out); - } - } - jabber_send(js, auth); - xmlnode_free(auth); - } else { - purple_connection_error_reason(js->gc, - PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, - _("SASL authentication failed")); - } -} - -static int -jabber_sasl_cb_log(void *context, int level, const char *message) -{ - if(level <= SASL_LOG_TRACE) - purple_debug_info("sasl", "%s\n", message); - - return SASL_OK; -} - -void -jabber_sasl_build_callbacks(JabberStream *js) -{ - int id; - - /* Set up our callbacks structure */ - if (js->sasl_cb == NULL) - js->sasl_cb = g_new0(sasl_callback_t,6); - - id = 0; - js->sasl_cb[id].id = SASL_CB_GETREALM; - js->sasl_cb[id].proc = jabber_sasl_cb_realm; - js->sasl_cb[id].context = (void *)js; - id++; - - js->sasl_cb[id].id = SASL_CB_AUTHNAME; - js->sasl_cb[id].proc = jabber_sasl_cb_simple; - js->sasl_cb[id].context = (void *)js; - id++; - - js->sasl_cb[id].id = SASL_CB_USER; - js->sasl_cb[id].proc = jabber_sasl_cb_simple; - js->sasl_cb[id].context = (void *)js; - id++; - - if (purple_account_get_password(js->gc->account) != NULL ) { - js->sasl_cb[id].id = SASL_CB_PASS; - js->sasl_cb[id].proc = jabber_sasl_cb_secret; - js->sasl_cb[id].context = (void *)js; - id++; - } - - js->sasl_cb[id].id = SASL_CB_LOG; - js->sasl_cb[id].proc = jabber_sasl_cb_log; - js->sasl_cb[id].context = (void*)js; - id++; - - js->sasl_cb[id].id = SASL_CB_LIST_END; -} - #endif void jabber_auth_start(JabberStream *js, xmlnode *packet) { -#ifndef HAVE_CYRUS_SASL - gboolean digest_md5 = FALSE, plain=FALSE; -#endif - + GSList *mechanisms = NULL; + GSList *l; + xmlnode *response = NULL; xmlnode *mechs, *mechnode; - + JabberSaslState state; + char *msg = NULL; if(js->registration) { jabber_register_start(js); @@ -490,7 +173,6 @@ } mechs = xmlnode_get_child(packet, "mechanisms"); - if(!mechs) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, @@ -498,76 +180,53 @@ return; } -#ifdef HAVE_CYRUS_SASL - js->sasl_mechs = g_string_new(""); -#endif - for(mechnode = xmlnode_get_child(mechs, "mechanism"); mechnode; mechnode = xmlnode_get_next_twin(mechnode)) { char *mech_name = xmlnode_get_data(mechnode); -#ifdef HAVE_CYRUS_SASL - /* Don't include Google Talk's X-GOOGLE-TOKEN mechanism, as we will not - * support it and including it gives a false fall-back to other mechs offerred, - * leading to incorrect error handling. - */ - if (purple_strequal(mech_name, "X-GOOGLE-TOKEN")) { - g_free(mech_name); - continue; - } - g_string_append(js->sasl_mechs, mech_name); - g_string_append_c(js->sasl_mechs, ' '); -#else - if (purple_strequal(mech_name, "DIGEST-MD5")) - digest_md5 = TRUE; - else if (purple_strequal(mech_name, "PLAIN")) - plain = TRUE; -#endif - g_free(mech_name); + if (mech_name && *mech_name) + mechanisms = g_slist_prepend(mechanisms, mech_name); + else if (mech_name) + g_free(mech_name); + } -#ifdef HAVE_CYRUS_SASL - js->auth_type = JABBER_AUTH_CYRUS; - - jabber_sasl_build_callbacks(js); - - jabber_auth_start_cyrus(js); -#else + for (l = auth_mechs; l; l = l->next) { + JabberSaslMech *possible = l->data; - if(digest_md5) { - xmlnode *auth; - - js->auth_type = JABBER_AUTH_DIGEST_MD5; - auth = xmlnode_new("auth"); - xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl"); - xmlnode_set_attrib(auth, "mechanism", "DIGEST-MD5"); + /* Is this the Cyrus SASL mechanism? */ + if (g_str_equal(possible->name, "*")) { + js->auth_mech = possible; + break; + } - jabber_send(js, auth); - xmlnode_free(auth); - } else if(plain) { - js->auth_type = JABBER_AUTH_PLAIN; + /* Can we find this mechanism in the server's list? */ + if (g_slist_find_custom(mechanisms, possible->name, (GCompareFunc)strcmp)) { + js->auth_mech = possible; + break; + } + } - if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE)) { - char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"), - js->gc->account->username); - purple_request_yes_no(js->gc, _("Plaintext Authentication"), - _("Plaintext Authentication"), - msg, - 1, - purple_connection_get_account(js->gc), NULL, NULL, - purple_connection_get_account(js->gc), allow_plaintext_auth, - disallow_plaintext_auth); - g_free(msg); - return; - } - finish_plaintext_authentication(js); - } else { + if (js->auth_mech == NULL) { + /* Found no good mechanisms... */ purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, _("Server does not use any supported authentication method")); + return; } -#endif + + state = js->auth_mech->start(js, mechs, &response, &msg); + if (state == JABBER_SASL_STATE_FAIL) { + purple_connection_error_reason(js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + msg ? msg : _("Unknown Error")); + } else if (response) { + jabber_send(js, response); + xmlnode_free(response); + } + + g_free(msg); } static void auth_old_result_cb(JabberStream *js, const char *from, @@ -578,19 +237,22 @@ jabber_stream_set_state(js, JABBER_STREAM_POST_AUTH); jabber_disco_items_server(js); } else { + PurpleAccount *account; PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; char *msg = jabber_parse_error(js, packet, &reason); xmlnode *error; const char *err_code; + account = purple_connection_get_account(js->gc); + /* FIXME: Why is this not in jabber_parse_error? */ if((error = xmlnode_get_child(packet, "error")) && (err_code = xmlnode_get_attrib(error, "code")) && g_str_equal(err_code, "401")) { reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; /* Clear the pasword if it isn't being saved */ - if (!purple_account_get_remember_password(js->gc->account)) - purple_account_set_password(js->gc->account, NULL); + if (!purple_account_get_remember_password(account)) + purple_account_set_password(account, NULL); } purple_connection_error_reason(js->gc, reason, msg); @@ -663,16 +325,17 @@ jabber_iq_send(iq); } else if(xmlnode_get_child(query, "password")) { - if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(js->gc->account, + PurpleAccount *account = purple_connection_get_account(js->gc); + if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(account, "auth_plain_in_clear", FALSE)) { char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"), - js->gc->account->username); + purple_account_get_username(account)); purple_request_yes_no(js->gc, _("Plaintext Authentication"), _("Plaintext Authentication"), msg, 1, - purple_connection_get_account(js->gc), NULL, NULL, - purple_connection_get_account(js->gc), allow_plaintext_auth, + account, NULL, NULL, + account, allow_plaintext_auth, disallow_plaintext_auth); g_free(msg); return; @@ -689,22 +352,30 @@ void jabber_auth_start_old(JabberStream *js) { + PurpleAccount *account; JabberIq *iq; xmlnode *query, *username; + account = purple_connection_get_account(js->gc); + /* * We can end up here without encryption if the server doesn't support * and we're not using old-style SSL. If the user * is requiring SSL/TLS, we need to enforce it. */ if (!jabber_stream_is_ssl(js) && - purple_account_get_bool(purple_connection_get_account(js->gc), "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { + purple_account_get_bool(account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, _("You require encryption, but it is not available on this server.")); return; } + if (js->registration) { + jabber_register_start(js); + return; + } + /* * IQ Auth doesn't have support for resource binding, so we need to pick a * default resource so it will work properly. jabberd14 throws an error and @@ -721,8 +392,8 @@ * password prompting here */ - if (!purple_account_get_password(js->gc->account)) { - purple_account_request_password(js->gc->account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc); + if (!purple_account_get_password(account)) { + purple_account_request_password(account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc); return; } #endif @@ -737,352 +408,65 @@ jabber_iq_send(iq); } -/* Parts of this algorithm are inspired by stuff in libgsasl */ -static GHashTable* parse_challenge(const char *challenge) -{ - const char *token_start, *val_start, *val_end, *cur; - GHashTable *ret = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_free); - - cur = challenge; - while(*cur != '\0') { - /* Find the end of the token */ - gboolean in_quotes = FALSE; - char *name, *value = NULL; - token_start = cur; - while(*cur != '\0' && (in_quotes || (!in_quotes && *cur != ','))) { - if (*cur == '"') - in_quotes = !in_quotes; - cur++; - } - - /* Find start of value. */ - val_start = strchr(token_start, '='); - if (val_start == NULL || val_start > cur) - val_start = cur; - - if (token_start != val_start) { - name = g_strndup(token_start, val_start - token_start); - - if (val_start != cur) { - val_start++; - while (val_start != cur && (*val_start == ' ' || *val_start == '\t' - || *val_start == '\r' || *val_start == '\n' - || *val_start == '"')) - val_start++; - - val_end = cur; - while (val_end != val_start && (*val_end == ' ' || *val_end == ',' || *val_end == '\t' - || *val_end == '\r' || *val_end == '\n' - || *val_end == '"' || *val_end == '\0')) - val_end--; - - if (val_start != val_end) - value = g_strndup(val_start, val_end - val_start + 1); - } - - g_hash_table_replace(ret, name, value); - } - - /* Find the start of the next token, if there is one */ - if (*cur != '\0') { - cur++; - while (*cur == ' ' || *cur == ',' || *cur == '\t' - || *cur == '\r' || *cur == '\n') - cur++; - } - } - - return ret; -} - -static char * -generate_response_value(JabberID *jid, const char *passwd, const char *nonce, - const char *cnonce, const char *a2, const char *realm) -{ - PurpleCipher *cipher; - PurpleCipherContext *context; - guchar result[16]; - size_t a1len; - - gchar *a1, *convnode=NULL, *convpasswd = NULL, *ha1, *ha2, *kd, *x, *z; - - if((convnode = g_convert(jid->node, -1, "iso-8859-1", "utf-8", - NULL, NULL, NULL)) == NULL) { - convnode = g_strdup(jid->node); - } - if(passwd && ((convpasswd = g_convert(passwd, -1, "iso-8859-1", - "utf-8", NULL, NULL, NULL)) == NULL)) { - convpasswd = g_strdup(passwd); - } - - cipher = purple_ciphers_find_cipher("md5"); - context = purple_cipher_context_new(cipher, NULL); - - x = g_strdup_printf("%s:%s:%s", convnode, realm, convpasswd ? convpasswd : ""); - purple_cipher_context_append(context, (const guchar *)x, strlen(x)); - purple_cipher_context_digest(context, sizeof(result), result, NULL); - - a1 = g_strdup_printf("xxxxxxxxxxxxxxxx:%s:%s", nonce, cnonce); - a1len = strlen(a1); - g_memmove(a1, result, 16); - - purple_cipher_context_reset(context, NULL); - purple_cipher_context_append(context, (const guchar *)a1, a1len); - purple_cipher_context_digest(context, sizeof(result), result, NULL); - - ha1 = purple_base16_encode(result, 16); - - purple_cipher_context_reset(context, NULL); - purple_cipher_context_append(context, (const guchar *)a2, strlen(a2)); - purple_cipher_context_digest(context, sizeof(result), result, NULL); - - ha2 = purple_base16_encode(result, 16); - - kd = g_strdup_printf("%s:%s:00000001:%s:auth:%s", ha1, nonce, cnonce, ha2); - - purple_cipher_context_reset(context, NULL); - purple_cipher_context_append(context, (const guchar *)kd, strlen(kd)); - purple_cipher_context_digest(context, sizeof(result), result, NULL); - purple_cipher_context_destroy(context); - - z = purple_base16_encode(result, 16); - - g_free(convnode); - g_free(convpasswd); - g_free(x); - g_free(a1); - g_free(ha1); - g_free(ha2); - g_free(kd); - - return z; -} - void jabber_auth_handle_challenge(JabberStream *js, xmlnode *packet) { - - if(js->auth_type == JABBER_AUTH_DIGEST_MD5) { - char *enc_in = xmlnode_get_data(packet); - char *dec_in; - char *enc_out; - GHashTable *parts; - - if(!enc_in) { - purple_connection_error_reason(js->gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Invalid response from server")); - return; - } - - dec_in = (char *)purple_base64_decode(enc_in, NULL); - purple_debug_misc("jabber", "decoded challenge (%" - G_GSIZE_FORMAT "): %s\n", strlen(dec_in), dec_in); - - parts = parse_challenge(dec_in); - - - if (g_hash_table_lookup(parts, "rspauth")) { - char *rspauth = g_hash_table_lookup(parts, "rspauth"); - - - if (rspauth && purple_strequal(rspauth, js->expected_rspauth)) { - jabber_send_raw(js, - "", - -1); - } else { - purple_connection_error_reason(js->gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Invalid challenge from server")); - } - g_free(js->expected_rspauth); - js->expected_rspauth = NULL; - } else { - /* assemble a response, and send it */ - /* see RFC 2831 */ - char *realm; - char *nonce; - - /* Make sure the auth string contains everything that should be there. - This isn't everything in RFC2831, but it is what we need. */ - - nonce = g_hash_table_lookup(parts, "nonce"); - - /* we're actually supposed to prompt the user for a realm if - * the server doesn't send one, but that really complicates things, - * so i'm not gonna worry about it until is poses a problem to - * someone, or I get really bored */ - realm = g_hash_table_lookup(parts, "realm"); - if(!realm) - realm = js->user->domain; - - if (nonce == NULL || realm == NULL) - purple_connection_error_reason(js->gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Invalid challenge from server")); - else { - GString *response = g_string_new(""); - char *a2; - char *auth_resp; - char *buf; - char *cnonce; - - cnonce = g_strdup_printf("%x%u%x", g_random_int(), (int)time(NULL), - g_random_int()); - - a2 = g_strdup_printf("AUTHENTICATE:xmpp/%s", realm); - auth_resp = generate_response_value(js->user, - purple_connection_get_password(js->gc), nonce, cnonce, a2, realm); - g_free(a2); - - a2 = g_strdup_printf(":xmpp/%s", realm); - js->expected_rspauth = generate_response_value(js->user, - purple_connection_get_password(js->gc), nonce, cnonce, a2, realm); - g_free(a2); - - g_string_append_printf(response, "username=\"%s\"", js->user->node); - g_string_append_printf(response, ",realm=\"%s\"", realm); - g_string_append_printf(response, ",nonce=\"%s\"", nonce); - g_string_append_printf(response, ",cnonce=\"%s\"", cnonce); - g_string_append_printf(response, ",nc=00000001"); - g_string_append_printf(response, ",qop=auth"); - g_string_append_printf(response, ",digest-uri=\"xmpp/%s\"", realm); - g_string_append_printf(response, ",response=%s", auth_resp); - g_string_append_printf(response, ",charset=utf-8"); + const char *ns = xmlnode_get_namespace(packet); - g_free(auth_resp); - g_free(cnonce); - - enc_out = purple_base64_encode((guchar *)response->str, response->len); - - purple_debug_misc("jabber", "decoded response (%" - G_GSIZE_FORMAT "): %s\n", - response->len, response->str); - - buf = g_strdup_printf("%s", enc_out); - - jabber_send_raw(js, buf, -1); - - g_free(buf); - - g_free(enc_out); - - g_string_free(response, TRUE); - } - } - - g_free(enc_in); - g_free(dec_in); - g_hash_table_destroy(parts); - } -#ifdef HAVE_CYRUS_SASL - else if (js->auth_type == JABBER_AUTH_CYRUS) { - char *enc_in = xmlnode_get_data(packet); - unsigned char *dec_in; - char *enc_out; - const char *c_out; - unsigned int clen; - gsize declen; - xmlnode *response; - - dec_in = purple_base64_decode(enc_in, &declen); - - js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen, - NULL, &c_out, &clen); - g_free(enc_in); - g_free(dec_in); - if (js->sasl_state != SASL_CONTINUE && js->sasl_state != SASL_OK) { - gchar *tmp = g_strdup_printf(_("SASL error: %s"), - sasl_errdetail(js->sasl)); - purple_debug_error("jabber", "Error is %d : %s\n", - js->sasl_state, sasl_errdetail(js->sasl)); - purple_connection_error_reason(js->gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); - g_free(tmp); - return; - } else { - response = xmlnode_new("response"); - xmlnode_set_namespace(response, "urn:ietf:params:xml:ns:xmpp-sasl"); - if (clen > 0) { - /* Cyrus SASL 2.1.22 appears to contain code to add the charset - * to the response for DIGEST-MD5 but there is no possibility - * it will be executed. - * - * My reading of the digestmd5 plugin indicates the username and - * realm are always encoded in UTF-8 (they seem to be the values - * we pass in), so we need to ensure charset=utf-8 is set. - */ - if (!purple_strequal(js->current_mech, "DIGEST-MD5") || - strstr(c_out, ",charset=")) - /* If we're not using DIGEST-MD5 or Cyrus SASL is fixed */ - enc_out = purple_base64_encode((unsigned char*)c_out, clen); - else { - char *tmp = g_strdup_printf("%s,charset=utf-8", c_out); - enc_out = purple_base64_encode((unsigned char*)tmp, clen + 14); - g_free(tmp); - } - - xmlnode_insert_data(response, enc_out, -1); - g_free(enc_out); - } - jabber_send(js, response); - xmlnode_free(response); - } - } -#endif -} - -void jabber_auth_handle_success(JabberStream *js, xmlnode *packet) -{ - const char *ns = xmlnode_get_namespace(packet); -#ifdef HAVE_CYRUS_SASL - const void *x; -#endif - - if (!purple_strequal(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) { + if (!purple_strequal(ns, NS_XMPP_SASL)) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Invalid response from server")); return; } -#ifdef HAVE_CYRUS_SASL - /* The SASL docs say that if the client hasn't returned OK yet, we - * should try one more round against it - */ - if (js->sasl_state != SASL_OK) { - char *enc_in = xmlnode_get_data(packet); - unsigned char *dec_in = NULL; - const char *c_out; - unsigned int clen; - gsize declen = 0; + if (js->auth_mech && js->auth_mech->handle_challenge) { + xmlnode *response = NULL; + char *msg = NULL; + JabberSaslState state = js->auth_mech->handle_challenge(js, packet, &response, &msg); + if (state == JABBER_SASL_STATE_FAIL) { + purple_connection_error_reason(js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + msg ? msg : _("Invalid challenge from server")); + } else if (response) { + jabber_send(js, response); + xmlnode_free(response); + } - if(enc_in != NULL) - dec_in = purple_base64_decode(enc_in, &declen); + g_free(msg); + } else + purple_debug_warning("jabber", "Received unexpected (and unhandled) \n"); +} - js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen, NULL, &c_out, &clen); +void jabber_auth_handle_success(JabberStream *js, xmlnode *packet) +{ + const char *ns = xmlnode_get_namespace(packet); - g_free(enc_in); - g_free(dec_in); + if (!purple_strequal(ns, NS_XMPP_SASL)) { + purple_connection_error_reason(js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server")); + return; + } + + if (js->auth_mech && js->auth_mech->handle_success) { + char *msg = NULL; + JabberSaslState state = js->auth_mech->handle_success(js, packet, &msg); - if (js->sasl_state != SASL_OK) { - /* This should never happen! */ + if (state == JABBER_SASL_STATE_FAIL) { purple_connection_error_reason(js->gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Invalid response from server")); - g_return_if_reached(); + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + msg ? msg : _("Invalid response from server")); + return; + } else if (state == JABBER_SASL_STATE_CONTINUE) { + purple_connection_error_reason(js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + msg ? msg : _("Server thinks authentication is complete, but client does not")); + return; } + + g_free(msg); } - /* If we've negotiated a security layer, we need to enable it */ - if (js->sasl) { - sasl_getprop(js->sasl, SASL_SSF, &x); - if (*(int *)x > 0) { - sasl_getprop(js->sasl, SASL_MAXOUTBUF, &x); - js->sasl_maxbuf = *(int *)x; - } - } -#endif /* * The stream will be reinitialized later in jabber_recv_cb_ssl() or @@ -1095,31 +479,23 @@ void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet) { PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; - char *msg; + char *msg = NULL; -#ifdef HAVE_CYRUS_SASL - if(js->auth_fail_count++ < 5) { - if (js->current_mech && *js->current_mech) { - char *pos; - if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) { - g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech)); - } - /* Remove space which separated this mech from the next */ - if ((js->sasl_mechs->str)[0] == ' ') { - g_string_erase(js->sasl_mechs, 0, 1); - } - } - if (*js->sasl_mechs->str) { - /* If we have remaining mechs to try, do so */ - sasl_dispose(&js->sasl); + if (js->auth_mech && js->auth_mech->handle_failure) { + xmlnode *stanza = NULL; + JabberSaslState state = js->auth_mech->handle_failure(js, packet, &stanza, &msg); - jabber_auth_start_cyrus(js); + if (state != JABBER_SASL_STATE_FAIL && stanza) { + jabber_send(js, stanza); + xmlnode_free(stanza); return; } } -#endif - msg = jabber_parse_error(js, packet, &reason); - if(!msg) { + + if (!msg) + msg = jabber_parse_error(js, packet, &reason); + + if (!msg) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Invalid response from server")); @@ -1128,3 +504,39 @@ g_free(msg); } } + +static gint compare_mech(gconstpointer a, gconstpointer b) +{ + const JabberSaslMech *mech_a = a; + const JabberSaslMech *mech_b = b; + + /* higher priority comes *before* lower priority in the list */ + if (mech_a->priority > mech_b->priority) + return -1; + else if (mech_a->priority < mech_b->priority) + return 1; + /* This really shouldn't happen */ + return 0; +} + +void jabber_auth_init(void) +{ + JabberSaslMech **tmp; + gint count, i; + + auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_plain_mech(), compare_mech); + auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_digest_md5_mech(), compare_mech); +#ifdef HAVE_CYRUS_SASL + auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_cyrus_mech(), compare_mech); +#endif + + tmp = jabber_auth_get_scram_mechs(&count); + for (i = 0; i < count; ++i) + auth_mechs = g_slist_insert_sorted(auth_mechs, tmp[i], compare_mech); +} + +void jabber_auth_uninit(void) +{ + g_slist_free(auth_mechs); + auth_mechs = NULL; +} diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/auth.h --- a/libpurple/protocols/jabber/auth.h Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/auth.h Fri Jan 08 20:11:49 2010 +0000 @@ -24,9 +24,27 @@ #ifndef PURPLE_JABBER_AUTH_H_ #define PURPLE_JABBER_AUTH_H_ +typedef struct _JabberSaslMech JabberSaslMech; + #include "jabber.h" #include "xmlnode.h" +typedef enum { + JABBER_SASL_STATE_FAIL = -1, /* Abort, Retry, Fail? */ + JABBER_SASL_STATE_OK = 0, /* Hooray! */ + JABBER_SASL_STATE_CONTINUE = 1 /* More authentication required */ +} JabberSaslState; + +struct _JabberSaslMech { + gint8 priority; /* Higher priority will be tried before lower priority */ + const gchar *name; + JabberSaslState (*start)(JabberStream *js, xmlnode *mechanisms, xmlnode **reply, char **msg); + JabberSaslState (*handle_challenge)(JabberStream *js, xmlnode *packet, xmlnode **reply, char **msg); + JabberSaslState (*handle_success)(JabberStream *js, xmlnode *packet, char **msg); + JabberSaslState (*handle_failure)(JabberStream *js, xmlnode *packet, xmlnode **reply, char **msg); + void (*dispose)(JabberStream *js); +}; + gboolean jabber_process_starttls(JabberStream *js, xmlnode *packet); void jabber_auth_start(JabberStream *js, xmlnode *packet); void jabber_auth_start_old(JabberStream *js); @@ -34,4 +52,14 @@ void jabber_auth_handle_success(JabberStream *js, xmlnode *packet); void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet); +JabberSaslMech *jabber_auth_get_plain_mech(void); +JabberSaslMech *jabber_auth_get_digest_md5_mech(void); +JabberSaslMech **jabber_auth_get_scram_mechs(gint *count); +#ifdef HAVE_CYRUS_SASL +JabberSaslMech *jabber_auth_get_cyrus_mech(void); +#endif + +void jabber_auth_init(void); +void jabber_auth_uninit(void); + #endif /* PURPLE_JABBER_AUTH_H_ */ diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/auth_cyrus.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/auth_cyrus.c Fri Jan 08 20:11:49 2010 +0000 @@ -0,0 +1,564 @@ +/* + * purple - Jabber Protocol Plugin + * + * 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 "core.h" +#include "debug.h" +#include "request.h" + +#include "auth.h" +#include "jabber.h" + +static JabberSaslState jabber_auth_start_cyrus(JabberStream *js, xmlnode **reply, + char **error); +static void jabber_sasl_build_callbacks(JabberStream *); + +static void disallow_plaintext_auth(PurpleAccount *account) +{ + purple_connection_error_reason(purple_account_get_connection(account), + PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, + _("Server requires plaintext authentication over an unencrypted stream")); +} + +static void start_cyrus_wrapper(JabberStream *js) +{ + char *error = NULL; + xmlnode *response = NULL; + JabberSaslState state = jabber_auth_start_cyrus(js, &response, &error); + + if (state == JABBER_SASL_STATE_FAIL) { + purple_connection_error_reason(js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + error); + g_free(error); + } else if (response) { + jabber_send(js, response); + xmlnode_free(response); + } +} + + +/* Callbacks for Cyrus SASL */ + +static int jabber_sasl_cb_realm(void *ctx, int id, const char **avail, const char **result) +{ + JabberStream *js = ctx; + + if (id != SASL_CB_GETREALM || !result) return SASL_BADPARAM; + + *result = js->user->domain; + + return SASL_OK; +} + +static int jabber_sasl_cb_simple(void *ctx, int id, const char **res, unsigned *len) +{ + JabberStream *js = ctx; + + switch(id) { + case SASL_CB_AUTHNAME: + *res = js->user->node; + break; + case SASL_CB_USER: + *res = ""; + break; + default: + return SASL_BADPARAM; + } + if (len) *len = strlen((char *)*res); + return SASL_OK; +} + +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; + static sasl_secret_t *x = NULL; + + account = purple_connection_get_account(js->gc); + pw = purple_account_get_password(account); + + if (!conn || !secret || id != SASL_CB_PASS) + return SASL_BADPARAM; + + len = strlen(pw); + x = (sasl_secret_t *) realloc(x, sizeof(sasl_secret_t) + len); + + if (!x) + return SASL_NOMEM; + + x->len = len; + strcpy((char*)x->data, pw); + + *secret = x; + return SASL_OK; +} + +static void allow_cyrus_plaintext_auth(PurpleAccount *account) +{ + PurpleConnection *gc; + JabberStream *js; + + gc = purple_account_get_connection(account); + js = purple_connection_get_protocol_data(gc); + + purple_account_set_bool(account, "auth_plain_in_clear", TRUE); + + start_cyrus_wrapper(js); +} + +static void auth_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields) +{ + PurpleAccount *account; + JabberStream *js; + const char *entry; + gboolean remember; + + /* The password prompt dialog doesn't get disposed if the account disconnects */ + if (!PURPLE_CONNECTION_IS_VALID(gc)) + return; + + account = purple_connection_get_account(gc); + js = purple_connection_get_protocol_data(gc); + + entry = purple_request_fields_get_string(fields, "password"); + remember = purple_request_fields_get_bool(fields, "remember"); + + if (!entry || !*entry) + { + purple_notify_error(account, NULL, _("Password is required to sign on."), NULL); + return; + } + + if (remember) + purple_account_set_remember_password(account, TRUE); + + purple_account_set_password(account, entry); + + /* Rebuild our callbacks as we now have a password to offer */ + jabber_sasl_build_callbacks(js); + + /* Restart our negotiation */ + start_cyrus_wrapper(js); +} + +static void +auth_no_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields) +{ + PurpleAccount *account; + JabberStream *js; + + /* The password prompt dialog doesn't get disposed if the account disconnects */ + if (!PURPLE_CONNECTION_IS_VALID(gc)) + return; + + account = purple_connection_get_account(gc); + js = purple_connection_get_protocol_data(gc); + + /* Disable the account as the user has canceled connecting */ + purple_account_set_enabled(account, purple_core_get_ui(), FALSE); +} + +static JabberSaslState +jabber_auth_start_cyrus(JabberStream *js, xmlnode **reply, char **error) +{ + PurpleAccount *account; + const char *clientout = NULL; + char *enc_out; + unsigned coutlen = 0; + sasl_security_properties_t secprops; + gboolean again; + gboolean plaintext = TRUE; + + /* Set up security properties and options */ + secprops.min_ssf = 0; + secprops.security_flags = SASL_SEC_NOANONYMOUS; + + account = purple_connection_get_account(js->gc); + + if (!jabber_stream_is_ssl(js)) { + secprops.max_ssf = -1; + secprops.maxbufsize = 4096; + plaintext = purple_account_get_bool(account, "auth_plain_in_clear", FALSE); + if (!plaintext) + secprops.security_flags |= SASL_SEC_NOPLAINTEXT; + } else { + secprops.max_ssf = 0; + secprops.maxbufsize = 0; + plaintext = TRUE; + } + secprops.property_names = 0; + secprops.property_values = 0; + + do { + again = FALSE; + + js->sasl_state = sasl_client_new("xmpp", js->serverFQDN, NULL, NULL, js->sasl_cb, 0, &js->sasl); + if (js->sasl_state==SASL_OK) { + sasl_setprop(js->sasl, SASL_SEC_PROPS, &secprops); + purple_debug_info("sasl", "Mechs found: %s\n", js->sasl_mechs->str); + js->sasl_state = sasl_client_start(js->sasl, js->sasl_mechs->str, NULL, &clientout, &coutlen, &js->current_mech); + } + switch (js->sasl_state) { + /* Success */ + case SASL_OK: + case SASL_CONTINUE: + break; + case SASL_NOMECH: + /* No mechanisms have offered to help */ + + /* Firstly, if we don't have a password try + * to get one + */ + + if (!purple_account_get_password(account)) { + purple_account_request_password(account, G_CALLBACK(auth_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc); + return JABBER_SASL_STATE_CONTINUE; + + /* If we've got a password, but aren't sending + * it in plaintext, see if we can turn on + * plaintext auth + */ + } else if (!plaintext) { + char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"), + purple_account_get_username(account)); + purple_request_yes_no(js->gc, _("Plaintext Authentication"), + _("Plaintext Authentication"), + msg, + 1, account, NULL, NULL, account, + allow_cyrus_plaintext_auth, + disallow_plaintext_auth); + g_free(msg); + return JABBER_SASL_STATE_CONTINUE; + + } else { + /* We have no mechs which can work. + * Try falling back on the old jabber:iq:auth method. We get here if the server supports + * one or more sasl mechs, we are compiled with cyrus-sasl support, but we support or can connect with none of + * the offerred mechs. jabberd 2.0 w/ SASL and Apple's iChat Server 10.5 both handle and expect + * jabber:iq:auth in this situation. iChat Server in particular offers SASL GSSAPI by default, which is often + * not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails. + * + * Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However, + * I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms. + * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers + * which would connect without issue otherwise. -evands + */ + js->auth_mech = NULL; + jabber_auth_start_old(js); + return JABBER_SASL_STATE_CONTINUE; + } + /* not reached */ + break; + + /* Fatal errors. Give up and go home */ + case SASL_BADPARAM: + case SASL_NOMEM: + break; + + /* For everything else, fail the mechanism and try again */ + default: + purple_debug_info("sasl", "sasl_state is %d, failing the mech and trying again\n", js->sasl_state); + + /* + * DAA: is this right? + * The manpage says that "mech" will contain the chosen mechanism on success. + * Presumably, if we get here that isn't the case and we shouldn't try again? + * I suspect that this never happens. + */ + /* + * SXW: Yes, this is right. What this handles is the situation where a + * mechanism, say GSSAPI, is tried. If that mechanism fails, it may be + * due to mechanism specific issues, so we want to try one of the other + * supported mechanisms. This code handles that case + */ + if (js->current_mech && *js->current_mech) { + char *pos; + if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) { + g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech)); + } + /* Remove space which separated this mech from the next */ + if ((js->sasl_mechs->str)[0] == ' ') { + g_string_erase(js->sasl_mechs, 0, 1); + } + again = TRUE; + } + + sasl_dispose(&js->sasl); + } + } while (again); + + if (js->sasl_state == SASL_CONTINUE || js->sasl_state == SASL_OK) { + xmlnode *auth = xmlnode_new("auth"); + xmlnode_set_namespace(auth, NS_XMPP_SASL); + xmlnode_set_attrib(auth, "mechanism", js->current_mech); + + xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth"); + xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true"); + + if (clientout) { + if (coutlen == 0) { + xmlnode_insert_data(auth, "=", -1); + } else { + enc_out = purple_base64_encode((unsigned char*)clientout, coutlen); + xmlnode_insert_data(auth, enc_out, -1); + g_free(enc_out); + } + } + + *reply = auth; + return JABBER_SASL_STATE_CONTINUE; + } else { + *error = g_strdup(_("SASL authentication failed")); + return JABBER_SASL_STATE_FAIL; + } +} + +static int +jabber_sasl_cb_log(void *context, int level, const char *message) +{ + if(level <= SASL_LOG_TRACE) + purple_debug_info("sasl", "%s\n", message); + + return SASL_OK; +} + +static void +jabber_sasl_build_callbacks(JabberStream *js) +{ + PurpleAccount *account; + int id; + + /* Set up our callbacks structure */ + if (js->sasl_cb == NULL) + js->sasl_cb = g_new0(sasl_callback_t,6); + + id = 0; + js->sasl_cb[id].id = SASL_CB_GETREALM; + js->sasl_cb[id].proc = jabber_sasl_cb_realm; + js->sasl_cb[id].context = (void *)js; + id++; + + js->sasl_cb[id].id = SASL_CB_AUTHNAME; + js->sasl_cb[id].proc = jabber_sasl_cb_simple; + js->sasl_cb[id].context = (void *)js; + id++; + + js->sasl_cb[id].id = SASL_CB_USER; + js->sasl_cb[id].proc = jabber_sasl_cb_simple; + js->sasl_cb[id].context = (void *)js; + id++; + + account = purple_connection_get_account(js->gc); + if (purple_account_get_password(account) != NULL ) { + js->sasl_cb[id].id = SASL_CB_PASS; + js->sasl_cb[id].proc = jabber_sasl_cb_secret; + js->sasl_cb[id].context = (void *)js; + id++; + } + + js->sasl_cb[id].id = SASL_CB_LOG; + js->sasl_cb[id].proc = jabber_sasl_cb_log; + js->sasl_cb[id].context = (void*)js; + id++; + + js->sasl_cb[id].id = SASL_CB_LIST_END; +} + +static JabberSaslState +jabber_cyrus_start(JabberStream *js, xmlnode *mechanisms, + xmlnode **reply, char **error) +{ + xmlnode *mechnode; + + js->sasl_mechs = g_string_new(""); + + for(mechnode = xmlnode_get_child(mechanisms, "mechanism"); mechnode; + mechnode = xmlnode_get_next_twin(mechnode)) + { + char *mech_name = xmlnode_get_data(mechnode); + + if (!mech_name || !*mech_name) { + g_free(mech_name); + continue; + } + + /* Don't include Google Talk's X-GOOGLE-TOKEN mechanism, as we will not + * support it and including it gives a false fall-back to other mechs offerred, + * leading to incorrect error handling. + */ + if (g_str_equal(mech_name, "X-GOOGLE-TOKEN")) { + g_free(mech_name); + continue; + } + + g_string_append(js->sasl_mechs, mech_name); + g_string_append_c(js->sasl_mechs, ' '); + g_free(mech_name); + } + + jabber_sasl_build_callbacks(js); + return jabber_auth_start_cyrus(js, reply, error); +} + +static JabberSaslState +jabber_cyrus_handle_challenge(JabberStream *js, xmlnode *packet, + xmlnode **reply, char **error) +{ + char *enc_in = xmlnode_get_data(packet); + unsigned char *dec_in; + char *enc_out; + const char *c_out; + unsigned int clen; + gsize declen; + + dec_in = purple_base64_decode(enc_in, &declen); + + js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen, + NULL, &c_out, &clen); + g_free(enc_in); + g_free(dec_in); + if (js->sasl_state != SASL_CONTINUE && js->sasl_state != SASL_OK) { + gchar *tmp = g_strdup_printf(_("SASL error: %s"), + sasl_errdetail(js->sasl)); + purple_debug_error("jabber", "Error is %d : %s\n", + js->sasl_state, sasl_errdetail(js->sasl)); + *error = tmp; + return JABBER_SASL_STATE_FAIL; + } else { + xmlnode *response = xmlnode_new("response"); + xmlnode_set_namespace(response, NS_XMPP_SASL); + if (clen > 0) { + /* Cyrus SASL 2.1.22 appears to contain code to add the charset + * to the response for DIGEST-MD5 but there is no possibility + * it will be executed. + * + * My reading of the digestmd5 plugin indicates the username and + * realm are always encoded in UTF-8 (they seem to be the values + * we pass in), so we need to ensure charset=utf-8 is set. + */ + if (!purple_strequal(js->current_mech, "DIGEST-MD5") || + strstr(c_out, ",charset=")) + /* If we're not using DIGEST-MD5 or Cyrus SASL is fixed */ + enc_out = purple_base64_encode((unsigned char*)c_out, clen); + else { + char *tmp = g_strdup_printf("%s,charset=utf-8", c_out); + enc_out = purple_base64_encode((unsigned char*)tmp, clen + 14); + g_free(tmp); + } + + xmlnode_insert_data(response, enc_out, -1); + g_free(enc_out); + } + + *reply = response; + return JABBER_SASL_STATE_CONTINUE; + } +} + +static JabberSaslState +jabber_cyrus_handle_success(JabberStream *js, xmlnode *packet, + char **error) +{ + const void *x; + + /* The SASL docs say that if the client hasn't returned OK yet, we + * should try one more round against it + */ + if (js->sasl_state != SASL_OK) { + char *enc_in = xmlnode_get_data(packet); + unsigned char *dec_in = NULL; + const char *c_out; + unsigned int clen; + gsize declen = 0; + + if(enc_in != NULL) + dec_in = purple_base64_decode(enc_in, &declen); + + js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen, NULL, &c_out, &clen); + + g_free(enc_in); + g_free(dec_in); + + if (js->sasl_state != SASL_OK) { + /* This should never happen! */ + *error = g_strdup(_("Invalid response from server")); + g_return_val_if_reached(JABBER_SASL_STATE_FAIL); + } + } + + /* If we've negotiated a security layer, we need to enable it */ + if (js->sasl) { + sasl_getprop(js->sasl, SASL_SSF, &x); + if (*(int *)x > 0) { + sasl_getprop(js->sasl, SASL_MAXOUTBUF, &x); + js->sasl_maxbuf = *(int *)x; + } + } + + return JABBER_SASL_STATE_OK; +} + +static JabberSaslState +jabber_cyrus_handle_failure(JabberStream *js, xmlnode *packet, + xmlnode **reply, char **error) +{ + if (js->auth_fail_count++ < 5) { + if (js->current_mech && *js->current_mech) { + char *pos; + if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) { + g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech)); + } + /* Remove space which separated this mech from the next */ + if ((js->sasl_mechs->str)[0] == ' ') { + g_string_erase(js->sasl_mechs, 0, 1); + } + } + if (*js->sasl_mechs->str) { + /* If we have remaining mechs to try, do so */ + sasl_dispose(&js->sasl); + + return jabber_auth_start_cyrus(js, reply, error); + } + } + + /* Nothing to send */ + return JABBER_SASL_STATE_FAIL; +} + +static JabberSaslMech cyrus_mech = { + 100, /* priority */ + "*", /* name; Cyrus provides a bunch of mechanisms, so use an invalid + * mechanism name (per rfc4422 3.1). */ + jabber_cyrus_start, + jabber_cyrus_handle_challenge, + jabber_cyrus_handle_success, + jabber_cyrus_handle_failure, + NULL, +}; + +JabberSaslMech *jabber_auth_get_cyrus_mech(void) +{ + return &cyrus_mech; +} diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/auth_digest_md5.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/auth_digest_md5.c Fri Jan 08 20:11:49 2010 +0000 @@ -0,0 +1,292 @@ +/* + * purple - Jabber Protocol Plugin + * + * 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 "debug.h" +#include "cipher.h" +#include "util.h" +#include "xmlnode.h" + +#include "auth.h" +#include "jabber.h" + +static JabberSaslState +digest_md5_start(JabberStream *js, xmlnode *packet, xmlnode **response, + char **error) +{ + xmlnode *auth = xmlnode_new("auth"); + xmlnode_set_namespace(auth, NS_XMPP_SASL); + xmlnode_set_attrib(auth, "mechanism", "DIGEST-MD5"); + + *response = auth; + return JABBER_SASL_STATE_CONTINUE; +} + +/* Parts of this algorithm are inspired by stuff in libgsasl */ +static GHashTable* parse_challenge(const char *challenge) +{ + const char *token_start, *val_start, *val_end, *cur; + GHashTable *ret = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + + cur = challenge; + while(*cur != '\0') { + /* Find the end of the token */ + gboolean in_quotes = FALSE; + char *name, *value = NULL; + token_start = cur; + while(*cur != '\0' && (in_quotes || (!in_quotes && *cur != ','))) { + if (*cur == '"') + in_quotes = !in_quotes; + cur++; + } + + /* Find start of value. */ + val_start = strchr(token_start, '='); + if (val_start == NULL || val_start > cur) + val_start = cur; + + if (token_start != val_start) { + name = g_strndup(token_start, val_start - token_start); + + if (val_start != cur) { + val_start++; + while (val_start != cur && (*val_start == ' ' || *val_start == '\t' + || *val_start == '\r' || *val_start == '\n' + || *val_start == '"')) + val_start++; + + val_end = cur; + while (val_end != val_start && (*val_end == ' ' || *val_end == ',' || *val_end == '\t' + || *val_end == '\r' || *val_end == '\n' + || *val_end == '"' || *val_end == '\0')) + val_end--; + + if (val_start != val_end) + value = g_strndup(val_start, val_end - val_start + 1); + } + + g_hash_table_replace(ret, name, value); + } + + /* Find the start of the next token, if there is one */ + if (*cur != '\0') { + cur++; + while (*cur == ' ' || *cur == ',' || *cur == '\t' + || *cur == '\r' || *cur == '\n') + cur++; + } + } + + return ret; +} + +static char * +generate_response_value(JabberID *jid, const char *passwd, const char *nonce, + const char *cnonce, const char *a2, const char *realm) +{ + PurpleCipher *cipher; + PurpleCipherContext *context; + guchar result[16]; + size_t a1len; + + gchar *a1, *convnode=NULL, *convpasswd = NULL, *ha1, *ha2, *kd, *x, *z; + + if((convnode = g_convert(jid->node, -1, "iso-8859-1", "utf-8", + NULL, NULL, NULL)) == NULL) { + convnode = g_strdup(jid->node); + } + if(passwd && ((convpasswd = g_convert(passwd, -1, "iso-8859-1", + "utf-8", NULL, NULL, NULL)) == NULL)) { + convpasswd = g_strdup(passwd); + } + + cipher = purple_ciphers_find_cipher("md5"); + context = purple_cipher_context_new(cipher, NULL); + + x = g_strdup_printf("%s:%s:%s", convnode, realm, convpasswd ? convpasswd : ""); + purple_cipher_context_append(context, (const guchar *)x, strlen(x)); + purple_cipher_context_digest(context, sizeof(result), result, NULL); + + a1 = g_strdup_printf("xxxxxxxxxxxxxxxx:%s:%s", nonce, cnonce); + a1len = strlen(a1); + g_memmove(a1, result, 16); + + purple_cipher_context_reset(context, NULL); + purple_cipher_context_append(context, (const guchar *)a1, a1len); + purple_cipher_context_digest(context, sizeof(result), result, NULL); + + ha1 = purple_base16_encode(result, 16); + + purple_cipher_context_reset(context, NULL); + purple_cipher_context_append(context, (const guchar *)a2, strlen(a2)); + purple_cipher_context_digest(context, sizeof(result), result, NULL); + + ha2 = purple_base16_encode(result, 16); + + kd = g_strdup_printf("%s:%s:00000001:%s:auth:%s", ha1, nonce, cnonce, ha2); + + purple_cipher_context_reset(context, NULL); + purple_cipher_context_append(context, (const guchar *)kd, strlen(kd)); + purple_cipher_context_digest(context, sizeof(result), result, NULL); + purple_cipher_context_destroy(context); + + z = purple_base16_encode(result, 16); + + g_free(convnode); + g_free(convpasswd); + g_free(x); + g_free(a1); + g_free(ha1); + g_free(ha2); + g_free(kd); + + return z; +} + +static JabberSaslState +digest_md5_handle_challenge(JabberStream *js, xmlnode *packet, + xmlnode **response, char **msg) +{ + xmlnode *reply = NULL; + char *enc_in = xmlnode_get_data(packet); + char *dec_in; + char *enc_out; + GHashTable *parts; + JabberSaslState state = JABBER_SASL_STATE_CONTINUE; + + if (!enc_in) { + *msg = g_strdup(_("Invalid response from server")); + return JABBER_SASL_STATE_FAIL; + } + + dec_in = (char *)purple_base64_decode(enc_in, NULL); + purple_debug_misc("jabber", "decoded challenge (%" + G_GSIZE_FORMAT "): %s\n", strlen(dec_in), dec_in); + + parts = parse_challenge(dec_in); + + if (g_hash_table_lookup(parts, "rspauth")) { + char *rspauth = g_hash_table_lookup(parts, "rspauth"); + + if (rspauth && purple_strequal(rspauth, js->expected_rspauth)) { + reply = xmlnode_new("response"); + xmlnode_set_namespace(reply, NS_XMPP_SASL); + } else { + *msg = g_strdup(_("Invalid challenge from server")); + state = JABBER_SASL_STATE_FAIL; + } + g_free(js->expected_rspauth); + js->expected_rspauth = NULL; + } else { + /* assemble a response, and send it */ + /* see RFC 2831 */ + char *realm; + char *nonce; + + /* Make sure the auth string contains everything that should be there. + This isn't everything in RFC2831, but it is what we need. */ + + nonce = g_hash_table_lookup(parts, "nonce"); + + /* we're actually supposed to prompt the user for a realm if + * the server doesn't send one, but that really complicates things, + * so i'm not gonna worry about it until is poses a problem to + * someone, or I get really bored */ + realm = g_hash_table_lookup(parts, "realm"); + if(!realm) + realm = js->user->domain; + + if (nonce == NULL || realm == NULL) { + *msg = g_strdup(_("Invalid challenge from server")); + state = JABBER_SASL_STATE_FAIL; + } else { + GString *response = g_string_new(""); + char *a2; + char *auth_resp; + char *cnonce; + + cnonce = g_strdup_printf("%x%u%x", g_random_int(), (int)time(NULL), + g_random_int()); + + a2 = g_strdup_printf("AUTHENTICATE:xmpp/%s", realm); + auth_resp = generate_response_value(js->user, + purple_connection_get_password(js->gc), nonce, cnonce, a2, realm); + g_free(a2); + + a2 = g_strdup_printf(":xmpp/%s", realm); + js->expected_rspauth = generate_response_value(js->user, + purple_connection_get_password(js->gc), nonce, cnonce, a2, realm); + g_free(a2); + + g_string_append_printf(response, "username=\"%s\"", js->user->node); + g_string_append_printf(response, ",realm=\"%s\"", realm); + g_string_append_printf(response, ",nonce=\"%s\"", nonce); + g_string_append_printf(response, ",cnonce=\"%s\"", cnonce); + g_string_append_printf(response, ",nc=00000001"); + g_string_append_printf(response, ",qop=auth"); + g_string_append_printf(response, ",digest-uri=\"xmpp/%s\"", realm); + g_string_append_printf(response, ",response=%s", auth_resp); + g_string_append_printf(response, ",charset=utf-8"); + + g_free(auth_resp); + g_free(cnonce); + + enc_out = purple_base64_encode((guchar *)response->str, response->len); + + purple_debug_misc("jabber", "decoded response (%" + G_GSIZE_FORMAT "): %s\n", + response->len, response->str); + + reply = xmlnode_new("response"); + xmlnode_set_namespace(reply, NS_XMPP_SASL); + xmlnode_insert_data(reply, enc_out, -1); + + g_free(enc_out); + + g_string_free(response, TRUE); + } + } + + g_free(enc_in); + g_free(dec_in); + g_hash_table_destroy(parts); + + *response = reply; + return state; +} + +static JabberSaslMech digest_md5_mech = { + 10, /* priority */ + "DIGEST-MD5", /* name */ + digest_md5_start, + digest_md5_handle_challenge, + NULL, /* handle_success */ + NULL, /* handle_failure */ + NULL /* handle_dispose */ +}; + +JabberSaslMech *jabber_auth_get_digest_md5_mech(void) +{ + return &digest_md5_mech; +} diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/auth_plain.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/auth_plain.c Fri Jan 08 20:11:49 2010 +0000 @@ -0,0 +1,119 @@ +/* + * purple - Jabber Protocol Plugin + * + * 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 "request.h" +#include "util.h" +#include "xmlnode.h" + +#include "jabber.h" +#include "auth.h" + +static xmlnode *finish_plaintext_authentication(JabberStream *js) +{ + xmlnode *auth; + GString *response; + gchar *enc_out; + + auth = xmlnode_new("auth"); + xmlnode_set_namespace(auth, NS_XMPP_SASL); + + xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth"); + xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true"); + + response = g_string_new(""); + response = g_string_append_len(response, "\0", 1); + response = g_string_append(response, js->user->node); + response = g_string_append_len(response, "\0", 1); + response = g_string_append(response, + purple_connection_get_password(js->gc)); + + enc_out = purple_base64_encode((guchar *)response->str, response->len); + + xmlnode_set_attrib(auth, "mechanism", "PLAIN"); + xmlnode_insert_data(auth, enc_out, -1); + g_free(enc_out); + g_string_free(response, TRUE); + + return auth; +} + +static void allow_plaintext_auth(PurpleAccount *account) +{ + PurpleConnection *gc = purple_account_get_connection(account); + JabberStream *js = purple_connection_get_protocol_data(gc); + xmlnode *response; + + purple_account_set_bool(account, "auth_plain_in_clear", TRUE); + + response = finish_plaintext_authentication(js); + jabber_send(js, response); + xmlnode_free(response); +} + +static void disallow_plaintext_auth(PurpleAccount *account) +{ + purple_connection_error_reason(purple_account_get_connection(account), + PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, + _("Server requires plaintext authentication over an unencrypted stream")); +} + +static JabberSaslState +jabber_plain_start(JabberStream *js, xmlnode *packet, xmlnode **response, char **error) +{ + PurpleAccount *account = purple_connection_get_account(js->gc); + char *msg; + + if (jabber_stream_is_ssl(js) || purple_account_get_bool(account, "auth_plain_in_clear", FALSE)) { + *response = finish_plaintext_authentication(js); + return JABBER_SASL_STATE_OK; + } + + msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"), + purple_account_get_username(account)); + purple_request_yes_no(js->gc, _("Plaintext Authentication"), + _("Plaintext Authentication"), + msg, + 1, + account, NULL, NULL, + account, allow_plaintext_auth, disallow_plaintext_auth); + g_free(msg); + return JABBER_SASL_STATE_CONTINUE; +} + +static JabberSaslMech plain_mech = { + 0, /* priority */ + "PLAIN", /* name */ + jabber_plain_start, + NULL, /* handle_challenge */ + NULL, /* handle_success */ + NULL, /* handle_failure */ + NULL /* dispose */ +}; + +JabberSaslMech *jabber_auth_get_plain_mech(void) +{ + return &plain_mech; +} diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/auth_scram.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/auth_scram.c Fri Jan 08 20:11:49 2010 +0000 @@ -0,0 +1,577 @@ +/* + * purple - Jabber Protocol Plugin + * + * 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 "auth.h" +#include "auth_scram.h" + +#include "cipher.h" +#include "debug.h" + +static const JabberScramHash hashes[] = { + { "-SHA-1", "sha1", 20 }, +}; + +static const JabberScramHash *mech_to_hash(const char *mech) +{ + int i; + + g_return_val_if_fail(mech != NULL && *mech != '\0', NULL); + + for (i = 0; i < G_N_ELEMENTS(hashes); ++i) { + if (strstr(mech, hashes[i].mech_substr)) + return &(hashes[i]); + } + + purple_debug_error("jabber", "Unknown SCRAM mechanism %s\n", mech); + g_return_val_if_reached(NULL); +} + +guchar *jabber_scram_hi(const JabberScramHash *hash, const GString *str, + GString *salt, guint iterations) +{ + PurpleCipherContext *context; + guchar *result; + guint i; + guchar *prev, *tmp; + + g_return_val_if_fail(hash != NULL, NULL); + g_return_val_if_fail(str != NULL && str->len > 0, NULL); + g_return_val_if_fail(salt != NULL && salt->len > 0, NULL); + g_return_val_if_fail(iterations > 0, NULL); + + prev = g_new0(guint8, hash->size); + tmp = g_new0(guint8, hash->size); + result = g_new0(guint8, hash->size); + + context = purple_cipher_context_new_by_name("hmac", NULL); + + /* Append INT(1), a four-octet encoding of the integer 1, most significant + * octet first. */ + g_string_append_len(salt, "\0\0\0\1", 4); + + /* Compute U0 */ + purple_cipher_context_set_option(context, "hash", (gpointer)hash->name); + purple_cipher_context_set_key_with_len(context, (guchar *)str->str, str->len); + purple_cipher_context_append(context, (guchar *)salt->str, salt->len); + purple_cipher_context_digest(context, hash->size, result, NULL); + + memcpy(prev, result, hash->size); + + /* Compute U1...Ui */ + for (i = 1; i < iterations; ++i) { + guint j; + purple_cipher_context_set_option(context, "hash", (gpointer)hash->name); + purple_cipher_context_set_key_with_len(context, (guchar *)str->str, str->len); + purple_cipher_context_append(context, prev, hash->size); + purple_cipher_context_digest(context, hash->size, tmp, NULL); + + for (j = 0; j < hash->size; ++j) + result[j] ^= tmp[j]; + + memcpy(prev, tmp, hash->size); + } + + purple_cipher_context_destroy(context); + g_free(tmp); + g_free(prev); + return result; +} + +/* + * Helper functions for doing the SCRAM calculations. The first argument + * is the hash algorithm. All buffers must be of the appropriate size + * according to the JabberScramHash. + * + * "str" is a NULL-terminated string for hmac(). + * + * Needless to say, these are fragile. + */ +static void +hmac(const JabberScramHash *hash, guchar *out, const guchar *key, const gchar *str) +{ + PurpleCipherContext *context; + + context = purple_cipher_context_new_by_name("hmac", NULL); + purple_cipher_context_set_option(context, "hash", (gpointer)hash->name); + purple_cipher_context_set_key_with_len(context, key, hash->size); + purple_cipher_context_append(context, (guchar *)str, strlen(str)); + purple_cipher_context_digest(context, hash->size, out, NULL); + purple_cipher_context_destroy(context); +} + +static void +hash(const JabberScramHash *hash, guchar *out, const guchar *data) +{ + PurpleCipherContext *context; + + context = purple_cipher_context_new_by_name(hash->name, NULL); + purple_cipher_context_append(context, data, hash->size); + purple_cipher_context_digest(context, hash->size, out, NULL); + purple_cipher_context_destroy(context); +} + +gboolean +jabber_scram_calc_proofs(JabberScramData *data, GString *salt, guint iterations) +{ + guint hash_len = data->hash->size; + guint i; + + GString *pass = g_string_new(data->password); + + guchar *salted_password; + guchar *client_key, *stored_key, *client_signature, *server_key; + + client_key = g_new0(guchar, hash_len); + stored_key = g_new0(guchar, hash_len); + client_signature = g_new0(guchar, hash_len); + server_key = g_new0(guchar, hash_len); + + data->client_proof = g_string_sized_new(hash_len); + data->client_proof->len = hash_len; + data->server_signature = g_string_sized_new(hash_len); + data->server_signature->len = hash_len; + + salted_password = jabber_scram_hi(data->hash, pass, salt, iterations); + + memset(pass->str, 0, pass->allocated_len); + g_string_free(pass, TRUE); + + if (!salted_password) + return FALSE; + + /* client_key = HMAC(salted_password, "Client Key") */ + hmac(data->hash, client_key, salted_password, "Client Key"); + /* server_key = HMAC(salted_password, "Server Key") */ + hmac(data->hash, server_key, salted_password, "Server Key"); + g_free(salted_password); + + /* stored_key = HASH(client_key) */ + hash(data->hash, stored_key, client_key); + + /* client_signature = HMAC(stored_key, auth_message) */ + hmac(data->hash, client_signature, stored_key, data->auth_message->str); + /* server_signature = HMAC(server_key, auth_message) */ + hmac(data->hash, (guchar *)data->server_signature->str, server_key, data->auth_message->str); + + /* client_proof = client_key XOR client_signature */ + for (i = 0; i < hash_len; ++i) + data->client_proof->str[i] = client_key[i] ^ client_signature[i]; + + g_free(server_key); + g_free(client_signature); + g_free(stored_key); + g_free(client_key); + + return TRUE; +} + +static gboolean +parse_server_step1(JabberScramData *data, const char *challenge, + gchar **out_nonce, GString **out_salt, guint *out_iterations) +{ + char **tokens; + char *token, *decoded, *tmp; + gsize len; + char *nonce = NULL; + GString *salt = NULL; + guint iterations; + + tokens = g_strsplit(challenge, ",", -1); + if (tokens == NULL) + return FALSE; + + token = tokens[0]; + if (token[0] != 'r' || token[1] != '=') + goto err; + + /* Ensure that the first cnonce_len bytes of the nonce are the original + * cnonce we sent to the server. + */ + if (0 != strncmp(data->cnonce, token + 2, strlen(data->cnonce))) + goto err; + + nonce = g_strdup(token + 2); + + /* The Salt, base64-encoded */ + token = tokens[1]; + if (token[0] != 's' || token[1] != '=') + goto err; + + decoded = (gchar *)purple_base64_decode(token + 2, &len); + if (!decoded || *decoded == '\0') { + g_free(decoded); + goto err; + } + salt = g_string_new_len(decoded, len); + g_free(decoded); + + /* The iteration count */ + token = tokens[2]; + if (token[0] != 'i' || token[1] != '=' || token[2] == '\0') + goto err; + + /* Validate the string */ + for (tmp = token + 2; *tmp; ++tmp) + if (!g_ascii_isdigit(*tmp)) + goto err; + + iterations = strtoul(token + 2, NULL, 10); + + g_strfreev(tokens); + *out_nonce = nonce; + *out_salt = salt; + *out_iterations = iterations; + return TRUE; + +err: + g_free(nonce); + if (salt) + g_string_free(salt, TRUE); + g_strfreev(tokens); + return FALSE; +} + +static gboolean +parse_server_step2(JabberScramData *data, const char *challenge, gchar **out_verifier) +{ + char **tokens; + char *token; + + tokens = g_strsplit(challenge, ",", -1); + if (tokens == NULL) + return FALSE; + + token = tokens[0]; + if (token[0] != 'v' || token[1] != '=' || token[2] == '\0') { + g_strfreev(tokens); + return FALSE; + } + + *out_verifier = g_strdup(token + 2); + g_strfreev(tokens); + return TRUE; +} + +gboolean +jabber_scram_feed_parser(JabberScramData *data, gchar *in, gchar **out) +{ + gboolean ret; + + g_return_val_if_fail(data != NULL, FALSE); + + g_string_append_c(data->auth_message, ','); + g_string_append(data->auth_message, in); + + if (data->step == 1) { + gchar *nonce, *proof; + GString *salt; + guint iterations; + + ret = parse_server_step1(data, in, &nonce, &salt, &iterations); + if (!ret) + return FALSE; + + g_string_append_c(data->auth_message, ','); + + /* "biws" is the base64 encoding of "n,,". I promise. */ + g_string_append_printf(data->auth_message, "c=%s,r=%s", "biws", nonce); +#ifdef CHANNEL_BINDING +#error fix this +#endif + + ret = jabber_scram_calc_proofs(data, salt, iterations); + if (!ret) + return FALSE; + + proof = purple_base64_encode((guchar *)data->client_proof->str, data->client_proof->len); + *out = g_strdup_printf("c=%s,r=%s,p=%s", "biws", nonce, proof); + g_free(proof); + } else if (data->step == 2) { + gchar *server_sig, *enc_server_sig; + gsize len; + + ret = parse_server_step2(data, in, &enc_server_sig); + if (!ret) + return FALSE; + + server_sig = (gchar *)purple_base64_decode(enc_server_sig, &len); + g_free(enc_server_sig); + + if (server_sig == NULL || len != data->server_signature->len) { + g_free(server_sig); + return FALSE; + } + + if (0 != memcmp(server_sig, data->server_signature->str, len)) { + g_free(server_sig); + return FALSE; + } + g_free(server_sig); + + *out = NULL; + } else { + purple_debug_error("jabber", "SCRAM: There is no step %d\n", data->step); + return FALSE; + } + + return TRUE; +} + +static gchar *escape_username(const gchar *in) +{ + gchar *tmp, *tmp2; + + tmp = purple_strreplace(in, "=", "=3D"); + tmp2 = purple_strreplace(tmp, ",", "=2D"); + g_free(tmp); + return tmp2; +} + +static JabberSaslState +scram_start(JabberStream *js, xmlnode *mechanisms, xmlnode **out, char **error) +{ + xmlnode *reply; + JabberScramData *data; + guint64 cnonce; +#ifdef CHANNEL_BINDING + gboolean binding_supported = TRUE; +#endif + gchar *dec_out, *enc_out; + gchar *prepped_node, *tmp; + gchar *prepped_pass; + + prepped_node = jabber_saslprep(js->user->node); + if (!prepped_node) { + *error = g_strdup(_("Unable to canonicalize username")); + return JABBER_SASL_STATE_FAIL; + } + + tmp = escape_username(prepped_node); + g_free(prepped_node); + prepped_node = tmp; + + prepped_pass = jabber_saslprep(purple_connection_get_password(js->gc)); + if (!prepped_pass) { + g_free(prepped_node); + *error = g_strdup(_("Unable to canonicalize password")); + return JABBER_SASL_STATE_FAIL; + } + + data = js->auth_mech_data = g_new0(JabberScramData, 1); + data->hash = mech_to_hash(js->auth_mech->name); + data->password = prepped_pass; + +#ifdef CHANNEL_BINDING + if (strstr(js->auth_mech_name, "-PLUS")) + data->channel_binding = TRUE; +#endif + cnonce = ((guint64)g_random_int() << 32) | g_random_int(); + data->cnonce = purple_base64_encode((guchar *)&cnonce, sizeof(cnonce)); + + data->auth_message = g_string_new(NULL); + g_string_printf(data->auth_message, "n=%s,r=%s", + prepped_node, data->cnonce); + g_free(prepped_node); + + data->step = 1; + + reply = xmlnode_new("auth"); + xmlnode_set_namespace(reply, NS_XMPP_SASL); + xmlnode_set_attrib(reply, "mechanism", js->auth_mech->name); + + /* TODO: Channel binding */ + dec_out = g_strdup_printf("%c,,%s", 'n', data->auth_message->str); + enc_out = purple_base64_encode((guchar *)dec_out, strlen(dec_out)); + purple_debug_misc("jabber", "initial SCRAM message '%s'\n", dec_out); + + xmlnode_insert_data(reply, enc_out, -1); + + g_free(enc_out); + g_free(dec_out); + + *out = reply; + return JABBER_SASL_STATE_CONTINUE; +} + +static JabberSaslState +scram_handle_challenge(JabberStream *js, xmlnode *challenge, xmlnode **out, char **error) +{ + JabberScramData *data = js->auth_mech_data; + xmlnode *reply; + gchar *enc_in, *dec_in; + gchar *enc_out = NULL, *dec_out = NULL; + gsize len; + JabberSaslState state = JABBER_SASL_STATE_FAIL; + + enc_in = xmlnode_get_data(challenge); + if (!enc_in || *enc_in == '\0') { + reply = xmlnode_new("abort"); + xmlnode_set_namespace(reply, NS_XMPP_SASL); + data->step = -1; + *error = g_strdup(_("Invalid challenge from server")); + goto out; + } + + dec_in = (gchar *)purple_base64_decode(enc_in, &len); + g_free(enc_in); + if (!dec_in || len != strlen(dec_in)) { + /* Danger afoot; SCRAM shouldn't contain NUL bytes */ + reply = xmlnode_new("abort"); + xmlnode_set_namespace(reply, NS_XMPP_SASL); + data->step = -1; + *error = g_strdup(_("Malicious challenge from server")); + goto out; + } + + purple_debug_misc("jabber", "decoded challenge: %s\n", dec_in); + + if (!jabber_scram_feed_parser(data, dec_in, &dec_out)) { + reply = xmlnode_new("abort"); + xmlnode_set_namespace(reply, NS_XMPP_SASL); + data->step = -1; + *error = g_strdup(_("Invalid challenge from server")); + goto out; + } + + data->step += 1; + + reply = xmlnode_new("response"); + xmlnode_set_namespace(reply, NS_XMPP_SASL); + + purple_debug_misc("jabber", "decoded response: %s\n", dec_out ? dec_out : "(null)"); + if (dec_out) { + enc_out = purple_base64_encode((guchar *)dec_out, strlen(dec_out)); + xmlnode_insert_data(reply, enc_out, -1); + } + + state = JABBER_SASL_STATE_CONTINUE; + +out: + g_free(enc_out); + g_free(dec_out); + + *out = reply; + return state; +} + +static JabberSaslState +scram_handle_success(JabberStream *js, xmlnode *packet, char **error) +{ + JabberScramData *data = js->auth_mech_data; + char *enc_in, *dec_in; + char *dec_out = NULL; + gsize len; + + enc_in = xmlnode_get_data(packet); + g_return_val_if_fail(enc_in != NULL && *enc_in != '\0', FALSE); + + if (data->step == 3) + return JABBER_SASL_STATE_OK; + + if (data->step != 2) { + *error = g_strdup(_("Unexpected response from server")); + return JABBER_SASL_STATE_FAIL; + } + + dec_in = (gchar *)purple_base64_decode(enc_in, &len); + g_free(enc_in); + if (!dec_in || len != strlen(dec_in)) { + /* Danger afoot; SCRAM shouldn't contain NUL bytes */ + g_free(dec_in); + *error = g_strdup(_("Invalid challenge from server")); + return JABBER_SASL_STATE_FAIL; + } + + purple_debug_misc("jabber", "decoded success: %s\n", dec_in); + + if (!jabber_scram_feed_parser(data, dec_in, &dec_out) || dec_out != NULL) { + g_free(dec_out); + *error = g_strdup(_("Invalid challenge from server")); + return JABBER_SASL_STATE_FAIL; + } + + /* Hooray */ + return JABBER_SASL_STATE_OK; +} + +void jabber_scram_data_destroy(JabberScramData *data) +{ + g_free(data->cnonce); + if (data->auth_message) + g_string_free(data->auth_message, TRUE); + if (data->client_proof) + g_string_free(data->client_proof, TRUE); + if (data->server_signature) + g_string_free(data->server_signature, TRUE); + if (data->password) { + memset(data->password, 0, strlen(data->password)); + g_free(data->password); + } + + g_free(data); +} + +static void scram_dispose(JabberStream *js) +{ + if (js->auth_mech_data) { + jabber_scram_data_destroy(js->auth_mech_data); + js->auth_mech_data = NULL; + } +} + +static JabberSaslMech scram_sha1_mech = { + 50, /* priority */ + "SCRAM-SHA-1", /* name */ + scram_start, + scram_handle_challenge, + scram_handle_success, + NULL, /* handle_failure */ + scram_dispose +}; + +#ifdef CHANNEL_BINDING +/* With channel binding */ +static JabberSaslMech scram_sha1_plus_mech = { + scram_sha1_mech.priority + 1, /* priority */ + "SCRAM-SHA-1-PLUS", /* name */ + scram_start, + scram_handle_challenge, + scram_handle_success, + NULL, /* handle_failure */ + scram_dispose +}; +#endif + +JabberSaslMech **jabber_auth_get_scram_mechs(gint *count) +{ + static JabberSaslMech *mechs[] = { + &scram_sha1_mech, +#ifdef CHANNEL_BINDING + &scram_sha1_plus_mech, +#endif + }; + + *count = G_N_ELEMENTS(mechs); + return mechs; +} diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/auth_scram.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/auth_scram.h Fri Jan 08 20:11:49 2010 +0000 @@ -0,0 +1,95 @@ +/** + * @file auth_scram.h Implementation of SASL-SCRAM authentication + * + * 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_JABBER_AUTH_SCRAM_H_ +#define PURPLE_JABBER_AUTH_SCRAM_H_ + +/* + * Every function in this file is ONLY exposed for tests. + * DO NOT USE ANYTHING HERE OR YOU WILL BE SENT TO THE PIT OF DESPAIR. + */ + +/* Per-connection state stored between messages. + * This is stored in js->auth_data_mech. + */ +typedef struct { + const char *mech_substr; + const char *name; + guint size; +} JabberScramHash; + +typedef struct { + const JabberScramHash *hash; + char *cnonce; + GString *auth_message; + + GString *client_proof; + GString *server_signature; + + gchar *password; + gboolean channel_binding; + int step; +} JabberScramData; + +#include "auth.h" + +/** + * Implements the Hi() function as described in the SASL-SCRAM I-D. + * + * @param hash The struct corresponding to the hash function to be used. + * @param str The string to perform the PBKDF2 operation on. + * @param salt The salt. + * @param iterations The number of iterations to perform. + * + * @returns A newly allocated string containing the result. The string is + * NOT null-terminated and its length is the length of the binary + * output of the hash function in-use. + */ +guchar *jabber_scram_hi(const JabberScramHash *hash, const GString *str, + GString *salt, guint iterations); + +/** + * Calculates the proofs as described in Section 3 of the SASL-SCRAM I-D. + * + * @param data A JabberScramData structure. hash and auth_message must be + * set. client_proof and server_signature will be set as a result + * of this function. + * @param salt The salt (as specified by the server) + * @param iterations The number of iterations to perform. + * + * @returns TRUE if the proofs were successfully calculated. FALSE otherwise. + */ +gboolean jabber_scram_calc_proofs(JabberScramData *data, GString *salt, + guint iterations); + +/** + * Feed the algorithm with the data from the server. + */ +gboolean jabber_scram_feed_parser(JabberScramData *data, gchar *in, gchar **out); + +/** + * Clean up and destroy the data struct + */ +void jabber_scram_data_destroy(JabberScramData *data); + +#endif /* PURPLE_JABBER_AUTH_SCRAM_H_ */ diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/buddy.c --- a/libpurple/protocols/jabber/buddy.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/buddy.c Fri Jan 08 20:11:49 2010 +0000 @@ -815,20 +815,33 @@ if (!jbi->jb->resources) { /* the buddy is offline */ - gchar *status = - g_strdup_printf("%s%s%s", _("Offline"), - jbi->last_message ? ": " : "", - jbi->last_message ? jbi->last_message : ""); + gboolean is_domain = jabber_jid_is_domain(jbi->jid); + if (jbi->last_seconds > 0) { char *last = purple_str_seconds_to_string(jbi->last_seconds); - gchar *message = g_strdup_printf(_("%s ago"), last); - purple_notify_user_info_prepend_pair(user_info, - _("Logged Off"), message); + gchar *message = NULL; + const gchar *title = NULL; + if (is_domain) { + title = _("Uptime"); + message = last; + last = NULL; + } else { + title = _("Logged Off"); + message = g_strdup_printf(_("%s ago"), last); + } + purple_notify_user_info_prepend_pair(user_info, title, message); g_free(last); g_free(message); } - purple_notify_user_info_prepend_pair(user_info, _("Status"), status); - g_free(status); + + if (!is_domain) { + gchar *status = + g_strdup_printf("%s%s%s", _("Offline"), + jbi->last_message ? ": " : "", + jbi->last_message ? jbi->last_message : ""); + purple_notify_user_info_prepend_pair(user_info, _("Status"), status); + g_free(status); + } } g_free(resource_name); @@ -1860,8 +1873,10 @@ * However, since the gateway might appear offline to us, we cannot get that information. Therefore, I just assume * that gateways on the roster can be identified by having no '@' in their jid. This is a faily safe assumption, since * people don't tend to have a server or other service there. + * + * TODO: Use disco#info... */ - if (g_utf8_strchr(name, -1, '@') == NULL) { + if (strchr(name, '@') == NULL) { act = purple_menu_action_new(_("Log In"), PURPLE_CALLBACK(jabber_buddy_login), NULL, NULL); diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/chat.c --- a/libpurple/protocols/jabber/chat.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/chat.c Fri Jan 08 20:11:49 2010 +0000 @@ -927,7 +927,7 @@ jcm = g_hash_table_lookup(chat->members, who); if (jcm && jcm->jid) jid = jcm->jid; - else if (g_utf8_strchr(who, -1, '@') != NULL) + else if (strchr(who, '@') != NULL) jid = who; else return FALSE; @@ -964,7 +964,7 @@ jcm = g_hash_table_lookup(chat->members, who); if (jcm && jcm->jid) jid = jcm->jid; - else if (g_utf8_strchr(who, -1, '@') != NULL) + else if (strchr(who, '@') != NULL) jid = who; else return FALSE; diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/disco.c --- a/libpurple/protocols/jabber/disco.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/disco.c Fri Jan 08 20:11:49 2010 +0000 @@ -370,10 +370,8 @@ if (js->pep) jabber_avatar_fetch_mine(js); - if (!(js->server_caps & JABBER_CAP_GOOGLE_ROSTER)) { - /* If the server supports JABBER_CAP_GOOGLE_ROSTER; we will have already requested it */ - jabber_roster_request(js); - } + /* Yes, please! */ + jabber_roster_request(js); if (js->server_caps & JABBER_CAP_ADHOC) { /* The server supports ad-hoc commands, so let's request the list */ @@ -555,9 +553,8 @@ if (!strcmp(NS_GOOGLE_MAIL_NOTIFY, var)) { js->server_caps |= JABBER_CAP_GMAIL_NOTIFY; jabber_gmail_init(js); - } else if (!strcmp("google:roster", var)) { + } else if (!strcmp(NS_GOOGLE_ROSTER, var)) { js->server_caps |= JABBER_CAP_GOOGLE_ROSTER; - jabber_google_roster_init(js); } else if (!strcmp("http://jabber.org/protocol/commands", var)) { js->server_caps |= JABBER_CAP_ADHOC; } else if (!strcmp(NS_SIMPLE_BLOCKING, var)) { diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/google.c --- a/libpurple/protocols/jabber/google.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/google.c Fri Jan 08 20:11:49 2010 +0000 @@ -30,6 +30,7 @@ #include "google.h" #include "jabber.h" #include "presence.h" +#include "roster.h" #include "iq.h" #include "chat.h" @@ -949,20 +950,6 @@ jabber_iq_send(iq); } -void jabber_google_roster_init(JabberStream *js) -{ - JabberIq *iq; - xmlnode *query; - - iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:roster"); - query = xmlnode_get_child(iq->node, "query"); - - xmlnode_set_attrib(query, "xmlns:gr", "google:roster"); - xmlnode_set_attrib(query, "gr:ext", "2"); - - jabber_iq_send(iq); -} - void jabber_google_roster_outgoing(JabberStream *js, xmlnode *query, xmlnode *item) { PurpleAccount *account = purple_connection_get_account(js->gc); @@ -972,7 +959,7 @@ while (list) { if (!strcmp(jid_norm, (char*)list->data)) { - xmlnode_set_attrib(query, "xmlns:gr", "google:roster"); + xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); xmlnode_set_attrib(query, "gr:ext", "2"); xmlnode_set_attrib(item, "gr:t", "B"); return; @@ -989,7 +976,7 @@ char *jid_norm; - const char *grt = xmlnode_get_attrib_with_namespace(item, "t", "google:roster"); + const char *grt = xmlnode_get_attrib_with_namespace(item, "t", NS_GOOGLE_ROSTER); const char *subscription = xmlnode_get_attrib(item, "subscription"); const char *ask = xmlnode_get_attrib(item, "ask"); @@ -1032,9 +1019,9 @@ return TRUE; } -void jabber_google_roster_add_deny(PurpleConnection *gc, const char *who) +void jabber_google_roster_add_deny(JabberStream *js, const char *who) { - JabberStream *js; + PurpleAccount *account; GSList *buddies; JabberIq *iq; xmlnode *query; @@ -1044,14 +1031,10 @@ JabberBuddy *jb; const char *balias; - js = (JabberStream*)(gc->proto_data); - - if (!js || !(js->server_caps & JABBER_CAP_GOOGLE_ROSTER)) - return; - jb = jabber_buddy_find(js, who, TRUE); - buddies = purple_find_buddies(js->gc->account, who); + account = purple_connection_get_account(js->gc); + buddies = purple_find_buddies(account, who); if(!buddies) return; @@ -1078,7 +1061,7 @@ xmlnode_set_attrib(item, "jid", who); xmlnode_set_attrib(item, "name", balias ? balias : ""); xmlnode_set_attrib(item, "gr:t", "B"); - xmlnode_set_attrib(query, "xmlns:gr", "google:roster"); + xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); xmlnode_set_attrib(query, "gr:ext", "2"); jabber_iq_send(iq); @@ -1098,12 +1081,11 @@ } } - purple_prpl_got_user_status(purple_connection_get_account(gc), who, "offline", NULL); + purple_prpl_got_user_status(account, who, "offline", NULL); } -void jabber_google_roster_rem_deny(PurpleConnection *gc, const char *who) +void jabber_google_roster_rem_deny(JabberStream *js, const char *who) { - JabberStream *js; GSList *buddies; JabberIq *iq; xmlnode *query; @@ -1112,14 +1094,6 @@ PurpleBuddy *b; const char *balias; - g_return_if_fail(gc != NULL); - g_return_if_fail(who != NULL); - - js = (JabberStream*)(gc->proto_data); - - if (!js || !(js->server_caps & JABBER_CAP_GOOGLE_ROSTER)) - return; - buddies = purple_find_buddies(purple_connection_get_account(js->gc), who); if(!buddies) return; @@ -1146,7 +1120,7 @@ balias = purple_buddy_get_local_buddy_alias(b); xmlnode_set_attrib(item, "jid", who); xmlnode_set_attrib(item, "name", balias ? balias : ""); - xmlnode_set_attrib(query, "xmlns:gr", "google:roster"); + xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); xmlnode_set_attrib(query, "gr:ext", "2"); jabber_iq_send(iq); diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/google.h --- a/libpurple/protocols/jabber/google.h Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/google.h Fri Jan 08 20:11:49 2010 +0000 @@ -32,7 +32,6 @@ void jabber_gmail_poke(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *new_mail); -void jabber_google_roster_init(JabberStream *js); void jabber_google_roster_outgoing(JabberStream *js, xmlnode *query, xmlnode *item); /* Returns FALSE if this should short-circuit processing of this roster item, or TRUE @@ -43,8 +42,8 @@ void jabber_google_presence_incoming(JabberStream *js, const char *who, JabberBuddyResource *jbr); char *jabber_google_presence_outgoing(PurpleStatus *tune); -void jabber_google_roster_add_deny(PurpleConnection *gc, const char *who); -void jabber_google_roster_rem_deny(PurpleConnection *gc, const char *who); +void jabber_google_roster_add_deny(JabberStream *js, const char *who); +void jabber_google_roster_rem_deny(JabberStream *js, const char *who); char *jabber_google_format_to_html(const char *text); diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/jabber.c Fri Jan 08 20:11:49 2010 +0000 @@ -229,13 +229,15 @@ jabber_iq_set_callback(iq, jabber_bind_result_cb, NULL); jabber_iq_send(iq); + } else if (xmlnode_get_child_with_namespace(packet, "ver", NS_ROSTER_VERSIONING)) { + js->server_caps |= JABBER_CAP_ROSTER_VERSIONING; } else /* if(xmlnode_get_child_with_namespace(packet, "auth")) */ { /* If we get an empty stream:features packet, or we explicitly get * an auth feature with namespace http://jabber.org/features/iq-auth * we should revert back to iq:auth authentication, even though we're * connecting to an XMPP server. */ - js->auth_type = JABBER_AUTH_IQ_AUTH; jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING); + jabber_auth_start_old(js); } } @@ -637,10 +639,11 @@ js->srv_query_data = NULL; if (responses == NULL) { + purple_debug_warning("jabber", "Unable to find alternative XMPP connection " + "methods after failing to connect directly."); purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Unable to find alternative XMPP connection " - "methods after failing to connect directly.")); + _("Unable to connect")); return; } @@ -1510,6 +1513,8 @@ purple_circ_buffer_destroy(js->write_buffer); if(js->writeh) purple_input_remove(js->writeh); + if (js->auth_mech && js->auth_mech->dispose) + js->auth_mech->dispose(js); #ifdef HAVE_CYRUS_SASL if(js->sasl) sasl_dispose(&js->sasl); @@ -1586,11 +1591,6 @@ case JABBER_STREAM_AUTHENTICATING: purple_connection_update_progress(js->gc, _("Authenticating"), js->gsc ? 7 : 3, JABBER_CONNECT_STEPS); - if(js->protocol_version == JABBER_PROTO_0_9 && js->registration) { - jabber_register_start(js); - } else if(js->auth_type == JABBER_AUTH_IQ_AUTH) { - jabber_auth_start_old(js); - } break; case JABBER_STREAM_POST_AUTH: purple_connection_update_progress(js->gc, _("Re-initializing Stream"), @@ -1745,13 +1745,15 @@ JabberIq *iq; xmlnode *block, *item; - js = gc->proto_data; + g_return_if_fail(who != NULL && *who != '\0'); + + js = purple_connection_get_protocol_data(gc); if (js == NULL) return; if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER) { - jabber_google_roster_add_deny(gc, who); + jabber_google_roster_add_deny(js, who); return; } @@ -1779,13 +1781,15 @@ JabberIq *iq; xmlnode *unblock, *item; - js = gc->proto_data; + g_return_if_fail(who != NULL && *who != '\0'); + + js = purple_connection_get_protocol_data(gc); if (js == NULL) return; if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER) { - jabber_google_roster_rem_deny(gc, who); + jabber_google_roster_rem_deny(js, who); return; } @@ -3519,6 +3523,8 @@ jabber_add_feature(JINGLE_TRANSPORT_ICEUDP, 0); #endif + jabber_auth_init(); + /* IPC functions */ purple_plugin_ipc_register(plugin, "contact_has_feature", PURPLE_CALLBACK(jabber_ipc_contact_has_feature), purple_marshal_BOOLEAN__POINTER_POINTER_POINTER, @@ -3553,6 +3559,7 @@ { purple_plugin_ipc_unregister_all(plugin); + jabber_auth_uninit(); jabber_features_destroy(); jabber_identities_destroy(); } diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/jabber.h --- a/libpurple/protocols/jabber/jabber.h Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/jabber.h Fri Jan 08 20:11:49 2010 +0000 @@ -47,6 +47,7 @@ JABBER_CAP_BLOCKING = 1 << 13, JABBER_CAP_ITEMS = 1 << 14, + JABBER_CAP_ROSTER_VERSIONING = 1 << 15, JABBER_CAP_RETRIEVED = 1 << 31 } JabberCapabilities; @@ -66,6 +67,7 @@ #include "namespaces.h" +#include "auth.h" #include "iq.h" #include "jutil.h" #include "xmlnode.h" @@ -106,13 +108,9 @@ JABBER_PROTO_0_9, JABBER_PROTO_1_0 } protocol_version; - enum { - JABBER_AUTH_UNKNOWN, - JABBER_AUTH_DIGEST_MD5, - JABBER_AUTH_PLAIN, - JABBER_AUTH_IQ_AUTH, - JABBER_AUTH_CYRUS - } auth_type; + + JabberSaslMech *auth_mech; + gpointer auth_mech_data; char *stream_id; JabberStreamState state; diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/jingle/jingle.c --- a/libpurple/protocols/jabber/jingle/jingle.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/jingle/jingle.c Fri Jan 08 20:11:49 2010 +0000 @@ -29,12 +29,13 @@ #include "content.h" #include "debug.h" #include "jingle.h" -#include #include "session.h" #include "iceudp.h" #include "rawudp.h" #include "rtp.h" +#include + GType jingle_get_type(const gchar *type) { diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/jingle/session.c --- a/libpurple/protocols/jabber/jingle/session.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/jingle/session.c Fri Jan 08 20:11:49 2010 +0000 @@ -377,7 +377,7 @@ { JingleSession *session = (JingleSession *)value; const gchar *jid = user_data; - gboolean use_bare = g_utf8_strchr(jid, -1, '/') == NULL; + gboolean use_bare = strchr(jid, '/') == NULL; gchar *remote_jid = jingle_session_get_remote_jid(session); gchar *cmp_jid = use_bare ? jabber_get_bare_jid(remote_jid) : g_strdup(remote_jid); @@ -438,7 +438,7 @@ data.jid = jid; data.ret = NULL; - data.use_bare = g_utf8_strchr(jid, -1, '/') == NULL; + data.use_bare = strchr(jid, '/') == NULL; g_hash_table_foreach(js->sessions, find_by_jid_ghr, &data); return data.ret; diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/jutil.c --- a/libpurple/protocols/jabber/jutil.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/jutil.c Fri Jan 08 20:11:49 2010 +0000 @@ -276,6 +276,42 @@ #endif /* USE_IDN */ } +char *jabber_saslprep(const char *in) +{ +#ifdef USE_IDN + char *out; + + g_return_val_if_fail(in != NULL, NULL); + g_return_val_if_fail(strlen(in) <= sizeof(idn_buffer) - 1, NULL); + + strncpy(idn_buffer, in, sizeof(idn_buffer) - 1); + idn_buffer[sizeof(idn_buffer) - 1] = '\0'; + + if (STRINGPREP_OK != stringprep(idn_buffer, sizeof(idn_buffer), 0, + stringprep_saslprep)) { + memset(idn_buffer, 0, sizeof(idn_buffer)); + return NULL; + } + + out = g_strdup(idn_buffer); + memset(idn_buffer, 0, sizeof(idn_buffer)); + return out; +#else /* USE_IDN */ + /* TODO: Something better than disallowing all non-ASCII characters */ + /* TODO: Is this even correct? */ + const guchar *c; + + c = (const guchar *)in; + while (*c) { + if (*c > 0x7f || + (*c < 0x20 && *c != '\t' && *c != '\n' && *c != '\r')) + return NULL; + } + + return g_strdup(in); +#endif /* USE_IDN */ +} + static JabberID* jabber_id_new_internal(const char *str, gboolean allow_terminating_slash) { @@ -473,6 +509,19 @@ } } +char *jabber_get_domain(const char *in) +{ + JabberID *jid = jabber_id_new(in); + char *out; + + if (!jid) + return NULL; + + out = g_strdup(jid->domain); + jabber_id_free(jid); + + return out; +} char *jabber_get_resource(const char *in) { @@ -513,6 +562,20 @@ NULL); } +gboolean +jabber_jid_is_domain(const char *jid) +{ + const char *c; + + for (c = jid; *c; ++c) { + if (*c == '@' || *c == '/') + return FALSE; + } + + return TRUE; +} + + JabberID * jabber_id_new(const char *str) { diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/jutil.h --- a/libpurple/protocols/jabber/jutil.h Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/jutil.h Fri Jan 08 20:11:49 2010 +0000 @@ -35,10 +35,13 @@ JabberID* jabber_id_new(const char *str); void jabber_id_free(JabberID *jid); +char *jabber_get_domain(const char *jid); char *jabber_get_resource(const char *jid); char *jabber_get_bare_jid(const char *jid); char *jabber_id_get_bare_jid(const JabberID *jid); +gboolean jabber_jid_is_domain(const char *jid); + const char *jabber_normalize(const PurpleAccount *account, const char *in); /* Returns true if JID is the bare JID of our server. */ @@ -51,6 +54,15 @@ gboolean jabber_domain_validate(const char *); gboolean jabber_resourceprep_validate(const char *); +/** + * Apply the SASLprep profile of stringprep to the string passed in. + * + * @returns A newly allocated string containing the normalized version + * of the input, or NULL if an error occurred (the string could + * not be normalized) + */ +char *jabber_saslprep(const char *); + PurpleConversation *jabber_find_unnormalized_conv(const char *name, PurpleAccount *account); char *jabber_calculate_data_sha1sum(gconstpointer data, size_t len); diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Fri Jan 08 20:11:49 2010 +0000 @@ -295,7 +295,7 @@ static gboolean xmpp_uri_handler(const char *proto, const char *user, GHashTable *params) { - char *acct_id = g_hash_table_lookup(params, "account"); + char *acct_id = params ? g_hash_table_lookup(params, "account") : NULL; PurpleAccount *acct; if (g_ascii_strcasecmp(proto, "xmpp")) @@ -307,7 +307,8 @@ return FALSE; /* xmpp:romeo@montague.net?message;subject=Test%20Message;body=Here%27s%20a%20test%20message */ - if (g_hash_table_lookup_extended(params, "message", NULL, NULL)) { + /* params is NULL if the URI has no '?' (or anything after it) */ + if (!params || g_hash_table_lookup_extended(params, "message", NULL, NULL)) { char *body = g_hash_table_lookup(params, "body"); if (user && *user) { PurpleConversation *conv = diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/namespaces.h --- a/libpurple/protocols/jabber/namespaces.h Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/namespaces.h Fri Jan 08 20:11:49 2010 +0000 @@ -85,6 +85,9 @@ /* XEP-0231 BoB (Bits of Binary) */ #define NS_BOB "urn:xmpp:bob" +/* XEP-0237 Roster Versioning */ +#define NS_ROSTER_VERSIONING "urn:xmpp:features:rosterver" + /* Google extensions */ #define NS_GOOGLE_CAMERA "http://www.google.com/xmpp/protocol/camera/v1" #define NS_GOOGLE_VIDEO "http://www.google.com/xmpp/protocol/video/v1" @@ -92,6 +95,7 @@ #define NS_GOOGLE_JINGLE_INFO "google:jingleinfo" #define NS_GOOGLE_MAIL_NOTIFY "google:mail:notify" +#define NS_GOOGLE_ROSTER "google:roster" #define NS_GOOGLE_PROTOCOL_SESSION "http://www.google.com/xmpp/protocol/session" #define NS_GOOGLE_SESSION "http://www.google.com/session" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/parser.c --- a/libpurple/protocols/jabber/parser.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/parser.c Fri Jan 08 20:11:49 2010 +0000 @@ -47,9 +47,7 @@ js->protocol_version = JABBER_PROTO_0_9; for(i=0; i < nb_attributes * 5; i += 5) { int attrib_len = attributes[i+4] - attributes[i+3]; - char *attrib = g_malloc(attrib_len + 1); - memcpy(attrib, attributes[i+3], attrib_len); - attrib[attrib_len] = '\0'; + char *attrib = g_strndup((gchar *)attributes[i+3], attrib_len); if(!xmlStrcmp(attributes[i], (xmlChar*) "version") && !strcmp(attrib, "1.0")) { @@ -88,10 +86,7 @@ const char *attrib_ns = (const char *)attributes[i+2]; char *txt; int attrib_len = attributes[i+4] - attributes[i+3]; - char *attrib = g_malloc(attrib_len + 1); - - memcpy(attrib, attributes[i+3], attrib_len); - attrib[attrib_len] = '\0'; + char *attrib = g_strndup((gchar *)attributes[i+3], attrib_len); txt = attrib; attrib = purple_unescape_html(txt); @@ -152,8 +147,7 @@ */ return; - if (error->level == XML_ERR_FATAL && error->message != NULL - && g_str_equal(error->message, "Extra content at the end of the document\n")) + if (error->level == XML_ERR_FATAL && error->code == XML_ERR_DOCUMENT_END) /* * This is probably more annoying than the vcard-temp error; it occurs * because we disconnect in most cases without waiting for the receiving @@ -269,8 +263,8 @@ * the opening and there was no version, we need to * immediately start legacy IQ auth. */ - js->auth_type = JABBER_AUTH_IQ_AUTH; jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING); + jabber_auth_start_old(js); } } diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/presence.c --- a/libpurple/protocols/jabber/presence.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/presence.c Fri Jan 08 20:11:49 2010 +0000 @@ -150,7 +150,7 @@ /* we don't want to send presence before we've gotten our roster */ if (js->state != JABBER_STREAM_CONNECTED) { - purple_debug_info("jabber", "attempt to send presence before roster retrieved\n"); + purple_debug_misc("jabber", "attempt to send presence before roster retrieved\n"); return; } @@ -433,7 +433,7 @@ JabberPresenceCapabilities *userdata) { JabberBuddyResource *jbr; - char *resource = g_utf8_strchr(userdata->from, -1, '/'); + char *resource = strchr(userdata->from, '/'); if (resource) resource += 1; diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/roster.c --- a/libpurple/protocols/jabber/roster.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/roster.c Fri Jan 08 20:11:49 2010 +0000 @@ -47,12 +47,50 @@ return g_string_free(out, FALSE); } +static void roster_request_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ + xmlnode *query; + + if (type == JABBER_IQ_ERROR) { + /* + * This shouldn't happen in any real circumstances and + * likely constitutes a server-side misconfiguration (i.e. + * explicitly not loading mod_roster...) + */ + purple_debug_error("jabber", "Error retrieving roster!?\n"); + jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); + return; + } + + query = xmlnode_get_child(packet, "query"); + if (query == NULL) { + jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); + return; + } + + jabber_roster_parse(js, from, type, id, query); + jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); +} + void jabber_roster_request(JabberStream *js) { + PurpleAccount *account; JabberIq *iq; + xmlnode *query; + + account = purple_connection_get_account(js->gc); iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:roster"); + query = xmlnode_get_child(iq->node, "query"); + if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER) { + xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); + xmlnode_set_attrib(query, "gr:ext", "2"); + } + + jabber_iq_set_callback(iq, roster_request_cb, NULL); jabber_iq_send(iq); } @@ -155,6 +193,9 @@ JabberIqType type, const char *id, xmlnode *query) { xmlnode *item, *group; +#if 0 + const char *ver; +#endif if (!jabber_is_own_account(js, from)) { purple_debug_warning("jabber", "Received bogon roster push from %s\n", @@ -181,18 +222,18 @@ continue; if(subscription) { - if (jb == js->user_jb) + if (g_str_equal(subscription, "remove")) + jb->subscription = JABBER_SUB_REMOVE; + else if (jb == js->user_jb) jb->subscription = JABBER_SUB_BOTH; - else if(!strcmp(subscription, "none")) + else if (g_str_equal(subscription, "none")) jb->subscription = JABBER_SUB_NONE; - else if(!strcmp(subscription, "to")) + else if (g_str_equal(subscription, "to")) jb->subscription = JABBER_SUB_TO; - else if(!strcmp(subscription, "from")) + else if (g_str_equal(subscription, "from")) jb->subscription = JABBER_SUB_FROM; - else if(!strcmp(subscription, "both")) + else if (g_str_equal(subscription, "both")) jb->subscription = JABBER_SUB_BOTH; - else if(!strcmp(subscription, "remove")) - jb->subscription = JABBER_SUB_REMOVE; } if(purple_strequal(ask, "subscribe")) @@ -227,13 +268,15 @@ } } - js->currently_parsing_roster_push = FALSE; +#if 0 + ver = xmlnode_get_attrib(query, "ver"); + if (ver) { + PurpleAccount *account = purple_connection_get_account(js->gc); + purple_account_set_string(account, "roster_ver", ver); + } +#endif - /* if we're just now parsing the roster for the first time, - * then now would be the time to declare ourselves connected. - */ - if (js->state != JABBER_STREAM_CONNECTED) - jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); + js->currently_parsing_roster_push = FALSE; } /* jabber_roster_update frees the GSList* passed in */ @@ -297,7 +340,7 @@ if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER) { jabber_google_roster_outgoing(js, query, item); - xmlnode_set_attrib(query, "xmlns:gr", "google:roster"); + xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); xmlnode_set_attrib(query, "gr:ext", "2"); } jabber_iq_send(iq); diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/jabber/si.c --- a/libpurple/protocols/jabber/si.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/jabber/si.c Fri Jan 08 20:11:49 2010 +0000 @@ -1448,7 +1448,7 @@ g_free(xfer->who); xfer->who = who; - if (jbr && jbr->caps.info) { + if (jbr && jabber_resource_know_capabilities(jbr)) { char *msg; if (jabber_resource_has_capability(jbr, NS_IBB)) diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/msn/msn.c --- a/libpurple/protocols/msn/msn.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/msn/msn.c Fri Jan 08 20:11:49 2010 +0000 @@ -345,17 +345,23 @@ msn_show_set_friendly_name(PurplePluginAction *action) { PurpleConnection *gc; + PurpleAccount *account; + char *tmp; gc = (PurpleConnection *) action->context; - - purple_request_input(gc, NULL, _("Set your friendly name."), + account = purple_connection_get_account(gc); + + tmp = g_strdup_printf(_("Set friendly name for %s."), + purple_account_get_username(account)); + purple_request_input(gc, _("Set your friendly name."), tmp, _("This is the name that other MSN buddies will " "see you as."), purple_connection_get_display_name(gc), FALSE, FALSE, NULL, _("OK"), G_CALLBACK(msn_act_id), _("Cancel"), NULL, - purple_connection_get_account(gc), NULL, NULL, + account, NULL, NULL, gc); + g_free(tmp); } static void @@ -1537,6 +1543,8 @@ { const char *bname; MsnAddReqData *data; + MsnSession *session; + MsnUser *user; bname = purple_buddy_get_name(buddy); @@ -1558,12 +1566,18 @@ data->buddy = buddy; data->group = group; - purple_request_input(gc, NULL, _("Authorization Request Message:"), - NULL, _("Please authorize me!"), TRUE, FALSE, NULL, - _("_OK"), G_CALLBACK(finish_auth_request), - _("_Cancel"), G_CALLBACK(cancel_auth_request), - purple_connection_get_account(gc), bname, NULL, - data); + session = purple_connection_get_protocol_data(gc); + user = msn_userlist_find_user(session->userlist, bname); + if (user && user->authorized) { + finish_auth_request(data, NULL); + } else { + purple_request_input(gc, NULL, _("Authorization Request Message:"), + NULL, _("Please authorize me!"), TRUE, FALSE, NULL, + _("_OK"), G_CALLBACK(finish_auth_request), + _("_Cancel"), G_CALLBACK(cancel_auth_request), + purple_connection_get_account(gc), bname, NULL, + data); + } } static void @@ -2653,7 +2667,7 @@ OPT_PROTO_MAIL_CHECK, NULL, /* user_splits */ NULL, /* protocol_options */ - {"png", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_SEND}, /* icon_spec */ + {"png,gif", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_SEND}, /* icon_spec */ msn_list_icon, /* list_icon */ msn_list_emblems, /* list_emblems */ msn_status_text, /* status_text */ diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/msn/slp.c --- a/libpurple/protocols/msn/slp.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/msn/slp.c Fri Jan 08 20:11:49 2010 +0000 @@ -126,22 +126,60 @@ g_free(content); msn_slplink_send_queued_slpmsgs(slpcall->slplink); - msn_slpcall_destroy(slpcall); + if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) + slpcall->wasted = TRUE; + else + msn_slpcall_destroy(slpcall); } } } -void -msn_xfer_progress_cb(MsnSlpCall *slpcall, gsize total_length, gsize len, gsize offset) +gssize +msn_xfer_write(const guchar *data, gsize len, PurpleXfer *xfer) { - PurpleXfer *xfer; + MsnSlpCall *slpcall; + + g_return_val_if_fail(xfer != NULL, -1); + g_return_val_if_fail(data != NULL, -1); + g_return_val_if_fail(len > 0, -1); + + g_return_val_if_fail(purple_xfer_get_type(xfer) == PURPLE_XFER_SEND, -1); + + slpcall = xfer->data; + /* Not sure I trust it'll be there */ + g_return_val_if_fail(slpcall != NULL, -1); + + g_return_val_if_fail(slpcall->xfer_msg != NULL, -1); + + slpcall->u.outgoing.len = len; + slpcall->u.outgoing.data = data; + msn_slplink_send_msgpart(slpcall->slplink, slpcall->xfer_msg); + return MIN(1202, len); +} - xfer = slpcall->xfer; +gssize +msn_xfer_read(guchar **data, PurpleXfer *xfer) +{ + MsnSlpCall *slpcall; + gsize len; + + g_return_val_if_fail(xfer != NULL, -1); + g_return_val_if_fail(data != NULL, -1); + + g_return_val_if_fail(purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE, -1); - xfer->bytes_sent = (offset + len); - xfer->bytes_remaining = total_length - (offset + len); + slpcall = xfer->data; + /* Not sure I trust it'll be there */ + g_return_val_if_fail(slpcall != NULL, -1); - purple_xfer_update_progress(xfer); + /* Just pass up the whole GByteArray. We'll make another. */ + *data = slpcall->u.incoming_data->data; + len = slpcall->u.incoming_data->len; + + g_byte_array_free(slpcall->u.incoming_data, FALSE); + slpcall->u.incoming_data = g_byte_array_new(); + + return len; } void @@ -305,7 +343,6 @@ obj = msn_object_new_from_string(msnobj_data); type = msn_object_get_type(obj); g_free(msnobj_data); - if (type == MSN_OBJECT_EMOTICON) { img = find_valid_emoticon(slplink->session->account, obj->location); } else if (type == MSN_OBJECT_USERTILE) { @@ -352,9 +389,7 @@ account = slpcall->slplink->session->account; - slpcall->cb = msn_xfer_completed_cb; slpcall->end_cb = msn_xfer_end_cb; - slpcall->progress_cb = msn_xfer_progress_cb; slpcall->branch = g_strdup(branch); slpcall->pending = TRUE; @@ -377,6 +412,10 @@ purple_xfer_set_init_fnc(xfer, msn_xfer_init); purple_xfer_set_request_denied_fnc(xfer, msn_xfer_cancel); purple_xfer_set_cancel_recv_fnc(xfer, msn_xfer_cancel); + purple_xfer_set_read_fnc(xfer, msn_xfer_read); + purple_xfer_set_write_fnc(xfer, msn_xfer_write); + + slpcall->u.incoming_data = g_byte_array_new(); slpcall->xfer = xfer; purple_xfer_ref(slpcall->xfer); diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/msn/slp.h --- a/libpurple/protocols/msn/slp.h Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/msn/slp.h Fri Jan 08 20:11:49 2010 +0000 @@ -29,9 +29,6 @@ #include "internal.h" #include "ft.h" -void msn_xfer_progress_cb(MsnSlpCall *slpcall, gsize total_length, gsize - len, gsize offset); - MsnSlpCall * msn_slp_sip_recv(MsnSlpLink *slplink, const char *body); @@ -41,6 +38,9 @@ const guchar *body, gsize size); void msn_xfer_cancel(PurpleXfer *xfer); +gssize msn_xfer_write(const guchar *data, gsize len, PurpleXfer *xfer); +gssize msn_xfer_read(guchar **data, PurpleXfer *xfer); + void msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session); void msn_queue_buddy_icon_request(MsnUser *user); diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/msn/slpcall.c --- a/libpurple/protocols/msn/slpcall.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/msn/slpcall.c Fri Jan 08 20:11:49 2010 +0000 @@ -105,10 +105,13 @@ slpcall->end_cb(slpcall, slpcall->slplink->session); if (slpcall->xfer != NULL) { + if (purple_xfer_get_type(slpcall->xfer) == PURPLE_XFER_RECEIVE) + g_byte_array_free(slpcall->u.incoming_data, TRUE); slpcall->xfer->data = NULL; purple_xfer_unref(slpcall->xfer); } + msn_slplink_remove_slpcall(slpcall->slplink, slpcall); g_free(slpcall->id); @@ -272,7 +275,8 @@ slpcall->timer = 0; } - slpcall->cb(slpcall, body, body_len); + if (slpcall->cb) + slpcall->cb(slpcall, body, body_len); slpcall->wasted = TRUE; } diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/msn/slpcall.h --- a/libpurple/protocols/msn/slpcall.h Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/msn/slpcall.h Fri Jan 08 20:11:49 2010 +0000 @@ -72,6 +72,14 @@ char *data_info; PurpleXfer *xfer; + union { + GByteArray *incoming_data; + struct { + gsize len; + const guchar *data; + } outgoing; + } u; + MsnSlpMessage *xfer_msg; /* A dirty hack */ MsnSlpCb cb; void (*end_cb)(MsnSlpCall *slpcall, MsnSession *session); diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/msn/slplink.c --- a/libpurple/protocols/msn/slplink.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/msn/slplink.c Fri Jan 08 20:11:49 2010 +0000 @@ -232,7 +232,7 @@ } } -static void +void msn_slplink_send_msgpart(MsnSlpLink *slplink, MsnSlpMessage *slpmsg) { MsnMessage *msg; @@ -247,11 +247,11 @@ if (slpmsg->offset < real_size) { - if (slpmsg->fp) + if (slpmsg->slpcall && slpmsg->slpcall->xfer && purple_xfer_get_type(slpmsg->slpcall->xfer) == PURPLE_XFER_SEND && + purple_xfer_get_status(slpmsg->slpcall->xfer) == PURPLE_XFER_STATUS_STARTED) { - char data[1202]; - len = fread(data, 1, sizeof(data), slpmsg->fp); - msn_message_set_bin_data(msg, data, len); + len = MIN(1202, slpmsg->slpcall->u.outgoing.len); + msn_message_set_bin_data(msg, slpmsg->slpcall->u.outgoing.data, len); } else { @@ -309,7 +309,13 @@ if (slpmsg->offset < real_size) { - msn_slplink_send_msgpart(slpmsg->slplink, slpmsg); + if (slpmsg->slpcall->xfer && purple_xfer_get_status(slpmsg->slpcall->xfer) == PURPLE_XFER_STATUS_STARTED) + { + slpmsg->slpcall->xfer_msg = slpmsg; + purple_xfer_prpl_ready(slpmsg->slpcall->xfer); + } + else + msn_slplink_send_msgpart(slpmsg->slplink, slpmsg); } else { @@ -448,20 +454,22 @@ send_file_cb(MsnSlpCall *slpcall) { MsnSlpMessage *slpmsg; - struct stat st; PurpleXfer *xfer; + xfer = (PurpleXfer *)slpcall->xfer; + purple_xfer_ref(xfer); + purple_xfer_start(xfer, -1, NULL, 0); + if (purple_xfer_get_status(xfer) != PURPLE_XFER_STATUS_STARTED) { + purple_xfer_unref(xfer); + return; + } + purple_xfer_unref(xfer); + slpmsg = msn_slpmsg_new(slpcall->slplink); slpmsg->slpcall = slpcall; slpmsg->flags = 0x1000030; slpmsg->info = "SLP FILE"; - - xfer = (PurpleXfer *)slpcall->xfer; - purple_xfer_start(slpcall->xfer, -1, NULL, 0); - slpmsg->fp = xfer->dest_fp; - if (g_stat(purple_xfer_get_local_filename(xfer), &st) == 0) - slpmsg->size = st.st_size; - xfer->dest_fp = NULL; /* Disable double fclose() */ + slpmsg->size = purple_xfer_get_size(xfer); msn_slplink_send_slpmsg(slpcall->slplink, slpmsg); } @@ -489,6 +497,7 @@ const char *data; guint64 offset; gsize len; + PurpleXfer *xfer = NULL; if (purple_debug_is_verbose()) msn_slpmsg_show(msg); @@ -525,12 +534,12 @@ if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 || slpmsg->flags == 0x1000030) { - PurpleXfer *xfer; - xfer = slpmsg->slpcall->xfer; - if (xfer != NULL) { + slpmsg->ft = TRUE; + slpmsg->slpcall->xfer_msg = slpmsg; + purple_xfer_ref(xfer); purple_xfer_start(xfer, -1, NULL, 0); @@ -540,14 +549,12 @@ g_return_if_reached(); } else { purple_xfer_unref(xfer); - slpmsg->fp = xfer->dest_fp; - xfer->dest_fp = NULL; /* Disable double fclose() */ } } } } } - if (!slpmsg->fp && slpmsg->size) + if (!slpmsg->ft && slpmsg->size) { slpmsg->buffer = g_try_malloc(slpmsg->size); if (slpmsg->buffer == NULL) @@ -569,10 +576,12 @@ } } - if (slpmsg->fp) + if (slpmsg->ft) { - /* fseek(slpmsg->fp, offset, SEEK_SET); */ - len = fwrite(data, 1, len, slpmsg->fp); + xfer = slpmsg->slpcall->xfer; + slpmsg->slpcall->u.incoming_data = + g_byte_array_append(slpmsg->slpcall->u.incoming_data, (const guchar *)data, len); + purple_xfer_prpl_ready(xfer); } else if (slpmsg->size && slpmsg->buffer) { @@ -613,29 +622,37 @@ slpcall = msn_slp_process_msg(slplink, slpmsg); - if (slpmsg->flags == 0x100) - { - MsnDirectConn *directconn; + if (slpcall == NULL) { + msn_slpmsg_destroy(slpmsg); + return; + } - directconn = slplink->directconn; + if (!slpcall->wasted) { + if (slpmsg->flags == 0x100) + { + MsnDirectConn *directconn; + + directconn = slplink->directconn; #if 0 - if (!directconn->acked) - msn_directconn_send_handshake(directconn); + if (!directconn->acked) + msn_directconn_send_handshake(directconn); #endif - } - else if (slpmsg->flags == 0x00 || slpmsg->flags == 0x1000000 || - slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 || - slpmsg->flags == 0x1000030) - { - /* Release all the messages and send the ACK */ + } + else if (slpmsg->flags == 0x00 || slpmsg->flags == 0x1000000 || + slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 || + slpmsg->flags == 0x1000030) + { + /* Release all the messages and send the ACK */ - msn_slplink_send_ack(slplink, msg); - msn_slplink_send_queued_slpmsgs(slplink); + msn_slplink_send_ack(slplink, msg); + msn_slplink_send_queued_slpmsgs(slplink); + } + } msn_slpmsg_destroy(slpmsg); - if (slpcall != NULL && slpcall->wasted) + if (slpcall->wasted) msn_slpcall_destroy(slpcall); } } @@ -652,9 +669,8 @@ #define MAX_FILE_NAME_LEN 0x226 static gchar * -gen_context(const char *file_name, const char *file_path) +gen_context(PurpleXfer *xfer, const char *file_name, const char *file_path) { - struct stat st; gsize size = 0; MsnContextHeader header; gchar *u8 = NULL; @@ -666,8 +682,7 @@ glong uni_len = 0; gsize len; - if (g_stat(file_path, &st) == 0) - size = st.st_size; + size = purple_xfer_get_size(xfer); if(!file_name) { gchar *basename = g_path_get_basename(file_path); @@ -732,7 +747,6 @@ slpcall->session_init_cb = send_file_cb; slpcall->end_cb = msn_xfer_end_cb; - slpcall->progress_cb = msn_xfer_progress_cb; slpcall->cb = msn_xfer_completed_cb; slpcall->xfer = xfer; purple_xfer_ref(slpcall->xfer); @@ -740,10 +754,12 @@ slpcall->pending = TRUE; purple_xfer_set_cancel_send_fnc(xfer, msn_xfer_cancel); + purple_xfer_set_read_fnc(xfer, msn_xfer_read); + purple_xfer_set_write_fnc(xfer, msn_xfer_write); xfer->data = slpcall; - context = gen_context(fn, fp); + context = gen_context(xfer, fn, fp); msn_slpcall_invite(slpcall, MSN_FT_GUID, 2, context); diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/msn/slplink.h --- a/libpurple/protocols/msn/slplink.h Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/msn/slplink.h Fri Jan 08 20:11:49 2010 +0000 @@ -84,6 +84,9 @@ void msn_slplink_process_msg(MsnSlpLink *slplink, MsnMessage *msg); void msn_slplink_request_ft(MsnSlpLink *slplink, PurpleXfer *xfer); +/* Only exported for msn_xfer_write */ +void msn_slplink_send_msgpart(MsnSlpLink *slplink, MsnSlpMessage *slpmsg); + void msn_slplink_request_object(MsnSlpLink *slplink, const char *info, MsnSlpCb cb, diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/msn/slpmsg.c --- a/libpurple/protocols/msn/slpmsg.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/msn/slpmsg.c Fri Jan 08 20:11:49 2010 +0000 @@ -60,9 +60,6 @@ slplink = slpmsg->slplink; - if (slpmsg->fp != NULL) - fclose(slpmsg->fp); - purple_imgstore_unref(slpmsg->img); /* We don't want to free the data of the PurpleStoredImage, @@ -96,7 +93,7 @@ /* We can only have one data source at a time. */ g_return_if_fail(slpmsg->buffer == NULL); g_return_if_fail(slpmsg->img == NULL); - g_return_if_fail(slpmsg->fp == NULL); + g_return_if_fail(slpmsg->ft == FALSE); if (body != NULL) slpmsg->buffer = g_memdup(body, size); @@ -112,7 +109,7 @@ /* We can only have one data source at a time. */ g_return_if_fail(slpmsg->buffer == NULL); g_return_if_fail(slpmsg->img == NULL); - g_return_if_fail(slpmsg->fp == NULL); + g_return_if_fail(slpmsg->ft == FALSE); slpmsg->img = purple_imgstore_ref(img); slpmsg->buffer = (guchar *)purple_imgstore_get_data(img); diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/msn/slpmsg.h --- a/libpurple/protocols/msn/slpmsg.h Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/msn/slpmsg.h Fri Jan 08 20:11:49 2010 +0000 @@ -54,7 +54,7 @@ gboolean sip; /**< A flag that states if this is a SIP slp message. */ long flags; - FILE *fp; + gboolean ft; PurpleStoredImage *img; guchar *buffer; long long offset; diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/msn/switchboard.c --- a/libpurple/protocols/msn/switchboard.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/msn/switchboard.c Fri Jan 08 20:11:49 2010 +0000 @@ -222,13 +222,28 @@ { MsnCmdProc *cmdproc; PurpleAccount *account; + char *semicolon; + char *passport; g_return_if_fail(swboard != NULL); cmdproc = swboard->cmdproc; account = cmdproc->session->account; - swboard->users = g_list_prepend(swboard->users, g_strdup(user)); + semicolon = strchr(user, ';'); + /* We don't really care about the machine ID. */ + if (semicolon) + passport = g_strndup(user, semicolon - user); + else + passport = g_strdup(user); + + /* Don't add multiple endpoints to the conversation. */ + if (g_list_find_custom(swboard->users, passport, (GCompareFunc)strcmp)) { + g_free(passport); + return; + } + + swboard->users = g_list_prepend(swboard->users, passport); swboard->current_users++; swboard->empty = FALSE; diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/msn/userlist.c --- a/libpurple/protocols/msn/userlist.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/msn/userlist.c Fri Jan 08 20:11:49 2010 +0000 @@ -210,6 +210,7 @@ if (list_op & MSN_LIST_PL_OP) { + user->authorized = TRUE; got_new_entry(gc, passport, store, message); } } diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/mxit/actions.c --- a/libpurple/protocols/mxit/actions.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/mxit/actions.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,11 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "protocol.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/mxit/chunk.c --- a/libpurple/protocols/mxit/chunk.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/mxit/chunk.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,10 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "protocol.h" #include "mxit.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/mxit/cipher.c --- a/libpurple/protocols/mxit/cipher.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/mxit/cipher.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,10 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "mxit.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/mxit/filexfer.c --- a/libpurple/protocols/mxit/filexfer.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/mxit/filexfer.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,10 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "protocol.h" #include "mxit.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/mxit/formcmds.c --- a/libpurple/protocols/mxit/formcmds.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/mxit/formcmds.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,8 +23,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include + +#include "internal.h" #include #include "purple.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/mxit/http.c --- a/libpurple/protocols/mxit/http.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/mxit/http.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,11 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "mxit.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/mxit/login.c --- a/libpurple/protocols/mxit/login.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/mxit/login.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,9 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include - +#include "internal.h" #include "purple.h" #include "protocol.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/mxit/markup.c --- a/libpurple/protocols/mxit/markup.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/mxit/markup.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,10 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "protocol.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/mxit/multimx.c --- a/libpurple/protocols/mxit/multimx.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/mxit/multimx.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,9 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include - +#include "internal.h" #include "purple.h" #include "prpl.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/mxit/mxit.c --- a/libpurple/protocols/mxit/mxit.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/mxit/mxit.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,10 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "notify.h" #include "plugin.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/mxit/profile.c --- a/libpurple/protocols/mxit/profile.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/mxit/profile.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,9 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include - +#include "internal.h" #include "purple.h" #include "mxit.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/mxit/protocol.c --- a/libpurple/protocols/mxit/protocol.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/mxit/protocol.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,11 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "protocol.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/mxit/roster.c --- a/libpurple/protocols/mxit/roster.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/mxit/roster.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,10 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "protocol.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/mxit/splashscreen.c --- a/libpurple/protocols/mxit/splashscreen.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/mxit/splashscreen.c Fri Jan 08 20:11:49 2010 +0000 @@ -23,6 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include "internal.h" #include "purple.h" #include "imgstore.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/myspace/user.c diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/oscar/clientlogin.c --- a/libpurple/protocols/oscar/clientlogin.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/oscar/clientlogin.c Fri Jan 08 20:11:49 2010 +0000 @@ -36,12 +36,12 @@ * http://dev.aol.com/authentication_for_clients */ +#include "oscar.h" +#include "oscarcommon.h" + #include "cipher.h" #include "core.h" -#include "oscar.h" -#include "oscarcommon.h" - #define URL_CLIENT_LOGIN "https://api.screenname.aol.com/auth/clientLogin" #define URL_START_OSCAR_SESSION "http://api.oscar.aol.com/aim/startOSCARSession" @@ -275,13 +275,20 @@ char *query_string, *signature, *url; gboolean use_tls = purple_account_get_bool(purple_connection_get_account(od->gc), "use_ssl", OSCAR_DEFAULT_USE_SSL); - /* Construct the GET parameters */ + /* + * Construct the GET parameters. 0x00000611 is the distid given to + * us by AOL for use as the default libpurple distid. + */ query_string = g_strdup_printf("a=%s" + "&distId=%d" "&f=xml" "&k=%s" "&ts=%" PURPLE_TIME_T_MODIFIER "&useTLS=%d", - purple_url_encode(token), get_client_key(od), hosttime, use_tls); + purple_url_encode(token), + oscar_get_ui_info_int(od->icq ? "prpl-icq-distid" + : "prpl-aim-distid", 0x00000611), + get_client_key(od), hosttime, use_tls); signature = generate_signature("GET", URL_START_OSCAR_SESSION, query_string, session_key); url = g_strdup_printf(URL_START_OSCAR_SESSION "?%s&sig_sha256=%s", diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/oscar/family_auth.c --- a/libpurple/protocols/oscar/family_auth.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/oscar/family_auth.c Fri Jan 08 20:11:49 2010 +0000 @@ -26,12 +26,12 @@ * */ +#include "oscar.h" + #include #include "cipher.h" -#include "oscar.h" - /* #define USE_XOR_FOR_ICQ */ #ifdef USE_XOR_FOR_ICQ diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/oscar/oscar.c --- a/libpurple/protocols/oscar/oscar.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/oscar/oscar.c Fri Jan 08 20:11:49 2010 +0000 @@ -378,12 +378,12 @@ } static gchar * -oscar_utf8_try_convert(PurpleAccount *account, const gchar *msg) +oscar_utf8_try_convert(PurpleAccount *account, OscarData *od, const gchar *msg) { const char *charset = NULL; char *ret = NULL; - if(oscar_util_valid_name_icq(purple_account_get_username(account))) + if (od->icq) charset = purple_account_get_string(account, "encoding", NULL); if(charset && *charset) @@ -798,24 +798,24 @@ } static void -oscar_user_info_convert_and_add_pair(PurpleAccount *account, PurpleNotifyUserInfo *user_info, +oscar_user_info_convert_and_add_pair(PurpleAccount *account, OscarData *od, PurpleNotifyUserInfo *user_info, const char *name, const char *value) { gchar *utf8; - if (value && value[0] && (utf8 = oscar_utf8_try_convert(account, value))) { + if (value && value[0] && (utf8 = oscar_utf8_try_convert(account, od, value))) { purple_notify_user_info_add_pair(user_info, name, utf8); g_free(utf8); } } static void -oscar_user_info_convert_and_add(PurpleAccount *account, PurpleNotifyUserInfo *user_info, +oscar_user_info_convert_and_add(PurpleAccount *account, OscarData *od, PurpleNotifyUserInfo *user_info, const char *name, const char *value) { gchar *utf8; - if (value && value[0] && (utf8 = oscar_utf8_try_convert(account, value))) { + if (value && value[0] && (utf8 = oscar_utf8_try_convert(account, od, value))) { purple_notify_user_info_add_pair(user_info, name, utf8); g_free(utf8); } @@ -1022,7 +1022,7 @@ char *tmp2 = g_markup_escape_text(tmp, strlen(tmp)); g_free(tmp); - oscar_user_info_convert_and_add_pair(account, user_info, _("Buddy Comment"), tmp2); + oscar_user_info_convert_and_add_pair(account, od, user_info, _("Buddy Comment"), tmp2); g_free(tmp2); } } @@ -2450,7 +2450,7 @@ * Note: There *may* be some clients which send messages as HTML formatted - * they need to be special-cased somehow. */ - if (oscar_util_valid_name_icq(purple_account_get_username(account)) && oscar_util_valid_name_icq(userinfo->bn)) { + if (od->icq && oscar_util_valid_name_icq(userinfo->bn)) { /* being recevied by ICQ from ICQ - escape HTML so it is displayed as sent */ gchar *tmp2 = g_markup_escape_text(tmp, -1); g_free(tmp); @@ -4173,7 +4173,7 @@ bi = NULL; purple_notify_user_info_add_pair(user_info, _("UIN"), who); - oscar_user_info_convert_and_add(account, user_info, _("Nick"), info->nick); + oscar_user_info_convert_and_add(account, od, user_info, _("Nick"), info->nick); if ((bi != NULL) && (bi->ipaddr != 0)) { char *tstr = g_strdup_printf("%hhu.%hhu.%hhu.%hhu", (bi->ipaddr & 0xff000000) >> 24, @@ -4183,9 +4183,9 @@ purple_notify_user_info_add_pair(user_info, _("IP Address"), tstr); g_free(tstr); } - oscar_user_info_convert_and_add(account, user_info, _("First Name"), info->first); - oscar_user_info_convert_and_add(account, user_info, _("Last Name"), info->last); - if (info->email && info->email[0] && (utf8 = oscar_utf8_try_convert(account, info->email))) { + oscar_user_info_convert_and_add(account, od, user_info, _("First Name"), info->first); + oscar_user_info_convert_and_add(account, od, user_info, _("Last Name"), info->last); + if (info->email && info->email[0] && (utf8 = oscar_utf8_try_convert(account, od, info->email))) { buf = g_strdup_printf("%s", utf8, utf8); purple_notify_user_info_add_pair(user_info, _("Email Address"), buf); g_free(buf); @@ -4194,7 +4194,7 @@ if (info->numaddresses && info->email2) { int i; for (i = 0; i < info->numaddresses; i++) { - if (info->email2[i] && info->email2[i][0] && (utf8 = oscar_utf8_try_convert(account, info->email2[i]))) { + if (info->email2[i] && info->email2[i][0] && (utf8 = oscar_utf8_try_convert(account, od, info->email2[i]))) { buf = g_strdup_printf("%s", utf8, utf8); purple_notify_user_info_add_pair(user_info, _("Email Address"), buf); g_free(buf); @@ -4202,7 +4202,7 @@ } } } - oscar_user_info_convert_and_add(account, user_info, _("Mobile Phone"), info->mobile); + oscar_user_info_convert_and_add(account, od, user_info, _("Mobile Phone"), info->mobile); if (info->gender != 0) purple_notify_user_info_add_pair(user_info, _("Gender"), (info->gender == 1 ? _("Female") : _("Male"))); @@ -4222,14 +4222,14 @@ * feel free to remove it. --rlaager */ mktime(tm); - oscar_user_info_convert_and_add(account, user_info, _("Birthday"), purple_date_format_short(tm)); + oscar_user_info_convert_and_add(account, od, user_info, _("Birthday"), purple_date_format_short(tm)); } if ((info->age > 0) && (info->age < 255)) { char age[5]; snprintf(age, sizeof(age), "%hhd", info->age); purple_notify_user_info_add_pair(user_info, _("Age"), age); } - if (info->personalwebpage && info->personalwebpage[0] && (utf8 = oscar_utf8_try_convert(account, info->personalwebpage))) { + if (info->personalwebpage && info->personalwebpage[0] && (utf8 = oscar_utf8_try_convert(account, od, info->personalwebpage))) { buf = g_strdup_printf("%s", utf8, utf8); purple_notify_user_info_add_pair(user_info, _("Personal Web Page"), buf); g_free(buf); @@ -4239,33 +4239,33 @@ if (buddy != NULL) oscar_user_info_append_status(gc, user_info, buddy, /* aim_userinfo_t */ NULL, /* strip_html_tags */ FALSE); - oscar_user_info_convert_and_add(account, user_info, _("Additional Information"), info->info); + oscar_user_info_convert_and_add(account, od, user_info, _("Additional Information"), info->info); purple_notify_user_info_add_section_break(user_info); if ((info->homeaddr && (info->homeaddr[0])) || (info->homecity && info->homecity[0]) || (info->homestate && info->homestate[0]) || (info->homezip && info->homezip[0])) { purple_notify_user_info_add_section_header(user_info, _("Home Address")); - oscar_user_info_convert_and_add(account, user_info, _("Address"), info->homeaddr); - oscar_user_info_convert_and_add(account, user_info, _("City"), info->homecity); - oscar_user_info_convert_and_add(account, user_info, _("State"), info->homestate); - oscar_user_info_convert_and_add(account, user_info, _("Zip Code"), info->homezip); + oscar_user_info_convert_and_add(account, od, user_info, _("Address"), info->homeaddr); + oscar_user_info_convert_and_add(account, od, user_info, _("City"), info->homecity); + oscar_user_info_convert_and_add(account, od, user_info, _("State"), info->homestate); + oscar_user_info_convert_and_add(account, od, user_info, _("Zip Code"), info->homezip); } if ((info->workaddr && info->workaddr[0]) || (info->workcity && info->workcity[0]) || (info->workstate && info->workstate[0]) || (info->workzip && info->workzip[0])) { purple_notify_user_info_add_section_header(user_info, _("Work Address")); - oscar_user_info_convert_and_add(account, user_info, _("Address"), info->workaddr); - oscar_user_info_convert_and_add(account, user_info, _("City"), info->workcity); - oscar_user_info_convert_and_add(account, user_info, _("State"), info->workstate); - oscar_user_info_convert_and_add(account, user_info, _("Zip Code"), info->workzip); + oscar_user_info_convert_and_add(account, od, user_info, _("Address"), info->workaddr); + oscar_user_info_convert_and_add(account, od, user_info, _("City"), info->workcity); + oscar_user_info_convert_and_add(account, od, user_info, _("State"), info->workstate); + oscar_user_info_convert_and_add(account, od, user_info, _("Zip Code"), info->workzip); } if ((info->workcompany && info->workcompany[0]) || (info->workdivision && info->workdivision[0]) || (info->workposition && info->workposition[0]) || (info->workwebpage && info->workwebpage[0])) { purple_notify_user_info_add_section_header(user_info, _("Work Information")); - oscar_user_info_convert_and_add(account, user_info, _("Company"), info->workcompany); - oscar_user_info_convert_and_add(account, user_info, _("Division"), info->workdivision); - oscar_user_info_convert_and_add(account, user_info, _("Position"), info->workposition); - - if (info->workwebpage && info->workwebpage[0] && (utf8 = oscar_utf8_try_convert(account, info->workwebpage))) { + oscar_user_info_convert_and_add(account, od, user_info, _("Company"), info->workcompany); + oscar_user_info_convert_and_add(account, od, user_info, _("Division"), info->workdivision); + oscar_user_info_convert_and_add(account, od, user_info, _("Position"), info->workposition); + + if (info->workwebpage && info->workwebpage[0] && (utf8 = oscar_utf8_try_convert(account, od, info->workwebpage))) { char *webpage = g_strdup_printf("%s", utf8, utf8); purple_notify_user_info_add_pair(user_info, _("Web Page"), webpage); g_free(webpage); @@ -4296,7 +4296,7 @@ info = va_arg(ap, struct aim_icq_info *); va_end(ap); - if (info->uin && info->nick && info->nick[0] && (utf8 = oscar_utf8_try_convert(account, info->nick))) { + if (info->uin && info->nick && info->nick[0] && (utf8 = oscar_utf8_try_convert(account, od, info->nick))) { g_snprintf(who, sizeof(who), "%u", info->uin); serv_got_alias(gc, who, utf8); if ((b = purple_find_buddy(account, who))) { @@ -4650,7 +4650,7 @@ if ((conn != NULL) && (conn->ready)) { /* If we're directly connected, send a direct IM */ - purple_debug_info("oscar", "Sending direct IM with flags %i", imflags); + purple_debug_info("oscar", "Sending direct IM with flags %i\n", imflags); purple_odc_send_im(conn, tmp1, imflags); } else { struct buddyinfo *bi; @@ -4751,7 +4751,7 @@ /* Messaging an SMS (mobile) user */ tmp2 = purple_markup_strip_html(tmp1); is_html = FALSE; - } else if (oscar_util_valid_name_icq(purple_account_get_username(account))) { + } else if (od->icq) { if (oscar_util_valid_name_icq(name)) { /* From ICQ to ICQ */ tmp2 = purple_markup_strip_html(tmp1); @@ -5047,6 +5047,9 @@ void oscar_set_status(PurpleAccount *account, PurpleStatus *status) { + PurpleConnection *pc; + OscarData *od; + purple_debug_info("oscar", "Set status to %s\n", purple_status_get_name(status)); if (!purple_status_is_active(status)) @@ -5055,11 +5058,14 @@ if (!purple_account_is_connected(account)) return; + pc = purple_account_get_connection(account); + od = purple_connection_get_protocol_data(pc); + /* Set the AIM-style away message for both AIM and ICQ accounts */ oscar_set_info_and_status(account, FALSE, NULL, TRUE, status); /* Set the ICQ status for ICQ accounts only */ - if (oscar_util_valid_name_icq(purple_account_get_username(account))) + if (od->icq) oscar_set_status_icq(account); } @@ -5404,7 +5410,7 @@ if (g_utf8_validate(gname, -1, NULL)) gname_utf8 = g_strdup(gname); else - gname_utf8 = oscar_utf8_try_convert(account, gname); + gname_utf8 = oscar_utf8_try_convert(account, od, gname); } else gname_utf8 = NULL; @@ -5419,7 +5425,7 @@ if (g_utf8_validate(alias, -1, NULL)) alias_utf8 = g_strdup(alias); else - alias_utf8 = oscar_utf8_try_convert(account, alias); + alias_utf8 = oscar_utf8_try_convert(account, od, alias); g_free(alias); } else alias_utf8 = NULL; @@ -5468,7 +5474,7 @@ if (g_utf8_validate(gname, -1, NULL)) gname_utf8 = g_strdup(gname); else - gname_utf8 = oscar_utf8_try_convert(account, gname); + gname_utf8 = oscar_utf8_try_convert(account, od, gname); } else gname_utf8 = NULL; @@ -5636,7 +5642,7 @@ return 1; gname = aim_ssi_itemlist_findparentname(od->ssi.local, name); - gname_utf8 = gname ? oscar_utf8_try_convert(account, gname) : NULL; + gname_utf8 = gname ? oscar_utf8_try_convert(account, od, gname) : NULL; alias = aim_ssi_getalias(od->ssi.local, gname, name); if (alias != NULL) @@ -5644,7 +5650,7 @@ if (g_utf8_validate(alias, -1, NULL)) alias_utf8 = g_strdup(alias); else - alias_utf8 = oscar_utf8_try_convert(account, alias); + alias_utf8 = oscar_utf8_try_convert(account, od, alias); } else alias_utf8 = NULL; @@ -6417,7 +6423,7 @@ data = g_new(struct name_data, 1); comment = aim_ssi_getcomment(od->ssi.local, purple_group_get_name(g), name); - comment_utf8 = comment ? oscar_utf8_try_convert(account, comment) : NULL; + comment_utf8 = comment ? oscar_utf8_try_convert(account, od, comment) : NULL; data->gc = gc; data->name = g_strdup(name); diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/oscar/oscarcommon.h --- a/libpurple/protocols/oscar/oscarcommon.h Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/oscar/oscarcommon.h Fri Jan 08 20:11:49 2010 +0000 @@ -24,8 +24,9 @@ * and libicq.c */ +#include "internal.h" + #include "accountopt.h" -#include "internal.h" #include "prpl.h" #include "version.h" #include "notify.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/protocols/yahoo/ycht.c --- a/libpurple/protocols/yahoo/ycht.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/protocols/yahoo/ycht.c Fri Jan 08 20:11:49 2010 +0000 @@ -25,8 +25,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include - #include "internal.h" #include "prpl.h" #include "notify.h" diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/proxy.c diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/purple-uninstalled.pc.in --- a/libpurple/purple-uninstalled.pc.in Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/purple-uninstalled.pc.in Fri Jan 08 20:11:49 2010 +0000 @@ -6,9 +6,12 @@ datadir=@datadir@ sysconfdir=@sysconfdir@ +abs_srcdir=@abs_srcdir@ +abs_builddir=@abs_builddir@ + Name: libpurple Description: libpurple is a GLib-based instant messenger library. Version: @VERSION@ Requires: glib-2.0 -Cflags: -I${pcfiledir} -Libs: ${pcfiledir}/libpurple.la +Cflags: -I${abs_srcdir} -I${abs_builddir} +Libs: ${abs_builddir}/libpurple.la diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/tests/Makefile.am --- a/libpurple/tests/Makefile.am Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/tests/Makefile.am Fri Jan 08 20:11:49 2010 +0000 @@ -11,6 +11,7 @@ tests.h \ test_cipher.c \ test_jabber_jutil.c \ + test_jabber_scram.c \ test_qq.c \ test_yahoo_util.c \ test_util.c \ @@ -26,11 +27,11 @@ -DBUILDDIR=\"$(top_builddir)\" check_libpurple_LDADD=\ - @CHECK_LIBS@ \ - $(GLIB_LIBS) \ $(top_builddir)/libpurple/protocols/jabber/libjabber.la \ $(top_builddir)/libpurple/protocols/qq/libqq.la \ $(top_builddir)/libpurple/protocols/yahoo/libymsg.la \ - $(top_builddir)/libpurple/libpurple.la + $(top_builddir)/libpurple/libpurple.la \ + @CHECK_LIBS@ \ + $(GLIB_LIBS) endif diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/tests/check_libpurple.c --- a/libpurple/tests/check_libpurple.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/tests/check_libpurple.c Fri Jan 08 20:11:49 2010 +0000 @@ -44,7 +44,7 @@ purple_eventloop_set_ui_ops(&eventloop_ui_ops); /* build our fake home directory */ - home_dir = g_build_path(BUILDDIR, "libpurple", "tests", "home", NULL); + home_dir = g_build_path(G_DIR_SEPARATOR_S, BUILDDIR, "libpurple", "tests", "home", NULL); purple_util_set_user_dir(home_dir); g_free(home_dir); @@ -67,6 +67,9 @@ int number_failed; SRunner *sr; + if (g_getenv("PURPLE_CHECK_DEBUG")) + purple_debug_set_enabled(TRUE); + /* Make g_return_... functions fatal, ALWAYS. * As this is the test code, this is NOT controlled * by PURPLE_FATAL_ASSERTS. */ @@ -76,6 +79,7 @@ srunner_add_suite(sr, cipher_suite()); srunner_add_suite(sr, jabber_jutil_suite()); + srunner_add_suite(sr, jabber_scram_suite()); srunner_add_suite(sr, qq_suite()); srunner_add_suite(sr, yahoo_util_suite()); srunner_add_suite(sr, util_suite()); diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/tests/test_cipher.c --- a/libpurple/tests/test_cipher.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/tests/test_cipher.c Fri Jan 08 20:11:49 2010 +0000 @@ -142,12 +142,13 @@ PurpleCipherContext *context = NULL; \ gchar cdigest[41]; \ gboolean ret = FALSE; \ + gchar *input = data; \ \ cipher = purple_ciphers_find_cipher("sha1"); \ context = purple_cipher_context_new(cipher, NULL); \ \ - if((data)) { \ - purple_cipher_context_append(context, (guchar *)(data), strlen((data))); \ + if (input) { \ + purple_cipher_context_append(context, (guchar *)input, strlen(input)); \ } else { \ gint j; \ guchar buff[1000]; \ @@ -202,12 +203,13 @@ PurpleCipherContext *context = NULL; \ gchar cdigest[65]; \ gboolean ret = FALSE; \ + gchar *input = data; \ \ cipher = purple_ciphers_find_cipher("sha256"); \ context = purple_cipher_context_new(cipher, NULL); \ \ - if((data)) { \ - purple_cipher_context_append(context, (guchar *)(data), strlen((data))); \ + if (input) { \ + purple_cipher_context_append(context, (guchar *)input, strlen(input)); \ } else { \ gint j; \ guchar buff[1000]; \ diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/tests/test_jabber_scram.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/tests/test_jabber_scram.c Fri Jan 08 20:11:49 2010 +0000 @@ -0,0 +1,117 @@ +#include + +#include "tests.h" +#include "../util.h" +#include "../protocols/jabber/auth_scram.h" +#include "../protocols/jabber/jutil.h" + +static JabberScramHash sha1_mech = { "-SHA-1", "sha1", 20 }; + +#define assert_pbkdf2_equal(password, salt, count, expected) { \ + GString *p = g_string_new(password); \ + GString *s = g_string_new(salt); \ + guchar *result = jabber_scram_hi(&sha1_mech, p, s, count); \ + fail_if(result == NULL, "Hi() returned NULL"); \ + fail_if(0 != memcmp(result, expected, 20), "Hi() returned invalid result"); \ + g_string_free(s, TRUE); \ + g_string_free(p, TRUE); \ +} + +START_TEST(test_pbkdf2) +{ + assert_pbkdf2_equal("password", "salt", 1, "\x0c\x60\xc8\x0f\x96\x1f\x0e\x71\xf3\xa9\xb5\x24\xaf\x60\x12\x06\x2f\xe0\x37\xa6"); + assert_pbkdf2_equal("password", "salt", 2, "\xea\x6c\x01\x4d\xc7\x2d\x6f\x8c\xcd\x1e\xd9\x2a\xce\x1d\x41\xf0\xd8\xde\x89\x57"); + assert_pbkdf2_equal("password", "salt", 4096, "\x4b\x00\x79\x01\xb7\x65\x48\x9a\xbe\xad\x49\xd9\x26\xf7\x21\xd0\x65\xa4\x29\xc1"); + +#if 0 + /* This causes libcheck to time out :-D */ + assert_pbkdf2_equal("password", "salt", 16777216, "\xee\xfe\x3d\x61\xcd\x4d\xa4\xe4\xe9\x94\x5b\x3d\x6b\xa2\x15\x8c\x26\x34\xe9\x84"); +#endif +} +END_TEST + +START_TEST(test_proofs) +{ + JabberScramData *data = g_new0(JabberScramData, 1); + gboolean ret; + GString *salt; + const char *client_proof; +/* const char *server_signature; */ + + data->hash = &sha1_mech; + data->password = g_strdup("password"); + data->auth_message = g_string_new("n=username@jabber.org,r=8jLxB5515dhFxBil5A0xSXMH," + "r=8jLxB5515dhFxBil5A0xSXMHabc,s=c2FsdA==,i=1," + "c=biws,r=8jLxB5515dhFxBil5A0xSXMHabc"); + client_proof = "\x48\x61\x30\xa5\x61\x0b\xae\xb9\xe4\x11\xa8\xfd\xa5\xcd\x34\x1d\x8a\x3c\x28\x17"; + + salt = g_string_new("salt"); + ret = jabber_scram_calc_proofs(data, salt, 1); + fail_if(ret == FALSE, "Failed to calculate SCRAM proofs!"); + + fail_unless(0 == memcmp(client_proof, data->client_proof->str, 20)); + g_string_free(salt, TRUE); + + jabber_scram_data_destroy(data); +} +END_TEST + +#define assert_successful_exchange(pw, nonce, start_data, challenge1, response1, success) { \ + JabberScramData *data = g_new0(JabberScramData, 1); \ + gboolean ret; \ + gchar *out; \ + \ + data->step = 1; \ + data->hash = &sha1_mech; \ + data->password = jabber_saslprep(pw); \ + fail_if(data->password == NULL); \ + data->cnonce = g_strdup(nonce); \ + data->auth_message = g_string_new(start_data); \ + \ + ret = jabber_scram_feed_parser(data, challenge1, &out); \ + fail_unless(ret == TRUE); \ + fail_unless(g_str_equal(out, response1), "Got unexpected response to challenge. Expected %s, got %s", response1, out); \ + g_free(out); \ + \ + data->step = 2; \ + ret = jabber_scram_feed_parser(data, success, &out); \ + fail_unless(ret == TRUE); \ + fail_unless(out == NULL); \ + \ + jabber_scram_data_destroy(data); \ +} + +START_TEST(test_mech) +{ + assert_successful_exchange("password", "H7yDYKAWBCrM2Fa5SxGa4iez", + "n=paul,r=H7yDYKAWBCrM2Fa5SxGa4iez", + "r=H7yDYKAWBCrM2Fa5SxGa4iezFPVDPpDUcGxPkH3RzP,s=3rXeErP/os7jUNqU,i=4096", + "c=biws,r=H7yDYKAWBCrM2Fa5SxGa4iezFPVDPpDUcGxPkH3RzP,p=pXkak78EuwwOEwk2/h/OzD7NkEI=", + "v=ldX4EBNnOgDnNTOCmbSfBHAUCOs="); + + assert_successful_exchange("pass½word", "GNb2HsNI7VnTv8ABsE5AnY8W", + "n=paul,r=GNb2HsNI7VnTv8ABsE5AnY8W", + "r=GNb2HsNI7VnTv8ABsE5AnY8W/w/I3eRKM0I7jxFWOH,s=ysAriUjPzFqOXnMQ,i=4096", + "c=biws,r=GNb2HsNI7VnTv8ABsE5AnY8W/w/I3eRKM0I7jxFWOH,p=n/CtgdWjOYnLQ4m9Na+wPn9D2uY=", + "v=4TkZwKWy6JHNmrUbU2+IdAaXtos="); +} +END_TEST + +Suite * +jabber_scram_suite(void) +{ + Suite *s = suite_create("Jabber SASL SCRAM functions"); + + TCase *tc = tcase_create("PBKDF2 Functionality"); + tcase_add_test(tc, test_pbkdf2); + suite_add_tcase(s, tc); + + tc = tcase_create("SCRAM Proofs"); + tcase_add_test(tc, test_proofs); + suite_add_tcase(s, tc); + + tc = tcase_create("SCRAM exchange"); + tcase_add_test(tc, test_mech); + suite_add_tcase(s, tc); + return s; +} diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/tests/tests.h --- a/libpurple/tests/tests.h Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/tests/tests.h Fri Jan 08 20:11:49 2010 +0000 @@ -10,6 +10,7 @@ Suite * master_suite(void); Suite * cipher_suite(void); Suite * jabber_jutil_suite(void); +Suite * jabber_scram_suite(void); Suite * qq_suite(void); Suite * yahoo_util_suite(void); Suite * util_suite(void); diff -r 10ec0fbfc790 -r 3a43e48c870e libpurple/xmlnode.c --- a/libpurple/xmlnode.c Fri Jan 08 19:01:39 2010 +0000 +++ b/libpurple/xmlnode.c Fri Jan 08 20:11:49 2010 +0000 @@ -588,9 +588,7 @@ const char *prefix = (const char *)attributes[i+1]; char *txt; int attrib_len = attributes[i+4] - attributes[i+3]; - char *attrib = g_malloc(attrib_len + 1); - memcpy(attrib, attributes[i+3], attrib_len); - attrib[attrib_len] = '\0'; + char *attrib = g_strndup((const char *)attributes[i+3], attrib_len); txt = attrib; attrib = purple_unescape_html(txt); g_free(txt); diff -r 10ec0fbfc790 -r 3a43e48c870e pidgin/gtkconv.c --- a/pidgin/gtkconv.c Fri Jan 08 19:01:39 2010 +0000 +++ b/pidgin/gtkconv.c Fri Jan 08 20:11:49 2010 +0000 @@ -5493,10 +5493,12 @@ } /* Somebody wants to keep this conversation around, so don't time it out */ - timer = GPOINTER_TO_INT(purple_conversation_get_data(conv, "close-timer")); - if (timer) { - purple_timeout_remove(timer); - purple_conversation_set_data(conv, "close-timer", GINT_TO_POINTER(0)); + if (conv) { + timer = GPOINTER_TO_INT(purple_conversation_get_data(conv, "close-timer")); + if (timer) { + purple_timeout_remove(timer); + purple_conversation_set_data(conv, "close-timer", GINT_TO_POINTER(0)); + } } } diff -r 10ec0fbfc790 -r 3a43e48c870e pidgin/gtkdialogs.c --- a/pidgin/gtkdialogs.c Fri Jan 08 19:01:39 2010 +0000 +++ b/pidgin/gtkdialogs.c Fri Jan 08 20:11:49 2010 +0000 @@ -197,7 +197,6 @@ {N_("Kurdish"), "ku", "Amed Ç. Jiyan", "amedcj@hotmail.com"}, {N_("Kurdish"), "ku", "Rizoyê Xerzî", "rizoxerzi@hotmail.com"}, {N_("Lao"), "lo", "Anousak Souphavah", "anousak@gmail.com"}, - {N_("Lithuanian"), "lt", "Laurynas Biveinis", "laurynas.biveinis@gmail.com"}, {N_("Macedonian"), "mk", "Arangel Angov ", "arangel@linux.net.mk"}, {N_("Macedonian"), "mk", "Ivana Kirkovska", "ivana.kirkovska@gmail.com"}, {N_("Macedonian"), "mk", "Jovan Naumovski", "jovan@lugola.net"}, @@ -276,8 +275,9 @@ {N_("Georgian"), "ka", "Temuri Doghonadze", NULL}, {N_("Korean"), "ko", "Sang-hyun S, A Ho-seok Lee", NULL}, {N_("Korean"), "ko", "Kyeong-uk Son", NULL}, + {N_("Lithuanian"), "lt", "Laurynas Biveinis", "laurynas.biveinis@gmail.com"}, + {N_("Lithuanian"), "lt", "Gediminas Čičinskas", NULL}, {N_("Lithuanian"), "lt", "Andrius Štikonas", NULL}, - {N_("Lithuanian"), "lt", "Gediminas Čičinskas", NULL}, {N_("Macedonian"), "mk", "Tomislav Markovski", NULL}, {N_("Bokmål Norwegian"), "nb", "Hallvard Glad", "hallvard.glad@gmail.com"}, {N_("Bokmål Norwegian"), "nb", "Petter Johan Olsen", NULL}, diff -r 10ec0fbfc790 -r 3a43e48c870e pidgin/gtkft.c --- a/pidgin/gtkft.c Fri Jan 08 19:01:39 2010 +0000 +++ b/pidgin/gtkft.c Fri Jan 08 20:11:49 2010 +0000 @@ -730,6 +730,7 @@ GtkWidget *sw; GtkWidget *button; GtkWidget *expander; + GtkWidget *alignment; GtkWidget *table; GtkWidget *checkbox; @@ -787,9 +788,15 @@ gtk_widget_set_sensitive(expander, FALSE); + /* Small indent make table fall under GtkExpander's label */ + alignment = gtk_alignment_new(1, 0, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 0, 0, 20, 0); + gtk_container_add(GTK_CONTAINER(expander), alignment); + gtk_widget_show(alignment); + /* The table of information. */ table = make_info_table(dialog); - gtk_container_add(GTK_CONTAINER(expander), table); + gtk_container_add(GTK_CONTAINER(alignment), table); gtk_widget_show(table); /* Open button */ diff -r 10ec0fbfc790 -r 3a43e48c870e pidgin/gtkimhtml.c --- a/pidgin/gtkimhtml.c Fri Jan 08 19:01:39 2010 +0000 +++ b/pidgin/gtkimhtml.c Fri Jan 08 20:11:49 2010 +0000 @@ -549,6 +549,7 @@ imhtml->tip_timer = 0; imhtml->tip_window = gtk_window_new (GTK_WINDOW_POPUP); gtk_widget_set_app_paintable (imhtml->tip_window, TRUE); + gtk_window_set_title(GTK_WINDOW(imhtml->tip_window), "GtkIMHtml"); gtk_window_set_resizable (GTK_WINDOW (imhtml->tip_window), FALSE); gtk_widget_set_name (imhtml->tip_window, "gtk-tooltips"); #if GTK_CHECK_VERSION(2,10,0) diff -r 10ec0fbfc790 -r 3a43e48c870e pidgin/gtkprefs.c --- a/pidgin/gtkprefs.c Fri Jan 08 19:01:39 2010 +0000 +++ b/pidgin/gtkprefs.c Fri Jan 08 20:11:49 2010 +0000 @@ -70,27 +70,33 @@ gchar *original_name; }; -static int sound_row_sel = 0; -static GtkWidget *prefsnotebook; - +/* Main dialog */ +static GtkWidget *prefs = NULL; + +/* Notebook */ +static GtkWidget *prefsnotebook = NULL; +static int notebook_page = 0; + +/* Conversations page */ +static GtkWidget *sample_imhtml = NULL; + +/* Themes page */ +static GtkWidget *prefs_sound_themes_combo_box; +static GtkWidget *prefs_blist_themes_combo_box; +static GtkWidget *prefs_status_themes_combo_box; +static GtkWidget *prefs_smiley_themes_combo_box; + +/* Sound theme specific */ static GtkWidget *sound_entry = NULL; - -static GtkWidget *prefs = NULL; -static GtkWidget *debugbutton = NULL; -static int notebook_page = 0; - +static int sound_row_sel = 0; +static gboolean prefs_sound_themes_loading; + +/* These exist outside the lifetime of the prefs dialog */ static GtkListStore *prefs_sound_themes; static GtkListStore *prefs_blist_themes; static GtkListStore *prefs_status_icon_themes; static GtkListStore *prefs_smiley_themes; -static GtkWidget *prefs_sound_themes_combo_box; -static GtkWidget *prefs_blist_themes_combo_box; -static GtkWidget *prefs_status_themes_combo_box; -static GtkWidget *prefs_smiley_themes_combo_box; - -static gboolean prefs_sound_themes_loading; - /* * PROTOTYPES */ @@ -334,10 +340,21 @@ /* Unregister callbacks. */ purple_prefs_disconnect_by_handle(prefs); - prefs = NULL; + /* NULL-ify globals */ sound_entry = NULL; - debugbutton = NULL; + sound_row_sel = 0; + prefs_sound_themes_loading = FALSE; + + prefs_sound_themes_combo_box = NULL; + prefs_blist_themes_combo_box = NULL; + prefs_status_themes_combo_box = NULL; + prefs_smiley_themes_combo_box = NULL; + + sample_imhtml = NULL; + notebook_page = 0; + prefsnotebook = NULL; + prefs = NULL; } static gchar * @@ -949,6 +966,7 @@ gtk_tree_model_get(GTK_TREE_MODEL(prefs_smiley_themes), &new_iter, 2, &new_theme, -1); purple_prefs_set_string(PIDGIN_PREFS_ROOT "/smileys/theme", new_theme); + pidgin_themes_smiley_themeize(sample_imhtml); g_free(new_theme); } @@ -1537,7 +1555,7 @@ G_CALLBACK(formatting_toggle_cb), toolbar); g_signal_connect_after(G_OBJECT(imhtml), "format_function_clear", G_CALLBACK(formatting_clear_cb), NULL); - + sample_imhtml = imhtml; gtk_widget_show(ret); @@ -2842,7 +2860,7 @@ purple_prefs_add_string(PIDGIN_PREFS_ROOT "/smileys/theme", "Default"); /* Smiley Callbacks */ - purple_prefs_connect_callback(prefs, PIDGIN_PREFS_ROOT "/smileys/theme", + purple_prefs_connect_callback(&prefs, PIDGIN_PREFS_ROOT "/smileys/theme", smiley_theme_pref_cb, NULL); pidgin_prefs_update_old(); diff -r 10ec0fbfc790 -r 3a43e48c870e pidgin/pidgin-uninstalled.pc.in --- a/pidgin/pidgin-uninstalled.pc.in Fri Jan 08 19:01:39 2010 +0000 +++ b/pidgin/pidgin-uninstalled.pc.in Fri Jan 08 20:11:49 2010 +0000 @@ -6,8 +6,11 @@ datadir=@datadir@ sysconfdir=@sysconfdir@ +abs_srcdir=@abs_srcdir@ +abs_builddir=@abs_builddir@ + Name: Pidgin Description: Pidgin is a GTK2-based instant messenger application. Version: @VERSION@ Requires: gtk+-2.0 purple -Cflags: -I${pcfiledir} +Cflags: -I${abs_srcdir} diff -r 10ec0fbfc790 -r 3a43e48c870e pidgin/plugins/disco/gtkdisco.c --- a/pidgin/plugins/disco/gtkdisco.c Fri Jan 08 19:01:39 2010 +0000 +++ b/pidgin/plugins/disco/gtkdisco.c Fri Jan 08 20:11:49 2010 +0000 @@ -225,8 +225,8 @@ gtk_widget_set_sensitive(dialog->account_widget, FALSE); username = purple_account_get_username(dialog->account); - at = g_utf8_strchr(username, -1, '@'); - slash = g_utf8_strchr(username, -1, '/'); + at = strchr(username, '@'); + slash = strchr(username, '/'); if (at && !slash) { server = g_strdup_printf("%s", at + 1); } else if (at && slash && at + 1 < slash) { diff -r 10ec0fbfc790 -r 3a43e48c870e pidgin/win32/nsis/pidgin-installer.nsi --- a/pidgin/win32/nsis/pidgin-installer.nsi Fri Jan 08 19:01:39 2010 +0000 +++ b/pidgin/win32/nsis/pidgin-installer.nsi Fri Jan 08 20:11:49 2010 +0000 @@ -191,6 +191,7 @@ !insertmacro MUI_LANGUAGE "Hungarian" !insertmacro MUI_LANGUAGE "Dutch" !insertmacro MUI_LANGUAGE "Norwegian" + !insertmacro MUI_LANGUAGE "NorwegianNynorsk" !insertmacro MUI_LANGUAGE "Polish" !insertmacro MUI_LANGUAGE "PortugueseBR" !insertmacro MUI_LANGUAGE "Portuguese" @@ -230,6 +231,7 @@ !insertmacro PIDGIN_MACRO_INCLUDE_LANGFILE "KURDISH" "${PIDGIN_NSIS_INCLUDE_PATH}\translations\kurdish.nsh" !insertmacro PIDGIN_MACRO_INCLUDE_LANGFILE "LITHUANIAN" "${PIDGIN_NSIS_INCLUDE_PATH}\translations\lithuanian.nsh" !insertmacro PIDGIN_MACRO_INCLUDE_LANGFILE "NORWEGIAN" "${PIDGIN_NSIS_INCLUDE_PATH}\translations\norwegian.nsh" + !insertmacro PIDGIN_MACRO_INCLUDE_LANGFILE "NORWEGIANNYNORSK" "${PIDGIN_NSIS_INCLUDE_PATH}\translations\norwegian_nynorsk.nsh" !insertmacro PIDGIN_MACRO_INCLUDE_LANGFILE "POLISH" "${PIDGIN_NSIS_INCLUDE_PATH}\translations\polish.nsh" !insertmacro PIDGIN_MACRO_INCLUDE_LANGFILE "PORTUGUESE" "${PIDGIN_NSIS_INCLUDE_PATH}\translations\portuguese.nsh" !insertmacro PIDGIN_MACRO_INCLUDE_LANGFILE "PORTUGUESEBR" "${PIDGIN_NSIS_INCLUDE_PATH}\translations\portuguese-br.nsh" diff -r 10ec0fbfc790 -r 3a43e48c870e pidgin/win32/nsis/translations/norwegian_nynorsk.nsh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/win32/nsis/translations/norwegian_nynorsk.nsh Fri Jan 08 20:11:49 2010 +0000 @@ -0,0 +1,80 @@ +;; +;; norwegian_nynorsk.nsh +;; +;; Norwegian nynorsk language strings for the Windows Pidgin NSIS installer. +;; Windows Code page: 1252 +;; +;; Version 3 + +; Startup Checks +!define INSTALLER_IS_RUNNING "Installasjonsprogrammet kjrer allereie." +!define PIDGIN_IS_RUNNING "Pidgin kjrer no. Lukk programmet og prv igjen." +!define GTK_INSTALLER_NEEDED "GTK+-kjremiljet manglar eller treng bli oppdatert.$\rInstaller v${GTK_MIN_VERSION} eller nyare av GTK+-kjremiljet" + +; License Page +!define PIDGIN_LICENSE_BUTTON "Neste >" +!define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) blir utgjeve med ein GNU General Public License (GPL). Lisensen er berre gjeven her for opplysningsforml. $_CLICK" + +; Components Page +!define PIDGIN_SECTION_TITLE "Pidgin lynmeldingsklient (pkravd)" +!define GTK_SECTION_TITLE "GTK+-kjremilj (pkravd om det ikkje er til stades no)" +!define PIDGIN_SHORTCUTS_SECTION_TITLE "Snarvegar" +!define PIDGIN_DESKTOP_SHORTCUT_SECTION_TITLE "Skrivebordet" +!define PIDGIN_STARTMENU_SHORTCUT_SECTION_TITLE "Startmenyen" +!define PIDGIN_SECTION_DESCRIPTION "Pidgin programfiler og DLL-ar" +!define GTK_SECTION_DESCRIPTION "Ei grafisk brukargrensesnittverktykasse p fleire plattformer som Pidgin nyttar" + +!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION "Snarvegar for starta Pidgin" +!define PIDGIN_DESKTOP_SHORTCUT_DESC "Lag ein snarveg til Pidgin p skrivebordet" +!define PIDGIN_STARTMENU_SHORTCUT_DESC "Lag ein snarveg til Pidgin p startmenyen" + +; GTK+ Directory Page +!define GTK_UPGRADE_PROMPT "Fann ei gammal utgve av GTK+-kjremiljet. Vil du oppdatera ho?$\rMerk: $(^Name) vil kanskje ikkje fungera om du ikkje oppdaterer." +!define GTK_WINDOWS_INCOMPATIBLE "Windows 95/98/Me er ikkje kompatibelt med GTK+ 2.8.0 eller nyare. GTK+ ${GTK_INSTALL_VERSION} kjem ikkje til bli installert.$\rInstallasjonen vil bli abvroten om ikkje GTK+ ${GTK_MIN_VERSION} eller nyare allereie er installert." + +; Installer Finish Page +!define PIDGIN_FINISH_VISIT_WEB_SITE "Besk Pidgin si nettside" + +; Pidgin Section Prompts and Texts +!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Klarte ikkje avinstallera Pidgin-utgva som er i bruk. Den nye utgva kjem til bli installert utan ta vekk den gjeldande." + +; GTK+ Section Prompts +!define GTK_INSTALL_ERROR "Klarte ikkje installera GTK+-kjremiljet." +!define GTK_BAD_INSTALL_PATH "Klarer ikkje laga eller f tilgang til bana du skreiv." + +; URL Handler section +!define URI_HANDLERS_SECTION_TITLE "URI-referanse" + +; Uninstall Section Prompts +!define un.PIDGIN_UNINSTALL_ERROR_1 "Avinstallasjonsprogrammet fann ikkje registerpostar for Pidgin.$\rTruleg har ein annan brukar installert denne applikasjonen." +!define un.PIDGIN_UNINSTALL_ERROR_2 "Du har ikkje lyve til kunna avinstallera denne applikasjonen." + +; Spellcheck Section Prompts +!define PIDGIN_SPELLCHECK_SECTION_TITLE "Stavekontrollhjelp" +!define PIDGIN_SPELLCHECK_ERROR "Klarte ikkje installera stavekontrollen" +!define PIDGIN_SPELLCHECK_DICT_ERROR "Klarte ikkje installera stavekontrollordlista" +!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION "Stavekontrollhjelp (treng internettsamband for installera)." +!define ASPELL_INSTALL_FAILED "Installasjonen feila" +!define PIDGIN_SPELLCHECK_BRETON "Bretonsk" +!define PIDGIN_SPELLCHECK_CATALAN "Katalansk" +!define PIDGIN_SPELLCHECK_CZECH "Tsjekkisk" +!define PIDGIN_SPELLCHECK_WELSH "Walisisk" +!define PIDGIN_SPELLCHECK_DANISH "Dansk" +!define PIDGIN_SPELLCHECK_GERMAN "Tysk" +!define PIDGIN_SPELLCHECK_GREEK "Gresk" +!define PIDGIN_SPELLCHECK_ENGLISH "Engelsk" +!define PIDGIN_SPELLCHECK_ESPERANTO "Esperanto" +!define PIDGIN_SPELLCHECK_SPANISH "Spansk" +!define PIDGIN_SPELLCHECK_FAROESE "Frysk" +!define PIDGIN_SPELLCHECK_FRENCH "Fransk" +!define PIDGIN_SPELLCHECK_ITALIAN "Italiensk" +!define PIDGIN_SPELLCHECK_DUTCH "Nederlandsk" +!define PIDGIN_SPELLCHECK_NORWEGIAN "Norsk" +!define PIDGIN_SPELLCHECK_POLISH "Polsk" +!define PIDGIN_SPELLCHECK_PORTUGUESE "Portugisisk" +!define PIDGIN_SPELLCHECK_ROMANIAN "Rumensk" +!define PIDGIN_SPELLCHECK_RUSSIAN "Russisk" +!define PIDGIN_SPELLCHECK_SLOVAK "Slovakisk" +!define PIDGIN_SPELLCHECK_SWEDISH "Svensk" +!define PIDGIN_SPELLCHECK_UKRAINIAN "Ukrainsk" + diff -r 10ec0fbfc790 -r 3a43e48c870e po/ChangeLog --- a/po/ChangeLog Fri Jan 08 19:01:39 2010 +0000 +++ b/po/ChangeLog Fri Jan 08 20:11:49 2010 +0000 @@ -1,5 +1,9 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.6.6 + * Norwegian Nynorsk win32 translation added (Yngve Spjeld Landro) + * Russian translation updated (Антон Самохвалов) + version 2.6.5 * No changes diff -r 10ec0fbfc790 -r 3a43e48c870e po/POTFILES.in --- a/po/POTFILES.in Fri Jan 08 19:01:39 2010 +0000 +++ b/po/POTFILES.in Fri Jan 08 20:11:49 2010 +0000 @@ -88,6 +88,10 @@ libpurple/protocols/irc/parse.c libpurple/protocols/jabber/adhoccommands.c libpurple/protocols/jabber/auth.c +libpurple/protocols/jabber/auth_cyrus.c +libpurple/protocols/jabber/auth_digest_md5.c +libpurple/protocols/jabber/auth_plain.c +libpurple/protocols/jabber/auth_scram.c libpurple/protocols/jabber/bosh.c libpurple/protocols/jabber/buddy.c libpurple/protocols/jabber/chat.c diff -r 10ec0fbfc790 -r 3a43e48c870e po/de.po --- a/po/de.po Fri Jan 08 19:01:39 2010 +0000 +++ b/po/de.po Fri Jan 08 20:11:49 2010 +0000 @@ -2,7 +2,7 @@ # Pidgin German translation # Copyright (C) 2001, Daniel Seifert # Copyright (C) 2002, Karsten Weiss -# Copyright (C) 2002-2009, Björn Voigt , +# Copyright (C) 2002-2010, Björn Voigt , # Jochen Kemnade # # This file is distributed under the same license as the Pidgin package. @@ -11,8 +11,8 @@ msgstr "" "Project-Id-Version: de\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-11-26 22:45+0100\n" -"PO-Revision-Date: 2009-11-26 22:44+0100\n" +"POT-Creation-Date: 2010-01-08 16:32+0100\n" +"PO-Revision-Date: 2010-01-08 16:30+0100\n" "Last-Translator: Björn Voigt \n" "Language-Team: Deutsch \n" "MIME-Version: 1.0\n" @@ -1564,6 +1564,7 @@ msgid "Online" msgstr "Online" +#. primative, no, id, name msgid "Offline" msgstr "Offline" @@ -1959,6 +1960,9 @@ msgstr "" "%s ist keine reguläre Datei. Pidgin wird die Datei nicht überschreiben.\n" +msgid "File is not readable." +msgstr "Datei ist nicht lesbar." + #, c-format msgid "%s wants to send you %s (%s)" msgstr "%s möchte Ihnen %s (%s) senden" @@ -2516,7 +2520,7 @@ msgstr "Wenn Benutzer so viele Minuten nicht gesprochen hat" msgid "Apply hiding rules to buddies" -msgstr "Regeln zum Verstecken auf Buddies anwenden" +msgstr "Regeln zum Verstecken auf Buddys anwenden" #. *< type #. *< ui_requirement @@ -3106,7 +3110,7 @@ msgstr "Passwort (nochmal)" msgid "Enter captcha text" -msgstr "Captcha-Text eigeben" +msgstr "Captcha-Text eingeben" msgid "Captcha" msgstr "Captcha" @@ -3187,10 +3191,12 @@ msgid "Add to chat..." msgstr "Zum Chat hinzufügen..." +#. 0 #. Global msgid "Available" msgstr "Verfügbar" +#. 1 #. get_yahoo_status_from_purple_status() returns YAHOO_STATUS_CUSTOM for #. * the generic away state (YAHOO_STATUS_TYPE_AWAY) with no message #. Away stuff @@ -3824,6 +3830,13 @@ "Der Server erfordert eine Klartext-Authentifizierung über einen " "unverschlüsselten Kanal" +#. This should never happen! +msgid "Invalid response from server" +msgstr "Ungültige Serverantwort" + +msgid "Server does not use any supported authentication method" +msgstr "Der Server benutzt keine der unterstützten Authentifizierungsmethoden" + #, c-format msgid "" "%s requires plaintext authentication over an unencrypted connection. Allow " @@ -3836,15 +3849,6 @@ msgid "Plaintext Authentication" msgstr "Klartext-Authentifizierung" -msgid "SASL authentication failed" -msgstr "SASL-Authentifizierung fehlgeschlagen" - -msgid "Invalid response from server" -msgstr "Ungültige Serverantwort" - -msgid "Server does not use any supported authentication method" -msgstr "Der Server benutzt keine der unterstützten Authentifizierungsmethoden" - msgid "You require encryption, but it is not available on this server." msgstr "" "Sie fordern Verschlüsselung, aber diese ist auf dem Server nicht verfügbar." @@ -3852,10 +3856,30 @@ msgid "Invalid challenge from server" msgstr "Ungültige Challenge vom Server" +msgid "Server thinks authentication is complete, but client does not" +msgstr "" +"Der Server ist der Meinung, dass die Authentifizierung vollständig ist, der " +"Client aber nicht" + +msgid "SASL authentication failed" +msgstr "SASL-Authentifizierung fehlgeschlagen" + #, c-format msgid "SASL error: %s" msgstr "SASL-Fehler: %s" +msgid "Unable to canonicalize username" +msgstr "Benutzername konnte nicht in Normalform gebracht werden" + +msgid "Unable to canonicalize password" +msgstr "Passwort konnte nicht in Normalform gebracht werden" + +msgid "Malicious challenge from server" +msgstr "Bösartige Challenge vom Server" + +msgid "Unexpected response from server" +msgstr "Unerwartete Antwort vom Server" + msgid "The BOSH connection manager terminated your session." msgstr "Der BOSH-Verbindungsmanager hat Ihre Sitzung beendet." @@ -3956,13 +3980,16 @@ msgid "Resource" msgstr "Ressource" +msgid "Uptime" +msgstr "Betriebszeit" + +msgid "Logged Off" +msgstr "Abgemeldet" + #, c-format msgid "%s ago" msgstr "vor %s" -msgid "Logged Off" -msgstr "Abgemeldet" - msgid "Middle Name" msgstr "Zweiter Name" @@ -4011,9 +4038,6 @@ msgid "Log Out" msgstr "Abmelden" -#. primative, no, id, name -#. 0 -#. 1 #. 2 msgid "Chatty" msgstr "Gesprächig" @@ -4021,6 +4045,7 @@ msgid "Extended Away" msgstr "Abwesend (erweitert)" +#. 3 msgid "Do Not Disturb" msgstr "Nicht stören" @@ -4157,13 +4182,6 @@ msgid "Ping timed out" msgstr "Ping-Zeitüberschreitung" -msgid "" -"Unable to find alternative XMPP connection methods after failing to connect " -"directly." -msgstr "" -"Nach dem Fehlschlagen einer direkten XMPP-Verbindung konnen keine " -"alternativen Verbindungsmethoden gefunden werden." - msgid "Invalid XMPP ID" msgstr "Ungültige XMPP-ID" @@ -4459,7 +4477,7 @@ msgstr "Ungültige ID" msgid "Invalid Namespace" -msgstr "Ungültiger Namenraum" +msgstr "Ungültiger Namensraum" msgid "Invalid XML" msgstr "Ungültiges XML" @@ -5139,6 +5157,10 @@ msgid "Your new MSN friendly name is too long." msgstr "Ihr neuer MSN-Benutzername zu lang." +#, c-format +msgid "Set friendly name for %s." +msgstr "Setze Spitznamen für %s." + msgid "Set your friendly name." msgstr "Setze Ihren Spitznamen." @@ -5875,7 +5897,7 @@ msgstr "Hier können Sie Ihr MXit-Profil aktualisieren" msgid "View Splash" -msgstr "Startbildschirn anschauen" +msgstr "Startbildschirm anschauen" msgid "There is no splash-screen currently available" msgstr "Es gibt gerade keinen Startbildschirm" @@ -6315,7 +6337,7 @@ msgstr "Bildschirmauflösung (dpi)" msgid "Base font size (points)" -msgstr "Basis-Schriftgrüße (Punkt)" +msgstr "Basis-Schriftgröße (Punkt)" msgid "User" msgstr "Benutzer" @@ -6888,7 +6910,7 @@ msgstr "Dienst nicht definiert" msgid "Obsolete SNAC" -msgstr "Obsoleteter SNAC" +msgstr "Obsoleter SNAC" msgid "Not supported by host" msgstr "Nicht unterstützt vom Host" @@ -7993,7 +8015,7 @@ #, c-format msgid "%u needs authorization" -msgstr "%u benötigt Authorisierung" +msgstr "%u benötigt Autorisierung" msgid "Add buddy authorize" msgstr "Buddy-Autorisierung hinzufügen" @@ -8678,8 +8700,8 @@ "The identifier '%s' may possibly refer to any of the following users. Please " "select the correct user from the list below to add them to your buddy list." msgstr "" -"Der Bezeichner '%s' kann sich vielleicht zu einem der folgenden Benutzer " -"beziehen. Bitte wählen Sie den korrekten Benutzer vor der untenstehenden " +"Der Bezeichner '%s' kann sich vielleicht auf einen der folgenden Benutzer " +"beziehen. Bitte wählen Sie den korrekten Benutzer aus der untenstehenden " "Liste, um ihn zu Ihrer Buddy-Liste hinzuzufügen." msgid "Select User" @@ -8923,7 +8945,7 @@ #, c-format msgid "The %s buddy is not trusted" -msgstr "Dem Buddy %s wird nicht (kryptografisch) vertraut" +msgstr "Dem Buddy %s wird nicht (kryptographisch) vertraut" msgid "" "You cannot receive buddy notifications until you import his/her public key. " @@ -9902,7 +9924,7 @@ #, c-format msgid "Failure: Remote does not support proposed cipher" msgstr "" -"Fehler: Entferntes Programm unterstützt die vorgeschlagene Cipher nicht" +"Fehler: Entferntes Programm unterstützt die vorgeschlagene Chiffre nicht" #, c-format msgid "Failure: Remote does not support proposed PKCS" @@ -12074,9 +12096,6 @@ msgid "Lao" msgstr "Laotisch" -msgid "Lithuanian" -msgstr "Litauisch" - msgid "Macedonian" msgstr "Makedonisch" @@ -12179,6 +12198,9 @@ msgid "Amharic" msgstr "Amharisch" +msgid "Lithuanian" +msgstr "Litauisch" + #, c-format msgid "About %s" msgstr "Über %s" @@ -12228,7 +12250,12 @@ "a>)
Wir können nicht bei Problemen mit Drittanbieter-Protokollen oder " "Plugins helfen!
Die Hauptsprache dieser Liste ist Englisch. Sie " "können gern in einer anderen Sprache schreiben, aber die Antworten könnten " -"weniger hilfreich sein.

" +"weniger hilfreich sein.
Deutschsprachige Benutzer können auch das Portal " +"Pidgin-IM.de nutzen. Dort finden " +"Sie aktuelle Informationen zu Pidgin, können mit anderen Benutzern im Forum diskutieren und Hilfe zu " +"Problemen finden. Beachten Sie, dass dieses Portal unabhängig vom " +"offiziellen Pidgin-Projekt ist.

" #, c-format msgid "" @@ -14326,17 +14353,17 @@ "haben." msgid "Markerline" -msgstr "Markierunglinie" +msgstr "Markierungslinie" msgid "Draw a line to indicate new messages in a conversation." msgstr "" "Eine Linie zeichnen um neue Nachrichten in einer Unterhaltung anzuzeigen." msgid "Jump to markerline" -msgstr "Springen zur Markierunglinie" +msgstr "Springen zur Markierungslinie" msgid "Draw Markerline in " -msgstr "Zeichne eine Markierunglinie in " +msgstr "Zeichne eine Markierungslinie in " msgid "_IM windows" msgstr "_IM-Fenster" @@ -14816,7 +14843,7 @@ msgstr "Zeigt die Buddy-Liste als tickerähnliche Laufschrift." msgid "Display Timestamps Every" -msgstr "Zeige Zeitstemple alle" +msgstr "Zeige Zeitstempel alle" #. *< type #. *< ui_requirement diff -r 10ec0fbfc790 -r 3a43e48c870e po/ru.po --- a/po/ru.po Fri Jan 08 19:01:39 2010 +0000 +++ b/po/ru.po Fri Jan 08 20:11:49 2010 +0000 @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: ru\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-11-29 20:30-0500\n" +"POT-Creation-Date: 2009-12-05 19:57-0800\n" "PO-Revision-Date: 2008-05-14 15:00+0400\n" "Last-Translator: Антон Самохвалов \n" "Language-Team: \n" @@ -1540,7 +1540,7 @@ msgstr "TinyURL для вышеуказанного: %s" msgid "Please wait while TinyURL fetches a shorter URL ..." -msgstr "" +msgstr "Пожалуйста, подождите пока TinyURL получает короткий адрес ..." msgid "Only create TinyURL for URLs of this length or greater" msgstr "Создайте TinyURL для адресов только с такой длиной или больше" @@ -3830,6 +3830,13 @@ msgstr "" "Сервер требует аутентификацию простым текстом через нешифрованный поток" +#. This should never happen! +msgid "Invalid response from server" +msgstr "Неверный отклик от сервера" + +msgid "Server does not use any supported authentication method" +msgstr "Сервер не использует ни одного поддерживаемого метода аутентификации" + #, c-format msgid "" "%s requires plaintext authentication over an unencrypted connection. Allow " @@ -3841,25 +3848,38 @@ msgid "Plaintext Authentication" msgstr "Аутентификация простым текстом" -msgid "SASL authentication failed" -msgstr "Аутентификация SASL провалилась" - -msgid "Invalid response from server" -msgstr "Неверный отклик от сервера" - -msgid "Server does not use any supported authentication method" -msgstr "Сервер не использует ни одного поддерживаемого метода аутентификации" - msgid "You require encryption, but it is not available on this server." msgstr "Вы запросили шифрование, но оно недоступно на сервере." msgid "Invalid challenge from server" msgstr "Неверный запрос с сервера" +msgid "Server thinks authentication is complete, but client does not" +msgstr "" + +msgid "SASL authentication failed" +msgstr "Аутентификация SASL провалилась" + #, c-format msgid "SASL error: %s" msgstr "Ошибка SASL: %s" +#, fuzzy +msgid "Unable to canonicalize username" +msgstr "Не удаётся настроить" + +#, fuzzy +msgid "Unable to canonicalize password" +msgstr "Не удалось открыть прослушиваемый порт." + +#, fuzzy +msgid "Malicious challenge from server" +msgstr "Неверный запрос с сервера" + +#, fuzzy +msgid "Unexpected response from server" +msgstr "Получен непредвиденный HTTP-отклик от сервера." + msgid "The BOSH connection manager terminated your session." msgstr "Управление соединениями BOSH прервало ваш сеанс." @@ -3959,13 +3979,21 @@ msgid "Resource" msgstr "Ресурс" +#, fuzzy +msgid "Uptime" +msgstr "Обновить" + +#, c-format +msgid "%s" +msgstr "" + +msgid "Logged Off" +msgstr "Вышел из сети" + #, c-format msgid "%s ago" msgstr "%s назад" -msgid "Logged Off" -msgstr "Вышел из сети" - msgid "Middle Name" msgstr "Отчество" @@ -5818,45 +5846,37 @@ msgstr "Ваше текущее настроение" #. add all moods to list -#, fuzzy msgid "New Mood" -msgstr "Настроение пользователя" - -#, fuzzy +msgstr "Новое настроение" + msgid "Change your Mood" -msgstr "Изменить пароль" - -#, fuzzy +msgstr "Изменить ваше настроение" + msgid "How do you feel right now?" -msgstr "Прямо сейчас меня здесь нет" - -#, fuzzy +msgstr "Как вы сейчас?" + msgid "The PIN you entered is invalid." -msgstr "Введённый ключ SecurID неверный." - -#, fuzzy +msgstr "Введённый PIN-код неправильный." + msgid "The PIN you entered has an invalid length [4-10]." -msgstr "Введённый ключ SecurID неверный." +msgstr "Введённый PIN-код неправильной длины [4-10]." msgid "The PIN is invalid. It should only consist of digits [0-9]." -msgstr "" - -#, fuzzy +msgstr "PIN-код неправильный. Он должен содержать только цифры [0-9]." + msgid "The two PINs you entered do not match." -msgstr "Новые пароли не совпадают." - -#, fuzzy +msgstr "Два введённых PIN-кода не совпадают." + msgid "The name you entered is invalid." -msgstr "Введённый ключ SecurID неверный." +msgstr "Введённое имя неправильное." msgid "" "The birthday you entered is invalid. The correct format is: 'YYYY-MM-DD'." -msgstr "" +msgstr "Введённая вами дата рождения неправильная. Формат такой: 'ГГГГ-ММ-ДД'." #. show error to user -#, fuzzy msgid "Profile Update Error" -msgstr "Ошибка записи" +msgstr "Ошибка Обновления Профиля" #. no profile information yet, so we cannot update #. (reference: "libpurple/request.h") @@ -5865,89 +5885,80 @@ msgid "Your profile information is not yet retrieved. Please try again later." msgstr "" +"Информация о вашем профиле ещё не получена. Пожалуйста, попробуйте позже." #. pin -#, fuzzy msgid "PIN" -msgstr "UIN" +msgstr "PIN" msgid "Verify PIN" -msgstr "" +msgstr "Проверьте PIN" #. display name -#, fuzzy msgid "Display Name" -msgstr "Фамилия" +msgstr "Показать имя" #. hidden msgid "Hide my number" -msgstr "" +msgstr "Скрыть мой номер" #. mobile number -#, fuzzy msgid "Mobile Number" -msgstr "Номер мобильного телефона" - -#, fuzzy +msgstr "Номер мобильного" + msgid "Update your Profile" -msgstr "Профиль пользователя" +msgstr "Обновить ваш профиль" msgid "Here you can update your MXit profile" -msgstr "" +msgstr "Здесь вы можете обновить ваш MXit-профиль" msgid "View Splash" -msgstr "" +msgstr "Показать заставку" msgid "There is no splash-screen currently available" -msgstr "" - -#, fuzzy +msgstr "Сейчас нет доступных экранов-заставок" + msgid "About" -msgstr "Обо мне" +msgstr "Про" #. display / change mood -#, fuzzy msgid "Change Mood..." -msgstr "Изменить пароль..." +msgstr "Изменить настроение..." #. display / change profile -#, fuzzy msgid "Change Profile..." -msgstr "Изменить пароль..." +msgstr "Изменить профиль..." #. display splash-screen -#, fuzzy msgid "View Splash..." -msgstr "Просмотреть журнал..." +msgstr "Показать заставку..." #. display plugin version -#, fuzzy msgid "About..." -msgstr "Обо мне" +msgstr "О модуле..." #. the file is too big -#, fuzzy msgid "The file you are trying to send is too large!" -msgstr "Сообщение слишком велико." +msgstr "Файл, которые вы пытаетесь отправить слишком велик!" msgid "" "Unable to connect to the MXit HTTP server. Please check your server settings." msgstr "" - -#, fuzzy +"Не удаётся соединиться с сервером HTTP MXit. Пожалуйста, проверьте ваши " +"настройки сервера." + msgid "Logging In..." -msgstr "Входит в сеть" +msgstr "Входит в сеть..." #, fuzzy msgid "" "Unable to connect to the MXit server. Please check your server settings." msgstr "" -"Не удаётся соединиться с сервером. Введите адрес сервера, с которым вы " -"хотите соединиться." - -#, fuzzy +"Не удаётся соединиться с сервером MXit. Пожалуйста, проверьте ваши настройки " +"сервера." + msgid "Connecting..." -msgstr "Соединение" +msgstr "Соединение..." #, fuzzy msgid "The nick name you entered is invalid." @@ -5959,142 +5970,126 @@ #. mxit login name msgid "MXit Login Name" -msgstr "" +msgstr "Имя входа MXit" #. nick name -#, fuzzy msgid "Nick Name" msgstr "Псевдоним" #. show the form to the user to complete #, fuzzy msgid "Register New MXit Account" -msgstr "Зарегистрировать новую учётную запись XMPP" - -#, fuzzy +msgstr "Зарегистрировать новую учётную запись MXit" + msgid "Please fill in the following fields:" -msgstr "Заполните следующие поля" +msgstr "Пожалуйста, заполните следующие поля:" #. no reply from the WAP site msgid "Error contacting the MXit WAP site. Please try again later." -msgstr "" +msgstr "Ошибка связи с сайтом MXit WAP. Пожалуйста, попробуйте позже." #. wapserver error #. server could not find the user msgid "" "MXit is currently unable to process the request. Please try again later." -msgstr "" +msgstr "MXit не может сейчас обработать апрос. Пожалуйста, попробуйте позже." msgid "Wrong security code entered. Please try again later." -msgstr "" +msgstr "Введён ошибочный код безопасности. Пожалуйста, попробуйте позже." msgid "Your session has expired. Please try again later." -msgstr "" +msgstr "Ваш сеанс истёк. Пожалуйста, попробуйте позже." msgid "Invalid country selected. Please try again." -msgstr "" +msgstr "Выбрана неправильная страна. Пожалуйста, попробуйте ещё раз." msgid "Username is not registered. Please register first." msgstr "" +"Имя пользователя не зарегистрировано. Пожалуйста, сначала зарегистрируйтесь." msgid "Username is already registered. Please choose another username." msgstr "" - -#, fuzzy +"Имя пользователя уже зарегистрировано. Пожалуйста, выберите другое имя." + msgid "Internal error. Please try again later." -msgstr "Сервер недоступен; повторите попытку позже" +msgstr "Внутренняя ошибка. Пожалуйста, попробуйте позже." msgid "You did not enter the security code" -msgstr "" - -#, fuzzy +msgstr "Вы не ввели код безопасности" + msgid "Security Code" -msgstr "Безопасность включена" +msgstr "Код безопасности" #. ask for input -#, fuzzy msgid "Enter Security Code" -msgstr "Введите код" - -#, fuzzy +msgstr "Введите код безопасности" + msgid "Your Country" -msgstr "Страна" - -#, fuzzy +msgstr "Ваша страна" + msgid "Your Language" -msgstr "Предпочитаемый язык" +msgstr "Ваш язык" #. display the form to the user and wait for his/her input -#, fuzzy msgid "MXit Authorization" -msgstr "Требовать авторизацию" +msgstr "Авторизация MXit" msgid "MXit account validation" -msgstr "" - -#, fuzzy +msgstr "Подтверждение учётной записи MXit" + msgid "Retrieving User Information..." -msgstr "Информация о сервере" +msgstr "Получение информации о пользователе..." msgid "Loading menu..." -msgstr "" - -#, fuzzy +msgstr "Загрузка меню..." + msgid "Status Message" -msgstr "Отправленные сообщения" - -#, fuzzy +msgstr "Сообщения состояния" + msgid "Hidden Number" -msgstr "Отчество" - -#, fuzzy +msgstr "Скрытый номер" + msgid "Your Mobile Number..." -msgstr "Ввести номер мобильного телефона..." +msgstr "Номер мобильного телефона..." #. Configuration options #. WAP server (reference: "libpurple/accountopt.h") -#, fuzzy msgid "WAP Server" -msgstr "Сервер" - -#, fuzzy +msgstr "WAP-сервер" + msgid "Connect via HTTP" -msgstr "Соединение по TCP" +msgstr "Соединение через HTTP" msgid "Enable splash-screen popup" -msgstr "" +msgstr "Включить контекстное меню экрана-заставки" #. we must have lost the connection, so terminate it so that we can reconnect msgid "We have lost the connection to MXit. Please reconnect." -msgstr "" +msgstr "Мы потеряли соединение с MXit. Пожалуйста, пересоединитесь." #. packet could not be queued for transmission -#, fuzzy msgid "Message Send Error" -msgstr "Ошибка сообщения XMPP" - -#, fuzzy +msgstr "Ошибка отправки сообщения" + msgid "Unable to process your request at this time" -msgstr "Не удаётся соединиться с сервером." +msgstr "Не удаётся сейчас обработать ваш запрос" msgid "Timeout while waiting for a response from the MXit server." -msgstr "" - -#, fuzzy +msgstr "Истекло время во время ожидания ответа от сервера MXit." + msgid "Successfully Logged In..." -msgstr "Успешно подсоединён Qun" +msgstr "Успешно вошёл в сеть..." #, fuzzy, c-format msgid "" "%s sent you an encrypted message, but it is not supported on this client." msgstr "%s послал вам приглашение к веб-камере, которое ещё не поддерживается." -#, fuzzy msgid "Message Error" -msgstr "Ошибка сообщения XMPP" +msgstr "Сообщение об ошибке" msgid "Cannot perform redirect using the specified protocol" -msgstr "" +msgstr "Не могу осуществить перенаправление используя указанный протокол" #, fuzzy msgid "An internal MXit server error occurred." @@ -6158,53 +6153,52 @@ #. bad packet msgid "Invalid packet received from MXit." -msgstr "" +msgstr "Получен неправильный пакет от MXit." #. connection error msgid "A connection error occurred to MXit. (read stage 0x01)" -msgstr "" +msgstr "Возникла ошибка соединения с MXit. (этап чтения 0x01)" #. connection closed msgid "A connection error occurred to MXit. (read stage 0x02)" -msgstr "" +msgstr "Возникла ошибка соединения с MXit. (этап чтения 0x02)" msgid "A connection error occurred to MXit. (read stage 0x03)" -msgstr "" +msgstr "Возникла ошибка соединения с MXit. (этап чтения 0x03)" #. malformed packet length record (too long) msgid "A connection error occurred to MXit. (read stage 0x04)" -msgstr "" +msgstr "Возникла ошибка соединения с MXit. (этап чтения 0x04)" #. connection error msgid "A connection error occurred to MXit. (read stage 0x05)" -msgstr "" +msgstr "Возникла ошибка соединения с MXit. (этап чтения 0x05)" #. connection closed msgid "A connection error occurred to MXit. (read stage 0x06)" -msgstr "" +msgstr "Возникла ошибка соединения с MXit. (этап чтения 0x06)" #, fuzzy msgid "Pending" -msgstr "Отправка" +msgstr "Рассмотрение" #, fuzzy msgid "Invited" -msgstr "Пригласить" +msgstr "Приглашено" #, fuzzy msgid "Rejected" -msgstr "Отвергнуть" +msgstr "Отклонено" #, fuzzy msgid "Deleted" -msgstr "Удалить" +msgstr "Удалено" msgid "MXit Advertising" -msgstr "" - -#, fuzzy +msgstr "Реклама MXit" + msgid "More Information" -msgstr "Информация о работе" +msgstr "Больше информации" #, c-format msgid "No such user: %s" @@ -13354,20 +13348,17 @@ "Выберите тему смайликов, которую вы хотели бы использовать. Новые темы могут " "быть установлены перетаскиванием их в список тем." -#, fuzzy msgid "Buddy List Theme:" -msgstr "Тема Списка собеседников" - -#, fuzzy +msgstr "Тема Списка собеседников:" + msgid "Status Icon Theme:" -msgstr "Статус для %s" +msgstr "Тема значков состояния:" msgid "Sound Theme:" -msgstr "" - -#, fuzzy +msgstr "Звуковая тема:" + msgid "Smiley Theme:" -msgstr "Темы смайликов" +msgstr "Тема смайликов:" msgid "Keyboard Shortcuts" msgstr "Горячие клавиши" @@ -13385,9 +13376,8 @@ msgid "On unread messages" msgstr "При наличии непрочитанных сообщений" -#, fuzzy msgid "Conversation Window" -msgstr "Окна бесед" +msgstr "Окно беседы" msgid "_Hide new IM conversations:" msgstr "_Скрывать новые беседы:" @@ -13481,21 +13471,18 @@ msgid "Cannot start proxy configuration program." msgstr "Не могу запустить программу настройки прокси." -#, fuzzy msgid "Cannot start browser configuration program." msgstr "Не могу запустить программу настройки веб-проводника." -#, fuzzy msgid "Disabled" msgstr "Отключено" -#, fuzzy, c-format +#, c-format msgid "Use _automatically detected IP address: %s" -msgstr "Использовать _автоматически обнаруженный IP-адрес: %s" - -#, fuzzy +msgstr "И_спользовать автоматически определённый IP-адрес: %s" + msgid "ST_UN server:" -msgstr "ST_UN сервер:" +msgstr "_STUN сервер:" msgid "Example: stunserver.org" msgstr "Пример: stunserver.org" @@ -13519,7 +13506,7 @@ #, fuzzy msgid "_End:" -msgstr "Р_азвернуть" +msgstr "Коне_ц:" #. TURN server msgid "Relay Server (TURN)" @@ -13527,15 +13514,15 @@ #, fuzzy msgid "_TURN server:" -msgstr "ST_UN-сервер:" +msgstr "_TURN-сервер:" #, fuzzy msgid "Use_rname:" -msgstr "Имя пользователя:" +msgstr "Им_я пользователя:" #, fuzzy msgid "Pass_word:" -msgstr "Пароль:" +msgstr "Паро_ль:" msgid "Seamonkey" msgstr "Seamonkey"