changeset 28696:8ada06fb65ed

jabber: Factor the SASL auth methods into their own files. This works with and without Cyrus SASL, though there's room for cleanup and de-duplication (some code is now duplicated between auth.c and auth_cyrus.c).
author Paul Aurich <paul@darkrain42.org>
date Sat, 07 Nov 2009 06:10:17 +0000
parents 13e668ef158d
children de8565bc63fe
files configure.ac libpurple/protocols/jabber/Makefile.am libpurple/protocols/jabber/auth.c libpurple/protocols/jabber/auth.h libpurple/protocols/jabber/auth_cyrus.c libpurple/protocols/jabber/auth_digest_md5.c libpurple/protocols/jabber/auth_plain.c libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/jabber.h libpurple/protocols/jabber/parser.c
diffstat 10 files changed, 1106 insertions(+), 777 deletions(-) [+]
line wrap: on
line diff
--- a/configure.ac	Sat Nov 07 04:41:04 2009 +0000
+++ b/configure.ac	Sat Nov 07 06:10:17 2009 +0000
@@ -2264,11 +2264,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	Sat Nov 07 04:41:04 2009 +0000
+++ b/libpurple/protocols/jabber/Makefile.am	Sat Nov 07 06:10:17 2009 +0000
@@ -7,6 +7,8 @@
 
 JABBERSOURCES = auth.c \
 			  auth.h \
+			  auth_digest_md5.c \
+			  auth_plain.c \
 			  buddy.c \
 			  buddy.h \
 			  bosh.c \
@@ -76,6 +78,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	Sat Nov 07 04:41:04 2009 +0000
+++ b/libpurple/protocols/jabber/auth.c	Sat Nov 07 06:10:17 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);
@@ -71,48 +73,19 @@
 
 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)
@@ -129,133 +102,39 @@
 		_("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)
 {
@@ -271,226 +150,20 @@
 	purple_account_set_enabled(conn->account, 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 +171,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,
@@ -705,6 +349,11 @@
 		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
@@ -737,308 +386,10 @@
 	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");
-
-				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")) {
 		purple_connection_error_reason(js->gc,
@@ -1047,42 +398,31 @@
 		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, "urn:ietf:params:xml: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 +437,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 +456,32 @@
 		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)
+{
+	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
+}
+
+void jabber_auth_uninit(void)
+{
+	g_slist_free(auth_mechs);
+	auth_mechs = NULL;
+}
--- a/libpurple/protocols/jabber/auth.h	Sat Nov 07 04:41:04 2009 +0000
+++ b/libpurple/protocols/jabber/auth.h	Sat Nov 07 06:10:17 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,13 @@
 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);
+#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	Sat Nov 07 06:10:17 2009 +0000
@@ -0,0 +1,540 @@
+/*
+ * 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;
+	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)
+{
+	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 *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;
+
+	/* Disable the account as the user has canceled connecting */
+	purple_account_set_enabled(conn->account, purple_core_get_ui(), FALSE);
+}
+
+static xmlnode *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 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?"),
+							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 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
+					 */
+					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, "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);
+			}
+		}
+
+		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)
+{
+	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;
+}
+
+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, "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);
+		}
+	}
+
+	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	Sat Nov 07 06:10:17 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, "urn:ietf:params:xml: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, "urn:ietf:params:xml: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, "urn:ietf:params:xml: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	Sat Nov 07 06:10:17 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, "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);
+
+	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;
+}
--- a/libpurple/protocols/jabber/jabber.c	Sat Nov 07 04:41:04 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sat Nov 07 06:10:17 2009 +0000
@@ -233,8 +233,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);
 	}
 }
 
@@ -1577,11 +1577,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"),
@@ -3463,6 +3458,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,
@@ -3497,6 +3494,7 @@
 {
 	purple_plugin_ipc_unregister_all(plugin);
 
+	jabber_auth_uninit();
 	jabber_features_destroy();
 	jabber_identities_destroy();
 }
--- a/libpurple/protocols/jabber/jabber.h	Sat Nov 07 04:41:04 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Sat Nov 07 06:10:17 2009 +0000
@@ -64,6 +64,7 @@
 #include "roomlist.h"
 #include "sslconn.h"
 
+#include "auth.h"
 #include "iq.h"
 #include "jutil.h"
 #include "xmlnode.h"
@@ -104,13 +105,8 @@
 		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;
 	char *stream_id;
 	JabberStreamState state;
 
--- a/libpurple/protocols/jabber/parser.c	Sat Nov 07 04:41:04 2009 +0000
+++ b/libpurple/protocols/jabber/parser.c	Sat Nov 07 06:10:17 2009 +0000
@@ -260,8 +260,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);
 	}
 }