/* auth.c
** strophe XMPP client library -- auth functions and handlers
**
** Copyright (C) 2005-2009 Collecta, Inc.
**
**  This software is provided AS-IS with no warranty, either express or
**  implied.
**
** This program is dual licensed under the MIT and GPLv3 licenses.
*/

/** @file
 *  Authentication function and handlers.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "strophe.h"
#include "common.h"
#include "sasl.h"
#include "sha1.h"
#include "rand.h"

#ifdef _MSC_VER
#define strcasecmp stricmp
#endif

/* TODO: these should configurable at runtime on a per connection basis  */

#ifndef FEATURES_TIMEOUT
/** @def FEATURES_TIMEOUT
 *  Time to wait for &lt;stream:features/&gt; stanza.
 */
#define FEATURES_TIMEOUT 15000 /* 15 seconds */
#endif
#ifndef BIND_TIMEOUT
/** @def BIND_TIMEOUT
 *  Time to wait for &lt;bind/&gt; stanza reply.
 */
#define BIND_TIMEOUT 15000 /* 15 seconds */
#endif
#ifndef SESSION_TIMEOUT
/** @def SESSION_TIMEOUT
 *  Time to wait for &lt;session/&gt; stanza reply.
 */
#define SESSION_TIMEOUT 15000 /* 15 seconds */
#endif
#ifndef LEGACY_TIMEOUT
/** @def LEGACY_TIMEOUT
 *  Time to wait for legacy authentication to complete.
 */
#define LEGACY_TIMEOUT 15000 /* 15 seconds */
#endif
#ifndef HANDSHAKE_TIMEOUT
/** @def HANDSHAKE_TIMEOUT
 *  Time to wait for component authentication to complete
 */
#define HANDSHAKE_TIMEOUT 15000 /* 15 seconds */
#endif

static void _auth(xmpp_conn_t * const conn);
static void _auth_legacy(xmpp_conn_t *conn);
static void _handle_open_sasl(xmpp_conn_t * const conn);
static void _handle_open_tls(xmpp_conn_t * const conn);

static int _handle_component_auth(xmpp_conn_t * const conn);
static int _handle_component_hs_response(xmpp_conn_t * const conn,
            xmpp_stanza_t * const stanza,
            void * const userdata);

static int _handle_features_sasl(xmpp_conn_t * const conn,
				 xmpp_stanza_t * const stanza,
				 void * const userdata);
static int _handle_sasl_result(xmpp_conn_t * const conn,
			xmpp_stanza_t * const stanza,
			void * const userdata);
static int _handle_digestmd5_challenge(xmpp_conn_t * const conn,
			xmpp_stanza_t * const stanza,
			void * const userdata);
static int _handle_digestmd5_rspauth(xmpp_conn_t * const conn,
			xmpp_stanza_t * const stanza,
			void * const userdata);
static int _handle_scram_sha1_challenge(xmpp_conn_t * const conn,
			xmpp_stanza_t * const stanza,
			void * const userdata);
static char *_make_scram_sha1_init_msg(xmpp_conn_t * const conn);

static int _handle_missing_features_sasl(xmpp_conn_t * const conn,
					 void * const userdata);
static int _handle_missing_bind(xmpp_conn_t * const conn,
				void * const userdata);
static int _handle_bind(xmpp_conn_t * const conn,
			xmpp_stanza_t * const stanza,
			void * const userdata);
static int _handle_session(xmpp_conn_t * const conn,
			   xmpp_stanza_t * const stanza,
			   void * const userdata);
static int _handle_missing_session(xmpp_conn_t * const conn,
				   void * const userdata);
static int _handle_missing_handshake(xmpp_conn_t * const conn,
                                     void * const userdata);

