propagate from branch 'im.pidgin.pidgin' (head f21da9280c0c8679008176c8f1a3bd74a09c1916) cpw.darkrain42.xmpp.scram

Mon, 30 Nov 2009 20:35:41 +0000

author
Paul Aurich <darkrain42@pidgin.im>
date
Mon, 30 Nov 2009 20:35:41 +0000
branch
cpw.darkrain42.xmpp.scram
changeset 29079
e2bca7671cd6
parent 29056
f21da9280c0c (current diff)
parent 29027
6af29b140195 (diff)
child 29080
ad5cd77354db

propagate from branch 'im.pidgin.pidgin' (head f21da9280c0c8679008176c8f1a3bd74a09c1916)
to branch 'im.pidgin.cpw.darkrain42.xmpp.scram' (head 6af29b140195931e7bf15955a3d643dddce418e0)

configure.ac file | annotate | diff | comparison | revisions
--- a/configure.ac	Mon Nov 30 15:46:58 2009 +0000
+++ b/configure.ac	Mon Nov 30 20:35:41 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	Mon Nov 30 15:46:58 2009 +0000
+++ b/libpurple/protocols/jabber/Makefile.am	Mon Nov 30 20:35:41 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	Mon Nov 30 15:46:58 2009 +0000
+++ b/libpurple/protocols/jabber/auth.c	Mon Nov 30 20:35:41 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,426 +76,101 @@
 
 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;
 	xmlnode *mechs, *mechnode;
 
-
 	if(js->registration) {
 		jabber_register_start(js);
 		return;
 	}
 
 	mechs = xmlnode_get_child(packet, "mechanisms");
-
 	if(!mechs) {
 		purple_connection_error_reason(js->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
@@ -498,76 +178,47 @@
 		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
+
+	response = js->auth_mech->start(js, mechs);
+	if (response) {
+		jabber_send(js, response);
+		xmlnode_free(response);
+	}
 }
 
 static void auth_old_result_cb(JabberStream *js, const char *from,
@@ -578,19 +229,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 +317,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 +344,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 +384,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 +400,43 @@
 	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(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);
+	if (js->auth_mech && js->auth_mech->handle_challenge) {
+		xmlnode *response = js->auth_mech->handle_challenge(js, packet);
+		if (response != NULL) {
+			jabber_send(js, response);
+			xmlnode_free(response);
+		}
+	} else
+		purple_debug_warning("jabber", "Received unexpected (and unhandled) <challenge/>\n");
+}
 
-		g_free(enc_in);
-		g_free(dec_in);
+void jabber_auth_handle_success(JabberStream *js, xmlnode *packet)
+{
+	const char *ns = xmlnode_get_namespace(packet);
 
-		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_if_reached();
-		}
+	if (!purple_strequal(ns, NS_XMPP_SASL)) {
+		purple_connection_error_reason(js->gc,
+			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+			_("Invalid response from server"));
+		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;
-		}
+
+	if (js->auth_mech && js->auth_mech->handle_success &&
+			!js->auth_mech->handle_success(js, packet)) {
+		return;
 	}
-#endif
 
 	/*
 	 * The stream will be reinitialized later in jabber_recv_cb_ssl() or
@@ -1097,27 +451,15 @@
 	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);
-
-			jabber_auth_start_cyrus(js);
+	if (js->auth_mech && js->auth_mech->handle_failure) {
+		xmlnode *stanza = js->auth_mech->handle_failure(js, packet);
+		if (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 +470,39 @@
 		g_free(msg);
 	}
 }
+
+static gint compare_mech(gconstpointer a, gconstpointer b)
+{
+	const JabberSaslMech *mech_a = a;
+	const JabberSaslMech *mech_b = b;
+
+	/* higher priority comes *before* lower priority in the list */
+	if (mech_a->priority > mech_b->priority)
+		return -1;
+	else if (mech_a->priority < mech_b->priority)
+		return 1;
+	/* This really shouldn't happen */
+	return 0;
+}
+
+void jabber_auth_init(void)
+{
+	JabberSaslMech **tmp;
+	gint count, i;
+
+	auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_plain_mech(), compare_mech);
+	auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_digest_md5_mech(), compare_mech);
+#ifdef HAVE_CYRUS_SASL
+	auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_cyrus_mech(), compare_mech);
+#endif
+
+	tmp = jabber_auth_get_scram_mechs(&count);
+	for (i = 0; i < count; ++i)
+		auth_mechs = g_slist_insert_sorted(auth_mechs, tmp[i], compare_mech);
+}
+
+void jabber_auth_uninit(void)
+{
+	g_slist_free(auth_mechs);
+	auth_mechs = NULL;
+}
--- a/libpurple/protocols/jabber/auth.h	Mon Nov 30 15:46:58 2009 +0000
+++ b/libpurple/protocols/jabber/auth.h	Mon Nov 30 20:35:41 2009 +0000
@@ -24,9 +24,21 @@
 #ifndef PURPLE_JABBER_AUTH_H_
 #define PURPLE_JABBER_AUTH_H_
 
