/* SPDX-License-Identifier: MIT OR GPL-3.0-only */ /* 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 or GPLv3 licenses. */ /** @file * Authentication function and handlers. */ #include #include #include #include "strophe.h" #include "common.h" #include "sasl.h" #include "sha1.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 <stream:features/> stanza. */ #define FEATURES_TIMEOUT 15000 /* 15 seconds */ #endif #ifndef BIND_TIMEOUT /** @def BIND_TIMEOUT * Time to wait for <bind/> stanza reply. */ #define BIND_TIMEOUT 15000 /* 15 seconds */ #endif #ifndef SESSION_TIMEOUT /** @def SESSION_TIMEOUT * Time to wait for <session/> 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 *conn); static void _auth_legacy(xmpp_conn_t *conn); static void _handle_open_compress(xmpp_conn_t *conn); static void _handle_open_sasl(xmpp_conn_t *conn); static void _handle_open_tls(xmpp_conn_t *conn); static int _handle_component_auth(xmpp_conn_t *conn); static int _handle_component_hs_response(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata); static int _handle_features_sasl(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata); static int _handle_features_compress(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata); static int _handle_sasl_result(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata); static int _handle_digestmd5_challenge(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata); static int _handle_digestmd5_rspauth(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata); static int _handle_scram_challenge(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata); struct scram_user_data; static int _make_scram_init_msg(struct scram_user_data *scram); static int _handle_missing_features_sasl(xmpp_conn_t *conn, void *userdata); static int _handle_missing_bind(xmpp_conn_t *conn, void *userdata); static int _handle_bind(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata); static int _handle_session(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata); static int _handle_missing_session(xmpp_conn_t *conn, void *userdata); static int _handle_missing_handshake(xmpp_conn_t *conn, void *userdata); static int _handle_sm(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata); /* stream:error handler */ static int _handle_error(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) { xmpp_stanza_t *child; const char *name; UNUSED(userdata); /* 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) strophe_free(conn->ctx, conn->stream_error->text); strophe_free(conn->ctx, conn->stream_error); } /* create stream error structure */ conn->stream_error = (xmpp_stream_error_t *)strophe_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) strophe_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 *conn, void *userdata) { UNUSED(userdata); strophe_debug(conn->ctx, "xmpp", "didn't get stream features"); /* legacy auth will be attempted */ _auth(conn); return 0; } typedef void (*text_handler)(xmpp_conn_t *conn, const char *text); static void _foreach_child(xmpp_conn_t *conn, xmpp_stanza_t *parent, const char *name, text_handler hndl) { xmpp_stanza_t *children; for (children = xmpp_stanza_get_children(parent); children; children = xmpp_stanza_get_next(children)) { const char *child_name = xmpp_stanza_get_name(children); if (child_name && strcmp(child_name, name) == 0) { char *text = xmpp_stanza_get_text(children); if (text == NULL) continue; hndl(conn, text); strophe_free(conn->ctx, text); } } } static void _handle_sasl_children(xmpp_conn_t *conn, const char *text) { if (strcasecmp(text, "PLAIN") == 0) { conn->sasl_support |= SASL_MASK_PLAIN; } else if (strcasecmp(text, "EXTERNAL") == 0 && (conn->tls_client_cert || conn->tls_client_key)) { conn->sasl_support |= SASL_MASK_EXTERNAL; } else if (strcasecmp(text, "DIGEST-MD5") == 0) { conn->sasl_support |= SASL_MASK_DIGESTMD5; } else if (strcasecmp(text, "ANONYMOUS") == 0) { conn->sasl_support |= SASL_MASK_ANONYMOUS; } else { size_t n; for (n = 0; n < scram_algs_num; ++n) { if (strcasecmp(text, scram_algs[n]->scram_name) == 0) { conn->sasl_support |= scram_algs[n]->mask; break; } } } } static int _handle_features(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) { xmpp_stanza_t *child; UNUSED(userdata); /* 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) { if (xmpp_stanza_get_child_by_name_and_ns(stanza, "starttls", XMPP_NS_TLS)) { conn->tls_support = 1; } } else { conn->tls_support = 0; } } /* 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 */ 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 *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 *conn, xmpp_stanza_t *stanza, void *userdata) { const char *name; UNUSED(userdata); name = xmpp_stanza_get_name(stanza); strophe_debug(conn->ctx, "xmpp", "handle proceedtls called for %s", name); if (strcmp(name, "proceed") == 0) { strophe_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 *conn, xmpp_stanza_t *stanza, void *userdata) { const char *name; name = xmpp_stanza_get_name(stanza); /* the server should send a or stanza */ if (strcmp(name, "failure") == 0) { strophe_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 */ 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); /* send stream tag */ conn_open_stream(conn); } else { /* got unexpected reply */ strophe_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 *conn, xmpp_stanza_t *stanza, void *userdata) { char *text; char *response; xmpp_stanza_t *auth, *authdata; const char *name; UNUSED(userdata); name = xmpp_stanza_get_name(stanza); strophe_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; } strophe_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); strophe_free(conn->ctx, response); xmpp_stanza_add_child_ex(auth, authdata, 0); handler_add(conn, _handle_digestmd5_rspauth, XMPP_NS_SASL, NULL, NULL, NULL); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); } 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 *conn, xmpp_stanza_t *stanza, void *userdata) { xmpp_stanza_t *auth; const char *name; UNUSED(userdata); name = xmpp_stanza_get_name(stanza); strophe_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); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); } else { return _handle_sasl_result(conn, stanza, "DIGEST-MD5"); } return 1; } struct scram_user_data { xmpp_conn_t *conn; int sasl_plus; char *scram_init; char *channel_binding; const char *first_bare; const struct hash_alg *alg; }; /* handle the challenge phase of SCRAM-SHA-1 auth */ static int _handle_scram_challenge(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) { char *text; char *response; xmpp_stanza_t *auth; xmpp_stanza_t *authdata; const char *name; char *challenge; struct scram_user_data *scram_ctx = (struct scram_user_data *)userdata; int rc; name = xmpp_stanza_get_name(stanza); strophe_debug(conn->ctx, "xmpp", "handle %s (challenge) called for %s", scram_ctx->alg->scram_name, 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)); strophe_free(conn->ctx, text); if (!challenge) goto err; response = sasl_scram(conn->ctx, scram_ctx->alg, scram_ctx->channel_binding, challenge, scram_ctx->first_bare, conn->jid, conn->pass); strophe_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); strophe_free(conn->ctx, response); xmpp_stanza_add_child_ex(auth, authdata, 0); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); rc = 1; /* Keep handler */ } else { /* * Free scram_ctx after calling _handle_sasl_result(). If authentication * fails, we want to try other mechanism which may be different SCRAM * mechanism. If we freed scram_ctx before the function, _auth() would * be able to allocate new scram_ctx object with the same address and * handler_add() would consider new SCRAM handler as duplicate, because * current handler is not removed yet. As result, libstrophe wouldn't * handle incoming challenge stanza. */ rc = _handle_sasl_result(conn, stanza, (void *)scram_ctx->alg->scram_name); strophe_free_and_null(conn->ctx, scram_ctx->channel_binding); strophe_free_and_null(conn->ctx, scram_ctx->scram_init); strophe_free(conn->ctx, scram_ctx); } return rc; err_release_auth: xmpp_stanza_release(auth); err_free_response: strophe_free(conn->ctx, response); err: strophe_free_and_null(conn->ctx, scram_ctx->channel_binding); strophe_free_and_null(conn->ctx, scram_ctx->scram_init); strophe_free(conn->ctx, scram_ctx); disconnect_mem_error(conn); return 0; } static int _make_scram_init_msg(struct scram_user_data *scram) { xmpp_conn_t *conn = scram->conn; xmpp_ctx_t *ctx = conn->ctx; const void *binding_data; const char *binding_type; char *node, *message; size_t message_len, binding_type_len = 0, binding_data_len; int l, is_secured = xmpp_conn_is_secured(conn); /* This buffer must be able to hold: * "p=<10 bytes binding type>,,<36 bytes binding data>" * + alignment */ char buf[56]; if (scram->sasl_plus) { if (!is_secured) { strophe_error( ctx, "xmpp", "SASL: Server requested a -PLUS variant to authenticate, " "but the connection is not secured. This is an error on " "the server side we can't do anything about."); return -1; } if (tls_init_channel_binding(conn->tls, &binding_type, &binding_type_len)) { return -1; } /* directly account for the '=' char in 'p=' */ binding_type_len += 1; } node = xmpp_jid_node(ctx, conn->jid); if (!node) { return -1; } /* 32 bytes nonce is enough */ xmpp_rand_nonce(ctx->rand, buf, 33); message_len = strlen(node) + strlen(buf) + 8 + binding_type_len + 1; message = strophe_alloc(ctx, message_len); if (!message) { goto err_node; } /* increase length to account for 'y,,', 'n,,' or 'p,,'. * In the 'p' case the '=' sign has already been accounted for above. */ binding_type_len += 3; if (scram->sasl_plus) { l = strophe_snprintf(message, message_len, "p=%s,,n=%s,r=%s", binding_type, node, buf); } else { l = strophe_snprintf(message, message_len, "%c,,n=%s,r=%s", is_secured ? 'y' : 'n', node, buf); } if (l < 0 || (size_t)l >= message_len) { goto err_msg; } if (binding_type_len > sizeof(buf)) { goto err_msg; } /* Make `first_bare` point to the 'n' of 'n=' of the * client-first-message */ scram->first_bare = message + binding_type_len; memcpy(buf, message, binding_type_len); if (scram->sasl_plus) { binding_data = tls_get_channel_binding_data(conn->tls, &binding_data_len); if (!binding_data) { goto err_msg; } if (binding_data_len > sizeof(buf) - binding_type_len) { strophe_error(ctx, "xmpp", "Channel binding data is too long (%zu)", binding_data_len); goto err_msg; } memcpy(&buf[binding_type_len], binding_data, binding_data_len); binding_type_len += binding_data_len; } scram->channel_binding = xmpp_base64_encode(ctx, (void *)buf, binding_type_len); memset(buf, 0, binding_type_len); strophe_free(ctx, node); scram->scram_init = message; return 0; err_msg: strophe_free(ctx, message); err_node: strophe_free(ctx, node); return -1; } static xmpp_stanza_t *_make_starttls(xmpp_conn_t *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 *conn, const char *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 *conn) { xmpp_stanza_t *auth; xmpp_stanza_t *authdata; struct scram_user_data *scram_ctx; char *authid; 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 { strophe_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); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); /* TLS was tried, unset flag */ conn->tls_support = 0; /* _auth() will be called later */ return; } if (conn->tls_mandatory && !xmpp_conn_is_secured(conn)) { strophe_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"); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); /* SASL ANONYMOUS was tried, unset flag */ 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, "="); } 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); handler_add(conn, _handle_sasl_result, XMPP_NS_SASL, NULL, NULL, "EXTERNAL"); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); /* SASL EXTERNAL was tried, unset flag */ conn->sasl_support &= ~SASL_MASK_EXTERNAL; } else if (anonjid) { strophe_error(conn->ctx, "auth", "No node in JID, and SASL ANONYMOUS unsupported."); xmpp_disconnect(conn); } else if (conn->pass == NULL) { strophe_error( conn->ctx, "auth", "Password hasn't been set, and SASL ANONYMOUS unsupported."); xmpp_disconnect(conn); } else if (conn->sasl_support & SASL_MASK_SCRAM) { size_t n; scram_ctx = strophe_alloc(conn->ctx, sizeof(*scram_ctx)); memset(scram_ctx, 0, sizeof(*scram_ctx)); for (n = 0; n < scram_algs_num; ++n) { if (conn->sasl_support & scram_algs[n]->mask) { scram_ctx->alg = scram_algs[n]; break; } } 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; } str = xmpp_base64_encode(conn->ctx, (unsigned char *)scram_ctx->scram_init, strlen(scram_ctx->scram_init)); 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); 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, (void *)scram_ctx); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); /* 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"); if (!auth) { disconnect_mem_error(conn); return; } handler_add(conn, _handle_digestmd5_challenge, XMPP_NS_SASL, NULL, NULL, NULL); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); /* 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); strophe_free(conn->ctx, str); strophe_free(conn->ctx, authid); xmpp_stanza_add_child_ex(auth, authdata, 0); handler_add(conn, _handle_sasl_result, XMPP_NS_SASL, NULL, NULL, "PLAIN"); send_stanza(conn, auth, XMPP_QUEUE_STROPHE); /* 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 { strophe_error(conn->ctx, "auth", "Cannot authenticate with known methods"); xmpp_disconnect(conn); } } static void _stream_negotiation_success(xmpp_conn_t *conn) { tls_clear_password_cache(conn); conn->stream_negotiation_completed = 1; /* call connection handler */ conn->conn_handler(conn, XMPP_CONN_CONNECT, 0, NULL, conn->userdata); } /** 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 and * . This function is not intended for use outside * of Strophe. * * @param conn a Strophe connection object */ void auth_handle_open(xmpp_conn_t *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 */ 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 *conn) { /* setup handlers for incoming */ 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 *conn) { strophe_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); } /* called when stream:stream tag received after compression has been enabled */ static void _handle_open_compress(xmpp_conn_t *conn) { strophe_debug(conn->ctx, "xmpp", "Reopened stream successfully."); /* setup stream:features handlers */ handler_add(conn, _handle_features_compress, XMPP_NS_STREAMS, "features", NULL, NULL); handler_add_timed(conn, _handle_missing_features, FEATURES_TIMEOUT, NULL); } static int _do_bind(xmpp_conn_t *conn, xmpp_stanza_t *bind) { xmpp_stanza_t *iq, *res, *text; char *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) { xmpp_stanza_release(bind); 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 */ strophe_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_ex(res, text, 0); xmpp_stanza_add_child_ex(bind, res, 0); strophe_free(conn->ctx, resource); } xmpp_stanza_add_child_ex(iq, bind, 0); /* send bind request */ send_stanza(conn, iq, XMPP_QUEUE_STROPHE); return 0; } static int _handle_compress_result(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata) { const char *name = xmpp_stanza_get_name(stanza); UNUSED(userdata); if (!name) return 0; if (strcmp(name, "compressed") == 0) { /* Stream compression enabled, we need to restart the stream */ strophe_debug(conn->ctx, "xmpp", "Stream compression enabled"); /* reset parser */ conn_prepare_reset(conn, _handle_open_sasl); /* make compression effective */ compression_init(conn); /* send stream tag */ conn_open_stream(conn); } return 0; } static int _handle_features_compress(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) { const char *compress = "zlib"; xmpp_stanza_t *child; /* remove missing features handler */ xmpp_timed_handler_delete(conn, _handle_missing_features); /* check for compression */ child = xmpp_stanza_get_child_by_name_and_ns(stanza, "compression", XMPP_NS_FEATURE_COMPRESSION); if (conn->compression.allowed && child) { _foreach_child(conn, child, "method", compression_handle_feature_children); } if (conn->compression.supported) { send_raw(conn, compress, strlen(compress), XMPP_QUEUE_STROPHE, NULL); handler_add(conn, _handle_compress_result, XMPP_NS_COMPRESSION, NULL, NULL, NULL); } else { return _handle_features_sasl(conn, stanza, userdata); } return 0; } static int _handle_features_sasl(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) { xmpp_stanza_t *bind, *session; xmpp_stanza_t *resume; char h[11]; UNUSED(userdata); /* Remove missing features handler */ xmpp_timed_handler_delete(conn, _handle_missing_features_sasl); /* Check whether resource binding is required */ bind = xmpp_stanza_get_child_by_name_and_ns(stanza, "bind", XMPP_NS_BIND); if (bind) { conn->bind_required = 1; bind = xmpp_stanza_copy(bind); if (!bind) { disconnect_mem_error(conn); return 0; } } else { conn->bind_required = 0; } /* Check whether session establishment is required. * * The mechanism is deprecated, but we still support it. * * RFC3921 contains Ch. 3 "Session Establishment". * * RFC6121 removes this and explains in Ch. 1.4: * "Interoperability Note: [...] Implementation and deployment experience * has shown that this additional step is unnecessary. [...]" */ session = xmpp_stanza_get_child_by_name_and_ns(stanza, "session", XMPP_NS_SESSION); if (session) { conn->session_required = xmpp_stanza_get_child_by_name(session, "optional") == NULL; } /* Check stream-management support */ if (xmpp_stanza_get_child_by_name_and_ns(stanza, "sm", XMPP_NS_SM)) { conn->sm_state->sm_support = 1; } /* We are expecting either and optionally since this is a XMPP style connection or we the previous session */ /* Check whether we can the previous session */ if (!conn->sm_disable && conn->sm_state->can_resume && conn->sm_state->previd && conn->sm_state->bound_jid) { resume = xmpp_stanza_new(conn->ctx); if (!resume) { disconnect_mem_error(conn); return 0; } conn->sm_state->bind = bind; conn->sm_state->resume = 1; xmpp_stanza_set_name(resume, "resume"); xmpp_stanza_set_ns(resume, XMPP_NS_SM); xmpp_stanza_set_attribute(resume, "previd", conn->sm_state->previd); strophe_snprintf(h, sizeof(h), "%u", conn->sm_state->sm_handled_nr); xmpp_stanza_set_attribute(resume, "h", h); send_stanza(conn, resume, XMPP_QUEUE_SM_STROPHE); handler_add(conn, _handle_sm, XMPP_NS_SM, NULL, NULL, NULL); } /* if bind is required, go ahead and start it */ else if (conn->bind_required) { /* bind resource */ _do_bind(conn, bind); } else { /* can't bind, disconnect */ if (bind) { xmpp_stanza_release(bind); } strophe_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 *conn, void *userdata) { UNUSED(userdata); strophe_error(conn->ctx, "xmpp", "Did not receive stream features " "after SASL authentication."); xmpp_disconnect(conn); return 0; } static void _session_start(xmpp_conn_t *conn) { xmpp_stanza_t *session; xmpp_stanza_t *iq = xmpp_iq_new(conn->ctx, "set", "_xmpp_session1"); if (!iq) { disconnect_mem_error(conn); return; } session = xmpp_stanza_new(conn->ctx); if (!session) { xmpp_stanza_release(iq); disconnect_mem_error(conn); return; } /* setup response handlers */ handler_add_id(conn, _handle_session, "_xmpp_session1", NULL); handler_add_timed(conn, _handle_missing_session, SESSION_TIMEOUT, NULL); xmpp_stanza_set_name(session, "session"); xmpp_stanza_set_ns(session, XMPP_NS_SESSION); xmpp_stanza_add_child_ex(iq, session, 0); /* send session establishment request */ send_stanza(conn, iq, XMPP_QUEUE_STROPHE); } static void _sm_enable(xmpp_conn_t *conn) { xmpp_stanza_t *enable = xmpp_stanza_new(conn->ctx); if (!enable) { disconnect_mem_error(conn); return; } xmpp_stanza_set_name(enable, "enable"); xmpp_stanza_set_ns(enable, XMPP_NS_SM); if (!conn->sm_state->dont_request_resume) xmpp_stanza_set_attribute(enable, "resume", "true"); handler_add(conn, _handle_sm, XMPP_NS_SM, NULL, NULL, NULL); send_stanza(conn, enable, XMPP_QUEUE_SM_STROPHE); conn->sm_state->sm_sent_nr = 0; conn->sm_state->sm_enabled = 1; trigger_sm_callback(conn); } static int _handle_bind(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) { const char *type; xmpp_stanza_t *binding, *jid_stanza; UNUSED(userdata); /* 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) { strophe_error(conn->ctx, "xmpp", "Binding failed."); xmpp_disconnect(conn); } else if (type && strcmp(type, "result") == 0) { binding = xmpp_stanza_get_child_by_name(stanza, "bind"); strophe_debug(conn->ctx, "xmpp", "Bind successful."); if (binding) { 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) { _session_start(conn); } /* send enable directly after the bind request */ else if (conn->sm_state->sm_support && !conn->sm_disable) { _sm_enable(conn); } /* if there's no xmpp session required and we didn't try to enable * stream-management, we're done here and the stream-negotiation was * successful */ else { _stream_negotiation_success(conn); } } else { strophe_error(conn->ctx, "xmpp", "Server sent malformed bind reply."); xmpp_disconnect(conn); } return 0; } static int _handle_missing_bind(xmpp_conn_t *conn, void *userdata) { UNUSED(userdata); strophe_error(conn->ctx, "xmpp", "Server did not reply to bind request."); xmpp_disconnect(conn); return 0; } static int _handle_session(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) { const char *type; UNUSED(userdata); /* 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) { strophe_error(conn->ctx, "xmpp", "Session establishment failed."); xmpp_disconnect(conn); } else if (type && strcmp(type, "result") == 0) { strophe_debug(conn->ctx, "xmpp", "Session establishment successful."); if (conn->sm_state->sm_support && !conn->sm_disable) { _sm_enable(conn); } else { _stream_negotiation_success(conn); } } else { strophe_error(conn->ctx, "xmpp", "Server sent malformed session reply."); xmpp_disconnect(conn); } return 0; } static int _handle_missing_session(xmpp_conn_t *conn, void *userdata) { UNUSED(userdata); strophe_error(conn->ctx, "xmpp", "Server did not reply to session request."); xmpp_disconnect(conn); return 0; } static int _handle_missing_legacy(xmpp_conn_t *conn, void *userdata) { UNUSED(userdata); strophe_error(conn->ctx, "xmpp", "Server did not reply to legacy " "authentication request."); xmpp_disconnect(conn); return 0; } static int _get_h_attribute(xmpp_stanza_t *stanza, unsigned long *ul_h) { const char *h = xmpp_stanza_get_attribute(stanza, "h"); if (!h || string_to_ul(h, ul_h)) { strophe_error( stanza->ctx, "xmpp", "SM error: failed parsing 'h', \"%s\" got converted to %llu.", STR_MAYBE_NULL(h), *ul_h); return -1; } return 0; } static void _sm_queue_cleanup(xmpp_conn_t *conn, unsigned long ul_h) { xmpp_send_queue_t *e; while ((e = peek_queue_front(&conn->sm_state->sm_queue))) { if (e->sm_h >= ul_h) break; e = pop_queue_front(&conn->sm_state->sm_queue); strophe_free(conn->ctx, queue_element_free(conn->ctx, e)); } } static void _sm_queue_resend(xmpp_conn_t *conn) { xmpp_send_queue_t *e; while ((e = pop_queue_front(&conn->sm_state->sm_queue))) { /* Re-send what was already sent out and is still in the * SM queue (i.e. it hasn't been ACK'ed by the server) */ strophe_debug_verbose(2, conn->ctx, "conn", "SM_Q_RESEND: %p, h=%lu", e, e->sm_h); send_raw(conn, e->data, e->len, e->owner, NULL); strophe_free(conn->ctx, queue_element_free(conn->ctx, e)); } } static int _handle_sm(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata) { xmpp_stanza_t *failed_cause, *bind = NULL; const char *name, *id, *previd, *resume, *cause; unsigned long ul_h = 0; UNUSED(userdata); name = xmpp_stanza_get_name(stanza); if (!name) goto err_sm; if (strcmp(name, "enabled") == 0) { conn->sm_state->sm_handled_nr = 0; resume = xmpp_stanza_get_attribute(stanza, "resume"); if (resume && (strcasecmp(resume, "true") || strcmp(resume, "1"))) { id = xmpp_stanza_get_attribute(stanza, "id"); if (!id) { strophe_error(conn->ctx, "xmpp", "SM error: server said it can resume, but " "didn't provide an ID."); name = NULL; goto err_sm; } conn->sm_state->can_resume = 1; conn->sm_state->id = strophe_strdup(conn->ctx, id); } /* We maybe have stuff in the SM queue if we tried to resume, but the * server doesn't remember all details of our session, but the `h` was * still available. */ _sm_queue_resend(conn); _stream_negotiation_success(conn); } else if (strcmp(name, "resumed") == 0) { previd = xmpp_stanza_get_attribute(stanza, "previd"); if (!previd || strcmp(previd, conn->sm_state->previd)) { strophe_error(conn->ctx, "xmpp", "SM error: previd didn't match, ours is \"%s\".", conn->sm_state->previd); name = NULL; goto err_sm; } if (_get_h_attribute(stanza, &ul_h)) { name = NULL; goto err_sm; } conn->sm_state->sm_enabled = 1; conn->sm_state->id = conn->sm_state->previd; conn->sm_state->previd = NULL; conn->bound_jid = conn->sm_state->bound_jid; conn->sm_state->bound_jid = NULL; if (conn->sm_state->sm_queue.head) conn->sm_state->sm_sent_nr = conn->sm_state->sm_queue.head->sm_h; else conn->sm_state->sm_sent_nr = ul_h; _sm_queue_cleanup(conn, ul_h); _sm_queue_resend(conn); strophe_debug(conn->ctx, "xmpp", "Session resumed successfully."); _stream_negotiation_success(conn); } else if (strcmp(name, "failed") == 0) { name = NULL; conn->sm_state->sm_enabled = 0; failed_cause = xmpp_stanza_get_child_by_ns(stanza, XMPP_NS_STANZAS_IETF); if (!failed_cause) goto err_sm; cause = xmpp_stanza_get_name(failed_cause); if (!cause) goto err_sm; if (!strcmp(cause, "item-not-found")) { if (conn->sm_state->resume) { /* It's no error if there's no `h` attribute included * but if there is, it gives a hint at what the server * already received. */ if (!_get_h_attribute(stanza, &ul_h)) { /* In cases there's no `h` included, drop all elements. */ ul_h = (unsigned long)-1; } _sm_queue_cleanup(conn, ul_h); } } else if (!strcmp(cause, "feature-not-implemented")) { conn->sm_state->resume = 0; conn->sm_state->can_resume = 0; /* remember that the server reports having support * for resumption, but actually it doesn't ... */ conn->sm_state->dont_request_resume = 1; } bind = conn->sm_state->bind; conn->sm_state->bind = NULL; reset_sm_state(conn->sm_state); _do_bind(conn, bind); } else { /* unknown stanza received */ name = NULL; } trigger_sm_callback(conn); err_sm: if (!name) { char *err = "Couldn't convert stanza to text!"; char *buf; size_t buflen; switch (xmpp_stanza_to_text(stanza, &buf, &buflen)) { case XMPP_EOK: break; case XMPP_EMEM: disconnect_mem_error(conn); return 0; default: buf = err; break; } strophe_warn(conn->ctx, "xmpp", "SM error: Stanza received was: %s", buf); if (buf != err) strophe_free(conn->ctx, buf); /* Don't disable for cases, they're no hard errors */ conn->sm_state->sm_enabled = bind != NULL; } return 0; } static int _handle_legacy(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) { const char *type; const char *name; UNUSED(userdata); /* 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) { strophe_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 */ strophe_error(conn->ctx, "xmpp", "Legacy client authentication failed."); xmpp_disconnect(conn); } else if (strcmp(type, "result") == 0) { /* auth succeeded */ strophe_debug(conn->ctx, "xmpp", "Legacy auth succeeded."); _stream_negotiation_success(conn); } else { strophe_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; strophe_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_ex(iq, query, 0); child = xmpp_stanza_new(conn->ctx); if (!child) goto err_free; xmpp_stanza_set_name(child, "username"); xmpp_stanza_add_child_ex(query, child, 0); 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); strophe_free(conn->ctx, str); xmpp_stanza_add_child_ex(child, authdata, 0); child = xmpp_stanza_new(conn->ctx); if (!child) goto err_free; xmpp_stanza_set_name(child, "password"); xmpp_stanza_add_child_ex(query, child, 0); authdata = xmpp_stanza_new(conn->ctx); if (!authdata) goto err_free; xmpp_stanza_set_text(authdata, conn->pass); xmpp_stanza_add_child_ex(child, authdata, 0); child = xmpp_stanza_new(conn->ctx); if (!child) goto err_free; xmpp_stanza_set_name(child, "resource"); xmpp_stanza_add_child_ex(query, child, 0); 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); strophe_free(conn->ctx, str); } else { xmpp_stanza_release(authdata); xmpp_stanza_release(iq); strophe_error(conn->ctx, "auth", "Cannot authenticate without resource"); xmpp_disconnect(conn); return; } xmpp_stanza_add_child_ex(child, authdata, 0); handler_add_id(conn, _handle_legacy, "_xmpp_auth1", NULL); handler_add_timed(conn, _handle_missing_legacy, LEGACY_TIMEOUT, NULL); send_stanza(conn, iq, XMPP_QUEUE_STROPHE); return; err_free: xmpp_stanza_release(iq); err: disconnect_mem_error(conn); } void auth_handle_component_open(xmpp_conn_t *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) { strophe_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 *conn) { uint8_t md_value[SHA1_DIGEST_SIZE]; SHA1_CTX mdctx; char *digest; size_t i; if (conn->stream_id == NULL) { strophe_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 = strophe_alloc(conn->ctx, 2 * sizeof(md_value) + 1); if (digest) { /* convert the digest into string representation */ for (i = 0; i < sizeof(md_value); i++) strophe_snprintf(digest + i * 2, 3, "%02x", md_value[i]); digest[2 * sizeof(md_value)] = '\0'; strophe_debug(conn->ctx, "auth", "Digest: %s, len: %d", digest, strlen(digest)); /* Send the digest to the server */ send_raw_string(conn, "%s", XMPP_NS_COMPONENT, digest); strophe_debug(conn->ctx, "auth", "Sent component handshake to the server."); strophe_free(conn->ctx, digest); } else { strophe_debug(conn->ctx, "auth", "Couldn't allocate memory for component " "handshake digest."); return XMPP_EMEM; } return 0; } /* Check if the received stanza is and set auth to true * and fire connection handler. */ int _handle_component_hs_response(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) { const char *name; UNUSED(userdata); 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) { strophe_debug(conn->ctx, "auth", "Handshake failed: %s", msg); strophe_free(conn->ctx, msg); } xmpp_disconnect(conn); return XMPP_EINT; } else { _stream_negotiation_success(conn); } /* 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 *conn, void *userdata) { UNUSED(userdata); strophe_error(conn->ctx, "xmpp", "Server did not reply to handshake request."); xmpp_disconnect(conn); return 0; } void auth_handle_open_raw(xmpp_conn_t *conn) { handler_reset_timed(conn, 0); /* user handlers are not called before stream negotiation has completed. */ _stream_negotiation_success(conn); } void auth_handle_open_stub(xmpp_conn_t *conn) { strophe_warn(conn->ctx, "auth", "Stub callback is called."); }