/* stream:error handler */
static int _handle_error(xmpp_conn_t * const conn,
			 xmpp_stanza_t * const stanza,
			 void * const userdata)
{
    xmpp_stanza_t *child;
    const char *name;

    /* free old stream error if it's still there */
    if (conn->stream_error) {
	xmpp_stanza_release(conn->stream_error->stanza);
	if (conn->stream_error->text)
	    xmpp_free(conn->ctx, conn->stream_error->text);
	xmpp_free(conn->ctx, conn->stream_error);
    }

    /* create stream error structure */
    conn->stream_error = (xmpp_stream_error_t *)xmpp_alloc(conn->ctx, sizeof(xmpp_stream_error_t));

	conn->stream_error->text = NULL;
	conn->stream_error->type = XMPP_SE_UNDEFINED_CONDITION;

    if (conn->stream_error) {
	child = xmpp_stanza_get_children(stanza);
	do {
	    const char *ns = NULL;

	    if (child) {
		ns = xmpp_stanza_get_ns(child);
	    }

	    if (ns && strcmp(ns, XMPP_NS_STREAMS_IETF) == 0) {
		name = xmpp_stanza_get_name(child);
		if (strcmp(name, "text") == 0) {
		    if (conn->stream_error->text)
			xmpp_free(conn->ctx, conn->stream_error->text);
		    conn->stream_error->text = xmpp_stanza_get_text(child);
		} else if (strcmp(name, "bad-format") == 0)
		    conn->stream_error->type = XMPP_SE_BAD_FORMAT;
		else if (strcmp(name, "bad-namespace-prefix") == 0)
		    conn->stream_error->type = XMPP_SE_BAD_NS_PREFIX;
		else if (strcmp(name, "conflict") == 0)
		    conn->stream_error->type = XMPP_SE_CONFLICT;
		else if (strcmp(name, "connection-timeout") == 0)
		    conn->stream_error->type = XMPP_SE_CONN_TIMEOUT;
		else if (strcmp(name, "host-gone") == 0)
		    conn->stream_error->type = XMPP_SE_HOST_GONE;
		else if (strcmp(name, "host-unknown") == 0)
		    conn->stream_error->type = XMPP_SE_HOST_UNKNOWN;
		else if (strcmp(name, "improper-addressing") == 0)
		    conn->stream_error->type = XMPP_SE_IMPROPER_ADDR;
		else if (strcmp(name, "internal-server-error") == 0)
		    conn->stream_error->type = XMPP_SE_INTERNAL_SERVER_ERROR;
		else if (strcmp(name, "invalid-from") == 0)
		    conn->stream_error->type = XMPP_SE_INVALID_FROM;
		else if (strcmp(name, "invalid-id") == 0)
		    conn->stream_error->type = XMPP_SE_INVALID_ID;
		else if (strcmp(name, "invalid-namespace") == 0)
		    conn->stream_error->type = XMPP_SE_INVALID_NS;
		else if (strcmp(name, "invalid-xml") == 0)
		    conn->stream_error->type = XMPP_SE_INVALID_XML;
		else if (strcmp(name, "not-authorized") == 0)
		    conn->stream_error->type = XMPP_SE_NOT_AUTHORIZED;
		else if (strcmp(name, "policy-violation") == 0)
		    conn->stream_error->type = XMPP_SE_POLICY_VIOLATION;
		else if (strcmp(name, "remote-connection-failed") == 0)
		    conn->stream_error->type = XMPP_SE_REMOTE_CONN_FAILED;
		else if (strcmp(name, "resource-constraint") == 0)
		    conn->stream_error->type = XMPP_SE_RESOURCE_CONSTRAINT;
		else if (strcmp(name, "restricted-xml") == 0)
		    conn->stream_error->type = XMPP_SE_RESTRICTED_XML;
		else if (strcmp(name, "see-other-host") == 0)
		    conn->stream_error->type = XMPP_SE_SEE_OTHER_HOST;
		else if (strcmp(name, "system-shutdown") == 0)
		    conn->stream_error->type = XMPP_SE_SYSTEM_SHUTDOWN;
		else if (strcmp(name, "undefined-condition") == 0)
		    conn->stream_error->type = XMPP_SE_UNDEFINED_CONDITION;
		else if (strcmp(name, "unsupported-encoding") == 0)
		    conn->stream_error->type = XMPP_SE_UNSUPPORTED_ENCODING;
		else if (strcmp(name, "unsupported-stanza-type") == 0)
		    conn->stream_error->type = XMPP_SE_UNSUPPORTED_STANZA_TYPE;
		else if (strcmp(name, "unsupported-version") == 0)
		    conn->stream_error->type = XMPP_SE_UNSUPPORTED_VERSION;
		else if (strcmp(name, "xml-not-well-formed") == 0)
		    conn->stream_error->type = XMPP_SE_XML_NOT_WELL_FORMED;
	    }
	} while ((child = xmpp_stanza_get_next(child)));

	conn->stream_error->stanza = xmpp_stanza_clone(stanza);
    }

    return 1;
}

/* stream:features handlers */
static int _handle_missing_features(xmpp_conn_t * const conn,
				    void * const userdata)
{
    xmpp_debug(conn->ctx, "xmpp", "didn't get stream features");

    /* legacy auth will be attempted */
    _auth(conn);

    return 0;
}



static int _handle_features(xmpp_conn_t * const conn,
			    xmpp_stanza_t * const stanza,
			    void * const userdata)
{
    xmpp_stanza_t *child, *mech;
    const char *ns;
    char *text;

    /* remove the handler that detects missing stream:features */
    xmpp_timed_handler_delete(conn, _handle_missing_features);

    /* check for TLS */
    if (!conn->secured) {
        if (!conn->tls_disabled) {
            child = xmpp_stanza_get_child_by_name(stanza, "starttls");
            if (child) {
                ns = xmpp_stanza_get_ns(child);
                conn->tls_support = ns != NULL && strcmp(ns, XMPP_NS_TLS) == 0;
            }
        } else {
            conn->tls_support = 0;
        }
    }

    /* check for SASL */
    child = xmpp_stanza_get_child_by_name(stanza, "mechanisms");
    ns = child ? xmpp_stanza_get_ns(child) : NULL;
    if (child && ns && strcmp(ns, XMPP_NS_SASL) == 0) {
	for (mech = xmpp_stanza_get_children(child); mech;
	     mech = xmpp_stanza_get_next(mech)) {
	    if (xmpp_stanza_get_name(mech) && strcmp(xmpp_stanza_get_name(mech), "mechanism") == 0) {
		text = xmpp_stanza_get_text(mech);
                if (text == NULL)
                    continue;

		if (strcasecmp(text, "PLAIN") == 0)
		    conn->sasl_support |= SASL_MASK_PLAIN;
		else if (strcasecmp(text, "DIGEST-MD5") == 0)
		    conn->sasl_support |= SASL_MASK_DIGESTMD5;
                else if (strcasecmp(text, "SCRAM-SHA-1") == 0)
                    conn->sasl_support |= SASL_MASK_SCRAMSHA1;
		else if (strcasecmp(text, "ANONYMOUS") == 0)
		    conn->sasl_support |= SASL_MASK_ANONYMOUS;

		xmpp_free(conn->ctx, text);
	    }
	}
    }

    /* Disable PLAIN when other secure mechanisms are supported */
    if (conn->sasl_support & ~(SASL_MASK_PLAIN | SASL_MASK_ANONYMOUS))
        conn->sasl_support &= ~SASL_MASK_PLAIN;

    _auth(conn);

    return 0;
}

