From 0d2421c0945856e1c6ed8a03802881134dd27018 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 22 Jul 2024 15:48:05 -0500 Subject: [PATCH 1/5] Add support for SASL2 https://xmpp.org/extensions/xep-0388.html --- src/auth.c | 178 +++++++++++++++++++++++++++++---------------------- src/common.h | 1 + strophe.h | 4 ++ 3 files changed, 106 insertions(+), 77 deletions(-) diff --git a/src/auth.c b/src/auth.c index a1bc3b5f..81d15d28 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,48 @@ 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; + 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); + } + } 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 +737,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 +789,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 +809,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 +856,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 +871,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 +890,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 +904,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 +914,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 81765a44..9e608fed 100644 --- a/src/common.h +++ b/src/common.h @@ -192,6 +192,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 | \ diff --git a/strophe.h b/strophe.h index 0999a878..db0662a4 100644 --- a/strophe.h +++ b/strophe.h @@ -53,6 +53,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'. */ From 027cc67b65830abff8683b2dd61b5ff5034eeb1b Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 22 Jul 2024 19:17:26 -0500 Subject: [PATCH 2/5] Add support for SASL2 user-agent --- src/auth.c | 57 +++++++++++++++++++++++++++++++- src/common.h | 3 ++ src/conn.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++ strophe.h | 8 +++++ 4 files changed, 160 insertions(+), 1 deletion(-) diff --git a/src/auth.c b/src/auth.c index 81d15d28..d32c941a 100644 --- a/src/auth.c +++ b/src/auth.c @@ -686,7 +686,7 @@ static xmpp_stanza_t *_make_sasl_auth(xmpp_conn_t *conn, const char *mechanism, const char *initial_data) { - xmpp_stanza_t *auth, *init; + xmpp_stanza_t *auth, *init, *user_agent; xmpp_stanza_t *inittxt = NULL; /* build auth stanza */ @@ -712,6 +712,61 @@ static xmpp_stanza_t *_make_sasl_auth(xmpp_conn_t *conn, 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); diff --git a/src/common.h b/src/common.h index 9e608fed..7a7fe168 100644 --- a/src/common.h +++ b/src/common.h @@ -304,6 +304,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 8db7d15d..82d617ce 100644 --- a/src/conn.c +++ b/src/conn.c @@ -626,6 +626,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 db0662a4..5cd777c2 100644 --- a/strophe.h +++ b/strophe.h @@ -405,6 +405,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, From 4d2dcefb8215247c477e21c7f79c46a19dbdc172 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Fri, 13 Mar 2026 14:15:24 +0100 Subject: [PATCH 3/5] Slightly re-factor `_make_sasl_auth()` Signed-off-by: Steffen Jaeckel --- src/auth.c | 159 +++++++++++++++++++++++++---------------------------- 1 file changed, 75 insertions(+), 84 deletions(-) diff --git a/src/auth.c b/src/auth.c index d32c941a..84327e9b 100644 --- a/src/auth.c +++ b/src/auth.c @@ -682,106 +682,97 @@ static xmpp_stanza_t *_make_starttls(xmpp_conn_t *conn) return starttls; } +static int _add_sasl2_child(xmpp_ctx_t *ctx, + xmpp_stanza_t *dst, + const char *name, + const char *data) +{ + xmpp_stanza_t *element = xmpp_stanza_new(ctx); + if (!element) + return 1; + xmpp_stanza_t *sub = xmpp_stanza_new(ctx); + if (!sub) { + xmpp_stanza_release(element); + return 1; + } + + xmpp_stanza_set_name(element, name); + xmpp_stanza_set_ns(element, XMPP_NS_SASL2); + xmpp_stanza_set_text(sub, data); + xmpp_stanza_add_child_ex(element, sub, 0); + xmpp_stanza_add_child_ex(dst, element, 0); + + return 0; +} + static xmpp_stanza_t *_make_sasl_auth(xmpp_conn_t *conn, const char *mechanism, const char *initial_data) { - xmpp_stanza_t *auth, *init, *user_agent; - xmpp_stanza_t *inittxt = NULL; + xmpp_stanza_t *auth = NULL, *user_agent = 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) { - 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 (auth == NULL) + return NULL; + + if (conn->sasl_support & SASL_MASK_SASL2) { + xmpp_stanza_set_name(auth, "authenticate"); + xmpp_stanza_set_ns(auth, XMPP_NS_SASL2); + if (initial_data) { + if (_add_sasl2_child(conn->ctx, auth, "initial-response", + initial_data)) { + goto error_out; } - 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); + } + if (conn->user_agent_id || conn->user_agent_software || + conn->user_agent_device) { + user_agent = xmpp_stanza_new(conn->ctx); + if (!user_agent) { + goto error_out; + } + 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) { + if (_add_sasl2_child(conn->ctx, user_agent, "software", + conn->user_agent_software)) { + goto error_out; } - 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); + if (conn->user_agent_device) { + if (_add_sasl2_child(conn->ctx, user_agent, "device", + conn->user_agent_device)) { + goto error_out; + } } + xmpp_stanza_add_child_ex(auth, user_agent, 0); } - xmpp_stanza_set_attribute(auth, "mechanism", mechanism); } else { - if (inittxt) - xmpp_stanza_release(inittxt); + xmpp_stanza_set_name(auth, "auth"); + xmpp_stanza_set_ns(auth, XMPP_NS_SASL); + if (initial_data) { + xmpp_stanza_t *inittxt = xmpp_stanza_new(conn->ctx); + if (!inittxt) { + goto error_out; + } + xmpp_stanza_set_text(inittxt, initial_data); + xmpp_stanza_add_child_ex(auth, inittxt, 0); + } } + xmpp_stanza_set_attribute(auth, "mechanism", mechanism); return auth; + +error_out: + if (user_agent) + xmpp_stanza_release(user_agent); + if (auth) + xmpp_stanza_release(auth); + return NULL; } /* authenticate the connection From e691abc5824ded717a056c6b9c002fe255175e81 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Fri, 13 Mar 2026 14:16:40 +0100 Subject: [PATCH 4/5] Make test_sasl work Signed-off-by: Steffen Jaeckel --- Makefile.am | 5 +++ tests/test_sasl.c | 89 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/Makefile.am b/Makefile.am index f918c979..bae03f38 100644 --- a/Makefile.am +++ b/Makefile.am @@ -189,6 +189,7 @@ STATIC_TESTS = \ tests/test_hash \ tests/test_jid \ tests/test_ctx \ + tests/test_sasl \ tests/test_send_queue \ tests/test_serialize_sm \ tests/test_string \ @@ -265,6 +266,10 @@ tests_test_resolver_LDFLAGS = -static tests_test_rand_SOURCES = tests/test_rand.c tests/test.c src/sha1.c tests_test_rand_CFLAGS = $(STROPHE_FLAGS) -I$(top_srcdir)/src +tests_test_sasl_SOURCES = tests/test_sasl.c src/sasl.c \ + src/crypto.c src/hash.c src/jid.c src/md5.c src/scram.c src/sha1.c src/sha256.c src/sha512.c src/snprintf.c src/util.c +tests_test_sasl_CFLAGS = -I$(top_srcdir)/src + tests_test_scram_SOURCES = tests/test_scram.c tests/test.c src/sha1.c \ src/sha256.c src/sha512.c tests_test_scram_CFLAGS = $(STROPHE_FLAGS) -I$(top_srcdir)/src diff --git a/tests/test_sasl.c b/tests/test_sasl.c index adf52a96..072c4792 100644 --- a/tests/test_sasl.c +++ b/tests/test_sasl.c @@ -10,9 +10,12 @@ ** This program is dual licensed under the MIT or GPLv3 licenses. */ +#include #include #include +#include "test.h" + #include "strophe.h" #include "common.h" #include "sasl.h" @@ -31,7 +34,53 @@ static const char response_md5[] = "cG9uc2U9NGVhNmU4N2JjMDkzMzUwNzQzZGIyOGQ3MDIwOGNhZmIsY2hhcnNl" "dD11dGYtOA=="; -int test_plain(xmpp_ctx_t *ctx) +/* stubs to build test without whole libstrophe */ +void *strophe_alloc(const xmpp_ctx_t *ctx, size_t size) +{ + (void)ctx; + return malloc(size); +} +void *strophe_realloc(const xmpp_ctx_t *ctx, void *p, size_t size) +{ + (void)ctx; + return realloc(p, size); +} + +void strophe_free(const xmpp_ctx_t *ctx, void *p) +{ + (void)ctx; + free(p); +} + +xmpp_ctx_t *xmpp_ctx_new(const xmpp_mem_t *mem, const xmpp_log_t *log) +{ + return calloc(1, sizeof(struct _xmpp_ctx_t)); +} + +void xmpp_ctx_free(xmpp_ctx_t *ctx) +{ + free(ctx); +} + +void xmpp_rand_nonce(xmpp_rand_t *rand, char *output, size_t len) +{ + const char *nonce = "00DEADBEEF00"; + memcpy(output, nonce, len); +} + +void xmpp_disconnect(xmpp_conn_t *conn) {} + +void strophe_error(const xmpp_ctx_t *ctx, + const char *area, + const char *fmt, + ...) +{ + (void)ctx; + (void)area; + (void)fmt; +} + +static int test_plain(xmpp_ctx_t *ctx) { char *result; @@ -49,19 +98,43 @@ int test_plain(xmpp_ctx_t *ctx) return 0; } -int test_digest_md5(xmpp_ctx_t *ctx) +static int test_digest_md5(xmpp_ctx_t *ctx) { - char *result; - - result = + int ret = 0; + char *result = sasl_digest_md5(ctx, challenge_md5, "somenode@somerealm", "secret"); - printf("response:\n%s\n", result); + assert(result != NULL); if (strcmp(response_md5, result)) { /* generated incorrect response to challenge */ - return 1; + size_t n = 0; + char *should, *is; + printf("\nshould: %s\n", response_md5); + printf(" is: %s\n", result); + printf(" "); + while (response_md5[n] == result[n]) { + putchar(' '); + n++; + } + printf("^\n"); + should = + xmpp_base64_decode_str(ctx, response_md5, strlen(response_md5)); + is = xmpp_base64_decode_str(ctx, result, strlen(result)); + printf("\nshould: %s\n", should); + printf(" is: %s\n", is); + printf(" "); + n = 0; + while (should[n] == is[n]) { + putchar(' '); + n++; + } + strophe_free(ctx, is); + strophe_free(ctx, should); + printf("^\n"); + ret = 1; } + strophe_free(ctx, result); - return 0; + return ret; } int main() From 8eb5f5a8bbb8f0cfd333e5a705a2622670312dc5 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 13 Mar 2026 23:42:01 -0500 Subject: [PATCH 5/5] Add new auth SASL2 syntax tests --- .gitignore | 1 + Makefile.am | 10 ++ tests/test_sasl2_syntax.c | 345 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 356 insertions(+) create mode 100644 tests/test_sasl2_syntax.c diff --git a/.gitignore b/.gitignore index 019e5308..11263129 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,7 @@ tests/test_md5 tests/test_rand tests/test_resolver tests/test_sasl +tests/test_sasl2_syntax tests/test_scram tests/test_send_queue tests/test_serialize_sm diff --git a/Makefile.am b/Makefile.am index bae03f38..43c566cf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -190,6 +190,7 @@ STATIC_TESTS = \ tests/test_jid \ tests/test_ctx \ tests/test_sasl \ + tests/test_sasl2_syntax \ tests/test_send_queue \ tests/test_serialize_sm \ tests/test_string \ @@ -270,6 +271,15 @@ tests_test_sasl_SOURCES = tests/test_sasl.c src/sasl.c \ src/crypto.c src/hash.c src/jid.c src/md5.c src/scram.c src/sha1.c src/sha256.c src/sha512.c src/snprintf.c src/util.c tests_test_sasl_CFLAGS = -I$(top_srcdir)/src +tests_test_sasl2_syntax_SOURCES = tests/test_sasl2_syntax.c src/stanza.c src/util.c src/jid.c src/snprintf.c src/hash.c src/md5.c src/sha1.c src/sha256.c src/sha512.c src/crypto.c src/rand.c src/scram.c src/sasl.c +tests_test_sasl2_syntax_CFLAGS = $(STROPHE_FLAGS) -I$(top_srcdir)/src $(PARSER_CFLAGS) +if PARSER_EXPAT +tests_test_sasl2_syntax_SOURCES += src/parser_expat.c +else +tests_test_sasl2_syntax_SOURCES += src/parser_libxml2.c +endif +tests_test_sasl2_syntax_LDADD = $(PARSER_LIBS) + tests_test_scram_SOURCES = tests/test_scram.c tests/test.c src/sha1.c \ src/sha256.c src/sha512.c tests_test_scram_CFLAGS = $(STROPHE_FLAGS) -I$(top_srcdir)/src diff --git a/tests/test_sasl2_syntax.c b/tests/test_sasl2_syntax.c new file mode 100644 index 00000000..f0b53238 --- /dev/null +++ b/tests/test_sasl2_syntax.c @@ -0,0 +1,345 @@ +/* SPDX-License-Identifier: MIT OR GPL-3.0-only */ +/* test_sasl2_syntax.c +** libstrophe XMPP client library -- test routines for SASL2 syntax +** +** Copyright (C) 2024-2026 libstrophe contributors +** +** 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 +#include + +#include "strophe.h" +#include "common.h" +#include "sasl.h" +#include "scram.h" +#include "test.h" + +/* stubs to build test without whole libstrophe */ + +static xmpp_stanza_t *last_sent_stanza = NULL; + +void send_stanza(xmpp_conn_t *conn, + xmpp_stanza_t *stanza, + xmpp_send_queue_owner_t owner) +{ + (void)conn; + (void)owner; + if (last_sent_stanza) + xmpp_stanza_release(last_sent_stanza); + last_sent_stanza = xmpp_stanza_copy(stanza); + xmpp_stanza_release(stanza); +} + +void handler_add(xmpp_conn_t *conn, + xmpp_handler handler, + const char *ns, + const char *name, + const char *type, + void *userdata) +{ + (void)conn; + (void)handler; + (void)ns; + (void)name; + (void)type; + (void)userdata; +} + +void conn_disconnect(xmpp_conn_t *conn) +{ + (void)conn; +} + +int xmpp_conn_is_secured(xmpp_conn_t *conn) +{ + (void)conn; + return 0; +} + +tls_t *tls_new(xmpp_conn_t *conn) +{ + (void)conn; + return NULL; +} + +void tls_free(tls_t *tls) +{ + (void)tls; +} + +void tls_clear_password_cache(xmpp_conn_t *conn) +{ + (void)conn; +} + +int tls_init_channel_binding(tls_t *tls, + const char **binding_prefix, + size_t *binding_prefix_len) +{ + (void)tls; + (void)binding_prefix; + (void)binding_prefix_len; + return 0; +} + +const void *tls_get_channel_binding_data(tls_t *tls, size_t *size) +{ + (void)tls; + (void)size; + return NULL; +} + +char *tls_id_on_xmppaddr(xmpp_conn_t *conn, unsigned int n) +{ + (void)conn; + (void)n; + return NULL; +} + +unsigned int tls_id_on_xmppaddr_num(xmpp_conn_t *conn) +{ + (void)conn; + return 0; +} + +void handler_reset_timed(xmpp_conn_t *conn, int user_only) +{ + (void)conn; + (void)user_only; +} + +void handler_add_timed(xmpp_conn_t *conn, + xmpp_timed_handler handler, + unsigned long period, + void *userdata) +{ + (void)conn; + (void)handler; + (void)period; + (void)userdata; +} + +void handler_add_id(xmpp_conn_t *conn, + xmpp_handler handler, + const char *id, + void *userdata) +{ + (void)conn; + (void)handler; + (void)id; + (void)userdata; +} + +void xmpp_timed_handler_delete(xmpp_conn_t *conn, xmpp_timed_handler handler) +{ + (void)conn; + (void)handler; +} + +void xmpp_disconnect(xmpp_conn_t *conn) +{ + (void)conn; +} + +void conn_prepare_reset(xmpp_conn_t *conn, xmpp_open_handler handler) +{ + (void)conn; + (void)handler; + puts("should not reset connection in SASL2"); + assert(0); +} + +void conn_open_stream(xmpp_conn_t *conn) +{ + (void)conn; + puts("should not reset connection in SASL2"); + assert(0); +} +void send_raw_string(xmpp_conn_t *conn, const char *fmt, ...) +{ + (void)conn; + (void)fmt; +} + +#define strophe_warn(x, ...) (void)x +#define strophe_debug(x, ...) (void)x + +void strophe_error(const xmpp_ctx_t *ctx, + const char *area, + const char *fmt, + ...) +{ + (void)ctx; + (void)area; + (void)fmt; +} + +void *strophe_alloc(const xmpp_ctx_t *ctx, size_t size) +{ + (void)ctx; + return malloc(size); +} + +void strophe_free(const xmpp_ctx_t *ctx, void *p) +{ + (void)ctx; + free(p); +} + +void *strophe_realloc(const xmpp_ctx_t *ctx, void *p, size_t size) +{ + (void)ctx; + return realloc(p, size); +} + +xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t *ctx) +{ + xmpp_conn_t *conn = malloc(sizeof(*conn)); + conn->ctx = ctx; + return conn; +} + +int xmpp_conn_release(xmpp_conn_t *conn) +{ + free(conn); + return 0; +} + +/* include auth.c to access static functions */ +#include "src/auth.c" + +/* If SASL2 isn't set, it shouldn't be used */ +static int test_make_sasl_auth() +{ + xmpp_conn_t conn = {.sasl_support = SASL_MASK_PLAIN}; + xmpp_stanza_t *auth = + _make_sasl_auth(&conn, "PLAIN", "AGZvb0BiYXIuY29tAHNlY3JldA=="); + + COMPARE(xmpp_stanza_get_name(auth), "auth"); + COMPARE(xmpp_stanza_get_ns(auth), XMPP_NS_SASL); + COMPARE(xmpp_stanza_get_attribute(auth, "mechanism"), "PLAIN"); + + xmpp_stanza_release(auth); + return 0; +} + +static int test_make_sasl_auth_v2() +{ + xmpp_conn_t conn = {.sasl_support = SASL_MASK_SASL2 | SASL_MASK_PLAIN}; + xmpp_stanza_t *auth = + _make_sasl_auth(&conn, "PLAIN", "AGZvb0BiYXIuY29tAHNlY3JldA=="); + xmpp_stanza_t *initial_response = + xmpp_stanza_get_child_by_name(auth, "initial-response"); + + assert(initial_response != NULL); + COMPARE(xmpp_stanza_get_name(auth), "authenticate"); + COMPARE(xmpp_stanza_get_ns(auth), XMPP_NS_SASL2); + COMPARE(xmpp_stanza_get_attribute(auth, "mechanism"), "PLAIN"); + COMPARE(xmpp_stanza_get_ns(initial_response), XMPP_NS_SASL2); + + xmpp_stanza_release(auth); + return 0; +} + +static int test_scram_challenge_v2(xmpp_ctx_t *ctx) +{ + extern const struct hash_alg scram_sha1; + xmpp_conn_t conn = {.jid = "foo@bar.com", + .pass = "secret", + .sasl_support = SASL_MASK_SASL2 | SASL_MASK_SCRAMSHA1}; + struct scram_user_data scram_ctx = {.alg = &scram_sha1, + .first_bare = "n=foo,r=random-nonce", + .channel_binding = + strophe_strdup(ctx, "biws")}; + struct scram_user_data *scram_ctx_heap = malloc(sizeof(*scram_ctx_heap)); + xmpp_stanza_t *stanza = xmpp_stanza_new(ctx); + xmpp_stanza_t *tstanza = xmpp_stanza_new(ctx); + + *scram_ctx_heap = scram_ctx; + xmpp_stanza_set_name(stanza, "challenge"); + xmpp_stanza_set_ns(stanza, XMPP_NS_SASL2); + xmpp_stanza_set_text(tstanza, "cj1meWtvK2QycGJiRmdITkRyOXBRdG16aHZ1U2EzeTg4" + "dDBkcGNjOVU3LHM9UVNYQ1IrUTZz" + "ZWs4YmY5MixpPTQwOTY="); + xmpp_stanza_add_child(stanza, tstanza); + xmpp_stanza_release(tstanza); + + _handle_scram_challenge(&conn, stanza, scram_ctx_heap); + + assert(last_sent_stanza != NULL); + COMPARE(xmpp_stanza_get_name(last_sent_stanza), "response"); + COMPARE(xmpp_stanza_get_ns(last_sent_stanza), XMPP_NS_SASL2); + COMPARE(xmpp_stanza_get_text(last_sent_stanza), + "Yz1iaXdzLHI9ZnlrbytkMnBiYkZnSE5EcjlwUXRtemh2dVNhM3k4OHQwZHBjYzlVNy" + "xwPTZCY2VjL3kyM1N1RlhkS1VucFpZTnRSRHJIQT0="); + + xmpp_stanza_release(last_sent_stanza); + last_sent_stanza = NULL; + xmpp_stanza_release(stanza); + + return 0; +} + +static int test_sasl_result_v2_success(xmpp_ctx_t *ctx) +{ + xmpp_conn_t conn = {.sasl_support = SASL_MASK_SASL2}; + xmpp_stanza_t *stanza; + + stanza = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(stanza, "success"); + xmpp_stanza_set_ns(stanza, XMPP_NS_SASL2); + + _handle_sasl_result(&conn, stanza, "SCRAM-SHA-1"); + + /* If connection resets, the reset stub will assert failure */ + + xmpp_stanza_release(stanza); + + return 0; +} + +int main() +{ + int ret; + + printf("testing _make_sasl_auth (SASL 1.0)... "); + ret = test_make_sasl_auth(); + if (ret) { + printf("failed!\n"); + return ret; + } + printf("ok.\n"); + + printf("testing _make_sasl_auth (SASL 2.0)... "); + ret = test_make_sasl_auth_v2(); + if (ret) { + printf("failed!\n"); + return ret; + } + printf("ok.\n"); + + printf("testing _handle_scram_challenge (SASL 2.0)... "); + ret = test_scram_challenge_v2(NULL); + if (ret) { + printf("failed!\n"); + return ret; + } + printf("ok.\n"); + + printf("testing _handle_sasl_result success (SASL 2.0)... "); + ret = test_sasl_result_v2_success(NULL); + if (ret) { + printf("failed!\n"); + return ret; + } + printf("ok.\n"); + + return ret; +}