diff --git a/.gitignore b/.gitignore index abceee2..592b7f9 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ examples/active examples/basic examples/bot examples/component +examples/ext_auth examples/roster examples/uuid examples/vcard diff --git a/Makefile.am b/Makefile.am index ac9b19b..fd38e5b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -98,6 +98,7 @@ noinst_PROGRAMS = \ examples/basic \ examples/bot \ examples/component \ + examples/ext_auth \ examples/roster \ examples/uuid \ examples/vcard @@ -114,6 +115,9 @@ examples_bot_LDADD = $(STROPHE_LIBS) examples_component_SOURCES = examples/component.c examples_component_CFLAGS = $(STROPHE_FLAGS) examples_component_LDADD = $(STROPHE_LIBS) +examples_ext_auth_SOURCES = examples/ext_auth.c +examples_ext_auth_CFLAGS = $(STROPHE_FLAGS) +examples_ext_auth_LDADD = $(STROPHE_LIBS) examples_roster_SOURCES = examples/roster.c examples_roster_CFLAGS = $(STROPHE_FLAGS) examples_roster_LDADD = $(STROPHE_LIBS) diff --git a/examples/ext_auth.c b/examples/ext_auth.c new file mode 100644 index 0000000..d86962f --- /dev/null +++ b/examples/ext_auth.c @@ -0,0 +1,290 @@ +/* ext_auth.c + * strophe XMPP client library -- PLAIN mechanism example with a raw connection + * + * Copyright (C) 2016 Dmitry Podgorny + * + * This software is provided AS-IS with no warranty, either express + * or implied. + * + * This program is dual licensed under the MIT and GPLv3 licenses. + */ + +#include +#include +#include + +#include + +typedef struct { + xmpp_ctx_t *ctx; + const char *jid; + const char *pass; + char *domain; + int authenticated; +} xmpp_auth_t; + +#define FEATURES_TIMEOUT 5000 /* 5 seconds */ + +static int _handle_error(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + fprintf(stderr, "DEBUG: received stream error\n"); + xmpp_disconnect(conn); + + return 0; +} + +static int _handle_proceedtls_default(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_auth_t *auth = (xmpp_auth_t *)userdata; + const char *name = xmpp_stanza_get_name(stanza); + + (void)auth; + if (strcmp(name, "proceed") == 0) { + if (xmpp_conn_tls_start(conn) == 0) { + xmpp_handler_delete(conn, _handle_error); + xmpp_conn_open_stream_default(conn); + } else { + /* failed tls spoils the connection, so disconnect */ + xmpp_disconnect(conn); + } + } else + xmpp_disconnect(conn); + + return 0; +} + +static int _plain_mechanim_exists(xmpp_ctx_t *ctx, xmpp_stanza_t *features) +{ + xmpp_stanza_t *mechanisms; + xmpp_stanza_t *child; + char *text; + int found = 0; + + mechanisms = xmpp_stanza_get_child_by_name(features, "mechanisms"); + child = mechanisms ? xmpp_stanza_get_children(mechanisms) : NULL; + while (!found && mechanisms && child) { + /* TODO xmpp_stanza_get_name(child) is "mechanism" */ + text = xmpp_stanza_get_text(child); + found = text && strcmp(text, "PLAIN") == 0; + xmpp_free(ctx, text); + child = xmpp_stanza_get_next(child); + } + return found; +} + +static char *_plain_mechanism_authdata(xmpp_auth_t *auth) +{ + char *node, *b64; + unsigned char *msg; + size_t node_len, pass_len; + + node = xmpp_jid_node(auth->ctx, auth->jid); + node_len = strlen(node); + pass_len = strlen(auth->pass); + msg = malloc(node_len + pass_len + 2); + msg[0] = '\0'; + memcpy(msg + 1, node, node_len); + msg[node_len + 1] = '\0'; + memcpy(msg + node_len + 2, auth->pass, pass_len); + b64 = xmpp_base64_encode(auth->ctx, msg, node_len + pass_len + 2); + + free(msg); + xmpp_free(auth->ctx, node); + + return b64; +} + +static int _plain_mechanism_handle_result(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_auth_t *auth = (xmpp_auth_t *)userdata; + const char *name; + + name = xmpp_stanza_get_name(stanza); + + if (strcmp(name, "success") == 0) { + /* SASL PLAIN auth successful, we need to restart the stream */ + fprintf(stderr, "DEBUG: SASL PLAIN auth successful\n"); + auth->authenticated = 1; + xmpp_handler_delete(conn, _handle_error); + xmpp_conn_open_stream_default(conn); + } else { + fprintf(stderr, "DEBUG: SASL authentication failed: %s\n", name); + xmpp_disconnect(conn); + } + return 0; +} + +static int _handle_missing_features(xmpp_conn_t * const conn, + void * const userdata) +{ + fprintf(stderr, "DEBUG: timeout\n"); + xmpp_disconnect(conn); + + return 0; +} + +static int _handle_features(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_auth_t *auth = (xmpp_auth_t *)userdata; + xmpp_ctx_t *ctx = auth->ctx; + xmpp_stanza_t *plain, *child; + char *str; + + xmpp_timed_handler_delete(conn, _handle_missing_features); + + if (auth->authenticated) { + /* after successful SASL authentication */ + /* TODO bind, session and we'are done */ + xmpp_disconnect(conn); /* XXX */ + return 0; + } + + /* secure connection if possible */ + child = xmpp_stanza_get_child_by_name(stanza, "starttls"); + if (child && (strcmp(xmpp_stanza_get_ns(child), XMPP_NS_TLS) == 0)) { + child = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(child, "starttls"); + xmpp_stanza_set_ns(child, XMPP_NS_TLS); + xmpp_handler_add(conn, _handle_proceedtls_default, + XMPP_NS_TLS, NULL, NULL, userdata); + xmpp_send(conn, child); + xmpp_stanza_release(child); + return 0; + } + + /* check if PLAIN mechanism is supported */ + if (!_plain_mechanim_exists(ctx, stanza)) { + fprintf(stderr, "DEBUG: PLAIN mechanism is NOT supported\n"); + xmpp_disconnect(conn); + return 0; + } + + /* perform authentication */ + plain = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(plain, "auth"); + xmpp_stanza_set_ns(plain, XMPP_NS_SASL); + xmpp_stanza_set_attribute(plain, "mechanism", "PLAIN"); + str = _plain_mechanism_authdata(auth); + child = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(child, str); + xmpp_free(ctx, str); + xmpp_stanza_add_child(plain, child); + xmpp_stanza_release(child); + + xmpp_handler_add(conn, _plain_mechanism_handle_result, + XMPP_NS_SASL, NULL, NULL, userdata); + xmpp_send(conn, plain); + xmpp_stanza_release(plain); + + return 0; +} + +static void conn_handler(xmpp_conn_t * const conn, + const xmpp_conn_event_t status, + const int error, + xmpp_stream_error_t * const stream_error, + void * const userdata) +{ + xmpp_auth_t *auth = (xmpp_auth_t *)userdata; + xmpp_ctx_t *ctx = auth->ctx; + int secured; + + if (status == XMPP_CONN_RAW_CONNECT) { + fprintf(stderr, "DEBUG: raw connection established\n"); + xmpp_conn_open_stream_default(conn); + } else if (status == XMPP_CONN_CONNECT) { + fprintf(stderr, "DEBUG: stream opened\n"); + secured = xmpp_conn_is_secured(conn); + fprintf(stderr, "DEBUG: connection is %s.\n", + secured ? "secured" : "NOT secured"); + + /* setup handler for stream:error */ + xmpp_handler_add(conn, _handle_error, XMPP_NS_STREAMS, + "error", NULL, NULL); + + /* setup handlers for incoming */ + xmpp_handler_add(conn, _handle_features, XMPP_NS_STREAMS, + "features", NULL, auth); + xmpp_timed_handler_add(conn, _handle_missing_features, + FEATURES_TIMEOUT, NULL); + } else { + fprintf(stderr, "DEBUG: disconnected\n"); + xmpp_stop(ctx); + } +} + +static xmpp_auth_t *xmpp_auth_new(void) +{ + xmpp_auth_t *auth; + + auth = malloc(sizeof(*auth)); + if (auth != NULL) { + memset(auth, 0, sizeof(*auth)); + } + return auth; +} + +static void xmpp_auth_release(xmpp_auth_t *auth) +{ + free(auth); +} + +int main(int argc, char **argv) +{ + xmpp_ctx_t *ctx; + xmpp_conn_t *conn; + xmpp_log_t *log; + xmpp_auth_t *auth; + const char *jid; + const char *pass; + const char *host = NULL; + + if (argc < 3 || argc > 4) { + fprintf(stderr, "Usage: %s []\n", argv[0]); + return 1; + } + + jid = argv[1]; + pass = argv[2]; + if (argc > 3) + host = argv[3]; + + /* + * Note, this example doesn't handle errors. Applications should check + * return values of non-void functions. + */ + + xmpp_initialize(); + log = xmpp_get_default_logger(XMPP_LEVEL_DEBUG); + ctx = xmpp_ctx_new(NULL, log); + conn = xmpp_conn_new(ctx); + + xmpp_conn_set_jid(conn, jid); + + /* private data */ + auth = xmpp_auth_new(); + auth->ctx = ctx; + auth->jid = jid; + auth->pass = pass; + auth->domain = xmpp_jid_domain(ctx, jid); + + xmpp_connect_raw(conn, host, 0, conn_handler, auth); + xmpp_run(ctx); + + /* release private data */ + xmpp_auth_release(auth); + + xmpp_conn_release(conn); + xmpp_ctx_free(ctx); + xmpp_shutdown(); + + return 0; +}