cpw.darkrain42.xmpp.scram: efd6615f: jabber: Factor the SASL auth methods int...
darkrain42 at pidgin.im
darkrain42 at pidgin.im
Sat Nov 7 02:06:16 EST 2009
-----------------------------------------------------------------
Revision: efd6615fa0dbcbc47776cb0c1fd2c1c7d2ec211f
Ancestor: 122c09fb418ceb629bb942da06d3a45a22f19680
Author: darkrain42 at pidgin.im
Date: 2009-11-07T06:10:17
Branch: im.pidgin.cpw.darkrain42.xmpp.scram
URL: http://d.pidgin.im/viewmtn/revision/info/efd6615fa0dbcbc47776cb0c1fd2c1c7d2ec211f
Added files:
libpurple/protocols/jabber/auth_cyrus.c
libpurple/protocols/jabber/auth_digest_md5.c
libpurple/protocols/jabber/auth_plain.c
Modified files:
configure.ac libpurple/protocols/jabber/Makefile.am
libpurple/protocols/jabber/auth.c
libpurple/protocols/jabber/auth.h
libpurple/protocols/jabber/jabber.c
libpurple/protocols/jabber/jabber.h
libpurple/protocols/jabber/parser.c
ChangeLog:
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).
-------------- next part --------------
============================================================
--- libpurple/protocols/jabber/auth_cyrus.c dbaf0f12a1caebff4f533df3235701aa4f6591ef
+++ libpurple/protocols/jabber/auth_cyrus.c dbaf0f12a1caebff4f533df3235701aa4f6591ef
@@ -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;
+}
============================================================
--- libpurple/protocols/jabber/auth_digest_md5.c 814134a6845f0328921cce6d3bd5fac955978634
+++ libpurple/protocols/jabber/auth_digest_md5.c 814134a6845f0328921cce6d3bd5fac955978634
@@ -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;
+}
============================================================
--- libpurple/protocols/jabber/auth_plain.c d12183f7cb820d0d90a9453f7a6b8abcc6f0a28f
+++ libpurple/protocols/jabber/auth_plain.c d12183f7cb820d0d90a9453f7a6b8abcc6f0a28f
@@ -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;
+}
============================================================
--- configure.ac fa1f3965da85c53506d37b600c68e3177a2c92ec
+++ configure.ac 5b16707c3795994a54b55fd3ef69c01e233d768f
@@ -2264,11 +2264,15 @@ if test "x$enable_cyrus_sasl" = "xyes" ;
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 #######################################################################
============================================================
--- libpurple/protocols/jabber/Makefile.am 94e91143750f6fa784291b1a93b1370ed949b7db
+++ libpurple/protocols/jabber/Makefile.am 2293d1e5cc6384a16aa034736295f5005c12cfb8
@@ -7,6 +7,8 @@ JABBERSOURCES = auth.c \
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-vers
libxmpp_la_LDFLAGS = -module -avoid-version
+if USE_CYRUS_SASL
+JABBERSOURCES += auth_cyrus.c
+endif
+
if STATIC_JABBER
st = -DPURPLE_STATIC_PRPL
============================================================
--- libpurple/protocols/jabber/auth.c 43129a119a0ffdf70c82f0ca06ae4d257f81a175
+++ libpurple/protocols/jabber/auth.c e6d71cbd8154684c68f1ca1915f0f5d6faf59f55
@@ -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_authenticat
static void finish_plaintext_authentication(JabberStream *js)
{
- if(js->auth_type == JABBER_AUTH_PLAIN) {
- xmlnode *auth;
- GString *response;
- gchar *enc_out;
+ JabberIq *iq;
+ xmlnode *query, *x;
- 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);
-
- 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 @@ static void disallow_plaintext_auth(Purp
_("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)
+static void
+auth_old_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
{
- JabberStream *js = (JabberStream *)ctx;
+ PurpleAccount *account;
+ JabberStream *js;
+ const char *entry;
+ gboolean remember;
- if (id != SASL_CB_GETREALM || !result) return SASL_BADPARAM;
+ /* The password prompt dialog doesn't get disposed if the account disconnects */
+ if (!PURPLE_CONNECTION_IS_VALID(gc))
+ return;
- *result = js->user->domain;
+ account = purple_connection_get_account(gc);
+ js = purple_connection_get_protocol_data(gc);
- return SASL_OK;
-}
-
-static int jabber_sasl_cb_simple(void *ctx, int id, const char **res, unsigned *len)
-{
- 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)
-{
- const char *entry;
- gboolean remember;
-
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_remember_password(account, TRUE);
- purple_account_set_password(js->gc->account, entry);
+ purple_account_set_password(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;
-
- /* 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;
-
- /* Restart our connection */
jabber_auth_start_old(js);
}
-
static void
auth_no_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields)
{
@@ -271,226 +150,20 @@ auth_no_pass_cb(PurpleConnection *conn,
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 @@ jabber_auth_start(JabberStream *js, xmln
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")) {
+
+ if (mech_name && *mech_name)
+ mechanisms = g_slist_prepend(mechanisms, mech_name);
+ else if (mech_name)
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);
}
-#ifdef HAVE_CYRUS_SASL
- js->auth_type = JABBER_AUTH_CYRUS;
+ for (l = auth_mechs; l; l = l->next) {
+ JabberSaslMech *possible = l->data;
- jabber_sasl_build_callbacks(js);
+ /* Is this the Cyrus SASL mechanism? */
+ if (g_str_equal(possible->name, "*")) {
+ js->auth_mech = possible;
+ break;
+ }
- jabber_auth_start_cyrus(js);
-#else
+ /* 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(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");
-
- jabber_send(js, auth);
- xmlnode_free(auth);
- } else if(plain) {
- js->auth_type = JABBER_AUTH_PLAIN;
-
- 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 @@ void jabber_auth_start_old(JabberStream
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,31 @@ void jabber_auth_start_old(JabberStream
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)
{
+ const char *ns = xmlnode_get_namespace(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);
+ 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;
}
-#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);
- }
+ 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);
}
- }
-#endif
+ } else
+ purple_debug_warning("jabber", "Received unexpected (and unhandled) <challenge/>\n");
}
void jabber_auth_handle_success(JabberStream *js, xmlnode *packet)
{
const char *ns = xmlnode_get_namespace(packet);
-#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 +419,10 @@ void jabber_auth_handle_success(JabberSt
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);
-
- 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_if_reached();
- }
+ if (js->auth_mech && js->auth_mech->handle_success &&
+ !js->auth_mech->handle_success(js, packet)) {
+ return;
}
- /* If we've negotiated a security layer, we need to enable it */
- if (js->sasl) {
- sasl_getprop(js->sasl, SASL_SSF, &x);
- if (*(int *)x > 0) {
- sasl_getprop(js->sasl, SASL_MAXOUTBUF, &x);
- js->sasl_maxbuf = *(int *)x;
- }
- }
-#endif
/*
* The stream will be reinitialized later in jabber_recv_cb_ssl() or
@@ -1097,27 +437,15 @@ void jabber_auth_handle_failure(JabberSt
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 @@ void jabber_auth_handle_failure(JabberSt
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;
+}
============================================================
--- libpurple/protocols/jabber/auth.h d516864e95e3dff0c5191cf2f2eacf315605eda5
+++ libpurple/protocols/jabber/auth.h f2caea0def47f7be781c6c70ed5d98c0f257bbab
@@ -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_failure(JabberSt
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_ */
============================================================
--- libpurple/protocols/jabber/jabber.c 9861db333983ecf98713755a830bd9aa5e004514
+++ libpurple/protocols/jabber/jabber.c 3380fe836fd52ecaf4df0f18df3a9ff870a5b712
@@ -233,8 +233,8 @@ void jabber_stream_features_parse(Jabber
* 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 @@ void jabber_stream_set_state(JabberStrea
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_init_plugin(PurplePlugin *plugin)
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 @@ jabber_uninit_plugin(PurplePlugin *plugi
{
purple_plugin_ipc_unregister_all(plugin);
+ jabber_auth_uninit();
jabber_features_destroy();
jabber_identities_destroy();
}
============================================================
--- libpurple/protocols/jabber/jabber.h ac103a30f7f417d8d67656982049ecde5f6cf545
+++ libpurple/protocols/jabber/jabber.h 6b8715d89aabdb10a06e50612fe8ebc6ae91ff98
@@ -64,6 +64,7 @@ typedef struct _JabberStream JabberStrea
#include "roomlist.h"
#include "sslconn.h"
+#include "auth.h"
#include "iq.h"
#include "jutil.h"
#include "xmlnode.h"
@@ -104,13 +105,8 @@ struct _JabberStream
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;
============================================================
--- libpurple/protocols/jabber/parser.c e6fe39f4e3b5b3e51da205ae37873758f53dc2e3
+++ libpurple/protocols/jabber/parser.c fc3d2a785aa950b7f7ad1620b0a7e100f8b36fa9
@@ -260,8 +260,8 @@ void jabber_parser_process(JabberStream
* 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);
}
}
More information about the Commits
mailing list