changeset 28744:d558d141aaae

propagate from branch 'im.pidgin.pidgin' (head 4185001f1d8e8d7c894fa194202f7051f06cb59f) to branch 'im.pidgin.cpw.darkrain42.xmpp.scram' (head 90b6819fa75dbf90fc1c2132fe445eaf4661c383)
author Paul Aurich <paul@darkrain42.org>
date Thu, 03 Dec 2009 05:39:00 +0000
parents ba1cefa62792 (current diff) 4f45aae3ace1 (diff)
children 0437b62ffaa5
files configure.ac libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/jutil.c libpurple/protocols/jabber/jutil.h libpurple/protocols/jabber/parser.c
diffstat 19 files changed, 2031 insertions(+), 793 deletions(-) [+]
line wrap: on
line diff
--- a/configure.ac	Thu Dec 03 05:38:42 2009 +0000
+++ b/configure.ac	Thu Dec 03 05:39:00 2009 +0000
@@ -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/libpurple/protocols/jabber/Makefile.am	Thu Dec 03 05:38:42 2009 +0000
+++ b/libpurple/protocols/jabber/Makefile.am	Thu Dec 03 05:39:00 2009 +0000
@@ -10,6 +10,10 @@
 			  adhoccommands.h \
 			  auth.c \
 			  auth.h \
+			  auth_digest_md5.c \
+			  auth_plain.c \
+			  auth_scram.c \
+			  auth_scram.h \
 			  buddy.c \
 			  buddy.h \
 			  bosh.c \
@@ -78,6 +82,10 @@
 
 libxmpp_la_LDFLAGS = -module -avoid-version
 
+if USE_CYRUS_SASL
+JABBERSOURCES += auth_cyrus.c
+endif
+
 if STATIC_JABBER
 
 st = -DPURPLE_STATIC_PRPL
--- a/libpurple/protocols/jabber/auth.c	Thu Dec 03 05:38:42 2009 +0000
+++ b/libpurple/protocols/jabber/auth.c	Thu Dec 03 05:39:00 2009 +0000
@@ -39,6 +39,8 @@
 #include "iq.h"
 #include "notify.h"
 
+static GSList *auth_mechs = NULL;
+
 static void auth_old_result_cb(JabberStream *js, const char *from,
                                JabberIqType type, const char *id,
                                xmlnode *packet, gpointer data);
