changeset 29190:4491a662d527

merged with im.pidgin.pidgin
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Thu, 17 Dec 2009 16:50:12 +0900
parents 5c77b620375c (current diff) afebd4e2fc1a (diff)
children 39bc284215ce
files configure.ac libpurple/certificate.c libpurple/protocols/gg/lib/http.c libpurple/protocols/gg/lib/pubdir50.c libpurple/protocols/jabber/google.c libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/si.c libpurple/protocols/oscar/oscar.c
diffstat 100 files changed, 2839 insertions(+), 1281 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Mon Nov 30 16:07:54 2009 +0900
+++ b/ChangeLog	Thu Dec 17 16:50:12 2009 +0900
@@ -1,7 +1,34 @@
-
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
 version 2.6.5 (??/??/20??):
+	libpurple:
+	* TLS certificates are actually stored to the local cache once again
+	  (accepting a name mismatch on a certificate should now be remembered)
+
+	General:
+	* Build-time fixes for Solaris.  (Paul Townsend)
+
+	AIM and ICQ:
+	* Messages from some mobile clients are no longer displayed as
+	  Chinese characters (broken in 2.6.4)
+
+	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.
+
+	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.
+	* Do not crash when attempting to register for a new account on Windows.
+	* Fix file transfer with clients that do not support Entity Capabilities
+	  (e.g. Spark)
 
 version 2.6.4 (11/29/2009):
 	libpurple:
--- a/ChangeLog.win32	Mon Nov 30 16:07:54 2009 +0900
+++ b/ChangeLog.win32	Thu Dec 17 16:50:12 2009 +0900
@@ -1,4 +1,5 @@
 version 2.6.5 (??/??/20??):
+	* Installer translations for: Norwegian nynorsk
 
 version 2.6.4 (11/29/2009):
 	* Register URL handlers for everything that Windows knows about.  Still
--- a/configure.ac	Mon Nov 30 16:07:54 2009 +0900
+++ b/configure.ac	Thu Dec 17 16:50:12 2009 +0900
@@ -1470,7 +1470,7 @@
 		AC_CHECK_LIB(pthread, pthread_create, )
 		AC_CHECK_LIB(util, openpty, )
 		AC_CHECK_LIB(db, dbopen, )
-		PY_LIBS="-lpython$PY_VERSION -L$PY_EXEC_PREFIX/lib/python$PY_VERSION/config"
+		PY_LIBS="-L$PY_EXEC_PREFIX/lib/python$PY_VERSION/config -lpython$PY_VERSION"
 		PY_CFLAGS="-I$PY_PREFIX/include/python$PY_VERSION"
 		AC_DEFINE(USE_PYTHON, [1], [Define if python headers are available.])
 		AC_MSG_RESULT(ok)
@@ -2267,11 +2267,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 #######################################################################
--- a/finch/finch.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/finch.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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 <internal.h>
 #include "finch.h"
-#include <internal.h>
 
 #include "account.h"
 #include "conversation.h"
--- a/finch/gntaccount.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/gntaccount.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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 <internal.h>
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -36,7 +38,6 @@
 #include <gntwindow.h>
 
 #include "finch.h"
-#include <internal.h>
 
 #include <account.h>
 #include <accountopt.h>
--- a/finch/gntblist.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/gntblist.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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 <internal.h>
 #include "finch.h"
-#include <internal.h>
 
 #include <account.h>
 #include <blist.h>
--- a/finch/gntcertmgr.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/gntcertmgr.c	Thu Dec 17 16:50:12 2009 +0900
@@ -25,8 +25,8 @@
  *
  */
 
+#include <internal.h>
 #include "finch.h"
-#include <internal.h>
 
 #include "certificate.h"
 #include "debug.h"
--- a/finch/gntconn.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/gntconn.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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 <internal.h>
 #include "finch.h"
-#include <internal.h>
 
 #include "account.h"
 #include "core.h"
--- a/finch/gntconv.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/gntconv.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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 <string.h>
 
+#include <internal.h>
 #include "finch.h"
-#include <internal.h>
 
 #include <cmds.h>
 #include <core.h>
--- a/finch/gntdebug.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/gntdebug.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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 <internal.h>
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -35,7 +38,6 @@
 
 #include "gntdebug.h"
 #include "finch.h"
-#include <internal.h>
 #include "notify.h"
 #include "util.h"
 
--- a/finch/gntft.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/gntft.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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 <internal.h>
 #include "finch.h"
-#include <internal.h>
 
 #include <gnt.h>
 #include <gntbox.h>
--- a/finch/gntlog.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/gntlog.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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 <internal.h>
 #include "finch.h"
-#include <internal.h>
 
 #include <gnt.h>
 #include <gntbox.h>