/* returns the correct auth id for a component or a client.
 * returned string must be freed by caller */
static char *_get_authid(xmpp_conn_t * const conn)
{
    char *authid = NULL;

    if (conn->type == XMPP_CLIENT) {
	/* authid is the node portion of jid */
	if (!conn->jid) return NULL;
	authid = xmpp_jid_node(conn->ctx, conn->jid);
    }

    return authid;
}

static int _handle_proceedtls_default(xmpp_conn_t * const conn,
			      xmpp_stanza_t * const stanza,
			      void * const userdata)
{
    const char *name;

    name = xmpp_stanza_get_name(stanza);
    xmpp_debug(conn->ctx, "xmpp", "handle proceedtls called for %s", name);

    if (strcmp(name, "proceed") == 0) {
        xmpp_debug(conn->ctx, "xmpp", "proceeding with TLS");

        if (conn_tls_start(conn) == 0) {
            conn_prepare_reset(conn, _handle_open_tls);
            conn_open_stream(conn);
        } else {
            /* failed tls spoils the connection, so disconnect */
            xmpp_disconnect(conn);
        }
    }

    return 0;
}

static int _handle_sasl_result(xmpp_conn_t * const conn,
			       xmpp_stanza_t * const stanza,
			       void * const userdata)
{
    const char *name;

    name = xmpp_stanza_get_name(stanza);

    /* the server should send a <success> or <failure> stanza */
    if (strcmp(name, "failure") == 0) {
	xmpp_debug(conn->ctx, "xmpp", "SASL %s auth failed",
		   (char *)userdata);

	/* fall back to next auth method */
	_auth(conn);
    } else if (strcmp(name, "success") == 0) {
	/* SASL auth successful, we need to restart the stream */
	xmpp_debug(conn->ctx, "xmpp", "SASL %s auth successful",
		   (char *)userdata);

	/* reset parser */
	conn_prepare_reset(conn, _handle_open_sasl);

	/* send stream tag */
	conn_open_stream(conn);
    } else {
	/* got unexpected reply */
	xmpp_error(conn->ctx, "xmpp", "Got unexpected reply to SASL %s"\
		   "authentication.", (char *)userdata);
	xmpp_disconnect(conn);
    }

    return 0;
}

/* handle the challenge phase of digest auth */
static int _handle_digestmd5_challenge(xmpp_conn_t * const conn,
			      xmpp_stanza_t * const stanza,
			      void * const userdata)
{
    char *text;
    char *response;
    xmpp_stanza_t *auth, *authdata;
    const char *name;

    name = xmpp_stanza_get_name(stanza);
    xmpp_debug(conn->ctx, "xmpp",\
	"handle digest-md5 (challenge) called for %s", name);

    if (strcmp(name, "challenge") == 0) {
	text = xmpp_stanza_get_text(stanza);
	response = sasl_digest_md5(conn->ctx, text, conn->jid, conn->pass);
	if (!response) {
	    disconnect_mem_error(conn);
	    return 0;
	}
	xmpp_free(conn->ctx, text);

	auth = xmpp_stanza_new(conn->ctx);
	if (!auth) {
	    disconnect_mem_error(conn);
	    return 0;
	}
	xmpp_stanza_set_name(auth, "response");
	xmpp_stanza_set_ns(auth, XMPP_NS_SASL);

	authdata = xmpp_stanza_new(conn->ctx);
	if (!authdata) {
	    disconnect_mem_error(conn);
	    return 0;
	}

	xmpp_stanza_set_text(authdata, response);
	xmpp_free(conn->ctx, response);

	xmpp_stanza_add_child(auth, authdata);
	xmpp_stanza_release(authdata);

	handler_add(conn, _handle_digestmd5_rspauth,
		    XMPP_NS_SASL, NULL, NULL, NULL);

	xmpp_send(conn, auth);
	xmpp_stanza_release(auth);

    } else {
	return _handle_sasl_result(conn, stanza, "DIGEST-MD5");
    }

    /* remove ourselves */
    return 0;
}

/* handle the rspauth phase of digest auth */
static int _handle_digestmd5_rspauth(xmpp_conn_t * const conn,
			      xmpp_stanza_t * const stanza,
			      void * const userdata)
{
    xmpp_stanza_t *auth;
    const char *name;

    name = xmpp_stanza_get_name(stanza);
    xmpp_debug(conn->ctx, "xmpp",
	"handle digest-md5 (rspauth) called for %s", name);


    if (strcmp(name, "challenge") == 0) {
	/* assume it's an rspauth response */
	auth = xmpp_stanza_new(conn->ctx);
	if (!auth) {
	    disconnect_mem_error(conn);
	    return 0;
	}
	xmpp_stanza_set_name(auth, "response");
	xmpp_stanza_set_ns(auth, XMPP_NS_SASL);
	xmpp_send(conn, auth);
	xmpp_stanza_release(auth);
    } else {
	return _handle_sasl_result(conn, stanza, "DIGEST-MD5");
    }

    return 1;
}

