diff --git a/src/auth.c b/src/auth.c index fd2d48b1..9fa87a83 100644 --- a/src/auth.c +++ b/src/auth.c @@ -277,11 +277,20 @@ _handle_features(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) } } - /* check for SASL */ - child = xmpp_stanza_get_child_by_name_and_ns(stanza, "mechanisms", - XMPP_NS_SASL); + /* check for SASL2 */ + child = xmpp_stanza_get_child_by_name_and_ns(stanza, "authentication", + XMPP_NS_SASL2); + if (child) { + conn->sasl_support |= SASL_MASK_SASL2; _foreach_child(conn, child, "mechanism", _handle_sasl_children); + } else { + /* check for SASL */ + child = xmpp_stanza_get_child_by_name_and_ns(stanza, "mechanisms", + XMPP_NS_SASL); + if (child) { + _foreach_child(conn, child, "mechanism", _handle_sasl_children); + } } /* Disable PLAIN when other secure mechanisms are supported */ @@ -350,17 +359,27 @@ _handle_sasl_result(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) /* fall back to next auth method */ _auth(conn); } else if (strcmp(name, "success") == 0) { - /* SASL auth successful, we need to restart the stream */ strophe_debug(conn->ctx, "xmpp", "SASL %s auth successful", (char *)userdata); - /* reset parser */ - conn_prepare_reset(conn, conn->compression.allowed - ? _handle_open_compress - : _handle_open_sasl); + if (conn->sasl_support & SASL_MASK_SASL2) { + /* New features will come, but no restart */ + if (conn->compression.allowed) { + _handle_open_compress(conn); + } else { + _handle_open_sasl(conn); + } + } else { + /* SASL auth successful, we need to restart the stream */ - /* send stream tag */ - conn_open_stream(conn); + /* reset parser */ + conn_prepare_reset(conn, conn->compression.allowed + ? _handle_open_compress + : _handle_open_sasl); + + /* send stream tag */ + conn_open_stream(conn); + } } else { /* got unexpected reply */ strophe_error(conn->ctx, "xmpp", @@ -389,6 +408,8 @@ static int _handle_digestmd5_challenge(xmpp_conn_t *conn, "handle digest-md5 (challenge) called for %s", name); if (strcmp(name, "challenge") == 0) { + const char *sasl_ns = + conn->sasl_support & SASL_MASK_SASL2 ? XMPP_NS_SASL2 : XMPP_NS_SASL; text = xmpp_stanza_get_text(stanza); response = sasl_digest_md5(conn->ctx, text, conn->jid, conn->pass); if (!response) { @@ -403,7 +424,7 @@ static int _handle_digestmd5_challenge(xmpp_conn_t *conn, return 0; } xmpp_stanza_set_name(auth, "response"); - xmpp_stanza_set_ns(auth, XMPP_NS_SASL); + xmpp_stanza_set_ns(auth, sasl_ns); authdata = xmpp_stanza_new(conn->ctx); if (!authdata) { @@ -416,8 +437,7 @@ static int _handle_digestmd5_challenge(xmpp_conn_t *conn, xmpp_stanza_add_child_ex(auth, authdata, 0); - handler_add(conn, _handle_digestmd5_rspauth, XMPP_NS_SASL, NULL, NULL, - NULL); + handler_add(conn, _handle_digestmd5_rspauth, sasl_ns, NULL, NULL, NULL); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); @@ -444,6 +464,8 @@ static int _handle_digestmd5_rspauth(xmpp_conn_t *conn, "handle digest-md5 (rspauth) called for %s", name); if (strcmp(name, "challenge") == 0) { + const char *sasl_ns = + conn->sasl_support & SASL_MASK_SASL2 ? XMPP_NS_SASL2 : XMPP_NS_SASL; /* assume it's an rspauth response */ auth = xmpp_stanza_new(conn->ctx); if (!auth) { @@ -451,7 +473,7 @@ static int _handle_digestmd5_rspauth(xmpp_conn_t *conn, return 0; } xmpp_stanza_set_name(auth, "response"); - xmpp_stanza_set_ns(auth, XMPP_NS_SASL); + xmpp_stanza_set_ns(auth, sasl_ns); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); } else { return _handle_sasl_result(conn, stanza, "DIGEST-MD5"); @@ -488,6 +510,8 @@ static int _handle_scram_challenge(xmpp_conn_t *conn, scram_ctx->alg->scram_name, name); if (strcmp(name, "challenge") == 0) { + const char *sasl_ns = + conn->sasl_support & SASL_MASK_SASL2 ? XMPP_NS_SASL2 : XMPP_NS_SASL; text = xmpp_stanza_get_text(stanza); if (!text) goto err; @@ -508,7 +532,7 @@ static int _handle_scram_challenge(xmpp_conn_t *conn, if (!auth) goto err_free_response; xmpp_stanza_set_name(auth, "response"); - xmpp_stanza_set_ns(auth, XMPP_NS_SASL); + xmpp_stanza_set_ns(auth, sasl_ns); authdata = xmpp_stanza_new(conn->ctx); if (!authdata) @@ -658,16 +682,103 @@ static xmpp_stanza_t *_make_starttls(xmpp_conn_t *conn) return starttls; } -static xmpp_stanza_t *_make_sasl_auth(xmpp_conn_t *conn, const char *mechanism) +static xmpp_stanza_t *_make_sasl_auth(xmpp_conn_t *conn, + const char *mechanism, + const char *initial_data) { - xmpp_stanza_t *auth; + xmpp_stanza_t *auth, *init, *user_agent; + xmpp_stanza_t *inittxt = NULL; /* build auth stanza */ + if (initial_data) { + inittxt = xmpp_stanza_new(conn->ctx); + if (!inittxt) + return NULL; + } auth = xmpp_stanza_new(conn->ctx); if (auth) { - xmpp_stanza_set_name(auth, "auth"); - xmpp_stanza_set_ns(auth, XMPP_NS_SASL); + if (conn->sasl_support & SASL_MASK_SASL2) { + xmpp_stanza_set_name(auth, "authenticate"); + xmpp_stanza_set_ns(auth, XMPP_NS_SASL2); + if (initial_data) { + init = xmpp_stanza_new(conn->ctx); + if (!init) { + xmpp_stanza_release(auth); + return NULL; + } + xmpp_stanza_set_name(init, "initial-response"); + xmpp_stanza_set_ns(init, XMPP_NS_SASL2); + xmpp_stanza_set_text(inittxt, initial_data); + xmpp_stanza_add_child_ex(init, inittxt, 0); + xmpp_stanza_add_child_ex(auth, init, 0); + } + if (conn->user_agent_id || conn->user_agent_software || + conn->user_agent_device) { + user_agent = xmpp_stanza_new(conn->ctx); + if (!user_agent) { + xmpp_stanza_release(auth); + return NULL; + } + xmpp_stanza_set_name(user_agent, "user-agent"); + xmpp_stanza_set_ns(user_agent, XMPP_NS_SASL2); + if (conn->user_agent_id) { + xmpp_stanza_set_attribute(user_agent, "id", + conn->user_agent_id); + } + if (conn->user_agent_software) { + xmpp_stanza_t *software = xmpp_stanza_new(conn->ctx); + if (!software) { + xmpp_stanza_release(user_agent); + xmpp_stanza_release(auth); + return NULL; + } + xmpp_stanza_set_name(software, "software"); + xmpp_stanza_set_ns(software, XMPP_NS_SASL2); + xmpp_stanza_t *txt = xmpp_stanza_new(conn->ctx); + if (!txt) { + xmpp_stanza_release(software); + xmpp_stanza_release(user_agent); + xmpp_stanza_release(auth); + return NULL; + } + xmpp_stanza_set_text(txt, conn->user_agent_software); + xmpp_stanza_add_child_ex(software, txt, 0); + xmpp_stanza_add_child_ex(user_agent, software, 0); + } + if (conn->user_agent_device) { + xmpp_stanza_t *device = xmpp_stanza_new(conn->ctx); + if (!device) { + xmpp_stanza_release(user_agent); + xmpp_stanza_release(auth); + return NULL; + } + xmpp_stanza_set_name(device, "device"); + xmpp_stanza_set_ns(device, XMPP_NS_SASL2); + xmpp_stanza_t *txt = xmpp_stanza_new(conn->ctx); + if (!txt) { + xmpp_stanza_release(device); + xmpp_stanza_release(user_agent); + xmpp_stanza_release(auth); + return NULL; + } + xmpp_stanza_set_text(txt, conn->user_agent_device); + xmpp_stanza_add_child_ex(device, txt, 0); + xmpp_stanza_add_child_ex(user_agent, device, 0); + } + xmpp_stanza_add_child_ex(auth, user_agent, 0); + } + } else { + xmpp_stanza_set_name(auth, "auth"); + xmpp_stanza_set_ns(auth, XMPP_NS_SASL); + if (initial_data) { + xmpp_stanza_set_text(inittxt, initial_data); + xmpp_stanza_add_child_ex(auth, inittxt, 0); + } + } xmpp_stanza_set_attribute(auth, "mechanism", mechanism); + } else { + if (inittxt) + xmpp_stanza_release(inittxt); } return auth; @@ -681,7 +792,6 @@ static xmpp_stanza_t *_make_sasl_auth(xmpp_conn_t *conn, const char *mechanism) static void _auth(xmpp_conn_t *conn) { xmpp_stanza_t *auth; - xmpp_stanza_t *authdata; struct scram_user_data *scram_ctx; char *authid; char *str; @@ -734,15 +844,18 @@ static void _auth(xmpp_conn_t *conn) return; } + const char *sasl_ns = + conn->sasl_support & SASL_MASK_SASL2 ? XMPP_NS_SASL2 : XMPP_NS_SASL; + if (anonjid && (conn->sasl_support & SASL_MASK_ANONYMOUS)) { /* some crap here */ - auth = _make_sasl_auth(conn, "ANONYMOUS"); + auth = _make_sasl_auth(conn, "ANONYMOUS", NULL); if (!auth) { disconnect_mem_error(conn); return; } - handler_add(conn, _handle_sasl_result, XMPP_NS_SASL, NULL, NULL, + handler_add(conn, _handle_sasl_result, sasl_ns, NULL, NULL, "ANONYMOUS"); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); @@ -751,40 +864,28 @@ static void _auth(xmpp_conn_t *conn) conn->sasl_support &= ~SASL_MASK_ANONYMOUS; } else if (conn->sasl_support & SASL_MASK_EXTERNAL) { /* more crap here */ - auth = _make_sasl_auth(conn, "EXTERNAL"); - if (!auth) { - disconnect_mem_error(conn); - return; - } - - authdata = xmpp_stanza_new(conn->ctx); - if (!authdata) { - xmpp_stanza_release(auth); - disconnect_mem_error(conn); - return; - } str = tls_id_on_xmppaddr(conn, 0); if (!str || (tls_id_on_xmppaddr_num(conn) == 1 && strcmp(str, conn->jid) == 0)) { - xmpp_stanza_set_text(authdata, "="); + str = strophe_strdup(conn->ctx, "="); } else { strophe_free(conn->ctx, str); str = xmpp_base64_encode(conn->ctx, (void *)conn->jid, strlen(conn->jid)); if (!str) { - xmpp_stanza_release(authdata); - xmpp_stanza_release(auth); disconnect_mem_error(conn); return; } - xmpp_stanza_set_text(authdata, str); } - strophe_free(conn->ctx, str); - xmpp_stanza_add_child_ex(auth, authdata, 0); + auth = _make_sasl_auth(conn, "EXTERNAL", str); + strophe_free(conn->ctx, str); + if (!auth) { + disconnect_mem_error(conn); + return; + } - handler_add(conn, _handle_sasl_result, XMPP_NS_SASL, NULL, NULL, - "EXTERNAL"); + handler_add(conn, _handle_sasl_result, sasl_ns, NULL, NULL, "EXTERNAL"); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); @@ -810,18 +911,11 @@ static void _auth(xmpp_conn_t *conn) } } - auth = _make_sasl_auth(conn, scram_ctx->alg->scram_name); - if (!auth) { - disconnect_mem_error(conn); - return; - } - scram_ctx->conn = conn; scram_ctx->sasl_plus = scram_ctx->alg->mask & SASL_MASK_SCRAM_PLUS ? 1 : 0; if (_make_scram_init_msg(scram_ctx)) { strophe_free(conn->ctx, scram_ctx); - xmpp_stanza_release(auth); disconnect_mem_error(conn); return; } @@ -832,25 +926,18 @@ static void _auth(xmpp_conn_t *conn) if (!str) { strophe_free(conn->ctx, scram_ctx->scram_init); strophe_free(conn->ctx, scram_ctx); - xmpp_stanza_release(auth); disconnect_mem_error(conn); return; } - authdata = xmpp_stanza_new(conn->ctx); - if (!authdata) { - strophe_free(conn->ctx, str); - strophe_free(conn->ctx, scram_ctx->scram_init); - strophe_free(conn->ctx, scram_ctx); - xmpp_stanza_release(auth); + auth = _make_sasl_auth(conn, scram_ctx->alg->scram_name, str); + strophe_free(conn->ctx, str); + if (!auth) { disconnect_mem_error(conn); return; } - xmpp_stanza_set_text(authdata, str); - strophe_free(conn->ctx, str); - xmpp_stanza_add_child_ex(auth, authdata, 0); - handler_add(conn, _handle_scram_challenge, XMPP_NS_SASL, NULL, NULL, + handler_add(conn, _handle_scram_challenge, sasl_ns, NULL, NULL, (void *)scram_ctx); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); @@ -858,13 +945,13 @@ static void _auth(xmpp_conn_t *conn) /* SASL algorithm was tried, unset flag */ conn->sasl_support &= ~scram_ctx->alg->mask; } else if (conn->sasl_support & SASL_MASK_DIGESTMD5) { - auth = _make_sasl_auth(conn, "DIGEST-MD5"); + auth = _make_sasl_auth(conn, "DIGEST-MD5", NULL); if (!auth) { disconnect_mem_error(conn); return; } - handler_add(conn, _handle_digestmd5_challenge, XMPP_NS_SASL, NULL, NULL, + handler_add(conn, _handle_digestmd5_challenge, sasl_ns, NULL, NULL, NULL); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); @@ -872,16 +959,6 @@ static void _auth(xmpp_conn_t *conn) /* 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); @@ -892,14 +969,16 @@ static void _auth(xmpp_conn_t *conn) disconnect_mem_error(conn); return; } - xmpp_stanza_set_text(authdata, str); + + auth = _make_sasl_auth(conn, "PLAIN", str); strophe_free(conn->ctx, str); strophe_free(conn->ctx, authid); + if (!auth) { + disconnect_mem_error(conn); + return; + } - xmpp_stanza_add_child_ex(auth, authdata, 0); - - handler_add(conn, _handle_sasl_result, XMPP_NS_SASL, NULL, NULL, - "PLAIN"); + handler_add(conn, _handle_sasl_result, sasl_ns, NULL, NULL, "PLAIN"); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); diff --git a/src/common.h b/src/common.h index fbb40344..97dba2aa 100644 --- a/src/common.h +++ b/src/common.h @@ -176,6 +176,7 @@ struct _xmpp_send_queue_t { #define SASL_MASK_SCRAMSHA1_PLUS (1 << 7) #define SASL_MASK_SCRAMSHA256_PLUS (1 << 8) #define SASL_MASK_SCRAMSHA512_PLUS (1 << 9) +#define SASL_MASK_SASL2 (1 << 10) #define SASL_MASK_SCRAM_PLUS \ (SASL_MASK_SCRAMSHA1_PLUS | SASL_MASK_SCRAMSHA256_PLUS | \ @@ -287,6 +288,9 @@ struct _xmpp_conn_t { char *domain; char *jid; char *pass; + char *user_agent_id; + char *user_agent_software; + char *user_agent_device; char *bound_jid; char *stream_id; diff --git a/src/conn.c b/src/conn.c index d8c7ab1e..03c61a82 100644 --- a/src/conn.c +++ b/src/conn.c @@ -623,6 +623,99 @@ void xmpp_conn_set_pass(xmpp_conn_t *conn, const char *pass) conn->pass = pass ? strophe_strdup(conn->ctx, pass) : NULL; } +/** Get the user-agent id used for authentication of a connection. + * + * @param conn a Strophe connection object + * + * @return a string containing the id or NULL if it has not been set + * + * @ingroup Connections + */ +const char *xmpp_conn_get_user_agent_id(const xmpp_conn_t *conn) +{ + return conn->user_agent_id; +} + +/** Set the user-agent id used to authenticate the connection. + * If any id was previously set, it will be discarded. The function + * will make a copy of the string. + * + * @param conn a Strophe connection object + * @param user_agent_id the id + * + * @ingroup Connections + */ +void xmpp_conn_set_user_agent_id(xmpp_conn_t *conn, const char *user_agent_id) +{ + if (conn->user_agent_id) + strophe_free(conn->ctx, conn->user_agent_id); + conn->user_agent_id = + user_agent_id ? strophe_strdup(conn->ctx, user_agent_id) : NULL; +} + +/** Get the software name used for authentication of a connection. + * + * @param conn a Strophe connection object + * + * @return a string containing the name or NULL if it has not been set + * + * @ingroup Connections + */ +const char *xmpp_conn_get_user_agent_software(const xmpp_conn_t *conn) +{ + return conn->user_agent_software; +} + +/** Set the user-agent software name used to authenticate the connection. + * If any name was previously set, it will be discarded. The function + * will make a copy of the string. + * + * @param conn a Strophe connection object + * @param user_agent_software the name + * + * @ingroup Connections + */ +void xmpp_conn_set_user_agent_software(xmpp_conn_t *conn, + const char *user_agent_software) +{ + if (conn->user_agent_software) + strophe_free(conn->ctx, conn->user_agent_software); + conn->user_agent_software = + user_agent_software ? strophe_strdup(conn->ctx, user_agent_software) + : NULL; +} + +/** Get the device name used for authentication of a connection. + * + * @param conn a Strophe connection object + * + * @return a string containing the name or NULL if it has not been set + * + * @ingroup Connections + */ +const char *xmpp_conn_get_user_agent_device(const xmpp_conn_t *conn) +{ + return conn->user_agent_device; +} + +/** Set the user-agent device name used to authenticate the connection. + * If any name was previously set, it will be discarded. The function + * will make a copy of the string. + * + * @param conn a Strophe connection object + * @param user_agent_device the name + * + * @ingroup Connections + */ +void xmpp_conn_set_user_agent_device(xmpp_conn_t *conn, + const char *user_agent_device) +{ + if (conn->user_agent_device) + strophe_free(conn->ctx, conn->user_agent_device); + conn->user_agent_device = + user_agent_device ? strophe_strdup(conn->ctx, user_agent_device) : NULL; +} + /** Get the strophe context that the connection is associated with. * @param conn a Strophe connection object * diff --git a/strophe.h b/strophe.h index b273ffa4..3ad0abf4 100644 --- a/strophe.h +++ b/strophe.h @@ -52,6 +52,10 @@ extern "C" { * Namespace definition for 'urn:ietf:params:xml:ns:xmpp-sasl'. */ #define XMPP_NS_SASL "urn:ietf:params:xml:ns:xmpp-sasl" +/** @def XMPP_NS_SASL2 + * Namespace definition for 'urn:xmpp:sasl:2'. + */ +#define XMPP_NS_SASL2 "urn:xmpp:sasl:2" /** @def XMPP_NS_BIND * Namespace definition for 'urn:ietf:params:xml:ns:xmpp-bind'. */ @@ -398,6 +402,14 @@ unsigned int xmpp_conn_cert_xmppaddr_num(xmpp_conn_t *conn); char *xmpp_conn_cert_xmppaddr(xmpp_conn_t *conn, unsigned int n); const char *xmpp_conn_get_pass(const xmpp_conn_t *conn); void xmpp_conn_set_pass(xmpp_conn_t *conn, const char *pass); +const char *xmpp_conn_get_user_agent_id(const xmpp_conn_t *conn); +void xmpp_conn_set_user_agent_id(xmpp_conn_t *conn, const char *user_agent_id); +const char *xmpp_conn_get_user_agent_software(const xmpp_conn_t *conn); +void xmpp_conn_set_user_agent_software(xmpp_conn_t *conn, + const char *user_agent_software); +const char *xmpp_conn_get_user_agent_device(const xmpp_conn_t *conn); +void xmpp_conn_set_user_agent_device(xmpp_conn_t *conn, + const char *user_agent_device); xmpp_ctx_t *xmpp_conn_get_context(xmpp_conn_t *conn); int xmpp_conn_is_secured(xmpp_conn_t *conn); void xmpp_conn_set_sockopt_callback(xmpp_conn_t *conn, diff --git a/tests/test_sasl2.c b/tests/test_sasl2.c new file mode 100644 index 00000000..adf52a96 --- /dev/null +++ b/tests/test_sasl2.c @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: MIT OR GPL-3.0-only */ +/* test_sasl.c +** libstrophe XMPP client library -- test routines for the SASL implementation +** +** 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 or GPLv3 licenses. +*/ + +#include +#include + +#include "strophe.h" +#include "common.h" +#include "sasl.h" + +static const unsigned char jid[] = "foo@bar.com"; +static const char password[] = "secret"; +static const char response_plain[] = "AGZvb0BiYXIuY29tAHNlY3JldA=="; + +static const char challenge_md5[] = + "cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgi" + "LGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg=="; +static const char response_md5[] = + "dXNlcm5hbWU9InNvbWVub2RlIixyZWFsbT0ic29tZXJlYWxtIixub25jZT0i" + "T0E2TUc5dEVRR20yaGgiLGNub25jZT0iMDBERUFEQkVFRjAwIixuYz0wMDAw" + "MDAwMSxxb3A9YXV0aCxkaWdlc3QtdXJpPSJ4bXBwL3NvbWVyZWFsbSIscmVz" + "cG9uc2U9NGVhNmU4N2JjMDkzMzUwNzQzZGIyOGQ3MDIwOGNhZmIsY2hhcnNl" + "dD11dGYtOA=="; + +int test_plain(xmpp_ctx_t *ctx) +{ + char *result; + + result = sasl_plain(ctx, jid, password); + if (result == NULL) { + /* SASL PLAIN internal failure! */ + return 1; + } + if (strncmp(response_plain, result, strlen(response_plain))) { + /* SASL PLAIN returned incorrect string! */ + return 2; + } + strophe_free(ctx, result); + + return 0; +} + +int test_digest_md5(xmpp_ctx_t *ctx) +{ + char *result; + + result = + sasl_digest_md5(ctx, challenge_md5, "somenode@somerealm", "secret"); + printf("response:\n%s\n", result); + if (strcmp(response_md5, result)) { + /* generated incorrect response to challenge */ + return 1; + } + + return 0; +} + +int main() +{ + xmpp_ctx_t *ctx; + int ret; + + printf("allocating context... "); + ctx = xmpp_ctx_new(NULL, NULL); + if (ctx == NULL) + printf("failed to create context\n"); + if (ctx == NULL) + return -1; + printf("ok.\n"); + + printf("testing SASL PLAIN... "); + ret = test_plain(ctx); + if (ret) + printf("failed!\n"); + if (ret) + return ret; + printf("ok.\n"); + + printf("testing SASL DIGEST-MD5... "); + ret = test_digest_md5(ctx); + if (ret) + printf("failed!\n"); + if (ret) + return ret; + printf("ok.\n"); + + printf("freeing context... "); + xmpp_ctx_free(ctx); + printf("ok.\n"); + + return ret; +}