--- a/finch/gntmedia.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/gntmedia.c	Thu Dec 17 16:50:12 2009 +0900
@@ -24,8 +24,8 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
+#include <internal.h>
 #include "finch.h"
-#include <internal.h>
 #include "gntconv.h"
 #include "gntmedia.h"
 
--- a/finch/gntnotify.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/gntnotify.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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 <internal.h>
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -32,7 +34,6 @@
 #include <gntwindow.h>
 
 #include "finch.h"
-#include <internal.h>
 
 #include <util.h>
 
--- a/finch/gntplugin.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/gntplugin.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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 <internal.h>
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -32,7 +34,6 @@
 #include <gntutils.h>
 
 #include "finch.h"
-#include <internal.h>
 
 #include "debug.h"
 #include "notify.h"
--- a/finch/gntpounce.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/gntpounce.c	Thu Dec 17 16:50:12 2009 +0900
@@ -24,6 +24,8 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
+#include <internal.h>
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
@@ -36,7 +38,6 @@
 #include <gntutils.h>
 
 #include "finch.h"
-#include <internal.h>
 
 #include "account.h"
 #include "conversation.h"
--- a/finch/libgnt/gntkeys.h	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/libgnt/gntkeys.h	Thu Dec 17 16:50:12 2009 +0900
@@ -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"
--- a/finch/libgnt/gntutils.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/libgnt/gntutils.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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 <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
@@ -46,8 +46,6 @@
 #include <libxml/tree.h>
 #endif
 