/* handle the challenge phase of SCRAM-SHA-1 auth */
static int _handle_scram_sha1_challenge(xmpp_conn_t * const conn,
					xmpp_stanza_t * const stanza,
					void * const userdata)
{
    char *text;
    char *response;
    xmpp_stanza_t *auth, *authdata;
    const char *name;
    char *challenge;
    char *scram_init = (char *)userdata;

    name = xmpp_stanza_get_name(stanza);
    xmpp_debug(conn->ctx, "xmpp",
               "handle SCRAM-SHA-1 (challenge) called for %s", name);

    if (strcmp(name, "challenge") == 0) {
        text = xmpp_stanza_get_text(stanza);
        if (!text)
            goto err;

        challenge = xmpp_base64_decode_str(conn->ctx, text, strlen(text));
        xmpp_free(conn->ctx, text);
        if (!challenge)
            goto err;

        response = sasl_scram_sha1(conn->ctx, challenge, scram_init,
                                   conn->jid, conn->pass);
        xmpp_free(conn->ctx, challenge);
        if (!response)
            goto err;

        auth = xmpp_stanza_new(conn->ctx);
        if (!auth)
            goto err_free_response;
        xmpp_stanza_set_name(auth, "response");
        xmpp_stanza_set_ns(auth, XMPP_NS_SASL);

        authdata = xmpp_stanza_new(conn->ctx);
        if (!authdata)
            goto err_release_auth;
        xmpp_stanza_set_text(authdata, response);
        xmpp_free(conn->ctx, response);

        xmpp_stanza_add_child(auth, authdata);
        xmpp_stanza_release(authdata);

        xmpp_send(conn, auth);
        xmpp_stanza_release(auth);

    } else {
        xmpp_free(conn->ctx, scram_init);
        return _handle_sasl_result(conn, stanza, "SCRAM-SHA-1");
    }

    return 1;

err_release_auth:
    xmpp_stanza_release(auth);
err_free_response:
    xmpp_free(conn->ctx, response);
err:
    xmpp_free(conn->ctx, scram_init);
    disconnect_mem_error(conn);
    return 0;
}

static char *_make_scram_sha1_init_msg(xmpp_conn_t * const conn)
{
    xmpp_ctx_t *ctx = conn->ctx;
    size_t message_len;
    char *node;
    char *message;
    char nonce[32];

    node = xmpp_jid_node(ctx, conn->jid);
    if (!node) {
        return NULL;
    }
    xmpp_rand_nonce(ctx->rand, nonce, sizeof(nonce));
    message_len = strlen(node) + strlen(nonce) + 8 + 1;
    message = xmpp_alloc(ctx, message_len);
    if (message) {
        xmpp_snprintf(message, message_len, "n,,n=%s,r=%s", node, nonce);
    }
    xmpp_free(ctx, node);

    return message;
}

static xmpp_stanza_t *_make_starttls(xmpp_conn_t * const conn)
{
    xmpp_stanza_t *starttls;

    /* build start stanza */
    starttls = xmpp_stanza_new(conn->ctx);
    if (starttls) {
	xmpp_stanza_set_name(starttls, "starttls");
	xmpp_stanza_set_ns(starttls, XMPP_NS_TLS);
    }

    return starttls;
}

static xmpp_stanza_t *_make_sasl_auth(xmpp_conn_t * const conn,
				 const char * const mechanism)
{
    xmpp_stanza_t *auth;

    /* build auth stanza */
    auth = xmpp_stanza_new(conn->ctx);
    if (auth) {
	xmpp_stanza_set_name(auth, "auth");
	xmpp_stanza_set_ns(auth, XMPP_NS_SASL);
	xmpp_stanza_set_attribute(auth, "mechanism", mechanism);
    }

    return auth;
}

/* authenticate the connection
 * this may get called multiple times.  if any auth method fails,
 * this will get called again until one auth method succeeds or every
 * method fails
 */