@@ -46,8 +48,11 @@
 gboolean
 jabber_process_starttls(JabberStream *js, xmlnode *packet)
 {
+	PurpleAccount *account;
 	xmlnode *starttls;
 
+	account = purple_connection_get_account(js->gc);
+
 	if((starttls = xmlnode_get_child(packet, "starttls"))) {
 		if(purple_ssl_is_supported()) {
 			jabber_send_raw(js,
@@ -58,7 +63,7 @@
 				PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
 				_("Server requires TLS/SSL, but no TLS/SSL support was found."));
 			return TRUE;
-		} else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) {
+		} else if(purple_account_get_bool(account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) {
 			purple_connection_error_reason(js->gc,
 				 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
 				_("You require encryption, but no TLS/SSL support was found."));
@@ -71,418 +76,96 @@
 
 static void finish_plaintext_authentication(JabberStream *js)
 {
-	if(js->auth_type == JABBER_AUTH_PLAIN) {
-		xmlnode *auth;
-		GString *response;
-		gchar *enc_out;
-
-		auth = xmlnode_new("auth");
-		xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl");
-
-		xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth");
-		xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true");
-
-		response = g_string_new("");
-		response = g_string_append_len(response, "\0", 1);
-		response = g_string_append(response, js->user->node);
-		response = g_string_append_len(response, "\0", 1);
-		response = g_string_append(response,
-				purple_connection_get_password(js->gc));
-
-		enc_out = purple_base64_encode((guchar *)response->str, response->len);
+	JabberIq *iq;
+	xmlnode *query, *x;
 
-		xmlnode_set_attrib(auth, "mechanism", "PLAIN");
-		xmlnode_insert_data(auth, enc_out, -1);
-		g_free(enc_out);
-		g_string_free(response, TRUE);
-
-		jabber_send(js, auth);
-		xmlnode_free(auth);
-	} else if(js->auth_type == JABBER_AUTH_IQ_AUTH) {
-		JabberIq *iq;
-		xmlnode *query, *x;
-
-		iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
-		query = xmlnode_get_child(iq->node, "query");
-		x = xmlnode_new_child(query, "username");
-		xmlnode_insert_data(x, js->user->node, -1);
-		x = xmlnode_new_child(query, "resource");
-		xmlnode_insert_data(x, js->user->resource, -1);
-		x = xmlnode_new_child(query, "password");
-		xmlnode_insert_data(x, purple_connection_get_password(js->gc), -1);
-		jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
-		jabber_iq_send(iq);
-	}
+	iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
+	query = xmlnode_get_child(iq->node, "query");
+	x = xmlnode_new_child(query, "username");
+	xmlnode_insert_data(x, js->user->node, -1);
+	x = xmlnode_new_child(query, "resource");
+	xmlnode_insert_data(x, js->user->resource, -1);
+	x = xmlnode_new_child(query, "password");
+	xmlnode_insert_data(x, purple_connection_get_password(js->gc), -1);
+	jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
+	jabber_iq_send(iq);
 }
 
 static void allow_plaintext_auth(PurpleAccount *account)
 {
+	PurpleConnection *gc;
+	JabberStream *js;
+
 	purple_account_set_bool(account, "auth_plain_in_clear", TRUE);
 
-	finish_plaintext_authentication(account->gc->proto_data);
+	gc = purple_account_get_connection(account);
+	js = purple_connection_get_protocol_data(gc);
+
+	finish_plaintext_authentication(js);
 }
 
 static void disallow_plaintext_auth(PurpleAccount *account)
 {
-	purple_connection_error_reason(account->gc,
+	purple_connection_error_reason(purple_account_get_connection(account),
 		PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
 		_("Server requires plaintext authentication over an unencrypted stream"));
 }
 
 #ifdef HAVE_CYRUS_SASL
-
-static void jabber_auth_start_cyrus(JabberStream *);
-static void jabber_sasl_build_callbacks(JabberStream *);
-
-/* Callbacks for Cyrus SASL */
-
-static int jabber_sasl_cb_realm(void *ctx, int id, const char **avail, const char **result)
-{
-	JabberStream *js = (JabberStream *)ctx;
-
-	if (id != SASL_CB_GETREALM || !result) return SASL_BADPARAM;
-
-	*result = js->user->domain;
-
-	return SASL_OK;
-}
-
-static int jabber_sasl_cb_simple(void *ctx, int id, const char **res, unsigned *len)
+static void
+auth_old_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
 {
-	JabberStream *js = (JabberStream *)ctx;
-
-	switch(id) {
-		case SASL_CB_AUTHNAME:
-			*res = js->user->node;
-			break;
-		case SASL_CB_USER:
-			*res = "";
-			break;
-		default:
-			return SASL_BADPARAM;
-	}
-	if (len) *len = strlen((char *)*res);
-	return SASL_OK;
-}
-
-static int jabber_sasl_cb_secret(sasl_conn_t *conn, void *ctx, int id, sasl_secret_t **secret)
-{
-	JabberStream *js = (JabberStream *)ctx;
-	const char *pw = purple_account_get_password(js->gc->account);
-	size_t len;
-	static sasl_secret_t *x = NULL;
-
-	if (!conn || !secret || id != SASL_CB_PASS)
-		return SASL_BADPARAM;
-
-	len = strlen(pw);
-	x = (sasl_secret_t *) realloc(x, sizeof(sasl_secret_t) + len);
-
-	if (!x)
-		return SASL_NOMEM;
-
-	x->len = len;
-	strcpy((char*)x->data, pw);
-
-	*secret = x;
-	return SASL_OK;
-}
-
-static void allow_cyrus_plaintext_auth(PurpleAccount *account)
-{
-	purple_account_set_bool(account, "auth_plain_in_clear", TRUE);
-
-	jabber_auth_start_cyrus(account->gc->proto_data);
-}
-
-static gboolean auth_pass_generic(JabberStream *js, PurpleRequestFields *fields)
-{
+	PurpleAccount *account;
+	JabberStream *js;
 	const char *entry;
 	gboolean remember;
 
+	/* The password prompt dialog doesn't get disposed if the account disconnects */
+	if (!PURPLE_CONNECTION_IS_VALID(gc))
+		return;
+
+	account = purple_connection_get_account(gc);
+	js = purple_connection_get_protocol_data(gc);
+
 	entry = purple_request_fields_get_string(fields, "password");
 	remember = purple_request_fields_get_bool(fields, "remember");
 
 	if (!entry || !*entry)
 	{
-		purple_notify_error(js->gc->account, NULL, _("Password is required to sign on."), NULL);
-		return FALSE;
+		purple_notify_error(account, NULL, _("Password is required to sign on."), NULL);
+		return;
 	}
 
 	if (remember)
-		purple_account_set_remember_password(js->gc->account, TRUE);
-
-	purple_account_set_password(js->gc->account, entry);
-
-	return TRUE;
-}
-
-static void auth_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields)
-{
-	JabberStream *js;
-
-	/* The password prompt dialog doesn't get disposed if the account disconnects */
-	if (!PURPLE_CONNECTION_IS_VALID(conn))
-		return;
-
-	js = conn->proto_data;
-
-	if (!auth_pass_generic(js, fields))
-		return;
+		purple_account_set_remember_password(account, TRUE);
 
-	/* Rebuild our callbacks as we now have a password to offer */
-	jabber_sasl_build_callbacks(js);
-
-	/* Restart our connection */
-	jabber_auth_start_cyrus(js);
-}
-
-static void
-auth_old_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields)
-{
-	JabberStream *js;
-
-	/* The password prompt dialog doesn't get disposed if the account disconnects */
-	if (!PURPLE_CONNECTION_IS_VALID(conn))
-		return;
-
-	js = conn->proto_data;
-
-	if (!auth_pass_generic(js, fields))
-		return;
+	purple_account_set_password(account, entry);
 
 	/* Restart our connection */
 	jabber_auth_start_old(js);
 }
 
-
 static void
-auth_no_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields)
+auth_no_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
 {
-	JabberStream *js;
-
 	/* The password prompt dialog doesn't get disposed if the account disconnects */
-	if (!PURPLE_CONNECTION_IS_VALID(conn))
+	if (!PURPLE_CONNECTION_IS_VALID(gc))
 		return;
 
-	js = conn->proto_data;
-
 	/* Disable the account as the user has canceled connecting */
-	purple_account_set_enabled(conn->account, purple_core_get_ui(), FALSE);
+	purple_account_set_enabled(purple_connection_get_account(gc), purple_core_get_ui(), FALSE);
 }
-
-static void jabber_auth_start_cyrus(JabberStream *js)
-{
-	const char *clientout = NULL;
-	char *enc_out;
-	unsigned coutlen = 0;
-	xmlnode *auth;
-	sasl_security_properties_t secprops;
-	gboolean again;
-	gboolean plaintext = TRUE;
-
-	/* Set up security properties and options */
-	secprops.min_ssf = 0;
-	secprops.security_flags = SASL_SEC_NOANONYMOUS;
-
-	if (!jabber_stream_is_ssl(js)) {
-		secprops.max_ssf = -1;
-		secprops.maxbufsize = 4096;
-		plaintext = purple_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE);
-		if (!plaintext)
-			secprops.security_flags |= SASL_SEC_NOPLAINTEXT;
-	} else {
-		secprops.max_ssf = 0;
-		secprops.maxbufsize = 0;
-		plaintext = TRUE;
-	}
-	secprops.property_names = 0;
-	secprops.property_values = 0;
-
-	do {
-		again = FALSE;
-
-		js->sasl_state = sasl_client_new("xmpp", js->serverFQDN, NULL, NULL, js->sasl_cb, 0, &js->sasl);
-		if (js->sasl_state==SASL_OK) {
-			sasl_setprop(js->sasl, SASL_SEC_PROPS, &secprops);
-			purple_debug_info("sasl", "Mechs found: %s\n", js->sasl_mechs->str);
-			js->sasl_state = sasl_client_start(js->sasl, js->sasl_mechs->str, NULL, &clientout, &coutlen, &js->current_mech);
-		}
-		switch (js->sasl_state) {
-			/* Success */
-			case SASL_OK:
-			case SASL_CONTINUE:
-				break;
-			case SASL_NOMECH:
-				/* No mechanisms have offered to help */
-
-				/* Firstly, if we don't have a password try
-				 * to get one
-				 */
-
-				if (!purple_account_get_password(js->gc->account)) {
-					purple_account_request_password(js->gc->account, G_CALLBACK(auth_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc);
-					return;
-
-				/* If we've got a password, but aren't sending
-				 * it in plaintext, see if we can turn on
-				 * plaintext auth
-				 */
-				} else if (!plaintext) {
-					char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection.  Allow this and continue authentication?"),
-							js->gc->account->username);
-					purple_request_yes_no(js->gc, _("Plaintext Authentication"),
-							_("Plaintext Authentication"),
-							msg,
-							1, js->gc->account, NULL, NULL, js->gc->account,
-							allow_cyrus_plaintext_auth,
-							disallow_plaintext_auth);
-					g_free(msg);
-					return;
-
-				} else {
-					/* We have no mechs which can work.
-					 * Try falling back on the old jabber:iq:auth method. We get here if the server supports
-					 * one or more sasl mechs, we are compiled with cyrus-sasl support, but we support or can connect with none of
-					 * the offerred mechs. jabberd 2.0 w/ SASL and Apple's iChat Server 10.5 both handle and expect
-					 * jabber:iq:auth in this situation.  iChat Server in particular offers SASL GSSAPI by default, which is often
-					 * not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails.
-					 *
-					 * Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However,
-					 * I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms.
-					 * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers
-					 * which would connect without issue otherwise. -evands
-					 */
-					js->auth_type = JABBER_AUTH_IQ_AUTH;
-					jabber_auth_start_old(js);
-					return;
-				}
-				/* not reached */
-				break;
-
-				/* Fatal errors. Give up and go home */
-			case SASL_BADPARAM:
-			case SASL_NOMEM:
-				break;
-
-				/* For everything else, fail the mechanism and try again */
-			default:
-				purple_debug_info("sasl", "sasl_state is %d, failing the mech and trying again\n", js->sasl_state);
-
-				/*
-				 * DAA: is this right?
-				 * The manpage says that "mech" will contain the chosen mechanism on success.
-				 * Presumably, if we get here that isn't the case and we shouldn't try again?
-				 * I suspect that this never happens.
-				 */
-				/*
-				 * SXW: Yes, this is right. What this handles is the situation where a
-				 * mechanism, say GSSAPI, is tried. If that mechanism fails, it may be
-				 * due to mechanism specific issues, so we want to try one of the other
-				 * supported mechanisms. This code handles that case
-				 */
-				if (js->current_mech && *js->current_mech) {
-					char *pos;
-					if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) {
-						g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech));
-					}
-					/* Remove space which separated this mech from the next */
-					if ((js->sasl_mechs->str)[0] == ' ') {
-						g_string_erase(js->sasl_mechs, 0, 1);
-					}
-					again = TRUE;
-				}
-
-				sasl_dispose(&js->sasl);
-		}
-	} while (again);
-
-	if (js->sasl_state == SASL_CONTINUE || js->sasl_state == SASL_OK) {
-		auth = xmlnode_new("auth");
-		xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl");
-		xmlnode_set_attrib(auth, "mechanism", js->current_mech);
-
-		xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth");
-		xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true");
-
-		if (clientout) {
-			if (coutlen == 0) {
-				xmlnode_insert_data(auth, "=", -1);
-			} else {
-				enc_out = purple_base64_encode((unsigned char*)clientout, coutlen);
-				xmlnode_insert_data(auth, enc_out, -1);
-				g_free(enc_out);
-			}
-		}
-		jabber_send(js, auth);
-		xmlnode_free(auth);
-	} else {
-		purple_connection_error_reason(js->gc,
-			PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
-			_("SASL authentication failed"));
-	}
-}
-
-static int
-jabber_sasl_cb_log(void *context, int level, const char *message)
-{
-	if(level <= SASL_LOG_TRACE)
-		purple_debug_info("sasl", "%s\n", message);
-
-	return SASL_OK;
-}
-
-void
-jabber_sasl_build_callbacks(JabberStream *js)
-{
-	int id;
-
-	/* Set up our callbacks structure */
-	if (js->sasl_cb == NULL)
-		js->sasl_cb = g_new0(sasl_callback_t,6);
-
-	id = 0;
-	js->sasl_cb[id].id = SASL_CB_GETREALM;
-	js->sasl_cb[id].proc = jabber_sasl_cb_realm;
-	js->sasl_cb[id].context = (void *)js;
-	id++;
-
-	js->sasl_cb[id].id = SASL_CB_AUTHNAME;
-	js->sasl_cb[id].proc = jabber_sasl_cb_simple;
-	js->sasl_cb[id].context = (void *)js;
-	id++;
-
-	js->sasl_cb[id].id = SASL_CB_USER;
-	js->sasl_cb[id].proc = jabber_sasl_cb_simple;
-	js->sasl_cb[id].context = (void *)js;
-	id++;
-
-	if (purple_account_get_password(js->gc->account) != NULL ) {
-		js->sasl_cb[id].id = SASL_CB_PASS;
-		js->sasl_cb[id].proc = jabber_sasl_cb_secret;
-		js->sasl_cb[id].context = (void *)js;
-		id++;
-	}
-
-	js->sasl_cb[id].id = SASL_CB_LOG;
-	js->sasl_cb[id].proc = jabber_sasl_cb_log;
-	js->sasl_cb[id].context = (void*)js;
-	id++;
-
-	js->sasl_cb[id].id = SASL_CB_LIST_END;
-}
-
 #endif
 
 void
 jabber_auth_start(JabberStream *js, xmlnode *packet)
 {
-#ifndef HAVE_CYRUS_SASL
-	gboolean digest_md5 = FALSE, plain=FALSE;
-#endif
-
+	GSList *mechanisms = NULL;
+	GSList *l;
+	xmlnode *response = NULL;
 	xmlnode *mechs, *mechnode;
-
+	JabberSaslState state;
+	const 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,51 @@
 		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);
