/* * 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 #include #include "auth.h" #include "disco.h" #include "jabber.h" #include "jutil.h" #include "iq.h" static GSList *auth_mechs = NULL; static void auth_old_result_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, PurpleXmlNode *packet, gpointer data); static void finish_plaintext_authentication(JabberStream *js) { JabberIq *iq; PurpleXmlNode *query, *x; iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth"); query = purple_xmlnode_get_child(iq->node, "query"); x = purple_xmlnode_new_child(query, "username"); purple_xmlnode_insert_data(x, js->user->node, -1); x = purple_xmlnode_new_child(query, "resource"); purple_xmlnode_insert_data(x, js->user->resource, -1); x = purple_xmlnode_new_child(query, "password"); purple_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); 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(purple_account_get_connection(account), PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, _("Server requires plaintext authentication over an unencrypted stream")); } static void auth_old_pass_cb(PurpleConnection *gc, PurpleRequestPage *page) { PurpleAccount *account; JabberStream *js; const char *entry; gboolean remember; /* TODO: the password prompt dialog doesn't get disposed if the account disconnects */ PURPLE_ASSERT_CONNECTION_IS_VALID(gc); account = purple_connection_get_account(gc); js = purple_connection_get_protocol_data(gc); entry = purple_request_page_get_string(page, "password"); remember = purple_request_page_get_bool(page, "remember"); if (!entry || !*entry) { purple_notify_error(account, NULL, _("Password is required to sign on."), NULL, purple_request_cpar_from_connection(gc)); return; } if(remember) { PurpleCredentialManager *manager = NULL; purple_account_set_remember_password(account, TRUE); manager = purple_credential_manager_get_default(); purple_credential_manager_write_password_async(manager, account, entry, NULL, NULL, NULL); } /* Store the new password in our connection. */ purple_connection_set_password(gc, entry); /* Restart our connection. */ jabber_auth_start_old(js); } static void auth_no_pass_cb(PurpleConnection *gc, G_GNUC_UNUSED PurpleRequestPage *page) { /* TODO: the password prompt dialog doesn't get disposed if the account disconnects */ PURPLE_ASSERT_CONNECTION_IS_VALID(gc); /* Disable the account as the user has cancelled connecting */ purple_account_set_enabled(purple_connection_get_account(gc), FALSE); } static void auth_old_read_pass_cb(GObject *source, GAsyncResult *result, gpointer data) { JabberStream *js = data; PurpleCredentialManager *manager = PURPLE_CREDENTIAL_MANAGER(source); GError *error = NULL; char *password = NULL; password = purple_credential_manager_read_password_finish(manager, result, &error); if(password == NULL || error != NULL) { PurpleAccount *account = NULL; const char *message = "unknown error"; if(error != NULL) { message = error->message; } purple_debug_warning("jabber", "failed to read password from the " "credential manager : %s", message); g_clear_error(&error); account = purple_connection_get_account(js->gc); purple_account_request_password(account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc); return; } /* Save the password in the connection. */ purple_connection_set_password(js->gc, password); purple_str_wipe(password); /* Restart the authentication process. */ jabber_auth_start_old(js); } void jabber_auth_start(JabberStream *js, PurpleXmlNode *packet) { GSList *mechanisms = NULL; GSList *l; PurpleXmlNode *response = NULL; PurpleXmlNode *mechs, *mechnode; JabberSaslState state; char *msg = NULL; mechs = purple_xmlnode_get_child(packet, "mechanisms"); if(!mechs) { purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Invalid response from server")); return; } for(mechnode = purple_xmlnode_get_child(mechs, "mechanism"); mechnode; mechnode = purple_xmlnode_get_next_twin(mechnode)) { char *mech_name = purple_xmlnode_get_data(mechnode); if (mech_name && *mech_name) mechanisms = g_slist_prepend(mechanisms, mech_name); else g_free(mech_name); } for (l = auth_mechs; l; l = l->next) { JabberSaslMech *possible = l->data; /* 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; } } g_slist_free_full(mechanisms, g_free); if (js->auth_mech == NULL) { /* Found no good mechanisms... */ purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, _("Server does not use any supported authentication method")); return; } state = js->auth_mech->start(js, mechs, &response, &msg); if (state == JABBER_SASL_STATE_FAIL) { purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, msg ? msg : _("Unknown Error")); } else if (response) { jabber_send(js, response); purple_xmlnode_free(response); } g_free(msg); } static void auth_old_result_cb(JabberStream *js, G_GNUC_UNUSED const char *from, JabberIqType type, G_GNUC_UNUSED const char *id, PurpleXmlNode *packet, G_GNUC_UNUSED gpointer data) { if (type == JABBER_IQ_RESULT) { 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); PurpleXmlNode *error; const char *err_code; account = purple_connection_get_account(js->gc); /* FIXME: Why is this not in jabber_parse_error? */ if((error = purple_xmlnode_get_child(packet, "error")) && (err_code = purple_xmlnode_get_attrib(error, "code")) && purple_strequal(err_code, "401")) { reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; /* Clear the password if it isn't being saved */ if(!purple_account_get_remember_password(account)) { PurpleCredentialManager *manager = NULL; manager = purple_credential_manager_get_default(); purple_credential_manager_clear_password_async(manager, account, NULL, NULL, NULL); } } purple_connection_error(js->gc, reason, msg); g_free(msg); } } static void auth_old_cb(JabberStream *js, G_GNUC_UNUSED const char *from, JabberIqType type, G_GNUC_UNUSED const char *id, PurpleXmlNode *packet, G_GNUC_UNUSED gpointer data) { JabberIq *iq; PurpleXmlNode *query, *x; const char *pw = purple_connection_get_password(js->gc); if (type == JABBER_IQ_ERROR) { PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; char *msg = jabber_parse_error(js, packet, &reason); purple_connection_error(js->gc, reason, msg); g_free(msg); } else if (type == JABBER_IQ_RESULT) { query = purple_xmlnode_get_child(packet, "query"); if (js->stream_id && *js->stream_id && purple_xmlnode_get_child(query, "digest")) { char *s, *hash; iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth"); query = purple_xmlnode_get_child(iq->node, "query"); x = purple_xmlnode_new_child(query, "username"); purple_xmlnode_insert_data(x, js->user->node, -1); x = purple_xmlnode_new_child(query, "resource"); purple_xmlnode_insert_data(x, js->user->resource, -1); x = purple_xmlnode_new_child(query, "digest"); s = g_strdup_printf("%s%s", js->stream_id, pw); hash = g_compute_checksum_for_string(G_CHECKSUM_SHA1, s, -1); purple_xmlnode_insert_data(x, hash, -1); g_free(hash); g_free(s); jabber_iq_set_callback(iq, auth_old_result_cb, NULL); jabber_iq_send(iq); } else if ((x = purple_xmlnode_get_child(query, "crammd5"))) { /* For future reference, this appears to be a custom OS X extension * to non-SASL authentication. */ const char *challenge; gchar *digest; /* Calculate the MHAC-MD5 digest */ challenge = purple_xmlnode_get_attrib(x, "challenge"); digest = g_compute_hmac_for_string(G_CHECKSUM_MD5, (guchar *)pw, strlen(pw), challenge, -1); g_return_if_fail(digest != NULL); /* Create the response query */ iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth"); query = purple_xmlnode_get_child(iq->node, "query"); x = purple_xmlnode_new_child(query, "username"); purple_xmlnode_insert_data(x, js->user->node, -1); x = purple_xmlnode_new_child(query, "resource"); purple_xmlnode_insert_data(x, js->user->resource, -1); x = purple_xmlnode_new_child(query, "crammd5"); purple_xmlnode_insert_data(x, digest, 32); g_free(digest); jabber_iq_set_callback(iq, auth_old_result_cb, NULL); jabber_iq_send(iq); } else if(purple_xmlnode_get_child(query, "password")) { PurpleAccount *account = purple_connection_get_account(js->gc); if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(account, "auth_plain_in_clear", FALSE)) { PurpleContactInfo *info = PURPLE_CONTACT_INFO(account); char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"), purple_contact_info_get_username(info)); purple_request_yes_no(js->gc, _("Plaintext Authentication"), _("Plaintext Authentication"), msg, 1, purple_request_cpar_from_account(account), account, allow_plaintext_auth, disallow_plaintext_auth); g_free(msg); return; } finish_plaintext_authentication(js); } else { purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, _("Server does not use any supported authentication method")); return; } } } void jabber_auth_start_old(JabberStream *js) { PurpleAccount *account; JabberIq *iq; PurpleXmlNode *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_strequal("require_tls", purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) { purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, _("You require encryption, but it is not available on this server.")); 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 * iChat server just fails silently. */ if (!js->user->resource || *js->user->resource == '\0') { g_free(js->user->resource); js->user->resource = g_strdup("Home"); } /* With Cyrus SASL, passwords are optional for this protocol. So, we need to * do our own password prompting here */ if (!purple_connection_get_password(js->gc)) { PurpleCredentialManager *manager = NULL; manager = purple_credential_manager_get_default(); purple_credential_manager_read_password_async(manager, account, NULL, auth_old_read_pass_cb, js); return; } iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:auth"); query = purple_xmlnode_get_child(iq->node, "query"); username = purple_xmlnode_new_child(query, "username"); purple_xmlnode_insert_data(username, js->user->node, -1); jabber_iq_set_callback(iq, auth_old_cb, NULL); jabber_iq_send(iq); } void jabber_auth_handle_challenge(JabberStream *js, PurpleXmlNode *packet) { const char *ns = purple_xmlnode_get_namespace(packet); if (!purple_strequal(ns, NS_XMPP_SASL)) { purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Invalid response from server")); return; } if (js->auth_mech && js->auth_mech->handle_challenge) { PurpleXmlNode *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(js->gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, msg ? msg : _("Invalid challenge from server")); } else if (response) { jabber_send(js, response); purple_xmlnode_free(response); } g_free(msg); } else purple_debug_warning("jabber", "Received unexpected (and unhandled) \n"); } void jabber_auth_handle_success(JabberStream *js, PurpleXmlNode *packet) { const char *ns = purple_xmlnode_get_namespace(packet); if (!purple_strequal(ns, NS_XMPP_SASL)) { purple_connection_error(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 (state == JABBER_SASL_STATE_FAIL) { purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, msg ? msg : _("Invalid response from server")); return; } else if (state == JABBER_SASL_STATE_CONTINUE) { purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, msg ? msg : _("Server thinks authentication is complete, but client does not")); return; } g_free(msg); } /* * The stream will be reinitialized later in jabber_recv_cb_ssl() or * jabber_bosh_connection_send. */ js->reinit = TRUE; jabber_stream_set_state(js, JABBER_STREAM_POST_AUTH); } void jabber_auth_handle_failure(JabberStream *js, PurpleXmlNode *packet) { PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; char *msg = NULL; if (js->auth_mech && js->auth_mech->handle_failure) { PurpleXmlNode *stanza = NULL; JabberSaslState state = js->auth_mech->handle_failure(js, packet, &stanza, &msg); if (state != JABBER_SASL_STATE_FAIL) { if (stanza) { jabber_send(js, stanza); purple_xmlnode_free(stanza); } return; } } if (!msg) msg = jabber_parse_error(js, packet, &reason); if (!msg) { purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Invalid response from server")); } else { purple_connection_error(js->gc, reason, msg); 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; } static void jabber_auth_add_mech(JabberSaslMech *mech) { auth_mechs = g_slist_insert_sorted(auth_mechs, mech, compare_mech); } void jabber_auth_init(void) { JabberSaslMech **tmp; gint count, i; jabber_auth_add_mech(jabber_auth_get_plain_mech()); jabber_auth_add_mech(jabber_auth_get_digest_md5_mech()); #ifdef HAVE_WEBEX_TOKEN jabber_auth_add_mech(jabber_auth_get_webex_token_mech()); #endif tmp = jabber_auth_get_scram_mechs(&count); for (i = 0; i < count; ++i) jabber_auth_add_mech(tmp[i]); } void jabber_auth_uninit(void) { g_slist_free(auth_mechs); auth_mechs = NULL; }