static void _auth(xmpp_conn_t * const conn)
{
    xmpp_stanza_t *auth;
    xmpp_stanza_t *authdata;
    char *authid;
    char *scram_init;
    char *str;
    int anonjid;

    /* if there is no node in conn->jid, we assume anonymous connect */
    str = xmpp_jid_node(conn->ctx, conn->jid);
    if (str == NULL) {
	anonjid = 1;
    } else {
	xmpp_free(conn->ctx, str);
	anonjid = 0;
    }

    if (conn->tls_support) {
	tls_t *tls = tls_new(conn);

	/* If we couldn't init tls, it isn't there, so go on */
	if (!tls) {
	    conn->tls_support = 0;
	    _auth(conn);
	    return;
	} else {
	    tls_free(tls);
	}

	auth = _make_starttls(conn);

	if (!auth) {
	    disconnect_mem_error(conn);
	    return;
	}

	handler_add(conn, _handle_proceedtls_default,
		    XMPP_NS_TLS, NULL, NULL, NULL);

	xmpp_send(conn, auth);
	xmpp_stanza_release(auth);

	/* TLS was tried, unset flag */
	conn->tls_support = 0;
	/* _auth() will be called later */
	return;
    }

    if (conn->tls_mandatory && !xmpp_conn_is_secured(conn)) {
        xmpp_error(conn->ctx, "xmpp", "TLS is not supported, but set as "
                                      "mandatory for this connection");
        conn_disconnect(conn);
        return;
    }

    if (anonjid && conn->sasl_support & SASL_MASK_ANONYMOUS) {
	/* some crap here */
	auth = _make_sasl_auth(conn, "ANONYMOUS");
	if (!auth) {
	    disconnect_mem_error(conn);
	    return;
	}

	handler_add(conn, _handle_sasl_result, XMPP_NS_SASL,
	            NULL, NULL, "ANONYMOUS");

	xmpp_send(conn, auth);
	xmpp_stanza_release(auth);

	/* SASL ANONYMOUS was tried, unset flag */
	conn->sasl_support &= ~SASL_MASK_ANONYMOUS;
    } else if (anonjid) {
	xmpp_error(conn->ctx, "auth",
		   "No node in JID, and SASL ANONYMOUS unsupported.");
	xmpp_disconnect(conn);
    } else if (conn->sasl_support & SASL_MASK_SCRAMSHA1) {
        auth = _make_sasl_auth(conn, "SCRAM-SHA-1");
        if (!auth) {
            disconnect_mem_error(conn);
            return;
        }

        /* don't free scram_init on success */
        scram_init = _make_scram_sha1_init_msg(conn);
        if (!scram_init) {
            xmpp_stanza_release(auth);
            disconnect_mem_error(conn);
            return;
        }

        str = xmpp_base64_encode(conn->ctx, (unsigned char *)scram_init,
                                 strlen(scram_init));
        if (!str) {
            xmpp_free(conn->ctx, scram_init);
            xmpp_stanza_release(auth);
            disconnect_mem_error(conn);
            return;
        }

        authdata = xmpp_stanza_new(conn->ctx);
        if (!authdata) {
            xmpp_free(conn->ctx, str);
            xmpp_free(conn->ctx, scram_init);
            xmpp_stanza_release(auth);
            disconnect_mem_error(conn);
            return;
        }
        xmpp_stanza_set_text(authdata, str);
        xmpp_free(conn->ctx, str);
        xmpp_stanza_add_child(auth, authdata);
        xmpp_stanza_release(authdata);

        handler_add(conn, _handle_scram_sha1_challenge,
                    XMPP_NS_SASL, NULL, NULL, (void *)scram_init);

        xmpp_send(conn, auth);
        xmpp_stanza_release(auth);

        /* SASL SCRAM-SHA-1 was tried, unset flag */
        conn->sasl_support &= ~SASL_MASK_SCRAMSHA1;
    } else if (conn->sasl_support & SASL_MASK_DIGESTMD5) {
	auth = _make_sasl_auth(conn, "DIGEST-MD5");
	if (!auth) {
	    disconnect_mem_error(conn);
	    return;

	}

	handler_add(conn, _handle_digestmd5_challenge,
		    XMPP_NS_SASL, NULL, NULL, NULL);

	xmpp_send(conn, auth);
	xmpp_stanza_release(auth);

	/* SASL DIGEST-MD5 was tried, unset flag */
	conn->sasl_support &= ~SASL_MASK_DIGESTMD5;
    } else if (conn->sasl_support & SASL_MASK_PLAIN) {
	auth = _make_sasl_auth(conn, "PLAIN");
	if (!auth) {
	    disconnect_mem_error(conn);
	    return;
	}
	authdata = xmpp_stanza_new(conn->ctx);
	if (!authdata) {
	    disconnect_mem_error(conn);
	    return;
	}
	authid = _get_authid(conn);
	if (!authid) {
	    disconnect_mem_error(conn);
	    return;
	}
	str = sasl_plain(conn->ctx, authid, conn->pass);
	if (!str) {
	    disconnect_mem_error(conn);
	    return;
	}
	xmpp_stanza_set_text(authdata, str);
	xmpp_free(conn->ctx, str);
	xmpp_free(conn->ctx, authid);

	xmpp_stanza_add_child(auth, authdata);
	xmpp_stanza_release(authdata);

	handler_add(conn, _handle_sasl_result,
		    XMPP_NS_SASL, NULL, NULL, "PLAIN");

	xmpp_send(conn, auth);
	xmpp_stanza_release(auth);

	/* SASL PLAIN was tried */
	conn->sasl_support &= ~SASL_MASK_PLAIN;
    } else if (conn->type == XMPP_CLIENT && conn->auth_legacy_enabled) {
        /* legacy client authentication */
        _auth_legacy(conn);
    } else {
        xmpp_error(conn->ctx, "auth", "Cannot authenticate with known methods");
        xmpp_disconnect(conn);
    }
}


/** Set up handlers at stream start.
 *  This function is called internally to Strophe for handling the opening
 *  of an XMPP stream.  It's called by the parser when a stream is opened
 *  or reset, and adds the initial handlers for <stream:error/> and
 *  <stream:features/>.  This function is not intended for use outside
 *  of Strophe.
 *
 *  @param conn a Strophe connection object
 */
void auth_handle_open(xmpp_conn_t * const conn)
{
    /* reset all timed handlers */
    handler_reset_timed(conn, 0);

    /* setup handler for stream:error, we will keep this handler
     * for reopened streams until connection is disconnected */
    handler_add(conn, _handle_error, XMPP_NS_STREAMS, "error", NULL, NULL);

    /* setup handlers for incoming <stream:features> */
    handler_add(conn, _handle_features,
		XMPP_NS_STREAMS, "features", NULL, NULL);
    handler_add_timed(conn, _handle_missing_features, FEATURES_TIMEOUT, NULL);
}

/* called when stream:stream tag received after TLS establishment */
static void _handle_open_tls(xmpp_conn_t * const conn)
{
    /* setup handlers for incoming <stream:features> */
    handler_add(conn, _handle_features,
		XMPP_NS_STREAMS, "features", NULL, NULL);
    handler_add_timed(conn, _handle_missing_features, FEATURES_TIMEOUT, NULL);
}

/* called when stream:stream tag received after SASL auth */
static void _handle_open_sasl(xmpp_conn_t * const conn)
{
    xmpp_debug(conn->ctx, "xmpp", "Reopened stream successfully.");

    /* setup stream:features handlers */
    handler_add(conn, _handle_features_sasl,
		XMPP_NS_STREAMS, "features", NULL, NULL);
    handler_add_timed(conn, _handle_missing_features_sasl,
		      FEATURES_TIMEOUT, NULL);
}

