diff -r 3df88daabac7 -r ddf17c6b7fed libpurple/protocols/jabber/auth.c
--- a/libpurple/protocols/jabber/auth.c Mon Nov 30 06:24:32 2009 +0000
+++ b/libpurple/protocols/jabber/auth.c Wed Dec 09 03:52:00 2009 +0000
@@ -39,6 +39,8 @@
#include "iq.h"
#include "notify.h"
+static GSList *auth_mechs = NULL;
+
static void auth_old_result_cb(JabberStream *js, const char *from,
JabberIqType type, const char *id,
xmlnode *packet, gpointer data);
@@ -46,8 +48,11 @@
gboolean
jabber_process_starttls(JabberStream *js, xmlnode *packet)
{
+ PurpleAccount *account;
xmlnode *starttls;
+ account = purple_connection_get_account(js->gc);
+
if((starttls = xmlnode_get_child(packet, "starttls"))) {
if(purple_ssl_is_supported()) {
jabber_send_raw(js,
@@ -58,7 +63,7 @@
PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
_("Server requires TLS/SSL, but no TLS/SSL support was found."));
return TRUE;
- } else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) {
+ } else if(purple_account_get_bool(account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) {
purple_connection_error_reason(js->gc,
PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
_("You require encryption, but no TLS/SSL support was found."));
@@ -71,418 +76,96 @@
static void finish_plaintext_authentication(JabberStream *js)
{
- if(js->auth_type == JABBER_AUTH_PLAIN) {
- xmlnode *auth;
- GString *response;
- gchar *enc_out;
-
- auth = xmlnode_new("auth");
- xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl");
-
- xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth");
- xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true");
-
- response = g_string_new("");
- response = g_string_append_len(response, "\0", 1);
- response = g_string_append(response, js->user->node);
- response = g_string_append_len(response, "\0", 1);
- response = g_string_append(response,
- purple_connection_get_password(js->gc));
-
- enc_out = purple_base64_encode((guchar *)response->str, response->len);
+ JabberIq *iq;
+ xmlnode *query, *x;
- xmlnode_set_attrib(auth, "mechanism", "PLAIN");
- xmlnode_insert_data(auth, enc_out, -1);
- g_free(enc_out);
- g_string_free(response, TRUE);
-
- jabber_send(js, auth);
- xmlnode_free(auth);
- } else if(js->auth_type == JABBER_AUTH_IQ_AUTH) {
- JabberIq *iq;
- xmlnode *query, *x;
-
- iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
- query = xmlnode_get_child(iq->node, "query");
- x = xmlnode_new_child(query, "username");
- xmlnode_insert_data(x, js->user->node, -1);
- x = xmlnode_new_child(query, "resource");
- xmlnode_insert_data(x, js->user->resource, -1);
- x = xmlnode_new_child(query, "password");
- xmlnode_insert_data(x, purple_connection_get_password(js->gc), -1);
- jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
- jabber_iq_send(iq);
- }
+ iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
+ query = xmlnode_get_child(iq->node, "query");
+ x = xmlnode_new_child(query, "username");
+ xmlnode_insert_data(x, js->user->node, -1);
+ x = xmlnode_new_child(query, "resource");
+ xmlnode_insert_data(x, js->user->resource, -1);
+ x = xmlnode_new_child(query, "password");
+ xmlnode_insert_data(x, purple_connection_get_password(js->gc), -1);
+ jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
+ jabber_iq_send(iq);
}
static void allow_plaintext_auth(PurpleAccount *account)
{
+ PurpleConnection *gc;
+ JabberStream *js;
+
purple_account_set_bool(account, "auth_plain_in_clear", TRUE);
- finish_plaintext_authentication(account->gc->proto_data);
+ gc = purple_account_get_connection(account);
+ js = purple_connection_get_protocol_data(gc);
+
+ finish_plaintext_authentication(js);
}
static void disallow_plaintext_auth(PurpleAccount *account)
{
- purple_connection_error_reason(account->gc,
+ purple_connection_error_reason(purple_account_get_connection(account),
PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
_("Server requires plaintext authentication over an unencrypted stream"));
}
#ifdef HAVE_CYRUS_SASL
-
-static void jabber_auth_start_cyrus(JabberStream *);
-static void jabber_sasl_build_callbacks(JabberStream *);
-
-/* Callbacks for Cyrus SASL */
-
-static int jabber_sasl_cb_realm(void *ctx, int id, const char **avail, const char **result)
-{
- JabberStream *js = (JabberStream *)ctx;
-
- if (id != SASL_CB_GETREALM || !result) return SASL_BADPARAM;
-
- *result = js->user->domain;
-
- return SASL_OK;
-}
-
-static int jabber_sasl_cb_simple(void *ctx, int id, const char **res, unsigned *len)
+static void
+auth_old_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
{
- JabberStream *js = (JabberStream *)ctx;
-
- switch(id) {
- case SASL_CB_AUTHNAME:
- *res = js->user->node;
- break;
- case SASL_CB_USER:
- *res = "";
- break;
- default:
- return SASL_BADPARAM;
- }
- if (len) *len = strlen((char *)*res);
- return SASL_OK;
-}
-
-static int jabber_sasl_cb_secret(sasl_conn_t *conn, void *ctx, int id, sasl_secret_t **secret)
-{
- JabberStream *js = (JabberStream *)ctx;
- const char *pw = purple_account_get_password(js->gc->account);
- size_t len;
- static sasl_secret_t *x = NULL;
-
- if (!conn || !secret || id != SASL_CB_PASS)
- return SASL_BADPARAM;
-
- len = strlen(pw);
- x = (sasl_secret_t *) realloc(x, sizeof(sasl_secret_t) + len);
-
- if (!x)
- return SASL_NOMEM;
-
- x->len = len;
- strcpy((char*)x->data, pw);
-
- *secret = x;
- return SASL_OK;
-}
-
-static void allow_cyrus_plaintext_auth(PurpleAccount *account)
-{
- purple_account_set_bool(account, "auth_plain_in_clear", TRUE);
-
- jabber_auth_start_cyrus(account->gc->proto_data);
-}
-
-static gboolean auth_pass_generic(JabberStream *js, PurpleRequestFields *fields)
-{
+ PurpleAccount *account;
+ JabberStream *js;
const char *entry;
gboolean remember;
+ /* The password prompt dialog doesn't get disposed if the account disconnects */
+ if (!PURPLE_CONNECTION_IS_VALID(gc))
+ return;
+
+ account = purple_connection_get_account(gc);
+ js = purple_connection_get_protocol_data(gc);
+
entry = purple_request_fields_get_string(fields, "password");
remember = purple_request_fields_get_bool(fields, "remember");
if (!entry || !*entry)
{
- purple_notify_error(js->gc->account, NULL, _("Password is required to sign on."), NULL);
- return FALSE;
+ purple_notify_error(account, NULL, _("Password is required to sign on."), NULL);
+ return;
}
if (remember)
- purple_account_set_remember_password(js->gc->account, TRUE);
-
- purple_account_set_password(js->gc->account, entry);
-
- return TRUE;
-}
-
-static void auth_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields)
-{
- JabberStream *js;
-
- /* The password prompt dialog doesn't get disposed if the account disconnects */
- if (!PURPLE_CONNECTION_IS_VALID(conn))
- return;
-
- js = conn->proto_data;
-
- if (!auth_pass_generic(js, fields))
- return;
+ purple_account_set_remember_password(account, TRUE);
- /* Rebuild our callbacks as we now have a password to offer */
- jabber_sasl_build_callbacks(js);
-
- /* Restart our connection */
- jabber_auth_start_cyrus(js);
-}
-
-static void
-auth_old_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields)
-{
- JabberStream *js;
-
- /* The password prompt dialog doesn't get disposed if the account disconnects */
- if (!PURPLE_CONNECTION_IS_VALID(conn))
- return;
-
- js = conn->proto_data;
-
- if (!auth_pass_generic(js, fields))
- return;
+ purple_account_set_password(account, entry);
/* Restart our connection */
jabber_auth_start_old(js);
}
-
static void
-auth_no_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields)
+auth_no_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
{
- JabberStream *js;
-
/* The password prompt dialog doesn't get disposed if the account disconnects */
- if (!PURPLE_CONNECTION_IS_VALID(conn))
+ if (!PURPLE_CONNECTION_IS_VALID(gc))
return;
- js = conn->proto_data;
-
/* Disable the account as the user has canceled connecting */
- purple_account_set_enabled(conn->account, purple_core_get_ui(), FALSE);
+ purple_account_set_enabled(purple_connection_get_account(gc), purple_core_get_ui(), FALSE);
}
-
-static void jabber_auth_start_cyrus(JabberStream *js)
-{
- const char *clientout = NULL;
- char *enc_out;
- unsigned coutlen = 0;
- xmlnode *auth;
- sasl_security_properties_t secprops;
- gboolean again;
- gboolean plaintext = TRUE;
-
- /* Set up security properties and options */
- secprops.min_ssf = 0;
- secprops.security_flags = SASL_SEC_NOANONYMOUS;
-
- if (!jabber_stream_is_ssl(js)) {
- secprops.max_ssf = -1;
- secprops.maxbufsize = 4096;
- plaintext = purple_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE);
- if (!plaintext)
- secprops.security_flags |= SASL_SEC_NOPLAINTEXT;
- } else {
- secprops.max_ssf = 0;
- secprops.maxbufsize = 0;
- plaintext = TRUE;
- }
- secprops.property_names = 0;
- secprops.property_values = 0;
-
- do {
- again = FALSE;
-
- js->sasl_state = sasl_client_new("xmpp", js->serverFQDN, NULL, NULL, js->sasl_cb, 0, &js->sasl);
- if (js->sasl_state==SASL_OK) {
- sasl_setprop(js->sasl, SASL_SEC_PROPS, &secprops);
- purple_debug_info("sasl", "Mechs found: %s\n", js->sasl_mechs->str);
- js->sasl_state = sasl_client_start(js->sasl, js->sasl_mechs->str, NULL, &clientout, &coutlen, &js->current_mech);
- }
- switch (js->sasl_state) {
- /* Success */
- case SASL_OK:
- case SASL_CONTINUE:
- break;
- case SASL_NOMECH:
- /* No mechanisms have offered to help */
-
- /* Firstly, if we don't have a password try
- * to get one
- */
-
- if (!purple_account_get_password(js->gc->account)) {
- purple_account_request_password(js->gc->account, G_CALLBACK(auth_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc);
- return;
-
- /* If we've got a password, but aren't sending
- * it in plaintext, see if we can turn on
- * plaintext auth
- */
- } else if (!plaintext) {
- char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"),
- js->gc->account->username);
- purple_request_yes_no(js->gc, _("Plaintext Authentication"),
- _("Plaintext Authentication"),
- msg,
- 1, js->gc->account, NULL, NULL, js->gc->account,
- allow_cyrus_plaintext_auth,
- disallow_plaintext_auth);
- g_free(msg);
- return;
-
- } else {
- /* We have no mechs which can work.
- * Try falling back on the old jabber:iq:auth method. We get here if the server supports
- * one or more sasl mechs, we are compiled with cyrus-sasl support, but we support or can connect with none of
- * the offerred mechs. jabberd 2.0 w/ SASL and Apple's iChat Server 10.5 both handle and expect
- * jabber:iq:auth in this situation. iChat Server in particular offers SASL GSSAPI by default, which is often
- * not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails.
- *
- * Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However,
- * I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms.
- * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers
- * which would connect without issue otherwise. -evands
- */
- js->auth_type = JABBER_AUTH_IQ_AUTH;
- jabber_auth_start_old(js);
- return;
- }
- /* not reached */
- break;
-
- /* Fatal errors. Give up and go home */
- case SASL_BADPARAM:
- case SASL_NOMEM:
- break;
-
- /* For everything else, fail the mechanism and try again */
- default:
- purple_debug_info("sasl", "sasl_state is %d, failing the mech and trying again\n", js->sasl_state);
-
- /*
- * DAA: is this right?
- * The manpage says that "mech" will contain the chosen mechanism on success.
- * Presumably, if we get here that isn't the case and we shouldn't try again?
- * I suspect that this never happens.
- */
- /*
- * SXW: Yes, this is right. What this handles is the situation where a
- * mechanism, say GSSAPI, is tried. If that mechanism fails, it may be
- * due to mechanism specific issues, so we want to try one of the other
- * supported mechanisms. This code handles that case
- */
- if (js->current_mech && *js->current_mech) {
- char *pos;
- if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) {
- g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech));
- }
- /* Remove space which separated this mech from the next */
- if ((js->sasl_mechs->str)[0] == ' ') {
- g_string_erase(js->sasl_mechs, 0, 1);
- }
- again = TRUE;
- }
-
- sasl_dispose(&js->sasl);
- }
- } while (again);
-
- if (js->sasl_state == SASL_CONTINUE || js->sasl_state == SASL_OK) {
- auth = xmlnode_new("auth");
- xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl");
- xmlnode_set_attrib(auth, "mechanism", js->current_mech);
-
- xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth");
- xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true");
-
- if (clientout) {
- if (coutlen == 0) {
- xmlnode_insert_data(auth, "=", -1);
- } else {
- enc_out = purple_base64_encode((unsigned char*)clientout, coutlen);
- xmlnode_insert_data(auth, enc_out, -1);
- g_free(enc_out);
- }
- }
- jabber_send(js, auth);
- xmlnode_free(auth);
- } else {
- purple_connection_error_reason(js->gc,
- PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
- _("SASL authentication failed"));
- }
-}
-
-static int
-jabber_sasl_cb_log(void *context, int level, const char *message)
-{
- if(level <= SASL_LOG_TRACE)
- purple_debug_info("sasl", "%s\n", message);
-
- return SASL_OK;
-}
-
-void
-jabber_sasl_build_callbacks(JabberStream *js)
-{
- int id;
-
- /* Set up our callbacks structure */
- if (js->sasl_cb == NULL)
- js->sasl_cb = g_new0(sasl_callback_t,6);
-
- id = 0;
- js->sasl_cb[id].id = SASL_CB_GETREALM;
- js->sasl_cb[id].proc = jabber_sasl_cb_realm;
- js->sasl_cb[id].context = (void *)js;
- id++;
-
- js->sasl_cb[id].id = SASL_CB_AUTHNAME;
- js->sasl_cb[id].proc = jabber_sasl_cb_simple;
- js->sasl_cb[id].context = (void *)js;
- id++;
-
- js->sasl_cb[id].id = SASL_CB_USER;
- js->sasl_cb[id].proc = jabber_sasl_cb_simple;
- js->sasl_cb[id].context = (void *)js;
- id++;
-
- if (purple_account_get_password(js->gc->account) != NULL ) {
- js->sasl_cb[id].id = SASL_CB_PASS;
- js->sasl_cb[id].proc = jabber_sasl_cb_secret;
- js->sasl_cb[id].context = (void *)js;
- id++;
- }
-
- js->sasl_cb[id].id = SASL_CB_LOG;
- js->sasl_cb[id].proc = jabber_sasl_cb_log;
- js->sasl_cb[id].context = (void*)js;
- id++;
-
- js->sasl_cb[id].id = SASL_CB_LIST_END;
-}
-
#endif
void
jabber_auth_start(JabberStream *js, xmlnode *packet)
{
-#ifndef HAVE_CYRUS_SASL
- gboolean digest_md5 = FALSE, plain=FALSE;
-#endif
-
+ GSList *mechanisms = NULL;
+ GSList *l;
+ xmlnode *response = NULL;
xmlnode *mechs, *mechnode;
-
+ JabberSaslState state;
+ 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
* 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,
- "",
- -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("%s", 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) \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;
+}