diff libpurple/protocols/jabber/auth.c @ 29190:4491a662d527

merged with im.pidgin.pidgin
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Thu, 17 Dec 2009 16:50:12 +0900
parents b94fd073187c
children c64b22932ffa
line wrap: on
line diff
--- a/libpurple/protocols/jabber/auth.c	Mon Nov 30 16:07:54 2009 +0900
+++ b/libpurple/protocols/jabber/auth.c	Thu Dec 17 16:50:12 2009 +0900
@@ -39,6 +39,8 @@
 #include "iq.h"
 #include "notify.h"
 
+static GSList *auth_mechs = NULL;
+
 static void auth_old_result_cb(JabberStream *js, const char *from,
                                JabberIqType type, const char *id,
                                xmlnode *packet, gpointer data);
@@ -46,8 +48,11 @@
 gboolean
 jabber_process_starttls(JabberStream *js, xmlnode *packet)
 {
+	PurpleAccount *account;
 	xmlnode *starttls;
 
+	account = purple_connection_get_account(js->gc);
+
 	if((starttls = xmlnode_get_child(packet, "starttls"))) {
 		if(purple_ssl_is_supported()) {
 			jabber_send_raw(js,
@@ -58,7 +63,7 @@
 				PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
 				_("Server requires TLS/SSL, but no TLS/SSL support was found."));
 			return TRUE;
-		} else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) {
+		} else if(purple_account_get_bool(account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) {
 			purple_connection_error_reason(js->gc,
 				 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
 				_("You require encryption, but no TLS/SSL support was found."));
@@ -71,418 +76,96 @@
 
 static void finish_plaintext_authentication(JabberStream *js)
 {
-	if(js->auth_type == JABBER_AUTH_PLAIN) {
-		xmlnode *auth;
-		GString *response;
-		gchar *enc_out;
-
-		auth = xmlnode_new("auth");
-		xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl");
-
-		xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth");
-		xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true");
-
-		response = g_string_new("");
-		response = g_string_append_len(response, "\0", 1);
-		response = g_string_append(response, js->user->node);
-		response = g_string_append_len(response, "\0", 1);
-		response = g_string_append(response,
-				purple_connection_get_password(js->gc));
-
-		enc_out = purple_base64_encode((guchar *)response->str, response->len);
+	JabberIq *iq;
+	xmlnode *query, *x;
 
-		xmlnode_set_attrib(auth, "mechanism", "PLAIN");
-		xmlnode_insert_data(auth, enc_out, -1);
-		g_free(enc_out);
-		g_string_free(response, TRUE);
-
-		jabber_send(js, auth);
-		xmlnode_free(auth);
-	} else if(js->auth_type == JABBER_AUTH_IQ_AUTH) {
-		JabberIq *iq;
-		xmlnode *query, *x;
-
-		iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
-		query = xmlnode_get_child(iq->node, "query");
-		x = xmlnode_new_child(query, "username");
-		xmlnode_insert_data(x, js->user->node, -1);
-		x = xmlnode_new_child(query, "resource");
-		xmlnode_insert_data(x, js->user->resource, -1);
-		x = xmlnode_new_child(query, "password");
-		xmlnode_insert_data(x, purple_connection_get_password(js->gc), -1);
-		jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
-		jabber_iq_send(iq);
-	}
+	iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
+	query = xmlnode_get_child(iq->node, "query");
+	x = xmlnode_new_child(query, "username");
+	xmlnode_insert_data(x, js->user->node, -1);
+	x = xmlnode_new_child(query, "resource");
+	xmlnode_insert_data(x, js->user->resource, -1);
+	x = xmlnode_new_child(query, "password");
+	xmlnode_insert_data(x, purple_connection_get_password(js->gc), -1);
+	jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
+	jabber_iq_send(iq);
 }
 
 static void allow_plaintext_auth(PurpleAccount *account)
 {
+	PurpleConnection *gc;
+	JabberStream *js;
+
 	purple_account_set_bool(account, "auth_plain_in_clear", TRUE);
 
-	finish_plaintext_authentication(account->gc->proto_data);
+	gc = purple_account_get_connection(account);
+	js = purple_connection_get_protocol_data(gc);
+
+	finish_plaintext_authentication(js);
 }
 
 static void disallow_plaintext_auth(PurpleAccount *account)
 {
-	purple_connection_error_reason(account->gc,
+	purple_connection_error_reason(purple_account_get_connection(account),
 		PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
 		_("Server requires plaintext authentication over an unencrypted stream"));
 }
 
 #ifdef HAVE_CYRUS_SASL
-
-static void jabber_auth_start_cyrus(JabberStream *);
-static void jabber_sasl_build_callbacks(JabberStream *);
-
-/* Callbacks for Cyrus SASL */
-
-static int jabber_sasl_cb_realm(void *ctx, int id, const char **avail, const char **result)
-{
-	JabberStream *js = (JabberStream *)ctx;
-
-	if (id != SASL_CB_GETREALM || !result) return SASL_BADPARAM;
-
-	*result = js->user->domain;
-
-	return SASL_OK;
-}
-
-static int jabber_sasl_cb_simple(void *ctx, int id, const char **res, unsigned *len)
+static void
+auth_old_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
 {
-	JabberStream *js = (JabberStream *)ctx;
-
-	switch(id) {
-		case SASL_CB_AUTHNAME:
-			*res = js->user->node;
-			break;
-		case SASL_CB_USER:
-			*res = "";
-			break;
-		default:
-			return SASL_BADPARAM;
-	}
-	if (len) *len = strlen((char *)*res);
-	return SASL_OK;
-}
-
-static int jabber_sasl_cb_secret(sasl_conn_t *conn, void *ctx, int id, sasl_secret_t **secret)
-{
-	JabberStream *js = (JabberStream *)ctx;
-	const char *pw = purple_account_get_password(js->gc->account);
-	size_t len;
-	static sasl_secret_t *x = NULL;
-
-	if (!conn || !secret || id != SASL_CB_PASS)
-		return SASL_BADPARAM;
-
-	len = strlen(pw);
-	x = (sasl_secret_t *) realloc(x, sizeof(sasl_secret_t) + len);
-
-	if (!x)
-		return SASL_NOMEM;
-
-	x->len = len;
-	strcpy((char*)x->data, pw);
-
-	*secret = x;
-	return SASL_OK;
-}
-
-static void allow_cyrus_plaintext_auth(PurpleAccount *account)
-{
-	purple_account_set_bool(account, "auth_plain_in_clear", TRUE);
-
-	jabber_auth_start_cyrus(account->gc->proto_data);
-}
-
-static gboolean auth_pass_generic(JabberStream *js, PurpleRequestFields *fields)
-{
+	PurpleAccount *account;
+	JabberStream *js;
 	const char *entry;
 	gboolean remember;
 
+	/* The password prompt dialog doesn't get disposed if the account disconnects */
+	if (!PURPLE_CONNECTION_IS_VALID(gc))
+		return;
+
+	account = purple_connection_get_account(gc);
+	js = purple_connection_get_protocol_data(gc);
+
 	entry = purple_request_fields_get_string(fields, "password");
 	remember = purple_request_fields_get_bool(fields, "remember");
 
 	if (!entry || !*entry)
 	{
-		purple_notify_error(js->gc->account, NULL, _("Password is required to sign on."), NULL);
-		return FALSE;
+		purple_notify_error(account, NULL, _("Password is required to sign on."), NULL);
+		return;
 	}
 
 	if (remember)
-		purple_account_set_remember_password(js->gc->account, TRUE);
-
-	purple_account_set_password(js->gc->account, entry);
-
-	return TRUE;
-}
-
-static void auth_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields)
-{
-	JabberStream *js;
-
-	/* The password prompt dialog doesn't get disposed if the account disconnects */
-	if (!PURPLE_CONNECTION_IS_VALID(conn))
-		return;
-
-	js = conn->proto_data;
-
-	if (!auth_pass_generic(js, fields))
-		return;
+		purple_account_set_remember_password(account, TRUE);
 
-	/* Rebuild our callbacks as we now have a password to offer */
-	jabber_sasl_build_callbacks(js);
-
-	/* Restart our connection */
-	jabber_auth_start_cyrus(js);
-}
-
-static void
-auth_old_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields)
-{
-	JabberStream *js;
-
-	/* The password prompt dialog doesn't get disposed if the account disconnects */
-	if (!PURPLE_CONNECTION_IS_VALID(conn))
-		return;
-
-	js = conn->proto_data;
-
-	if (!auth_pass_generic(js, fields))
-		return;
+	purple_account_set_password(account, entry);
 
 	/* Restart our connection */
 	jabber_auth_start_old(js);
 }
 
-
 static void
-auth_no_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields)
+auth_no_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
 {
-	JabberStream *js;
-
 	/* The password prompt dialog doesn't get disposed if the account disconnects */
-	if (!PURPLE_CONNECTION_IS_VALID(conn))
+	if (!PURPLE_CONNECTION_IS_VALID(gc))
 		return;
 
-	js = conn->proto_data;
-
 	/* Disable the account as the user has canceled connecting */
-	purple_account_set_enabled(conn->account, purple_core_get_ui(), FALSE);
+	purple_account_set_enabled(purple_connection_get_account(gc), purple_core_get_ui(), FALSE);
 }
-
-static void jabber_auth_start_cyrus(JabberStream *js)
-{
-	const char *clientout = NULL;
-	char *enc_out;
-	unsigned coutlen = 0;
-	xmlnode *auth;
-	sasl_security_properties_t secprops;
-	gboolean again;
-	gboolean plaintext = TRUE;
-
-	/* Set up security properties and options */
-	secprops.min_ssf = 0;
-	secprops.security_flags = SASL_SEC_NOANONYMOUS;
-
-	if (!jabber_stream_is_ssl(js)) {
-		secprops.max_ssf = -1;
-		secprops.maxbufsize = 4096;
-		plaintext = purple_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE);
-		if (!plaintext)
-			secprops.security_flags |= SASL_SEC_NOPLAINTEXT;
-	} else {
-		secprops.max_ssf = 0;
-		secprops.maxbufsize = 0;
-		plaintext = TRUE;
-	}
-	secprops.property_names = 0;
-	secprops.property_values = 0;
-
-	do {
-		again = FALSE;
-
-		js->sasl_state = sasl_client_new("xmpp", js->serverFQDN, NULL, NULL, js->sasl_cb, 0, &js->sasl);
-		if (js->sasl_state==SASL_OK) {
-			sasl_setprop(js->sasl, SASL_SEC_PROPS, &secprops);
-			purple_debug_info("sasl", "Mechs found: %s\n", js->sasl_mechs->str);
-			js->sasl_state = sasl_client_start(js->sasl, js->sasl_mechs->str, NULL, &clientout, &coutlen, &js->current_mech);
-		}
-		switch (js->sasl_state) {
-			/* Success */
-			case SASL_OK:
-			case SASL_CONTINUE:
-				break;
-			case SASL_NOMECH:
-				/* No mechanisms have offered to help */
-
-				/* Firstly, if we don't have a password try
-				 * to get one
-				 */
-
-				if (!purple_account_get_password(js->gc->account)) {
-					purple_account_request_password(js->gc->account, G_CALLBACK(auth_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc);
-					return;
-
-				/* If we've got a password, but aren't sending
-				 * it in plaintext, see if we can turn on
-				 * plaintext auth
-				 */
-				} else if (!plaintext) {
-					char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection.  Allow this and continue authentication?"),
-							js->gc->account->username);
-					purple_request_yes_no(js->gc, _("Plaintext Authentication"),
-							_("Plaintext Authentication"),
-							msg,
-							1, js->gc->account, NULL, NULL, js->gc->account,
-							allow_cyrus_plaintext_auth,
-							disallow_plaintext_auth);
-					g_free(msg);
-					return;
-
-				} else {
-					/* We have no mechs which can work.
-					 * Try falling back on the old jabber:iq:auth method. We get here if the server supports
-					 * one or more sasl mechs, we are compiled with cyrus-sasl support, but we support or can connect with none of
-					 * the offerred mechs. jabberd 2.0 w/ SASL and Apple's iChat Server 10.5 both handle and expect
-					 * jabber:iq:auth in this situation.  iChat Server in particular offers SASL GSSAPI by default, which is often
-					 * not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails.
-					 *
-					 * Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However,
-					 * I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms.
-					 * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers
-					 * which would connect without issue otherwise. -evands
-					 */
-					js->auth_type = JABBER_AUTH_IQ_AUTH;
-					jabber_auth_start_old(js);
-					return;
-				}
-				/* not reached */
-				break;
-
-				/* Fatal errors. Give up and go home */
-			case SASL_BADPARAM:
-			case SASL_NOMEM:
-				break;
-
-				/* For everything else, fail the mechanism and try again */
-			default:
-				purple_debug_info("sasl", "sasl_state is %d, failing the mech and trying again\n", js->sasl_state);
-
-				/*
-				 * DAA: is this right?
-				 * The manpage says that "mech" will contain the chosen mechanism on success.
-				 * Presumably, if we get here that isn't the case and we shouldn't try again?
-				 * I suspect that this never happens.
-				 */
-				/*
-				 * SXW: Yes, this is right. What this handles is the situation where a
-				 * mechanism, say GSSAPI, is tried. If that mechanism fails, it may be
-				 * due to mechanism specific issues, so we want to try one of the other
-				 * supported mechanisms. This code handles that case
-				 */
-				if (js->current_mech && *js->current_mech) {
-					char *pos;
-					if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) {
-						g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech));
-					}
-					/* Remove space which separated this mech from the next */
-					if ((js->sasl_mechs->str)[0] == ' ') {
-						g_string_erase(js->sasl_mechs, 0, 1);
-					}
-					again = TRUE;
-				}
-
-				sasl_dispose(&js->sasl);
-		}
-	} while (again);
-
-	if (js->sasl_state == SASL_CONTINUE || js->sasl_state == SASL_OK) {
-		auth = xmlnode_new("auth");
-		xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl");
-		xmlnode_set_attrib(auth, "mechanism", js->current_mech);
-
-		xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth");
-		xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true");
-
-		if (clientout) {
-			if (coutlen == 0) {
-				xmlnode_insert_data(auth, "=", -1);
-			} else {
-				enc_out = purple_base64_encode((unsigned char*)clientout, coutlen);
-				xmlnode_insert_data(auth, enc_out, -1);
-				g_free(enc_out);
-			}
-		}
-		jabber_send(js, auth);
-		xmlnode_free(auth);
-	} else {
-		purple_connection_error_reason(js->gc,
-			PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
-			_("SASL authentication failed"));
-	}
-}
-
-static int
-jabber_sasl_cb_log(void *context, int level, const char *message)
-{
-	if(level <= SASL_LOG_TRACE)
-		purple_debug_info("sasl", "%s\n", message);
-
-	return SASL_OK;
-}
-
-void
-jabber_sasl_build_callbacks(JabberStream *js)
-{
-	int id;
-
-	/* Set up our callbacks structure */
-	if (js->sasl_cb == NULL)
-		js->sasl_cb = g_new0(sasl_callback_t,6);
-
-	id = 0;
-	js->sasl_cb[id].id = SASL_CB_GETREALM;
-	js->sasl_cb[id].proc = jabber_sasl_cb_realm;
-	js->sasl_cb[id].context = (void *)js;
-	id++;
-
-	js->sasl_cb[id].id = SASL_CB_AUTHNAME;
-	js->sasl_cb[id].proc = jabber_sasl_cb_simple;
-	js->sasl_cb[id].context = (void *)js;
-	id++;
-
-	js->sasl_cb[id].id = SASL_CB_USER;
-	js->sasl_cb[id].proc = jabber_sasl_cb_simple;
-	js->sasl_cb[id].context = (void *)js;
-	id++;
-
-	if (purple_account_get_password(js->gc->account) != NULL ) {
-		js->sasl_cb[id].id = SASL_CB_PASS;
-		js->sasl_cb[id].proc = jabber_sasl_cb_secret;
-		js->sasl_cb[id].context = (void *)js;
-		id++;
-	}
-
-	js->sasl_cb[id].id = SASL_CB_LOG;
-	js->sasl_cb[id].proc = jabber_sasl_cb_log;
-	js->sasl_cb[id].context = (void*)js;
-	id++;
-
-	js->sasl_cb[id].id = SASL_CB_LIST_END;
-}
-
 #endif
 
 void
 jabber_auth_start(JabberStream *js, xmlnode *packet)
 {
-#ifndef HAVE_CYRUS_SASL
-	gboolean digest_md5 = FALSE, plain=FALSE;
-#endif
-
+	GSList *mechanisms = NULL;
+	GSList *l;
+	xmlnode *response = NULL;
 	xmlnode *mechs, *mechnode;
-
+	JabberSaslState state;
+	char *msg = NULL;
 
 	if(js->registration) {
 		jabber_register_start(js);
@@ -490,7 +173,6 @@
 	}
 
 	mechs = xmlnode_get_child(packet, "mechanisms");
-
 	if(!mechs) {
 		purple_connection_error_reason(js->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
@@ -498,76 +180,53 @@
 		return;
 	}
 
-#ifdef HAVE_CYRUS_SASL
-	js->sasl_mechs = g_string_new("");
-#endif
-
 	for(mechnode = xmlnode_get_child(mechs, "mechanism"); mechnode;
 			mechnode = xmlnode_get_next_twin(mechnode))
 	{
 		char *mech_name = xmlnode_get_data(mechnode);
-#ifdef HAVE_CYRUS_SASL
-		/* Don't include Google Talk's X-GOOGLE-TOKEN mechanism, as we will not
-		 * support it and including it gives a false fall-back to other mechs offerred,
-		 * leading to incorrect error handling.
-		 */
-		if (purple_strequal(mech_name, "X-GOOGLE-TOKEN")) {
-			g_free(mech_name);
-			continue;
-		}
 
-		g_string_append(js->sasl_mechs, mech_name);
-		g_string_append_c(js->sasl_mechs, ' ');
-#else
-		if (purple_strequal(mech_name, "DIGEST-MD5"))
-			digest_md5 = TRUE;
-		else if (purple_strequal(mech_name, "PLAIN"))
-			plain = TRUE;
-#endif
-		g_free(mech_name);
+		if (mech_name && *mech_name)
+			mechanisms = g_slist_prepend(mechanisms, mech_name);
+		else if (mech_name)
+			g_free(mech_name);
+
 	}
 
-#ifdef HAVE_CYRUS_SASL
-	js->auth_type = JABBER_AUTH_CYRUS;
-
-	jabber_sasl_build_callbacks(js);
-
-	jabber_auth_start_cyrus(js);
-#else
+	for (l = auth_mechs; l; l = l->next) {
+		JabberSaslMech *possible = l->data;
 
-	if(digest_md5) {
-		xmlnode *auth;
-
-		js->auth_type = JABBER_AUTH_DIGEST_MD5;
-		auth = xmlnode_new("auth");
-		xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl");
-		xmlnode_set_attrib(auth, "mechanism", "DIGEST-MD5");
+		/* Is this the Cyrus SASL mechanism? */
+		if (g_str_equal(possible->name, "*")) {
+			js->auth_mech = possible;
+			break;
+		}
 
-		jabber_send(js, auth);
-		xmlnode_free(auth);
-	} else if(plain) {
-		js->auth_type = JABBER_AUTH_PLAIN;
+		/* Can we find this mechanism in the server's list? */
+		if (g_slist_find_custom(mechanisms, possible->name, (GCompareFunc)strcmp)) {
+			js->auth_mech = possible;
+			break;
+		}
+	}
 
-		if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE)) {
-			char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection.  Allow this and continue authentication?"),
-					js->gc->account->username);
-			purple_request_yes_no(js->gc, _("Plaintext Authentication"),
-					_("Plaintext Authentication"),
-					msg,
-					1,
-					purple_connection_get_account(js->gc), NULL, NULL,
-					purple_connection_get_account(js->gc), allow_plaintext_auth,
-					disallow_plaintext_auth);
-			g_free(msg);
-			return;
-		}
-		finish_plaintext_authentication(js);
-	} else {
+	if (js->auth_mech == NULL) {
+		/* Found no good mechanisms... */
 		purple_connection_error_reason(js->gc,
 				PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
 				_("Server does not use any supported authentication method"));
+		return;
 	}
-#endif
+
+	state = js->auth_mech->start(js, mechs, &response, &msg);
+	if (state == JABBER_SASL_STATE_FAIL) {
+		purple_connection_error_reason(js->gc,
+				PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
+				msg ? msg : _("Unknown Error"));
+	} else if (response) {
+		jabber_send(js, response);
+		xmlnode_free(response);
+	}
+
+	g_free(msg);
 }
 
 static void auth_old_result_cb(JabberStream *js, const char *from,
@@ -578,19 +237,22 @@
 		jabber_stream_set_state(js, JABBER_STREAM_POST_AUTH);
 		jabber_disco_items_server(js);
 	} else {
+		PurpleAccount *account;
 		PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
 		char *msg = jabber_parse_error(js, packet, &reason);
 		xmlnode *error;
 		const char *err_code;
 
+		account = purple_connection_get_account(js->gc);
+
 		/* FIXME: Why is this not in jabber_parse_error? */
 		if((error = xmlnode_get_child(packet, "error")) &&
 					(err_code = xmlnode_get_attrib(error, "code")) &&
 					g_str_equal(err_code, "401")) {
 			reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
 			/* Clear the pasword if it isn't being saved */
-			if (!purple_account_get_remember_password(js->gc->account))
-				purple_account_set_password(js->gc->account, NULL);
+			if (!purple_account_get_remember_password(account))
+				purple_account_set_password(account, NULL);
 		}
 
 		purple_connection_error_reason(js->gc, reason, msg);
@@ -663,16 +325,17 @@
 			jabber_iq_send(iq);
 
 		} else if(xmlnode_get_child(query, "password")) {
-			if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(js->gc->account,
+			PurpleAccount *account = purple_connection_get_account(js->gc);
+			if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(account,
 						"auth_plain_in_clear", FALSE)) {
 				char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection.  Allow this and continue authentication?"),
-											js->gc->account->username);
+											purple_account_get_username(account));
 				purple_request_yes_no(js->gc, _("Plaintext Authentication"),
 						_("Plaintext Authentication"),
 						msg,
 						1,
-						purple_connection_get_account(js->gc), NULL, NULL,
-						purple_connection_get_account(js->gc), allow_plaintext_auth,
+						account, NULL, NULL,
+						account, allow_plaintext_auth,
 						disallow_plaintext_auth);
 				g_free(msg);
 				return;