+typedef struct _JabberSaslMech JabberSaslMech;
+
 #include "jabber.h"
 #include "xmlnode.h"
 
+struct _JabberSaslMech {
+	gint8 priority; /* Higher priority will be tried before lower priority */
+	const gchar *name;
+	xmlnode *(*start)(JabberStream *js, xmlnode *mechanisms);
+	xmlnode *(*handle_challenge)(JabberStream *js, xmlnode *packet);
+	gboolean (*handle_success)(JabberStream *js, xmlnode *packet);
+	xmlnode *(*handle_failure)(JabberStream *js, xmlnode *packet);
+	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 +46,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	Mon Nov 30 20:35:41 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	Mon Nov 30 20:35:41 2009 +0000
@@ -0,0 +1,291 @@
+/*
+ * 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 xmlnode *digest_md5_start(JabberStream *js, xmlnode *packet)
+{
+	xmlnode *auth;
+
+	auth = xmlnode_new("auth");
+	xmlnode_set_namespace(auth, NS_XMPP_SASL);
+	xmlnode_set_attrib(auth, "mechanism", "DIGEST-MD5");
+
+	return auth;
+}
+
+/* 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 xmlnode *digest_md5_handle_challenge(JabberStream *js, xmlnode *packet)
+{
+	xmlnode *reply = NULL;
+	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 NULL;
+	}
+
+	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 {
+			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 *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);
+
+	return reply;
+}
+
+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	Mon Nov 30 20:35:41 2009 +0000
@@ -0,0 +1,116 @@
+/*
+ * 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 xmlnode *jabber_plain_start(JabberStream *js, xmlnode *packet)
+{
+	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))
+		return finish_plaintext_authentication(js);
+
+	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 NULL;
+}
+
+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	Mon Nov 30 20:35:41 2009 +0000
@@ -0,0 +1,601 @@
+/*
+ * 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 struct {
+	const char *mech_substr;
+	const char *hash;
+} mech_hashes[] = {
+	{ "-SHA-1", "sha1" },
+};
+
+static const struct {
+	const char *hash;
+	guint size;
+} hash_sizes[] = {
+	{ "sha1", 20 },
+};
+
+static const gchar *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(mech_hashes); ++i) {
+		if (strstr(mech, mech_hashes[i].mech_substr))
+			return mech_hashes[i].hash;
+	}
+
+	purple_debug_error("jabber", "Unknown SCRAM mechanism %s\n", mech);
+
+	return NULL;
+}
+
+static guint hash_to_output_len(const gchar *hash)
+{
+	int i;
+
+	g_return_val_if_fail(hash != NULL && *hash != '\0', 0);
+
+	for (i = 0; i < G_N_ELEMENTS(hash_sizes); ++i) {
+		if (g_str_equal(hash, hash_sizes[i].hash))
+			return hash_sizes[i].size;
+	}
+
+	purple_debug_error("jabber", "Unknown SCRAM hash function %s\n", hash);
+
+	return 0;
+}
+
+guchar *jabber_scram_hi(const gchar *hash, const GString *str,
+                        GString *salt, guint iterations)
+{
+	PurpleCipherContext *context;
+	guchar *result;
+	guint i;
+	guint hash_len;
+	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);
+
+	hash_len = hash_to_output_len(hash);
+	g_return_val_if_fail(hash_len > 0, NULL);
+
+	prev = g_new0(guint8, hash_len);
+	tmp = g_new0(guint8, hash_len);
+	result = g_new0(guint8, hash_len);
+
+	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);
+	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_len, result, NULL);
+
+	memcpy(prev, result, hash_len);
+
+	/* Compute U1...Ui */
+	for (i = 1; i < iterations; ++i) {
+		guint j;
+		purple_cipher_context_set_option(context, "hash", (gpointer)hash);
+		purple_cipher_context_set_key_with_len(context, (guchar *)str->str, str->len);
+		purple_cipher_context_append(context, prev, hash_len);
+		purple_cipher_context_digest(context, hash_len, tmp, NULL);
+
+		for (j = 0; j < hash_len; ++j)
+			result[j] ^= tmp[j];
+
+		memcpy(prev, tmp, hash_len);
+	}
+
+	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 and the second (len) is the length of the output
+ * buffer and key/data (the fourth argument).
+ * "str" is a NULL-terminated string for hmac().
+ *
+ * Needless to say, these are fragile.
+ */
+static void
+hmac(const gchar *hash_alg, gsize len, 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_alg);
+	purple_cipher_context_set_key_with_len(context, key, len);
+	purple_cipher_context_append(context, (guchar *)str, strlen(str));
+	purple_cipher_context_digest(context, len, out, NULL);
+	purple_cipher_context_destroy(context);
+}
+
+static void
+hash(const gchar *hash_alg, gsize len, guchar *out, const guchar *data)
+{
+	PurpleCipherContext *context;
+
+	context = purple_cipher_context_new_by_name(hash_alg, NULL);
+	purple_cipher_context_append(context, data, len);
+	purple_cipher_context_digest(context, len, out, NULL);
+	purple_cipher_context_destroy(context);
+}
+
+gboolean
+jabber_scram_calc_proofs(JabberScramData *data, GString *salt, guint iterations)
+{
+	guint hash_len = hash_to_output_len(data->hash);
+	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, hash_len, client_key, salted_password, "Client Key");
+	/* server_key = HMAC(salted_password, "Server Key") */
+	hmac(data->hash, hash_len, server_key, salted_password, "Server Key");
+	g_free(salted_password);
+
+	/* stored_key = HASH(client_key) */
+	hash(data->hash, hash_len, stored_key, client_key);
+
+	/* client_signature = HMAC(stored_key, auth_message) */
+	hmac(data->hash, hash_len, client_signature, stored_key, data->auth_message->str);
+	/* server_signature = HMAC(server_key, auth_message) */
+	hmac(data->hash, hash_len, (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)
+{
+	GString *s = g_string_new(in);
+	gchar *c;
+	gsize i = 0;
+
+	c = s->str;
+	while (*c) {
+		if (*c == ',' || *c == '=') {
+			g_string_erase(s, i, 1);
+			g_string_insert(s, i, *c == ',' ? "=2C" : "=3D");
+		}
+
+		++c; ++i;
+	}
+
+	return g_string_free(s, FALSE);
+}
+
+static xmlnode *scram_start(JabberStream *js, xmlnode *mechanisms)
+{
+	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) {
+		/* TODO: Error handling in the response value from scram_start */
+		return NULL;
+	}
+
+	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);
+		return NULL;
+	}
+
+	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);
+
+	return reply;
+}
+
+static xmlnode *scram_handle_challenge(JabberStream *js, xmlnode *challenge)
+{
+	JabberScramData *data = js->auth_mech_data;
+	xmlnode *reply;
+	gchar *enc_in, *dec_in;
+	gchar *enc_out = NULL, *dec_out = NULL;
+	gsize len;
+
+	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;
+		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;
+		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;
+		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);
+	}
+
+out:
+	g_free(enc_out);
+	g_free(dec_out);
+
+	return reply;
+}
+
+static gboolean scram_handle_success(JabberStream *js, xmlnode *packet)
+{
+	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 TRUE;
+
+	if (data->step != 2)
+		return FALSE;
+
+	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);
+		return FALSE;
+	}
+
+	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);
+		return FALSE;
+	}
+
+	/* Hooray */
+	return TRUE;
+}
+
+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
+
+/* For tests */
+JabberSaslMech *jabber_scram_get_sha1(void)
+{
+	return &scram_sha1_mech;
+}
+
+JabberSaslMech **jabber_auth_get_scram_mechs(gint *count)
+{
+	static JabberSaslMech *mechs[] = {
+		&scram_sha1_mech,
+#ifdef CHANNEL_BINDING
+		&scram_sha1_plus_mech,
+#endif
+	};
+
+	g_return_val_if_fail(count != NULL, NULL);
+
+	*count = G_N_ELEMENTS(mechs);
+	return mechs;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/auth_scram.h	Mon Nov 30 20:35:41 2009 +0000
@@ -0,0 +1,94 @@
+/**
+ * @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 *hash;
+	char *cnonce;
+	GString *auth_message;
+
+	GString *client_proof;
+	GString *server_signature;
+
+	gchar *password;
+	gboolean channel_binding;
+	int step;
+} JabberScramData;
+
+#include "auth.h"
+
+JabberSaslMech *jabber_scram_get_sha1(void);
+
+/**
+ * Implements the Hi() function as described in the SASL-SCRAM I-D.
+ *
+ * @param hash The name of a hash function to be used with HMAC.  This should
+ *             be suitable to be passed to the libpurple cipher API.  Typically
+ *             it will be "sha1".
+ * @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 char *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	Mon Nov 30 15:46:58 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Mon Nov 30 20:35:41 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);
 	}
 }
 
@@ -1509,6 +1509,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);
@@ -1585,11 +1587,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"),
@@ -3518,6 +3515,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,
@@ -3552,6 +3551,7 @@
 {
 	purple_plugin_ipc_unregister_all(plugin);
 
+	jabber_auth_uninit();
 	jabber_features_destroy();
 	jabber_identities_destroy();
 }
--- a/libpurple/protocols/jabber/jabber.h	Mon Nov 30 15:46:58 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Mon Nov 30 20:35:41 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	Mon Nov 30 15:46:58 2009 +0000
+++ b/libpurple/protocols/jabber/jutil.c	Mon Nov 30 20:35:41 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	Mon Nov 30 15:46:58 2009 +0000
+++ b/libpurple/protocols/jabber/jutil.h	Mon Nov 30 20:35:41 2009 +0000
@@ -51,6 +51,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	Mon Nov 30 15:46:58 2009 +0000
+++ b/libpurple/protocols/jabber/parser.c	Mon Nov 30 20:35:41 2009 +0000
@@ -269,8 +269,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	Mon Nov 30 15:46:58 2009 +0000
+++ b/libpurple/tests/Makefile.am	Mon Nov 30 20:35:41 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	Mon Nov 30 15:46:58 2009 +0000
+++ b/libpurple/tests/check_libpurple.c	Mon Nov 30 20:35:41 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	Mon Nov 30 20:35:41 2009 +0000
@@ -0,0 +1,99 @@
+#include <string.h>
+
+#include "tests.h"
+#include "../util.h"
+#include "../protocols/jabber/auth_scram.h"
+
+#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", 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";
+	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
+
+START_TEST(test_mech)
+{
+	JabberScramData *data = g_new0(JabberScramData, 1);
+	gboolean ret;
+	gchar *out;
+
+	data->step = 1;
+	data->hash = "sha1";
+	data->password = g_strdup("password");
+	data->cnonce = g_strdup("H7yDYKAWBCrM2Fa5SxGa4iez");
+	data->auth_message = g_string_new("n=paul,r=H7yDYKAWBCrM2Fa5SxGa4iez");
+
+	ret = jabber_scram_feed_parser(data, "r=H7yDYKAWBCrM2Fa5SxGa4iezFPVDPpDUcGxPkH3RzP,s=3rXeErP/os7jUNqU,i=4096", &out);
+	fail_unless(ret == TRUE);
+	fail_unless(g_str_equal(out, "c=biws,r=H7yDYKAWBCrM2Fa5SxGa4iezFPVDPpDUcGxPkH3RzP,p=pXkak78EuwwOEwk2/h/OzD7NkEI="), "Failed. Got %s instead", out);
+	g_free(out);
+
+	data->step = 2;
+	ret = jabber_scram_feed_parser(data, "v=ldX4EBNnOgDnNTOCmbSfBHAUCOs=", &out);
+	fail_unless(ret == TRUE);
+	fail_unless(out == NULL);
+
+	jabber_scram_data_destroy(data);
+}
+END_TEST
+
+Suite *
+jabber_scram_suite(void)
+{
+	Suite *s = suite_create("Jabber SASL SCRAM functions");
+
+	TCase *tc = tcase_create("PBKDF2 Functionality");
+	tcase_add_test(tc, test_pbkdf2);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("SCRAM Proofs");
+	tcase_add_test(tc, test_proofs);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("SCRAM exchange");
+	tcase_add_test(tc, test_mech);
+	suite_add_tcase(s, tc);
+	return s;
+}
--- a/libpurple/tests/tests.h	Mon Nov 30 15:46:58 2009 +0000
+++ b/libpurple/tests/tests.h	Mon Nov 30 20:35:41 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);

mercurial