static int _handle_features_sasl(xmpp_conn_t * const conn,
				 xmpp_stanza_t * const stanza,
				 void * const userdata)
{
    xmpp_stanza_t *bind, *session, *iq, *res, *text, *opt;
    const char *ns;
    char *resource;

    /* remove missing features handler */
    xmpp_timed_handler_delete(conn, _handle_missing_features_sasl);

    /* we are expecting <bind/> and <session/> since this is a
       XMPP style connection */

    /* check whether resource binding is required */
    bind = xmpp_stanza_get_child_by_name(stanza, "bind");
    if (bind) {
        ns = xmpp_stanza_get_ns(bind);
	conn->bind_required = ns != NULL && strcmp(ns, XMPP_NS_BIND) == 0;
    }

    /* check whether session establishment is required */
    session = xmpp_stanza_get_child_by_name(stanza, "session");
    if (session) {
        ns = xmpp_stanza_get_ns(session);
        opt = xmpp_stanza_get_child_by_name(session, "optional");
	if (!opt)
            conn->session_required = ns != NULL &&
                                     strcmp(ns, XMPP_NS_SESSION) == 0;
    }

    /* if bind is required, go ahead and start it */
    if (conn->bind_required) {
	/* bind resource */

	/* setup response handlers */
	handler_add_id(conn, _handle_bind, "_xmpp_bind1", NULL);
	handler_add_timed(conn, _handle_missing_bind,
			  BIND_TIMEOUT, NULL);

	/* send bind request */
	iq = xmpp_iq_new(conn->ctx, "set", "_xmpp_bind1");
	if (!iq) {
	    disconnect_mem_error(conn);
	    return 0;
	}

	bind = xmpp_stanza_copy(bind);
	if (!bind) {
	    xmpp_stanza_release(iq);
	    disconnect_mem_error(conn);
	    return 0;
	}

	/* request a specific resource if we have one */
        resource = xmpp_jid_resource(conn->ctx, conn->jid);
	if ((resource != NULL) && (strlen(resource) == 0)) {
	    /* jabberd2 doesn't handle an empty resource */
	    xmpp_free(conn->ctx, resource);
	    resource = NULL;
	}

	/* if we have a resource to request, do it. otherwise the
	   server will assign us one */
	if (resource) {
	    res = xmpp_stanza_new(conn->ctx);
	    if (!res) {
		xmpp_stanza_release(bind);
		xmpp_stanza_release(iq);
		disconnect_mem_error(conn);
		return 0;
	    }
	    xmpp_stanza_set_name(res, "resource");
	    text = xmpp_stanza_new(conn->ctx);
	    if (!text) {
		xmpp_stanza_release(res);
		xmpp_stanza_release(bind);
		xmpp_stanza_release(iq);
		disconnect_mem_error(conn);
		return 0;
	    }
	    xmpp_stanza_set_text(text, resource);
	    xmpp_stanza_add_child(res, text);
            xmpp_stanza_release(text);
	    xmpp_stanza_add_child(bind, res);
            xmpp_stanza_release(res);
	    xmpp_free(conn->ctx, resource);
	}

	xmpp_stanza_add_child(iq, bind);
	xmpp_stanza_release(bind);

	/* send bind request */
	xmpp_send(conn, iq);
	xmpp_stanza_release(iq);
    } else {
	/* can't bind, disconnect */
	xmpp_error(conn->ctx, "xmpp", "Stream features does not allow "\
		   "resource bind.");
	xmpp_disconnect(conn);
    }

    return 0;
}

static int _handle_missing_features_sasl(xmpp_conn_t * const conn,
					 void * const userdata)
{
    xmpp_error(conn->ctx, "xmpp", "Did not receive stream features "\
	       "after SASL authentication.");
    xmpp_disconnect(conn);
    return 0;
}

static int _handle_bind(xmpp_conn_t * const conn,
			xmpp_stanza_t * const stanza,
			void * const userdata)
{
    const char *type;
    xmpp_stanza_t *iq, *session;

    /* delete missing bind handler */
    xmpp_timed_handler_delete(conn, _handle_missing_bind);

    /* server has replied to bind request */
    type = xmpp_stanza_get_type(stanza);
    if (type && strcmp(type, "error") == 0) {
	xmpp_error(conn->ctx, "xmpp", "Binding failed.");
	xmpp_disconnect(conn);
    } else if (type && strcmp(type, "result") == 0) {
        xmpp_stanza_t *binding = xmpp_stanza_get_child_by_name(stanza, "bind");
	xmpp_debug(conn->ctx, "xmpp", "Bind successful.");

        if (binding) {
            xmpp_stanza_t *jid_stanza = xmpp_stanza_get_child_by_name(binding,
                                                                      "jid");
            if (jid_stanza) {
                conn->bound_jid = xmpp_stanza_get_text(jid_stanza);
            }
        }

	/* establish a session if required */
	if (conn->session_required) {
	    /* setup response handlers */
	    handler_add_id(conn, _handle_session, "_xmpp_session1", NULL);
	    handler_add_timed(conn, _handle_missing_session,
			      SESSION_TIMEOUT, NULL);

	    /* send session request */
            iq = xmpp_iq_new(conn->ctx, "set", "_xmpp_session1");
	    if (!iq) {
		disconnect_mem_error(conn);
		return 0;
	    }

	    session = xmpp_stanza_new(conn->ctx);
	    if (!session) {
		xmpp_stanza_release(iq);
		disconnect_mem_error(conn);
                return 0;
	    }

	    xmpp_stanza_set_name(session, "session");
	    xmpp_stanza_set_ns(session, XMPP_NS_SESSION);

	    xmpp_stanza_add_child(iq, session);
	    xmpp_stanza_release(session);

	    /* send session establishment request */
	    xmpp_send(conn, iq);
	    xmpp_stanza_release(iq);
	} else {
	    conn->authenticated = 1;

	    /* call connection handler */
	    conn->conn_handler(conn, XMPP_CONN_CONNECT, 0, NULL,
			       conn->userdata);
	}
    } else {
	xmpp_error(conn->ctx, "xmpp", "Server sent malformed bind reply.");
	xmpp_disconnect(conn);
    }

    return 0;
}

