diff --git a/.gitignore b/.gitignore index a3ab96c..69f56fd 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ examples/active examples/basic examples/bot examples/component +examples/register examples/roster examples/uuid examples/vcard diff --git a/ChangeLog b/ChangeLog index 95adbf3..0caf124 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,7 @@ 0.10.0 - c-ares support - LibreSSL support + - examples/register implements XEP-0077 - New API: - xmpp_stanza_get_child_by_name_and_ns() - xmpp_conn_is_connecting() diff --git a/Makefile.am b/Makefile.am index a435f55..8ca940a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -101,7 +101,8 @@ noinst_PROGRAMS = \ examples/component \ examples/roster \ examples/uuid \ - examples/vcard + examples/vcard \ + examples/register examples_active_SOURCES = examples/active.c examples_active_CFLAGS = $(STROPHE_FLAGS) @@ -124,6 +125,9 @@ examples_uuid_LDADD = $(STROPHE_LIBS) examples_vcard_SOURCES = examples/vcard.c examples_vcard_CFLAGS = $(STROPHE_FLAGS) examples_vcard_LDADD = $(STROPHE_LIBS) +examples_register_SOURCES = examples/register.c +examples_register_CFLAGS = $(STROPHE_FLAGS) +examples_register_LDADD = $(STROPHE_LIBS) ## Tests diff --git a/examples/register.c b/examples/register.c new file mode 100644 index 0000000..70a8014 --- /dev/null +++ b/examples/register.c @@ -0,0 +1,357 @@ +/* register.c + * strophe XMPP client library -- In-band registration (XEP-0077) + * + * Copyright (C) 2020 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. + */ + +/* + * This example uses a "raw" connection to establish connection to a server + * without account. Then it queries server to register new account according + * to XEP-0077. + * + * How to use it. After the application connects and receives instructions + * from the server, user will be prompted to type information such as + * username, password, etc. Press enter without typing if you want to skip + * a field and not to send it to the server. + * + * Notice, the example doesn't implement forms. Therefore, it won't work + * in complicated scenarios. + */ + +#include +#include +#include + +#include + +typedef struct { + xmpp_ctx_t *ctx; + const char *jid; +} xmpp_reg_t; + +#define FEATURES_TIMEOUT 5000 /* 5 seconds */ + +static void iq_reg_send_form(xmpp_reg_t *reg, + xmpp_conn_t *conn, + xmpp_stanza_t *stanza) +{ + xmpp_ctx_t *ctx = reg->ctx; + xmpp_stanza_t *query; + xmpp_stanza_t *next; + xmpp_stanza_t *elem; + xmpp_stanza_t *text; + xmpp_stanza_t *iq; + const char *name; + ssize_t rc; + size_t size; + char *s; + + query = xmpp_stanza_get_child_by_name(stanza, "query"); + if (!query) { + xmpp_disconnect(conn); + return; + } + + next = xmpp_stanza_get_children(query); + query = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(query, "query"); + xmpp_stanza_set_ns(query, XMPP_NS_REGISTER); + while (next) { + name = xmpp_stanza_get_name(next); + if (name && strcmp(name, "instructions") == 0) { + s = xmpp_stanza_get_text(next); + printf("instructions: %s\n", s); + xmpp_free(ctx, s); + } else { + printf("%s: ", name); + s = NULL; + size = 0; + rc = getline(&s, &size, stdin); + if (rc > 0 && s[rc - 1] == '\n') + s[rc - 1] = '\0'; + if (rc > 0 && strlen(s) > 0) { + elem = xmpp_stanza_new(ctx); + text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(text, s); + xmpp_stanza_set_name(elem, name); + xmpp_stanza_add_child(elem, text); + xmpp_stanza_add_child(query, elem); + xmpp_stanza_release(text); + xmpp_stanza_release(elem); + } + free(s); + } + next = xmpp_stanza_get_next(next); + } + + if (xmpp_stanza_get_children(query) == NULL) { + fprintf(stderr, "DEBUG: nothing to send\n"); + xmpp_disconnect(conn); + } else { + iq = xmpp_iq_new(ctx, "set", "reg2"); + xmpp_stanza_add_child(iq, query); + xmpp_stanza_release(query); + xmpp_send(conn, iq); + xmpp_stanza_release(iq); + } +} + +static int iq_reg2_cb(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + const char *type; + + type = xmpp_stanza_get_type(stanza); + if (!type || strcmp(type, "error") == 0) { + fprintf(stderr, "DEBUG: error during registration\n"); + goto quit; + } + + if (strcmp(type, "result") != 0) { + fprintf(stderr, "DEBUG: expected type 'result', but got %s\n", type); + goto quit; + } + + fprintf(stderr, "DEBUG: successful registration\n"); + +quit: + xmpp_disconnect(conn); + + return 0; +} + +static int iq_reg_cb(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_reg_t *reg = (xmpp_reg_t *)userdata; + xmpp_stanza_t *registered = NULL; + xmpp_stanza_t *query; + const char *type; + + type = xmpp_stanza_get_type(stanza); + if (!type || strcmp(type, "error") == 0) { + fprintf(stderr, "DEBUG: error during registration\n"); + xmpp_disconnect(conn); + goto quit; + } + + if (strcmp(type, "result") != 0) { + fprintf(stderr, "DEBUG: expected type 'result', but got %s\n", type); + xmpp_disconnect(conn); + goto quit; + } + + query = xmpp_stanza_get_child_by_name(stanza, "query"); + if (query) + registered = xmpp_stanza_get_child_by_name(query, "registered"); + if (registered != NULL) { + fprintf(stderr, "DEBUG: already registered\n"); + xmpp_disconnect(conn); + goto quit; + } + xmpp_id_handler_add(conn, iq_reg2_cb, "reg2", reg); + iq_reg_send_form(reg, conn, stanza); + +quit: + return 0; +} + +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) +{ + const char *name = xmpp_stanza_get_name(stanza); + + if (strcmp(name, "proceed") == 0) { + fprintf(stderr, "DEBUG: proceeding with TLS\n"); + if (xmpp_conn_tls_start(conn) == 0) { + xmpp_handler_delete(conn, _handle_error); + xmpp_conn_open_stream_default(conn); + } else { + fprintf(stderr, "DEBUG: TLS failed\n"); + /* failed tls spoils the connection, so disconnect */ + 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_reg_t *reg = (xmpp_reg_t *)userdata; + xmpp_ctx_t *ctx = reg->ctx; + xmpp_stanza_t *child; + xmpp_stanza_t *iq; + char *domain; + + xmpp_timed_handler_delete(conn, _handle_missing_features); + + /* 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)) { + fprintf(stderr, "DEBUG: server supports TLS, try to establish\n"); + 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, NULL); + xmpp_send(conn, child); + xmpp_stanza_release(child); + return 0; + } + + /* check whether server supports in-band registration */ + child = xmpp_stanza_get_child_by_name(stanza, "register"); + if (child && strcmp(xmpp_stanza_get_ns(child), XMPP_NS_REGISTER) == 0) { + fprintf(stderr, "DEBUG: server doesn't support in-band registration\n"); + xmpp_disconnect(conn); + return 0; + } + + fprintf(stderr, "DEBUG: server supports in-band registration\n"); + domain = xmpp_jid_domain(ctx, reg->jid); + iq = xmpp_iq_new(ctx, "get", "reg1"); + xmpp_stanza_set_to(iq, domain); + child = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(child, "query"); + xmpp_stanza_set_ns(child, XMPP_NS_REGISTER); + xmpp_stanza_add_child(iq, child); + + xmpp_handler_add(conn, iq_reg_cb, XMPP_NS_REGISTER, "iq", NULL, reg); + xmpp_send(conn, iq); + + xmpp_free(ctx, domain); + xmpp_stanza_release(child); + xmpp_stanza_release(iq); + + 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_reg_t *reg = (xmpp_reg_t *)userdata; + 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, reg); + xmpp_timed_handler_add(conn, _handle_missing_features, + FEATURES_TIMEOUT, NULL); + } else { + fprintf(stderr, "DEBUG: disconnected\n"); + xmpp_stop(reg->ctx); + } +} + +xmpp_reg_t *xmpp_reg_new(void) +{ + xmpp_reg_t *reg; + + reg = malloc(sizeof(*reg)); + if (reg != NULL) { + memset(reg, 0, sizeof(*reg)); + } + return reg; +} + +void xmpp_reg_release(xmpp_reg_t *reg) +{ + free(reg); +} + +int main(int argc, char **argv) +{ + xmpp_ctx_t *ctx; + xmpp_conn_t *conn; + xmpp_log_t *log; + xmpp_reg_t *reg; + const char *jid; + const char *host = NULL; + char *domain; + + if (argc < 2 || argc > 3) { + fprintf(stderr, "Usage: %s []\n", argv[0]); + return 1; + } + + jid = argv[1]; + if (argc > 2) + host = argv[2]; + + /* + * 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); + + /* jid can be a jid or domain for "raw" connection */ + domain = xmpp_jid_domain(ctx, jid); + xmpp_conn_set_jid(conn, domain); + xmpp_free(ctx, domain); + + /* private data */ + reg = xmpp_reg_new(); + reg->ctx = ctx; + reg->jid = jid; + + xmpp_connect_raw(conn, host, 0, conn_handler, reg); + xmpp_run(ctx); + + /* release private data */ + xmpp_reg_release(reg); + + xmpp_conn_release(conn); + xmpp_ctx_free(ctx); + xmpp_shutdown(); + + return 0; +} diff --git a/strophe.h b/strophe.h index 8426be9..a348f13 100644 --- a/strophe.h +++ b/strophe.h @@ -71,6 +71,10 @@ extern "C" { * Namespace definition for 'jabber:iq:roster'. */ #define XMPP_NS_ROSTER "jabber:iq:roster" +/** @def XMPP_NS_REGISTER + * Namespace definition for 'jabber:iq:register'. + */ +#define XMPP_NS_REGISTER "jabber:iq:register" /* error defines */ /** @def XMPP_EOK