-#include "config.h"
-
 void gnt_util_get_text_bound(const char *text, int *width, int *height)
 {
 	const char *s = text, *last;
--- a/finch/libgnt/wms/s.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/finch/libgnt/wms/s.c	Thu Dec 17 16:50:12 2009 +0900
@@ -1,8 +1,8 @@
+#include "internal.h"
+
 #include <string.h>
 #include <sys/types.h>
 
-#include "internal.h"
-
 #include "gnt.h"
 #include "gntbox.h"
 #include "gntmenu.h"
--- a/libpurple/certificate.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/certificate.c	Thu Dec 17 16:50:12 2009 +0900
@@ -1431,9 +1431,8 @@
 	tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,
 						 "tls_peers");
 	if (tls_peers) {
-		if (!purple_certificate_pool_contains(tls_peers, vrq->subject_name) &&
-		        !purple_certificate_pool_store(tls_peers,vrq->subject_name,
-		                                       peer_crt)) {
+		if (!purple_certificate_pool_store(tls_peers,vrq->subject_name,
+		                                   peer_crt)) {
 			purple_debug_error("certificate/x509/tls_cached",
 			                   "FAILED to cache peer certificate\n");
 		}
--- a/libpurple/conversation.h	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/conversation.h	Thu Dec 17 16:50:12 2009 +0900
@@ -1025,7 +1025,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.
  *
--- a/libpurple/protocols/gg/lib/common.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/gg/lib/common.c	Thu Dec 17 16:50:12 2009 +0900
@@ -19,6 +19,8 @@
  *  USA.
  */
 
+#include "libgadu.h"
+
 #ifndef _WIN32
 #include <sys/types.h>
 #include <sys/ioctl.h>
@@ -41,8 +43,6 @@
 #include <string.h>
 #include <unistd.h>
 
-#include "libgadu.h"
-
 FILE *gg_debug_file = NULL;
 
 #ifndef GG_DEBUG_DISABLE
--- a/libpurple/protocols/gg/lib/dcc.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/gg/lib/dcc.c	Thu Dec 17 16:50:12 2009 +0900
@@ -19,6 +19,8 @@
  *  USA.
  */
 
+#include "libgadu.h"
+
 #include <sys/types.h>
 #include <sys/stat.h>
 #ifndef _WIN32
@@ -41,8 +43,6 @@
 #include <unistd.h>
 
 #include "compat.h"
-#include "libgadu.h"
-
 #ifndef GG_DEBUG_DISABLE
 /*
  * gg_dcc_debug_data() // funkcja wewntrzna
--- a/libpurple/protocols/gg/lib/events.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/gg/lib/events.c	Thu Dec 17 16:50:12 2009 +0900
@@ -20,6 +20,8 @@
  *  USA.
  */
 
+#include "libgadu.h"
+
 #include <sys/types.h>
 #ifndef _WIN32
 #include <sys/wait.h>
@@ -46,7 +48,6 @@
 #endif
 
 #include "compat.h"
-#include "libgadu.h"
 
 /*
  * gg_event_free()
--- a/libpurple/protocols/gg/lib/http.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/gg/lib/http.c	Thu Dec 17 16:50:12 2009 +0900
@@ -18,6 +18,8 @@
  *  USA.
  */
 
+#include "libgadu.h"
+
 #include <sys/types.h>
 #ifndef _WIN32
 #include <sys/wait.h>
@@ -43,8 +45,6 @@
 #include <unistd.h>
 
 #include "compat.h"
-#include "libgadu.h"
-#include <glib.h>
 
 /*
  * gg_http_connect() // funkcja pomocnicza
--- a/libpurple/protocols/gg/lib/libgadu.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/gg/lib/libgadu.c	Thu Dec 17 16:50:12 2009 +0900
@@ -21,6 +21,8 @@
  *  USA.
  */
 
+#include "libgadu.h"
+
 #include <sys/types.h>
 #ifndef _WIN32
 #include <sys/wait.h>
@@ -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;
--- a/libpurple/protocols/gg/lib/pubdir.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/gg/lib/pubdir.c	Thu Dec 17 16:50:12 2009 +0900
@@ -19,6 +19,8 @@
  *  USA.
  */
 
+#include "libgadu.h"
+
 #include <ctype.h>
 #include <errno.h>
 #include <stdarg.h>
@@ -27,8 +29,6 @@
 #include <string.h>
 #include <unistd.h>
 
-#include "libgadu.h"
-
 /*
  * gg_register3()
  *
--- a/libpurple/protocols/gg/lib/pubdir50.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/gg/lib/pubdir50.c	Thu Dec 17 16:50:12 2009 +0900
@@ -18,14 +18,14 @@
  *  USA.
  */
 
+#include "libgadu.h"
+
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
 #include <glib.h>
 
-#include "libgadu.h"
-
 /*
  * gg_pubdir50_new()
  *
--- a/libpurple/protocols/jabber/Makefile.am	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/Makefile.am	Thu Dec 17 16:50:12 2009 +0900
@@ -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
--- a/libpurple/protocols/jabber/Makefile.mingw	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/Makefile.mingw	Thu Dec 17 16:50:12 2009 +0900
@@ -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 \
--- a/libpurple/protocols/jabber/auth.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/auth.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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
 	 * <stream:features/> 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,
-						"<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />",
-						-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("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response>", 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) <challenge/>\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;
+}
--- a/libpurple/protocols/jabber/auth.h	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/auth.h	Thu Dec 17 16:50:12 2009 +0900
@@ -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_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/auth_cyrus.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/auth_digest_md5.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/auth_plain.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/auth_scram.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/auth_scram.h	Thu Dec 17 16:50:12 2009 +0900
@@ -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_ */
--- a/libpurple/protocols/jabber/buddy.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/buddy.c	Thu Dec 17 16:50:12 2009 +0900
@@ -815,20 +815,32 @@
 
 	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 = g_strdup_printf(_("%s"), last);
+			} 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 +1872,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);
--- a/libpurple/protocols/jabber/chat.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/chat.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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;
--- a/libpurple/protocols/jabber/disco.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/disco.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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)) {
--- a/libpurple/protocols/jabber/google.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/google.c	Thu Dec 17 16:50:12 2009 +0900
@@ -31,6 +31,7 @@
 #include "google.h"
 #include "jabber.h"
 #include "presence.h"
+#include "roster.h"
 #include "iq.h"
 #include "chat.h"
 
@@ -950,20 +951,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);
@@ -973,7 +960,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;
@@ -990,7 +977,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");
 
@@ -1033,9 +1020,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;
@@ -1045,14 +1032,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;
 
@@ -1079,7 +1062,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);
@@ -1099,12 +1082,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;
@@ -1113,14 +1095,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;
@@ -1147,7 +1121,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);
--- a/libpurple/protocols/jabber/google.h	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/google.h	Thu Dec 17 16:50:12 2009 +0900
@@ -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);
 
--- a/libpurple/protocols/jabber/jabber.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/jabber.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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);
 	}
 }
 
@@ -639,10 +641,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;
 	}
 
@@ -1118,7 +1121,8 @@
 
 	if(cbdata->js->registration) {
 		username = g_strdup_printf("%s@%s%s%s", cbdata->js->user->node, cbdata->js->user->domain,
-			cbdata->js->user->resource ? "/" : "", cbdata->js->user->resource);
+			cbdata->js->user->resource ? "/" : "",
+			cbdata->js->user->resource ? cbdata->js->user->resource : "");
 		purple_account_set_username(cbdata->js->gc->account, username);
 		g_free(username);
 	}
@@ -1511,6 +1515,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);
@@ -1587,11 +1593,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"),
@@ -1746,13 +1747,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;
 	}
 
@@ -1780,13 +1783,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;
 	}
 
@@ -3521,6 +3526,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,
@@ -3555,6 +3562,7 @@
 {
 	purple_plugin_ipc_unregister_all(plugin);
 
+	jabber_auth_uninit();
 	jabber_features_destroy();
 	jabber_identities_destroy();
 }