static int _handle_missing_bind(xmpp_conn_t * const conn,
				void * const userdata)
{
    xmpp_error(conn->ctx, "xmpp", "Server did not reply to bind request.");
    xmpp_disconnect(conn);
    return 0;
}

static int _handle_session(xmpp_conn_t * const conn,
			   xmpp_stanza_t * const stanza,
			   void * const userdata)
{
    const char *type;

    /* delete missing session handler */
    xmpp_timed_handler_delete(conn, _handle_missing_session);

    /* server has replied to the session request */
    type = xmpp_stanza_get_type(stanza);
    if (type && strcmp(type, "error") == 0) {
	xmpp_error(conn->ctx, "xmpp", "Session establishment failed.");
	xmpp_disconnect(conn);
    } else if (type && strcmp(type, "result") == 0) {
	xmpp_debug(conn->ctx, "xmpp", "Session establishment successful.");

	conn->authenticated = 1;

	/* call connection handler */
	conn->conn_handler(conn, XMPP_CONN_CONNECT, 0, NULL, conn->userdata);
    } else {
	xmpp_error(conn->ctx, "xmpp", "Server sent malformed session reply.");
	xmpp_disconnect(conn);
    }

    return 0;
}

static int _handle_missing_session(xmpp_conn_t * const conn,
				   void * const userdata)
{
    xmpp_error(conn->ctx, "xmpp", "Server did not reply to session request.");
    xmpp_disconnect(conn);
    return 0;
}

static int _handle_missing_legacy(xmpp_conn_t * const conn,
				  void * const userdata)
{
    xmpp_error(conn->ctx, "xmpp", "Server did not reply to legacy "\
	       "authentication request.");
    xmpp_disconnect(conn);
    return 0;
}

static int _handle_legacy(xmpp_conn_t * const conn,
			  xmpp_stanza_t * const stanza,
			  void * const userdata)
{
    const char *type;
    const char *name;

    /* delete missing handler */
    xmpp_timed_handler_delete(conn, _handle_missing_legacy);

    /* server responded to legacy auth request */
    type = xmpp_stanza_get_type(stanza);
    name = xmpp_stanza_get_name(stanza);
    if (!type || strcmp(name, "iq") != 0) {
	xmpp_error(conn->ctx, "xmpp", "Server sent us an unexpected response "\
		   "to legacy authentication request.");
	xmpp_disconnect(conn);
    } else if (strcmp(type, "error") == 0) {
	/* legacy client auth failed, no more fallbacks */
	xmpp_error(conn->ctx, "xmpp", "Legacy client authentication failed.");
	xmpp_disconnect(conn);
    } else if (strcmp(type, "result") == 0) {
	/* auth succeeded */
	xmpp_debug(conn->ctx, "xmpp", "Legacy auth succeeded.");

	conn->authenticated = 1;
	conn->conn_handler(conn, XMPP_CONN_CONNECT, 0, NULL, conn->userdata);
    } else {
	xmpp_error(conn->ctx, "xmpp", "Server sent us a legacy authentication "\
		   "response with a bad type.");
	xmpp_disconnect(conn);
    }

    return 0;
}

static void _auth_legacy(xmpp_conn_t *conn)
{
    xmpp_stanza_t *iq;
    xmpp_stanza_t *authdata;
    xmpp_stanza_t *query;
    xmpp_stanza_t *child;
    char *str;

    xmpp_debug(conn->ctx, "auth", "Legacy authentication request");

    iq = xmpp_iq_new(conn->ctx, "set", "_xmpp_auth1");
    if (!iq)
        goto err;

    query = xmpp_stanza_new(conn->ctx);
    if (!query)
        goto err_free;
    xmpp_stanza_set_name(query, "query");
    xmpp_stanza_set_ns(query, XMPP_NS_AUTH);
    xmpp_stanza_add_child(iq, query);
    xmpp_stanza_release(query);

    child = xmpp_stanza_new(conn->ctx);
    if (!child)
        goto err_free;
    xmpp_stanza_set_name(child, "username");
    xmpp_stanza_add_child(query, child);
    xmpp_stanza_release(child);

    authdata = xmpp_stanza_new(conn->ctx);
    if (!authdata)
        goto err_free;
    str = xmpp_jid_node(conn->ctx, conn->jid);
    if (!str) {
        xmpp_stanza_release(authdata);
        goto err_free;
    }
    xmpp_stanza_set_text(authdata, str);
    xmpp_free(conn->ctx, str);
    xmpp_stanza_add_child(child, authdata);
    xmpp_stanza_release(authdata);

    child = xmpp_stanza_new(conn->ctx);
    if (!child)
        goto err_free;
    xmpp_stanza_set_name(child, "password");
    xmpp_stanza_add_child(query, child);
    xmpp_stanza_release(child);

    authdata = xmpp_stanza_new(conn->ctx);
    if (!authdata)
        goto err_free;
    xmpp_stanza_set_text(authdata, conn->pass);
    xmpp_stanza_add_child(child, authdata);
    xmpp_stanza_release(authdata);

    child = xmpp_stanza_new(conn->ctx);
    if (!child)
        goto err_free;
    xmpp_stanza_set_name(child, "resource");
    xmpp_stanza_add_child(query, child);
    xmpp_stanza_release(child);

    authdata = xmpp_stanza_new(conn->ctx);
    if (!authdata)
        goto err_free;
    str = xmpp_jid_resource(conn->ctx, conn->jid);
    if (str) {
        xmpp_stanza_set_text(authdata, str);
        xmpp_free(conn->ctx, str);
    } else {
        xmpp_stanza_release(authdata);
        xmpp_stanza_release(iq);
        xmpp_error(conn->ctx, "auth", "Cannot authenticate without resource");
        xmpp_disconnect(conn);
        return;
    }
    xmpp_stanza_add_child(child, authdata);
    xmpp_stanza_release(authdata);

    handler_add_id(conn, _handle_legacy, "_xmpp_auth1", NULL);
    handler_add_timed(conn, _handle_missing_legacy, LEGACY_TIMEOUT, NULL);

    xmpp_send(conn, iq);
    xmpp_stanza_release(iq);
    return;

err_free:
    xmpp_stanza_release(iq);
err:
    disconnect_mem_error(conn);
}