@@ -689,22 +352,30 @@
 
 void jabber_auth_start_old(JabberStream *js)
 {
+	PurpleAccount *account;
 	JabberIq *iq;
 	xmlnode *query, *username;
 
+	account = purple_connection_get_account(js->gc);
+
 	/*
 	 * We can end up here without encryption if the server doesn't support
 	 * <stream:features/> and we're not using old-style SSL.  If the user
 	 * is requiring SSL/TLS, we need to enforce it.
 	 */
 	if (!jabber_stream_is_ssl(js) &&
-			purple_account_get_bool(purple_connection_get_account(js->gc), "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) {
+			purple_account_get_bool(account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) {
 		purple_connection_error_reason(js->gc,
 			PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
 			_("You require encryption, but it is not available on this server."));
 		return;
 	}
 
+	if (js->registration) {
+		jabber_register_start(js);
+		return;
+	}
+
 	/*
 	 * IQ Auth doesn't have support for resource binding, so we need to pick a
 	 * default resource so it will work properly.  jabberd14 throws an error and
@@ -721,8 +392,8 @@
 	 * password prompting here
 	 */
 
-	if (!purple_account_get_password(js->gc->account)) {
-		purple_account_request_password(js->gc->account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc);
+	if (!purple_account_get_password(account)) {
+		purple_account_request_password(account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc);
 		return;
 	}
 #endif
@@ -737,352 +408,65 @@
 	jabber_iq_send(iq);
 }
 
-/* Parts of this algorithm are inspired by stuff in libgsasl */
-static GHashTable* parse_challenge(const char *challenge)
-{
-	const char *token_start, *val_start, *val_end, *cur;
-	GHashTable *ret = g_hash_table_new_full(g_str_hash, g_str_equal,
-			g_free, g_free);
-
-	cur = challenge;
-	while(*cur != '\0') {
-		/* Find the end of the token */
-		gboolean in_quotes = FALSE;
-		char *name, *value = NULL;
-		token_start = cur;
-		while(*cur != '\0' && (in_quotes || (!in_quotes && *cur != ','))) {
-			if (*cur == '"')
-				in_quotes = !in_quotes;
-			cur++;
-		}
-
-		/* Find start of value.  */
-		val_start = strchr(token_start, '=');
-		if (val_start == NULL || val_start > cur)
-			val_start = cur;
-
-		if (token_start != val_start) {
-			name = g_strndup(token_start, val_start - token_start);
-
-			if (val_start != cur) {
-				val_start++;
-				while (val_start != cur && (*val_start == ' ' || *val_start == '\t'
-						|| *val_start == '\r' || *val_start == '\n'
-						|| *val_start == '"'))
-					val_start++;
-
-				val_end = cur;
-				while (val_end != val_start && (*val_end == ' ' || *val_end == ',' || *val_end == '\t'
-						|| *val_end == '\r' || *val_end == '\n'
-						|| *val_end == '"'  || *val_end == '\0'))
-					val_end--;
-
-				if (val_start != val_end)
-					value = g_strndup(val_start, val_end - val_start + 1);
-			}
-
-			g_hash_table_replace(ret, name, value);
-		}
-
-		/* Find the start of the next token, if there is one */
-		if (*cur != '\0') {
-			cur++;
-			while (*cur == ' ' || *cur == ',' || *cur == '\t'
-					|| *cur == '\r' || *cur == '\n')
-				cur++;
-		}
-	}
-
-	return ret;
-}
-
-static char *
-generate_response_value(JabberID *jid, const char *passwd, const char *nonce,
-		const char *cnonce, const char *a2, const char *realm)
-{
-	PurpleCipher *cipher;
-	PurpleCipherContext *context;
-	guchar result[16];
-	size_t a1len;
-
-	gchar *a1, *convnode=NULL, *convpasswd = NULL, *ha1, *ha2, *kd, *x, *z;
-
-	if((convnode = g_convert(jid->node, -1, "iso-8859-1", "utf-8",
-					NULL, NULL, NULL)) == NULL) {
-		convnode = g_strdup(jid->node);
-	}
-	if(passwd && ((convpasswd = g_convert(passwd, -1, "iso-8859-1",
-						"utf-8", NULL, NULL, NULL)) == NULL)) {
-		convpasswd = g_strdup(passwd);
-	}
-
-	cipher = purple_ciphers_find_cipher("md5");
-	context = purple_cipher_context_new(cipher, NULL);
-
-	x = g_strdup_printf("%s:%s:%s", convnode, realm, convpasswd ? convpasswd : "");
-	purple_cipher_context_append(context, (const guchar *)x, strlen(x));
-	purple_cipher_context_digest(context, sizeof(result), result, NULL);
-
-	a1 = g_strdup_printf("xxxxxxxxxxxxxxxx:%s:%s", nonce, cnonce);
-	a1len = strlen(a1);
-	g_memmove(a1, result, 16);
-
-	purple_cipher_context_reset(context, NULL);
-	purple_cipher_context_append(context, (const guchar *)a1, a1len);
-	purple_cipher_context_digest(context, sizeof(result), result, NULL);
-
-	ha1 = purple_base16_encode(result, 16);
-
-	purple_cipher_context_reset(context, NULL);
-	purple_cipher_context_append(context, (const guchar *)a2, strlen(a2));
-	purple_cipher_context_digest(context, sizeof(result), result, NULL);
-
-	ha2 = purple_base16_encode(result, 16);
-
-	kd = g_strdup_printf("%s:%s:00000001:%s:auth:%s", ha1, nonce, cnonce, ha2);
-
-	purple_cipher_context_reset(context, NULL);
-	purple_cipher_context_append(context, (const guchar *)kd, strlen(kd));
-	purple_cipher_context_digest(context, sizeof(result), result, NULL);
-	purple_cipher_context_destroy(context);
-
-	z = purple_base16_encode(result, 16);
-
-	g_free(convnode);
-	g_free(convpasswd);
-	g_free(x);
-	g_free(a1);
-	g_free(ha1);
-	g_free(ha2);
-	g_free(kd);
-
-	return z;
-}
-
 void
 jabber_auth_handle_challenge(JabberStream *js, xmlnode *packet)
 {
-
-	if(js->auth_type == JABBER_AUTH_DIGEST_MD5) {
-		char *enc_in = xmlnode_get_data(packet);
-		char *dec_in;
-		char *enc_out;
-		GHashTable *parts;
-
-		if(!enc_in) {
-			purple_connection_error_reason(js->gc,
-				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-				_("Invalid response from server"));
-			return;
-		}
-
-		dec_in = (char *)purple_base64_decode(enc_in, NULL);
-		purple_debug_misc("jabber", "decoded challenge (%"
-				G_GSIZE_FORMAT "): %s\n", strlen(dec_in), dec_in);
-
-		parts = parse_challenge(dec_in);
-
-
-		if (g_hash_table_lookup(parts, "rspauth")) {
-			char *rspauth = g_hash_table_lookup(parts, "rspauth");
-
-
-			if (rspauth && purple_strequal(rspauth, js->expected_rspauth)) {
-				jabber_send_raw(js,
-						"<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />",
-						-1);
-			} else {
-				purple_connection_error_reason(js->gc,
-					PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-					_("Invalid challenge from server"));
-			}
-			g_free(js->expected_rspauth);
-			js->expected_rspauth = NULL;
-		} else {
-			/* assemble a response, and send it */
-			/* see RFC 2831 */
-			char *realm;
-			char *nonce;
-
-			/* Make sure the auth string contains everything that should be there.
-			   This isn't everything in RFC2831, but it is what we need. */
-
-			nonce = g_hash_table_lookup(parts, "nonce");
-
-			/* we're actually supposed to prompt the user for a realm if
-			 * the server doesn't send one, but that really complicates things,
-			 * so i'm not gonna worry about it until is poses a problem to
-			 * someone, or I get really bored */
-			realm = g_hash_table_lookup(parts, "realm");
-			if(!realm)
-				realm = js->user->domain;
-
-			if (nonce == NULL || realm == NULL)
-				purple_connection_error_reason(js->gc,
-					PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-					_("Invalid challenge from server"));
-			else {
-				GString *response = g_string_new("");
-				char *a2;
-				char *auth_resp;
-				char *buf;
-				char *cnonce;
-
-				cnonce = g_strdup_printf("%x%u%x", g_random_int(), (int)time(NULL),
-						g_random_int());
-
-				a2 = g_strdup_printf("AUTHENTICATE:xmpp/%s", realm);
-				auth_resp = generate_response_value(js->user,
-						purple_connection_get_password(js->gc), nonce, cnonce, a2, realm);
-				g_free(a2);
-
-				a2 = g_strdup_printf(":xmpp/%s", realm);
-				js->expected_rspauth = generate_response_value(js->user,
-						purple_connection_get_password(js->gc), nonce, cnonce, a2, realm);
-				g_free(a2);
-
-				g_string_append_printf(response, "username=\"%s\"", js->user->node);
-				g_string_append_printf(response, ",realm=\"%s\"", realm);
-				g_string_append_printf(response, ",nonce=\"%s\"", nonce);
-				g_string_append_printf(response, ",cnonce=\"%s\"", cnonce);
-				g_string_append_printf(response, ",nc=00000001");
-				g_string_append_printf(response, ",qop=auth");
-				g_string_append_printf(response, ",digest-uri=\"xmpp/%s\"", realm);
-				g_string_append_printf(response, ",response=%s", auth_resp);
-				g_string_append_printf(response, ",charset=utf-8");
+	const char *ns = xmlnode_get_namespace(packet);
 
-				g_free(auth_resp);
-				g_free(cnonce);
-
-				enc_out = purple_base64_encode((guchar *)response->str, response->len);
-
-				purple_debug_misc("jabber", "decoded response (%"
-						G_GSIZE_FORMAT "): %s\n",
-						response->len, response->str);
-
-				buf = g_strdup_printf("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response>", enc_out);
-
-				jabber_send_raw(js, buf, -1);
-
-				g_free(buf);
-
-				g_free(enc_out);
-
-				g_string_free(response, TRUE);
-			}
-		}
-
-		g_free(enc_in);
-		g_free(dec_in);
-		g_hash_table_destroy(parts);
-	}
-#ifdef HAVE_CYRUS_SASL
-	else if (js->auth_type == JABBER_AUTH_CYRUS) {
-		char *enc_in = xmlnode_get_data(packet);
-		unsigned char *dec_in;
-		char *enc_out;
-		const char *c_out;
-		unsigned int clen;
-		gsize declen;
-		xmlnode *response;
-
-		dec_in = purple_base64_decode(enc_in, &declen);
-
-		js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen,
-						  NULL, &c_out, &clen);
-		g_free(enc_in);
-		g_free(dec_in);
-		if (js->sasl_state != SASL_CONTINUE && js->sasl_state != SASL_OK) {
-			gchar *tmp = g_strdup_printf(_("SASL error: %s"),
-					sasl_errdetail(js->sasl));
-			purple_debug_error("jabber", "Error is %d : %s\n",
-					js->sasl_state, sasl_errdetail(js->sasl));
-			purple_connection_error_reason(js->gc,
-				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
-			g_free(tmp);
-			return;
-		} else {
-			response = xmlnode_new("response");
-			xmlnode_set_namespace(response, "urn:ietf:params:xml:ns:xmpp-sasl");
-			if (clen > 0) {
-				/* Cyrus SASL 2.1.22 appears to contain code to add the charset
-				 * to the response for DIGEST-MD5 but there is no possibility
-				 * it will be executed.
-				 *
-				 * My reading of the digestmd5 plugin indicates the username and
-				 * realm are always encoded in UTF-8 (they seem to be the values
-				 * we pass in), so we need to ensure charset=utf-8 is set.
-				 */
-				if (!purple_strequal(js->current_mech, "DIGEST-MD5") ||
-						strstr(c_out, ",charset="))
-					/* If we're not using DIGEST-MD5 or Cyrus SASL is fixed */
-					enc_out = purple_base64_encode((unsigned char*)c_out, clen);
-				else {
-					char *tmp = g_strdup_printf("%s,charset=utf-8", c_out);
-					enc_out = purple_base64_encode((unsigned char*)tmp, clen + 14);
-					g_free(tmp);
-				}
-
-				xmlnode_insert_data(response, enc_out, -1);
-				g_free(enc_out);
-			}
-			jabber_send(js, response);
-			xmlnode_free(response);
-		}
-	}
-#endif
-}
-
-void jabber_auth_handle_success(JabberStream *js, xmlnode *packet)
-{
-	const char *ns = xmlnode_get_namespace(packet);
-#ifdef HAVE_CYRUS_SASL
-	const void *x;
-#endif
-
-	if (!purple_strequal(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) {
+	if (!purple_strequal(ns, NS_XMPP_SASL)) {
 		purple_connection_error_reason(js->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 			_("Invalid response from server"));
 		return;
 	}
 
-#ifdef HAVE_CYRUS_SASL
-	/* The SASL docs say that if the client hasn't returned OK yet, we
-	 * should try one more round against it
-	 */
-	if (js->sasl_state != SASL_OK) {
-		char *enc_in = xmlnode_get_data(packet);
-		unsigned char *dec_in = NULL;
-		const char *c_out;
-		unsigned int clen;
-		gsize declen = 0;
+	if (js->auth_mech && js->auth_mech->handle_challenge) {
+		xmlnode *response = NULL;
+		char *msg = NULL;
+		JabberSaslState state = js->auth_mech->handle_challenge(js, packet, &response, &msg);
+		if (state == JABBER_SASL_STATE_FAIL) {
+			purple_connection_error_reason(js->gc,
+					PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
+					msg ? msg : _("Invalid challenge from server"));
+		} else if (response) {
+			jabber_send(js, response);
+			xmlnode_free(response);
+		}
 
-		if(enc_in != NULL)
-			dec_in = purple_base64_decode(enc_in, &declen);
+		g_free(msg);
+	} else
+		purple_debug_warning("jabber", "Received unexpected (and unhandled) <challenge/>\n");
+}
 
-		js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen, NULL, &c_out, &clen);
+void jabber_auth_handle_success(JabberStream *js, xmlnode *packet)
+{
+	const char *ns = xmlnode_get_namespace(packet);
 
-		g_free(enc_in);
-		g_free(dec_in);
+	if (!purple_strequal(ns, NS_XMPP_SASL)) {
+		purple_connection_error_reason(js->gc,
+			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+			_("Invalid response from server"));
+		return;
+	}
+
+	if (js->auth_mech && js->auth_mech->handle_success) {
+		char *msg = NULL;
+		JabberSaslState state = js->auth_mech->handle_success(js, packet, &msg);
 
-		if (js->sasl_state != SASL_OK) {
-			/* This should never happen! */
+		if (state == JABBER_SASL_STATE_FAIL) {
 			purple_connection_error_reason(js->gc,
-				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-				_("Invalid response from server"));
-			g_return_if_reached();
+					PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
+					msg ? msg : _("Invalid response from server"));
+			return;
+		} else if (state == JABBER_SASL_STATE_CONTINUE) {
+			purple_connection_error_reason(js->gc,
+					PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
+					msg ? msg : _("Server thinks authentication is complete, but client does not"));
+			return;
 		}
+
+		g_free(msg);
 	}
-	/* If we've negotiated a security layer, we need to enable it */
-	if (js->sasl) {
-		sasl_getprop(js->sasl, SASL_SSF, &x);
-		if (*(int *)x > 0) {
-			sasl_getprop(js->sasl, SASL_MAXOUTBUF, &x);
-			js->sasl_maxbuf = *(int *)x;
-		}
-	}
-#endif
 
 	/*
 	 * The stream will be reinitialized later in jabber_recv_cb_ssl() or
@@ -1095,31 +479,23 @@
 void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet)
 {
 	PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
-	char *msg;
+	char *msg = NULL;
 
-#ifdef HAVE_CYRUS_SASL
-	if(js->auth_fail_count++ < 5) {
-		if (js->current_mech && *js->current_mech) {
-			char *pos;
-			if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) {
-				g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech));
-			}
-			/* Remove space which separated this mech from the next */
-			if ((js->sasl_mechs->str)[0] == ' ') {
-				g_string_erase(js->sasl_mechs, 0, 1);
-			}
-		}
-		if (*js->sasl_mechs->str) {
-			/* If we have remaining mechs to try, do so */
-			sasl_dispose(&js->sasl);
+	if (js->auth_mech && js->auth_mech->handle_failure) {
+		xmlnode *stanza = NULL;
+		JabberSaslState state = js->auth_mech->handle_failure(js, packet, &stanza, &msg);
 
-			jabber_auth_start_cyrus(js);
+		if (state != JABBER_SASL_STATE_FAIL && stanza) {
+			jabber_send(js, stanza);
+			xmlnode_free(stanza);
 			return;
 		}
 	}
-#endif
-	msg = jabber_parse_error(js, packet, &reason);
-	if(!msg) {
+
+	if (!msg)
+		msg = jabber_parse_error(js, packet, &reason);
+
+	if (!msg) {
 		purple_connection_error_reason(js->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 			_("Invalid response from server"));
@@ -1128,3 +504,39 @@
 		g_free(msg);
 	}
 }
+
+static gint compare_mech(gconstpointer a, gconstpointer b)
+{
+	const JabberSaslMech *mech_a = a;
+	const JabberSaslMech *mech_b = b;
+
+	/* higher priority comes *before* lower priority in the list */
+	if (mech_a->priority > mech_b->priority)
+		return -1;
+	else if (mech_a->priority < mech_b->priority)
+		return 1;
+	/* This really shouldn't happen */
+	return 0;
+}
+
+void jabber_auth_init(void)
+{
+	JabberSaslMech **tmp;
+	gint count, i;
+
+	auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_plain_mech(), compare_mech);
+	auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_digest_md5_mech(), compare_mech);
+#ifdef HAVE_CYRUS_SASL
+	auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_cyrus_mech(), compare_mech);
+#endif
+
+	tmp = jabber_auth_get_scram_mechs(&count);
+	for (i = 0; i < count; ++i)
+		auth_mechs = g_slist_insert_sorted(auth_mechs, tmp[i], compare_mech);
+}
+
+void jabber_auth_uninit(void)
+{
+	g_slist_free(auth_mechs);
+	auth_mechs = NULL;
+}