--- a/libpurple/protocols/jabber/jabber.h	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/jabber.h	Thu Dec 17 16:50:12 2009 +0900
@@ -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;
 
--- a/libpurple/protocols/jabber/jingle/jingle.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/jingle/jingle.c	Thu Dec 17 16:50:12 2009 +0900
@@ -29,12 +29,13 @@
 #include "content.h"
 #include "debug.h"
 #include "jingle.h"
-#include <string.h>
 #include "session.h"
 #include "iceudp.h"
 #include "rawudp.h"
 #include "rtp.h"
 
+#include <string.h>
+
 GType
 jingle_get_type(const gchar *type)
 {
--- a/libpurple/protocols/jabber/jingle/session.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/jingle/session.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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;
--- a/libpurple/protocols/jabber/jutil.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/jutil.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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)
 {
--- a/libpurple/protocols/jabber/jutil.h	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/jutil.h	Thu Dec 17 16:50:12 2009 +0900
@@ -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);
--- a/libpurple/protocols/jabber/libxmpp.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/libxmpp.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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 =
--- a/libpurple/protocols/jabber/namespaces.h	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/namespaces.h	Thu Dec 17 16:50:12 2009 +0900
@@ -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"
--- a/libpurple/protocols/jabber/parser.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/parser.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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 <stream:stream> 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);
 	}
 }
 