void auth_handle_component_open(xmpp_conn_t * const conn)
{
    int rc;

    /* reset all timed handlers */
    handler_reset_timed(conn, 0);

    handler_add(conn, _handle_error, XMPP_NS_STREAMS, "error", NULL, NULL);
    handler_add(conn, _handle_component_hs_response, NULL,
                "handshake", NULL, NULL);
    handler_add_timed(conn, _handle_missing_handshake, HANDSHAKE_TIMEOUT, NULL);

    rc = _handle_component_auth(conn);
    if (rc != 0) {
        xmpp_error(conn->ctx, "auth", "Component authentication failed.");
        xmpp_disconnect(conn);
    }
}

/* Will compute SHA1 and authenticate the component to the server */
int _handle_component_auth(xmpp_conn_t * const conn)
{
    uint8_t md_value[SHA1_DIGEST_SIZE];
    SHA1_CTX mdctx;
    char *digest;
    size_t i;

    if (conn->stream_id == NULL) {
        xmpp_error(conn->ctx, "auth", "Received no stream id from the server.");
        return XMPP_EINT;
    }

    /* Feed the session id and passphrase to the algorithm.
     * We need to compute SHA1(session_id + passphrase)
     */
    crypto_SHA1_Init(&mdctx);
    crypto_SHA1_Update(&mdctx, (uint8_t*)conn->stream_id,
                       strlen(conn->stream_id));
    crypto_SHA1_Update(&mdctx, (uint8_t*)conn->pass, strlen(conn->pass));
    crypto_SHA1_Final(&mdctx, md_value);

    digest = xmpp_alloc(conn->ctx, 2*sizeof(md_value)+1);
    if (digest) {
        /* convert the digest into string representation */
        for (i = 0; i < sizeof(md_value); i++)
            xmpp_snprintf(digest+i*2, 3, "%02x", md_value[i]);
        digest[2*sizeof(md_value)] = '\0';

        xmpp_debug(conn->ctx, "auth", "Digest: %s, len: %d",
                   digest, strlen(digest));

        /* Send the digest to the server */
        xmpp_send_raw_string(conn, "<handshake xmlns='%s'>%s</handshake>",
                             XMPP_NS_COMPONENT, digest);
        xmpp_debug(conn->ctx, "auth", "Sent component handshake to the server.");
        xmpp_free(conn->ctx, digest);
    } else {
        xmpp_debug(conn->ctx, "auth", "Couldn't allocate memory for component "\
                                      "handshake digest.");
        return XMPP_EMEM;
    }

    return 0;
}

/* Check if the received stanza is <handshake/> and set auth to true
 * and fire connection handler.
 */
int _handle_component_hs_response(xmpp_conn_t * const conn,
            xmpp_stanza_t * const stanza,
            void * const userdata)
{
    const char *name;

    xmpp_timed_handler_delete(conn, _handle_missing_handshake);

    name = xmpp_stanza_get_name(stanza);
    if (strcmp(name, "handshake") != 0) {
        char *msg;
        size_t msg_size;
        xmpp_stanza_to_text(stanza, &msg, &msg_size);
        if (msg) {
            xmpp_debug(conn->ctx, "auth", "Handshake failed: %s", msg);
            xmpp_free(conn->ctx, msg);
        }
        xmpp_disconnect(conn);
        return XMPP_EINT;
    } else {
        conn->authenticated = 1;
        conn->conn_handler(conn, XMPP_CONN_CONNECT, 0, NULL, conn->userdata);
    }

    /* We don't need this handler anymore, return 0 so it can be deleted
     * from the list of handlers.
     */
    return 0;
}

int _handle_missing_handshake(xmpp_conn_t * const conn, void * const userdata)
{
    xmpp_error(conn->ctx, "xmpp", "Server did not reply to handshake request.");
    xmpp_disconnect(conn);
    return 0;
}

void auth_handle_open_raw(xmpp_conn_t * const conn)
{
    handler_reset_timed(conn, 0);
    /* user handlers are not called before authentication is completed. */
    conn->authenticated = 1;
    conn->conn_handler(conn, XMPP_CONN_CONNECT, 0, NULL, conn->userdata);
}

void auth_handle_open_stub(xmpp_conn_t * const conn)
{
    xmpp_warn(conn->ctx, "auth", "Stub callback is called.");
}
