diff -r 000000000000 -r d0f3a028347a loudmouth/src/lm-sasl.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/loudmouth/src/lm-sasl.c Tue Feb 02 01:10:06 2010 +0200 @@ -0,0 +1,876 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007 Collabora Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + +//hack - to get over compilation error +#include +#include "lm-sock.h" +#include "lm-debug.h" +#include "lm-error.h" +#include "lm-internals.h" +#include "lm-message-queue.h" +#include "lm-misc.h" +#include "lm-ssl-internals.h" +#include "lm-parser.h" +#include "lm-sha.h" +#include "lm-connection.h" +#include "lm-utils.h" +#include "lm-socket.h" +#include "lm-sasl.h" + +#include "md5.h" +#include "base64.h" + +typedef enum { + AUTH_TYPE_PLAIN = 1, + AUTH_TYPE_DIGEST = 2 +} AuthType; + +typedef enum { + SASL_AUTH_STATE_NO_MECH, + SASL_AUTH_STATE_PLAIN_STARTED, + SASL_AUTH_STATE_DIGEST_MD5_STARTED, + SASL_AUTH_STATE_DIGEST_MD5_SENT_AUTH_RESPONSE, + SASL_AUTH_STATE_DIGEST_MD5_SENT_FINAL_RESPONSE, +} SaslAuthState; + +struct _LmSASL { + LmConnection *connection; + AuthType auth_type; + SaslAuthState state; + gchar *username; + gchar *password; + gchar *server; + gchar *digest_md5_rspauth; + LmMessageHandler *features_cb; + LmMessageHandler *challenge_cb; + LmMessageHandler *success_cb; + LmMessageHandler *failure_cb; + + gboolean features_received; + gboolean start_auth; + + LmSASLResultHandler handler; +}; + +#define XMPP_NS_SASL_AUTH "urn:ietf:params:xml:ns:xmpp-sasl" + +static LmHandlerResult sasl_features_cb (LmMessageHandler *handler, + LmConnection *connection, + LmMessage *message, + gpointer user_data); + +static LmHandlerResult sasl_challenge_cb (LmMessageHandler *handler, + LmConnection *connection, + LmMessage *message, + gpointer user_data); + +static LmHandlerResult sasl_success_cb (LmMessageHandler *handler, + LmConnection *connection, + LmMessage *message, + gpointer user_data); + +static LmHandlerResult sasl_failure_cb (LmMessageHandler *handler, + LmConnection *connection, + LmMessage *message, + gpointer user_data); + + +/* DIGEST-MD5 mechanism code from libgibber */ + +static gchar * +sasl_strndup_unescaped (const gchar *str, gsize len) +{ + const gchar *s; + gchar *d; + gchar *ret; + + ret = g_malloc0 (len + 1); + for (s = str, d = ret ; s < (str + len) ; s++, d++) { + if (*s == '\\') s++; + *d = *s; + } + + return ret; +} + +static GHashTable * +sasl_digest_md5_challenge_to_hash (const gchar * challenge) +{ + const gchar *keystart, *keyend, *valstart; + const gchar *c = challenge; + gchar *key, *val; + GHashTable *result; + + result = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + + do { + while (g_ascii_isspace(*c)) c++; + + keystart = c; + for (; *c != '\0' && *c != '='; c++); + + if (*c == '\0' || c == keystart) goto error; + + keyend = c; + c++; + + if (*c == '"') { + c++; + valstart = c; + for (; *c != '\0' && *c != '"'; c++); + if (*c == '\0' || c == valstart) goto error; + val = sasl_strndup_unescaped (valstart, c - valstart); + c++; + } else { + valstart = c; + for (; *c != '\0' && *c != ','; c++); + if (c == valstart) goto error; + val = g_strndup (valstart, c - valstart); + } + + key = g_strndup (keystart, keyend - keystart); + + g_hash_table_insert (result, key, val); + + if (*c == ',') c++; + } while (*c != '\0'); + + return result; +error: + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, + "Failed to parse challenge: %s", challenge);*/ + lm_verbose("[sasl_digest_md5_challenge_to_hash]:Failed to parse challenge: %s", challenge); + g_hash_table_destroy (result); + return NULL; +} + +static gchar * +sasl_md5_hex_hash (gchar *value, gsize len) +{ + md5_byte_t digest_md5[16]; + md5_state_t md5_calc; + GString *str; + int i; + + str = g_string_sized_new (32); + + md5_init (&md5_calc); + md5_append (&md5_calc, (const md5_byte_t *)value, len); + md5_finish (&md5_calc, digest_md5); + + for (i = 0 ; i < 16 ; i++) { + g_string_append_printf (str, "%02x", digest_md5[i]); + } + + return g_string_free (str, FALSE); +} + +static gchar * +sasl_digest_md5_generate_cnonce(void) +{ + /* RFC 2831 recommends the the nonce to be either hexadecimal or base64 with + * at least 64 bits of entropy */ +#define NR 8 + guint32 n[NR]; + int i; + + for (i = 0; i < NR; i++) { + n[i] = g_random_int(); + } + + return base64_encode ((gchar *)n, sizeof(n)); +} + +static gchar * +sasl_md5_prepare_response (LmSASL *sasl, GHashTable *challenge) +{ + GString *response; + const gchar *realm, *nonce; + gchar *a1, *a1h, *a2, *a2h, *kd, *kdh; + gchar *cnonce = NULL; + gchar *tmp; + md5_byte_t digest_md5[16]; + md5_state_t md5_calc; + gsize len; + + response = g_string_new (""); + + if (sasl->username == NULL || sasl->password == NULL) { + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, + "%s: no username or password provided", G_STRFUNC);*/ + lm_verbose("[sasl_md5_prepare_response]: %s: no username or password provided", G_STRFUNC); + if (sasl->handler) { + sasl->handler (sasl, sasl->connection, + FALSE, "no username/password provided"); + } + goto error; + } + + nonce = g_hash_table_lookup (challenge, "nonce"); + if (nonce == NULL || nonce == '\0') { + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, + "%s: server didn't provide a nonce in the challenge", + G_STRFUNC);*/ + lm_verbose("[sasl_md5_prepare_response]: %s: server didn't provide a nonce in the challenge", G_STRFUNC); + if (sasl->handler) { + sasl->handler (sasl, sasl->connection, + FALSE, "server error"); + } + goto error; + } + + cnonce = sasl_digest_md5_generate_cnonce (); + + /* FIXME challenge can contain multiple realms */ + realm = g_hash_table_lookup (challenge, "realm"); + if (realm == NULL) { + realm = sasl->server; + } + + /* FIXME properly escape values */ + g_string_append_printf (response, "username=\"%s\"", sasl->username); + g_string_append_printf (response, ",realm=\"%s\"", realm); + g_string_append_printf (response, ",digest-uri=\"xmpp/%s\"", realm); + g_string_append_printf (response, ",nonce=\"%s\",nc=00000001", nonce); + g_string_append_printf (response, ",cnonce=\"%s\"", cnonce); + /* FIXME should check if auth is in the cop challenge val */ + g_string_append_printf (response, ",qop=auth,charset=utf-8"); + + tmp = g_strdup_printf ("%s:%s:%s", + sasl->username, realm, sasl->password); + md5_init (&md5_calc); + md5_append (&md5_calc, (const md5_byte_t *)tmp, strlen(tmp)); + md5_finish (&md5_calc, digest_md5); + g_free (tmp); + + a1 = g_strdup_printf ("0123456789012345:%s:%s", nonce, cnonce); + len = strlen (a1); + memcpy (a1, digest_md5, 16); + a1h = sasl_md5_hex_hash (a1, len); + + a2 = g_strdup_printf ("AUTHENTICATE:xmpp/%s", realm); + a2h = sasl_md5_hex_hash (a2, strlen(a2)); + + kd = g_strdup_printf ("%s:%s:00000001:%s:auth:%s", + a1h, nonce, cnonce, a2h); + kdh = sasl_md5_hex_hash (kd, strlen(kd)); + g_string_append_printf (response, ",response=%s", kdh); + + g_free (kd); + g_free (kdh); + g_free (a2); + g_free (a2h); + + /* Calculate the response we expect from the server */ + a2 = g_strdup_printf (":xmpp/%s", realm); + a2h = sasl_md5_hex_hash (a2, strlen(a2)); + + kd = g_strdup_printf ("%s:%s:00000001:%s:auth:%s", a1h, nonce, cnonce, a2h); + g_free (sasl->digest_md5_rspauth); + sasl->digest_md5_rspauth = sasl_md5_hex_hash (kd, strlen(kd)); + + g_free (a1); + g_free (a1h); + g_free (a2); + g_free (a2h); + g_free (kd); + +out: + g_free (cnonce); + if (response) { + return g_string_free (response, FALSE); + } else { + return NULL; + } + +error: + g_string_free (response, TRUE); + response = NULL; + goto out; +} + +static gboolean +sasl_digest_md5_send_initial_response (LmSASL *sasl, GHashTable *challenge) +{ + LmMessage *msg; + gchar *response; + gchar *response64; + int result; + + response = sasl_md5_prepare_response(sasl, challenge); + if (response == NULL) { + return FALSE; + } + + response64 = base64_encode ((gchar *)response, strlen(response)); + + msg = lm_message_new (NULL, LM_MESSAGE_TYPE_RESPONSE); + lm_message_node_set_attributes (msg->node, + "xmlns", XMPP_NS_SASL_AUTH, + NULL); + lm_message_node_set_value (msg->node, response64); + + result = lm_connection_send (sasl->connection, msg, NULL); + + g_free (response); + g_free (response64); + lm_message_unref (msg); + + if (!result) { + return FALSE; + } + + sasl->state = SASL_AUTH_STATE_DIGEST_MD5_SENT_AUTH_RESPONSE; + + return TRUE; +} + +static gboolean +sasl_digest_md5_check_server_response(LmSASL *sasl, GHashTable *challenge) +{ + LmMessage *msg; + const gchar *rspauth; + int result; + + rspauth = g_hash_table_lookup (challenge, "rspauth"); + if (rspauth == NULL) { + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, + "%s: server sent an invalid reply (no rspauth)\n", + G_STRFUNC);*/ + lm_verbose("%s: server sent an invalid reply (no rspauth)\n",G_STRFUNC); + + if (sasl->handler) { + sasl->handler (sasl, sasl->connection, + TRUE, "server error"); + } + return FALSE; + } + + if (strcmp (sasl->digest_md5_rspauth, rspauth) != 0) { + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, + "%s: server sent an invalid reply (rspauth not matching)\n", + G_STRFUNC);*/ + lm_verbose("%s: server sent an invalid reply (rspauth not matching)\n", + G_STRFUNC); + + if (sasl->handler) { + sasl->handler (sasl, sasl->connection, + TRUE, "server error"); + } + return FALSE; + } + + msg = lm_message_new (NULL, LM_MESSAGE_TYPE_RESPONSE); + lm_message_node_set_attributes (msg->node, + "xmlns", XMPP_NS_SASL_AUTH, + NULL); + + result = lm_connection_send (sasl->connection, msg, NULL); + lm_message_unref (msg); + + if (!result) { + g_warning ("Failed to send SASL response\n"); + return FALSE; + } + + sasl->state = SASL_AUTH_STATE_DIGEST_MD5_SENT_FINAL_RESPONSE; + + return TRUE; +} + +static gboolean +sasl_digest_md5_handle_challenge (LmSASL *sasl, LmMessageNode *node) +{ + const gchar *encoded; + gchar *challenge; + gsize len; + GHashTable *h; + + encoded = lm_message_node_get_value (node); + if (!encoded) { + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, + "%s: got empty challenge!", G_STRFUNC);*/ + lm_verbose("[sasl_digest_md5_handle_challenge]: %s: got empty challenge!", G_STRFUNC); + return FALSE; + } + + challenge = (gchar *) base64_decode (encoded, &len); + h = sasl_digest_md5_challenge_to_hash (challenge); + g_free(challenge); + + if (!h) { + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, + "%s: server sent an invalid challenge", G_STRFUNC);*/ + lm_verbose("[sasl_digest_md5_handle_challenge]: %s: server sent an invalid challenge", G_STRFUNC); + if (sasl->handler) { + sasl->handler (sasl, sasl->connection, + FALSE, "server error"); + } + return FALSE; + } + + switch (sasl->state) { + case SASL_AUTH_STATE_DIGEST_MD5_STARTED: + sasl_digest_md5_send_initial_response (sasl, h); + break; + case SASL_AUTH_STATE_DIGEST_MD5_SENT_AUTH_RESPONSE: + sasl_digest_md5_check_server_response (sasl, h); + break; + default: + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, + "%s: server sent a challenge at the wrong time", + G_STRFUNC);*/ + lm_verbose( "[sasl_digst_md5_handle_challenge]: %s: server sent a challenge at the wrong time", + G_STRFUNC); + if (sasl->handler) { + sasl->handler (sasl, sasl->connection, + FALSE, "server error"); + } + + return FALSE; + } + + g_hash_table_destroy(h); + + return TRUE; +} + +static LmHandlerResult +sasl_challenge_cb (LmMessageHandler *handler, + LmConnection *connection, + LmMessage *message, + gpointer user_data) +{ + LmSASL *sasl; + const gchar *ns; + + ns = lm_message_node_get_attribute (message->node, "xmlns"); + if (!ns || strcmp (ns, XMPP_NS_SASL_AUTH) != 0) { + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + + sasl = (LmSASL *) user_data; + + switch (sasl->auth_type) { + case AUTH_TYPE_PLAIN: + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, + "%s: server sent challenge for PLAIN mechanism", + G_STRFUNC);*/ + lm_verbose("[sasl_challenge_cb]: %s: server sent challenge for PLAIN mechanism",G_STRFUNC); + + if (sasl->handler) { + sasl->handler (sasl, sasl->connection, + FALSE, "server error"); + } + break; + case AUTH_TYPE_DIGEST: + sasl_digest_md5_handle_challenge (sasl, message->node); + break; + default: + g_warning ("Wrong auth type"); + break; + } + UNUSED_FORMAL_PARAM(handler); + UNUSED_FORMAL_PARAM(connection); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +static LmHandlerResult +sasl_success_cb (LmMessageHandler *handler, + LmConnection *connection, + LmMessage *message, + gpointer user_data) +{ + LmSASL *sasl; + const gchar *ns; + + ns = lm_message_node_get_attribute (message->node, "xmlns"); + if (!ns || strcmp (ns, XMPP_NS_SASL_AUTH) != 0) { + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + + sasl = (LmSASL *) user_data; + + switch (sasl->auth_type) { + case AUTH_TYPE_PLAIN: + if (sasl->state != SASL_AUTH_STATE_PLAIN_STARTED) { + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, + "%s: server sent success before finishing auth", + G_STRFUNC);*/ + lm_verbose("[sasl_success_cb]: %s: server sent success before finishing auth", G_STRFUNC); + if (sasl->handler) { + sasl->handler (sasl, sasl->connection, + FALSE, "server error"); + } + } + break; + case AUTH_TYPE_DIGEST: + if (sasl->state != SASL_AUTH_STATE_DIGEST_MD5_SENT_AUTH_RESPONSE && + sasl->state != SASL_AUTH_STATE_DIGEST_MD5_SENT_FINAL_RESPONSE) { + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, + "%s: server sent success before finishing auth", + G_STRFUNC);*/ + lm_verbose("[sasl_success_cb]: %s: server sent success before finishing auth", G_STRFUNC); + if (sasl->handler) { + sasl->handler (sasl, sasl->connection, + FALSE, "server error"); + } + } + break; + default: + g_warning ("Wrong auth type"); + break; + } + +/* g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, + "%s: SASL authentication successful", G_STRFUNC);*/ + lm_verbose("[sasl_success_cb]: %s: SASL authentication successful", G_STRFUNC); + + if (sasl->handler) { + sasl->handler (sasl, sasl->connection, TRUE, NULL); + } + UNUSED_FORMAL_PARAM(handler); + UNUSED_FORMAL_PARAM(connection); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + +} + +static LmHandlerResult +sasl_failure_cb (LmMessageHandler *handler, + LmConnection *connection, + LmMessage *message, + gpointer user_data) +{ + LmSASL *sasl; + const gchar *ns; + const gchar *reason = "unknown reason"; + lm_verbose("[sasl_failure_cb]: inside sasl_failure_cb\n"); + ns = lm_message_node_get_attribute (message->node, "xmlns"); + if (!ns || strcmp (ns, XMPP_NS_SASL_AUTH) != 0) { + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + + sasl = (LmSASL *) user_data; + + if (message->node->children) { + const gchar *r; + + r = lm_message_node_get_value (message->node->children); + if (r) { + reason = r; + } + } + lm_verbose("[sasl_failure_cb]: %s: SASL authentication failed: %s\n", G_STRFUNC, reason); + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, + "%s: SASL authentication failed: %s", G_STRFUNC, reason);*/ + + if (sasl->handler) { + sasl->handler (sasl, sasl->connection, FALSE, reason); + } + UNUSED_FORMAL_PARAM(handler); + UNUSED_FORMAL_PARAM(connection); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + + +static gboolean +sasl_start (LmSASL *sasl) +{ + LmMessage *auth_msg; + gboolean result; + const char *mech = NULL; + + auth_msg = lm_message_new (NULL, LM_MESSAGE_TYPE_AUTH); + lm_verbose("[sasl_start]: inside sasl_start\n"); + if (sasl->auth_type == AUTH_TYPE_PLAIN) { + GString *str; + gchar *cstr; + + str = g_string_new (""); + + mech = "PLAIN"; + sasl->state = SASL_AUTH_STATE_PLAIN_STARTED; + lm_verbose("[sasl_start]: inside sasl_start: auth type is PLAIN\n"); + if (sasl->username == NULL || sasl->password == NULL) { + lm_verbose("[sasl_start]: no username or password provided\n"); + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, + "%s: no username or password provided", + G_STRFUNC);*/ + if (sasl->handler) { + sasl->handler (sasl, sasl->connection, FALSE, "no username/password provided"); + } + + return FALSE; + } + + g_string_append_c (str, '\0'); + g_string_append (str, sasl->username); + g_string_append_c (str, '\0'); + g_string_append (str, sasl->password); + cstr = base64_encode ((gchar *)str->str, str->len); + + lm_message_node_set_value (auth_msg->node, cstr); + + g_string_free (str, TRUE); + g_free (cstr); + + /* Here we say the Google magic word. Bad Google. */ + lm_message_node_set_attributes (auth_msg->node, + "xmlns:ga", "http://www.google.com/talk/protocol/auth", + "ga:client-uses-full-bind-result", "true", + NULL); + + } + else if (sasl->auth_type == AUTH_TYPE_DIGEST) { + mech = "DIGEST-MD5"; + sasl->state = SASL_AUTH_STATE_DIGEST_MD5_STARTED; + } + + lm_message_node_set_attributes (auth_msg->node, + "xmlns", XMPP_NS_SASL_AUTH, + "mechanism", mech, + NULL); + + result = lm_connection_send (sasl->connection, auth_msg, NULL); + lm_message_unref (auth_msg); + + if (!result) { + return FALSE; + } + + return TRUE; +} + +static gboolean +sasl_set_auth_type (LmSASL *sasl, LmMessageNode *mechanisms) +{ + LmMessageNode *m; + const gchar *ns; + + sasl->auth_type = 0; + lm_verbose("[sasl_set_auth_type]: inside sasl_set_auth_type\n"); + ns = lm_message_node_get_attribute (mechanisms, "xmlns"); + if (!ns || strcmp (ns, XMPP_NS_SASL_AUTH) != 0) { + return FALSE; + } + + for (m = mechanisms->children; m; m = m->next) { + const gchar *name; + + name = lm_message_node_get_value (m); + + if (!name) { + continue; + } + if (strcmp (name, "PLAIN") == 0) { + sasl->auth_type |= AUTH_TYPE_PLAIN; + continue; + } + if (strcmp (name, "DIGEST-MD5") == 0) { + sasl->auth_type |= AUTH_TYPE_DIGEST; + continue; + } + + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, + "%s: unknown SASL auth mechanism: %s", G_STRFUNC, name);*/ + lm_verbose ("[sasl_set_auth_type]: %s: unknown SASL auth mechanism: %s", G_STRFUNC, name); + } + + return TRUE; +} + +static gboolean +sasl_authenticate (LmSASL *sasl) +{ + lm_verbose ("[sasl_authenticate]: inside sasl_authenticate\n"); + if (sasl->auth_type == 0) { + /*g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, + "%s: no supported SASL auth mechanisms found", + G_STRFUNC);*/ + lm_verbose ("[sasl_authenticate]: %s: no supported SASL auth mechanisms found",G_STRFUNC); + + return FALSE; + } + + /* Prefer DIGEST */ + if (sasl->auth_type & AUTH_TYPE_DIGEST) { + sasl->auth_type = AUTH_TYPE_DIGEST; + return sasl_start (sasl); + } + else if (sasl->auth_type & AUTH_TYPE_PLAIN) { + sasl->auth_type = AUTH_TYPE_PLAIN; + return sasl_start (sasl); + } + + return FALSE; +} + +static LmHandlerResult +sasl_features_cb (LmMessageHandler *handler, + LmConnection *connection, + LmMessage *message, + gpointer user_data) +{ + LmMessageNode *mechanisms; + LmSASL *sasl; + + lm_verbose ("\n[sasl_feature_cb]: Stream features received\n\n"); + //g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, "Stream features received\n"); + mechanisms = lm_message_node_find_child (message->node, "mechanisms"); + if (!mechanisms) { + gchar* childname = NULL; + lm_message_node_get_child(message->node,childname); + lm_verbose("[sasl_feature_cb]: Child name of stream:features %s\n", message->node->children->name); + lm_verbose("[sasl_feature_cb]: Child pointer of stream:features %u\n", message->node->children); + lm_verbose ("[sasl_feature_cb]: mechanisms empty:LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS \n"); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + lm_verbose("[sasl_features_cb]: Mechanisms not null\n"); + + sasl = (LmSASL *) user_data; + sasl->features_received = TRUE; + + sasl_set_auth_type (sasl, mechanisms); + + if (sasl->start_auth) { + lm_verbose ("[sasl_feature_cb]: Going to call sasl_authenticate\n"); + sasl_authenticate (sasl); + } + UNUSED_FORMAL_PARAM(handler); + UNUSED_FORMAL_PARAM(connection); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; +} + +LmSASL * +lm_sasl_new (LmConnection *connection) +{ + LmSASL *sasl; + + sasl = g_new0 (LmSASL, 1); + + sasl->connection = connection; + sasl->features_received = FALSE; + sasl->start_auth = FALSE; + + sasl->features_cb = lm_message_handler_new (sasl_features_cb, + sasl, + NULL); + lm_verbose ("[lm_sasl_new]: inside \n"); + lm_connection_register_message_handler (connection, + sasl->features_cb, + LM_MESSAGE_TYPE_STREAM_FEATURES, + LM_HANDLER_PRIORITY_LAST); + return sasl; +} + +void +lm_sasl_authenticate (LmSASL *sasl, + const gchar *username, + const gchar *password, + const gchar *server, + LmSASLResultHandler handler) +{ + sasl->username = g_strdup (username); + sasl->password = g_strdup (password); + sasl->server = g_strdup (server); + sasl->handler = handler; + + sasl->challenge_cb = lm_message_handler_new (sasl_challenge_cb, + sasl, + NULL); + lm_connection_register_message_handler (sasl->connection, + sasl->challenge_cb, + LM_MESSAGE_TYPE_CHALLENGE, + LM_HANDLER_PRIORITY_FIRST); + + sasl->success_cb = lm_message_handler_new (sasl_success_cb, + sasl, + NULL); + lm_connection_register_message_handler (sasl->connection, + sasl->success_cb, + LM_MESSAGE_TYPE_SUCCESS, + LM_HANDLER_PRIORITY_FIRST); + + sasl->failure_cb = lm_message_handler_new (sasl_failure_cb, + sasl, + NULL); + lm_connection_register_message_handler (sasl->connection, + sasl->failure_cb, + LM_MESSAGE_TYPE_FAILURE, + LM_HANDLER_PRIORITY_FIRST); + + if (sasl->features_received) { + lm_verbose("[lm_sasl_authenticate]: calling sasl_authenticate\n"); + sasl_authenticate (sasl); + } else { + sasl->start_auth = TRUE; + } +} + +void +lm_sasl_free (LmSASL *sasl) +{ + g_return_if_fail (sasl != NULL); + + g_free (sasl->username); + g_free (sasl->password); + g_free (sasl->server); + + if (sasl->features_cb) { + lm_connection_unregister_message_handler (sasl->connection, + sasl->features_cb, + LM_MESSAGE_TYPE_STREAM_FEATURES); + } + + if (sasl->challenge_cb) { + lm_connection_unregister_message_handler (sasl->connection, + sasl->challenge_cb, + LM_MESSAGE_TYPE_CHALLENGE); + } + + if (sasl->success_cb) { + lm_connection_unregister_message_handler (sasl->connection, + sasl->success_cb, + LM_MESSAGE_TYPE_SUCCESS); + } + + if (sasl->failure_cb) { + lm_connection_unregister_message_handler (sasl->connection, + sasl->failure_cb, + LM_MESSAGE_TYPE_FAILURE); + } + + g_free (sasl); +} + + +void +lm_sasl_get_auth_params (LmSASL *sasl, const gchar **username, + const gchar **password) +{ + *username = sasl->username; + *password = sasl->password; +} +