--- a/libpurple/protocols/jabber/presence.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/presence.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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;
--- a/libpurple/protocols/jabber/roster.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/roster.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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,7 @@
                          JabberIqType type, const char *id, xmlnode *query)
 {
 	xmlnode *item, *group;
+	const char *ver;
 
 	if (!jabber_is_own_account(js, from)) {
 		purple_debug_warning("jabber", "Received bogon roster push from %s\n",
@@ -181,18 +220,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 +266,13 @@
 		}
 	}
 
-	js->currently_parsing_roster_push = FALSE;
+	ver = xmlnode_get_attrib(query, "ver");
+	if (ver) {
+		 PurpleAccount *account = purple_connection_get_account(js->gc);
+		 purple_account_set_string(account, "roster_ver", ver);
+	}
 
-	/* 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 +336,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);
--- a/libpurple/protocols/jabber/si.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/si.c	Thu Dec 17 16:50:12 2009 +0900
@@ -1459,7 +1459,7 @@
 	g_free(xfer->who);
 	xfer->who = who;
 
-	if (jbr) {
+	if (jbr && jabber_resource_know_capabilities(jbr)) {
 		char *msg;
 
 		if (jabber_resource_has_capability(jbr, NS_IBB))
--- a/libpurple/protocols/msn/slp.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/msn/slp.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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
@@ -332,9 +370,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;
@@ -357,6 +393,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);
--- a/libpurple/protocols/msn/slp.h	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/msn/slp.h	Thu Dec 17 16:50:12 2009 +0900
@@ -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);
--- a/libpurple/protocols/msn/slpcall.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/msn/slpcall.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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;
 		}
--- a/libpurple/protocols/msn/slpcall.h	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/msn/slpcall.h	Thu Dec 17 16:50:12 2009 +0900
@@ -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);
--- a/libpurple/protocols/msn/slplink.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/msn/slplink.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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);
 	}
 }
@@ -732,7 +749,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,6 +756,8 @@
 	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;
 
--- a/libpurple/protocols/msn/slplink.h	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/msn/slplink.h	Thu Dec 17 16:50:12 2009 +0900
@@ -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,
--- a/libpurple/protocols/msn/slpmsg.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/msn/slpmsg.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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);
--- a/libpurple/protocols/msn/slpmsg.h	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/msn/slpmsg.h	Thu Dec 17 16:50:12 2009 +0900
@@ -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;
--- a/libpurple/protocols/msn/switchboard.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/msn/switchboard.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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;
 
--- a/libpurple/protocols/msn/userlist.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/msn/userlist.c	Thu Dec 17 16:50:12 2009 +0900
@@ -50,8 +50,11 @@
 	{
 		MsnSession *session = pa->gc->proto_data;
 		MsnUserList *userlist = session->userlist;
+		PurpleAccount *account = purple_connection_get_account(pa->gc);
 
 		msn_userlist_add_buddy_to_list(userlist, pa->who, MSN_LIST_AL);
+		purple_privacy_deny_remove(account, pa->who, TRUE);
+		purple_privacy_permit_add(account, pa->who, TRUE);
 
 		msn_del_contact_from_list(session, NULL, pa->who, MSN_LIST_PL);
 	}
--- a/libpurple/protocols/mxit/actions.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/mxit/actions.c	Thu Dec 17 16:50:12 2009 +0900
@@ -23,11 +23,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include	<stdio.h>
-#include	<stdlib.h>
-#include	<string.h>
-#include	<time.h>
-
+#include    "internal.h"
 #include	"purple.h"
 
 #include	"protocol.h"
--- a/libpurple/protocols/mxit/chunk.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/mxit/chunk.c	Thu Dec 17 16:50:12 2009 +0900
@@ -23,10 +23,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include	<stdio.h>
-#include	<unistd.h>
-#include	<string.h>
-
+#include    "internal.h"
 #include	"purple.h"
 #include	"protocol.h"
 #include	"mxit.h"
--- a/libpurple/protocols/mxit/cipher.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/mxit/cipher.c	Thu Dec 17 16:50:12 2009 +0900
@@ -23,10 +23,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include	<stdio.h>
-#include	<unistd.h>
-#include	<string.h>
-
+#include    "internal.h"
 #include	"purple.h"
 
 #include	"mxit.h"
--- a/libpurple/protocols/mxit/filexfer.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/mxit/filexfer.c	Thu Dec 17 16:50:12 2009 +0900
@@ -23,10 +23,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include	<stdio.h>
-#include	<unistd.h>
-#include	<string.h>
-
+#include    "internal.h"
 #include	"purple.h"
 #include	"protocol.h"
 #include	"mxit.h"
--- a/libpurple/protocols/mxit/formcmds.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/mxit/formcmds.c	Thu Dec 17 16:50:12 2009 +0900
@@ -23,8 +23,8 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include <string.h>
-#include <glib.h>
+
+#include "internal.h"
 #include <glib/gprintf.h>
 
 #include "purple.h"
--- a/libpurple/protocols/mxit/http.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/mxit/http.c	Thu Dec 17 16:50:12 2009 +0900
@@ -23,11 +23,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include	<stdio.h>
-#include	<unistd.h>
-#include	<string.h>
-#include	<errno.h>
-
+#include    "internal.h"
 #include	"purple.h"
 
 #include	"mxit.h"
--- a/libpurple/protocols/mxit/login.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/mxit/login.c	Thu Dec 17 16:50:12 2009 +0900
@@ -23,9 +23,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include	<stdio.h>
-#include	<string.h>
-
+#include    "internal.h"
 #include	"purple.h"
 
 #include	"protocol.h"
--- a/libpurple/protocols/mxit/markup.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/mxit/markup.c	Thu Dec 17 16:50:12 2009 +0900
@@ -23,10 +23,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include	<stdio.h>
-#include	<unistd.h>
-#include	<string.h>
-
+#include    "internal.h"
 #include	"purple.h"
 
 #include	"protocol.h"
--- a/libpurple/protocols/mxit/multimx.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/mxit/multimx.c	Thu Dec 17 16:50:12 2009 +0900
@@ -23,9 +23,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include <string.h>
-#include <errno.h>
-
+#include "internal.h"
 #include "purple.h"
 #include "prpl.h"
 
--- a/libpurple/protocols/mxit/mxit.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/mxit/mxit.c	Thu Dec 17 16:50:12 2009 +0900
@@ -23,10 +23,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include	<glib.h>
-#include	<stdio.h>
-#include	<string.h>
-
+#include    "internal.h"
 #include	"purple.h"
 #include	"notify.h"
 #include	"plugin.h"
--- a/libpurple/protocols/mxit/profile.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/mxit/profile.c	Thu Dec 17 16:50:12 2009 +0900
@@ -23,9 +23,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include	<ctype.h>
-#include	<string.h>
-
+#include    "internal.h"
 #include	"purple.h"
 
 #include	"mxit.h"
--- a/libpurple/protocols/mxit/protocol.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/mxit/protocol.c	Thu Dec 17 16:50:12 2009 +0900
@@ -23,11 +23,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include	<stdio.h>
-#include	<unistd.h>
-#include	<string.h>
-#include	<errno.h>
-
+#include    "internal.h"
 #include	"purple.h"
 
 #include	"protocol.h"
--- a/libpurple/protocols/mxit/roster.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/mxit/roster.c	Thu Dec 17 16:50:12 2009 +0900
@@ -23,10 +23,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include	<stdio.h>
-#include	<unistd.h>
-#include	<string.h>
-
+#include    "internal.h"
 #include	"purple.h"
 
 #include	"protocol.h"
--- a/libpurple/protocols/mxit/splashscreen.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/mxit/splashscreen.c	Thu Dec 17 16:50:12 2009 +0900
@@ -23,6 +23,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
+#include "internal.h"
 #include <glib/gstdio.h>
 
 #include "purple.h"
--- a/libpurple/protocols/myspace/user.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/myspace/user.c	Thu Dec 17 16:50:12 2009 +0900
@@ -70,6 +70,8 @@
 	if (!user)
 		return;
 
+	purple_util_fetch_url_cancel(user->url_data);
+
 	g_free(user->client_info);
 	g_free(user->gender);
 	g_free(user->location);
@@ -212,6 +214,8 @@
 	const char *name = purple_buddy_get_name(user->buddy);
 	PurpleAccount *account;
 
+	user->url_data = NULL;
+
 	purple_debug_info("msim_downloaded_buddy_icon",
 			"Downloaded %" G_GSIZE_FORMAT " bytes\n", len);
 
@@ -375,7 +379,7 @@
 
 		/* Only download if URL changed */
 		if (!previous_url || !g_str_equal(previous_url, user->image_url)) {
-			purple_util_fetch_url(user->image_url, TRUE, NULL, TRUE, msim_downloaded_buddy_icon, (gpointer)user);
+			user->url_data = purple_util_fetch_url(user->image_url, TRUE, NULL, TRUE, msim_downloaded_buddy_icon, (gpointer)user);
 		}
 	} else if (g_str_equal(key_str, "LastImageUpdated")) {
 		/* TODO: use somewhere */
--- a/libpurple/protocols/myspace/user.h	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/myspace/user.h	Thu Dec 17 16:50:12 2009 +0900
@@ -40,6 +40,7 @@
 	gchar *image_url;
 	guint last_image_updated;
 	gboolean temporary_user;
+	PurpleUtilFetchUrlData *url_data;
 } MsimUser;
 
 /* Callback function pointer type for when a user's information is received,
--- a/libpurple/protocols/oscar/clientlogin.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/oscar/clientlogin.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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",
--- a/libpurple/protocols/oscar/family_auth.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/oscar/family_auth.c	Thu Dec 17 16:50:12 2009 +0900
@@ -26,12 +26,12 @@
  *
  */
 
