pidgin/pidgin

1896a80ff8e3
Route GLib debug logging directly to the Finch debug window

Instead of flowing through purple debug, this merges some bits of the existing GLib log handler, and the purple debug printer.

Testing Done:
Open the Debug window an see some `GLib-*` outputs.

Reviewed at https://reviews.imfreedom.org/r/1057/
/*
* 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 <config.h>
#include <glib/gi18n-lib.h>
#include <purple.h>
#include "auth.h"
#include "jabber.h"
static JabberSaslState jabber_auth_start_cyrus(JabberStream *js, PurpleXmlNode **reply,
char **error);
static void jabber_sasl_build_callbacks(JabberStream *);
static void disallow_plaintext_auth(PurpleAccount *account)
{
purple_connection_error(purple_account_get_connection(account),
PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
_("Server may require plaintext authentication over an unencrypted stream"));
}
static void start_cyrus_wrapper(JabberStream *js)
{
char *error = NULL;
PurpleXmlNode *response = NULL;
JabberSaslState state = jabber_auth_start_cyrus(js, &response, &error);
if (state == JABBER_SASL_STATE_FAIL) {
purple_connection_error(js->gc,
PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
error);
g_free(error);
} else if (response) {
jabber_send(js, response);
purple_xmlnode_free(response);
}
}
/* 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;
size_t len;
if (!conn || !secret || id != SASL_CB_PASS)
return SASL_BADPARAM;
len = strlen(js->sasl_password);
/* Not an off-by-one because sasl_secret_t defines char data[1] */
/* TODO: This can probably be moved to glib's allocator */
js->sasl_secret = malloc(sizeof(sasl_secret_t) + len);
if (!js->sasl_secret)
return SASL_NOMEM;
js->sasl_secret->len = len;
strcpy((char*)js->sasl_secret->data, js->sasl_password);
*secret = js->sasl_secret;
return SASL_OK;
}
static void allow_cyrus_plaintext_auth(PurpleAccount *account)
{
PurpleConnection *gc;
JabberStream *js;
gc = purple_account_get_connection(account);
js = purple_connection_get_protocol_data(gc);
purple_account_set_bool(account, "auth_plain_in_clear", TRUE);
start_cyrus_wrapper(js);
}
static void auth_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
{
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_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,
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);
}
js->sasl_password = g_strdup(entry);
/* Rebuild our callbacks as we now have a password to offer */
jabber_sasl_build_callbacks(js);
/* Restart our negotiation */
start_cyrus_wrapper(js);
}
static void
auth_no_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
{
PurpleAccount *account;
/* 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);
/* Disable the account as the user has cancelled connecting */
purple_account_set_enabled(account, purple_core_get_ui(), FALSE);
}
static gboolean remove_current_mech(JabberStream *js) {
char *pos;
if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) {
size_t len = strlen(js->current_mech);
/* Clean up space that separated this Mech from the one before or after it */
if (pos > js->sasl_mechs->str && *(pos - 1) == ' ') {
/* Handle removing space before when current_mech isn't the first mech in the list */
pos--;
len++;
} else if (strlen(pos) > len && *(pos + len) == ' ') {
/* Handle removing space after */
len++;
}
g_string_erase(js->sasl_mechs, pos - js->sasl_mechs->str, len);
return TRUE;
}
return FALSE;
}
static JabberSaslState
jabber_auth_start_cyrus(JabberStream *js, PurpleXmlNode **reply, char **error)
{
PurpleAccount *account;
const char *clientout = NULL;
char *enc_out;
unsigned coutlen = 0;
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;
account = purple_connection_get_account(js->gc);
if (!jabber_stream_is_ssl(js)) {
secprops.max_ssf = -1;
secprops.maxbufsize = 4096;
plaintext = purple_account_get_bool(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 (!js->sasl_password) {
purple_account_request_password(account, G_CALLBACK(auth_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc);
return JABBER_SASL_STATE_CONTINUE;
/* If we've got a password, but aren't sending
* it in plaintext, see if we can turn on
* plaintext auth
*/
/* XXX Should we just check for PLAIN/LOGIN being offered mechanisms? */
} else if (!plaintext) {
char *msg = g_strdup_printf(_("%s may require 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, purple_request_cpar_from_account(account), account,
allow_cyrus_plaintext_auth,
disallow_plaintext_auth);
g_free(msg);
return JABBER_SASL_STATE_CONTINUE;
} else
js->auth_fail_count++;
if (js->auth_fail_count == 1 &&
purple_strequal(js->sasl_mechs->str, "GSSAPI")) {
/* If we tried GSSAPI first, it failed, and it was the only method we had to try, try jabber:iq:auth
* for compatibility with iChat 10.5 Server and other jabberd based servers.
*
* iChat Server 10.5 and certain other corporate servers offer 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_mech = NULL;
jabber_auth_start_old(js);
return JABBER_SASL_STATE_CONTINUE;
}
break;
/* Fatal errors. Give up and go home */
case SASL_BADPARAM:
case SASL_NOMEM:
*error = g_strdup(_("SASL authentication failed"));
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);
js->auth_fail_count++;
/*
* 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) {
remove_current_mech(js);
/* Should we only try again if we've removed the mech? */
again = TRUE;
}
sasl_dispose(&js->sasl);
}
} while (again);
if (js->sasl_state == SASL_CONTINUE || js->sasl_state == SASL_OK) {
PurpleXmlNode *auth = purple_xmlnode_new("auth");
purple_xmlnode_set_namespace(auth, NS_XMPP_SASL);
purple_xmlnode_set_attrib(auth, "mechanism", js->current_mech);
if (clientout) {
if (coutlen == 0) {
purple_xmlnode_insert_data(auth, "=", -1);
} else {
enc_out = g_base64_encode((unsigned char*)clientout, coutlen);
purple_xmlnode_insert_data(auth, enc_out, -1);
g_free(enc_out);
}
}
*reply = auth;
return JABBER_SASL_STATE_CONTINUE;
} else {
return JABBER_SASL_STATE_FAIL;
}
}
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 = (void *)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 = (void *)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 = (void *)jabber_sasl_cb_simple;
js->sasl_cb[id].context = (void *)js;
id++;
if (js->sasl_password != NULL) {
js->sasl_cb[id].id = SASL_CB_PASS;
js->sasl_cb[id].proc = (void *)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 = (void *)jabber_sasl_cb_log;
js->sasl_cb[id].context = (void*)js;
id++;
js->sasl_cb[id].id = SASL_CB_LIST_END;
}
static JabberSaslState
jabber_cyrus_start(JabberStream *js, PurpleXmlNode *mechanisms,
PurpleXmlNode **reply, char **error)
{
PurpleXmlNode *mechnode, *hostname;
JabberSaslState ret;
js->sasl_mechs = g_string_new("");
js->sasl_password = g_strdup(purple_connection_get_password(js->gc));
/* XEP-0233 says we should grab the hostname for Kerberos v5, but there
* is no claim about other SASL mechanisms. Fortunately, most don't
* care what we use, so just use the domainpart. */
hostname = purple_xmlnode_get_child_with_namespace(
mechanisms, "hostname", NS_XMPP_SERVER_REGISTRATION);
if (hostname) {
js->serverFQDN = purple_xmlnode_get_data(hostname);
}
if (js->serverFQDN == NULL) {
js->serverFQDN = g_strdup(js->user->domain);
}
for(mechnode = purple_xmlnode_get_child(mechanisms, "mechanism"); mechnode;
mechnode = purple_xmlnode_get_next_twin(mechnode))
{
char *mech_name = purple_xmlnode_get_data(mechnode);
/* Ignore blank mechanisms and EXTERNAL. External isn't
* supported, and Cyrus SASL's mechanism returns
* SASL_NOMECH when the caller (us) doesn't configure it.
* Except SASL_NOMECH is supposed to mean "no concordant
* mechanisms"... Easiest just to blacklist it (for now).
*/
if (!mech_name || !*mech_name ||
purple_strequal(mech_name, "EXTERNAL")) {
g_free(mech_name);
continue;
}
g_string_append(js->sasl_mechs, mech_name);
g_string_append_c(js->sasl_mechs, ' ');
g_free(mech_name);
}
/* Strip off the trailing ' ' */
if (js->sasl_mechs->len > 1)
g_string_truncate(js->sasl_mechs, js->sasl_mechs->len - 1);
jabber_sasl_build_callbacks(js);
ret = jabber_auth_start_cyrus(js, reply, error);
/*
* Triggered if no overlap between server and client
* supported mechanisms.
*/
if (ret == JABBER_SASL_STATE_FAIL && *error == NULL)
*error = g_strdup(_("Server does not use any supported authentication method"));
return ret;
}
static JabberSaslState
jabber_cyrus_handle_challenge(JabberStream *js, PurpleXmlNode *packet,
PurpleXmlNode **reply, char **error)
{
char *enc_in = purple_xmlnode_get_data(packet);
unsigned char *dec_in;
char *enc_out;
const char *c_out;
unsigned int clen;
gsize declen;
dec_in = g_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));
*error = tmp;
return JABBER_SASL_STATE_FAIL;
} else {
PurpleXmlNode *response = purple_xmlnode_new("response");
purple_xmlnode_set_namespace(response, 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 = g_base64_encode((unsigned char*)c_out, clen);
else {
char *tmp = g_strdup_printf("%s,charset=utf-8", c_out);
enc_out = g_base64_encode((unsigned char*)tmp, clen + 14);
g_free(tmp);
}
purple_xmlnode_insert_data(response, enc_out, -1);
g_free(enc_out);
}
*reply = response;
return JABBER_SASL_STATE_CONTINUE;
}
}
static JabberSaslState
jabber_cyrus_handle_success(JabberStream *js, PurpleXmlNode *packet,
char **error)
{
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 = purple_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 = g_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 happens when the server sends back jibberish
* in the "additional data with success" case.
* Seen with Wildfire 3.0.1.
*/
*error = g_strdup(_("Invalid response from server"));
return JABBER_SASL_STATE_FAIL;
}
}
/* 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 JABBER_SASL_STATE_OK;
}
static JabberSaslState
jabber_cyrus_handle_failure(JabberStream *js, PurpleXmlNode *packet,
PurpleXmlNode **reply, char **error)
{
if (js->auth_fail_count++ < 5) {
if (js->current_mech && *js->current_mech) {
remove_current_mech(js);
}
/* Should we only try again if we've actually removed a mech? */
if (*js->sasl_mechs->str) {
/* If we have remaining mechs to try, do so */
sasl_dispose(&js->sasl);
return jabber_auth_start_cyrus(js, reply, error);
} else if ((js->auth_fail_count == 1) &&
purple_strequal(js->current_mech, "GSSAPI")) {
/* If we tried GSSAPI first, it failed, and it was the only method we had to try, try jabber:iq:auth
* for compatibility with iChat 10.5 Server and other jabberd based servers.
*
* iChat Server 10.5 and certain other corporate servers offer 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
*/
sasl_dispose(&js->sasl);
js->sasl = NULL;
js->auth_mech = NULL;
jabber_auth_start_old(js);
return JABBER_SASL_STATE_CONTINUE;
}
}
/* Nothing to send */
return JABBER_SASL_STATE_FAIL;
}
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;
}