+	}
 }
 
 static void auth_old_result_cb(JabberStream *js, const char *from,
@@ -578,19 +235,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 +323,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 +350,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 +390,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 +406,61 @@
 	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;
+		const 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);
+		}
+	} else
+		purple_debug_warning("jabber", "Received unexpected (and unhandled) <challenge/>\n");
+}
+
+void jabber_auth_handle_success(JabberStream *js, xmlnode *packet)
+{
+	const char *ns = xmlnode_get_namespace(packet);
 
-		if(enc_in != NULL)
-			dec_in = purple_base64_decode(enc_in, &declen);
+	if (!purple_strequal(ns, NS_XMPP_SASL)) {
+		purple_connection_error_reason(js->gc,
+			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+			_("Invalid response from server"));
+		return;
+	}
 
-		js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen, NULL, &c_out, &clen);
+	if (js->auth_mech && js->auth_mech->handle_success) {
+		const char *msg = NULL;
+		JabberSaslState state = js->auth_mech->handle_success(js, packet, &msg);
 
-		g_free(enc_in);
-		g_free(dec_in);
-
-		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;
 		}
 	}
-	/* 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
@@ -1097,27 +475,18 @@
 	PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
 	char *msg;
 
-#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;
+		const char *msg = 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) {
 		purple_connection_error_reason(js->gc,
@@ -1128,3 +497,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	Thu Dec 03 05:38:42 2009 +0000
+++ b/libpurple/protocols/jabber/auth.h	Thu Dec 03 05:39:00 2009 +0000
@@ -24,9 +24,27 @@
 #ifndef PURPLE_JABBER_AUTH_H_
 #define PURPLE_JABBER_AUTH_H_
 
+typedef struct _JabberSaslMech JabberSaslMech;
+
 #include "jabber.h"
 #include "xmlnode.h"
 
+typedef enum {
+	JABBER_SASL_STATE_FAIL = -1,    /* Abort, Retry, Fail? */
+	JABBER_SASL_STATE_OK = 0,       /* Hooray! */
+	JABBER_SASL_STATE_CONTINUE = 1  /* More authentication required */
+} JabberSaslState;
+
+struct _JabberSaslMech {
+	gint8 priority; /* Higher priority will be tried before lower priority */
+	const gchar *name;
+	JabberSaslState (*start)(JabberStream *js, xmlnode *mechanisms, xmlnode **reply, const char **msg);
+	JabberSaslState (*handle_challenge)(JabberStream *js, xmlnode *packet, xmlnode **reply, const char **msg);
+	JabberSaslState (*handle_success)(JabberStream *js, xmlnode *packet, const char **msg);
+	JabberSaslState (*handle_failure)(JabberStream *js, xmlnode *packet, xmlnode **reply, const 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 03 05:39:00 2009 +0000
@@ -0,0 +1,552 @@
+/*
+ * 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 xmlnode *jabber_auth_start_cyrus(JabberStream *);
+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"));
+}
+
+/* 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;
+	xmlnode *response;
+
+	gc = purple_account_get_connection(account);
+	js = purple_connection_get_protocol_data(gc);
+
+	purple_account_set_bool(account, "auth_plain_in_clear", TRUE);
+
+	response = jabber_auth_start_cyrus(js);
+	if (response) {
+		jabber_send(js, response);
+		xmlnode_free(response);
+	}
+}
+
+static void auth_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
+{
+	PurpleAccount *account;
+	JabberStream *js;
+	xmlnode *response;
+	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 connection */
+	response = jabber_auth_start_cyrus(js);
+	if (response) {
+		jabber_send(js, response);
+		xmlnode_free(response);
+	}
+}
+
+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 xmlnode *jabber_auth_start_cyrus(JabberStream *js)
+{
+	PurpleAccount *account;
+	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;
+
+	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 NULL;
+
+				/* 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 NULL;
+
+				} 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 NULL;
+				}
+				/* 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, 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);
+			}
+		}
+
+		return auth;
+	} else {
+		purple_connection_error_reason(js->gc,
+			PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
+			_("SASL authentication failed"));
+
+		return NULL;
+	}
+}
+
+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 xmlnode *jabber_cyrus_start(JabberStream *js, xmlnode *mechanisms)
+{
+	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);
+}
+
+static xmlnode *jabber_cyrus_handle_challenge(JabberStream *js, xmlnode *packet)
+{
+	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 = 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_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);
+	} else {
+		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);
+		}
+	}
+
+	return response;
+}
+
+static gboolean jabber_cyrus_handle_success(JabberStream *js, xmlnode *packet)
+{
+	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! */
+			purple_connection_error_reason(js->gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Invalid response from server"));
+			g_return_val_if_reached(FALSE);
+		}
+	}
+
+	/* 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 TRUE;
+}
+
+static xmlnode *jabber_cyrus_handle_failure(JabberStream *js, xmlnode *packet)
+{
+	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);
+		}
+	}
+
+	/* Nothing to send */
+	return NULL;
+}
+
+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 03 05:39:00 2009 +0000
@@ -0,0 +1,292 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ *
+ */
+#include "internal.h"
+
+#include "debug.h"
+#include "cipher.h"
+#include "util.h"
+#include "xmlnode.h"
+
+#include "auth.h"
+#include "jabber.h"
+
+static JabberSaslState
+digest_md5_start(JabberStream *js, xmlnode *packet, xmlnode **response,
+                 const char **msg)
+{
+	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, const 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 = _("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 = _("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 = _("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 03 05:39:00 2009 +0000
@@ -0,0 +1,119 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ *
+ */
+#include "internal.h"
+
+#include "account.h"
+#include "debug.h"
+#include "request.h"
+#include "util.h"
+#include "xmlnode.h"
+
+#include "jabber.h"
+#include "auth.h"
+
+static xmlnode *finish_plaintext_authentication(JabberStream *js)
+{
+	xmlnode *auth;
+	GString *response;
+	gchar *enc_out;
+
+	auth = xmlnode_new("auth");
+	xmlnode_set_namespace(auth, NS_XMPP_SASL);
+
+	xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth");
+	xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true");
+
+	response = g_string_new("");
+	response = g_string_append_len(response, "\0", 1);
+	response = g_string_append(response, js->user->node);
+	response = g_string_append_len(response, "\0", 1);
+	response = g_string_append(response,
+			purple_connection_get_password(js->gc));
+
+	enc_out = purple_base64_encode((guchar *)response->str, response->len);
+
+	xmlnode_set_attrib(auth, "mechanism", "PLAIN");
+	xmlnode_insert_data(auth, enc_out, -1);
+	g_free(enc_out);
+	g_string_free(response, TRUE);
+
+	return auth;
+}
+
+static void allow_plaintext_auth(PurpleAccount *account)
+{
+	PurpleConnection *gc = purple_account_get_connection(account);
+	JabberStream *js = purple_connection_get_protocol_data(gc);
+	xmlnode *response;
+
+	purple_account_set_bool(account, "auth_plain_in_clear", TRUE);
+
+	response = finish_plaintext_authentication(js);
+	jabber_send(js, response);
+	xmlnode_free(response);
+}
+
+static void disallow_plaintext_auth(PurpleAccount *account)
+{
+	purple_connection_error_reason(purple_account_get_connection(account),
+		PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
+		_("Server requires plaintext authentication over an unencrypted stream"));
+}
+
+static JabberSaslState
+jabber_plain_start(JabberStream *js, xmlnode *packet, xmlnode **response, const 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 03 05:39:00 2009 +0000
@@ -0,0 +1,570 @@
+/*
+ * 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[hash_len];
+	guchar stored_key[hash_len];
+	guchar client_signature[hash_len];
+	guchar server_key[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];
+
+	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, const 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 = _("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 = _("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, const 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 = _("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 = _("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 = _("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, const 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 = _("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 = _("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 = _("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 03 05:39:00 2009 +0000
@@ -0,0 +1,95 @@
+/**
+ * @file auth_scram.h Implementation of SASL-SCRAM authentication
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef PURPLE_JABBER_AUTH_SCRAM_H_
+#define PURPLE_JABBER_AUTH_SCRAM_H_
+
+/*
+ * Every function in this file is ONLY exposed for tests.
+ * DO NOT USE ANYTHING HERE OR YOU WILL BE SENT TO THE PIT OF DESPAIR.
+ */
+
+/* Per-connection state stored between messages.
+ * This is stored in js->auth_data_mech.
+ */
+typedef struct {
+	const char *mech_substr;
+	const char *name;
+	guint size;
+} JabberScramHash;
+
+typedef struct {
+	const JabberScramHash *hash;
+	char *cnonce;
+	GString *auth_message;
+
+	GString *client_proof;
+	GString *server_signature;
+
+	gchar *password;
+	gboolean channel_binding;
+	int step;
+} JabberScramData;
+
+#include "auth.h"
+
+/**
+ * Implements the Hi() function as described in the SASL-SCRAM I-D.
+ *
+ * @param hash The struct corresponding to the hash function to be used.
+ * @param str  The string to perform the PBKDF2 operation on.
+ * @param salt The salt.
+ * @param iterations The number of iterations to perform.
+ *
+ * @returns A newly allocated string containing the result. The string is
+ *          NOT null-terminated and its length is the length of the binary
+ *          output of the hash function in-use.
+ */
+guchar *jabber_scram_hi(const JabberScramHash *hash, const GString *str,
+                        GString *salt, guint iterations);
+
+/**
+ * Calculates the proofs as described in Section 3 of the SASL-SCRAM I-D.
+ *
+ * @param data A JabberScramData structure. hash and auth_message must be
+ *             set. client_proof and server_signature will be set as a result
+ *             of this function.
+ * @param salt       The salt (as specified by the server)
+ * @param iterations The number of iterations to perform.
+ *
+ * @returns TRUE if the proofs were successfully calculated. FALSE otherwise.
+ */
+gboolean jabber_scram_calc_proofs(JabberScramData *data, GString *salt,
+                                  guint iterations);
+
+/**
+ * Feed the algorithm with the data from the server.
+ */
+gboolean jabber_scram_feed_parser(JabberScramData *data, gchar *in, gchar **out);
+
+/**
+ * Clean up and destroy the data struct
+ */
+void jabber_scram_data_destroy(JabberScramData *data);
+
+#endif /* PURPLE_JABBER_AUTH_SCRAM_H_ */
--- a/libpurple/protocols/jabber/jabber.c	Thu Dec 03 05:38:42 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Thu Dec 03 05:39:00 2009 +0000
@@ -234,8 +234,8 @@
 		 * 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);
 	}
 }
 
@@ -1510,6 +1510,8 @@
 	purple_circ_buffer_destroy(js->write_buffer);
 	if(js->writeh)
 		purple_input_remove(js->writeh);
+	if (js->auth_mech && js->auth_mech->dispose)
+		js->auth_mech->dispose(js);
 #ifdef HAVE_CYRUS_SASL
 	if(js->sasl)
 		sasl_dispose(&js->sasl);
@@ -1586,11 +1588,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"),
@@ -3519,6 +3516,8 @@
 	jabber_add_feature(JINGLE_TRANSPORT_ICEUDP, 0);
 #endif
 
+	jabber_auth_init();
+
 	/* IPC functions */
 	purple_plugin_ipc_register(plugin, "contact_has_feature", PURPLE_CALLBACK(jabber_ipc_contact_has_feature),
 							 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER,
@@ -3553,6 +3552,7 @@
 {
 	purple_plugin_ipc_unregister_all(plugin);
 
+	jabber_auth_uninit();
 	jabber_features_destroy();
 	jabber_identities_destroy();
 }
--- a/libpurple/protocols/jabber/jabber.h	Thu Dec 03 05:38:42 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Thu Dec 03 05:39:00 2009 +0000
@@ -66,6 +66,7 @@
 
 #include "namespaces.h"
 
+#include "auth.h"
 #include "iq.h"
 #include "jutil.h"
 #include "xmlnode.h"
@@ -106,13 +107,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/jutil.c	Thu Dec 03 05:38:42 2009 +0000
+++ b/libpurple/protocols/jabber/jutil.c	Thu Dec 03 05:39:00 2009 +0000
@@ -276,6 +276,42 @@
 #endif /* USE_IDN */
 }
 
+char *jabber_saslprep(const char *in)
+{
+#ifdef USE_IDN
+	char *out;
+
+	g_return_val_if_fail(in != NULL, NULL);
+	g_return_val_if_fail(strlen(in) <= sizeof(idn_buffer) - 1, NULL);
+
+	strncpy(idn_buffer, in, sizeof(idn_buffer) - 1);
+	idn_buffer[sizeof(idn_buffer) - 1] = '\0';
+
+	if (STRINGPREP_OK != stringprep(idn_buffer, sizeof(idn_buffer), 0,
+	                                stringprep_saslprep)) {
+		memset(idn_buffer, 0, sizeof(idn_buffer));
+		return NULL;
+	}
+
+	out = g_strdup(idn_buffer);
+	memset(idn_buffer, 0, sizeof(idn_buffer));
+	return out;
+#else /* USE_IDN */
+	/* TODO: Something better than disallowing all non-ASCII characters */
+	/* TODO: Is this even correct? */
+	const guchar *c;
+
+	c = (const guchar *)in;
+	while (*c) {
+		if (*c > 0x7f ||
+				(*c < 0x20 && *c != '\t' && *c != '\n' && *c != '\r'))
+			return NULL;
+	}
+
+	return g_strdup(in);
+#endif /* USE_IDN */
+}
+
 static JabberID*
 jabber_id_new_internal(const char *str, gboolean allow_terminating_slash)
 {
--- a/libpurple/protocols/jabber/jutil.h	Thu Dec 03 05:38:42 2009 +0000
+++ b/libpurple/protocols/jabber/jutil.h	Thu Dec 03 05:39:00 2009 +0000
@@ -54,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/parser.c	Thu Dec 03 05:38:42 2009 +0000
+++ b/libpurple/protocols/jabber/parser.c	Thu Dec 03 05:39:00 2009 +0000
@@ -263,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/tests/Makefile.am	Thu Dec 03 05:38:42 2009 +0000
+++ b/libpurple/tests/Makefile.am	Thu Dec 03 05:39:00 2009 +0000
@@ -11,6 +11,7 @@
 	    tests.h \
 		test_cipher.c \
 		test_jabber_jutil.c \
+		test_jabber_scram.c \
 		test_qq.c \
 		test_yahoo_util.c \
 		test_util.c \
--- a/libpurple/tests/check_libpurple.c	Thu Dec 03 05:38:42 2009 +0000
+++ b/libpurple/tests/check_libpurple.c	Thu Dec 03 05:39:00 2009 +0000
@@ -76,6 +76,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());
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/test_jabber_scram.c	Thu Dec 03 05:39:00 2009 +0000
@@ -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	Thu Dec 03 05:38:42 2009 +0000
+++ b/libpurple/tests/tests.h	Thu Dec 03 05:39:00 2009 +0000
@@ -10,6 +10,7 @@
 Suite * master_suite(void);
 Suite * cipher_suite(void);
 Suite * jabber_jutil_suite(void);
+Suite * jabber_scram_suite(void);
 Suite * qq_suite(void);
 Suite * yahoo_util_suite(void);
 Suite * util_suite(void);
--- a/po/POTFILES.in	Thu Dec 03 05:38:42 2009 +0000
+++ b/po/POTFILES.in	Thu Dec 03 05:39:00 2009 +0000
@@ -88,6 +88,9 @@
 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/bosh.c
 libpurple/protocols/jabber/buddy.c
 libpurple/protocols/jabber/chat.c