+#include "oscar.h"
+
 #include <ctype.h>
 
 #include "cipher.h"
 
-#include "oscar.h"
-
 /* #define USE_XOR_FOR_ICQ */
 
 #ifdef USE_XOR_FOR_ICQ
--- a/libpurple/protocols/oscar/oscar.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/oscar/oscar.c	Thu Dec 17 16:50:12 2009 +0900
@@ -477,10 +477,12 @@
 		/* Should just be "ASCII" */
 		charsetstr1 = "ASCII";
 		charsetstr2 = "UTF-8";
+		charsetstr3 = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING);
 	} else if (charset == AIM_CHARSET_QUIRKUTF8) {
 		/* Mobile AIM client on a Nokia 3100 and an LG VX6000 */
 		charsetstr1 = "UTF-8";  //iChat use 0x000d when it sends UTF-8. --yaz
 		charsetstr2 = "ISO-8859-1";
+		charsetstr3 = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING);
 	} else {
 		/* Unknown, hope for valid UTF-8... */
 		charsetstr1 = "UTF-8";
@@ -3607,9 +3609,9 @@
 
 			purple_debug_misc("oscar",
 					"created room: %s %hu %hu %hu %u %hu %hu %hhu %hu %s %s\n",
-					fqcn, exchange, instance, flags, createtime,
+					fqcn ? fqcn : "(null)", exchange, instance, flags, createtime,
 					maxmsglen, maxoccupancy, createperms, unknown,
-					name, ck);
+					name ? name : "(null)", ck);
 			aim_chat_join(od, exchange, ck, instance);
 			}
 			break;
@@ -4676,7 +4678,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;
--- a/libpurple/protocols/oscar/oscarcommon.h	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/oscar/oscarcommon.h	Thu Dec 17 16:50:12 2009 +0900
@@ -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"
--- a/libpurple/protocols/yahoo/ycht.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/yahoo/ycht.c	Thu Dec 17 16:50:12 2009 +0900
@@ -25,8 +25,6 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include <string.h>
-
 #include "internal.h"
 #include "prpl.h"
 #include "notify.h"
--- a/libpurple/proxy.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/proxy.c	Thu Dec 17 16:50:12 2009 +0900
@@ -1099,6 +1099,36 @@
 			connect_data->host, connect_data->port,
 			connect_data->host, connect_data->port);
 
+	if (purple_proxy_info_get_username(connect_data->gpi) != NULL)
+	{
+		char *t1, *t2, *ntlm_type1;
+		char hostname[256];
+
+		ret = gethostname(hostname, sizeof(hostname));
+		hostname[sizeof(hostname) - 1] = '\0';
+		if (ret < 0 || hostname[0] == '\0') {
+			purple_debug_warning("proxy", "gethostname() failed -- is your hostname set?");
+			strcpy(hostname, "localhost");
+		}
+
+		t1 = g_strdup_printf("%s:%s",
+			purple_proxy_info_get_username(connect_data->gpi),
+			purple_proxy_info_get_password(connect_data->gpi) ?
+				purple_proxy_info_get_password(connect_data->gpi) : "");
+		t2 = purple_base64_encode((const guchar *)t1, strlen(t1));
+		g_free(t1);
+
+		ntlm_type1 = purple_ntlm_gen_type1(hostname, "");
+
+		g_string_append_printf(request,
+			"Proxy-Authorization: Basic %s\r\n"
+			"Proxy-Authorization: NTLM %s\r\n"
+			"Proxy-Connection: Keep-Alive\r\n",
+			t2, ntlm_type1);
+		g_free(ntlm_type1);
+		g_free(t2);
+	}
+
 	g_string_append(request, "\r\n");
 
 	connect_data->write_buf_len = request->len;
--- a/libpurple/tests/Makefile.am	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/tests/Makefile.am	Thu Dec 17 16:50:12 2009 +0900
@@ -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
--- a/libpurple/tests/check_libpurple.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/tests/check_libpurple.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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());
--- a/libpurple/tests/test_cipher.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/tests/test_cipher.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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]; \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/test_jabber_scram.c	Thu Dec 17 16:50:12 2009 +0900
@@ -0,0 +1,117 @@
+#include <string.h>
+
+#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;
+}
--- a/libpurple/tests/tests.h	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/tests/tests.h	Thu Dec 17 16:50:12 2009 +0900
@@ -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);
--- a/libpurple/xmlnode.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/xmlnode.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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);
--- a/pidgin/gtkdialogs.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/pidgin/gtkdialogs.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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},
--- a/pidgin/plugins/disco/gtkdisco.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/pidgin/plugins/disco/gtkdisco.c	Thu Dec 17 16:50:12 2009 +0900
@@ -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) {
--- a/pidgin/win32/nsis/pidgin-installer.nsi	Mon Nov 30 16:07:54 2009 +0900
+++ b/pidgin/win32/nsis/pidgin-installer.nsi	Thu Dec 17 16:50:12 2009 +0900
@@ -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"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/win32/nsis/translations/norwegian_nynorsk.nsh	Thu Dec 17 16:50:12 2009 +0900
@@ -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"
+
--- a/po/ChangeLog	Mon Nov 30 16:07:54 2009 +0900
+++ b/po/ChangeLog	Thu Dec 17 16:50:12 2009 +0900
@@ -1,5 +1,9 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.6.5
+	* Norwegian Nynorsk win32 translation added (Yngve Spjeld Landro)
+	* Russian translation updated (Антон Самохвалов)
+
 version 2.6.4
 	* Afrikaans translation updated (Friedel Wolff)
 	* Chinese (Hong Kong) translation updated (Ambrose C. Li, Paladin R. Liu)
--- a/po/POTFILES.in	Mon Nov 30 16:07:54 2009 +0900
+++ b/po/POTFILES.in	Thu Dec 17 16:50:12 2009 +0900
@@ -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
--- a/po/de.po	Mon Nov 30 16:07:54 2009 +0900
+++ b/po/de.po	Thu Dec 17 16:50:12 2009 +0900
@@ -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: 2009-12-10 22:38+0100\n"
+"PO-Revision-Date: 2009-12-11 20:55+0100\n"
 "Last-Translator: Björn Voigt <bjoern@cs.tu-berlin.de>\n"
 "Language-Team: Deutsch <de@li.org>\n"
 "MIME-Version: 1.0\n"
@@ -1564,6 +1564,7 @@
 msgid "Online"
 msgstr "Online"
 
+#. primative,						no,							id,			name
 msgid "Offline"
 msgstr "Offline"
 
@@ -2516,7 +2517,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 +3107,7 @@
 msgstr "Passwort (nochmal)"
 
 msgid "Enter captcha text"
-msgstr "Captcha-Text eigeben"
+msgstr "Captcha-Text eingeben"
 
 msgid "Captcha"
 msgstr "Captcha"
@@ -3187,10 +3188,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 +3827,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 +3846,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 +3853,32 @@
 msgid "Invalid challenge from server"
 msgstr "Ungültige Challenge vom Server"
 
+msgid "Server thinks authentication is complete, but client does not"
+msgstr ""
+
+msgid "SASL authentication failed"
+msgstr "SASL-Authentifizierung fehlgeschlagen"
+
 #, c-format
 msgid "SASL error: %s"
 msgstr "SASL-Fehler: %s"
 
+#, fuzzy
+msgid "Unable to canonicalize username"
+msgstr "Kann nicht konfigurieren"
+
+#, fuzzy
+msgid "Unable to canonicalize password"
+msgstr "Es konnte kein lauschender Port geöffnet werden."
+
+#, fuzzy
+msgid "Malicious challenge from server"
+msgstr "Ungültige Challenge vom Server"
+
+#, fuzzy
+msgid "Unexpected response from server"
+msgstr "Ungültige HTTP-Antwort vom Server empfangen"
+
 msgid "The BOSH connection manager terminated your session."
 msgstr "Der BOSH-Verbindungsmanager hat Ihre Sitzung beendet."
 
@@ -3956,13 +3979,21 @@
 msgid "Resource"
 msgstr "Ressource"
 
+#, fuzzy
+msgid "Uptime"
+msgstr "Aktualisieren"
+
+#, c-format
+msgid "%s"
+msgstr ""
+
+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 +4042,6 @@
 msgid "Log Out"
 msgstr "Abmelden"
 
-#. primative,						no,							id,			name
-#. 0
-#. 1
 #. 2
 msgid "Chatty"
 msgstr "Gesprächig"
@@ -4021,6 +4049,7 @@
 msgid "Extended Away"
 msgstr "Abwesend (erweitert)"
 
+#. 3
 msgid "Do Not Disturb"
 msgstr "Nicht stören"
 
@@ -4161,7 +4190,7 @@
 "Unable to find alternative XMPP connection methods after failing to connect "
 "directly."
 msgstr ""
-"Nach dem Fehlschlagen einer direkten XMPP-Verbindung konnen keine "
+"Nach dem Fehlschlagen einer direkten XMPP-Verbindung können keine "
 "alternativen Verbindungsmethoden gefunden werden."
 
 msgid "Invalid XMPP ID"
@@ -4459,7 +4488,7 @@
 msgstr "Ungültige ID"
 
 msgid "Invalid Namespace"
-msgstr "Ungültiger Namenraum"
+msgstr "Ungültiger Namensraum"
 
 msgid "Invalid XML"
 msgstr "Ungültiges XML"
@@ -5875,7 +5904,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 +6344,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 +6917,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 +8022,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 +8707,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 +8952,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 +9931,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"
@@ -12221,14 +12250,7 @@
 "<br/>We can't help with 3rd party protocols or plugins!<br/>This list's "
 "primary language is <b>English</b>.  You are welcome to post in another "
 "language, but the responses may be less helpful.<br/><br/>"
-msgstr ""
-"<font size=\"4\">Hilfe von anderen Pidgin-Benutzern:</font> <a href=\"mailto:"
-"support@pidgin.im\">support@pidgin.im</a><br/>Dies ist eine <b>öffentliche</"
-"b> Mailing-Liste! (<a href=\"http://pidgin.im/pipermail/support/\">Archiv</"
-"a>)<br/>Wir können nicht bei Problemen mit Drittanbieter-Protokollen oder "
-"Plugins helfen!<br/>Die Hauptsprache dieser Liste ist <b>Englisch</b>.  Sie "
-"können gern in einer anderen Sprache schreiben, aber die Antworten könnten "
-"weniger hilfreich sein.<br/><br/>"
+msgstr "<font size=\"4\">Hilfe von anderen Pidgin-Benutzern:</font> <a href=\"mailto:support@pidgin.im\">support@pidgin.im</a><br/>Dies ist eine <b>öffentliche</b> Mailing-Liste! (<a href=\"http://pidgin.im/pipermail/support/\">Archiv</a>)<br/>Wir können nicht bei Problemen mit Drittanbieter-Protokollen oder Plugins helfen!<br/>Die Hauptsprache dieser Liste ist <b>Englisch</b>.  Sie können gern in einer anderen Sprache schreiben, aber die Antworten könnten weniger hilfreich sein.<br/>Deutschsprachige Benutzer können auch das Portal <a href=\"http://www.pidgin-im.de/\">Pidgin-IM.de</a> nutzen.  Dort finden Sie aktuelle Informationen zu Pidgin, können mit anderen Benutzern im <a href=\"http://forum.pidgin-im.de/\">Forum</a> diskutieren und Hilfe zu Problemen finden.  Beachten Sie, dass dieses Portal unabhängig vom offiziellen Pidgin-Projekt ist.<br/><br/>"
 
 #, c-format
 msgid ""
@@ -14326,17 +14348,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 +14838,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
--- a/po/ru.po	Mon Nov 30 16:07:54 2009 +0900
+++ b/po/ru.po	Thu Dec 17 16:50:12 2009 +0900
@@ -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: Антон Самохвалов <samant.ua@mail.ru>\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 "<span style=\"italic\">Example: stunserver.org</span>"
 msgstr "<span style=\"italic\">Пример: stunserver.org</span>"
@@ -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"