From: Mike Lowis Date: Wed, 2 Mar 2016 16:05:10 +0000 (-0500) Subject: Initial commit of libstrophe code which will be used as the basis for xmpp functionality X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=7f0c639392639202d82b75ba492a00bdabbe6a2b;p=archive%2Facc.git Initial commit of libstrophe code which will be used as the basis for xmpp functionality --- diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..54f7b5c --- /dev/null +++ b/Makefile @@ -0,0 +1,74 @@ +#------------------------------------------------------------------------------ +# Build Configuration +#------------------------------------------------------------------------------ +# name and version +VERSION = 0.0.1 + +# tools +CC = gcc +LD = ${CC} + +# flags +LIBS = -lexpat -lssl -lresolv +CPPFLAGS = -DVERSION="$(VERSION)" +CFLAGS = -std=gnu11 ${INCS} ${CPPFLAGS} +LDFLAGS = ${LIBS} +INCS = -Isource/ \ + -I/usr/include/ + +# commands +COMPILE = ${CC} ${CFLAGS} -c -o $@ $< +LINK = ${LD} -o $@ $^ ${LDFLAGS} +CLEAN = @rm -f + +#------------------------------------------------------------------------------ +# Build-Specific Macros +#------------------------------------------------------------------------------ +# library macros +BIN = acc +SRCS = source/auth.c \ + source/basic.c \ + source/conn.c \ + source/ctx.c \ + source/event.c \ + source/handler.c \ + source/hash.c \ + source/jid.c \ + source/md5.c \ + source/parser_expat.c \ + source/rand.c \ + source/resolver.c \ + source/sasl.c \ + source/scram.c \ + source/sha1.c \ + source/snprintf.c \ + source/sock.c \ + source/stanza.c \ + source/thread.c \ + source/util.c \ + source/tls_openssl.c \ + source/uuid.c + +# load user-specific settings +-include config.mk + +#------------------------------------------------------------------------------ +# Phony Targets +#------------------------------------------------------------------------------ +.PHONY: all + +all: ${BIN} + +${BIN}: ${SRCS:.c=.o} + ${LINK} + +clean: + ${CLEAN} ${BIN} ${SRCS:.c=.o} + ${CLEAN} ${SRCS:.c=.gcno} ${SRCS:.c=.gcda} + +.c.o: + ${COMPILE} + +# load dependency files +-include ${DEPS} + diff --git a/source/auth.c b/source/auth.c new file mode 100644 index 0000000..ebf8973 --- /dev/null +++ b/source/auth.c @@ -0,0 +1,1235 @@ +/* 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 and GPLv3 licenses. +*/ + +/** @file + * Authentication function and handlers. + */ + +#include +#include +#include + +#include "strophe.h" +#include "common.h" +#include "sasl.h" +#include "sha1.h" +#include "rand.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 * const conn); +static void _handle_open_sasl(xmpp_conn_t * const conn); + +static int _handle_component_auth(xmpp_conn_t * const conn); +static int _handle_component_hs_response(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); + +static int _handle_missing_legacy(xmpp_conn_t * const conn, + void * const userdata); +static int _handle_legacy(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); +static int _handle_features_sasl(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); +static int _handle_sasl_result(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); +static int _handle_digestmd5_challenge(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); +static int _handle_digestmd5_rspauth(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); +static int _handle_scram_sha1_challenge(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); +static char *_make_scram_sha1_init_msg(xmpp_conn_t * const conn); + +static int _handle_missing_features_sasl(xmpp_conn_t * const conn, + void * const userdata); +static int _handle_missing_bind(xmpp_conn_t * const conn, + void * const userdata); +static int _handle_bind(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); +static int _handle_session(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); +static int _handle_missing_session(xmpp_conn_t * const conn, + void * const userdata); +static int _handle_missing_handshake(xmpp_conn_t * const conn, + void * const userdata); + +/* stream:error handler */ +static int _handle_error(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_stanza_t *child; + char *name; + + /* 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) + xmpp_free(conn->ctx, conn->stream_error->text); + xmpp_free(conn->ctx, conn->stream_error); + } + + /* create stream error structure */ + conn->stream_error = (xmpp_stream_error_t *)xmpp_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 { + 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) + xmpp_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 * const conn, + void * const userdata) +{ + xmpp_debug(conn->ctx, "xmpp", "didn't get stream features"); + + /* legacy auth will be attempted */ + _auth(conn); + + return 0; +} + + + +static int _handle_features(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_stanza_t *child, *mech; + char *text; + + /* 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) { + child = xmpp_stanza_get_child_by_name(stanza, "starttls"); + if (child && (strcmp(xmpp_stanza_get_ns(child), XMPP_NS_TLS) == 0)) + conn->tls_support = 1; + } else { + conn->tls_support = 0; + } + } + + /* check for SASL */ + child = xmpp_stanza_get_child_by_name(stanza, "mechanisms"); + if (child && (strcmp(xmpp_stanza_get_ns(child), XMPP_NS_SASL) == 0)) { + for (mech = xmpp_stanza_get_children(child); mech; + mech = xmpp_stanza_get_next(mech)) { + if (xmpp_stanza_get_name(mech) && strcmp(xmpp_stanza_get_name(mech), "mechanism") == 0) { + text = xmpp_stanza_get_text(mech); + if (strcasecmp(text, "PLAIN") == 0) + conn->sasl_support |= SASL_MASK_PLAIN; + else if (strcasecmp(text, "DIGEST-MD5") == 0) + conn->sasl_support |= SASL_MASK_DIGESTMD5; + else if (strcasecmp(text, "SCRAM-SHA-1") == 0) + conn->sasl_support |= SASL_MASK_SCRAMSHA1; + else if (strcasecmp(text, "ANONYMOUS") == 0) + conn->sasl_support |= SASL_MASK_ANONYMOUS; + + xmpp_free(conn->ctx, text); + } + } + } + + _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 * const 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 * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *name; + + name = xmpp_stanza_get_name(stanza); + xmpp_debug(conn->ctx, "xmpp", "handle proceedtls called for %s", name); + + if (strcmp(name, "proceed") == 0) { + xmpp_debug(conn->ctx, "xmpp", "proceeding with TLS"); + + if (conn_tls_start(conn) == 0) { + 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 * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *name; + + name = xmpp_stanza_get_name(stanza); + + /* the server should send a or stanza */ + if (strcmp(name, "failure") == 0) { + xmpp_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 PLAIN auth successful, we need to restart the stream */ + xmpp_debug(conn->ctx, "xmpp", "SASL %s auth successful", + (char *)userdata); + + /* reset parser */ + conn_prepare_reset(conn, _handle_open_sasl); + + /* send stream tag */ + conn_open_stream(conn); + } else { + /* got unexpected reply */ + xmpp_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 * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *text; + char *response; + xmpp_stanza_t *auth, *authdata; + char *name; + + name = xmpp_stanza_get_name(stanza); + xmpp_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; + } + xmpp_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); + xmpp_free(conn->ctx, response); + + xmpp_stanza_add_child(auth, authdata); + xmpp_stanza_release(authdata); + + handler_add(conn, _handle_digestmd5_rspauth, + XMPP_NS_SASL, NULL, NULL, NULL); + + xmpp_send(conn, auth); + xmpp_stanza_release(auth); + + } 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 * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_stanza_t *auth; + char *name; + + name = xmpp_stanza_get_name(stanza); + xmpp_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); + xmpp_send(conn, auth); + xmpp_stanza_release(auth); + } else { + return _handle_sasl_result(conn, stanza, "DIGEST-MD5"); + } + + return 1; +} + +/* handle the challenge phase of SCRAM-SHA-1 auth */ +static int _handle_scram_sha1_challenge(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *text; + char *response; + xmpp_stanza_t *auth, *authdata; + char *name; + char *challenge; + char *scram_init = (char *)userdata; + + name = xmpp_stanza_get_name(stanza); + xmpp_debug(conn->ctx, "xmpp", + "handle SCRAM-SHA-1 (challenge) called for %s", name); + + if (strcmp(name, "challenge") == 0) { + text = xmpp_stanza_get_text(stanza); + if (!text) + goto err; + + challenge = (char *)base64_decode(conn->ctx, text, strlen(text)); + xmpp_free(conn->ctx, text); + if (!challenge) + goto err; + + response = sasl_scram_sha1(conn->ctx, challenge, scram_init, + conn->jid, conn->pass); + xmpp_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); + xmpp_free(conn->ctx, response); + + xmpp_stanza_add_child(auth, authdata); + xmpp_stanza_release(authdata); + + xmpp_send(conn, auth); + xmpp_stanza_release(auth); + + } else { + xmpp_free(conn->ctx, scram_init); + return _handle_sasl_result(conn, stanza, "SCRAM-SHA-1"); + } + + return 1; + +err_release_auth: + xmpp_stanza_release(auth); +err_free_response: + xmpp_free(conn->ctx, response); +err: + xmpp_free(conn->ctx, scram_init); + disconnect_mem_error(conn); + return 0; +} + +static char *_make_scram_sha1_init_msg(xmpp_conn_t * const conn) +{ + size_t message_len; + char *node; + char *message; + char nonce[32]; + + node = xmpp_jid_node(conn->ctx, conn->jid); + if (!node) { + return NULL; + } + xmpp_rand_nonce(conn->ctx, nonce, sizeof(nonce)); + message_len = strlen(node) + strlen(nonce) + 8 + 1; + message = xmpp_alloc(conn->ctx, message_len); + if (message) { + xmpp_snprintf(message, message_len, "n,,n=%s,r=%s", node, nonce); + } + xmpp_free(conn->ctx, node); + + return message; +} + +static xmpp_stanza_t *_make_starttls(xmpp_conn_t * const 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 * const conn, + const char * const 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 * const conn) +{ + xmpp_stanza_t *auth, *authdata, *query, *child, *iq; + char *str, *authid; + char *scram_init; + 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 { + xmpp_free(conn->ctx, str); + anonjid = 0; + } + + if (conn->tls_support) + { + tls_t *tls = tls_new(conn->ctx, conn->sock); + + /* 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); + + xmpp_send(conn, auth); + xmpp_stanza_release(auth); + + /* TLS was tried, unset flag */ + conn->tls_support = 0; + /* _auth() will be called later */ + return; + } + + if (conn->tls_mandatory && !xmpp_conn_is_secured(conn)) { + xmpp_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"); + + xmpp_send(conn, auth); + xmpp_stanza_release(auth); + + /* SASL ANONYMOUS was tried, unset flag */ + conn->sasl_support &= ~SASL_MASK_ANONYMOUS; + } else if (anonjid) { + xmpp_error(conn->ctx, "auth", + "No node in JID, and SASL ANONYMOUS unsupported."); + xmpp_disconnect(conn); + } else if (conn->sasl_support & SASL_MASK_SCRAMSHA1) { + auth = _make_sasl_auth(conn, "SCRAM-SHA-1"); + if (!auth) { + disconnect_mem_error(conn); + return; + } + + /* don't free scram_init on success */ + scram_init = _make_scram_sha1_init_msg(conn); + if (!scram_init) { + xmpp_stanza_release(auth); + disconnect_mem_error(conn); + return; + } + + str = (char *)base64_encode(conn->ctx, (unsigned char *)scram_init, + strlen(scram_init)); + if (!str) { + xmpp_free(conn->ctx, scram_init); + xmpp_stanza_release(auth); + disconnect_mem_error(conn); + return; + } + + authdata = xmpp_stanza_new(conn->ctx); + if (!authdata) { + xmpp_free(conn->ctx, str); + xmpp_free(conn->ctx, scram_init); + xmpp_stanza_release(auth); + disconnect_mem_error(conn); + return; + } + xmpp_stanza_set_text(authdata, str); + xmpp_free(conn->ctx, str); + xmpp_stanza_add_child(auth, authdata); + xmpp_stanza_release(authdata); + + handler_add(conn, _handle_scram_sha1_challenge, + XMPP_NS_SASL, NULL, NULL, (void *)scram_init); + + xmpp_send(conn, auth); + xmpp_stanza_release(auth); + + /* SASL SCRAM-SHA-1 was tried, unset flag */ + conn->sasl_support &= ~SASL_MASK_SCRAMSHA1; + } 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); + + xmpp_send(conn, auth); + xmpp_stanza_release(auth); + + /* 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); + xmpp_free(conn->ctx, str); + xmpp_free(conn->ctx, authid); + + xmpp_stanza_add_child(auth, authdata); + xmpp_stanza_release(authdata); + + handler_add(conn, _handle_sasl_result, + XMPP_NS_SASL, NULL, NULL, "PLAIN"); + + xmpp_send(conn, auth); + xmpp_stanza_release(auth); + + /* SASL PLAIN was tried */ + conn->sasl_support &= ~SASL_MASK_PLAIN; + } else if (conn->type == XMPP_CLIENT) { + /* legacy client authentication */ + + iq = xmpp_stanza_new(conn->ctx); + if (!iq) { + disconnect_mem_error(conn); + return; + } + xmpp_stanza_set_name(iq, "iq"); + xmpp_stanza_set_type(iq, "set"); + xmpp_stanza_set_id(iq, "_xmpp_auth1"); + + query = xmpp_stanza_new(conn->ctx); + if (!query) { + xmpp_stanza_release(iq); + disconnect_mem_error(conn); + return; + } + xmpp_stanza_set_name(query, "query"); + xmpp_stanza_set_ns(query, XMPP_NS_AUTH); + xmpp_stanza_add_child(iq, query); + xmpp_stanza_release(query); + + child = xmpp_stanza_new(conn->ctx); + if (!child) { + xmpp_stanza_release(iq); + disconnect_mem_error(conn); + return; + } + xmpp_stanza_set_name(child, "username"); + xmpp_stanza_add_child(query, child); + xmpp_stanza_release(child); + + authdata = xmpp_stanza_new(conn->ctx); + if (!authdata) { + xmpp_stanza_release(iq); + disconnect_mem_error(conn); + return; + } + str = xmpp_jid_node(conn->ctx, conn->jid); + xmpp_stanza_set_text(authdata, str); + xmpp_free(conn->ctx, str); + xmpp_stanza_add_child(child, authdata); + xmpp_stanza_release(authdata); + + child = xmpp_stanza_new(conn->ctx); + if (!child) { + xmpp_stanza_release(iq); + disconnect_mem_error(conn); + return; + } + xmpp_stanza_set_name(child, "password"); + xmpp_stanza_add_child(query, child); + xmpp_stanza_release(child); + + authdata = xmpp_stanza_new(conn->ctx); + if (!authdata) { + xmpp_stanza_release(iq); + disconnect_mem_error(conn); + return; + } + xmpp_stanza_set_text(authdata, conn->pass); + xmpp_stanza_add_child(child, authdata); + xmpp_stanza_release(authdata); + + child = xmpp_stanza_new(conn->ctx); + if (!child) { + xmpp_stanza_release(iq); + disconnect_mem_error(conn); + return; + } + xmpp_stanza_set_name(child, "resource"); + xmpp_stanza_add_child(query, child); + xmpp_stanza_release(child); + + authdata = xmpp_stanza_new(conn->ctx); + if (!authdata) { + xmpp_stanza_release(iq); + disconnect_mem_error(conn); + return; + } + str = xmpp_jid_resource(conn->ctx, conn->jid); + if (str) { + xmpp_stanza_set_text(authdata, str); + xmpp_free(conn->ctx, str); + } else { + xmpp_stanza_release(authdata); + xmpp_stanza_release(iq); + xmpp_error(conn->ctx, "auth", + "Cannot authenticate without resource"); + xmpp_disconnect(conn); + return; + } + xmpp_stanza_add_child(child, authdata); + xmpp_stanza_release(authdata); + + handler_add_id(conn, _handle_legacy, "_xmpp_auth1", NULL); + handler_add_timed(conn, _handle_missing_legacy, + LEGACY_TIMEOUT, NULL); + + xmpp_send(conn, iq); + xmpp_stanza_release(iq); + } +} + + +/** 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 * const conn) +{ + /* reset all timed handlers */ + handler_reset_timed(conn, 0); + + /* setup handler for stream:error */ + 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 SASL auth */ +static void _handle_open_sasl(xmpp_conn_t * const conn) +{ + xmpp_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); +} + +static int _handle_features_sasl(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_stanza_t *bind, *session, *iq, *res, *text; + char *resource; + + /* remove missing features handler */ + xmpp_timed_handler_delete(conn, _handle_missing_features_sasl); + + /* we are expecting and since this is a + XMPP style connection */ + + bind = xmpp_stanza_get_child_by_name(stanza, "bind"); + if (bind && strcmp(xmpp_stanza_get_ns(bind), XMPP_NS_BIND) == 0) { + /* resource binding is required */ + conn->bind_required = 1; + } + + session = xmpp_stanza_get_child_by_name(stanza, "session"); + if (session && strcmp(xmpp_stanza_get_ns(session), XMPP_NS_SESSION) == 0) { + /* session establishment required */ + conn->session_required = 1; + } + + /* if bind is required, go ahead and start it */ + if (conn->bind_required) { + /* bind 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_stanza_new(conn->ctx); + if (!iq) { + disconnect_mem_error(conn); + return 0; + } + + xmpp_stanza_set_name(iq, "iq"); + xmpp_stanza_set_type(iq, "set"); + xmpp_stanza_set_id(iq, "_xmpp_bind1"); + + bind = xmpp_stanza_copy(bind); + if (!bind) { + xmpp_stanza_release(iq); + 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 */ + xmpp_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(res, text); + xmpp_stanza_release(text); + xmpp_stanza_add_child(bind, res); + xmpp_stanza_release(res); + xmpp_free(conn->ctx, resource); + } + + xmpp_stanza_add_child(iq, bind); + xmpp_stanza_release(bind); + + /* send bind request */ + xmpp_send(conn, iq); + xmpp_stanza_release(iq); + } else { + /* can't bind, disconnect */ + xmpp_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 * const conn, + void * const userdata) +{ + xmpp_error(conn->ctx, "xmpp", "Did not receive stream features "\ + "after SASL authentication."); + xmpp_disconnect(conn); + return 0; +} + +static int _handle_bind(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *type; + xmpp_stanza_t *iq, *session; + + /* 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) { + xmpp_error(conn->ctx, "xmpp", "Binding failed."); + xmpp_disconnect(conn); + } else if (type && strcmp(type, "result") == 0) { + xmpp_stanza_t *binding = xmpp_stanza_get_child_by_name(stanza, "bind"); + xmpp_debug(conn->ctx, "xmpp", "Bind successful."); + + if (binding) { + xmpp_stanza_t *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) { + /* setup response handlers */ + handler_add_id(conn, _handle_session, "_xmpp_session1", NULL); + handler_add_timed(conn, _handle_missing_session, + SESSION_TIMEOUT, NULL); + + /* send session request */ + iq = xmpp_stanza_new(conn->ctx); + if (!iq) { + disconnect_mem_error(conn); + return 0; + } + + xmpp_stanza_set_name(iq, "iq"); + xmpp_stanza_set_type(iq, "set"); + xmpp_stanza_set_id(iq, "_xmpp_session1"); + + session = xmpp_stanza_new(conn->ctx); + if (!session) { + xmpp_stanza_release(iq); + disconnect_mem_error(conn); + } + + xmpp_stanza_set_name(session, "session"); + xmpp_stanza_set_ns(session, XMPP_NS_SESSION); + + xmpp_stanza_add_child(iq, session); + xmpp_stanza_release(session); + + /* send session establishment request */ + xmpp_send(conn, iq); + xmpp_stanza_release(iq); + } else { + conn->authenticated = 1; + + /* call connection handler */ + conn->conn_handler(conn, XMPP_CONN_CONNECT, 0, NULL, + conn->userdata); + } + } else { + xmpp_error(conn->ctx, "xmpp", "Server sent malformed bind reply."); + xmpp_disconnect(conn); + } + + return 0; +} + +static int _handle_missing_bind(xmpp_conn_t * const conn, + void * const userdata) +{ + xmpp_error(conn->ctx, "xmpp", "Server did not reply to bind request."); + xmpp_disconnect(conn); + return 0; +} + +static int _handle_session(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *type; + + /* 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) { + xmpp_error(conn->ctx, "xmpp", "Session establishment failed."); + xmpp_disconnect(conn); + } else if (type && strcmp(type, "result") == 0) { + xmpp_debug(conn->ctx, "xmpp", "Session establishment successful."); + + conn->authenticated = 1; + + /* call connection handler */ + conn->conn_handler(conn, XMPP_CONN_CONNECT, 0, NULL, conn->userdata); + } else { + xmpp_error(conn->ctx, "xmpp", "Server sent malformed session reply."); + xmpp_disconnect(conn); + } + + return 0; +} + +static int _handle_missing_session(xmpp_conn_t * const conn, + void * const userdata) +{ + xmpp_error(conn->ctx, "xmpp", "Server did not reply to session request."); + xmpp_disconnect(conn); + return 0; +} + +static int _handle_legacy(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *type, *name; + + /* 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) { + xmpp_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 */ + xmpp_error(conn->ctx, "xmpp", "Legacy client authentication failed."); + xmpp_disconnect(conn); + } else if (strcmp(type, "result") == 0) { + /* auth succeeded */ + xmpp_debug(conn->ctx, "xmpp", "Legacy auth succeeded."); + + conn->authenticated = 1; + conn->conn_handler(conn, XMPP_CONN_CONNECT, 0, NULL, conn->userdata); + } else { + xmpp_error(conn->ctx, "xmpp", "Server sent us a legacy authentication "\ + "response with a bad type."); + xmpp_disconnect(conn); + } + + return 0; +} + +static int _handle_missing_legacy(xmpp_conn_t * const conn, + void * const userdata) +{ + xmpp_error(conn->ctx, "xmpp", "Server did not reply to legacy "\ + "authentication request."); + xmpp_disconnect(conn); + return 0; +} + +void auth_handle_component_open(xmpp_conn_t * const conn) +{ + /* 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); + + _handle_component_auth(conn); +} + +/* Will compute SHA1 and authenticate the component to the server */ +int _handle_component_auth(xmpp_conn_t * const conn) +{ + uint8_t md_value[SHA1_DIGEST_SIZE]; + SHA1_CTX mdctx; + char *digest; + size_t i; + + /* 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 = xmpp_alloc(conn->ctx, 2*sizeof(md_value)+1); + if (digest) { + /* convert the digest into string representation */ + for (i = 0; i < sizeof(md_value); i++) + xmpp_snprintf(digest+i*2, 3, "%02x", md_value[i]); + digest[2*sizeof(md_value)] = '\0'; + + xmpp_debug(conn->ctx, "auth", "Digest: %s, len: %d", + digest, strlen(digest)); + + /* Send the digest to the server */ + xmpp_send_raw_string(conn, "%s", + XMPP_NS_COMPONENT, digest); + xmpp_debug(conn->ctx, "auth", "Sent component handshake to the server."); + xmpp_free(conn->ctx, digest); + } else { + xmpp_debug(conn->ctx, "auth", "Couldn't allocate memory for component "\ + "handshake digest."); + xmpp_disconnect(conn); + 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 * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *name; + + 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) { + xmpp_debug(conn->ctx, "auth", "Handshake failed: %s", msg); + xmpp_free(conn->ctx, msg); + } + xmpp_disconnect(conn); + return XMPP_EINT; + } else { + conn->authenticated = 1; + conn->conn_handler(conn, XMPP_CONN_CONNECT, 0, NULL, conn->userdata); + } + + /* 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 * const conn, void * const userdata) +{ + xmpp_error(conn->ctx, "xmpp", "Server did not reply to handshake request."); + xmpp_disconnect(conn); + return 0; +} diff --git a/source/basic.c b/source/basic.c new file mode 100644 index 0000000..652d860 --- /dev/null +++ b/source/basic.c @@ -0,0 +1,112 @@ +/* basic.c +** libstrophe XMPP client library -- basic usage example +** +** 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 and GPLv3 licenses. +*/ + +#include +#include + +#include + + +/* define a handler for connection events */ +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_ctx_t *ctx = (xmpp_ctx_t *)userdata; + int secured; + + if (status == XMPP_CONN_CONNECT) { + fprintf(stderr, "DEBUG: connected\n"); + secured = xmpp_conn_is_secured(conn); + fprintf(stderr, "DEBUG: connection is %s.\n", + secured ? "secured" : "NOT secured"); + xmpp_disconnect(conn); + } + else { + fprintf(stderr, "DEBUG: disconnected\n"); + xmpp_stop(ctx); + } +} + +int main(int argc, char **argv) +{ + xmpp_ctx_t *ctx; + xmpp_conn_t *conn; + xmpp_log_t *log; + char *jid, *pass, *host = NULL; + long flags = 0; + int i; + + /* take a jid and password on the command line */ + for (i = 1; i < argc; ++i) { + if (strcmp(argv[i], "--disable-tls") == 0) + flags |= XMPP_CONN_FLAG_DISABLE_TLS; + else if (strcmp(argv[i], "--mandatory-tls") == 0) + flags |= XMPP_CONN_FLAG_MANDATORY_TLS; + else if (strcmp(argv[i], "--legacy-ssl") == 0) + flags |= XMPP_CONN_FLAG_LEGACY_SSL; + else + break; + } + if ((argc - i) < 2 || (argc - i) > 3) { + fprintf(stderr, "Usage: basic [options] []\n\n" + "Options:\n" + " --disable-tls Disable TLS.\n" + " --mandatory-tls Deny plaintext connection.\n" + " --legacy-ssl Use old style SSL.\n\n" + "Note: --disable-tls conflicts with --mandatory-tls or " + "--legacy-ssl\n"); + return 1; + } + + jid = argv[i]; + pass = argv[i + 1]; + if (i + 2 < argc) + host = argv[i + 2]; + + /* + * Note, this example doesn't handle errors. Applications should check + * return values of non-void functions. + */ + + /* init library */ + xmpp_initialize(); + + /* create a context */ + log = xmpp_get_default_logger(XMPP_LEVEL_DEBUG); /* pass NULL instead to silence output */ + ctx = xmpp_ctx_new(NULL, log); + + /* create a connection */ + conn = xmpp_conn_new(ctx); + + /* configure connection properties (optional) */ + xmpp_conn_set_flags(conn, flags); + + /* setup authentication information */ + xmpp_conn_set_jid(conn, jid); + xmpp_conn_set_pass(conn, pass); + + /* initiate connection */ + xmpp_connect_client(conn, host, 0, conn_handler, ctx); + + /* enter the event loop - + our connect handler will trigger an exit */ + xmpp_run(ctx); + + /* release our connection and context */ + xmpp_conn_release(conn); + xmpp_ctx_free(ctx); + + /* final shutdown of the library */ + xmpp_shutdown(); + + return 0; +} diff --git a/source/common.h b/source/common.h new file mode 100644 index 0000000..207734d --- /dev/null +++ b/source/common.h @@ -0,0 +1,270 @@ +/* common.h +** strophe XMPP client library -- internal common structures +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * Internally used functions and structures. + */ + +#ifndef __LIBSTROPHE_COMMON_H__ +#define __LIBSTROPHE_COMMON_H__ + +#include +#include + + +#include "strophe.h" +#include "ostypes.h" +#include "sock.h" +#include "tls.h" +#include "hash.h" +#include "util.h" +#include "parser.h" +#include "rand.h" +#include "snprintf.h" + +/** run-time context **/ + +typedef enum { + XMPP_LOOP_NOTSTARTED, + XMPP_LOOP_RUNNING, + XMPP_LOOP_QUIT +} xmpp_loop_status_t; + +typedef struct _xmpp_connlist_t { + xmpp_conn_t *conn; + struct _xmpp_connlist_t *next; +} xmpp_connlist_t; + +struct _xmpp_ctx_t { + const xmpp_mem_t *mem; + const xmpp_log_t *log; + + xmpp_rand_t *rand; + xmpp_loop_status_t loop_status; + xmpp_connlist_t *connlist; +}; + + +/* convenience functions for accessing the context */ +void *xmpp_alloc(const xmpp_ctx_t * const ctx, const size_t size); +void *xmpp_realloc(const xmpp_ctx_t * const ctx, void *p, + const size_t size); +char *xmpp_strdup(const xmpp_ctx_t * const ctx, const char * const s); + +void xmpp_log(const xmpp_ctx_t * const ctx, + const xmpp_log_level_t level, + const char * const area, + const char * const fmt, + va_list ap); + +/* wrappers for xmpp_log at specific levels */ +void xmpp_error(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...); +void xmpp_warn(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...); +void xmpp_info(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...); +void xmpp_debug(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...); + +/** connection **/ + +/* opaque connection object */ +typedef enum { + XMPP_STATE_DISCONNECTED, + XMPP_STATE_CONNECTING, + XMPP_STATE_CONNECTED +} xmpp_conn_state_t; + +typedef struct _xmpp_send_queue_t xmpp_send_queue_t; +struct _xmpp_send_queue_t { + char *data; + size_t len; + size_t written; + + xmpp_send_queue_t *next; +}; + +typedef struct _xmpp_handlist_t xmpp_handlist_t; +struct _xmpp_handlist_t { + /* common members */ + int user_handler; + void *handler; + void *userdata; + int enabled; /* handlers are added disabled and enabled after the + * handler chain is processed to prevent stanzas from + * getting processed by newly added handlers */ + xmpp_handlist_t *next; + + union { + /* timed handlers */ + struct { + unsigned long period; + uint64_t last_stamp; + }; + /* id handlers */ + struct { + char *id; + }; + /* normal handlers */ + struct { + char *ns; + char *name; + char *type; + }; + }; +}; + +#define SASL_MASK_PLAIN 0x01 +#define SASL_MASK_DIGESTMD5 0x02 +#define SASL_MASK_ANONYMOUS 0x04 +#define SASL_MASK_SCRAMSHA1 0x08 + +enum { + XMPP_PORT_CLIENT = 5222, + XMPP_PORT_CLIENT_LEGACY_SSL = 5223, + XMPP_PORT_COMPONENT = 5347, +}; + +typedef void (*xmpp_open_handler)(xmpp_conn_t * const conn); + +struct _xmpp_conn_t { + unsigned int ref; + xmpp_ctx_t *ctx; + xmpp_conn_type_t type; + + xmpp_conn_state_t state; + uint64_t timeout_stamp; + int error; + xmpp_stream_error_t *stream_error; + sock_t sock; + tls_t *tls; + + int tls_support; + int tls_disabled; + int tls_mandatory; + int tls_legacy_ssl; + int tls_failed; /* set when tls fails, so we don't try again */ + int sasl_support; /* if true, field is a bitfield of supported + mechanisms */ + int secured; /* set when stream is secured with TLS */ + + /* if server returns or we must do them */ + int bind_required; + int session_required; + + char *lang; + char *domain; + char *connectdomain; + char *connectport; + char *jid; + char *pass; + char *bound_jid; + char *stream_id; + + /* send queue and parameters */ + int blocking_send; + int send_queue_max; + int send_queue_len; + xmpp_send_queue_t *send_queue_head; + xmpp_send_queue_t *send_queue_tail; + + /* xml parser */ + int reset_parser; + parser_t *parser; + + /* timeouts */ + unsigned int connect_timeout; + + /* event handlers */ + + /* stream open handler */ + xmpp_open_handler open_handler; + + /* user handlers only get called after authentication */ + int authenticated; + + /* connection events handler */ + xmpp_conn_handler conn_handler; + void *userdata; + + /* other handlers */ + xmpp_handlist_t *timed_handlers; + hash_t *id_handlers; + xmpp_handlist_t *handlers; +}; + +void conn_disconnect(xmpp_conn_t * const conn); +void conn_disconnect_clean(xmpp_conn_t * const conn); +void conn_open_stream(xmpp_conn_t * const conn); +int conn_tls_start(xmpp_conn_t * const conn); +void conn_prepare_reset(xmpp_conn_t * const conn, xmpp_open_handler handler); +void conn_parser_reset(xmpp_conn_t * const conn); + + +typedef enum { + XMPP_STANZA_UNKNOWN, + XMPP_STANZA_TEXT, + XMPP_STANZA_TAG +} xmpp_stanza_type_t; + +struct _xmpp_stanza_t { + int ref; + xmpp_ctx_t *ctx; + + xmpp_stanza_type_t type; + + xmpp_stanza_t *prev; + xmpp_stanza_t *next; + xmpp_stanza_t *children; + xmpp_stanza_t *parent; + + char *data; + + hash_t *attributes; +}; + +/* handler management */ +void handler_fire_stanza(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza); +uint64_t handler_fire_timed(xmpp_ctx_t * const ctx); +void handler_reset_timed(xmpp_conn_t *conn, int user_only); +void handler_add_timed(xmpp_conn_t * const conn, + xmpp_timed_handler handler, + const unsigned long period, + void * const userdata); +void handler_add_id(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const id, + void * const userdata); +void handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const ns, + const char * const name, + const char * const type, + void * const userdata); + +/* utility functions */ +void disconnect_mem_error(xmpp_conn_t * const conn); + +/* auth functions */ +void auth_handle_open(xmpp_conn_t * const conn); +void auth_handle_component_open(xmpp_conn_t * const conn); + +#endif /* __LIBSTROPHE_COMMON_H__ */ diff --git a/source/conn.c b/source/conn.c new file mode 100644 index 0000000..1765a49 --- /dev/null +++ b/source/conn.c @@ -0,0 +1,984 @@ +/* conn.c +** strophe XMPP client library -- connection object functions +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * Connection management. + */ + +/** @defgroup Connections Connection management + */ + +#include +#include +#include + +#include "strophe.h" + +#include "common.h" +#include "util.h" +#include "parser.h" +#include "resolver.h" + +#ifndef DEFAULT_SEND_QUEUE_MAX +/** @def DEFAULT_SEND_QUEUE_MAX + * The default maximum send queue size. This is currently unused. + */ +#define DEFAULT_SEND_QUEUE_MAX 64 +#endif +#ifndef DISCONNECT_TIMEOUT +/** @def DISCONNECT_TIMEOUT + * The time to wait (in milliseconds) for graceful disconnection to + * complete before the connection is reset. The default is 2 seconds. + */ +#define DISCONNECT_TIMEOUT 2000 /* 2 seconds */ +#endif +#ifndef CONNECT_TIMEOUT +/** @def CONNECT_TIMEOUT + * The time to wait (in milliseconds) for a connection attempt to succeed + * or error. The default is 5 seconds. + */ +#define CONNECT_TIMEOUT 5000 /* 5 seconds */ +#endif + +static int _disconnect_cleanup(xmpp_conn_t * const conn, + void * const userdata); + +static void _handle_stream_start(char *name, char **attrs, + void * const userdata); +static void _handle_stream_end(char *name, + void * const userdata); +static void _handle_stream_stanza(xmpp_stanza_t *stanza, + void * const userdata); +static unsigned short _conn_default_port(xmpp_conn_t * const conn); + +/** Create a new Strophe connection object. + * + * @param ctx a Strophe context object + * + * @return a Strophe connection object or NULL on an error + * + * @ingroup Connections + */ +xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t * const ctx) +{ + xmpp_conn_t *conn = NULL; + xmpp_connlist_t *tail, *item; + + if (ctx == NULL) return NULL; + + conn = xmpp_alloc(ctx, sizeof(xmpp_conn_t)); + if (conn != NULL) { + conn->ctx = ctx; + + conn->type = XMPP_UNKNOWN; + conn->state = XMPP_STATE_DISCONNECTED; + conn->sock = -1; + conn->tls = NULL; + conn->timeout_stamp = 0; + conn->error = 0; + conn->stream_error = NULL; + + /* default send parameters */ + conn->blocking_send = 0; + conn->send_queue_max = DEFAULT_SEND_QUEUE_MAX; + conn->send_queue_len = 0; + conn->send_queue_head = NULL; + conn->send_queue_tail = NULL; + + /* default timeouts */ + conn->connect_timeout = CONNECT_TIMEOUT; + + conn->lang = xmpp_strdup(conn->ctx, "en"); + if (!conn->lang) { + xmpp_free(conn->ctx, conn); + return NULL; + } + conn->domain = NULL; + conn->jid = NULL; + conn->pass = NULL; + conn->stream_id = NULL; + conn->bound_jid = NULL; + + conn->tls_support = 0; + conn->tls_disabled = 0; + conn->tls_mandatory = 0; + conn->tls_legacy_ssl = 0; + conn->tls_failed = 0; + conn->sasl_support = 0; + conn->secured = 0; + + conn->bind_required = 0; + conn->session_required = 0; + + conn->parser = parser_new(conn->ctx, + _handle_stream_start, + _handle_stream_end, + _handle_stream_stanza, + conn); + conn->reset_parser = 0; + conn_prepare_reset(conn, auth_handle_open); + + conn->authenticated = 0; + conn->conn_handler = NULL; + conn->userdata = NULL; + conn->timed_handlers = NULL; + /* we own (and will free) the hash values */ + conn->id_handlers = hash_new(conn->ctx, 32, NULL); + conn->handlers = NULL; + + /* give the caller a reference to connection */ + conn->ref = 1; + + /* add connection to ctx->connlist */ + tail = conn->ctx->connlist; + while (tail && tail->next) tail = tail->next; + + item = xmpp_alloc(conn->ctx, sizeof(xmpp_connlist_t)); + if (!item) { + xmpp_error(conn->ctx, "xmpp", "failed to allocate memory"); + xmpp_free(conn->ctx, conn->lang); + parser_free(conn->parser); + xmpp_free(conn->ctx, conn); + conn = NULL; + } else { + item->conn = conn; + item->next = NULL; + + if (tail) tail->next = item; + else conn->ctx->connlist = item; + } + } + + return conn; +} + +/** Clone a Strophe connection object. + * + * @param conn a Strophe connection object + * + * @return the same conn object passed in with its reference count + * incremented by 1 + * + * @ingroup Connections + */ +xmpp_conn_t *xmpp_conn_clone(xmpp_conn_t * const conn) +{ + conn->ref++; + return conn; +} + +/** Release a Strophe connection object. + * Decrement the reference count by one for a connection, freeing the + * connection object if the count reaches 0. + * + * @param conn a Strophe connection object + * + * @return TRUE if the connection object was freed and FALSE otherwise + * + * @ingroup Connections + */ +int xmpp_conn_release(xmpp_conn_t * const conn) +{ + xmpp_ctx_t *ctx; + xmpp_send_queue_t *sq, *tsq; + xmpp_connlist_t *item, *prev; + xmpp_handlist_t *hlitem, *thli; + hash_iterator_t *iter; + const char *key; + int released = 0; + + if (conn->ref > 1) + conn->ref--; + else { + ctx = conn->ctx; + + /* remove connection from context's connlist */ + if (ctx->connlist->conn == conn) { + item = ctx->connlist; + ctx->connlist = item->next; + xmpp_free(ctx, item); + } else { + prev = NULL; + item = ctx->connlist; + while (item && item->conn != conn) { + prev = item; + item = item->next; + } + + if (!item) { + xmpp_error(ctx, "xmpp", "Connection not in context's list\n"); + } else { + prev->next = item->next; + xmpp_free(ctx, item); + } + } + + /* free handler stuff + * note that userdata is the responsibility of the client + * and the handler pointers don't need to be freed since they + * are pointers to functions */ + + hlitem = conn->timed_handlers; + while (hlitem) { + thli = hlitem; + hlitem = hlitem->next; + + xmpp_free(ctx, thli); + } + + /* id handlers + * we have to traverse the hash table freeing list elements + * then release the hash table */ + iter = hash_iter_new(conn->id_handlers); + while ((key = hash_iter_next(iter))) { + hlitem = (xmpp_handlist_t *)hash_get(conn->id_handlers, key); + while (hlitem) { + thli = hlitem; + hlitem = hlitem->next; + xmpp_free(conn->ctx, thli->id); + xmpp_free(conn->ctx, thli); + } + } + hash_iter_release(iter); + hash_release(conn->id_handlers); + + hlitem = conn->handlers; + while (hlitem) { + thli = hlitem; + hlitem = hlitem->next; + + if (thli->ns) xmpp_free(ctx, thli->ns); + if (thli->name) xmpp_free(ctx, thli->name); + if (thli->type) xmpp_free(ctx, thli->type); + xmpp_free(ctx, thli); + } + + if (conn->stream_error) { + xmpp_stanza_release(conn->stream_error->stanza); + if (conn->stream_error->text) + xmpp_free(ctx, conn->stream_error->text); + xmpp_free(ctx, conn->stream_error); + } + + parser_free(conn->parser); + + /* free queued */ + sq = conn->send_queue_head; + while (sq) { + tsq = sq; + sq = sq->next; + xmpp_free(ctx, tsq->data); + xmpp_free(ctx, tsq); + } + + if (conn->domain) xmpp_free(ctx, conn->domain); + if (conn->jid) xmpp_free(ctx, conn->jid); + if (conn->bound_jid) xmpp_free(ctx, conn->bound_jid); + if (conn->pass) xmpp_free(ctx, conn->pass); + if (conn->stream_id) xmpp_free(ctx, conn->stream_id); + if (conn->lang) xmpp_free(ctx, conn->lang); + xmpp_free(ctx, conn); + released = 1; + } + + return released; +} + +/** Get the JID which is or will be bound to the connection. + * + * @param conn a Strophe connection object + * + * @return a string containing the full JID or NULL if it has not been set + * + * @ingroup Connections + */ +const char *xmpp_conn_get_jid(const xmpp_conn_t * const conn) +{ + return conn->jid; +} + +/** + * Get the JID discovered during binding time. + * + * This JID will contain the resource used by the current connection. + * This is useful in the case where a resource was not specified for + * binding. + * + * @param conn a Strophe connection object. + * + * @return a string containing the full JID or NULL if it's not been discovered + * + * @ingroup Connections + */ +const char *xmpp_conn_get_bound_jid(const xmpp_conn_t * const conn) +{ + return conn->bound_jid; +} + +/** Set the JID of the user that will be bound to the connection. + * If any JID was previously set, it will be discarded. This should not be + * be used after a connection is created. The function will make a copy of + * the JID string. If the supllied JID is missing the node, SASL + * ANONYMOUS authentication will be used. + * + * @param conn a Strophe connection object + * @param jid a full or bare JID + * + * @ingroup Connections + */ +void xmpp_conn_set_jid(xmpp_conn_t * const conn, const char * const jid) +{ + if (conn->jid) xmpp_free(conn->ctx, conn->jid); + conn->jid = xmpp_strdup(conn->ctx, jid); +} + +/** Get the password used for authentication of a connection. + * + * @param conn a Strophe connection object + * + * @return a string containing the password or NULL if it has not been set + * + * @ingroup Connections + */ +const char *xmpp_conn_get_pass(const xmpp_conn_t * const conn) +{ + return conn->pass; +} + +/** Set the password used to authenticate the connection. + * If any password was previously set, it will be discarded. The function + * will make a copy of the password string. + * + * @param conn a Strophe connection object + * @param pass the password + * + * @ingroup Connections + */ +void xmpp_conn_set_pass(xmpp_conn_t * const conn, const char * const pass) +{ + if (conn->pass) xmpp_free(conn->ctx, conn->pass); + conn->pass = xmpp_strdup(conn->ctx, pass); +} + +/** Get the strophe context that the connection is associated with. +* @param conn a Strophe connection object +* +* @return a Strophe context +* +* @ingroup Connections +*/ +xmpp_ctx_t* xmpp_conn_get_context(xmpp_conn_t * const conn) +{ + return conn->ctx; +} + +/** Initiate a connection to the XMPP server. + * This function returns immediately after starting the connection + * process to the XMPP server, and notifiations of connection state changes + * will be sent to the callback function. The domain and port to connect to + * are usually determined by an SRV lookup for the xmpp-client service at + * the domain specified in the JID. If SRV lookup fails, altdomain and + * altport will be used instead if specified. + * + * @param conn a Strophe connection object + * @param altdomain a string with domain to use if SRV lookup fails. If this + * is NULL, the domain from the JID will be used. + * @param altport an integer port number to use if SRV lookup fails. If this + * is 0, the default port will be assumed. + * @param callback a xmpp_conn_handler callback function that will receive + * notifications of connection status + * @param userdata an opaque data pointer that will be passed to the callback + * + * @return 0 on success and -1 on an error + * + * @ingroup Connections + */ +int xmpp_connect_client(xmpp_conn_t * const conn, + const char * const altdomain, + unsigned short altport, + xmpp_conn_handler callback, + void * const userdata) +{ + char domain[2048]; + unsigned short port; + const char *prefdomain = NULL; + int found; + + if (conn->state != XMPP_STATE_DISCONNECTED) + return -1; + if (conn->domain != NULL) + xmpp_free(conn->ctx, conn->domain); + + conn->type = XMPP_CLIENT; + conn->secured = 0; + conn->tls_failed = 0; + conn->domain = xmpp_jid_domain(conn->ctx, conn->jid); + if (!conn->domain) return -1; + + if (altdomain != NULL) { + xmpp_debug(conn->ctx, "xmpp", "Connecting via altdomain."); + prefdomain = altdomain; + port = altport ? altport : _conn_default_port(conn); + } else { + found = resolver_srv_lookup("xmpp-client", "tcp", conn->domain, + domain, sizeof(domain), &port); + if (!found) { + xmpp_debug(conn->ctx, "xmpp", "SRV lookup failed, " + "connecting via domain."); + prefdomain = conn->domain; + port = altport ? altport : _conn_default_port(conn); + } + if (conn->tls_legacy_ssl) { + /* SSL tunneled connection on 5223 port is legacy and doesn't + * have an SRV record. Force port 5223 here unless altport is + * specified. + */ + port = altport ? altport : XMPP_PORT_CLIENT_LEGACY_SSL; + } + } + if (prefdomain != NULL) { + strncpy(domain, prefdomain, sizeof(domain)); + domain[sizeof(domain) - 1] = '\0'; + } + conn->sock = sock_connect(domain, port); + xmpp_debug(conn->ctx, "xmpp", "sock_connect to %s:%u returned %d", + domain, port, conn->sock); + if (conn->sock == -1) return -1; + + /* setup handler */ + conn->conn_handler = callback; + conn->userdata = userdata; + + /* FIXME: it could happen that the connect returns immediately as + * successful, though this is pretty unlikely. This would be a little + * hard to fix, since we'd have to detect and fire off the callback + * from within the event loop */ + + conn->state = XMPP_STATE_CONNECTING; + conn->timeout_stamp = time_stamp(); + xmpp_debug(conn->ctx, "xmpp", "attempting to connect to %s", domain); + + return 0; +} + +/** Initiate a component connection to server. + * This function returns immediately after starting the connection + * process to the XMPP server, and notifiations of connection state changes + * will be sent to the internal callback function that will set up handler + * for the component handshake as defined in XEP-0114. + * The domain and port to connect to must be provided in this case as the JID + * provided to the call serves as component identifier to the server and is + * not subject to DNS resolution. + * + * @param conn a Strophe connection object + * @param server a string with domain to use directly as the domain can't be + * extracted from the component name/JID. If this is not set, the call + * will fail. + * @param port an integer port number to use to connect to server expecting + * an external component. If this is 0, the port 5347 will be assumed. + * @param callback a xmpp_conn_handler callback function that will receive + * notifications of connection status + * @param userdata an opaque data pointer that will be passed to the callback + * + * @return 0 on success and -1 on an error + * + * @ingroup Connections + */ +int xmpp_connect_component(xmpp_conn_t * const conn, const char * const server, + unsigned short port, xmpp_conn_handler callback, + void * const userdata) +{ + unsigned short connectport; + + if (conn->state != XMPP_STATE_DISCONNECTED) + return -1; + if (conn->domain != NULL) + xmpp_free(conn->ctx, conn->domain); + + conn->type = XMPP_COMPONENT; + conn->secured = 0; + conn->tls_failed = 0; + /* JID serves as an identificator here and will be used as "to" attribute + of the stream */ + conn->domain = xmpp_strdup(conn->ctx, conn->jid); + + /* The server domain, jid and password MUST be specified. */ + if (!(server && conn->jid && conn->pass)) return -1; + + connectport = port ? port : _conn_default_port(conn); + + xmpp_debug(conn->ctx, "xmpp", "Connecting via %s", server); + conn->sock = sock_connect(server, connectport); + xmpp_debug(conn->ctx, "xmpp", "sock_connect to %s:%u returned %d", + server, connectport, conn->sock); + if (conn->sock == -1) return -1; + + /* XEP-0114 does not support TLS */ + conn->tls_disabled = 1; + + /* setup handler */ + conn->conn_handler = callback; + conn->userdata = userdata; + + conn_prepare_reset(conn, auth_handle_component_open); + + /* FIXME: it could happen that the connect returns immediately as + * successful, though this is pretty unlikely. This would be a little + * hard to fix, since we'd have to detect and fire off the callback + * from within the event loop */ + + conn->state = XMPP_STATE_CONNECTING; + conn->timeout_stamp = time_stamp(); + xmpp_debug(conn->ctx, "xmpp", "attempting to connect to %s", server); + + return 0; +} + +/** Cleanly disconnect the connection. + * This function is only called by the stream parser when + * is received, and it not intended to be called by code outside of Strophe. + * + * @param conn a Strophe connection object + */ +void conn_disconnect_clean(xmpp_conn_t * const conn) +{ + /* remove the timed handler */ + xmpp_timed_handler_delete(conn, _disconnect_cleanup); + + conn_disconnect(conn); +} + +/** Disconnect from the XMPP server. + * This function immediately disconnects from the XMPP server, and should + * not be used outside of the Strophe library. + * + * @param conn a Strophe connection object + */ +void conn_disconnect(xmpp_conn_t * const conn) +{ + xmpp_debug(conn->ctx, "xmpp", "Closing socket."); + conn->state = XMPP_STATE_DISCONNECTED; + if (conn->tls) { + tls_stop(conn->tls); + tls_free(conn->tls); + conn->tls = NULL; + } + sock_close(conn->sock); + + /* fire off connection handler */ + conn->conn_handler(conn, XMPP_CONN_DISCONNECT, conn->error, + conn->stream_error, conn->userdata); +} + +/* prepares a parser reset. this is called from handlers. we can't + * reset the parser immediately as it is not re-entrant. */ +void conn_prepare_reset(xmpp_conn_t * const conn, xmpp_open_handler handler) +{ + conn->reset_parser = 1; + conn->open_handler = handler; +} + +/* reset the parser */ +void conn_parser_reset(xmpp_conn_t * const conn) +{ + conn->reset_parser = 0; + parser_reset(conn->parser); +} + +/* timed handler for cleanup if normal disconnect procedure takes too long */ +static int _disconnect_cleanup(xmpp_conn_t * const conn, + void * const userdata) +{ + xmpp_debug(conn->ctx, "xmpp", + "disconnection forced by cleanup timeout"); + + conn_disconnect(conn); + + return 0; +} + +/** Initiate termination of the connection to the XMPP server. + * This function starts the disconnection sequence by sending + * to the XMPP server. This function will do nothing + * if the connection state is CONNECTING or CONNECTED. + * + * @param conn a Strophe connection object + * + * @ingroup Connections + */ +void xmpp_disconnect(xmpp_conn_t * const conn) +{ + if (conn->state != XMPP_STATE_CONNECTING && + conn->state != XMPP_STATE_CONNECTED) + return; + + /* close the stream */ + xmpp_send_raw_string(conn, ""); + + /* setup timed handler in case disconnect takes too long */ + handler_add_timed(conn, _disconnect_cleanup, + DISCONNECT_TIMEOUT, NULL); +} + +/** Send a raw string to the XMPP server. + * This function is a convenience function to send raw string data to the + * XMPP server. It is used by Strophe to send short messages instead of + * building up an XML stanza with DOM methods. This should be used with care + * as it does not validate the data; invalid data may result in immediate + * stream termination by the XMPP server. + * + * @param conn a Strophe connection object + * @param fmt a printf-style format string followed by a variable list of + * arguments to format + */ +void xmpp_send_raw_string(xmpp_conn_t * const conn, + const char * const fmt, ...) +{ + va_list ap; + size_t len; + char buf[1024]; /* small buffer for common case */ + char *bigbuf; + + va_start(ap, fmt); + len = xmpp_vsnprintf(buf, 1024, fmt, ap); + va_end(ap); + + if (len >= 1024) { + /* we need more space for this data, so we allocate a big + * enough buffer and print to that */ + len++; /* account for trailing \0 */ + bigbuf = xmpp_alloc(conn->ctx, len); + if (!bigbuf) { + xmpp_debug(conn->ctx, "xmpp", "Could not allocate memory for send_raw_string"); + return; + } + va_start(ap, fmt); + xmpp_vsnprintf(bigbuf, len, fmt, ap); + va_end(ap); + + xmpp_debug(conn->ctx, "conn", "SENT: %s", bigbuf); + + /* len - 1 so we don't send trailing \0 */ + xmpp_send_raw(conn, bigbuf, len - 1); + + xmpp_free(conn->ctx, bigbuf); + } else { + xmpp_debug(conn->ctx, "conn", "SENT: %s", buf); + + xmpp_send_raw(conn, buf, len); + } +} + +/** Send raw bytes to the XMPP server. + * This function is a convenience function to send raw bytes to the + * XMPP server. It is usedly primarly by xmpp_send_raw_string. This + * function should be used with care as it does not validate the bytes and + * invalid data may result in stream termination by the XMPP server. + * + * @param conn a Strophe connection object + * @param data a buffer of raw bytes + * @param len the length of the data in the buffer + */ +void xmpp_send_raw(xmpp_conn_t * const conn, + const char * const data, const size_t len) +{ + xmpp_send_queue_t *item; + + if (conn->state != XMPP_STATE_CONNECTED) return; + + /* create send queue item for queue */ + item = xmpp_alloc(conn->ctx, sizeof(xmpp_send_queue_t)); + if (!item) return; + + item->data = xmpp_alloc(conn->ctx, len); + if (!item->data) { + xmpp_free(conn->ctx, item); + return; + } + memcpy(item->data, data, len); + item->len = len; + item->next = NULL; + item->written = 0; + + /* add item to the send queue */ + if (!conn->send_queue_tail) { + /* first item, set head and tail */ + conn->send_queue_head = item; + conn->send_queue_tail = item; + } else { + /* add to the tail */ + conn->send_queue_tail->next = item; + conn->send_queue_tail = item; + } + conn->send_queue_len++; +} + +/** Send an XML stanza to the XMPP server. + * This is the main way to send data to the XMPP server. The function will + * terminate without action if the connection state is not CONNECTED. + * + * @param conn a Strophe connection object + * @param stanza a Strophe stanza object + * + * @ingroup Connections + */ +void xmpp_send(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza) +{ + char *buf; + size_t len; + int ret; + + if (conn->state == XMPP_STATE_CONNECTED) { + if ((ret = xmpp_stanza_to_text(stanza, &buf, &len)) == 0) { + xmpp_send_raw(conn, buf, len); + xmpp_debug(conn->ctx, "conn", "SENT: %s", buf); + xmpp_free(conn->ctx, buf); + } + } +} + +/** Send the opening <stream:stream> tag to the server. + * This function is used by Strophe to begin an XMPP stream. It should + * not be used outside of the library. + * + * @param conn a Strophe connection object + */ +void conn_open_stream(xmpp_conn_t * const conn) +{ + xmpp_send_raw_string(conn, + "" \ + "", + conn->domain, + conn->lang, + conn->type == XMPP_CLIENT ? XMPP_NS_CLIENT : + XMPP_NS_COMPONENT, + XMPP_NS_STREAMS); +} + +int conn_tls_start(xmpp_conn_t * const conn) +{ + int rc; + + if (conn->tls_disabled) { + conn->tls = NULL; + rc = -ENOSYS; + } else { + conn->tls = tls_new(conn->ctx, conn->sock); + rc = conn->tls == NULL ? -ENOMEM : 0; + } + + if (conn->tls != NULL) { + if (tls_start(conn->tls)) { + conn->secured = 1; + conn_prepare_reset(conn, auth_handle_open); + } else { + rc = tls_error(conn->tls); + conn->error = rc; + tls_free(conn->tls); + conn->tls = NULL; + conn->tls_failed = 1; + } + } + if (rc != 0) + xmpp_debug(conn->ctx, "conn", "Couldn't start TLS! error %d", rc); + + return rc; +} + +/** Return applied flags for the connection. + * + * @param conn a Strophe connection object + * + * @return ORed connection flags that are applied for the connection. + */ +long xmpp_conn_get_flags(const xmpp_conn_t * const conn) +{ + long flags; + + flags = XMPP_CONN_FLAG_DISABLE_TLS * conn->tls_disabled | + XMPP_CONN_FLAG_MANDATORY_TLS * conn->tls_mandatory | + XMPP_CONN_FLAG_LEGACY_SSL * conn->tls_legacy_ssl; + + return flags; +} + +/** Set flags for the connection. + * This function applies set flags and resets unset ones. Default connection + * configuration is all flags unset. Flags can be applied only for a connection + * in disconnected state. + * All unsupported flags are ignored. If a flag is unset after successful set + * operation then the flag is not supported by current version. + * + * Supported flags are: + * + * - XMPP_CONN_FLAG_DISABLE_TLS + * - XMPP_CONN_FLAG_MANDATORY_TLS + * - XMPP_CONN_FLAG_LEGACY_SSL + * + * @param conn a Strophe connection object + * @param flags ORed connection flags + * + * @return 0 on success or -1 if flags can't be applied. + */ +int xmpp_conn_set_flags(xmpp_conn_t * const conn, long flags) +{ + if (conn->state != XMPP_STATE_DISCONNECTED) { + xmpp_error(conn->ctx, "conn", "Flags can be set only " + "for disconnected connection"); + return -1; + } + if (flags & XMPP_CONN_FLAG_DISABLE_TLS && + flags & (XMPP_CONN_FLAG_MANDATORY_TLS | XMPP_CONN_FLAG_LEGACY_SSL)) { + xmpp_error(conn->ctx, "conn", "Flags 0x%04lx conflict", flags); + return -1; + } + + conn->tls_disabled = (flags & XMPP_CONN_FLAG_DISABLE_TLS) ? 1 : 0; + conn->tls_mandatory = (flags & XMPP_CONN_FLAG_MANDATORY_TLS) ? 1 : 0; + conn->tls_legacy_ssl = (flags & XMPP_CONN_FLAG_LEGACY_SSL) ? 1 : 0; + + return 0; +} + +/** Disable TLS for this connection, called by users of the library. + * Occasionally a server will be misconfigured to send the starttls + * feature, but will not support the handshake. + * + * @param conn a Strophe connection object + * + * @note this function is deprecated + * @see xmpp_conn_set_flags() + */ +void xmpp_conn_disable_tls(xmpp_conn_t * const conn) +{ + conn->tls_disabled = 1; +} + +/** Returns whether TLS session is established or not. */ +int xmpp_conn_is_secured(xmpp_conn_t * const conn) +{ + return conn->secured && !conn->tls_failed && conn->tls != NULL ? 1 : 0; +} + +static void _log_open_tag(xmpp_conn_t *conn, char **attrs) +{ + char buf[4096]; + size_t pos; + int len; + int i; + char *attr; + + if (!attrs) return; + + pos = 0; + len = xmpp_snprintf(buf, 4096, "ctx, attrs[i]); + len = xmpp_snprintf(&buf[pos], 4096 - pos, " %s='%s'", + attr, attrs[i+1]); + xmpp_free(conn->ctx, attr); + if (len < 0) return; + pos += len; + } + + len = xmpp_snprintf(&buf[pos], 4096 - pos, ">"); + if (len < 0) return; + + xmpp_debug(conn->ctx, "xmpp", "RECV: %s", buf); +} + +static char *_get_stream_attribute(char **attrs, char *name) +{ + int i; + + if (!attrs) return NULL; + + for (i = 0; attrs[i]; i += 2) + if (strcmp(name, attrs[i]) == 0) + return attrs[i+1]; + + return NULL; +} + +static void _handle_stream_start(char *name, char **attrs, + void * const userdata) +{ + xmpp_conn_t *conn = (xmpp_conn_t *)userdata; + char *id; + + if (strcmp(name, "stream")) { + printf("name = %s\n", name); + xmpp_error(conn->ctx, "conn", "Server did not open valid stream."); + conn_disconnect(conn); + } else { + _log_open_tag(conn, attrs); + + if (conn->stream_id) xmpp_free(conn->ctx, conn->stream_id); + + id = _get_stream_attribute(attrs, "id"); + if (id) + conn->stream_id = xmpp_strdup(conn->ctx, id); + + if (!conn->stream_id) { + xmpp_error(conn->ctx, "conn", "Memory allocation failed."); + conn_disconnect(conn); + } + } + + /* call stream open handler */ + conn->open_handler(conn); +} + +static void _handle_stream_end(char *name, + void * const userdata) +{ + xmpp_conn_t *conn = (xmpp_conn_t *)userdata; + + /* stream is over */ + xmpp_debug(conn->ctx, "xmpp", "RECV: "); + conn_disconnect_clean(conn); +} + +static void _handle_stream_stanza(xmpp_stanza_t *stanza, + void * const userdata) +{ + xmpp_conn_t *conn = (xmpp_conn_t *)userdata; + char *buf; + size_t len; + + if (xmpp_stanza_to_text(stanza, &buf, &len) == 0) { + xmpp_debug(conn->ctx, "xmpp", "RECV: %s", buf); + xmpp_free(conn->ctx, buf); + } + + handler_fire_stanza(conn, stanza); +} + +static unsigned short _conn_default_port(xmpp_conn_t * const conn) +{ + switch (conn->type) { + case XMPP_CLIENT: + return conn->tls_legacy_ssl ? XMPP_PORT_CLIENT_LEGACY_SSL : + XMPP_PORT_CLIENT; + case XMPP_COMPONENT: + return XMPP_PORT_COMPONENT; + default: + return -1; + }; +} diff --git a/source/ctx.c b/source/ctx.c new file mode 100644 index 0000000..ec2b498 --- /dev/null +++ b/source/ctx.c @@ -0,0 +1,429 @@ +/* ctx.c +** strophe XMPP client library -- run-time context implementation +** +** Copyright (C) 2005-2009 Collecta, Inc. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This program is dual licensed under the MIT and GPLv3 licenses. +*/ + +/** @file + * Runtime contexts, library initialization and shutdown, and versioning. + */ + +/** @defgroup Context Context objects + * These functions create and manipulate Strophe context objects. + * + * In order to support usage in a variety of environments, the + * Strophe library uses a runtime context object. This object + * contains the information on how to do memory allocation and + * logging. This allows the user to control how memory is allocated + * and what do to with log messages. + * + * These issues do not affect programs in the common case, but many + * environments require special treatment. Abstracting these into a runtime + * context object makes it easy to use Strophe on embedded platforms. + * + * Objects in Strophe are reference counted to ease memory management issues, + * but the context objects are not. + */ + +/** @defgroup Init Initialization, shutdown, and versioning + * These functions initialize and shutdown the library, and also allow + * for API version checking. Failure to properly call these functions may + * result in strange (and platform dependent) behavior. + * + * Specifically, the socket library on Win32 platforms must be initialized + * before use (although this is not the case on POSIX systems). The TLS + * subsystem must also seed the random number generator. + */ + +#include +#include +#include +#include + +#include "strophe.h" +#include "common.h" +#include "util.h" + +/** Initialize the Strophe library. + * This function initializes subcomponents of the Strophe library and must + * be called for Strophe to operate correctly. + * + * @ingroup Init + */ + void xmpp_initialize(void) +{ + sock_initialize(); + tls_initialize(); +} + +/** Shutdown the Strophe library. + * + * @ingroup Init + */ +void xmpp_shutdown(void) +{ + tls_shutdown(); + sock_shutdown(); +} + +/* version information */ + +#ifndef LIBXMPP_VERSION_MAJOR +/** @def LIBXMPP_VERSION_MAJOR + * The major version number of Strophe. + */ +#define LIBXMPP_VERSION_MAJOR (0) +#endif +#ifndef LIBXMPP_VERSION_MINOR +/** @def LIBXMPP_VERSION_MINOR + * The minor version number of Strophe. + */ +#define LIBXMPP_VERSION_MINOR (0) +#endif + +/** Check that Strophe supports a specific API version. + * + * @param major the major version number + * @param minor the minor version number + * + * @return TRUE if the version is supported and FALSE if unsupported + * + * @ingroup Init + */ +int xmpp_version_check(int major, int minor) +{ + return (major == LIBXMPP_VERSION_MAJOR) && + (minor >= LIBXMPP_VERSION_MINOR); +} + +/* We define the global default allocator, logger, and context here. */ + +/* Wrap stdlib routines malloc, free, and realloc for default memory + * management. + */ +static void *_malloc(const size_t size, void * const userdata) +{ + return malloc(size); +} + +static void _free(void *p, void * const userdata) +{ + free(p); +} + +static void *_realloc(void *p, const size_t size, void * const userdata) +{ + return realloc(p, size); +} + +/* default memory function map */ +static xmpp_mem_t xmpp_default_mem = { + _malloc, /* use the thinly wrapped stdlib routines by default */ + _free, + _realloc, + NULL +}; + +/* log levels and names */ +static const char * const _xmpp_log_level_name[4] = {"DEBUG", "INFO", "WARN", "ERROR"}; +static const xmpp_log_level_t _xmpp_default_logger_levels[] = {XMPP_LEVEL_DEBUG, + XMPP_LEVEL_INFO, + XMPP_LEVEL_WARN, + XMPP_LEVEL_ERROR}; + +/** Log a message. + * The default logger writes to stderr. + * + * @param userdata the opaque data used by the default logger. This contains + * the filter level in the default logger. + * @param level the level to log at + * @param area the area the log message is for + * @param msg the log message + */ +static void xmpp_default_logger(void * const userdata, + const xmpp_log_level_t level, + const char * const area, + const char * const msg) +{ + xmpp_log_level_t filter_level = * (xmpp_log_level_t*)userdata; + if (level >= filter_level) + fprintf(stderr, "%s %s %s\n", area, _xmpp_log_level_name[level], msg); +} + +static const xmpp_log_t _xmpp_default_loggers[] = { + {&xmpp_default_logger, (void*)&_xmpp_default_logger_levels[XMPP_LEVEL_DEBUG]}, + {&xmpp_default_logger, (void*)&_xmpp_default_logger_levels[XMPP_LEVEL_INFO]}, + {&xmpp_default_logger, (void*)&_xmpp_default_logger_levels[XMPP_LEVEL_WARN]}, + {&xmpp_default_logger, (void*)&_xmpp_default_logger_levels[XMPP_LEVEL_ERROR]} +}; + +/** Get a default logger with filtering. + * The default logger provides a basic logging setup which writes log + * messages to stderr. Only messages where level is greater than or + * equal to the filter level will be logged. + * + * @param level the highest level the logger will log at + * + * @return the log structure for the given level + * + * @ingroup Context + */ +xmpp_log_t *xmpp_get_default_logger(xmpp_log_level_t level) +{ + /* clamp to the known range */ + if (level > XMPP_LEVEL_ERROR) level = XMPP_LEVEL_ERROR; + if (level < XMPP_LEVEL_DEBUG) level = XMPP_LEVEL_DEBUG; + + return (xmpp_log_t*)&_xmpp_default_loggers[level]; +} + +static xmpp_log_t xmpp_default_log = { NULL, NULL }; + +/* convenience functions for accessing the context */ + +/** Allocate memory in a Strophe context. + * All Strophe functions will use this to allocate memory. + * + * @param ctx a Strophe context object + * @param size the number of bytes to allocate + * + * @return a pointer to the allocated memory or NULL on an error + */ +void *xmpp_alloc(const xmpp_ctx_t * const ctx, const size_t size) +{ + return ctx->mem->alloc(size, ctx->mem->userdata); +} + +/** Free memory in a Strophe context. + * All Strophe functions will use this to free allocated memory. + * + * @param ctx a Strophe context object + * @param p a pointer referencing memory to be freed + */ +void xmpp_free(const xmpp_ctx_t * const ctx, void *p) +{ + ctx->mem->free(p, ctx->mem->userdata); +} + +/** Reallocate memory in a Strophe context. + * All Strophe functions will use this to reallocate memory. + * + * @param ctx a Strophe context object + * @param p a pointer to previously allocated memory + * @param size the new size in bytes to allocate + * + * @return a pointer to the reallocated memory or NULL on an error + */ +void *xmpp_realloc(const xmpp_ctx_t * const ctx, void *p, + const size_t size) +{ + return ctx->mem->realloc(p, size, ctx->mem->userdata); +} + +/** Write a log message to the logger. + * Write a log message to the logger for the context for the specified + * level and area. This function takes a printf-style format string and a + * variable argument list (in va_list) format. This function is not meant + * to be called directly, but is used via xmpp_error, xmpp_warn, xmpp_info, + * and xmpp_debug. + * + * @param ctx a Strophe context object + * @param level the level at which to log + * @param area the area to log for + * @param fmt a printf-style format string for the message + * @param ap variable argument list supplied for the format string + */ +void xmpp_log(const xmpp_ctx_t * const ctx, + const xmpp_log_level_t level, + const char * const area, + const char * const fmt, + va_list ap) +{ + int oldret, ret; + char smbuf[1024]; + char *buf; + va_list copy; + + va_copy(copy, ap); + ret = xmpp_vsnprintf(smbuf, sizeof(smbuf), fmt, ap); + if (ret >= (int)sizeof(smbuf)) { + buf = (char *)xmpp_alloc(ctx, ret + 1); + if (!buf) { + buf = NULL; + xmpp_error(ctx, "log", "Failed allocating memory for log message."); + va_end(copy); + return; + } + oldret = ret; + ret = xmpp_vsnprintf(buf, ret + 1, fmt, copy); + if (ret > oldret) { + xmpp_error(ctx, "log", "Unexpected error"); + xmpp_free(ctx, buf); + va_end(copy); + return; + } + } else { + buf = smbuf; + } + va_end(copy); + + if (ctx->log->handler) + ctx->log->handler(ctx->log->userdata, level, area, buf); + + if (buf != smbuf) + xmpp_free(ctx, buf); +} + +/** Write to the log at the ERROR level. + * This is a convenience function for writing to the log at the + * ERROR level. It takes a printf-style format string followed by a + * variable list of arguments for formatting. + * + * @param ctx a Strophe context object + * @param area the area to log for + * @param fmt a printf-style format string followed by a variable list of + * arguments to format + */ +void xmpp_error(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + xmpp_log(ctx, XMPP_LEVEL_ERROR, area, fmt, ap); + va_end(ap); +} + +/** Write to the log at the WARN level. + * This is a convenience function for writing to the log at the WARN level. + * It takes a printf-style format string followed by a variable list of + * arguments for formatting. + * + * @param ctx a Strophe context object + * @param area the area to log for + * @param fmt a printf-style format string followed by a variable list of + * arguments to format + */ +void xmpp_warn(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + xmpp_log(ctx, XMPP_LEVEL_WARN, area, fmt, ap); + va_end(ap); +} + +/** Write to the log at the INFO level. + * This is a convenience function for writing to the log at the INFO level. + * It takes a printf-style format string followed by a variable list of + * arguments for formatting. + * + * @param ctx a Strophe context object + * @param area the area to log for + * @param fmt a printf-style format string followed by a variable list of + * arguments to format + */ +void xmpp_info(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + xmpp_log(ctx, XMPP_LEVEL_INFO, area, fmt, ap); + va_end(ap); +} + +/** Write to the log at the DEBUG level. + * This is a convenience function for writing to the log at the DEBUG level. + * It takes a printf-style format string followed by a variable list of + * arguments for formatting. + * + * @param ctx a Strophe context object + * @param area the area to log for + * @param fmt a printf-style format string followed by a variable list of + * arguments to format + */ +void xmpp_debug(const xmpp_ctx_t * const ctx, + const char * const area, + const char * const fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + xmpp_log(ctx, XMPP_LEVEL_DEBUG, area, fmt, ap); + va_end(ap); +} + +/** Create and initialize a Strophe context object. + * If mem is NULL, a default allocation setup will be used which + * wraps malloc(), free(), and realloc() from the standard library. + * If log is NULL, a default logger will be used which does no + * logging. Basic filtered logging to stderr can be done with the + * xmpp_get_default_logger() convenience function. + * + * @param mem a pointer to an xmpp_mem_t structure or NULL + * @param log a pointer to an xmpp_log_t structure or NULL + * + * @return the allocated Strophe context object or NULL on an error + * + * @ingroup Context + */ +xmpp_ctx_t *xmpp_ctx_new(const xmpp_mem_t * const mem, + const xmpp_log_t * const log) +{ + xmpp_ctx_t *ctx = NULL; + + if (mem == NULL) + ctx = xmpp_default_mem.alloc(sizeof(xmpp_ctx_t), NULL); + else + ctx = mem->alloc(sizeof(xmpp_ctx_t), mem->userdata); + + if (ctx != NULL) { + if (mem != NULL) + ctx->mem = mem; + else + ctx->mem = &xmpp_default_mem; + + if (log == NULL) + ctx->log = &xmpp_default_log; + else + ctx->log = log; + + ctx->connlist = NULL; + ctx->loop_status = XMPP_LOOP_NOTSTARTED; + ctx->rand = xmpp_rand_new(ctx); + if (ctx->rand == NULL) { + xmpp_free(ctx, ctx); + ctx = NULL; + } + } + + return ctx; +} + +/** Free a Strophe context object that is no longer in use. + * + * @param ctx a Strophe context object + * + * @ingroup Context + */ +void xmpp_ctx_free(xmpp_ctx_t * const ctx) +{ + /* mem and log are owned by their suppliers */ + xmpp_rand_free(ctx, ctx->rand); + xmpp_free(ctx, ctx); /* pull the hole in after us */ +} + diff --git a/source/event.c b/source/event.c new file mode 100644 index 0000000..ee10fd6 --- /dev/null +++ b/source/event.c @@ -0,0 +1,367 @@ +/* event.c +** strophe XMPP client library -- event loop and management +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * Event loop and management. + */ + +/** @defgroup EventLoop Event loop + * These functions manage the Strophe event loop. + * + * Simple tools can use xmpp_run() and xmpp_stop() to manage the life + * cycle of the program. A common idiom is to set up a few initial + * event handers, call xmpp_run(), and then respond and react to + * events as they come in. At some point, one of the handlers will + * call xmpp_stop() to quit the event loop which leads to the program + * terminating. + * + * More complex programs will have their own event loops, and should + * ensure that xmpp_run_once() is called regularly from there. For + * example, a GUI program will already include an event loop to + * process UI events from users, and xmpp_run_once() would be called + * from an idle function. + */ + +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#define _sleep(x) usleep(x*1000) +#else +#include +#define ETIMEDOUT WSAETIMEDOUT +#define ECONNRESET WSAECONNRESET +#define ECONNABORTED WSAECONNABORTED +#define _sleep(x) Sleep(x) +#endif + +#include +#include "common.h" +#include "parser.h" + +#ifndef DEFAULT_TIMEOUT +/** @def DEFAULT_TIMEOUT + * The default timeout in milliseconds for the event loop. + * This is set to 1 millisecond. + */ +#define DEFAULT_TIMEOUT 1 +#endif + +/** Run the event loop once. + * This function will run send any data that has been queued by + * xmpp_send and related functions and run through the Strophe even + * loop a single time, and will not wait more than timeout + * milliseconds for events. This is provided to support integration + * with event loops outside the library, and if used, should be + * called regularly to achieve low latency event handling. + * + * @param ctx a Strophe context object + * @param timeout time to wait for events in milliseconds + * + * @ingroup EventLoop + */ +void xmpp_run_once(xmpp_ctx_t *ctx, const unsigned long timeout) +{ + xmpp_connlist_t *connitem; + xmpp_conn_t *conn; + fd_set rfds, wfds; + sock_t max = 0; + int ret; + struct timeval tv; + xmpp_send_queue_t *sq, *tsq; + int towrite; + char buf[4096]; + uint64_t next; + long usec; + int tls_read_bytes = 0; + + if (ctx->loop_status == XMPP_LOOP_QUIT) return; + ctx->loop_status = XMPP_LOOP_RUNNING; + + /* send queued data */ + connitem = ctx->connlist; + while (connitem) { + conn = connitem->conn; + if (conn->state != XMPP_STATE_CONNECTED) { + connitem = connitem->next; + continue; + } + + /* if we're running tls, there may be some remaining data waiting to + * be sent, so push that out */ + if (conn->tls) { + ret = tls_clear_pending_write(conn->tls); + + if (ret < 0 && !tls_is_recoverable(tls_error(conn->tls))) { + /* an error occured */ + xmpp_debug(ctx, "xmpp", "Send error occured, disconnecting."); + conn->error = ECONNABORTED; + conn_disconnect(conn); + } + } + + /* write all data from the send queue to the socket */ + sq = conn->send_queue_head; + while (sq) { + towrite = sq->len - sq->written; + + if (conn->tls) { + ret = tls_write(conn->tls, &sq->data[sq->written], towrite); + + if (ret < 0 && !tls_is_recoverable(tls_error(conn->tls))) { + /* an error occured */ + conn->error = tls_error(conn->tls); + break; + } else if (ret < towrite) { + /* not all data could be sent now */ + if (ret >= 0) sq->written += ret; + break; + } + + } else { + ret = sock_write(conn->sock, &sq->data[sq->written], towrite); + + if (ret < 0 && !sock_is_recoverable(sock_error())) { + /* an error occured */ + conn->error = sock_error(); + break; + } else if (ret < towrite) { + /* not all data could be sent now */ + if (ret >= 0) sq->written += ret; + break; + } + } + + /* all data for this queue item written, delete and move on */ + xmpp_free(ctx, sq->data); + tsq = sq; + sq = sq->next; + xmpp_free(ctx, tsq); + + /* pop the top item */ + conn->send_queue_head = sq; + /* if we've sent everything update the tail */ + if (!sq) conn->send_queue_tail = NULL; + } + + /* tear down connection on error */ + if (conn->error) { + /* FIXME: need to tear down send queues and random other things + * maybe this should be abstracted */ + xmpp_debug(ctx, "xmpp", "Send error occured, disconnecting."); + conn->error = ECONNABORTED; + conn_disconnect(conn); + } + + connitem = connitem->next; + } + + /* reset parsers if needed */ + for (connitem = ctx->connlist; connitem; connitem = connitem->next) { + if (connitem->conn->reset_parser) + conn_parser_reset(connitem->conn); + } + + + /* fire any ready timed handlers, then + make sure we don't wait past the time when timed handlers need + to be called */ + next = handler_fire_timed(ctx); + + usec = ((next < timeout) ? next : timeout) * 1000; + tv.tv_sec = usec / 1000000; + tv.tv_usec = usec % 1000000; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + + /* find events to watch */ + connitem = ctx->connlist; + while (connitem) { + conn = connitem->conn; + + switch (conn->state) { + case XMPP_STATE_CONNECTING: + /* connect has been called and we're waiting for it to complete */ + /* connection will give us write or error events */ + + /* make sure the timeout hasn't expired */ + if (time_elapsed(conn->timeout_stamp, time_stamp()) <= + conn->connect_timeout) + FD_SET(conn->sock, &wfds); + else { + conn->error = ETIMEDOUT; + xmpp_info(ctx, "xmpp", "Connection attempt timed out."); + conn_disconnect(conn); + } + break; + case XMPP_STATE_CONNECTED: + FD_SET(conn->sock, &rfds); + break; + case XMPP_STATE_DISCONNECTED: + /* do nothing */ + default: + break; + } + + /* Check if there is something in the SSL buffer. */ + if (conn->tls) { + tls_read_bytes += tls_pending(conn->tls); + } + + if (conn->state != XMPP_STATE_DISCONNECTED && conn->sock > max) + max = conn->sock; + + connitem = connitem->next; + } + + /* check for events */ + if (max > 0) + ret = select(max + 1, &rfds, &wfds, NULL, &tv); + else { + if (timeout > 0) + _sleep(timeout); + return; + } + + /* select errored */ + if (ret < 0) { + if (!sock_is_recoverable(sock_error())) + xmpp_error(ctx, "xmpp", "event watcher internal error %d", + sock_error()); + return; + } + + /* no events happened */ + if (ret == 0 && tls_read_bytes == 0) return; + + /* process events */ + connitem = ctx->connlist; + while (connitem) { + conn = connitem->conn; + + switch (conn->state) { + case XMPP_STATE_CONNECTING: + if (FD_ISSET(conn->sock, &wfds)) { + /* connection complete */ + + /* check for error */ + ret = sock_connect_error(conn->sock); + if (ret != 0) { + /* connection failed */ + xmpp_debug(ctx, "xmpp", "connection failed, error %d", ret); + conn_disconnect(conn); + break; + } + + conn->state = XMPP_STATE_CONNECTED; + xmpp_debug(ctx, "xmpp", "connection successful"); + + if (conn->tls_legacy_ssl) { + xmpp_debug(ctx, "xmpp", "using legacy SSL connection"); + ret = conn_tls_start(conn); + if (ret != 0) { + conn_disconnect(conn); + break; + } + } + + /* send stream init */ + conn_open_stream(conn); + } + + break; + case XMPP_STATE_CONNECTED: + if (FD_ISSET(conn->sock, &rfds) || (conn->tls && tls_pending(conn->tls))) { + if (conn->tls) { + ret = tls_read(conn->tls, buf, 4096); + } else { + ret = sock_read(conn->sock, buf, 4096); + } + + if (ret > 0) { + ret = parser_feed(conn->parser, buf, ret); + if (!ret) { + /* parse error, we need to shut down */ + /* FIXME */ + xmpp_debug(ctx, "xmpp", "parse error, disconnecting"); + conn_disconnect(conn); + } + } else { + if (conn->tls) { + if (!tls_is_recoverable(tls_error(conn->tls))) + { + xmpp_debug(ctx, "xmpp", "Unrecoverable TLS error, %d.", tls_error(conn->tls)); + conn->error = tls_error(conn->tls); + conn_disconnect(conn); + } + } else { + /* return of 0 means socket closed by server */ + xmpp_debug(ctx, "xmpp", "Socket closed by remote host."); + conn->error = ECONNRESET; + conn_disconnect(conn); + } + } + } + + break; + case XMPP_STATE_DISCONNECTED: + /* do nothing */ + default: + break; + } + + connitem = connitem->next; + } + + /* fire any ready handlers */ + handler_fire_timed(ctx); +} + +/** Start the event loop. + * This function continuously calls xmpp_run_once and does not return + * until xmpp_stop has been called. + * + * @param ctx a Strophe context object + * + * @ingroup EventLoop + */ +void xmpp_run(xmpp_ctx_t *ctx) +{ + if (ctx->loop_status != XMPP_LOOP_NOTSTARTED) return; + + ctx->loop_status = XMPP_LOOP_RUNNING; + while (ctx->loop_status == XMPP_LOOP_RUNNING) { + xmpp_run_once(ctx, DEFAULT_TIMEOUT); + } + + xmpp_debug(ctx, "event", "Event loop completed."); +} + +/** Stop the event loop. + * This will stop the event loop after the current iteration and cause + * xmpp_run to exit. + * + * @param ctx a Strophe context object + * + * @ingroup EventLoop + */ +void xmpp_stop(xmpp_ctx_t *ctx) +{ + xmpp_debug(ctx, "event", "Stopping event loop."); + + if (ctx->loop_status == XMPP_LOOP_RUNNING) + ctx->loop_status = XMPP_LOOP_QUIT; +} diff --git a/source/handler.c b/source/handler.c new file mode 100644 index 0000000..c369435 --- /dev/null +++ b/source/handler.c @@ -0,0 +1,597 @@ +/* handler.c +** strophe XMPP client library -- event handler management +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * Event handler management. + */ + +/** @defgroup Handlers Stanza and timed event handlers + */ + +#include +#include +#include + +#include "strophe.h" +#include "common.h" +#include "ostypes.h" + +/** Fire off all stanza handlers that match. + * This function is called internally by the event loop whenever stanzas + * are received from the XMPP server. + * + * @param conn a Strophe connection object + * @param stanza a Strophe stanza object + */ +void handler_fire_stanza(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza) +{ + xmpp_handlist_t *item, *prev; + char *id, *ns, *name, *type; + + /* call id handlers */ + id = xmpp_stanza_get_id(stanza); + if (id) { + prev = NULL; + item = (xmpp_handlist_t *)hash_get(conn->id_handlers, id); + while (item) { + xmpp_handlist_t *next = item->next; + + if (item->user_handler && !conn->authenticated) { + item = next; + continue; + } + + if (!((xmpp_handler)(item->handler))(conn, stanza, item->userdata)) { + /* handler is one-shot, so delete it */ + if (prev) + prev->next = next; + else { + hash_drop(conn->id_handlers, id); + hash_add(conn->id_handlers, id, next); + } + xmpp_free(conn->ctx, item->id); + xmpp_free(conn->ctx, item); + item = NULL; + } + if (item) + prev = item; + item = next; + } + } + + /* call handlers */ + ns = xmpp_stanza_get_ns(stanza); + name = xmpp_stanza_get_name(stanza); + type = xmpp_stanza_get_type(stanza); + + /* enable all added handlers */ + for (item = conn->handlers; item; item = item->next) + item->enabled = 1; + + prev = NULL; + item = conn->handlers; + while (item) { + /* skip newly added handlers */ + if (!item->enabled) { + prev = item; + item = item->next; + continue; + } + + /* don't call user handlers until authentication succeeds */ + if (item->user_handler && !conn->authenticated) { + prev = item; + item = item->next; + continue; + } + + if ((!item->ns || (ns && strcmp(ns, item->ns) == 0) || + xmpp_stanza_get_child_by_ns(stanza, item->ns)) && + (!item->name || (name && strcmp(name, item->name) == 0)) && + (!item->type || (type && strcmp(type, item->type) == 0))) + if (!((xmpp_handler)(item->handler))(conn, stanza, item->userdata)) { + /* handler is one-shot, so delete it */ + if (prev) + prev->next = item->next; + else + conn->handlers = item->next; + if (item->ns) xmpp_free(conn->ctx, item->ns); + if (item->name) xmpp_free(conn->ctx, item->name); + if (item->type) xmpp_free(conn->ctx, item->type); + xmpp_free(conn->ctx, item); + item = NULL; + } + + if (item) { + prev = item; + item = item->next; + } else if (prev) + item = prev->next; + else + item = conn->handlers; + } +} + +/** Fire off all timed handlers that are ready. + * This function is called internally by the event loop. + * + * @param ctx a Strophe context object + * + * @return the time in milliseconds until the next handler will be ready + */ +uint64_t handler_fire_timed(xmpp_ctx_t * const ctx) +{ + xmpp_connlist_t *connitem; + xmpp_handlist_t *handitem, *temp; + int ret, fired; + uint64_t elapsed, min; + + min = (uint64_t)(-1); + + connitem = ctx->connlist; + while (connitem) { + if (connitem->conn->state != XMPP_STATE_CONNECTED) { + connitem = connitem->next; + continue; + } + + /* enable all handlers that were added */ + for (handitem = connitem->conn->timed_handlers; handitem; + handitem = handitem->next) + handitem->enabled = 1; + + handitem = connitem->conn->timed_handlers; + while (handitem) { + /* skip newly added handlers */ + if (!handitem->enabled) { + handitem = handitem->next; + continue; + } + + /* only fire user handlers after authentication */ + if (handitem->user_handler && !connitem->conn->authenticated) { + handitem = handitem->next; + continue; + } + + fired = 0; + elapsed = time_elapsed(handitem->last_stamp, time_stamp()); + if (elapsed >= handitem->period) { + /* fire! */ + fired = 1; + handitem->last_stamp = time_stamp(); + ret = ((xmpp_timed_handler)handitem->handler)(connitem->conn, handitem->userdata); + } else if (min > (handitem->period - elapsed)) + min = handitem->period - elapsed; + + temp = handitem; + handitem = handitem->next; + + /* delete handler if it returned false */ + if (fired && !ret) + xmpp_timed_handler_delete(connitem->conn, temp->handler); + } + + connitem = connitem->next; + } + + return min; +} + +/** Reset all timed handlers. + * This function is called internally when a connection is successful. + * + * @param conn a Strophe connection object + * @param user_only whether to reset all handlers or only user ones + */ +void handler_reset_timed(xmpp_conn_t *conn, int user_only) +{ + xmpp_handlist_t *handitem; + + handitem = conn->timed_handlers; + while (handitem) { + if ((user_only && handitem->user_handler) || !user_only) + handitem->last_stamp = time_stamp(); + + handitem = handitem->next; + } +} + +static void _timed_handler_add(xmpp_conn_t * const conn, + xmpp_timed_handler handler, + const unsigned long period, + void * const userdata, + const int user_handler) +{ + xmpp_handlist_t *item, *tail; + + /* check if handler is already in the list */ + for (item = conn->timed_handlers; item; item = item->next) { + if (item->handler == (void *)handler) + break; + } + if (item) return; + + /* build new item */ + item = xmpp_alloc(conn->ctx, sizeof(xmpp_handlist_t)); + if (!item) return; + + item->user_handler = user_handler; + item->handler = (void *)handler; + item->userdata = userdata; + item->enabled = 0; + item->next = NULL; + + item->period = period; + item->last_stamp = time_stamp(); + + /* append item to list */ + if (!conn->timed_handlers) + conn->timed_handlers = item; + else { + tail = conn->timed_handlers; + while (tail->next) + tail = tail->next; + tail->next = item; + } +} + +/** Delete a timed handler. + * + * @param conn a Strophe connection object + * @param handler function pointer to the handler + * + * @ingroup Handlers + */ +void xmpp_timed_handler_delete(xmpp_conn_t * const conn, + xmpp_timed_handler handler) +{ + xmpp_handlist_t *item, *prev; + + if (!conn->timed_handlers) return; + + prev = NULL; + item = conn->timed_handlers; + while (item) { + if (item->handler == (void *)handler) + break; + prev = item; + item = item->next; + } + + if (item) { + if (prev) + prev->next = item->next; + else + conn->timed_handlers = item->next; + + xmpp_free(conn->ctx, item); + } +} + +static void _id_handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const id, + void * const userdata, int user_handler) +{ + xmpp_handlist_t *item, *tail; + + /* check if handler is already in the list */ + item = (xmpp_handlist_t *)hash_get(conn->id_handlers, id); + while (item) { + if (item->handler == (void *)handler) + break; + item = item->next; + } + if (item) return; + + /* build new item */ + item = xmpp_alloc(conn->ctx, sizeof(xmpp_handlist_t)); + if (!item) return; + + item->user_handler = user_handler; + item->handler = (void *)handler; + item->userdata = userdata; + item->enabled = 0; + item->next = NULL; + + item->id = xmpp_strdup(conn->ctx, id); + if (!item->id) { + xmpp_free(conn->ctx, item); + return; + } + + /* put on list in hash table */ + tail = (xmpp_handlist_t *)hash_get(conn->id_handlers, id); + if (!tail) + hash_add(conn->id_handlers, id, item); + else { + while (tail->next) + tail = tail->next; + tail->next = item; + } +} + +/** Delete an id based stanza handler. + * + * @param conn a Strophe connection object + * @param handler a function pointer to a stanza handler + * @param id a string containing the id the handler is for + * + * @ingroup Handlers + */ +void xmpp_id_handler_delete(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const id) +{ + xmpp_handlist_t *item, *prev; + + prev = NULL; + item = (xmpp_handlist_t *)hash_get(conn->id_handlers, id); + if (!item) return; + + while (item) { + if (item->handler == (void *)handler) + break; + + prev = item; + item = item->next; + } + + if (item) { + if (prev) + prev->next = item->next; + else { + hash_drop(conn->id_handlers, id); + hash_add(conn->id_handlers, id, item->next); + } + xmpp_free(conn->ctx, item->id); + xmpp_free(conn->ctx, item); + } +} + +/* add a stanza handler */ +static void _handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const ns, + const char * const name, + const char * const type, + void * const userdata, int user_handler) +{ + xmpp_handlist_t *item, *tail; + + /* check if handler already in list */ + for (item = conn->handlers; item; item = item->next) { + if (item->handler == (void *)handler) + break; + } + if (item) return; + + /* build new item */ + item = (xmpp_handlist_t *)xmpp_alloc(conn->ctx, sizeof(xmpp_handlist_t)); + if (!item) return; + + item->user_handler = user_handler; + item->handler = (void *)handler; + item->userdata = userdata; + item->enabled = 0; + item->next = NULL; + + if (ns) { + item->ns = xmpp_strdup(conn->ctx, ns); + if (!item->ns) { + xmpp_free(conn->ctx, item); + return; + } + } else + item->ns = NULL; + if (name) { + item->name = xmpp_strdup(conn->ctx, name); + if (!item->name) { + if (item->ns) xmpp_free(conn->ctx, item->ns); + xmpp_free(conn->ctx, item); + return; + } + } else + item->name = NULL; + if (type) { + item->type = xmpp_strdup(conn->ctx, type); + if (!item->type) { + if (item->ns) xmpp_free(conn->ctx, item->ns); + if (item->name) xmpp_free(conn->ctx, item->name); + xmpp_free(conn->ctx, item); + } + } else + item->type = NULL; + + /* append to list */ + if (!conn->handlers) + conn->handlers = item; + else { + tail = conn->handlers; + while (tail->next) + tail = tail->next; + tail->next = item; + } +} + +/** Delete a stanza handler. + * + * @param conn a Strophe connection object + * @param handler a function pointer to a stanza handler + * + * @ingroup Handlers + */ +void xmpp_handler_delete(xmpp_conn_t * const conn, + xmpp_handler handler) +{ + xmpp_handlist_t *prev, *item; + + if (!conn->handlers) return; + + prev = NULL; + item = conn->handlers; + while (item) { + if (item->handler == (void *)handler) + break; + + prev = item; + item = item->next; + } + + if (item) { + if (prev) + prev->next = item->next; + else + conn->handlers = item->next; + + if (item->ns) xmpp_free(conn->ctx, item->ns); + if (item->name) xmpp_free(conn->ctx, item->name); + if (item->type) xmpp_free(conn->ctx, item->type); + xmpp_free(conn->ctx, item); + } +} + +/** Add a timed handler. + * The handler will fire for the first time once the period has elapsed, + * and continue firing regularly after that. Strophe will try its best + * to fire handlers as close to the period times as it can, but accuracy + * will vary depending on the resolution of the event loop. + * + * If the handler function returns true, it will be kept, and if it + * returns false, it will be deleted from the list of handlers. + * + * @param conn a Strophe connection object + * @param handler a function pointer to a timed handler + * @param period the time in milliseconds between firings + * @param userdata an opaque data pointer that will be passed to the handler + * + * @ingroup Handlers + */ +void xmpp_timed_handler_add(xmpp_conn_t * const conn, + xmpp_timed_handler handler, + const unsigned long period, + void * const userdata) +{ + _timed_handler_add(conn, handler, period, userdata, 1); +} + +/** Add a timed system handler. + * This function is used to add internal timed handlers and should not be + * used outside of the library. + * + * @param conn a Strophe connection object + * @param handler a function pointer to a timed handler + * @param period the time in milliseconds between firings + * @param userdata an opaque data pointer that will be passed to the handler + */ +void handler_add_timed(xmpp_conn_t * const conn, + xmpp_timed_handler handler, + const unsigned long period, + void * const userdata) +{ + _timed_handler_add(conn, handler, period, userdata, 0); +} + +/** Add an id based stanza handler. + + * This function adds a stanza handler for an <iq/> stanza of + * type 'result' or 'error' with a specific id attribute. This can + * be used to handle responses to specific <iq/>s. + * + * If the handler function returns true, it will be kept, and if it + * returns false, it will be deleted from the list of handlers. + * + * @param conn a Strophe connection object + * @param handler a function pointer to a stanza handler + * @param id a string with the id + * @param userdata an opaque data pointer that will be passed to the handler + * + * @ingroup Handlers + */ +void xmpp_id_handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const id, + void * const userdata) +{ + _id_handler_add(conn, handler, id, userdata, 1); +} + +/** Add an id based system stanza handler. + * This function is used to add internal id based stanza handlers and should + * not be used outside of the library. + * + * @param conn a Strophe connection object + * @param handler a function pointer to a stanza handler + * @param id a string with the id + * @param userdata an opaque data pointer that will be passed to the handler + */ +void handler_add_id(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const id, + void * const userdata) +{ + _id_handler_add(conn, handler, id, userdata, 0); +} + +/** Add a stanza handler. + * This function is used to add a stanza handler to a connection. + * The handler will be called when the any of the filters match. The + * name filter matches to the top level stanza name. The type filter + * matches the 'type' attribute of the top level stanza. The ns + * filter matches the namespace ('xmlns' attribute) of either the top + * level stanza or any of it's immediate children (this allows you do + * handle specific <iq/> stanzas based on the <query/> + * child namespace. + * + * If the handler function returns true, it will be kept, and if it + * returns false, it will be deleted from the list of handlers. + * + * @param conn a Strophe connection object + * @param handler a function pointer to a stanza handler + * @param ns a string with the namespace to match + * @param name a string with the stanza name to match + * @param type a string with the 'type' attribute to match + * @param userdata an opaque data pointer that will be passed to the handler + * + * @ingroup Handlers + */ +void xmpp_handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const ns, + const char * const name, + const char * const type, + void * const userdata) +{ + _handler_add(conn, handler, ns, name, type, userdata, 1); +} + +/** Add a system stanza handler. + * This function is used to add internal stanza handlers and should + * not be used outside of the library. + * + * @param conn a Strophe connection object + * @param handler a function pointer to a stanza handler + * @param ns a string with the namespace to match + * @param name a string with the stanza name to match + * @param type a string with the 'type' attribute value to match + * @param userdata an opaque data pointer that will be passed to the handler + */ +void handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const ns, + const char * const name, + const char * const type, + void * const userdata) +{ + _handler_add(conn, handler, ns, name, type, userdata, 0); +} diff --git a/source/hash.c b/source/hash.c new file mode 100644 index 0000000..8318d60 --- /dev/null +++ b/source/hash.c @@ -0,0 +1,276 @@ +/* hash.c +** strophe XMPP client library -- hash table implementation +** +** Copyright (C) 2005-2009 Collecta, Inc. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This program is dual licensed under the MIT and GPLv3 licenses. +*/ + +/** @file + * Hash tables. + */ + +#include +#include + +#include "strophe.h" +#include "common.h" +#include "hash.h" + +/* private types */ +typedef struct _hashentry_t hashentry_t; + +struct _hashentry_t { + hashentry_t *next; + char *key; + void *value; +}; + +struct _hash_t { + unsigned int ref; + xmpp_ctx_t *ctx; + hash_free_func free; + int length; + int num_keys; + hashentry_t **entries; +}; + +struct _hash_iterator_t { + unsigned int ref; + hash_t *table; + hashentry_t *entry; + int index; +}; + +/** allocate and initialize a new hash table */ +hash_t *hash_new(xmpp_ctx_t * const ctx, const int size, + hash_free_func free_func) +{ + hash_t *result = NULL; + + result = xmpp_alloc(ctx, sizeof(hash_t)); + if (result != NULL) { + result->entries = xmpp_alloc(ctx, size * sizeof(hashentry_t *)); + if (result->entries == NULL) { + xmpp_free(ctx, result); + return NULL; + } + memset(result->entries, 0, size * sizeof(hashentry_t *)); + result->length = size; + + result->ctx = ctx; + result->free = free_func; + result->num_keys = 0; + /* give the caller a reference */ + result->ref = 1; + } + + return result; +} + +/** obtain a new reference to an existing hash table */ +hash_t *hash_clone(hash_t * const table) +{ + table->ref++; + return table; +} + +/** release a hash table that is no longer needed */ +void hash_release(hash_t * const table) +{ + xmpp_ctx_t *ctx = table->ctx; + hashentry_t *entry, *next; + int i; + + if (table->ref > 1) + table->ref--; + else { + for (i = 0; i < table->length; i++) { + entry = table->entries[i]; + while (entry != NULL) { + next = entry->next; + xmpp_free(ctx, entry->key); + if (table->free) table->free(ctx, entry->value); + xmpp_free(ctx, entry); + entry = next; + } + } + xmpp_free(ctx, table->entries); + xmpp_free(ctx, table); + } +} + +/** hash a key for our table lookup */ +static int _hash_key(hash_t *table, const char *key) +{ + int hash = 0; + int shift = 0; + const char *c = key; + + while (*c != '\0') { + /* assume 32 bit ints */ + hash ^= ((int)*c++ << shift); + shift += 8; + if (shift > 24) shift = 0; + } + + return hash % table->length; +} + +/** add a key, value pair to a hash table. + * each key can appear only once; the value of any + * identical key will be replaced + */ +int hash_add(hash_t *table, const char * const key, void *data) +{ + xmpp_ctx_t *ctx = table->ctx; + hashentry_t *entry = NULL; + int table_index = _hash_key(table, key); + + /* drop existing entry, if any */ + hash_drop(table, key); + + /* allocate and fill a new entry */ + entry = xmpp_alloc(ctx, sizeof(hashentry_t)); + if (!entry) return -1; + entry->key = xmpp_strdup(ctx, key); + if (!entry->key) { + xmpp_free(ctx, entry); + return -1; + } + entry->value = data; + /* insert ourselves in the linked list */ + /* TODO: this leaks duplicate keys */ + entry->next = table->entries[table_index]; + table->entries[table_index] = entry; + table->num_keys++; + + return 0; +} + +/** look up a key in a hash table */ +void *hash_get(hash_t *table, const char *key) +{ + hashentry_t *entry; + int table_index = _hash_key(table, key); + void *result = NULL; + + /* look up the hash entry */ + entry = table->entries[table_index]; + while (entry != NULL) { + /* traverse the linked list looking for the key */ + if (!strcmp(key, entry->key)) { + /* match */ + result = entry->value; + return result; + } + entry = entry->next; + } + /* no match */ + return result; +} + +/** delete a key from a hash table */ +int hash_drop(hash_t *table, const char *key) +{ + xmpp_ctx_t *ctx = table->ctx; + hashentry_t *entry, *prev; + int table_index = _hash_key(table, key); + + /* look up the hash entry */ + entry = table->entries[table_index]; + prev = NULL; + while (entry != NULL) { + /* traverse the linked list looking for the key */ + if (!strcmp(key, entry->key)) { + /* match, remove the entry */ + xmpp_free(ctx, entry->key); + if (table->free) table->free(ctx, entry->value); + if (prev == NULL) { + table->entries[table_index] = entry->next; + } else { + prev->next = entry->next; + } + xmpp_free(ctx, entry); + table->num_keys--; + return 0; + } + prev = entry; + entry = entry->next; + } + /* no match */ + return -1; +} + +int hash_num_keys(hash_t *table) +{ + return table->num_keys; +} + +/** allocate and initialize a new iterator */ +hash_iterator_t *hash_iter_new(hash_t *table) +{ + xmpp_ctx_t *ctx = table->ctx; + hash_iterator_t *iter; + + iter = xmpp_alloc(ctx, sizeof(*iter)); + if (iter != NULL) { + iter->ref = 1; + iter->table = hash_clone(table); + iter->entry = NULL; + iter->index = -1; + } + + return iter; +} + + +/** release an iterator that is no longer needed */ +void hash_iter_release(hash_iterator_t *iter) +{ + xmpp_ctx_t *ctx = iter->table->ctx; + + iter->ref--; + + if (iter->ref <= 0) { + hash_release(iter->table); + xmpp_free(ctx, iter); + } +} + +/** return the next hash table key from the iterator. + the returned key should not be freed */ +const char * hash_iter_next(hash_iterator_t *iter) +{ + hash_t *table = iter->table; + hashentry_t *entry = iter->entry; + int i; + + /* advance until we find the next entry */ + if (entry != NULL) entry = entry->next; + if (entry == NULL) { + /* we're off the end of list, search for a new entry */ + i = iter->index + 1; + while (i < iter->table->length) { + entry = table->entries[i]; + if (entry != NULL) { + iter->index = i; + break; + } + i++; + } + } + + if (entry == NULL) { + /* no more keys! */ + return NULL; + } + + /* remember our current match */ + iter->entry = entry; + return entry->key; +} + diff --git a/source/hash.h b/source/hash.h new file mode 100644 index 0000000..55892d5 --- /dev/null +++ b/source/hash.h @@ -0,0 +1,61 @@ +/* hash.h +** strophe XMPP client library -- hash table interface +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * Hash table API. + */ + +#ifndef __LIBSTROPHE_HASH_H__ +#define __LIBSTROPHE_HASH_H__ + +typedef struct _hash_t hash_t; + +typedef void (*hash_free_func)(const xmpp_ctx_t * const ctx, void *p); + +/** allocate and initialize a new hash table */ +hash_t *hash_new(xmpp_ctx_t * const ctx, const int size, + hash_free_func free_func); + +/** allocate a new reference to an existing hash table */ +hash_t *hash_clone(hash_t * const table); + +/** release a hash table when no longer needed */ +void hash_release(hash_t * const table); + +/** add a key, value pair to a hash table. + * each key can appear only once; the value of any + * identical key will be replaced + */ +int hash_add(hash_t *table, const char * const key, void *data); + +/** look up a key in a hash table */ +void *hash_get(hash_t *table, const char *key); + +/** delete a key from a hash table */ +int hash_drop(hash_t *table, const char *key); + +/** return the number of keys in a hash */ +int hash_num_keys(hash_t *table); + +/** hash key iterator functions */ +typedef struct _hash_iterator_t hash_iterator_t; + +/** allocate and initialize a new iterator */ +hash_iterator_t *hash_iter_new(hash_t *table); + +/** release an iterator that is no longer needed */ +void hash_iter_release(hash_iterator_t *iter); + +/** return the next hash table key from the iterator. + the returned key should not be freed */ +const char * hash_iter_next(hash_iterator_t *iter); + +#endif /* __LIBXMPPP_HASH_H__ */ diff --git a/source/jid.c b/source/jid.c new file mode 100644 index 0000000..a28daab --- /dev/null +++ b/source/jid.c @@ -0,0 +1,174 @@ +/* jid.c +** strophe XMPP client library -- helper functions for parsing JIDs +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * JID creation and parsing. + */ + +#include + +#include "strophe.h" +#include "common.h" + +/** Create a JID string from component parts node, domain, and resource. + * + * @param ctx the Strophe context object + * @param node a string representing the node + * @param domain a string representing the domain. Required. + * @param resource a string representing the resource + * + * @return an allocated string with the full JID or NULL if no domain + * is specified + */ +char *xmpp_jid_new(xmpp_ctx_t *ctx, const char *node, + const char *domain, + const char *resource) +{ + char *result; + int len,nlen,dlen,rlen; + + /* jid must at least have a domain */ + if (domain == NULL) return NULL; + + /* accumulate lengths */ + dlen = strlen(domain); + nlen = (node) ? strlen(node) + 1 : 0; + rlen = (resource) ? strlen(resource) + 1 : 0; + len = nlen + dlen + rlen; + + /* concat components */ + result = xmpp_alloc(ctx, len + 1); + if (result != NULL) { + if (node != NULL) { + memcpy(result, node, nlen - 1); + result[nlen-1] = '@'; + } + memcpy(result + nlen, domain, dlen); + if (resource != NULL) { + result[nlen+dlen] = '/'; + memcpy(result+nlen+dlen+1, resource, rlen - 1); + } + result[len] = '\0'; + } + + return result; +} + +/** Create a bare JID from a JID. + * + * @param ctx the Strophe context object + * @param jid the JID + * + * @return an allocated string with the bare JID or NULL on an error + */ +char *xmpp_jid_bare(xmpp_ctx_t *ctx, const char *jid) +{ + char *result; + const char *c; + + c = strchr(jid, '/'); + if (c == NULL) return xmpp_strdup(ctx, jid); + + result = xmpp_alloc(ctx, c-jid+1); + if (result != NULL) { + memcpy(result, jid, c-jid); + result[c-jid] = '\0'; + } + + return result; +} + +/** Create a node string from a JID. + * + * @param ctx a Strophe context object + * @param jid the JID + * + * @return an allocated string with the node or NULL if no node is found + * or an error occurs + */ +char *xmpp_jid_node(xmpp_ctx_t *ctx, const char *jid) +{ + char *result = NULL; + const char *c; + + c = strchr(jid, '@'); + if (c != NULL) { + result = xmpp_alloc(ctx, (c-jid) + 1); + if (result != NULL) { + memcpy(result, jid, (c-jid)); + result[c-jid] = '\0'; + } + } + + return result; +} + +/** Create a domain string from a JID. + * + * @param ctx the Strophe context object + * @param jid the JID + * + * @return an allocated string with the domain or NULL on an error + */ +char *xmpp_jid_domain(xmpp_ctx_t *ctx, const char *jid) +{ + char *result = NULL; + const char *c,*s; + + c = strchr(jid, '@'); + if (c == NULL) { + /* no node, assume domain */ + c = jid; + } else { + /* advance past the separator */ + c++; + } + s = strchr(c, '/'); + if (s == NULL) { + /* no resource */ + s = c + strlen(c); + } + result = xmpp_alloc(ctx, (s-c) + 1); + if (result != NULL) { + memcpy(result, c, (s-c)); + result[s-c] = '\0'; + } + + return result; +} + +/** Create a resource string from a JID. + * + * @param ctx a Strophe context object + * @param jid the JID + * + * @return an allocated string with the resource or NULL if no resource + * is found or an error occurs + */ +char *xmpp_jid_resource(xmpp_ctx_t *ctx, const char *jid) +{ + char *result = NULL; + const char *c; + int len; + + c = strchr(jid, '/'); + if (c != NULL) { + c++; + len = strlen(c); + result = xmpp_alloc(ctx, len + 1); + if (result != NULL) { + memcpy(result, c, len); + result[len] = '\0'; + } + } + + return result; +} diff --git a/source/md5.c b/source/md5.c new file mode 100644 index 0000000..4e890be --- /dev/null +++ b/source/md5.c @@ -0,0 +1,270 @@ +/* md5.c +** MD5 hash function implemention, adapted for local use +** +** This code is in the Public Domain +*/ + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +/** @file + * MD5 hash. + */ + +#include /* memcpy(), memset() */ +#include "md5.h" + +/* little-endian word access macros */ +#define GET_32BIT_LSB_FIRST(cp) \ + (((uint32_t)(unsigned char)(cp)[0]) | \ + ((uint32_t)(unsigned char)(cp)[1] << 8 ) | \ + ((uint32_t)(unsigned char)(cp)[2] << 16) | \ + ((uint32_t)(unsigned char)(cp)[3] << 24)) + +#define PUT_32BIT_LSB_FIRST(cp, value) \ + do { \ + (cp)[0] = (value) & 0xFF; \ + (cp)[1] = ((value) >> 8) & 0xFF; \ + (cp)[2] = ((value) >> 16) & 0xFF; \ + (cp)[3] = ((value) >> 24) & 0xFF; \ + } while(0) + +static void MD5Transform(uint32_t buf[4], const unsigned char inext[64], + struct MD5Context *ctx); + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; + + memset(ctx->in, 0, 64); +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void MD5Update(struct MD5Context *ctx, unsigned char const *buf, uint32_t len) +{ + uint32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = (t + ((uint32_t)len << 3)) & 0xffffffff) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + MD5Transform(ctx->buf, ctx->in, ctx); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + MD5Transform(ctx->buf, ctx->in, ctx); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void MD5Final(unsigned char digest[16], struct MD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + MD5Transform(ctx->buf, ctx->in, ctx); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + + /* Append length in bits and transform */ + PUT_32BIT_LSB_FIRST(ctx->in + 56, ctx->bits[0]); + PUT_32BIT_LSB_FIRST(ctx->in + 60, ctx->bits[1]); + + MD5Transform(ctx->buf, ctx->in, ctx); + PUT_32BIT_LSB_FIRST(digest, ctx->buf[0]); + PUT_32BIT_LSB_FIRST(digest + 4, ctx->buf[1]); + PUT_32BIT_LSB_FIRST(digest + 8, ctx->buf[2]); + PUT_32BIT_LSB_FIRST(digest + 12, ctx->buf[3]); + memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ +} + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +/* debugging version: */ +/* +#define MD5STEP(f, w, x, y, z, data, s) \ + printf("MD5STEP: w: %x x: %x y: %x z: %x data: %x s: %x\n", \ + w, x, y, z, data, s); \ + printf("f(x,y,z) = %x\n", f(x,y,z)+data); \ + ( w += f(x, y, z) + data, printf(" - w: %x ", w), \ + w = w<>(32-s), printf(" - w: %x\n", w), w += x ) +*/ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void MD5Transform(uint32_t buf[4], const unsigned char inext[64], + struct MD5Context *ctx) +{ + register uint32_t a, b, c, d, i; + uint32_t in[16]; + + for (i = 0; i < 16; i++) + in[i] = GET_32BIT_LSB_FIRST(inext + 4 * i); + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} diff --git a/source/md5.h b/source/md5.h new file mode 100644 index 0000000..235d4fa --- /dev/null +++ b/source/md5.h @@ -0,0 +1,28 @@ +/* md5.h +** interface to MD5 hash function +** +** This code is in the Public Domain. +*/ + +/** @file + * MD5 hash API. + */ + +#ifndef MD5_H +#define MD5_H + +/* make sure the stdint.h types are available */ +#include "ostypes.h" + +struct MD5Context { + uint32_t buf[4]; + uint32_t bits[2]; + unsigned char in[64]; +}; + +void MD5Init(struct MD5Context *context); +void MD5Update(struct MD5Context *context, unsigned char const *buf, + uint32_t len); +void MD5Final(unsigned char digest[16], struct MD5Context *context); + +#endif /* !MD5_H */ diff --git a/source/oocontext.cpp b/source/oocontext.cpp new file mode 100644 index 0000000..9274ad6 --- /dev/null +++ b/source/oocontext.cpp @@ -0,0 +1,85 @@ +/* oocontext.cpp +** strophe XMPP client library -- C++ context implementation +** +** Copyright (C) 2005-2009 Collecta, Inc. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This program is dual licensed under the MIT and GPLv3 licenses. +*/ + +#include + +#include "strophe.h" +#include "strophepp.h" + +XMPP::Context::Context() +{ + m_mem.alloc = callAlloc; + m_mem.realloc = callRealloc; + m_mem.free = callFree; + m_mem.userdata = (void *)this; + + m_log.handler = callLog; + m_log.userdata = (void *)this; + + m_ctx = ::xmpp_ctx_new(&m_mem, &m_log); +} + +XMPP::Context::~Context() +{ + ::xmpp_ctx_free(m_ctx); +} + +void *XMPP::Context::alloc(const size_t size) +{ + return ::malloc(size); +} + +void *XMPP::Context::realloc(void *p, const size_t size) +{ + return ::realloc(p, size); +} + +void XMPP::Context::free(void *p) +{ + ::free(p); +} + +void XMPP::Context::log(const xmpp_log_level_t level, + const char * const area, + const char * const msg) +{ + /* do nothing by default */ +} + +xmpp_ctx_t *XMPP::Context::getContext() +{ + return m_ctx; +} + +void *XMPP::Context::callAlloc(const size_t size, void * const userdata) +{ + return reinterpret_cast(userdata)->alloc(size); +} + +void *XMPP::Context::callRealloc(void *p, const size_t size, + void * const userdata) +{ + return reinterpret_cast(userdata)->realloc(p, size); +} + +void XMPP::Context::callFree(void *p, void * const userdata) +{ + reinterpret_cast(userdata)->free(p); +} + +void XMPP::Context::callLog(void * const userdata, + const xmpp_log_level_t level, + const char * const area, + const char * const msg) +{ + reinterpret_cast(userdata)->log(level, area, msg); +} + diff --git a/source/oostanza.cpp b/source/oostanza.cpp new file mode 100644 index 0000000..eea2adf --- /dev/null +++ b/source/oostanza.cpp @@ -0,0 +1,79 @@ +/* oostanza.cpp +** strophe XMPP client library -- C++ context implementation +** +** Copyright (C) 2005-2009 Collecta, Inc. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This program is dual licensed under the MIT and GPLv3 licenses. +*/ + +#include "strophe.h" +#include "strophepp.h" + +using namespace XMPP; + +void *Stanza::operator new(size_t size, Context *ctx) +{ + void *p; + + /* we must allocate extra room for the Context object so that the + destructor can access it to free the object. C++ does not allow + us to access normal members in the destructor, so we have to hide + it. This must be prepended as well, since C++ will add stuff to + the end in subclasses. */ + + p = ctx->alloc(size + sizeof(Context *)); + if (!p) return p; + + *reinterpret_cast(p) = ctx; + p = reinterpret_cast(reinterpret_cast(p) + + sizeof(Context *)); + + return p; +} + +void Stanza::operator delete(void *p) +{ + Context *ctx; + + ctx = *reinterpret_cast(reinterpret_cast(p) - 4); + ctx->free(reinterpret_cast(p) - 4); +} + +Stanza::Stanza(Context *ctx) +{ + m_ctx = ctx; + m_stanza = ::xmpp_stanza_new(ctx->getContext()); + // TODO: check for errors +} + +Stanza::~Stanza() +{ +} + +Stanza *Stanza::create(Context *ctx) +{ + return new (ctx) Stanza(ctx); +} + +void Stanza::release() +{ + if (::xmpp_stanza_release(m_stanza)) + delete this; +} + +Stanza *Stanza::clone() +{ + ::xmpp_stanza_clone(m_stanza); + return this; +} + +Stanza *Stanza::copy() +{ + // TODO + return NULL; +} + + diff --git a/source/ostypes.h b/source/ostypes.h new file mode 100644 index 0000000..c3c8f47 --- /dev/null +++ b/source/ostypes.h @@ -0,0 +1,44 @@ +/* ostypes.h +** strophe XMPP client library -- type definitions for platforms +** without stdint.h +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * Type definitions for platforms without stdint.h. + */ + +#ifndef __LIBSTROPHE_OSTYPES_H__ +#define __LIBSTROPHE_OSTYPES_H__ + +#include /* size_t */ + +#if defined (_MSC_VER) && _MSC_VER < 1600 +typedef signed char int8_t; +typedef short int int16_t; +typedef int int32_t; +typedef __int64 int64_t; + +typedef unsigned char uint8_t; +typedef unsigned short int uint16_t; +typedef unsigned int uint32_t; +typedef unsigned __int64 uint64_t; + +#ifndef UINT32_MAX +#define UINT32_MAX 0xffffffff +#endif /* UINT32_MAX */ +#ifndef SIZE_MAX +#define SIZE_MAX UINT32_MAX +#endif /* SIZE_MAX */ + +#else +#include +#endif + +#endif /* __LIBSTROPHE_OSTYPES_H__ */ diff --git a/source/parser.h b/source/parser.h new file mode 100644 index 0000000..063763c --- /dev/null +++ b/source/parser.h @@ -0,0 +1,41 @@ +/* parser.h +** strophe XMPP client library -- parser structures and functions +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * Internally used functions and structures. + */ + +#ifndef __LIBSTROPHE_PARSER_H__ +#define __LIBSTROPHE_PARSER_H__ + +#include "strophe.h" + +typedef struct _parser_t parser_t; + +typedef void (*parser_start_callback)(char *name, + char **attrs, + void * const userdata); +typedef void (*parser_end_callback)(char *name, void * const userdata); +typedef void (*parser_stanza_callback)(xmpp_stanza_t *stanza, + void * const userdata); + + +parser_t *parser_new(xmpp_ctx_t *ctx, + parser_start_callback startcb, + parser_end_callback endcb, + parser_stanza_callback stanzacb, + void *userdata); +void parser_free(parser_t * const parser); +char* parser_attr_name(xmpp_ctx_t *ctx, char *nsname); +int parser_reset(parser_t *parser); +int parser_feed(parser_t *parser, char *chunk, int len); + +#endif /* __LIBSTROPHE_PARSER_H__ */ diff --git a/source/parser_expat.c b/source/parser_expat.c new file mode 100644 index 0000000..3e1f963 --- /dev/null +++ b/source/parser_expat.c @@ -0,0 +1,248 @@ +/* parser.c +** strophe XMPP client library -- xml parser handlers and utility functions +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * XML parser handlers. + */ + +#include +#include +#include + +#include + +#include +#include "common.h" +#include "parser.h" + +/* Use the Unit Separator to delimit namespace and name in our XML*/ +#define NAMESPACE_SEP ('\x1F') + +struct _parser_t { + xmpp_ctx_t *ctx; + XML_Parser expat; + parser_start_callback startcb; + parser_end_callback endcb; + parser_stanza_callback stanzacb; + void *userdata; + int depth; + xmpp_stanza_t *stanza; +}; + +/* return allocated string with the name from a delimited + * namespace/name string */ +static char *_xml_name(xmpp_ctx_t *ctx, const char *nsname) +{ + char *result = NULL; + const char *c; + int len; + + c = strchr(nsname, NAMESPACE_SEP); + if (c == NULL) return xmpp_strdup(ctx, nsname); + + c++; + len = strlen(c); + result = xmpp_alloc(ctx, len + 1); + if (result != NULL) { + memcpy(result, c, len); + result[len] = '\0'; + } + + return result; +} + +/* return allocated string with the namespace from a delimited string */ +static char *_xml_namespace(xmpp_ctx_t *ctx, const char *nsname) +{ + char *result = NULL; + const char *c; + + c = strchr(nsname, NAMESPACE_SEP); + if (c != NULL) { + result = xmpp_alloc(ctx, (c-nsname) + 1); + if (result != NULL) { + memcpy(result, nsname, (c-nsname)); + result[c-nsname] = '\0'; + } + } + + return result; +} + +static void _set_attributes(xmpp_stanza_t *stanza, const XML_Char **attrs) +{ + char *attr; + int i; + + if (!attrs) return; + + for (i = 0; attrs[i]; i += 2) { + /* namespaced attributes aren't used in xmpp, discard namespace */ + attr = _xml_name(stanza->ctx, attrs[i]); + xmpp_stanza_set_attribute(stanza, attr, attrs[i+1]); + xmpp_free(stanza->ctx, attr); + } +} + +static void _start_element(void *userdata, + const XML_Char *nsname, + const XML_Char **attrs) +{ + parser_t *parser = (parser_t *)userdata; + xmpp_stanza_t *child; + char *ns, *name; + + ns = _xml_namespace(parser->ctx, nsname); + name = _xml_name(parser->ctx, nsname); + + if (parser->depth == 0) { + /* notify the owner */ + if (parser->startcb) + parser->startcb((char *)name, (char **)attrs, + parser->userdata); + } else { + /* build stanzas at depth 1 */ + if (!parser->stanza && parser->depth != 1) { + /* something terrible happened */ + /* FIXME: shutdown disconnect */ + xmpp_error(parser->ctx, "parser", "oops, where did our stanza go?"); + } else { + child = xmpp_stanza_new(parser->ctx); + if (!child) { + /* FIXME: can't allocate, disconnect */ + } + xmpp_stanza_set_name(child, name); + _set_attributes(child, attrs); + if (ns) + xmpp_stanza_set_ns(child, ns); + + if (parser->stanza != NULL) { + xmpp_stanza_add_child(parser->stanza, child); + xmpp_stanza_release(child); + } + parser->stanza = child; + } + } + + if (ns) xmpp_free(parser->ctx, ns); + if (name) xmpp_free(parser->ctx, name); + + parser->depth++; +} + +static void _end_element(void *userdata, const XML_Char *name) +{ + parser_t *parser = (parser_t *)userdata; + + parser->depth--; + + if (parser->depth == 0) { + /* notify the owner */ + if (parser->endcb) + parser->endcb((char *)name, parser->userdata); + } else { + if (parser->stanza->parent) { + /* we're finishing a child stanza, so set current to the parent */ + parser->stanza = parser->stanza->parent; + } else { + if (parser->stanzacb) + parser->stanzacb(parser->stanza, + parser->userdata); + xmpp_stanza_release(parser->stanza); + parser->stanza = NULL; + } + } +} + +static void _characters(void *userdata, const XML_Char *s, int len) +{ + parser_t *parser = (parser_t *)userdata; + xmpp_stanza_t *stanza; + + if (parser->depth < 2) return; + + /* create and populate stanza */ + stanza = xmpp_stanza_new(parser->ctx); + if (!stanza) { + /* FIXME: allocation error, disconnect */ + return; + } + xmpp_stanza_set_text_with_size(stanza, s, len); + + xmpp_stanza_add_child(parser->stanza, stanza); + xmpp_stanza_release(stanza); +} + +parser_t *parser_new(xmpp_ctx_t *ctx, + parser_start_callback startcb, + parser_end_callback endcb, + parser_stanza_callback stanzacb, + void *userdata) +{ + parser_t *parser; + + parser = xmpp_alloc(ctx, sizeof(parser_t)); + if (parser != NULL) { + parser->ctx = ctx; + parser->expat = NULL; + parser->startcb = startcb; + parser->endcb = endcb; + parser->stanzacb = stanzacb; + parser->userdata = userdata; + parser->depth = 0; + parser->stanza = NULL; + + parser_reset(parser); + } + + return parser; +} + +char* parser_attr_name(xmpp_ctx_t *ctx, char *nsname) +{ + return _xml_name(ctx, nsname); +} + +/* free a parser */ +void parser_free(parser_t *parser) +{ + if (parser->expat) + XML_ParserFree(parser->expat); + + xmpp_free(parser->ctx, parser); +} + +/* shuts down and restarts XML parser. true on success */ +int parser_reset(parser_t *parser) +{ + if (parser->expat) + XML_ParserFree(parser->expat); + + if (parser->stanza) + xmpp_stanza_release(parser->stanza); + + parser->expat = XML_ParserCreateNS(NULL, NAMESPACE_SEP); + if (!parser->expat) return 0; + + parser->depth = 0; + parser->stanza = NULL; + + XML_SetUserData(parser->expat, parser); + XML_SetElementHandler(parser->expat, _start_element, _end_element); + XML_SetCharacterDataHandler(parser->expat, _characters); + + return 1; +} + +int parser_feed(parser_t *parser, char *chunk, int len) +{ + return XML_Parse(parser->expat, chunk, len, 0); +} diff --git a/source/parser_libxml2.c b/source/parser_libxml2.c new file mode 100644 index 0000000..15656f9 --- /dev/null +++ b/source/parser_libxml2.c @@ -0,0 +1,283 @@ +/* parser.c +** strophe XMPP client library -- xml parser handlers and utility functions +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * XML parser handlers. + */ + +#include +#include +#include + +#include +#include + +#include +#include "common.h" +#include "parser.h" + +struct _parser_t { + xmpp_ctx_t *ctx; + xmlParserCtxtPtr xmlctx; + xmlSAXHandler handlers; + parser_start_callback startcb; + parser_end_callback endcb; + parser_stanza_callback stanzacb; + void *userdata; + int depth; + xmpp_stanza_t *stanza; +}; + +static void _set_attributes(xmpp_stanza_t *stanza, int nattrs, + const xmlChar **attrs) +{ + int i, len; + char *value; + + if (!attrs) return; + + /* SAX2 uses array of localname/prefix/uri/value_begin/value_end */ + for (i = 0; i < nattrs*5; i += 5) { + len = attrs[i+4] - attrs[i+3]; + value = xmpp_alloc(stanza->ctx, len + 1); + if (value) { + memcpy(value, attrs[i+3], len); + value[len] = '\0'; + xmpp_stanza_set_attribute(stanza, (const char *)attrs[i], value); + xmpp_free(stanza->ctx, value); + } + } +} + +/* SAX2 gives us the attrs in an incredibly inconvenient array, + * convert it to what the start callback is expecting */ +static char **_convert_attrs(parser_t *parser, int nattrs, + const xmlChar **attrs) +{ + int c, i, o, len; + char *value; + char **ret; + + if (!attrs) return NULL; + + ret = xmpp_alloc(parser->ctx, (nattrs+1)*2*sizeof(char*)); + if (!ret) return NULL; + memset(ret, 0, (nattrs+1)*2*sizeof(char*)); + + for (c = 0; c < nattrs; c++) { + i = c * 5; + o = c * 2; + + len = attrs[i+4] - attrs[i+3]; + value = xmpp_alloc(parser->ctx, len + 1); + if (value) { + memcpy(value, attrs[i+3], len); + value[len] = '\0'; + ret[o] = xmpp_strdup(parser->ctx, (char*)attrs[i]); + ret[o+1] = value; + } + } + + return ret; +} + +static void _free_cbattrs(parser_t *parser, char **attrs) +{ + int i; + + if (!attrs) + return; + + for (i = 0; attrs[i]; i += 2) { + if (attrs[i]) xmpp_free(parser->ctx, attrs[i]); + if (attrs[i+1]) xmpp_free(parser->ctx, attrs[i+1]); + } + + xmpp_free(parser->ctx, attrs); +} + +static void _start_element(void *userdata, + const xmlChar *name, const xmlChar *prefix, + const xmlChar *uri, int nnamespaces, + const xmlChar **namespaces, int nattrs, + int ndefaulted, const xmlChar **attrs) +{ + parser_t *parser = (parser_t *)userdata; + xmpp_stanza_t *child; + char **cbattrs; + + if (parser->depth == 0) { + /* notify the owner */ + if (parser->startcb) + cbattrs = _convert_attrs(parser, nattrs, attrs); + parser->startcb((char *)name, cbattrs, + parser->userdata); + _free_cbattrs(parser, cbattrs); + } else { + /* build stanzas at depth 1 */ + if (!parser->stanza && parser->depth != 1) { + /* something terrible happened */ + /* FIXME: we should probably trigger a disconnect */ + xmpp_error(parser->ctx, "parser", "oops, where did our stanza go?"); + } else if (!parser->stanza) { + /* starting a new toplevel stanza */ + parser->stanza = xmpp_stanza_new(parser->ctx); + if (!parser->stanza) { + /* FIXME: can't allocate, disconnect */ + } + xmpp_stanza_set_name(parser->stanza, (char *)name); + _set_attributes(parser->stanza, nattrs, attrs); + if (uri) + xmpp_stanza_set_ns(parser->stanza, (char *)uri); + } else { + /* starting a child of conn->stanza */ + child = xmpp_stanza_new(parser->ctx); + if (!child) { + /* FIXME: can't allocate, disconnect */ + } + xmpp_stanza_set_name(child, (char *)name); + _set_attributes(child, nattrs, attrs); + if (uri) + xmpp_stanza_set_ns(child, (char *)uri); + + /* add child to parent */ + xmpp_stanza_add_child(parser->stanza, child); + + /* the child is owned by the toplevel stanza now */ + xmpp_stanza_release(child); + + /* make child the current stanza */ + parser->stanza = child; + } + } + + parser->depth++; +} + +static void _end_element(void *userdata, const xmlChar *name, + const xmlChar *prefix, const xmlChar *uri) +{ + parser_t *parser = (parser_t *)userdata; + + parser->depth--; + + if (parser->depth == 0) { + /* notify owner */ + if (parser->endcb) + parser->endcb((char *)name, parser->userdata); + } else { + if (parser->stanza->parent) { + /* we're finishing a child stanza, so set current to the parent */ + parser->stanza = parser->stanza->parent; + } else { + if (parser->stanzacb) + parser->stanzacb(parser->stanza, + parser->userdata); + xmpp_stanza_release(parser->stanza); + parser->stanza = NULL; + } + } +} + +static void _characters(void *userdata, const xmlChar *chr, int len) +{ + parser_t *parser = (parser_t *)userdata; + xmpp_stanza_t *stanza; + + /* skip unimportant whitespace, etc */ + if (parser->depth < 2) return; + + /* create and populate stanza */ + stanza = xmpp_stanza_new(parser->ctx); + if (!stanza) { + /* FIXME: allocation error, disconnect */ + return; + } + xmpp_stanza_set_text_with_size(stanza, (char *)chr, len); + + xmpp_stanza_add_child(parser->stanza, stanza); + xmpp_stanza_release(stanza); +} + +/* create a new parser */ +parser_t *parser_new(xmpp_ctx_t *ctx, + parser_start_callback startcb, + parser_end_callback endcb, + parser_stanza_callback stanzacb, + void *userdata) +{ + parser_t *parser; + + parser = xmpp_alloc(ctx, sizeof(parser_t)); + if (parser != NULL) { + parser->ctx = ctx; + parser->xmlctx = NULL; + memset(&parser->handlers, 0, sizeof(xmlSAXHandler)); + parser->handlers.initialized = XML_SAX2_MAGIC; + parser->handlers.startElementNs = _start_element; + parser->handlers.endElementNs = _end_element; + parser->handlers.characters = _characters; + parser->startcb = startcb; + parser->endcb = endcb; + parser->stanzacb = stanzacb; + parser->userdata = userdata; + parser->depth = 0; + parser->stanza = NULL; + + parser_reset(parser); + } + + return parser; +} + +char* parser_attr_name(xmpp_ctx_t *ctx, char *nsname) +{ + return xmpp_strdup(ctx, nsname); +} + +/* free a parser */ +void parser_free(parser_t *parser) +{ + if (parser->xmlctx) + xmlFreeParserCtxt(parser->xmlctx); + xmpp_free(parser->ctx, parser); +} + +/* shuts down and restarts XML parser. true on success */ +int parser_reset(parser_t *parser) +{ + if (parser->xmlctx) + xmlFreeParserCtxt(parser->xmlctx); + + if (parser->stanza) + xmpp_stanza_release(parser->stanza); + + parser->xmlctx = xmlCreatePushParserCtxt(&parser->handlers, + parser, NULL, 0, NULL); + if (!parser->xmlctx) return 0; + + parser->depth = 0; + parser->stanza = NULL; + + return 1; +} + +/* feed a chunk of data to the parser */ +int parser_feed(parser_t *parser, char *chunk, int len) +{ + /* xmlParseChunk API returns 0 on success which is opposite logic to + the status returned by parser_feed */ + if(!xmlParseChunk(parser->xmlctx, chunk, len, 0)) { + return 1; + } else { + return 0; + } +} diff --git a/source/rand.c b/source/rand.c new file mode 100644 index 0000000..473ac7a --- /dev/null +++ b/source/rand.c @@ -0,0 +1,307 @@ +/* rand.c + * strophe XMPP client library -- pseudo-random number generator + * + * Copyright (C) 2014 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. + */ + +/** @file + * Pseudo-random number generator. + * + * Implemented Hash_DRBG mechanism according to NIST SP 800-90A. + * Hash function is SHA1. + */ + +#include +#include +#include + +#include "common.h" +#include "ostypes.h" +#include "sha1.h" + +#define outlen SHA1_DIGEST_SIZE +#define seedlen (440 / 8) +#define reseed_interval 0x7fffffff + +/* maximum number of bytes that can be generated per call */ +#define GENERATE_MAX (outlen * 10) +#define ENTROPY_MAX 128 +#define NONCE_MAX 8 + +#define RESEED_NEEDED (-1) + +struct Hash_DRBG_CTX_struc { + uint8_t V[seedlen]; + uint8_t C[seedlen]; + uint32_t reseed_counter; +}; +typedef struct Hash_DRBG_CTX_struc Hash_DRBG_CTX; + +struct _xmpp_rand_t { + int inited; + unsigned reseed_count; + Hash_DRBG_CTX ctx; +}; + +/* returns smallest number mupliple of y that not less than x */ +#define round_up(x, y) (((x) + (y) - 1) / (y) * (y)) +/* returns smallest integer number that not less than x/y */ +#define div_round_up(x, y) (((x) + (y) - 1) / (y)) + +/* adds two arrays as numbers in big-endian representation and stores + * result in the first one. + */ +static void arr_add(uint8_t *arr1, size_t arr1_len, + uint8_t *arr2, size_t arr2_len) +{ + size_t i; + uint32_t acc; + uint32_t carry = 0; + + assert(arr1_len >= arr2_len); + + for (i = 1; (i <= arr2_len) || (carry != 0 && i <= arr1_len); ++i) { + acc = (uint32_t)arr1[arr1_len - i] + carry; + if (i <= arr2_len) + acc += (uint32_t)arr2[arr2_len - i]; + carry = acc >> 8; + arr1[arr1_len - i] = (uint8_t)(acc & 0xff); + } +} + +/* stores 32-bit number in big-endian representation */ +static void store_be32(uint32_t val, uint8_t be[4]) +{ + be[0] = (uint8_t)((val >> 24) & 0xff); + be[1] = (uint8_t)((val >> 16) & 0xff); + be[2] = (uint8_t)((val >> 8) & 0xff); + be[3] = (uint8_t)(val & 0xff); +} + +static void Hash_df(uint8_t *input_string, size_t input_string_len, + uint8_t *output_string, size_t no_of_bytes_to_return) +{ + uint8_t counter; + uint8_t temp[round_up(seedlen, outlen)]; + uint8_t conj[ENTROPY_MAX + NONCE_MAX + seedlen + 6]; + size_t len; + size_t i; + size_t offset; + + assert(no_of_bytes_to_return <= sizeof(temp)); + assert(input_string_len + 5 <= sizeof(conj)); + + len = div_round_up(no_of_bytes_to_return, outlen); + for (i = 1; i <= len; ++i) { + offset = (i - 1) * outlen; + counter = (uint8_t)i; + conj[0] = counter; + store_be32((uint32_t)no_of_bytes_to_return * 8, conj + 1); + memcpy(conj + 5, input_string, input_string_len); + crypto_SHA1(conj, input_string_len + 5, temp + offset); + } + + memcpy(output_string, temp, no_of_bytes_to_return); +} + +/* assume personalization_string is zero length string */ +static void Hash_DRBG_Instantiate(Hash_DRBG_CTX *ctx, + uint8_t *entropy_input, + size_t entropy_input_len, + uint8_t *nonce, size_t nonce_len) +{ + uint8_t seed_material[ENTROPY_MAX + NONCE_MAX]; + uint8_t seed0[seedlen + 1]; + uint8_t *seed = seed0 + 1; + + assert(entropy_input_len <= ENTROPY_MAX); + assert(nonce_len <= NONCE_MAX); + + memcpy(seed_material, entropy_input, entropy_input_len); + memcpy(seed_material + entropy_input_len, nonce, nonce_len); + Hash_df(seed_material, entropy_input_len + nonce_len, seed, seedlen); + seed0[0] = 0; + + memcpy(ctx->V, seed, seedlen); + Hash_df(seed0, sizeof(seed0), ctx->C, seedlen); + ctx->reseed_counter = 1; +} + +/* assume additional_input is zero length string */ +static void Hash_DRBG_Reseed(Hash_DRBG_CTX *ctx, + uint8_t *entropy_input, + size_t entropy_input_len) +{ + uint8_t seed_material[1 + seedlen + ENTROPY_MAX]; + uint8_t seed0[seedlen + 1]; + uint8_t *seed = seed0 + 1; + + assert(entropy_input_len <= ENTROPY_MAX); + + seed_material[0] = 1; + memcpy(seed_material + 1, ctx->V, seedlen); + memcpy(seed_material + 1 + seedlen, entropy_input, entropy_input_len); + Hash_df(seed_material, entropy_input_len + seedlen + 1, seed, seedlen); + seed0[0] = 0; + + memcpy(ctx->V, seed, seedlen); + Hash_df(seed0, sizeof(seed0), ctx->C, seedlen); + ctx->reseed_counter = 1; +} + +static void Hashgen(uint8_t *V, uint8_t *output, + size_t requested_number_of_bytes) +{ + uint8_t data[seedlen]; + uint8_t W[GENERATE_MAX]; + uint8_t i1 = 1; + size_t m; + size_t i; + size_t offset; + + assert(requested_number_of_bytes <= sizeof(W)); + + m = div_round_up(requested_number_of_bytes, outlen); + memcpy(data, V, seedlen); + for (i = 1; i <= m; ++i) { + offset = (i - 1) * outlen; + crypto_SHA1(data, seedlen, W + offset); + /* increase data by 1 */ + arr_add(data, sizeof(data), &i1, 1); + } + + memcpy(output, W, requested_number_of_bytes); +} + +/* assume additional_input is zero length string */ +static int Hash_DRBG_Generate(Hash_DRBG_CTX *ctx, uint8_t *output, + size_t requested_number_of_bytes) +{ + uint8_t H[outlen]; + uint8_t V3[seedlen + 1]; + uint8_t reseed_counter[4]; + + if (ctx->reseed_counter > reseed_interval || ctx->reseed_counter == 0) + return RESEED_NEEDED; + + Hashgen(ctx->V, output, requested_number_of_bytes); + + V3[0] = 3; + memcpy(V3 + 1, ctx->V, seedlen); + crypto_SHA1(V3, sizeof(V3), H); + arr_add(ctx->V, sizeof(ctx->V), ctx->C, sizeof(ctx->C)); + arr_add(ctx->V, sizeof(ctx->V), H, sizeof(H)); + store_be32(ctx->reseed_counter, reseed_counter); + arr_add(ctx->V, sizeof(ctx->V), reseed_counter, sizeof(reseed_counter)); + + ++ctx->reseed_counter; + return 0; +} + +#define ENTROPY_ACCUMULATE(ptr, last, type, arg) \ +do { \ + type __arg = (type)(arg); \ + if ((char*)ptr + sizeof(__arg) < (char*)last && \ + __arg != (type)-1) \ + { \ + *(type*)ptr = __arg; \ + ptr = (void*)((char*)ptr + sizeof(__arg)); \ + } \ +} while (0) + +static void xmpp_rand_reseed(xmpp_ctx_t *ctx) +{ + uint8_t entropy[ENTROPY_MAX]; + uint8_t *ptr = entropy; + const uint8_t *last = entropy + sizeof(entropy); + size_t len; + xmpp_rand_t *rand = ctx->rand; + + /* entropy: + * 1. time(2) + * 2. clock(3) if != -1 + * 3. xmpp_ctx_t address to make unique seed within one process + * 4. counter to make unique seed within one context + * 5. local ports of every connection in list (getsockname) + * 6. other non-constant info that can be retieved from socket + * + * rand(3) can't be used as it isn't thread-safe. + * XXX 5 and 6 not implemented yet. + */ + + ENTROPY_ACCUMULATE(ptr, last, time_t, time(NULL)); + ENTROPY_ACCUMULATE(ptr, last, clock_t, clock()); + ENTROPY_ACCUMULATE(ptr, last, void *, ctx); + ENTROPY_ACCUMULATE(ptr, last, unsigned, ++rand->reseed_count); + len = ptr - entropy; + + if (rand->inited) { + Hash_DRBG_Reseed(&rand->ctx, entropy, len); + } else { + Hash_DRBG_Instantiate(&rand->ctx, entropy, len, NULL, 0); + rand->inited = 1; + } +} + +xmpp_rand_t *xmpp_rand_new(xmpp_ctx_t *ctx) +{ + xmpp_rand_t *out = xmpp_alloc(ctx, sizeof(*out)); + if (out != NULL) { + memset(out, 0, sizeof(*out)); + } + return out; +} + +void xmpp_rand_free(xmpp_ctx_t *ctx, xmpp_rand_t *rand) +{ + xmpp_free(ctx, rand); +} + +void xmpp_rand_bytes(xmpp_ctx_t *ctx, uint8_t *output, size_t len) +{ + int rc; + xmpp_rand_t *rand = ctx->rand; + + rc = Hash_DRBG_Generate(&rand->ctx, output, len); + if (rc == RESEED_NEEDED) { + xmpp_rand_reseed(ctx); + rc = Hash_DRBG_Generate(&rand->ctx, output, len); + assert(rc == 0); + } +} + +int xmpp_rand(xmpp_ctx_t *ctx) +{ + int result; + + xmpp_rand_bytes(ctx, (uint8_t *)&result, sizeof(result)); + return result; +} + +void xmpp_rand_nonce(xmpp_ctx_t *ctx, char *output, size_t len) +{ + size_t i; + size_t rand_len = len / 2; +#ifndef _MSC_VER + uint8_t rand_buf[rand_len]; +#else + uint8_t* rand_buf = (uint8_t*)_alloca(rand_len); +#endif + + /* current implementation returns printable HEX representation of + * a random buffer, however base64 encoding can be used instead; + * the only problem is that base64_encode() allocates memory and + * as result can fail. + */ + + xmpp_rand_bytes(ctx, rand_buf, rand_len); + for (i = 0; i < rand_len; ++i) { + xmpp_snprintf(output + i * 2, len, "%02x", (unsigned char)rand_buf[i]); + len -= 2; + } +} diff --git a/source/rand.h b/source/rand.h new file mode 100644 index 0000000..0879ce9 --- /dev/null +++ b/source/rand.h @@ -0,0 +1,40 @@ +/* rand.h + * strophe XMPP client library -- pseudo-random number generator + * + * Copyright (C) 2014 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. + */ + +/** @file + * Pseudo-random number generator. + */ + +#ifndef __LIBSTROPHE_RAND_H__ +#define __LIBSTROPHE_RAND_H__ + +#include "strophe.h" +#include "ostypes.h" + +typedef struct _xmpp_rand_t xmpp_rand_t; + +xmpp_rand_t *xmpp_rand_new(xmpp_ctx_t *ctx); +void xmpp_rand_free(xmpp_ctx_t *ctx, xmpp_rand_t *rand); + +/** Analogue of rand(3). */ +int xmpp_rand(xmpp_ctx_t *ctx); + +/** Generates random bytes. */ +void xmpp_rand_bytes(xmpp_ctx_t *ctx, uint8_t *output, size_t len); + +/** Generates a nonce that is printable randomized string. + * + * @param len Number of bytes reserved for the output string, including + * end of line '\0'. + */ +void xmpp_rand_nonce(xmpp_ctx_t *ctx, char *output, size_t len); + +#endif /* __LIBSTROPHE_RAND_H__ */ diff --git a/source/resolver.c b/source/resolver.c new file mode 100644 index 0000000..5faa171 --- /dev/null +++ b/source/resolver.c @@ -0,0 +1,641 @@ +/* resolver.h + * strophe XMPP client library -- DNS resolver + * + * Copyright (C) 2015 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. + */ + +/** @file + * DNS resolver. + */ + +#ifndef _WIN32 +#include +#include +#include /* res_query */ +#endif /* _WIN32 */ + +#include /* strncpy */ + +#include "common.h" +#include "resolver.h" +#include "ostypes.h" + +#define MESSAGE_HEADER_LEN 12 +#define MESSAGE_RESPONSE 1 +#define MESSAGE_T_SRV 33 +#define MESSAGE_C_IN 1 + +struct message_header { + uint16_t id; + uint8_t octet2; + uint8_t octet3; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +}; + +#ifdef _WIN32 +static int resolver_win32_srv_lookup(const char *fulldomain, + char *target, size_t target_len, + unsigned short *port); +static int resolver_win32_srv_query(const char *fulldomain, + unsigned char *buf, size_t len); +#endif /* _WIN32 */ + +/* the same as ntohs(), but receives pointer to the value */ +static uint16_t xmpp_ntohs_ptr(const void *ptr) +{ + const uint8_t *p = (const uint8_t *)ptr; + + return (uint16_t)((p[0] << 8U) + p[1]); +} + +static uint8_t message_header_qr(const struct message_header *header) +{ + return (header->octet2 >> 7) & 1; +} + +static uint8_t message_header_rcode(const struct message_header *header) +{ + return header->octet3 & 0x0f; +} + +static unsigned message_name_get(const unsigned char *buf, size_t buf_len, + unsigned buf_offset, + char *name, size_t name_max) +{ + size_t name_len = 0; + unsigned i = buf_offset; + unsigned pointer; + unsigned char label_len; + + while ((label_len = buf[i++]) != 0) { + /* label */ + if ((label_len & 0xc0) == 0) { + if (name != NULL) { + if (name_len != 0) + name[name_len++] = '.'; + strncpy(&name[name_len], (char *)&buf[i], label_len); + } + i += label_len; + name_len += label_len; + + /* pointer */ + } else if ((label_len & 0xc0) == 0xc0) { + pointer = (label_len & 0x3f) << 8 | buf[i++]; + (void)message_name_get(buf, buf_len, pointer, &name[name_len], + name_max - name_len); + /* pointer is always the last */ + break; + + /* The 10 and 01 combinations are reserved for future use. */ + } else { + return 0; + } + } + if (label_len == 0 && name != NULL) + name[name_len] = '\0'; + + return i - buf_offset; +} + +static unsigned message_name_len(const unsigned char *buf, size_t buf_len, + unsigned buf_offset) +{ + return message_name_get(buf, buf_len, buf_offset, NULL, SIZE_MAX); +} + +int resolver_srv_lookup_buf(const unsigned char *buf, size_t len, + char *target, size_t target_len, + unsigned short *port) +{ + int set = 0; + unsigned i; + unsigned j; + unsigned name_len; + unsigned rdlength; + uint16_t type; + uint16_t class; + uint16_t priority; + uint16_t priority_min; + struct message_header header; + + if (len < MESSAGE_HEADER_LEN) + return 0; + + header.id = xmpp_ntohs_ptr(&buf[0]); + header.octet2 = buf[2]; + header.octet3 = buf[3]; + header.qdcount = xmpp_ntohs_ptr(&buf[4]); + header.ancount = xmpp_ntohs_ptr(&buf[6]); + header.nscount = xmpp_ntohs_ptr(&buf[8]); + header.arcount = xmpp_ntohs_ptr(&buf[10]); + if (message_header_qr(&header) != MESSAGE_RESPONSE || + message_header_rcode(&header) != 0) + { + return 0; + } + j = MESSAGE_HEADER_LEN; + + /* skip question section */ + for (i = 0; i < header.qdcount; ++i) { + name_len = message_name_len(buf, len, j); + if (name_len == 0) { + /* error in name format */ + return 0; + } + j += name_len + 4; + } + + /* + * RFC2052: A client MUST attempt to contact the target host + * with the lowest-numbered priority it can reach. + */ + for (i = 0; i < header.ancount; ++i) { + name_len = message_name_len(buf, len, j); + j += name_len; + type = xmpp_ntohs_ptr(&buf[j]); + class = xmpp_ntohs_ptr(&buf[j + 2]); + rdlength = xmpp_ntohs_ptr(&buf[j + 8]); + j += 10; + if (type == MESSAGE_T_SRV && class == MESSAGE_C_IN) { + priority = xmpp_ntohs_ptr(&buf[j]); + if (!set || priority < priority_min) { + *port = xmpp_ntohs_ptr(&buf[j + 4]); + name_len = message_name_get(buf, len, j + 6, target, target_len); + set = name_len > 0 ? 1 : 0; + priority_min = priority; + } + } + j += rdlength; + } + + return set; +} + +int resolver_srv_lookup(const char *service, const char *proto, + const char *domain, char *target, + size_t target_len, unsigned short *port) +{ + char fulldomain[2048]; + unsigned char buf[65535]; + int len; + int set = 0; + + xmpp_snprintf(fulldomain, sizeof(fulldomain), + "_%s._%s.%s", service, proto, domain); + +#ifdef _WIN32 + set = resolver_win32_srv_lookup(fulldomain, target, target_len, port); + if (set) + return set; + len = resolver_win32_srv_query(fulldomain, buf, sizeof(buf)); +#else /* _WIN32 */ + len = res_query(fulldomain, MESSAGE_C_IN, MESSAGE_T_SRV, buf, sizeof(buf)); +#endif /* _WIN32 */ + + if (len > 0) + set = resolver_srv_lookup_buf(buf, (size_t)len, target, target_len, port); + + return set; +} + +/* FIXME: interface that returns array of results, maybe sorted by priority */ + +#ifdef _WIN32 + +/******************************************************************************* + * Next part was copied from sock.c and contains old win32 code. + * + * The idea is to get raw response from a name server and pass it to + * resolver_srv_lookup_buf(). In fact, resolver_win32_srv_query() replaces + * the call of res_query(). + * Dnsapi code is left unchanged and moved to a separated function + * resolver_srv_win32_lookup(). + * + * XXX If the code is compiled it should work like before. + ******************************************************************************/ + +#include +#include +#include +#include + +struct dnsquery_header +{ + unsigned short id; + unsigned char qr; + unsigned char opcode; + unsigned char aa; + unsigned char tc; + unsigned char rd; + unsigned char ra; + unsigned char z; + unsigned char rcode; + unsigned short qdcount; + unsigned short ancount; + unsigned short nscount; + unsigned short arcount; +}; + +struct dnsquery_question +{ + char qname[1024]; + unsigned short qtype; + unsigned short qclass; +}; + +static void netbuf_add_16bitnum(unsigned char *buf, int buflen, int *offset, unsigned short num) +{ + unsigned char *start = buf + *offset; + unsigned char *p = start; + + /* assuming big endian */ + *p++ = (num >> 8) & 0xff; + *p++ = (num) & 0xff; + + *offset += 2; +} + +static void netbuf_add_domain_name(unsigned char *buf, int buflen, int *offset, + char *name) +{ + unsigned char *start = buf + *offset; + unsigned char *p = start; + unsigned char *wordstart, *wordend; + + wordstart = (unsigned char *)name; + + while (*wordstart) + { + int len; + wordend = wordstart; + while (*wordend && *wordend != '.') + { + wordend++; + } + + len = (int)(wordend - wordstart); + + if (len > 0x3F) + { + len = 0x3F; + } + + *p++ = len; + + while (wordstart != wordend) + { + *p++ = *wordstart++; + } + + if (*wordstart == '.') + { + wordstart++; + } + } + + *p++ = '\0'; + + *offset += p - start; +} + +static void netbuf_add_dnsquery_header(unsigned char *buf, int buflen, int *offset, struct dnsquery_header *header) +{ + unsigned char *p; + + netbuf_add_16bitnum(buf, buflen, offset, header->id); + + p = buf + *offset; + *p++ = ((header->qr & 0x01) << 7) + | ((header->opcode & 0x0F) << 3) + | ((header->aa & 0x01) << 2) + | ((header->tc & 0x01) << 1) + | ((header->rd & 0x01)); + *p++ = ((header->ra & 0x01) << 7) + | ((header->z & 0x07) << 4) + | ((header->rcode & 0x0F)); + *offset += 2; + + netbuf_add_16bitnum(buf, buflen, offset, header->qdcount); + netbuf_add_16bitnum(buf, buflen, offset, header->ancount); + netbuf_add_16bitnum(buf, buflen, offset, header->nscount); + netbuf_add_16bitnum(buf, buflen, offset, header->arcount); +} + +static void netbuf_add_dnsquery_question(unsigned char *buf, int buflen, int *offset, struct dnsquery_question *question) +{ + netbuf_add_domain_name(buf, buflen, offset, question->qname); + netbuf_add_16bitnum(buf, buflen, offset, question->qtype); + netbuf_add_16bitnum(buf, buflen, offset, question->qclass); +} + +static int resolver_win32_srv_lookup(const char *fulldomain, + char *target, size_t target_len, + unsigned short *port) +{ + int set = 0; + + /* try using dnsapi first */ + if (!set) + { + HINSTANCE hdnsapi = NULL; + + DNS_STATUS (WINAPI * pDnsQuery_A)(PCSTR, WORD, DWORD, PIP4_ARRAY, PDNS_RECORD*, PVOID*); + void (WINAPI * pDnsRecordListFree)(PDNS_RECORD, DNS_FREE_TYPE); + + if (hdnsapi = LoadLibrary("dnsapi.dll")) { + + pDnsQuery_A = (void *)GetProcAddress(hdnsapi, "DnsQuery_A"); + pDnsRecordListFree = (void *)GetProcAddress(hdnsapi, "DnsRecordListFree"); + + if (pDnsQuery_A && pDnsRecordListFree) { + PDNS_RECORD dnsrecords = NULL; + DNS_STATUS error; + + error = pDnsQuery_A(fulldomain, DNS_TYPE_SRV, DNS_QUERY_STANDARD, NULL, &dnsrecords, NULL); + + if (error == 0) { + PDNS_RECORD current = dnsrecords; + + while (current) { + if (current->wType == DNS_TYPE_SRV) { + xmpp_snprintf(target, target_len, "%s", current->Data.Srv.pNameTarget); + *port = current->Data.Srv.wPort; + set = 1; + + current = NULL; + } else { + current = current->pNext; + } + } + } + + pDnsRecordListFree(dnsrecords, DnsFreeRecordList); + } + + FreeLibrary(hdnsapi); + } + } + + return set; +} + +static int resolver_win32_srv_query(const char *fulldomain, + unsigned char *buf, size_t len) +{ + int set = 0; + int insize; + + /* if dnsapi didn't work/isn't there, try querying the dns server manually */ + if (!set) + { + struct dnsquery_header header; + struct dnsquery_question question; + int offset = 0; + int addrlen; + sock_t sock; + struct sockaddr_in dnsaddr; + char dnsserverips[16][256]; + int numdnsservers = 0; + int j; + + /* Try getting the DNS server ips from GetNetworkParams() in iphlpapi first */ + if (!numdnsservers) + { + HINSTANCE hiphlpapi = NULL; + DWORD (WINAPI * pGetNetworkParams)(PFIXED_INFO, PULONG); + + if (hiphlpapi = LoadLibrary("Iphlpapi.dll")) + { + pGetNetworkParams = (void *)GetProcAddress(hiphlpapi, "GetNetworkParams"); + + if (pGetNetworkParams) + { + FIXED_INFO *fi; + ULONG len; + DWORD error; + char buffer[65535]; + + len = 65535; + fi = buffer; + + if ((error = pGetNetworkParams(fi, &len)) == ERROR_SUCCESS) + { + IP_ADDR_STRING *pias = &(fi->DnsServerList); + + while (pias && numdnsservers < 16) + { + strcpy(dnsserverips[numdnsservers++], pias->IpAddress.String); + pias = pias->Next; + } + } + } + } + FreeLibrary(hiphlpapi); + } + + /* Next, try getting the DNS server ips from the registry */ + if (!numdnsservers) + { + HKEY search; + LONG error; + + error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters", 0, KEY_READ, &search); + + if (error != ERROR_SUCCESS) + { + error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\VxD\\MSTCP", 0, KEY_READ, &search); + } + + if (error == ERROR_SUCCESS) + { + char name[512]; + DWORD len = 512; + + error = RegQueryValueEx(search, "NameServer", NULL, NULL, (LPBYTE)name, &len); + + if (error != ERROR_SUCCESS) + { + error = RegQueryValueEx(search, "DhcpNameServer", NULL, NULL, (LPBYTE)name, &len); + } + + if (error == ERROR_SUCCESS) + { + char *parse = "0123456789.", *start, *end; + start = name; + end = name; + name[len] = '\0'; + + while (*start && numdnsservers < 16) + { + while (strchr(parse, *end)) + { + end++; + } + + strncpy(dnsserverips[numdnsservers++], start, end - start); + + while (*end && !strchr(parse, *end)) + { + end++; + } + + start = end; + } + } + } + + RegCloseKey(search); + } + + if (!numdnsservers) + { + HKEY searchlist; + LONG error; + + error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces", 0, KEY_READ, &searchlist); + + if (error == ERROR_SUCCESS) + { + unsigned int i; + DWORD numinterfaces = 0; + + RegQueryInfoKey(searchlist, NULL, NULL, NULL, &numinterfaces, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + for (i = 0; i < numinterfaces; i++) + { + char name[512]; + DWORD len = 512; + HKEY searchentry; + + RegEnumKeyEx(searchlist, i, (LPTSTR)name, &len, NULL, NULL, NULL, NULL); + + if (RegOpenKeyEx(searchlist, name, 0, KEY_READ, &searchentry) == ERROR_SUCCESS) + { + if (RegQueryValueEx(searchentry, "DhcpNameServer", NULL, NULL, (LPBYTE)name, &len) == ERROR_SUCCESS) + { + char *parse = "0123456789.", *start, *end; + start = name; + end = name; + name[len] = '\0'; + + while (*start && numdnsservers < 16) + { + while (strchr(parse, *end)) + { + end++; + } + + strncpy(dnsserverips[numdnsservers++], start, end - start); + + while (*end && !strchr(parse, *end)) + { + end++; + } + + start = end; + } + } + else if (RegQueryValueEx(searchentry, "NameServer", NULL, NULL, (LPBYTE)name, &len) == ERROR_SUCCESS) + { + char *parse = "0123456789.", *start, *end; + start = name; + end = name; + name[len] = '\0'; + + while (*start && numdnsservers < 16) + { + while (strchr(parse, *end)) + { + end++; + } + + strncpy(dnsserverips[numdnsservers++], start, end - start); + + while (*end && !strchr(parse, *end)) + { + end++; + } + + start = end; + } + } + RegCloseKey(searchentry); + } + } + RegCloseKey(searchlist); + } + } + + /* If we have a DNS server, use it */ + if (numdnsservers) + { + ULONG nonblocking = 1; + int i; + + memset(&header, 0, sizeof(header)); + header.id = 12345; /* FIXME: Get a better id here */ + header.rd = 1; + header.qdcount = 1; + + netbuf_add_dnsquery_header(buf, len, &offset, &header); + + memset(&question, 0, sizeof(question)); + strncpy(question.qname, fulldomain, 1024); + question.qtype = 33; /* SRV */ + question.qclass = 1; /* INTERNET! */ + + netbuf_add_dnsquery_question(buf, len, &offset, &question); + + insize = 0; + for (i = 0; i < numdnsservers && insize <= 0; i++) + { + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + ioctlsocket(sock, FIONBIO, &nonblocking); + + memset(&dnsaddr, 0, sizeof(dnsaddr)); + + dnsaddr.sin_family = AF_INET; + dnsaddr.sin_port = htons(53); + dnsaddr.sin_addr.s_addr = inet_addr(dnsserverips[i]); + + addrlen = sizeof(dnsaddr); + sendto(sock, (char *)buf, offset, 0, (struct sockaddr *)&dnsaddr, addrlen); + for (j = 0; j < 50; j++) + { + insize = recvfrom(sock, (char *)buf, len, 0, (struct sockaddr *)&dnsaddr, &addrlen); + if (insize == SOCKET_ERROR) + { + if (sock_error() == WSAEWOULDBLOCK) + { + Sleep(100); + } + else + { + break; + } + } + else + { + break; + } + } + + closesocket(sock); + } + set = insize > 0; + } + + } + + return set ? insize : -1; +} + +#endif /* _WIN32 */ diff --git a/source/resolver.h b/source/resolver.h new file mode 100644 index 0000000..f4f662f --- /dev/null +++ b/source/resolver.h @@ -0,0 +1,41 @@ +/* resolver.h + * strophe XMPP client library -- DNS resolver + * + * Copyright (C) 2015 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. + */ + +/** @file + * DNS resolver. + */ + +#ifndef __LIBSTROPHE_RESOLVER_H__ +#define __LIBSTROPHE_RESOLVER_H__ + +#include "ostypes.h" + +/** Perform lookup for RFC1035 message format. */ +int resolver_srv_lookup_buf(const unsigned char *buf, size_t len, + char *target, size_t target_len, + unsigned short *port); + +/** Resolve SRV record. + * + * @param service service of the SRV record + * @param proto protocol of the SRV record + * @param domain resolving domain + * @param target pre-allocated string where result is stored + * @param target_len maximum size of the target + * @param port pointer where resulting port is stored + * + * @return 1 on success or 0 on fail + */ +int resolver_srv_lookup(const char *service, const char *proto, + const char *domain, char *target, + size_t target_len, unsigned short *port); + +#endif /* __LIBSTROPHE_RESOLVER_H__ */ diff --git a/source/sasl.c b/source/sasl.c new file mode 100644 index 0000000..332947d --- /dev/null +++ b/source/sasl.c @@ -0,0 +1,716 @@ +/* sasl.c +** strophe XMPP client library -- SASL authentication helpers +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * SASL authentication. + */ + +#include +#include + +#include "strophe.h" +#include "common.h" +#include "ostypes.h" +#include "sasl.h" +#include "md5.h" +#include "sha1.h" +#include "scram.h" +#include "rand.h" + +#ifdef _WIN32 +#define strtok_r strtok_s +#endif + + +/** generate authentication string for the SASL PLAIN mechanism */ +char *sasl_plain(xmpp_ctx_t *ctx, const char *authid, const char *password) { + int idlen, passlen; + char *result = NULL; + char *msg; + + /* our message is Base64(authzid,\0,authid,\0,password) + if there is no authzid, that field is left empty */ + + idlen = strlen(authid); + passlen = strlen(password); + msg = xmpp_alloc(ctx, 2 + idlen + passlen); + if (msg != NULL) { + msg[0] = '\0'; + memcpy(msg+1, authid, idlen); + msg[1+idlen] = '\0'; + memcpy(msg+1+idlen+1, password, passlen); + result = base64_encode(ctx, (unsigned char *)msg, 2 + idlen + passlen); + xmpp_free(ctx, msg); + } + + return result; +} + +/** helpers for digest auth */ + +/* create a new, null-terminated string from a substring */ +static char *_make_string(xmpp_ctx_t *ctx, const char *s, const unsigned len) +{ + char *result; + + result = xmpp_alloc(ctx, len + 1); + if (result != NULL) { + memcpy(result, s, len); + result[len] = '\0'; + } + return result; +} + +/* create a new, null-terminated string quoting another string */ +static char *_make_quoted(xmpp_ctx_t *ctx, const char *s) +{ + char *result; + int len = strlen(s); + + result = xmpp_alloc(ctx, len + 3); + if (result != NULL) { + result[0] = '"'; + memcpy(result+1, s, len); + result[len+1] = '"'; + result[len+2] = '\0'; + } + return result; +} + +/* split key, value pairs into a hash */ +static hash_t *_parse_digest_challenge(xmpp_ctx_t *ctx, const char *msg) +{ + hash_t *result; + unsigned char *text; + char *key, *value; + unsigned char *s, *t; + + text = base64_decode(ctx, msg, strlen(msg)); + if (text == NULL) { + xmpp_error(ctx, "SASL", "couldn't Base64 decode challenge!"); + return NULL; + } + + result = hash_new(ctx, 10, xmpp_free); + if (result != NULL) { + s = text; + while (*s != '\0') { + /* skip any leading commas and spaces */ + while ((*s == ',') || (*s == ' ')) s++; + /* accumulate a key ending at '=' */ + t = s; + while ((*t != '=') && (*t != '\0')) t++; + if (*t == '\0') break; /* bad string */ + key = _make_string(ctx, (char *)s, (t-s)); + if (key == NULL) break; + /* advance our start pointer past the key */ + s = t + 1; + t = s; + /* if we see quotes, grab the string in between */ + if ((*s == '\'') || (*s == '"')) { + t++; + while ((*t != *s) && (*t != '\0')) + t++; + value = _make_string(ctx, (char *)s+1, (t-s-1)); + if (*t == *s) { + s = t + 1; + } else { + s = t; + } + /* otherwise, accumulate a value ending in ',' or '\0' */ + } else { + while ((*t != ',') && (*t != '\0')) t++; + value = _make_string(ctx, (char *)s, (t-s)); + s = t; + } + if (value == NULL) { + xmpp_free(ctx, key); + break; + } + /* TODO: check for collisions per spec */ + hash_add(result, key, value); + /* hash table now owns the value, free the key */ + xmpp_free(ctx, key); + } + } + xmpp_free(ctx, text); + + return result; +} + +/** expand a 16 byte MD5 digest to a 32 byte hex representation */ +static void _digest_to_hex(const char *digest, char *hex) +{ + int i; + const char hexdigit[] = "0123456789abcdef"; + + for (i = 0; i < 16; i++) { + *hex++ = hexdigit[ (digest[i] >> 4) & 0x0F ]; + *hex++ = hexdigit[ digest[i] & 0x0F ]; + } +} + +/** append 'key="value"' to a buffer, growing as necessary */ +static char *_add_key(xmpp_ctx_t *ctx, hash_t *table, const char *key, + char *buf, int *len, int quote) +{ + int olen,nlen; + int keylen, valuelen; + const char *value, *qvalue; + char *c; + + /* allocate a zero-length string if necessary */ + if (buf == NULL) { + buf = xmpp_alloc(ctx, 1); + buf[0] = '\0'; + } + if (buf == NULL) return NULL; + + /* get current string length */ + olen = strlen(buf); + value = hash_get(table, key); + if (value == NULL) { + xmpp_error(ctx, "SASL", "couldn't retrieve value for '%s'", key); + value = ""; + } + if (quote) { + qvalue = _make_quoted(ctx, value); + } else { + qvalue = value; + } + /* added length is key + '=' + value */ + /* (+ ',' if we're not the first entry */ + keylen = strlen(key); + valuelen = strlen(qvalue); + nlen = (olen ? 1 : 0) + keylen + 1 + valuelen + 1; + buf = xmpp_realloc(ctx, buf, olen+nlen); + + if (buf != NULL) { + c = buf + olen; + if (olen) *c++ = ','; + memcpy(c, key, keylen); c += keylen; + *c++ = '='; + memcpy(c, qvalue, valuelen); c += valuelen; + *c++ = '\0'; + } + + if (quote) xmpp_free(ctx, (char *)qvalue); + + return buf; +} + +/** generate auth response string for the SASL DIGEST-MD5 mechanism */ +char *sasl_digest_md5(xmpp_ctx_t *ctx, const char *challenge, + const char *jid, const char *password) { + hash_t *table; + char *result = NULL; + char *node, *domain, *realm; + char *value; + char *response; + int rlen; + struct MD5Context MD5; + unsigned char digest[16], HA1[16], HA2[16]; + char hex[32]; + char cnonce[13]; + + /* our digest response is + Hex( KD( HEX(MD5(A1)), + nonce ':' nc ':' cnonce ':' qop ':' HEX(MD5(A2)) + )) + + where KD(k, s) = MD5(k ':' s), + A1 = MD5( node ':' realm ':' password ) ':' nonce ':' cnonce + A2 = "AUTHENTICATE" ':' "xmpp/" domain + + If there is an authzid it is ':'-appended to A1 */ + + /* parse the challenge */ + table = _parse_digest_challenge(ctx, challenge); + if (table == NULL) { + xmpp_error(ctx, "SASL", "couldn't parse digest challenge"); + return NULL; + } + + node = xmpp_jid_node(ctx, jid); + domain = xmpp_jid_domain(ctx, jid); + + /* generate default realm of domain if one didn't come from the + server */ + realm = hash_get(table, "realm"); + if (realm == NULL || strlen(realm) == 0) { + hash_add(table, "realm", xmpp_strdup(ctx, domain)); + realm = hash_get(table, "realm"); + } + + /* add our response fields */ + hash_add(table, "username", xmpp_strdup(ctx, node)); + xmpp_rand_nonce(ctx, cnonce, sizeof(cnonce)); + hash_add(table, "cnonce", xmpp_strdup(ctx, cnonce)); + hash_add(table, "nc", xmpp_strdup(ctx, "00000001")); + hash_add(table, "qop", xmpp_strdup(ctx, "auth")); + value = xmpp_alloc(ctx, 5 + strlen(domain) + 1); + memcpy(value, "xmpp/", 5); + memcpy(value+5, domain, strlen(domain)); + value[5+strlen(domain)] = '\0'; + hash_add(table, "digest-uri", value); + + /* generate response */ + + /* construct MD5(node : realm : password) */ + MD5Init(&MD5); + MD5Update(&MD5, (unsigned char *)node, strlen(node)); + MD5Update(&MD5, (unsigned char *)":", 1); + MD5Update(&MD5, (unsigned char *)realm, strlen(realm)); + MD5Update(&MD5, (unsigned char *)":", 1); + MD5Update(&MD5, (unsigned char *)password, strlen(password)); + MD5Final(digest, &MD5); + + /* digest now contains the first field of A1 */ + + MD5Init(&MD5); + MD5Update(&MD5, digest, 16); + MD5Update(&MD5, (unsigned char *)":", 1); + value = hash_get(table, "nonce"); + MD5Update(&MD5, (unsigned char *)value, strlen(value)); + MD5Update(&MD5, (unsigned char *)":", 1); + value = hash_get(table, "cnonce"); + MD5Update(&MD5, (unsigned char *)value, strlen(value)); + MD5Final(digest, &MD5); + + /* now digest is MD5(A1) */ + memcpy(HA1, digest, 16); + + /* construct MD5(A2) */ + MD5Init(&MD5); + MD5Update(&MD5, (unsigned char *)"AUTHENTICATE:", 13); + value = hash_get(table, "digest-uri"); + MD5Update(&MD5, (unsigned char *)value, strlen(value)); + if (strcmp(hash_get(table, "qop"), "auth") != 0) { + MD5Update(&MD5, (unsigned char *)":00000000000000000000000000000000", + 33); + } + MD5Final(digest, &MD5); + + memcpy(HA2, digest, 16); + + /* construct response */ + MD5Init(&MD5); + _digest_to_hex((char *)HA1, hex); + MD5Update(&MD5, (unsigned char *)hex, 32); + MD5Update(&MD5, (unsigned char *)":", 1); + value = hash_get(table, "nonce"); + MD5Update(&MD5, (unsigned char *)value, strlen(value)); + MD5Update(&MD5, (unsigned char *)":", 1); + value = hash_get(table, "nc"); + MD5Update(&MD5, (unsigned char *)value, strlen(value)); + MD5Update(&MD5, (unsigned char *)":", 1); + value = hash_get(table, "cnonce"); + MD5Update(&MD5, (unsigned char *)value, strlen(value)); + MD5Update(&MD5, (unsigned char *)":", 1); + value = hash_get(table, "qop"); + MD5Update(&MD5, (unsigned char *)value, strlen(value)); + MD5Update(&MD5, (unsigned char *)":", 1); + _digest_to_hex((char *)HA2, hex); + MD5Update(&MD5, (unsigned char *)hex, 32); + MD5Final(digest, &MD5); + + response = xmpp_alloc(ctx, 32+1); + _digest_to_hex((char *)digest, hex); + memcpy(response, hex, 32); + response[32] = '\0'; + hash_add(table, "response", response); + + /* construct reply */ + result = NULL; + rlen = 0; + result = _add_key(ctx, table, "username", result, &rlen, 1); + result = _add_key(ctx, table, "realm", result, &rlen, 1); + result = _add_key(ctx, table, "nonce", result, &rlen, 1); + result = _add_key(ctx, table, "cnonce", result, &rlen, 1); + result = _add_key(ctx, table, "nc", result, &rlen, 0); + result = _add_key(ctx, table, "qop", result, &rlen, 0); + result = _add_key(ctx, table, "digest-uri", result, &rlen, 1); + result = _add_key(ctx, table, "response", result, &rlen, 0); + result = _add_key(ctx, table, "charset", result, &rlen, 0); + + xmpp_free(ctx, node); + xmpp_free(ctx, domain); + hash_release(table); /* also frees value strings */ + + /* reuse response for the base64 encode of our result */ + response = base64_encode(ctx, (unsigned char *)result, strlen(result)); + xmpp_free(ctx, result); + + return response; +} + +/** generate auth response string for the SASL SCRAM-SHA-1 mechanism */ +char *sasl_scram_sha1(xmpp_ctx_t *ctx, const char *challenge, + const char *first_bare, const char *jid, + const char *password) +{ + uint8_t key[SHA1_DIGEST_SIZE]; + uint8_t sign[SHA1_DIGEST_SIZE]; + char *r = NULL; + char *s = NULL; + char *i = NULL; + char *sval; + size_t sval_len; + long ival; + char *tmp; + char *ptr; + char *saveptr = NULL; + char *response; + char *auth; + char *response_b64; + char *sign_b64; + char *result = NULL; + size_t response_len; + size_t auth_len; + int j; + + tmp = xmpp_strdup(ctx, challenge); + if (!tmp) { + return NULL; + } + + ptr = strtok_r(tmp, ",", &saveptr); + while (ptr) { + if (strncmp(ptr, "r=", 2) == 0) { + r = ptr; + } else if (strncmp(ptr, "s=", 2) == 0) { + s = ptr + 2; + } else if (strncmp(ptr, "i=", 2) == 0) { + i = ptr + 2; + } + ptr = strtok_r(NULL, ",", &saveptr); + } + + if (!r || !s || !i) { + goto out; + } + + sval = (char *)base64_decode(ctx, s, strlen(s)); + if (!sval) { + goto out; + } + sval_len = base64_decoded_len(ctx, s, strlen(s)); + ival = strtol(i, &saveptr, 10); + + auth_len = 10 + strlen(r) + strlen(first_bare) + strlen(challenge); + auth = xmpp_alloc(ctx, auth_len); + if (!auth) { + goto out_sval; + } + + response_len = 39 + strlen(r); + response = xmpp_alloc(ctx, response_len); + if (!response) { + goto out_auth; + } + + xmpp_snprintf(response, response_len, "c=biws,%s", r); + xmpp_snprintf(auth, auth_len, "%s,%s,%s", first_bare + 3, challenge, + response); + + SCRAM_SHA1_ClientKey((uint8_t *)password, strlen(password), + (uint8_t *)sval, sval_len, (uint32_t)ival, key); + SCRAM_SHA1_ClientSignature(key, (uint8_t *)auth, strlen(auth), sign); + for (j = 0; j < SHA1_DIGEST_SIZE; j++) { + sign[j] ^= key[j]; + } + + sign_b64 = base64_encode(ctx, sign, sizeof(sign)); + if (!sign_b64) { + goto out_response; + } + + if (strlen(response) + strlen(sign_b64) + 3 + 1 > response_len) { + xmpp_free(ctx, sign_b64); + goto out_response; + } + strcat(response, ",p="); + strcat(response, sign_b64); + xmpp_free(ctx, sign_b64); + + response_b64 = base64_encode(ctx, (unsigned char *)response, + strlen(response)); + if (!response_b64) { + goto out_response; + } + result = response_b64; + +out_response: + xmpp_free(ctx, response); +out_auth: + xmpp_free(ctx, auth); +out_sval: + xmpp_free(ctx, sval); +out: + xmpp_free(ctx, tmp); + return result; +} + + +/** Base64 encoding routines. Implemented according to RFC 3548 */ + +/** map of all byte values to the base64 values, or to + '65' which indicates an invalid character. '=' is '64' */ +static const char _base64_invcharmap[256] = { + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,62, 65,65,65,63, + 52,53,54,55, 56,57,58,59, 60,61,65,65, 65,64,65,65, + 65, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,65, 65,65,65,65, + 65,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65, + 65,65,65,65, 65,65,65,65, 65,65,65,65, 65,65,65,65 +}; + +/** map of all 6-bit values to their corresponding byte + in the base64 alphabet. Padding char is the value '64' */ +static const char _base64_charmap[65] = { + 'A','B','C','D', 'E','F','G','H', + 'I','J','K','L', 'M','N','O','P', + 'Q','R','S','T', 'U','V','W','X', + 'Y','Z','a','b', 'c','d','e','f', + 'g','h','i','j', 'k','l','m','n', + 'o','p','q','r', 's','t','u','v', + 'w','x','y','z', '0','1','2','3', + '4','5','6','7', '8','9','+','/', + '=' +}; + +int base64_encoded_len(xmpp_ctx_t *ctx, const unsigned len) +{ + /* encoded steam is 4 bytes for every three, rounded up */ + return ((len + 2)/3) << 2; +} + +char *base64_encode(xmpp_ctx_t *ctx, + const unsigned char * const buffer, const unsigned len) +{ + int clen; + char *cbuf, *c; + uint32_t word, hextet; + unsigned i; + + clen = base64_encoded_len(ctx, len); + cbuf = xmpp_alloc(ctx, clen + 1); + if (cbuf != NULL) { + c = cbuf; + /* loop over data, turning every 3 bytes into 4 characters */ + for (i = 0; i + 2 < len; i += 3) { + word = buffer[i] << 16 | buffer[i+1] << 8 | buffer[i+2]; + hextet = (word & 0x00FC0000) >> 18; + *c++ = _base64_charmap[hextet]; + hextet = (word & 0x0003F000) >> 12; + *c++ = _base64_charmap[hextet]; + hextet = (word & 0x00000FC0) >> 6; + *c++ = _base64_charmap[hextet]; + hextet = (word & 0x000003F); + *c++ = _base64_charmap[hextet]; + } + /* zero, one or two bytes left */ + switch (len - i) { + case 0: + break; + case 1: + hextet = (buffer[len-1] & 0xFC) >> 2; + *c++ = _base64_charmap[hextet]; + hextet = (buffer[len-1] & 0x03) << 4; + *c++ = _base64_charmap[hextet]; + *c++ = _base64_charmap[64]; /* pad */ + *c++ = _base64_charmap[64]; /* pad */ + break; + case 2: + hextet = (buffer[len-2] & 0xFC) >> 2; + *c++ = _base64_charmap[hextet]; + hextet = ((buffer[len-2] & 0x03) << 4) | + ((buffer[len-1] & 0xF0) >> 4); + *c++ = _base64_charmap[hextet]; + hextet = (buffer[len-1] & 0x0F) << 2; + *c++ = _base64_charmap[hextet]; + *c++ = _base64_charmap[64]; /* pad */ + break; + } + /* add a terminal null */ + *c = '\0'; + } + + return cbuf; +} + +int base64_decoded_len(xmpp_ctx_t *ctx, + const char * const buffer, const unsigned len) +{ + int nudge; + int c; + + if (len < 4) return 0; + + /* count the padding characters for the remainder */ + nudge = -1; + c = _base64_invcharmap[(int)buffer[len-1]]; + if (c < 64) nudge = 0; + else if (c == 64) { + c = _base64_invcharmap[(int)buffer[len-2]]; + if (c < 64) nudge = 1; + else if (c == 64) { + c = _base64_invcharmap[(int)buffer[len-3]]; + if (c < 64) nudge = 2; + } + } + if (nudge < 0) return 0; /* reject bad coding */ + + /* decoded steam is 3 bytes for every four */ + return 3 * (len >> 2) - nudge; +} + +unsigned char *base64_decode(xmpp_ctx_t *ctx, + const char * const buffer, const unsigned len) +{ + int dlen; + unsigned char *dbuf, *d; + uint32_t word, hextet = 0; + unsigned i; + + /* len must be a multiple of 4 */ + if (len & 0x03) return NULL; + + dlen = base64_decoded_len(ctx, buffer, len); + dbuf = xmpp_alloc(ctx, dlen + 1); + if (dbuf != NULL) { + d = dbuf; + /* loop over each set of 4 characters, decoding 3 bytes */ + for (i = 0; i + 3 < len; i += 4) { + hextet = _base64_invcharmap[(int)buffer[i]]; + if (hextet & 0xC0) break; + word = hextet << 18; + hextet = _base64_invcharmap[(int)buffer[i+1]]; + if (hextet & 0xC0) break; + word |= hextet << 12; + hextet = _base64_invcharmap[(int)buffer[i+2]]; + if (hextet & 0xC0) break; + word |= hextet << 6; + hextet = _base64_invcharmap[(int)buffer[i+3]]; + if (hextet & 0xC0) break; + word |= hextet; + *d++ = (word & 0x00FF0000) >> 16; + *d++ = (word & 0x0000FF00) >> 8; + *d++ = (word & 0x000000FF); + } + if (hextet > 64) goto _base64_decode_error; + /* handle the remainder */ + switch (dlen % 3) { + case 0: + /* nothing to do */ + break; + case 1: + /* redo the last quartet, checking for correctness */ + hextet = _base64_invcharmap[(int)buffer[len-4]]; + if (hextet & 0xC0) goto _base64_decode_error; + word = hextet << 2; + hextet = _base64_invcharmap[(int)buffer[len-3]]; + if (hextet & 0xC0) goto _base64_decode_error; + word |= hextet >> 4; + *d++ = word & 0xFF; + hextet = _base64_invcharmap[(int)buffer[len-2]]; + if (hextet != 64) goto _base64_decode_error; + hextet = _base64_invcharmap[(int)buffer[len-1]]; + if (hextet != 64) goto _base64_decode_error; + break; + case 2: + /* redo the last quartet, checking for correctness */ + hextet = _base64_invcharmap[(int)buffer[len-4]]; + if (hextet & 0xC0) goto _base64_decode_error; + word = hextet << 10; + hextet = _base64_invcharmap[(int)buffer[len-3]]; + if (hextet & 0xC0) goto _base64_decode_error; + word |= hextet << 4; + hextet = _base64_invcharmap[(int)buffer[len-2]]; + if (hextet & 0xC0) goto _base64_decode_error; + word |= hextet >> 2; + *d++ = (word & 0xFF00) >> 8; + *d++ = (word & 0x00FF); + hextet = _base64_invcharmap[(int)buffer[len-1]]; + if (hextet != 64) goto _base64_decode_error; + break; + } + *d = '\0'; + } + return dbuf; + +_base64_decode_error: + /* invalid character; abort decoding! */ + xmpp_free(ctx, dbuf); + return NULL; +} + +/*** self tests ***/ +#ifdef TEST + +#include + +int test_charmap_identity(void) +{ + int i, v, u; + + for (i = 0; i < 65; i++) { + v = _base64_charmap[i]; + if (v > 255) return 1; + u = _base64_invcharmap[v]; +/* printf("map: %d -> %d -> %d\n", i, v, u); */ + if (u != i) return 1; + } + + return 0; +} + +int test_charmap_range(void) +{ + int i, v; + + for (i = 64; i < 256; i++) { + v = _base64_invcharmap[i]; + if (i < 64) return 1; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret = 0; + + printf("testing charmap identity..."); + ret = test_charmap_identity(); + if (ret) return ret; + printf(" ok.\n"); + + printf("testing charmap range..."); + ret = test_charmap_range(); + if (ret) return ret; + printf(" ok.\n"); + + printf("no error\n"); + return 0; +} + +#endif /* TEST */ diff --git a/source/sasl.h b/source/sasl.h new file mode 100644 index 0000000..bc7511a --- /dev/null +++ b/source/sasl.h @@ -0,0 +1,44 @@ +/* sasl.h +** strophe XMPP client library -- SASL authentication helpers +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * SASL authentication helpers. + */ + +#ifndef __LIBSTROPHE_SASL_H__ +#define __LIBSTROPHE_SASL_H__ + +#include "strophe.h" + +/** low-level sasl routines */ + +char *sasl_plain(xmpp_ctx_t *ctx, const char *authid, const char *password); +char *sasl_digest_md5(xmpp_ctx_t *ctx, const char *challenge, + const char *jid, const char *password); +char *sasl_scram_sha1(xmpp_ctx_t *ctx, const char *challenge, + const char *first_bare, const char *jid, + const char *password); + + +/** Base64 encoding routines. Implemented according to RFC 3548 */ + +int base64_encoded_len(xmpp_ctx_t *ctx, const unsigned len); + +char *base64_encode(xmpp_ctx_t *ctx, + const unsigned char * const buffer, const unsigned len); + +int base64_decoded_len(xmpp_ctx_t *ctx, + const char * const buffer, const unsigned len); + +unsigned char *base64_decode(xmpp_ctx_t *ctx, + const char * const buffer, const unsigned len); + +#endif /* _LIBXMPP_SASL_H__ */ diff --git a/source/scram.c b/source/scram.c new file mode 100644 index 0000000..97d0784 --- /dev/null +++ b/source/scram.c @@ -0,0 +1,131 @@ +/* scram.c + * strophe XMPP client library + * + * SCRAM-SHA1 helper functions according to RFC5802 + * HMAC-SHA1 implementation according to RFC2104 + * + * Copyright (C) 2013 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. + */ + +/** @file + * SCRAM-SHA1 helper functions. + */ + +#include +#include + +#include "sha1.h" +#include "ostypes.h" + +#include "scram.h" + +#define HMAC_BLOCK_SIZE 64 + +static const uint8_t ipad = 0x36; +static const uint8_t opad = 0x5C; + +static void crypto_HMAC_SHA1(const uint8_t *key, size_t key_len, + const uint8_t *text, size_t len, + uint8_t *digest) +{ + uint8_t key_pad[HMAC_BLOCK_SIZE]; + uint8_t key_ipad[HMAC_BLOCK_SIZE]; + uint8_t key_opad[HMAC_BLOCK_SIZE]; + uint8_t sha_digest[SHA1_DIGEST_SIZE]; + int i; + SHA1_CTX ctx; + + memset(key_pad, 0, sizeof(key_pad)); + if (key_len <= HMAC_BLOCK_SIZE) { + memcpy(key_pad, key, key_len); + } else { + /* according to RFC2104 */ + crypto_SHA1(key, key_len, key_pad); + } + + for (i = 0; i < HMAC_BLOCK_SIZE; i++) { + key_ipad[i] = key_pad[i] ^ ipad; + key_opad[i] = key_pad[i] ^ opad; + } + + crypto_SHA1_Init(&ctx); + crypto_SHA1_Update(&ctx, key_ipad, HMAC_BLOCK_SIZE); + crypto_SHA1_Update(&ctx, text, len); + crypto_SHA1_Final(&ctx, sha_digest); + + crypto_SHA1_Init(&ctx); + crypto_SHA1_Update(&ctx, key_opad, HMAC_BLOCK_SIZE); + crypto_SHA1_Update(&ctx, sha_digest, SHA1_DIGEST_SIZE); + crypto_SHA1_Final(&ctx, digest); +} + +static void SCRAM_SHA1_Hi(const uint8_t *text, size_t len, + const uint8_t *salt, size_t salt_len, uint32_t i, + uint8_t *digest) +{ + int k; + uint32_t j; + uint8_t tmp[128]; + + static uint8_t int1[] = {0x0, 0x0, 0x0, 0x1}; + + /* assume salt + INT(1) isn't longer than sizeof(tmp) */ + assert(salt_len <= sizeof(tmp) - sizeof(int1)); + + memset(digest, 0, SHA1_DIGEST_SIZE); + if (i == 0) { + return; + } + + memcpy(tmp, salt, salt_len); + memcpy(&tmp[salt_len], int1, sizeof(int1)); + + /* 'text' for Hi is a 'key' for HMAC */ + crypto_HMAC_SHA1(text, len, tmp, salt_len + sizeof(int1), digest); + memcpy(tmp, digest, SHA1_DIGEST_SIZE); + + for (j = 1; j < i; j++) { + crypto_HMAC_SHA1(text, len, tmp, SHA1_DIGEST_SIZE, tmp); + for (k = 0; k < SHA1_DIGEST_SIZE; k++) { + digest[k] ^= tmp[k]; + } + } +} + +void SCRAM_SHA1_ClientKey(const uint8_t *password, size_t len, + const uint8_t *salt, size_t salt_len, uint32_t i, + uint8_t *key) +{ + uint8_t salted[SHA1_DIGEST_SIZE]; + + /* XXX: Normalize(password) is omitted */ + + SCRAM_SHA1_Hi(password, len, salt, salt_len, i, salted); + crypto_HMAC_SHA1(salted, SHA1_DIGEST_SIZE, (uint8_t *)"Client Key", + strlen("Client Key"), key); +} + +void SCRAM_SHA1_ClientSignature(const uint8_t *ClientKey, + const uint8_t *AuthMessage, size_t len, + uint8_t *sign) +{ + uint8_t stored[SHA1_DIGEST_SIZE]; + + crypto_SHA1(ClientKey, SHA1_DIGEST_SIZE, stored); + crypto_HMAC_SHA1(stored, SHA1_DIGEST_SIZE, AuthMessage, len, sign); +} + +void SCRAM_SHA1_ClientProof(const uint8_t *ClientKey, + const uint8_t *ClientSignature, + uint8_t *proof) +{ + int i; + for (i = 0; i < SHA1_DIGEST_SIZE; i++) { + proof[i] = ClientKey[i] ^ ClientSignature[i]; + } +} diff --git a/source/scram.h b/source/scram.h new file mode 100644 index 0000000..e6b8cc8 --- /dev/null +++ b/source/scram.h @@ -0,0 +1,36 @@ +/* scram.h + * strophe XMPP client library -- SCRAM-SHA1 helper functions + * + * Copyright (C) 2013 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. + */ + +/** @file + * SCRAM-SHA1 helper functions. + */ + +#ifndef __LIBSTROPHE_SCRAM_H__ +#define __LIBSTROPHE_SCRAM_H__ + +/* make sure the stdint.h types are available */ +#include "ostypes.h" + +#include "sha1.h" + +void SCRAM_SHA1_ClientKey(const uint8_t *password, size_t len, + const uint8_t *salt, size_t salt_len, uint32_t i, + uint8_t *key); + +void SCRAM_SHA1_ClientSignature(const uint8_t *ClientKey, + const uint8_t *AuthMessage, size_t len, + uint8_t *sign); + +void SCRAM_SHA1_ClientProof(const uint8_t *ClientKey, + const uint8_t *ClientSignature, + uint8_t *proof); + +#endif /* __LIBSTROPHE_SCRAM_H__ */ diff --git a/source/sha1.c b/source/sha1.c new file mode 100644 index 0000000..028d3a7 --- /dev/null +++ b/source/sha1.c @@ -0,0 +1,253 @@ +/** @file + * SHA-1 hash. + */ + +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +----------------- +Modified 7/98 +By James H. Brown +Still 100% Public Domain + +Corrected a problem which generated improper hash values on 16 bit machines +Routine SHA1Update changed from + void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int +len) +to + void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned +long len) + +The 'len' parameter was declared an int which works fine on 32 bit machines. +However, on 16 bit machines an int is too small for the shifts being done +against +it. This caused the hash function to generate incorrect values if len was +greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update(). + +Since the file IO in main() reads 16K at a time, any file 8K or larger would +be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million +"a"s). + +I also changed the declaration of variables i & j in SHA1Update to +unsigned long from unsigned int for the same reason. + +These changes should make no difference to any 32 bit implementations since +an +int and a long are the same size in those environments. + +-- +I also corrected a few compiler warnings generated by Borland C. +1. Added #include for exit() prototype +2. Removed unused variable 'j' in SHA1Final +3. Changed exit(0) to return(0) at end of main. + +ALL changes I made can be located by searching for comments containing 'JHB' +----------------- +Modified 8/98 +By Steve Reid +Still 100% public domain + +1- Removed #include and used return() instead of exit() +2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall) +3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net + +----------------- +Modified 4/01 +By Saul Kravitz +Still 100% PD +Modified to run on Compaq Alpha hardware. + +----------------- +Modified 07/2002 +By Ralph Giles +Still 100% public domain +modified for use with stdint types, autoconf +code cleanup, removed attribution comments +switched SHA1Final() argument order for consistency +use SHA1_ prefix for public api +move public api to sha1.h +*/ + +/* Don't change user's data */ +#define SHA1HANDSOFF + +#include + +#include "ostypes.h" +#include "sha1.h" + +static uint32_t host_to_be(uint32_t i); +static void SHA1_Transform(uint32_t state[5], const uint8_t buffer[64]); + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#define blk0(i) (block->l[i] = host_to_be(block->l[i])) +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +static uint32_t host_to_be(uint32_t i) +{ +#define le_to_be(i) ((rol((i),24) & 0xFF00FF00) | (rol((i),8) & 0x00FF00FF)) +#if defined(__BIG_ENDIAN__) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \ + __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + return i; +#elif defined(__LITTLE_ENDIAN__) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \ + __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + return le_to_be(i); +#else /* fallback to run-time check */ + static const union { + uint32_t u; + unsigned char c; + } check = {1}; + return check.c ? le_to_be(i) : i; +#endif +} + +/* Hash a single 512-bit block. This is the core of the algorithm. */ +static void SHA1_Transform(uint32_t state[5], const uint8_t buffer[64]) +{ + uint32_t a, b, c, d, e; + typedef union { + uint8_t c[64]; + uint32_t l[16]; + } CHAR64LONG16; + CHAR64LONG16* block; + +#ifdef SHA1HANDSOFF + static uint8_t workspace[64]; + block = (CHAR64LONG16*)workspace; + memcpy(block, buffer, 64); +#else + block = (CHAR64LONG16*)buffer; +#endif + + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + /* Wipe variables */ + a = b = c = d = e = 0; +} + + +/* SHA1Init - Initialize new context */ +void crypto_SHA1_Init(SHA1_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ +void crypto_SHA1_Update(SHA1_CTX* context, const uint8_t* data, + const size_t len) +{ + size_t i, j; + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; + context->count[1] += (len >> 29); + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1_Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1_Transform(context->state, data + i); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ +void crypto_SHA1_Final(SHA1_CTX* context, uint8_t* digest) +{ + uint32_t i; + uint8_t finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + crypto_SHA1_Update(context, (uint8_t *)"\200", 1); + while ((context->count[0] & 504) != 448) { + crypto_SHA1_Update(context, (uint8_t *)"\0", 1); + } + crypto_SHA1_Update(context, finalcount, 8); /* Should cause a SHA1_Transform() */ + for (i = 0; i < SHA1_DIGEST_SIZE; i++) { + digest[i] = (uint8_t) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + + /* Wipe variables */ + i = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(finalcount, 0, 8); /* SWR */ + +#ifdef SHA1HANDSOFF /* make SHA1Transform overwrite its own static vars */ + SHA1_Transform(context->state, context->buffer); +#endif +} + + +void crypto_SHA1(const uint8_t* data, size_t len, uint8_t* digest) +{ + SHA1_CTX ctx; + crypto_SHA1_Init(&ctx); + crypto_SHA1_Update(&ctx, data, len); + crypto_SHA1_Final(&ctx, digest); +} diff --git a/source/sha1.h b/source/sha1.h new file mode 100644 index 0000000..8dd7700 --- /dev/null +++ b/source/sha1.h @@ -0,0 +1,36 @@ +/* public api for steve reid's public domain SHA-1 implementation */ +/* this file is in the public domain */ + +/** @file + * SHA-1 hash API. + */ + +#ifndef __LIBSTROPHE_SHA1_H__ +#define __LIBSTROPHE_SHA1_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* make sure the stdint.h types are available */ +#include "ostypes.h" + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + uint8_t buffer[64]; +} SHA1_CTX; + +#define SHA1_DIGEST_SIZE 20 + +void crypto_SHA1_Init(SHA1_CTX* context); +void crypto_SHA1_Update(SHA1_CTX* context, const uint8_t* data, + const size_t len); +void crypto_SHA1_Final(SHA1_CTX* context, uint8_t* digest); +void crypto_SHA1(const uint8_t* data, size_t len, uint8_t* digest); + +#ifdef __cplusplus +} +#endif + +#endif /* __LIBSTROPHE_SHA1_H__ */ diff --git a/source/snprintf.c b/source/snprintf.c new file mode 100644 index 0000000..e018edb --- /dev/null +++ b/source/snprintf.c @@ -0,0 +1,726 @@ +/* + * Copyright Patrick Powell 1995 + * This code is based on code written by Patrick Powell (papowell@astart.com) + * It may be used for any purpose as long as this notice remains intact + * on all source code distributions + */ + +/************************************************************** + * Original: + * Patrick Powell Tue Apr 11 09:48:21 PDT 1995 + * A bombproof version of doprnt (dopr) included. + * Sigh. This sort of thing is always nasty do deal with. Note that + * the version here does not include floating point... + * + * snprintf() is used instead of sprintf() as it does limit checks + * for string length. This covers a nasty loophole. + * + * The other functions are there to prevent NULL pointers from + * causing nast effects. + * + * More Recently: + * Brandon Long 9/15/96 for mutt 0.43 + * This was ugly. It is still ugly. I opted out of floating point + * numbers, but the formatter understands just about everything + * from the normal C string format, at least as far as I can tell from + * the Solaris 2.5 printf(3S) man page. + * + * Brandon Long 10/22/97 for mutt 0.87.1 + * Ok, added some minimal floating point support, which means this + * probably requires libm on most operating systems. Don't yet + * support the exponent (e,E) and sigfig (g,G). Also, fmtint() + * was pretty badly broken, it just wasn't being exercised in ways + * which showed it, so that's been fixed. Also, formated the code + * to mutt conventions, and removed dead code left over from the + * original. Also, there is now a builtin-test, just compile with: + * gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm + * and run snprintf for results. + * + * Thomas Roessler 01/27/98 for mutt 0.89i + * The PGP code was using unsigned hexadecimal formats. + * Unfortunately, unsigned formats simply didn't work. + * + * Michael Elkins 03/05/98 for mutt 0.90.8 + * The original code assumed that both snprintf() and vsnprintf() were + * missing. Some systems only have snprintf() but not vsnprintf(), so + * the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF. + * + * Andrew Tridgell (tridge@samba.org) Oct 1998 + * fixed handling of %.0f + * added test for HAVE_LONG_DOUBLE + * + * Russ Allbery 2000-08-26 + * fixed return value to comply with C99 + * fixed handling of snprintf(NULL, ...) + * + **************************************************************/ + +/** @file + * A snprintf implementation. + */ + +/* JAM: we don't need this - #include "config.h" */ + +/* JAM: changed declarations to xmpp_snprintf and xmpp_vsnprintf to + avoid namespace collision. */ + +#include "snprintf.h" + +/* varargs declarations: */ + +#include +#define VA_LOCAL_DECL va_list ap +#define VA_START(f) va_start(ap, f) +#define VA_END va_end(ap) + +#ifndef HAVE_VSNPRINTF + +#include +#include +#include + +#ifdef HAVE_LONG_DOUBLE +#define LDOUBLE long double +#else +#define LDOUBLE double +#endif + +static int dopr (char *buffer, size_t maxlen, const char *format, + va_list args); +static int fmtstr (char *buffer, size_t *currlen, size_t maxlen, + char *value, int flags, int min, int max); +static int fmtint (char *buffer, size_t *currlen, size_t maxlen, + long value, int base, int min, int max, int flags); +static int fmtfp (char *buffer, size_t *currlen, size_t maxlen, + LDOUBLE fvalue, int min, int max, int flags); +static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c ); + +/* + * dopr(): poor man's version of doprintf + */ + +/* format read states */ +#define DP_S_DEFAULT 0 +#define DP_S_FLAGS 1 +#define DP_S_MIN 2 +#define DP_S_DOT 3 +#define DP_S_MAX 4 +#define DP_S_MOD 5 +#define DP_S_CONV 6 +#define DP_S_DONE 7 + +/* format flags - Bits */ +#define DP_F_MINUS (1 << 0) +#define DP_F_PLUS (1 << 1) +#define DP_F_SPACE (1 << 2) +#define DP_F_NUM (1 << 3) +#define DP_F_ZERO (1 << 4) +#define DP_F_UP (1 << 5) +#define DP_F_UNSIGNED (1 << 6) + +/* Conversion Flags */ +#define DP_C_SHORT 1 +#define DP_C_LONG 2 +#define DP_C_LDOUBLE 3 + +#define char_to_int(p) (p - '0') +#define MAX(p,q) ((p >= q) ? p : q) +#define MIN(p,q) ((p <= q) ? p : q) + +static int dopr (char *buffer, size_t maxlen, const char *format, va_list args) +{ + char ch; + long value; + LDOUBLE fvalue; + char *strvalue; + int min; + int max; + int state; + int flags; + int cflags; + int total; + size_t currlen; + + state = DP_S_DEFAULT; + currlen = flags = cflags = min = 0; + max = -1; + ch = *format++; + total = 0; + + while (state != DP_S_DONE) + { + if (ch == '\0') + state = DP_S_DONE; + + switch(state) + { + case DP_S_DEFAULT: + if (ch == '%') + state = DP_S_FLAGS; + else + total += dopr_outch (buffer, &currlen, maxlen, ch); + ch = *format++; + break; + case DP_S_FLAGS: + switch (ch) + { + case '-': + flags |= DP_F_MINUS; + ch = *format++; + break; + case '+': + flags |= DP_F_PLUS; + ch = *format++; + break; + case ' ': + flags |= DP_F_SPACE; + ch = *format++; + break; + case '#': + flags |= DP_F_NUM; + ch = *format++; + break; + case '0': + flags |= DP_F_ZERO; + ch = *format++; + break; + default: + state = DP_S_MIN; + break; + } + break; + case DP_S_MIN: + if (isdigit(ch)) + { + min = 10*min + char_to_int (ch); + ch = *format++; + } + else if (ch == '*') + { + min = va_arg (args, int); + ch = *format++; + state = DP_S_DOT; + } + else + state = DP_S_DOT; + break; + case DP_S_DOT: + if (ch == '.') + { + state = DP_S_MAX; + ch = *format++; + } + else + state = DP_S_MOD; + break; + case DP_S_MAX: + if (isdigit(ch)) + { + if (max < 0) + max = 0; + max = 10*max + char_to_int (ch); + ch = *format++; + } + else if (ch == '*') + { + max = va_arg (args, int); + ch = *format++; + state = DP_S_MOD; + } + else + state = DP_S_MOD; + break; + case DP_S_MOD: + /* Currently, we don't support Long Long, bummer */ + switch (ch) + { + case 'h': + cflags = DP_C_SHORT; + ch = *format++; + break; + case 'l': + cflags = DP_C_LONG; + ch = *format++; + break; + case 'L': + cflags = DP_C_LDOUBLE; + ch = *format++; + break; + default: + break; + } + state = DP_S_CONV; + break; + case DP_S_CONV: + switch (ch) + { + case 'd': + case 'i': + if (cflags == DP_C_SHORT) + value = va_arg (args, int); + else if (cflags == DP_C_LONG) + value = va_arg (args, long int); + else + value = va_arg (args, int); + total += fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags); + break; + case 'o': + flags |= DP_F_UNSIGNED; + if (cflags == DP_C_SHORT) + value = va_arg (args, int); + else if (cflags == DP_C_LONG) + value = va_arg (args, unsigned long int); + else + value = va_arg (args, unsigned int); + total += fmtint (buffer, &currlen, maxlen, value, 8, min, max, flags); + break; + case 'u': + flags |= DP_F_UNSIGNED; + if (cflags == DP_C_SHORT) + value = va_arg (args, int); + else if (cflags == DP_C_LONG) + value = va_arg (args, unsigned long int); + else + value = va_arg (args, unsigned int); + total += fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags); + break; + case 'X': + flags |= DP_F_UP; + case 'x': + flags |= DP_F_UNSIGNED; + if (cflags == DP_C_SHORT) + value = va_arg (args, int); + else if (cflags == DP_C_LONG) + value = va_arg (args, unsigned long int); + else + value = va_arg (args, unsigned int); + total += fmtint (buffer, &currlen, maxlen, value, 16, min, max, flags); + break; + case 'f': + if (cflags == DP_C_LDOUBLE) + fvalue = va_arg (args, LDOUBLE); + else + fvalue = va_arg (args, double); + /* um, floating point? */ + total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags); + break; + case 'E': + flags |= DP_F_UP; + case 'e': + if (cflags == DP_C_LDOUBLE) + fvalue = va_arg (args, LDOUBLE); + else + fvalue = va_arg (args, double); + break; + case 'G': + flags |= DP_F_UP; + case 'g': + if (cflags == DP_C_LDOUBLE) + fvalue = va_arg (args, LDOUBLE); + else + fvalue = va_arg (args, double); + break; + case 'c': + total += dopr_outch (buffer, &currlen, maxlen, va_arg (args, int)); + break; + case 's': + strvalue = va_arg (args, char *); + total += fmtstr (buffer, &currlen, maxlen, strvalue, flags, min, max); + break; + case 'p': + strvalue = va_arg (args, void *); + total += fmtint (buffer, &currlen, maxlen, (long) strvalue, 16, min, + max, flags); + break; + case 'n': + if (cflags == DP_C_SHORT) + { + short int *num; + num = va_arg (args, short int *); + *num = currlen; + } + else if (cflags == DP_C_LONG) + { + long int *num; + num = va_arg (args, long int *); + *num = currlen; + } + else + { + int *num; + num = va_arg (args, int *); + *num = currlen; + } + break; + case '%': + total += dopr_outch (buffer, &currlen, maxlen, ch); + break; + case 'w': + /* not supported yet, treat as next char */ + ch = *format++; + break; + default: + /* Unknown, skip */ + break; + } + ch = *format++; + state = DP_S_DEFAULT; + flags = cflags = min = 0; + max = -1; + break; + case DP_S_DONE: + break; + default: + /* hmm? */ + break; /* some picky compilers need this */ + } + } + if (buffer != NULL && maxlen > 0) + { + if (currlen < maxlen - 1) + buffer[currlen] = '\0'; + else + buffer[maxlen - 1] = '\0'; + } + return total; +} + +static int fmtstr (char *buffer, size_t *currlen, size_t maxlen, + char *value, int flags, int min, int max) +{ + int padlen, strln; /* amount to pad */ + int cnt = 0; + int total = 0; + + if (value == 0) + { + value = ""; + } + + for (strln = 0; value[strln]; ++strln); /* strlen */ + if (max >= 0 && max < strln) + strln = max; + padlen = min - strln; + if (padlen < 0) + padlen = 0; + if (flags & DP_F_MINUS) + padlen = -padlen; /* Left Justify */ + + while (padlen > 0) + { + total += dopr_outch (buffer, currlen, maxlen, ' '); + --padlen; + } + while (*value && ((max < 0) || (cnt < max))) + { + total += dopr_outch (buffer, currlen, maxlen, *value++); + ++cnt; + } + while (padlen < 0) + { + total += dopr_outch (buffer, currlen, maxlen, ' '); + ++padlen; + } + return total; +} + +/* Have to handle DP_F_NUM (ie 0x and 0 alternates) */ + +static int fmtint (char *buffer, size_t *currlen, size_t maxlen, + long value, int base, int min, int max, int flags) +{ + int signvalue = 0; + unsigned long uvalue; + char convert[20]; + int place = 0; + int spadlen = 0; /* amount to space pad */ + int zpadlen = 0; /* amount to zero pad */ + int caps = 0; + int total = 0; + + if (max < 0) + max = 0; + + uvalue = value; + + if(!(flags & DP_F_UNSIGNED)) + { + if( value < 0 ) { + signvalue = '-'; + uvalue = -value; + } + else + if (flags & DP_F_PLUS) /* Do a sign (+/i) */ + signvalue = '+'; + else + if (flags & DP_F_SPACE) + signvalue = ' '; + } + + if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */ + + do { + convert[place++] = + (caps? "0123456789ABCDEF":"0123456789abcdef") + [uvalue % (unsigned)base ]; + uvalue = (uvalue / (unsigned)base ); + } while(uvalue && (place < 20)); + if (place == 20) place--; + convert[place] = 0; + + zpadlen = max - place; + spadlen = min - MAX (max, place) - (signvalue ? 1 : 0); + if (zpadlen < 0) zpadlen = 0; + if (spadlen < 0) spadlen = 0; + if (flags & DP_F_ZERO) + { + zpadlen = MAX(zpadlen, spadlen); + spadlen = 0; + } + if (flags & DP_F_MINUS) + spadlen = -spadlen; /* Left Justifty */ + +#ifdef DEBUG_SNPRINTF + dprint (1, (debugfile, "zpad: %d, spad: %d, min: %d, max: %d, place: %d\n", + zpadlen, spadlen, min, max, place)); +#endif + + /* Spaces */ + while (spadlen > 0) + { + total += dopr_outch (buffer, currlen, maxlen, ' '); + --spadlen; + } + + /* Sign */ + if (signvalue) + total += dopr_outch (buffer, currlen, maxlen, signvalue); + + /* Zeros */ + if (zpadlen > 0) + { + while (zpadlen > 0) + { + total += dopr_outch (buffer, currlen, maxlen, '0'); + --zpadlen; + } + } + + /* Digits */ + while (place > 0) + total += dopr_outch (buffer, currlen, maxlen, convert[--place]); + + /* Left Justified spaces */ + while (spadlen < 0) { + total += dopr_outch (buffer, currlen, maxlen, ' '); + ++spadlen; + } + + return total; +} + +static LDOUBLE abs_val (LDOUBLE value) +{ + LDOUBLE result = value; + + if (value < 0) + result = -value; + + return result; +} + +static LDOUBLE _snp_pow10 (int exp) +{ + LDOUBLE result = 1; + + while (exp) + { + result *= 10; + exp--; + } + + return result; +} + +static long _snp_round (LDOUBLE value) +{ + long intpart; + + intpart = value; + value = value - intpart; + if (value >= 0.5) + intpart++; + + return intpart; +} + +static int fmtfp (char *buffer, size_t *currlen, size_t maxlen, + LDOUBLE fvalue, int min, int max, int flags) +{ + int signvalue = 0; + LDOUBLE ufvalue; + char iconvert[20]; + char fconvert[20]; + int iplace = 0; + int fplace = 0; + int padlen = 0; /* amount to pad */ + int zpadlen = 0; + int caps = 0; + int total = 0; + long intpart; + long fracpart; + + /* + * AIX manpage says the default is 0, but Solaris says the default + * is 6, and sprintf on AIX defaults to 6 + */ + if (max < 0) + max = 6; + + ufvalue = abs_val (fvalue); + + if (fvalue < 0) + signvalue = '-'; + else + if (flags & DP_F_PLUS) /* Do a sign (+/i) */ + signvalue = '+'; + else + if (flags & DP_F_SPACE) + signvalue = ' '; + +#if 0 + if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */ +#endif + + intpart = ufvalue; + + /* + * Sorry, we only support 9 digits past the decimal because of our + * conversion method + */ + if (max > 9) + max = 9; + + /* We "cheat" by converting the fractional part to integer by + * multiplying by a factor of 10 + */ + fracpart = _snp_round ((_snp_pow10 (max)) * (ufvalue - intpart)); + + if (fracpart >= _snp_pow10 (max)) + { + intpart++; + fracpart -= _snp_pow10 (max); + } + +#ifdef DEBUG_SNPRINTF + dprint (1, (debugfile, "fmtfp: %f =? %d.%d\n", fvalue, intpart, fracpart)); +#endif + + /* Convert integer part */ + do { + iconvert[iplace++] = + (caps? "0123456789ABCDEF":"0123456789abcdef")[intpart % 10]; + intpart = (intpart / 10); + } while(intpart && (iplace < 20)); + if (iplace == 20) iplace--; + iconvert[iplace] = 0; + + /* Convert fractional part */ + do { + fconvert[fplace++] = + (caps? "0123456789ABCDEF":"0123456789abcdef")[fracpart % 10]; + fracpart = (fracpart / 10); + } while(fracpart && (fplace < 20)); + if (fplace == 20) fplace--; + fconvert[fplace] = 0; + + /* -1 for decimal point, another -1 if we are printing a sign */ + padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0); + zpadlen = max - fplace; + if (zpadlen < 0) + zpadlen = 0; + if (padlen < 0) + padlen = 0; + if (flags & DP_F_MINUS) + padlen = -padlen; /* Left Justifty */ + + if ((flags & DP_F_ZERO) && (padlen > 0)) + { + if (signvalue) + { + total += dopr_outch (buffer, currlen, maxlen, signvalue); + --padlen; + signvalue = 0; + } + while (padlen > 0) + { + total += dopr_outch (buffer, currlen, maxlen, '0'); + --padlen; + } + } + while (padlen > 0) + { + total += dopr_outch (buffer, currlen, maxlen, ' '); + --padlen; + } + if (signvalue) + total += dopr_outch (buffer, currlen, maxlen, signvalue); + + while (iplace > 0) + total += dopr_outch (buffer, currlen, maxlen, iconvert[--iplace]); + + /* + * Decimal point. This should probably use locale to find the correct + * char to print out. + */ + if (max > 0) + { + total += dopr_outch (buffer, currlen, maxlen, '.'); + + while (fplace > 0) + total += dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]); + } + + while (zpadlen > 0) + { + total += dopr_outch (buffer, currlen, maxlen, '0'); + --zpadlen; + } + + while (padlen < 0) + { + total += dopr_outch (buffer, currlen, maxlen, ' '); + ++padlen; + } + + return total; +} + +static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c) +{ + if (*currlen + 1 < maxlen) + buffer[(*currlen)++] = c; + return 1; +} + +int xmpp_vsnprintf (char *str, size_t count, const char *fmt, va_list args) +{ + if (str != NULL) + str[0] = 0; + return dopr(str, count, fmt, args); +} +#endif /* !HAVE_VSNPRINTF */ + +#ifndef HAVE_SNPRINTF +/* VARARGS3 */ +int xmpp_snprintf (char *str,size_t count,const char *fmt,...) +{ + VA_LOCAL_DECL; + int total; + + VA_START (fmt); + total = xmpp_vsnprintf(str, count, fmt, ap); + VA_END; + return total; +} +#endif /* !HAVE_SNPRINTF */ diff --git a/source/snprintf.h b/source/snprintf.h new file mode 100644 index 0000000..1f005cc --- /dev/null +++ b/source/snprintf.h @@ -0,0 +1,34 @@ +/* + * Copyright Patrick Powell 1995 + * This code is based on code written by Patrick Powell (papowell@astart.com) + * It may be used for any purpose as long as this notice remains intact + * on all source code distributions + */ + +/** @file + * Compatibility wrappers for OSes lacking snprintf(3) and/or vsnprintf(3). + */ + +#ifndef __LIBSTROPHE_SNPRINTF_H__ +#define __LIBSTROPHE_SNPRINTF_H__ + +#include +#include + +#if defined(HAVE_SNPRINTF) || defined(HAVE_VSNPRINTF) +#include +#endif + +#ifdef HAVE_SNPRINTF +#define xmpp_snprintf snprintf +#else +int xmpp_snprintf(char *str, size_t count, const char *fmt, ...); +#endif + +#ifdef HAVE_VSNPRINTF +#define xmpp_vsnprintf vsnprintf +#else +int xmpp_vsnprintf(char *str, size_t count, const char *fmt, va_list arg); +#endif + +#endif /* __LIBSTROPHE_SNPRINTF_H__ */ diff --git a/source/sock.c b/source/sock.c new file mode 100644 index 0000000..49ef205 --- /dev/null +++ b/source/sock.c @@ -0,0 +1,201 @@ +/* sock.c +** strophe XMPP client library -- socket abstraction implementation +** +** Copyright (C) 2005-2009 Collecta, Inc. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This program is dual licensed under the MIT and GPLv3 licenses. +*/ + +/** @file + * Socket abstraction. + */ + +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +#include "sock.h" +#include "snprintf.h" + +void sock_initialize(void) +{ +#ifdef _WIN32 + WSADATA wsad; + WSAStartup(0x0101, &wsad); +#endif +} + +void sock_shutdown(void) +{ +#ifdef _WIN32 + WSACleanup(); +#endif +} + +int sock_error(void) +{ +#ifdef _WIN32 + return WSAGetLastError(); +#else + return errno; +#endif +} + +static int _in_progress(int error) +{ +#ifdef _WIN32 + return (error == WSAEWOULDBLOCK || error == WSAEINPROGRESS); +#else + return (error == EINPROGRESS); +#endif +} + +sock_t sock_connect(const char * const host, const unsigned short port) +{ + sock_t sock; + char service[6]; + struct addrinfo *res, *ainfo, hints; + int err; + + xmpp_snprintf(service, 6, "%u", port); + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; +#ifdef AI_ADDRCONFIG + hints.ai_flags = AI_ADDRCONFIG; +#endif /* AI_ADDRCONFIG */ + hints.ai_protocol = IPPROTO_TCP; + hints.ai_socktype = SOCK_STREAM; + + err = getaddrinfo(host, service, &hints, &res); + if (err != 0) + return -1; + + for (ainfo = res; ainfo != NULL; ainfo = ainfo->ai_next) { + sock = socket(ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol); + if (sock < 0) + continue; + + err = sock_set_nonblocking(sock); + if (err == 0) { + err = connect(sock, ainfo->ai_addr, ainfo->ai_addrlen); + if (err == 0 || _in_progress(sock_error())) + break; + } + + close(sock); + } + freeaddrinfo(res); + sock = ainfo == NULL ? -1 : sock; + + return sock; +} + +int sock_close(const sock_t sock) +{ +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif +} + +int sock_set_blocking(const sock_t sock) +{ +#ifdef _WIN32 + u_long block = 0; + return ioctlsocket(sock, FIONBIO, &block); +#else + int rc; + + rc = fcntl(sock, F_GETFL, NULL); + if (rc >= 0) { + rc = fcntl(sock, F_SETFL, rc & (~O_NONBLOCK)); + } + return rc; +#endif +} + +int sock_set_nonblocking(const sock_t sock) +{ +#ifdef _WIN32 + u_long nonblock = 1; + return ioctlsocket(sock, FIONBIO, &nonblock); +#else + int rc; + + rc = fcntl(sock, F_GETFL, NULL); + if (rc >= 0) { + rc = fcntl(sock, F_SETFL, rc | O_NONBLOCK); + } + return rc; +#endif +} + +int sock_read(const sock_t sock, void * const buff, const size_t len) +{ + return recv(sock, buff, len, 0); +} + +int sock_write(const sock_t sock, const void * const buff, const size_t len) +{ + return send(sock, buff, len, 0); +} + +int sock_is_recoverable(const int error) +{ +#ifdef _WIN32 + return (error == WSAEINTR || error == WSAEWOULDBLOCK || + error == WSAEINPROGRESS); +#else + return (error == EAGAIN || error == EINTR); +#endif +} + +int sock_connect_error(const sock_t sock) +{ + struct sockaddr sa; + socklen_t len; + char temp; + + memset(&sa, 0, sizeof(sa)); + sa.sa_family = AF_UNSPEC; + len = sizeof(sa); + + /* we don't actually care about the peer name, we're just checking if + * we're connected or not */ + if (getpeername(sock, &sa, &len) == 0) + { + return 0; + } + + /* it's possible that the error wasn't ENOTCONN, so if it wasn't, + * return that */ +#ifdef _WIN32 + if (sock_error() != WSAENOTCONN) return sock_error(); +#else + if (sock_error() != ENOTCONN) return sock_error(); +#endif + + /* load the correct error into errno through error slippage */ + recv(sock, &temp, 1, 0); + + return sock_error(); +} diff --git a/source/sock.h b/source/sock.h new file mode 100644 index 0000000..753d60c --- /dev/null +++ b/source/sock.h @@ -0,0 +1,44 @@ +/* sock.h +** strophe XMPP client library -- socket abstraction header +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * Socket abstraction API. + */ + +#ifndef __LIBSTROPHE_SOCK_H__ +#define __LIBSTROPHE_SOCK_H__ + +#include + +#ifndef _WIN32 +typedef int sock_t; +#else +#include +typedef SOCKET sock_t; +#endif + +void sock_initialize(void); +void sock_shutdown(void); + +int sock_error(void); + +sock_t sock_connect(const char * const host, const unsigned short port); +int sock_close(const sock_t sock); + +int sock_set_blocking(const sock_t sock); +int sock_set_nonblocking(const sock_t sock); +int sock_read(const sock_t sock, void * const buff, const size_t len); +int sock_write(const sock_t sock, const void * const buff, const size_t len); +int sock_is_recoverable(const int error); +/* checks for an error after connect, return 0 if connect successful */ +int sock_connect_error(const sock_t sock); + +#endif /* __LIBSTROPHE_SOCK_H__ */ diff --git a/source/stanza.c b/source/stanza.c new file mode 100644 index 0000000..9c42061 --- /dev/null +++ b/source/stanza.c @@ -0,0 +1,1079 @@ +/* stanza.c +** strophe XMPP client library -- XMPP stanza object and utilities +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * Stanza creation and manipulation. + */ + +/** @defgroup Stanza Stanza creation and manipulation + */ + +#include +#include + +#include "strophe.h" +#include "common.h" +#include "hash.h" + +#ifdef _WIN32 +#define inline __inline +#endif + +/** Create a stanza object. + * This function allocates and initializes and blank stanza object. + * The stanza will have a reference count of one, so the caller does not + * need to clone it. + * + * @param ctx a Strophe context object + * + * @return a stanza object + * + * @ingroup Stanza + */ +xmpp_stanza_t *xmpp_stanza_new(xmpp_ctx_t *ctx) +{ + xmpp_stanza_t *stanza; + + stanza = xmpp_alloc(ctx, sizeof(xmpp_stanza_t)); + if (stanza != NULL) { + stanza->ref = 1; + stanza->ctx = ctx; + stanza->type = XMPP_STANZA_UNKNOWN; + stanza->prev = NULL; + stanza->next = NULL; + stanza->children = NULL; + stanza->parent = NULL; + stanza->data = NULL; + stanza->attributes = NULL; + } + + return stanza; +} + +/** Clone a stanza object. + * This function increments the reference count of the stanza object. + * + * @param stanza a Strophe stanza object + * + * @return the stanza object with it's reference count incremented + * + * @ingroup Stanza + */ +xmpp_stanza_t *xmpp_stanza_clone(xmpp_stanza_t * const stanza) +{ + stanza->ref++; + + return stanza; +} + +/* + * Copy the attributes of stanza src into stanza dst. Return -1 on error. + */ +static int _stanza_copy_attributes(xmpp_stanza_t * dst, + const xmpp_stanza_t * const src) +{ + hash_iterator_t *iter = NULL; + const char *key; + void *val; + + dst->attributes = hash_new(src->ctx, 8, xmpp_free); + if (!dst->attributes) + return -1; + iter = hash_iter_new(src->attributes); + if (!iter) + goto error; + while ((key = hash_iter_next(iter))) { + val = xmpp_strdup(src->ctx, + (char *)hash_get(src->attributes, key)); + if (!val) + goto error; + + if (hash_add(dst->attributes, key, val)) { + xmpp_free(src->ctx, val); + goto error; + } + } + hash_iter_release(iter); + return 0; + +error: + if (iter != NULL) + hash_iter_release(iter); + hash_release(dst->attributes); + return -1; +} + +/** Copy a stanza and its children. + * This function copies a stanza along with all its children and returns + * the new stanza and children with a reference count of 1. The returned + * stanza will have no parent and no siblings. This function is useful + * for extracting a child stanza for inclusion in another tree. + * + * @param stanza a Strophe stanza object + * + * @return a new Strophe stanza object + * + * @ingroup Stanza + */ +xmpp_stanza_t *xmpp_stanza_copy(const xmpp_stanza_t * const stanza) +{ + xmpp_stanza_t *copy, *child, *copychild, *tail; + + copy = xmpp_stanza_new(stanza->ctx); + if (!copy) goto copy_error; + + copy->type = stanza->type; + + if (stanza->data) { + copy->data = xmpp_strdup(stanza->ctx, stanza->data); + if (!copy->data) goto copy_error; + } + + if (stanza->attributes) { + if (_stanza_copy_attributes(copy, stanza) == -1) + goto copy_error; + } + + tail = copy->children; + for (child = stanza->children; child; child = child->next) { + copychild = xmpp_stanza_copy(child); + if (!copychild) goto copy_error; + copychild->parent = copy; + + if (tail) { + copychild->prev = tail; + tail->next = copychild; + } else + copy->children = copychild; + tail = copychild; + } + + return copy; + +copy_error: + /* release all the hitherto allocated memory */ + if (copy) xmpp_stanza_release(copy); + return NULL; +} + +/** Release a stanza object and all of its children. + * This function releases a stanza object and potentially all of its + * children, which may cause the object(s) to be freed. + * + * @param stanza a Strophe stanza object + * + * @return TRUE if the object was freed and FALSE otherwise + * + * @ingroup Stanza + */ +int xmpp_stanza_release(xmpp_stanza_t * const stanza) +{ + int released = 0; + xmpp_stanza_t *child, *tchild; + + /* release stanza */ + if (stanza->ref > 1) + stanza->ref--; + else { + /* release all children */ + child = stanza->children; + while (child) { + tchild = child; + child = child->next; + xmpp_stanza_release(tchild); + } + + if (stanza->attributes) hash_release(stanza->attributes); + if (stanza->data) xmpp_free(stanza->ctx, stanza->data); + xmpp_free(stanza->ctx, stanza); + released = 1; + } + + return released; +} + +/** Determine if a stanza is a text node. + * + * @param stanza a Strophe stanza object + * + * @return TRUE if the stanza is a text node, FALSE otherwise + * + * @ingroup Stanza + */ +int xmpp_stanza_is_text(xmpp_stanza_t * const stanza) +{ + return (stanza && stanza->type == XMPP_STANZA_TEXT); +} + +/** Determine if a stanza is a tag node. + * + * @param stanza a Strophe stanza object + * + * @return TRUE if the stanza is a tag node, FALSE otherwise + * + * @ingroup Stanza + */ +int xmpp_stanza_is_tag(xmpp_stanza_t * const stanza) +{ + return (stanza && stanza->type == XMPP_STANZA_TAG); +} + +/* Escape a string with for use in a XML text node or attribute. Assumes that + * the input string is encoded in UTF-8. On sucess, returns a pointer to a + * buffer with the resulting data which must be xmpp_free()'d by the caller. + * On failure, returns NULL. + */ + +static char *_escape_xml(xmpp_ctx_t * const ctx, char *text) +{ + size_t len = 0; + char *src; + char *dst; + char *buf; + for (src = text; *src != '\0'; src++) { + switch (*src) { + case '<': /* "<" */ + case '>': /* ">" */ + len += 4; + break; + case '&': /* "&" */ + len += 5; + break; + case '"': + len += 6; /*""" */ + break; + default: + len++; + } + } + if ((buf = xmpp_alloc(ctx, (len+1) * sizeof(char))) == NULL) + return NULL; /* Error */ + dst = buf; + for (src = text; *src != '\0'; src++) { + switch (*src) { + case '<': + strcpy(dst, "<"); + dst += 4; + break; + case '>': + strcpy(dst, ">"); + dst += 4; + break; + case '&': + strcpy(dst, "&"); + dst += 5; + break; + case '"': + strcpy(dst, """); + dst += 6; + break; + default: + *dst = *src; + dst++; + } + } + *dst = '\0'; + return buf; +} + +/* small helper function */ +static inline void _render_update(int *written, const int length, + const int lastwrite, + size_t *left, char **ptr) +{ + *written += lastwrite; + + if (*written > length) { + *left = 0; + *ptr = NULL; + } else { + *left -= lastwrite; + *ptr = &(*ptr)[lastwrite]; + } +} + +/* always returns number of bytes written or that would have been + * written if the buffer was large enough + * return values < 0 indicate some error occured, + * and return values > buflen indicate buffer was not large enough + */ +static int _render_stanza_recursive(xmpp_stanza_t *stanza, + char * const buf, size_t const buflen) +{ + char *ptr = buf; + size_t left = buflen; + int ret, written; + xmpp_stanza_t *child; + hash_iterator_t *iter; + const char *key; + char *tmp; + + written = 0; + + if (stanza->type == XMPP_STANZA_UNKNOWN) return XMPP_EINVOP; + + if (stanza->type == XMPP_STANZA_TEXT) { + if (!stanza->data) return XMPP_EINVOP; + + tmp = _escape_xml(stanza->ctx, stanza->data); + if (tmp == NULL) return XMPP_EMEM; + ret = xmpp_snprintf(ptr, left, "%s", tmp); + xmpp_free(stanza->ctx, tmp); + if (ret < 0) return XMPP_EMEM; + _render_update(&written, buflen, ret, &left, &ptr); + } else { /* stanza->type == XMPP_STANZA_TAG */ + if (!stanza->data) return XMPP_EINVOP; + + /* write begining of tag and attributes */ + ret = xmpp_snprintf(ptr, left, "<%s", stanza->data); + if (ret < 0) return XMPP_EMEM; + _render_update(&written, buflen, ret, &left, &ptr); + + if (stanza->attributes && hash_num_keys(stanza->attributes) > 0) { + iter = hash_iter_new(stanza->attributes); + while ((key = hash_iter_next(iter))) { + if (!strcmp(key, "xmlns")) { + /* don't output namespace if parent stanza is the same */ + if (stanza->parent && + stanza->parent->attributes && + hash_get(stanza->parent->attributes, key) && + !strcmp((char*)hash_get(stanza->attributes, key), + (char*)hash_get(stanza->parent->attributes, key))) + continue; + /* or if this is the stream namespace */ + if (!stanza->parent && + !strcmp((char*)hash_get(stanza->attributes, key), + XMPP_NS_CLIENT)) + continue; + } + tmp = _escape_xml(stanza->ctx, + (char *)hash_get(stanza->attributes, key)); + if (tmp == NULL) return XMPP_EMEM; + ret = xmpp_snprintf(ptr, left, " %s=\"%s\"", key, tmp); + xmpp_free(stanza->ctx, tmp); + if (ret < 0) return XMPP_EMEM; + _render_update(&written, buflen, ret, &left, &ptr); + } + hash_iter_release(iter); + } + + if (!stanza->children) { + /* write end if singleton tag */ + ret = xmpp_snprintf(ptr, left, "/>"); + if (ret < 0) return XMPP_EMEM; + _render_update(&written, buflen, ret, &left, &ptr); + } else { + /* this stanza has child stanzas */ + + /* write end of start tag */ + ret = xmpp_snprintf(ptr, left, ">"); + if (ret < 0) return XMPP_EMEM; + _render_update(&written, buflen, ret, &left, &ptr); + + /* iterate and recurse over child stanzas */ + child = stanza->children; + while (child) { + ret = _render_stanza_recursive(child, ptr, left); + if (ret < 0) return ret; + + _render_update(&written, buflen, ret, &left, &ptr); + + child = child->next; + } + + /* write end tag */ + ret = xmpp_snprintf(ptr, left, "", stanza->data); + if (ret < 0) return XMPP_EMEM; + + _render_update(&written, buflen, ret, &left, &ptr); + } + } + + return written; +} + +/** Render a stanza object to text. + * This function renders a given stanza object, along with its + * children, to text. The text is returned in an allocated, + * null-terminated buffer. It starts by allocating a 1024 byte buffer + * and reallocates more memory if that is not large enough. + * + * @param stanza a Strophe stanza object + * @param buf a reference to a string pointer + * @param buflen a reference to a size_t + * + * @return 0 on success (XMPP_EOK), and a number less than 0 on failure + * (XMPP_EMEM, XMPP_EINVOP) + * + * @ingroup Stanza + */ +int xmpp_stanza_to_text(xmpp_stanza_t *stanza, + char ** const buf, + size_t * const buflen) +{ + char *buffer, *tmp; + size_t length; + int ret; + + /* allocate a default sized buffer and attempt to render */ + length = 1024; + buffer = xmpp_alloc(stanza->ctx, length); + if (!buffer) { + *buf = NULL; + *buflen = 0; + return XMPP_EMEM; + } + + ret = _render_stanza_recursive(stanza, buffer, length); + if (ret < 0) return ret; + + if (ret > length - 1) { + tmp = xmpp_realloc(stanza->ctx, buffer, ret + 1); + if (!tmp) { + xmpp_free(stanza->ctx, buffer); + *buf = NULL; + *buflen = 0; + return XMPP_EMEM; + } + length = ret + 1; + buffer = tmp; + + ret = _render_stanza_recursive(stanza, buffer, length); + if (ret > length - 1) return XMPP_EMEM; + } + + buffer[length - 1] = 0; + + *buf = buffer; + *buflen = ret; + + return XMPP_EOK; +} + +/** Set the name of a stanza. + * + * @param stanza a Strophe stanza object + * @param name a string with the name of the stanza + * + * @return XMPP_EOK on success, a number less than 0 on failure (XMPP_EMEM, + * XMPP_EINVOP) + * + * @ingroup Stanza + */ +int xmpp_stanza_set_name(xmpp_stanza_t *stanza, + const char * const name) +{ + if (stanza->type == XMPP_STANZA_TEXT) return XMPP_EINVOP; + + if (stanza->data) xmpp_free(stanza->ctx, stanza->data); + + stanza->type = XMPP_STANZA_TAG; + stanza->data = xmpp_strdup(stanza->ctx, name); + + return stanza->data == NULL ? XMPP_EMEM : XMPP_EOK; +} + +/** Get the stanza name. + * This function returns a pointer to the stanza name. If the caller needs + * to store this data, it must make a copy. + * + * @param stanza a Strophe stanza object + * + * @return a string with the stanza name + * + * @ingroup Stanza + */ +char *xmpp_stanza_get_name(xmpp_stanza_t * const stanza) +{ + if (stanza->type == XMPP_STANZA_TEXT) return NULL; + return stanza->data; +} + +/** Count the attributes in a stanza object. + * + * @param stanza a Strophe stanza object + * + * @return the number of attributes for the stanza object + * + * @ingroup Stanza + */ +int xmpp_stanza_get_attribute_count(xmpp_stanza_t * const stanza) +{ + if (stanza->attributes == NULL) { + return 0; + } + + return hash_num_keys(stanza->attributes); +} + +/** Get all attributes for a stanza object. + * This function populates the array with attributes from the stanza. The + * attr array will be in the format: attr[i] = attribute name, + * attr[i+1] = attribute value. + * + * @param stanza a Strophe stanza object + * @param attr the string array to populate + * @param attrlen the size of the array + * + * @return the number of slots used in the array, which will be 2 times the + * number of attributes in the stanza + * + * @ingroup Stanza + */ +int xmpp_stanza_get_attributes(xmpp_stanza_t * const stanza, + const char **attr, int attrlen) +{ + hash_iterator_t *iter; + const char *key; + int num = 0; + + if (stanza->attributes == NULL) { + return 0; + } + + iter = hash_iter_new(stanza->attributes); + while ((key = hash_iter_next(iter)) != NULL && attrlen) { + attr[num++] = key; + attrlen--; + if (attrlen == 0) { + hash_iter_release(iter); + return num; + } + attr[num++] = hash_get(stanza->attributes, key); + attrlen--; + if (attrlen == 0) { + hash_iter_release(iter); + return num; + } + } + + hash_iter_release(iter); + return num; +} + +/** Set an attribute for a stanza object. + * + * @param stanza a Strophe stanza object + * @param key a string with the attribute name + * @param value a string with the attribute value + * + * @return XMPP_EOK (0) on success or a number less than 0 on failure + * + * @ingroup Stanza + */ +int xmpp_stanza_set_attribute(xmpp_stanza_t * const stanza, + const char * const key, + const char * const value) +{ + char *val; + + if (stanza->type != XMPP_STANZA_TAG) return XMPP_EINVOP; + + if (!stanza->attributes) { + stanza->attributes = hash_new(stanza->ctx, 8, xmpp_free); + if (!stanza->attributes) return XMPP_EMEM; + } + + val = xmpp_strdup(stanza->ctx, value); + if (!val) { + hash_release(stanza->attributes); + return XMPP_EMEM; + } + + hash_add(stanza->attributes, key, val); + + return XMPP_EOK; +} + +/** Set the stanza namespace. + * This is a convenience function equivalent to calling: + * xmpp_stanza_set_attribute(stanza, "xmlns", ns); + * + * @param stanza a Strophe stanza object + * @param ns a string with the namespace + * + * @return XMPP_EOK (0) on success or a number less than 0 on failure + * + * @ingroup Stanza + */ +int xmpp_stanza_set_ns(xmpp_stanza_t * const stanza, + const char * const ns) +{ + return xmpp_stanza_set_attribute(stanza, "xmlns", ns); +} + +/** Add a child stanza to a stanza object. + * This function clones the child and appends it to the stanza object's + * children. + * + * @param stanza a Strophe stanza object + * @param child the child stanza object + * + * @return XMPP_EOK (0) on success or a number less than 0 on failure + * + * @ingroup Stanza + */ +int xmpp_stanza_add_child(xmpp_stanza_t *stanza, xmpp_stanza_t *child) +{ + xmpp_stanza_t *s; + + /* get a reference to the child */ + xmpp_stanza_clone(child); + + child->parent = stanza; + + if (!stanza->children) + stanza->children = child; + else { + s = stanza->children; + while (s->next) s = s->next; + s->next = child; + child->prev = s; + } + + return XMPP_EOK; +} + +/** Set the text data for a text stanza. + * This function copies the text given and sets the stanza object's text to + * it. Attempting to use this function on a stanza that has a name will + * fail with XMPP_EINVOP. This function takes the text as a null-terminated + * string. + * + * @param stanza a Strophe stanza object + * @param text a string with the text + * + * @return XMPP_EOK (0) on success or a number less than zero on failure + * + * @ingroup Stanza + */ +int xmpp_stanza_set_text(xmpp_stanza_t *stanza, + const char * const text) +{ + if (stanza->type == XMPP_STANZA_TAG) return XMPP_EINVOP; + + stanza->type = XMPP_STANZA_TEXT; + + if (stanza->data) xmpp_free(stanza->ctx, stanza->data); + stanza->data = xmpp_strdup(stanza->ctx, text); + + return stanza->data == NULL ? XMPP_EMEM : XMPP_EOK; +} + +/** Set the text data for a text stanza. + * This function copies the text given and sets teh stanza object's text to + * it. Attempting to use this function on a stanza that has a name will + * fail with XMPP_EINVOP. This function takes the text as buffer and a length + * as opposed to a null-terminated string. + * + * @param stanza a Strophe stanza object + * @param text a buffer with the text + * @param size the length of the text + * + * @return XMPP_EOK (0) on success and a number less than 0 on failure + * + * @ingroup Stanza + */ +int xmpp_stanza_set_text_with_size(xmpp_stanza_t *stanza, + const char * const text, + const size_t size) +{ + if (stanza->type == XMPP_STANZA_TAG) return XMPP_EINVOP; + + stanza->type = XMPP_STANZA_TEXT; + + if (stanza->data) xmpp_free(stanza->ctx, stanza->data); + stanza->data = xmpp_alloc(stanza->ctx, size + 1); + if (!stanza->data) return XMPP_EMEM; + + memcpy(stanza->data, text, size); + stanza->data[size] = 0; + + return XMPP_EOK; +} + +/** Get the 'id' attribute of the stanza object. + * This is a convenience function equivalent to: + * xmpp_stanza_get_attribute(stanza, "id"); + * + * @param stanza a Strophe stanza object + * + * @return a string with the 'id' attribute value + * + * @ingroup Stanza + */ +char *xmpp_stanza_get_id(xmpp_stanza_t * const stanza) +{ + return xmpp_stanza_get_attribute(stanza, "id"); +} + +/** Get the namespace attribute of the stanza object. + * This is a convenience function equivalent to: + * xmpp_stanza_get_attribute(stanza, "xmlns"); + * + * @param stanza a Strophe stanza object + * + * @return a string with the 'xmlns' attribute value + * + * @ingroup Stanza + */ +char *xmpp_stanza_get_ns(xmpp_stanza_t * const stanza) +{ + return xmpp_stanza_get_attribute(stanza, "xmlns"); +} + +/** Get the 'type' attribute of the stanza object. + * This is a convenience function equivalent to: + * xmpp_stanza_get_attribute(stanza, "type"); + * + * @param stanza a Strophe stanza object + * + * @return a string with the 'type' attribute value + * + * @ingroup Stanza + */ +char *xmpp_stanza_get_type(xmpp_stanza_t * const stanza) +{ + return xmpp_stanza_get_attribute(stanza, "type"); +} + +/** Get the 'to' attribute of the stanza object. + * This is a convenience function equivalent to: + * xmpp_stanza_get_attribute(stanza, "to"); + * + * @param stanza a Strophe stanza object + * + * @return a string with the 'to' attribute value + * + * @ingroup Stanza + */ +char *xmpp_stanza_get_to(xmpp_stanza_t * const stanza) +{ + return xmpp_stanza_get_attribute(stanza, "to"); +} + +/** Get the 'from' attribute of the stanza object. + * This is a convenience function equivalent to: + * xmpp_stanza_get_attribute(stanza, "from"); + * + * @param stanza a Strophe stanza object + * + * @return a string with the 'from' attribute value + * + * @ingroup Stanza + */ +char *xmpp_stanza_get_from(xmpp_stanza_t * const stanza) +{ + return xmpp_stanza_get_attribute(stanza, "from"); +} + +/** Get the first child of stanza with name. + * This function searches all the immediate children of stanza for a child + * stanza that matches the name. The first matching child is returned. + * + * @param stanza a Strophe stanza object + * @param name a string with the name to match + * + * @return the matching child stanza object or NULL if no match was found + * + * @ingroup Stanza + */ +xmpp_stanza_t *xmpp_stanza_get_child_by_name(xmpp_stanza_t * const stanza, + const char * const name) +{ + xmpp_stanza_t *child; + + for (child = stanza->children; child; child = child->next) { + if (child->type == XMPP_STANZA_TAG && + (strcmp(name, xmpp_stanza_get_name(child)) == 0)) + break; + } + + return child; +} + +/** Get the first child of a stanza with a given namespace. + * This function searches all the immediate children of a stanza for a child + * stanza that matches the namespace provided. The first matching child + * is returned. + * + * @param stanza a Strophe stanza object + * @param ns a string with the namespace to match + * + * @return the matching child stanza object or NULL if no match was found + * + * @ingroup Stanza + */ +xmpp_stanza_t *xmpp_stanza_get_child_by_ns(xmpp_stanza_t * const stanza, + const char * const ns) +{ + xmpp_stanza_t *child; + + for (child = stanza->children; child; child = child->next) { + if (xmpp_stanza_get_ns(child) && + strcmp(ns, xmpp_stanza_get_ns(child)) == 0) + break; + } + + return child; +} + +/** Get the list of children. + * This function returns the first child of the stanza object. The rest + * of the children can be obtained by calling xmpp_stanza_get_next() to + * iterate over the siblings. + * + * @param stanza a Strophe stanza object + * + * @return the first child stanza or NULL if there are no children + * + * @ingroup Stanza + */ +xmpp_stanza_t *xmpp_stanza_get_children(xmpp_stanza_t * const stanza) +{ + return stanza->children; +} + +/** Get the next sibling of a stanza. + * + * @param stanza a Strophe stanza object + * + * @return the next sibling stanza or NULL if there are no more siblings + * + * @ingroup Stanza + */ +xmpp_stanza_t *xmpp_stanza_get_next(xmpp_stanza_t * const stanza) +{ + return stanza->next; +} + +/** Get the text data for a text stanza. + * This function copies the text data from a stanza and returns the new + * allocated string. The caller is responsible for freeing this string + * with xmpp_free(). + * + * @param stanza a Strophe stanza object + * + * @return an allocated string with the text data + * + * @ingroup Stanza + */ +char *xmpp_stanza_get_text(xmpp_stanza_t * const stanza) +{ + size_t len, clen; + xmpp_stanza_t *child; + char *text; + + if (stanza->type == XMPP_STANZA_TEXT) { + if (stanza->data) + return xmpp_strdup(stanza->ctx, stanza->data); + else + return NULL; + } + + len = 0; + for (child = stanza->children; child; child = child->next) + if (child->type == XMPP_STANZA_TEXT) + len += strlen(child->data); + + if (len == 0) return NULL; + + text = (char *)xmpp_alloc(stanza->ctx, len + 1); + if (!text) return NULL; + + len = 0; + for (child = stanza->children; child; child = child->next) + if (child->type == XMPP_STANZA_TEXT) { + clen = strlen(child->data); + memcpy(&text[len], child->data, clen); + len += clen; + } + + text[len] = 0; + + return text; +} + +/** Get the text data pointer for a text stanza. + * This function copies returns the raw pointer to the text data in the + * stanza. This should only be used in very special cases where the + * caller needs to translate the datatype as this will save a double + * allocation. The caller should not hold onto this pointer, and is + * responsible for allocating a copy if it needs one. + * + * @param stanza a Strophe stanza object + * + * @return an string pointer to the data or NULL + * + * @ingroup Stanza + */ +char *xmpp_stanza_get_text_ptr(xmpp_stanza_t * const stanza) +{ + if (stanza->type == XMPP_STANZA_TEXT) + return stanza->data; + return NULL; +} + +/** Set the 'id' attribute of a stanza. + * + * This is a convenience function for: + * xmpp_stanza_set_attribute(stanza, 'id', id); + * + * @param stanza a Strophe stanza object + * @param id a string containing the 'id' value + * + * @return XMPP_EOK (0) on success or a number less than 0 on failure + * + * @ingroup Stanza + */ +int xmpp_stanza_set_id(xmpp_stanza_t * const stanza, + const char * const id) +{ + return xmpp_stanza_set_attribute(stanza, "id", id); +} + +/** Set the 'type' attribute of a stanza. + * This is a convenience function for: + * xmpp_stanza_set_attribute(stanza, 'type', type); + * + * @param stanza a Strophe stanza object + * @param type a string containing the 'type' value + * + * @return XMPP_EOK (0) on success or a number less than 0 on failure + * + * @ingroup Stanza + */ +int xmpp_stanza_set_type(xmpp_stanza_t * const stanza, + const char * const type) +{ + return xmpp_stanza_set_attribute(stanza, "type", type); +} + +/** Set the 'to' attribute of a stanza. + * + * This is a convenience function for: + * xmpp_stanza_set_attribute(stanza, 'to', to); + * + * @param stanza a Strophe stanza object + * @param to a string containing the 'to' value + * + * @return XMPP_EOK (0) on success or a number less than 0 on failure + * + * @ingroup Stanza + */ +int xmpp_stanza_set_to(xmpp_stanza_t * const stanza, + const char * const to) +{ + return xmpp_stanza_set_attribute(stanza, "to", to); +} + +/** Set the 'from' attribute of a stanza. + * + * This is a convenience function for: + * xmpp_stanza_set_attribute(stanza, 'from', from); + * + * @param stanza a Strophe stanza object + * @param from a string containing the 'from' value + * + * @return XMPP_EOK (0) on success or a number less than 0 on failure + * + * @ingroup Stanza + */ +int xmpp_stanza_set_from(xmpp_stanza_t * const stanza, + const char * const from) +{ + return xmpp_stanza_set_attribute(stanza, "from", from); +} + +/** Get an attribute from a stanza. + * This function returns a pointer to the attribute value. If the caller + * wishes to save this value it must make its own copy. + * + * @param stanza a Strophe stanza object + * @param name a string containing attribute name + * + * @return a string with the attribute value or NULL on an error + * + * @ingroup Stanza + */ +char *xmpp_stanza_get_attribute(xmpp_stanza_t * const stanza, + const char * const name) +{ + if (stanza->type != XMPP_STANZA_TAG) + return NULL; + + if (!stanza->attributes) + return NULL; + + return hash_get(stanza->attributes, name); +} + +/** Delete an attribute from a stanza. + * + * @param stanza a Strophe stanza object + * @param name a string containing attribute name + * + * @return XMPP_EOK (0) on success or a number less than 0 on failure + * + * @ingroup Stanza + */ +int xmpp_stanza_del_attribute(xmpp_stanza_t * const stanza, + const char * const name) +{ + if (stanza->type != XMPP_STANZA_TAG) + return -1; + + if (!stanza->attributes) + return -1; + + return hash_drop(stanza->attributes, name); +} + +/** Create a stanza object in reply to another. + * This function makes a copy of a stanza object with the attribute “to” set + * its original “from”. + * The stanza will have a reference count of one, so the caller does not + * need to clone it. + * + * @param stanza a Strophe stanza object + * + * @return a new Strophe stanza object + * + * @ingroup Stanza + */ +xmpp_stanza_t *xmpp_stanza_reply(xmpp_stanza_t * const stanza) +{ + xmpp_stanza_t *copy; + + copy = xmpp_stanza_new(stanza->ctx); + if (!copy) goto copy_error; + + copy->type = stanza->type; + + if (stanza->data) { + copy->data = xmpp_strdup(stanza->ctx, stanza->data); + if (!copy->data) goto copy_error; + } + + if (stanza->attributes) { + if (_stanza_copy_attributes(copy, stanza) == -1) + goto copy_error; + } + + xmpp_stanza_set_to(copy, xmpp_stanza_get_from(stanza)); + xmpp_stanza_del_attribute(copy, "from"); + + return copy; + +copy_error: + if (copy) xmpp_stanza_release(copy); + return NULL; +} diff --git a/source/strophe.h b/source/strophe.h new file mode 100644 index 0000000..91b0669 --- /dev/null +++ b/source/strophe.h @@ -0,0 +1,397 @@ +/* strophe.h +** strophe XMPP client library C API +** +** Copyright (C) 2005-2009 Collecta, Inc. +** +** This software is provided AS-IS with no warranty, either express or +** implied. +** +** This software is dual licensed under the MIT and GPLv3 licenses. +*/ + +/** @file + * Strophe public C API definitions. + */ + +#ifndef __LIBSTROPHE_STROPHE_H__ +#define __LIBSTROPHE_STROPHE_H__ + +#include /* size_t */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* namespace defines */ +/** @def XMPP_NS_CLIENT + * Namespace definition for 'jabber:client'. + */ +#define XMPP_NS_CLIENT "jabber:client" +/** @def XMPP_NS_COMPONENT + * Namespace definition for 'jabber:component:accept'. + */ +#define XMPP_NS_COMPONENT "jabber:component:accept" +/** @def XMPP_NS_STREAMS + * Namespace definition for 'http://etherx.jabber.org/streams'. + */ +#define XMPP_NS_STREAMS "http://etherx.jabber.org/streams" +/** @def XMPP_NS_STREAMS_IETF + * Namespace definition for 'urn:ietf:params:xml:ns:xmpp-streams'. + */ +#define XMPP_NS_STREAMS_IETF "urn:ietf:params:xml:ns:xmpp-streams" +/** @def XMPP_NS_TLS + * Namespace definition for 'url:ietf:params:xml:ns:xmpp-tls'. + */ +#define XMPP_NS_TLS "urn:ietf:params:xml:ns:xmpp-tls" +/** @def XMPP_NS_SASL + * Namespace definition for 'urn:ietf:params:xml:ns:xmpp-sasl'. + */ +#define XMPP_NS_SASL "urn:ietf:params:xml:ns:xmpp-sasl" +/** @def XMPP_NS_BIND + * Namespace definition for 'urn:ietf:params:xml:ns:xmpp-bind'. + */ +#define XMPP_NS_BIND "urn:ietf:params:xml:ns:xmpp-bind" +/** @def XMPP_NS_SESSION + * Namespace definition for 'urn:ietf:params:xml:ns:xmpp-session'. + */ +#define XMPP_NS_SESSION "urn:ietf:params:xml:ns:xmpp-session" +/** @def XMPP_NS_AUTH + * Namespace definition for 'jabber:iq:auth'. + */ +#define XMPP_NS_AUTH "jabber:iq:auth" +/** @def XMPP_NS_DISCO_INFO + * Namespace definition for 'http://jabber.org/protocol/disco#info'. + */ +#define XMPP_NS_DISCO_INFO "http://jabber.org/protocol/disco#info" +/** @def XMPP_NS_DISCO_ITEMS + * Namespace definition for 'http://jabber.org/protocol/disco#items'. + */ +#define XMPP_NS_DISCO_ITEMS "http://jabber.org/protocol/disco#items" +/** @def XMPP_NS_ROSTER + * Namespace definition for 'jabber:iq:roster'. + */ +#define XMPP_NS_ROSTER "jabber:iq:roster" + +/* error defines */ +/** @def XMPP_EOK + * Success error code. + */ +#define XMPP_EOK 0 +/** @def XMPP_EMEM + * Memory related failure error code. + * + * This is returned on allocation errors and signals that the host may + * be out of memory. + */ +#define XMPP_EMEM -1 +/** @def XMPP_EINVOP + * Invalid operation error code. + * + * This error code is returned when the operation was invalid and signals + * that the Strophe API is being used incorrectly. + */ +#define XMPP_EINVOP -2 +/** @def XMPP_EINT + * Internal failure error code. + */ +#define XMPP_EINT -3 + +/* initialization and shutdown */ +void xmpp_initialize(void); +void xmpp_shutdown(void); + +/* version */ +int xmpp_version_check(int major, int minor); + +/* run-time contexts */ + +/* user-replaceable memory allocator */ +typedef struct _xmpp_mem_t xmpp_mem_t; + +/* user-replaceable log object */ +typedef struct _xmpp_log_t xmpp_log_t; + +/* opaque run time context containing the above hooks */ +typedef struct _xmpp_ctx_t xmpp_ctx_t; + +xmpp_ctx_t *xmpp_ctx_new(const xmpp_mem_t * const mem, + const xmpp_log_t * const log); +void xmpp_ctx_free(xmpp_ctx_t * const ctx); + +struct _xmpp_mem_t { + void *(*alloc)(const size_t size, void * const userdata); + void (*free)(void *p, void * const userdata); + void *(*realloc)(void *p, const size_t size, void * const userdata); + void *userdata; +}; + +typedef enum { + XMPP_LEVEL_DEBUG, + XMPP_LEVEL_INFO, + XMPP_LEVEL_WARN, + XMPP_LEVEL_ERROR +} xmpp_log_level_t; + +typedef enum { + XMPP_UNKNOWN, + XMPP_CLIENT, + XMPP_COMPONENT +} xmpp_conn_type_t; + +typedef void (*xmpp_log_handler)(void * const userdata, + const xmpp_log_level_t level, + const char * const area, + const char * const msg); + +struct _xmpp_log_t { + xmpp_log_handler handler; + void *userdata; + /* mutex_t lock; */ +}; + +/* return a default logger filtering at a given level */ +xmpp_log_t *xmpp_get_default_logger(xmpp_log_level_t level); + +/* connection */ + +/* opaque connection object */ +typedef struct _xmpp_conn_t xmpp_conn_t; +typedef struct _xmpp_stanza_t xmpp_stanza_t; + +/* connect callback */ +typedef enum { + XMPP_CONN_CONNECT, + XMPP_CONN_DISCONNECT, + XMPP_CONN_FAIL +} xmpp_conn_event_t; + +typedef enum { + XMPP_SE_BAD_FORMAT, + XMPP_SE_BAD_NS_PREFIX, + XMPP_SE_CONFLICT, + XMPP_SE_CONN_TIMEOUT, + XMPP_SE_HOST_GONE, + XMPP_SE_HOST_UNKNOWN, + XMPP_SE_IMPROPER_ADDR, + XMPP_SE_INTERNAL_SERVER_ERROR, + XMPP_SE_INVALID_FROM, + XMPP_SE_INVALID_ID, + XMPP_SE_INVALID_NS, + XMPP_SE_INVALID_XML, + XMPP_SE_NOT_AUTHORIZED, + XMPP_SE_POLICY_VIOLATION, + XMPP_SE_REMOTE_CONN_FAILED, + XMPP_SE_RESOURCE_CONSTRAINT, + XMPP_SE_RESTRICTED_XML, + XMPP_SE_SEE_OTHER_HOST, + XMPP_SE_SYSTEM_SHUTDOWN, + XMPP_SE_UNDEFINED_CONDITION, + XMPP_SE_UNSUPPORTED_ENCODING, + XMPP_SE_UNSUPPORTED_STANZA_TYPE, + XMPP_SE_UNSUPPORTED_VERSION, + XMPP_SE_XML_NOT_WELL_FORMED +} xmpp_error_type_t; + +#define XMPP_CONN_FLAG_DISABLE_TLS 0x0001 +#define XMPP_CONN_FLAG_MANDATORY_TLS 0x0002 +#define XMPP_CONN_FLAG_LEGACY_SSL 0x0004 + +typedef struct { + xmpp_error_type_t type; + char *text; + xmpp_stanza_t *stanza; +} xmpp_stream_error_t; + +typedef void (*xmpp_conn_handler)(xmpp_conn_t * const conn, + const xmpp_conn_event_t event, + const int error, + xmpp_stream_error_t * const stream_error, + void * const userdata); + +xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t * const ctx); +xmpp_conn_t * xmpp_conn_clone(xmpp_conn_t * const conn); +int xmpp_conn_release(xmpp_conn_t * const conn); + +long xmpp_conn_get_flags(const xmpp_conn_t * const conn); +int xmpp_conn_set_flags(xmpp_conn_t * const conn, long flags); +const char *xmpp_conn_get_jid(const xmpp_conn_t * const conn); +const char *xmpp_conn_get_bound_jid(const xmpp_conn_t * const conn); +void xmpp_conn_set_jid(xmpp_conn_t * const conn, const char * const jid); +const char *xmpp_conn_get_pass(const xmpp_conn_t * const conn); +void xmpp_conn_set_pass(xmpp_conn_t * const conn, const char * const pass); +xmpp_ctx_t* xmpp_conn_get_context(xmpp_conn_t * const conn); +void xmpp_conn_disable_tls(xmpp_conn_t * const conn); +int xmpp_conn_is_secured(xmpp_conn_t * const conn); + +int xmpp_connect_client(xmpp_conn_t * const conn, + const char * const altdomain, + unsigned short altport, + xmpp_conn_handler callback, + void * const userdata); + +int xmpp_connect_component(xmpp_conn_t * const conn, const char * const server, + unsigned short port, xmpp_conn_handler callback, + void * const userdata); + +void xmpp_disconnect(xmpp_conn_t * const conn); + +void xmpp_send(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza); + +void xmpp_send_raw_string(xmpp_conn_t * const conn, + const char * const fmt, ...); +void xmpp_send_raw(xmpp_conn_t * const conn, + const char * const data, const size_t len); + + +/* handlers */ + +/* if the handle returns false it is removed */ +typedef int (*xmpp_timed_handler)(xmpp_conn_t * const conn, + void * const userdata); + +void xmpp_timed_handler_add(xmpp_conn_t * const conn, + xmpp_timed_handler handler, + const unsigned long period, + void * const userdata); +void xmpp_timed_handler_delete(xmpp_conn_t * const conn, + xmpp_timed_handler handler); + + +/* if the handler returns false it is removed */ +typedef int (*xmpp_handler)(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata); + +void xmpp_handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const ns, + const char * const name, + const char * const type, + void * const userdata); +void xmpp_handler_delete(xmpp_conn_t * const conn, + xmpp_handler handler); + +void xmpp_id_handler_add(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const id, + void * const userdata); +void xmpp_id_handler_delete(xmpp_conn_t * const conn, + xmpp_handler handler, + const char * const id); + +/* +void xmpp_register_stanza_handler(conn, stanza, xmlns, type, handler) +*/ + +/** stanzas **/ + +/** allocate an initialize a blank stanza */ +xmpp_stanza_t *xmpp_stanza_new(xmpp_ctx_t *ctx); + +/** clone a stanza */ +xmpp_stanza_t *xmpp_stanza_clone(xmpp_stanza_t * const stanza); + +/** copies a stanza and all children */ +xmpp_stanza_t * xmpp_stanza_copy(const xmpp_stanza_t * const stanza); + +/** free a stanza object and it's contents */ +int xmpp_stanza_release(xmpp_stanza_t * const stanza); + +/** free some blocks returned by other APIs, for example the + buffer you get from xmpp_stanza_to_text **/ +void xmpp_free(const xmpp_ctx_t * const ctx, void *p); + +int xmpp_stanza_is_text(xmpp_stanza_t * const stanza); +int xmpp_stanza_is_tag(xmpp_stanza_t * const stanza); + +/** marshall a stanza into text for transmission or display **/ +int xmpp_stanza_to_text(xmpp_stanza_t *stanza, + char ** const buf, size_t * const buflen); + +xmpp_stanza_t *xmpp_stanza_get_children(xmpp_stanza_t * const stanza); +xmpp_stanza_t *xmpp_stanza_get_child_by_name(xmpp_stanza_t * const stanza, + const char * const name); +xmpp_stanza_t *xmpp_stanza_get_child_by_ns(xmpp_stanza_t * const stanza, + const char * const ns); +xmpp_stanza_t *xmpp_stanza_get_next(xmpp_stanza_t * const stanza); +char *xmpp_stanza_get_attribute(xmpp_stanza_t * const stanza, + const char * const name); +int xmpp_stanza_get_attribute_count(xmpp_stanza_t * const stanza); +int xmpp_stanza_get_attributes(xmpp_stanza_t * const stanza, + const char **attr, int attrlen); +char * xmpp_stanza_get_ns(xmpp_stanza_t * const stanza); +/* concatenate all child text nodes. this function + * returns a string that must be freed by the caller */ + +char *xmpp_stanza_get_text(xmpp_stanza_t * const stanza); +char *xmpp_stanza_get_text_ptr(xmpp_stanza_t * const stanza); +char *xmpp_stanza_get_name(xmpp_stanza_t * const stanza); + +int xmpp_stanza_add_child(xmpp_stanza_t *stanza, xmpp_stanza_t *child); +int xmpp_stanza_set_ns(xmpp_stanza_t * const stanza, const char * const ns); +/* set_attribute adds/replaces attributes */ +int xmpp_stanza_set_attribute(xmpp_stanza_t * const stanza, + const char * const key, + const char * const value); +int xmpp_stanza_set_name(xmpp_stanza_t *stanza, + const char * const name); +int xmpp_stanza_set_text(xmpp_stanza_t *stanza, + const char * const text); +int xmpp_stanza_set_text_with_size(xmpp_stanza_t *stanza, + const char * const text, + const size_t size); + +int xmpp_stanza_del_attribute(xmpp_stanza_t * const stanza, + const char * const name); + +/* common stanza helpers */ +char *xmpp_stanza_get_type(xmpp_stanza_t * const stanza); +char *xmpp_stanza_get_id(xmpp_stanza_t * const stanza); +char *xmpp_stanza_get_to(xmpp_stanza_t * const stanza); +char *xmpp_stanza_get_from(xmpp_stanza_t * const stanza); +int xmpp_stanza_set_id(xmpp_stanza_t * const stanza, + const char * const id); +int xmpp_stanza_set_type(xmpp_stanza_t * const stanza, + const char * const type); +int xmpp_stanza_set_to(xmpp_stanza_t * const stanza, + const char * const to); +int xmpp_stanza_set_from(xmpp_stanza_t * const stanza, + const char * const from); + +/* allocate and initialize a stanza in reply to another */ +xmpp_stanza_t *xmpp_stanza_reply(xmpp_stanza_t * const stanza); + +/* stanza subclasses */ +/* unimplemented +void xmpp_message_new(); +void xmpp_message_get_body(); +void xmpp_message_set_body(); + +void xmpp_iq_new(); +void xmpp_presence_new(); +*/ + +/** jid **/ +/* these return new strings that must be xmpp_free()'d */ +char *xmpp_jid_new(xmpp_ctx_t *ctx, const char *node, + const char *domain, + const char *resource); +char *xmpp_jid_bare(xmpp_ctx_t *ctx, const char *jid); +char *xmpp_jid_node(xmpp_ctx_t *ctx, const char *jid); +char *xmpp_jid_domain(xmpp_ctx_t *ctx, const char *jid); +char *xmpp_jid_resource(xmpp_ctx_t *ctx, const char *jid); + +/** UUID **/ +char *xmpp_uuid_gen(xmpp_ctx_t *ctx); + +/** event loop **/ +void xmpp_run_once(xmpp_ctx_t *ctx, const unsigned long timeout); +void xmpp_run(xmpp_ctx_t *ctx); +void xmpp_stop(xmpp_ctx_t *ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* __LIBSTROPHE_STROPHE_H__ */ diff --git a/source/thread.c b/source/thread.c new file mode 100644 index 0000000..07fcf4a --- /dev/null +++ b/source/thread.c @@ -0,0 +1,116 @@ +/* thread.c +** strophe XMPP client library -- thread abstraction +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * Thread absraction. + */ + +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#endif + +#include "strophe.h" +#include "common.h" +#include "thread.h" + +struct _mutex_t { + const xmpp_ctx_t *ctx; + +#ifdef _WIN32 + HANDLE mutex; +#else + pthread_mutex_t *mutex; +#endif +}; + +/* mutex functions */ + +mutex_t *mutex_create(const xmpp_ctx_t * ctx) +{ + mutex_t *mutex; + + mutex = xmpp_alloc(ctx, sizeof(mutex_t)); + if (mutex) { + mutex->ctx = ctx; +#ifdef _WIN32 + mutex->mutex = CreateMutex(NULL, FALSE, NULL); +#else + mutex->mutex = xmpp_alloc(ctx, sizeof(pthread_mutex_t)); + if (mutex->mutex) + if (pthread_mutex_init(mutex->mutex, NULL) != 0) { + xmpp_free(ctx, mutex->mutex); + mutex->mutex = NULL; + } +#endif + if (!mutex->mutex) { + xmpp_free(ctx, mutex); + mutex = NULL; + } + } + + return mutex; +} + +int mutex_destroy(mutex_t *mutex) +{ + int ret = 1; + const xmpp_ctx_t *ctx; + +#ifdef _WIN32 + if (mutex->mutex) + ret = CloseHandle(mutex->mutex); +#else + if (mutex->mutex) + ret = pthread_mutex_destroy(mutex->mutex) == 0; +#endif + ctx = mutex->ctx; + xmpp_free(ctx, mutex); + + return ret; +} + +int mutex_lock(mutex_t *mutex) +{ + int ret; + +#ifdef _WIN32 + ret = WaitForSingleObject(mutex->mutex, INFINITE) == 0; +#else + ret = pthread_mutex_lock(mutex->mutex) == 0; +#endif + + return ret; +} + +int mutex_trylock(mutex_t *mutex) +{ + /* TODO */ + return 0; +} + +int mutex_unlock(mutex_t *mutex) +{ + int ret; + +#ifdef _WIN32 + ret = ReleaseMutex(mutex->mutex); +#else + ret = pthread_mutex_unlock(mutex->mutex) == 0; +#endif + + return ret; +} diff --git a/source/thread.h b/source/thread.h new file mode 100644 index 0000000..e16d5aa --- /dev/null +++ b/source/thread.h @@ -0,0 +1,40 @@ +/* thread.h +** strophe XMPP client library -- thread abstraction header +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * Threading abstraction API. + */ + +#ifndef __LIBSTROPHE_THREAD_H__ +#define __LIBSTROPHE_THREAD_H__ + +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +#include "strophe.h" + +typedef struct _mutex_t mutex_t; + +/* mutex functions */ + +mutex_t *mutex_create(const xmpp_ctx_t *ctx); +int mutex_destroy(mutex_t *mutex); +int mutex_lock(mutex_t *mutex); +int mutex_trylock(mutex_t *mutex); +int mutex_unlock(mutex_t *mutex); + +#endif /* __LIBSTROPHE_THREAD_H__ */ diff --git a/source/tls.h b/source/tls.h new file mode 100644 index 0000000..efe5ff7 --- /dev/null +++ b/source/tls.h @@ -0,0 +1,44 @@ +/* tls.h +** strophe XMPP client library -- TLS abstraction header +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * TLS abstraction API. + */ + +#ifndef __LIBSTROPHE_TLS_H__ +#define __LIBSTROPHE_TLS_H__ + +#include "common.h" +#include "sock.h" + +typedef struct _tls tls_t; + +void tls_initialize(void); +void tls_shutdown(void); + +tls_t *tls_new(xmpp_ctx_t *ctx, sock_t sock); +void tls_free(tls_t *tls); + +int tls_set_credentials(tls_t *tls, const char *cafilename); + +int tls_start(tls_t *tls); +int tls_stop(tls_t *tls); + +int tls_error(tls_t *tls); + +int tls_pending(tls_t *tls); +int tls_read(tls_t *tls, void * const buff, const size_t len); +int tls_write(tls_t *tls, const void * const buff, const size_t len); + +int tls_clear_pending_write(tls_t *tls); +int tls_is_recoverable(int error); + +#endif /* __LIBSTROPHE_TLS_H__ */ diff --git a/source/tls_dummy.c b/source/tls_dummy.c new file mode 100644 index 0000000..a2179e4 --- /dev/null +++ b/source/tls_dummy.c @@ -0,0 +1,91 @@ +/* tls_dummy.c +** strophe XMPP client library -- TLS abstraction dummy impl. +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * TLS dummy implementation. + */ + +#include "common.h" +#include "tls.h" +#include "sock.h" + +struct _tls { + xmpp_ctx_t *ctx; /* do we need this? */ + sock_t sock; + /* we don't implement anything */ +}; + +void tls_initialize(void) +{ + return; +} + +void tls_shutdown(void) +{ + return; +} + +tls_t *tls_new(xmpp_ctx_t *ctx, sock_t sock) +{ + /* always fail */ + return NULL; +} + +void tls_free(tls_t *tls) +{ + return; +} + +int tls_set_credentials(tls_t *tls, const char *cafilename) +{ + return -1; +} + +int tls_start(tls_t *tls) +{ + return -1; +} + +int tls_stop(tls_t *tls) +{ + return -1; +} + +int tls_error(tls_t *tls) +{ + /* todo: some kind of error polling/dump */ + return 0; +} + +int tls_pending(tls_t *tls) +{ + return 0; +} + +int tls_read(tls_t *tls, void * const buff, const size_t len) +{ + return -1; +} + +int tls_write(tls_t *tls, const void * const buff, const size_t len) +{ + return -1; +} + +int tls_clear_pending_write(tls_t *tls) +{ + return -1; +} + +int tls_is_recoverable(int error) +{ + return 0; +} diff --git a/source/tls_gnutls.c b/source/tls_gnutls.c new file mode 100644 index 0000000..e94a983 --- /dev/null +++ b/source/tls_gnutls.c @@ -0,0 +1,146 @@ +/* tls.c +** strophe XMPP client library -- TLS abstraction header +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * TLS implementation with GNUTLS + */ + +#include + +#include "common.h" +#include "tls.h" +#include "sock.h" + +/* FIXME this shouldn't be a constant string */ +#define CAFILE "/etc/ssl/certs/ca-certificates.crt" + +struct _tls { + xmpp_ctx_t *ctx; /* do we need this? */ + sock_t sock; + gnutls_session_t session; + gnutls_certificate_credentials_t cred; + int lasterror; +}; + +void tls_initialize(void) +{ + /* initialize the GNU TLS global state */ + gnutls_global_init(); + + /* TODO: wire in xmpp_ctx_t allocator somehow? + unfortunately in gnutls it's global, so we can + only do so much. */ +} + +void tls_shutdown(void) +{ + /* tear down the GNU TLS global state */ + gnutls_global_deinit(); +} + +tls_t *tls_new(xmpp_ctx_t *ctx, sock_t sock) +{ + tls_t *tls = xmpp_alloc(ctx, sizeof(tls_t)); + + if (tls) { + tls->ctx = ctx; + tls->sock = sock; + gnutls_init(&tls->session, GNUTLS_CLIENT); + + gnutls_certificate_allocate_credentials(&tls->cred); + tls_set_credentials(tls, CAFILE); + + gnutls_set_default_priority(tls->session); + + /* fixme: this may require setting a callback on win32? */ + gnutls_transport_set_int(tls->session, sock); + } + + return tls; +} + +void tls_free(tls_t *tls) +{ + gnutls_deinit(tls->session); + gnutls_certificate_free_credentials(tls->cred); + xmpp_free(tls->ctx, tls); +} + +int tls_set_credentials(tls_t *tls, const char *cafilename) +{ + int err; + + /* set trusted credentials -- takes a .pem filename */ + err = gnutls_certificate_set_x509_trust_file(tls->cred, + cafilename, GNUTLS_X509_FMT_PEM); + if (err >= 0) { + err = gnutls_credentials_set(tls->session, GNUTLS_CRD_CERTIFICATE, + tls->cred); + } + tls->lasterror = err; + + return err == GNUTLS_E_SUCCESS; +} + +int tls_start(tls_t *tls) +{ + sock_set_blocking(tls->sock); + tls->lasterror = gnutls_handshake(tls->session); + sock_set_nonblocking(tls->sock); + + return tls->lasterror == GNUTLS_E_SUCCESS; +} + +int tls_stop(tls_t *tls) +{ + tls->lasterror = gnutls_bye(tls->session, GNUTLS_SHUT_RDWR); + return tls->lasterror == GNUTLS_E_SUCCESS; +} + +int tls_error(tls_t *tls) +{ + return tls->lasterror; +} + +int tls_is_recoverable(int error) +{ + return !gnutls_error_is_fatal(error); +} + +int tls_pending(tls_t *tls) +{ + return gnutls_record_check_pending (tls->session); +} + +int tls_read(tls_t *tls, void * const buff, const size_t len) +{ + int ret; + + ret = gnutls_record_recv(tls->session, buff, len); + tls->lasterror = ret < 0 ? ret : 0; + + return ret; +} + +int tls_write(tls_t *tls, const void * const buff, const size_t len) +{ + int ret; + + ret = gnutls_record_send(tls->session, buff, len); + tls->lasterror = ret < 0 ? ret : 0; + + return ret; +} + +int tls_clear_pending_write(tls_t *tls) +{ + return 0; +} diff --git a/source/tls_openssl.c b/source/tls_openssl.c new file mode 100644 index 0000000..2351275 --- /dev/null +++ b/source/tls_openssl.c @@ -0,0 +1,182 @@ +/* tls_openssl.c +** strophe XMPP client library -- TLS abstraction openssl impl. +** +** Copyright (C) 2005-008 Collecta, Inc. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This program is dual licensed under the MIT and GPLv3 licenses. +*/ + +/** @file + * TLS implementation with OpenSSL. + */ + +#include + +#ifndef _WIN32 +#include +#else +#include +#endif + +#include + +#include "common.h" +#include "tls.h" +#include "sock.h" + +struct _tls { + xmpp_ctx_t *ctx; + sock_t sock; + SSL_CTX *ssl_ctx; + SSL *ssl; + int lasterror; +}; + +void tls_initialize(void) +{ + SSL_library_init(); + SSL_load_error_strings(); +} + +void tls_shutdown(void) +{ + return; +} + +int tls_error(tls_t *tls) +{ + return tls->lasterror; +} + +tls_t *tls_new(xmpp_ctx_t *ctx, sock_t sock) +{ + tls_t *tls = xmpp_alloc(ctx, sizeof(*tls)); + + if (tls) { + int ret; + memset(tls, 0, sizeof(*tls)); + + tls->ctx = ctx; + tls->sock = sock; + tls->ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + + SSL_CTX_set_client_cert_cb(tls->ssl_ctx, NULL); + SSL_CTX_set_mode (tls->ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_set_verify (tls->ssl_ctx, SSL_VERIFY_NONE, NULL); + + tls->ssl = SSL_new(tls->ssl_ctx); + + ret = SSL_set_fd(tls->ssl, sock); + if (ret <= 0) { + tls->lasterror = SSL_get_error(tls->ssl, ret); + tls_error(tls); + tls_free(tls); + tls = NULL; + } + } + + return tls; +} + +void tls_free(tls_t *tls) +{ + SSL_free(tls->ssl); + SSL_CTX_free(tls->ssl_ctx); + xmpp_free(tls->ctx, tls); + return; +} + +int tls_set_credentials(tls_t *tls, const char *cafilename) +{ + return -1; +} + +int tls_start(tls_t *tls) +{ + fd_set fds; + struct timeval tv; + int error; + int ret; + + /* Since we're non-blocking, loop the connect call until it + succeeds or fails */ + while (1) { + ret = SSL_connect(tls->ssl); + error = ret <= 0 ? SSL_get_error(tls->ssl, ret) : 0; + + if (ret == -1 && tls_is_recoverable(error)) { + /* wait for something to happen on the sock before looping back */ + tv.tv_sec = 0; + tv.tv_usec = 1000; + + FD_ZERO(&fds); + FD_SET(tls->sock, &fds); + + if (error == SSL_ERROR_WANT_READ) + select(tls->sock + 1, &fds, NULL, NULL, &tv); + else + select(tls->sock + 1, NULL, &fds, NULL, &tv); + continue; + } + + /* success or fatal error */ + break; + } + tls->lasterror = error; + + return ret <= 0 ? 0 : 1; + +} + +int tls_stop(tls_t *tls) +{ + int ret; + + ret = SSL_shutdown(tls->ssl); + tls->lasterror = ret <= 0 ? SSL_get_error(tls->ssl, ret) : 0; + + return ret <= 0 ? 0 : 1; +} + +int tls_is_recoverable(int error) +{ + return (error == SSL_ERROR_NONE || error == SSL_ERROR_WANT_READ + || error == SSL_ERROR_WANT_WRITE + || error == SSL_ERROR_WANT_CONNECT + || error == SSL_ERROR_WANT_ACCEPT); +} + +int tls_pending(tls_t *tls) +{ + return SSL_pending(tls->ssl); +} + +int tls_read(tls_t *tls, void * const buff, const size_t len) +{ + int ret = SSL_read(tls->ssl, buff, len); + + if (ret <= 0) { + tls->lasterror = SSL_get_error(tls->ssl, ret); + } + + return ret; +} + +int tls_write(tls_t *tls, const void * const buff, const size_t len) +{ + int ret = SSL_write(tls->ssl, buff, len); + + if (ret <= 0) { + tls->lasterror = SSL_get_error(tls->ssl, ret); + } + + return ret; +} + +int tls_clear_pending_write(tls_t *tls) +{ + return 0; +} diff --git a/source/tls_schannel.c b/source/tls_schannel.c new file mode 100644 index 0000000..a0f7007 --- /dev/null +++ b/source/tls_schannel.c @@ -0,0 +1,648 @@ +/* tls_schannel.c +** strophe XMPP client library -- TLS abstraction schannel impl. +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * TLS implementation with Win32 SChannel. + */ + +#include "common.h" +#include "tls.h" +#include "sock.h" + +#define SECURITY_WIN32 +#include +#include + +struct _tls { + xmpp_ctx_t *ctx; + sock_t sock; + + HANDLE hsec32; + SecurityFunctionTable *sft; + CredHandle hcred; + SecPkgInfo *spi; + int init; + + CtxtHandle hctxt; + SecPkgContext_StreamSizes spcss; + + unsigned char *recvbuffer; + unsigned int recvbuffermaxlen; + unsigned int recvbufferpos; + + unsigned char *readybuffer; + unsigned int readybufferpos; + unsigned int readybufferlen; + + unsigned char *sendbuffer; + unsigned int sendbuffermaxlen; + unsigned int sendbufferlen; + unsigned int sendbufferpos; + + SECURITY_STATUS lasterror; +}; + +void tls_initialize(void) +{ + return; +} + +void tls_shutdown(void) +{ + return; +} + +tls_t *tls_new(xmpp_ctx_t *ctx, sock_t sock) +{ + tls_t *tls; + PSecurityFunctionTable (*pInitSecurityInterface)(void); + SCHANNEL_CRED scred; + int ret; + ALG_ID algs[1]; + + SecPkgCred_SupportedAlgs spc_sa; + SecPkgCred_CipherStrengths spc_cs; + SecPkgCred_SupportedProtocols spc_sp; + + OSVERSIONINFO osvi; + + memset(&osvi, 0, sizeof(osvi)); + osvi.dwOSVersionInfoSize = sizeof(osvi); + + GetVersionEx(&osvi); + + /* no TLS support on win9x/me, despite what anyone says */ + if (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { + return NULL; + } + + tls = xmpp_alloc(ctx, sizeof(*tls)); + + if (!tls) { + return NULL; + } + + memset(tls, 0, sizeof(*tls)); + tls->ctx = ctx; + tls->sock = sock; + + if (!(tls->hsec32 = LoadLibrary ("secur32.dll"))) { + tls_free(tls); + return NULL; + } + + if (!(pInitSecurityInterface = + (void *)GetProcAddress(tls->hsec32, "InitSecurityInterfaceA"))) { + tls_free(tls); + return NULL; + } + + tls->sft = pInitSecurityInterface(); + + if (!tls->sft) { + tls_free(tls); + return NULL; + } + + ret = tls->sft->QuerySecurityPackageInfo(UNISP_NAME, &(tls->spi)); + + if (ret != SEC_E_OK) + { + tls_free(tls); + return NULL; + } + + xmpp_debug(ctx, "TLSS", "QuerySecurityPackageInfo() success"); + + memset(&scred, 0, sizeof(scred)); + scred.dwVersion = SCHANNEL_CRED_VERSION; + /*scred.grbitEnabledProtocols = SP_PROT_TLS1_CLIENT;*/ + /* Something down the line doesn't like AES, so force it to RC4 */ + algs[0] = CALG_RC4; + scred.cSupportedAlgs = 1; + scred.palgSupportedAlgs = algs; + + ret = tls->sft->AcquireCredentialsHandleA(NULL, UNISP_NAME, + SECPKG_CRED_OUTBOUND, NULL, &scred, NULL, NULL, &(tls->hcred), NULL); + + if (ret != SEC_E_OK) + { + tls_free(tls); + return NULL; + } + + xmpp_debug(ctx, "TLSS", "AcquireCredentialsHandle() success"); + + tls->init = 1; + + /* This bunch of queries should trip up wine until someone fixes + * schannel support there */ + ret = tls->sft->QueryCredentialsAttributes(&(tls->hcred), SECPKG_ATTR_SUPPORTED_ALGS, &spc_sa); + if (ret != SEC_E_OK) + { + tls_free(tls); + return NULL; + } + + ret = tls->sft->QueryCredentialsAttributes(&(tls->hcred), SECPKG_ATTR_CIPHER_STRENGTHS, &spc_cs); + if (ret != SEC_E_OK) + { + tls_free(tls); + return NULL; + } + + ret = tls->sft->QueryCredentialsAttributes(&(tls->hcred), SECPKG_ATTR_SUPPORTED_PROTOCOLS, &spc_sp); + if (ret != SEC_E_OK) + { + tls_free(tls); + return NULL; + } + + return tls; +} + +void tls_free(tls_t *tls) +{ + if (tls->recvbuffer) { + xmpp_free(tls->ctx, tls->recvbuffer); + } + + if (tls->readybuffer) { + xmpp_free(tls->ctx, tls->readybuffer); + } + + if (tls->sendbuffer) { + xmpp_free(tls->ctx, tls->sendbuffer); + } + + if (tls->init) { + tls->sft->FreeCredentialsHandle(&(tls->hcred)); + } + + tls->sft = NULL; + + if (tls->hsec32) { + FreeLibrary(tls->hsec32); + tls->hsec32 = NULL; + } + + xmpp_free(tls->ctx, tls); + return; +} + +int tls_set_credentials(tls_t *tls, const char *cafilename) +{ + return -1; +} + +int tls_start(tls_t *tls) +{ + ULONG ctxtreq = 0, ctxtattr = 0; + SecBufferDesc sbdin, sbdout; + SecBuffer sbin[2], sbout[1]; + SECURITY_STATUS ret; + int sent; + char *name = NULL; + + /* search the ctx's conns for our sock, and use the domain there as our + * name */ + { + xmpp_connlist_t *listentry = tls->ctx->connlist; + + while (listentry) { + xmpp_conn_t *conn = listentry->conn; + + if (conn->sock == tls->sock) { + name = strdup(conn->domain); + listentry = NULL; + } else { + listentry = listentry->next; + } + } + } + + ctxtreq = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT + | ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR + | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM + | ISC_REQ_MANUAL_CRED_VALIDATION | ISC_REQ_INTEGRITY; + + memset(&(sbout[0]), 0, sizeof(sbout[0])); + sbout[0].BufferType = SECBUFFER_TOKEN; + + memset(&sbdout, 0, sizeof(sbdout)); + sbdout.ulVersion = SECBUFFER_VERSION; + sbdout.cBuffers = 1; + sbdout.pBuffers = sbout; + + memset(&(sbin[0]), 0, sizeof(sbin[0])); + sbin[0].BufferType = SECBUFFER_TOKEN; + sbin[0].pvBuffer = xmpp_alloc(tls->ctx, tls->spi->cbMaxToken); + sbin[0].cbBuffer = tls->spi->cbMaxToken; + + memset(&(sbin[1]), 0, sizeof(sbin[1])); + sbin[1].BufferType = SECBUFFER_EMPTY; + + memset(&sbdin, 0, sizeof(sbdin)); + sbdin.ulVersion = SECBUFFER_VERSION; + sbdin.cBuffers = 2; + sbdin.pBuffers = sbin; + + ret = tls->sft->InitializeSecurityContextA(&(tls->hcred), NULL, name, ctxtreq, 0, 0, + NULL, 0, &(tls->hctxt), &sbdout, + &ctxtattr, NULL); + + while (ret == SEC_I_CONTINUE_NEEDED + || ret == SEC_I_INCOMPLETE_CREDENTIALS) { + unsigned char *p = sbin[0].pvBuffer; + int len = 0, inbytes = 0; + + if (sbdout.pBuffers[0].cbBuffer) { + unsigned char *writebuff = sbdout.pBuffers[0].pvBuffer; + unsigned int writelen = sbdout.pBuffers[0].cbBuffer; + + sent = sock_write(tls->sock, writebuff, writelen); + if (sent == -1) { + tls->lasterror = sock_error(); + } + else + { + writebuff += sent; + writelen -= sent; + } + tls->sft->FreeContextBuffer(sbdout.pBuffers[0].pvBuffer); + sbdout.pBuffers[0].pvBuffer = NULL; + sbdout.pBuffers[0].cbBuffer = 0; + } + + /* poll for a bit until the remote server stops sending data, ie it + * finishes sending the token */ + inbytes = 1; + { + fd_set fds; + struct timeval tv; + + tv.tv_sec = 2; + tv.tv_usec = 0; + + FD_ZERO(&fds); + FD_SET(tls->sock, &fds); + + select(tls->sock, &fds, NULL, NULL, &tv); + } + + while (inbytes > 0) { + fd_set fds; + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 1000; + + FD_ZERO(&fds); + FD_SET(tls->sock, &fds); + + select(tls->sock, &fds, NULL, NULL, &tv); + + inbytes = sock_read(tls->sock, p, tls->spi->cbMaxToken - len); + + if (inbytes > 0) { + len += inbytes; + p += inbytes; + } + else + { + tls->lasterror = sock_error(); + } + + } + + sbin[0].cbBuffer = len; + + ret = tls->sft->InitializeSecurityContextA(&(tls->hcred), &(tls->hctxt), name, + ctxtreq, 0, 0, &sbdin, 0, + &(tls->hctxt), &sbdout, + &ctxtattr, NULL); + } + + if (ret == SEC_E_OK) { + if (sbdout.pBuffers[0].cbBuffer) { + unsigned char *writebuff = sbdout.pBuffers[0].pvBuffer; + unsigned int writelen = sbdout.pBuffers[0].cbBuffer; + sent = sock_write(tls->sock, writebuff, writelen); + if (sent == -1) { + tls->lasterror = sock_error(); + } + else + { + writebuff += sent; + writelen -= sent; + } + tls->sft->FreeContextBuffer(sbdout.pBuffers[0].pvBuffer); + sbdout.pBuffers[0].pvBuffer = NULL; + sbdout.pBuffers[0].cbBuffer = 0; + } + } + + xmpp_free(tls->ctx, sbin[0].pvBuffer); + + if (ret != SEC_E_OK) { + tls->lasterror = ret; + return 0; + } + + tls->sft->QueryContextAttributes(&(tls->hctxt), SECPKG_ATTR_STREAM_SIZES, + &(tls->spcss)); + + tls->recvbuffermaxlen = tls->spcss.cbHeader + tls->spcss.cbMaximumMessage + + tls->spcss.cbTrailer; + tls->recvbuffer = xmpp_alloc(tls->ctx, tls->recvbuffermaxlen); + tls->recvbufferpos = 0; + + tls->sendbuffermaxlen = tls->spcss.cbHeader + tls->spcss.cbMaximumMessage + + tls->spcss.cbTrailer; + tls->sendbuffer = xmpp_alloc(tls->ctx, tls->sendbuffermaxlen); + tls->sendbufferpos = 0; + tls->sendbufferlen = 0; + + tls->readybuffer = xmpp_alloc(tls->ctx, tls->spcss.cbMaximumMessage); + tls->readybufferpos = 0; + tls->readybufferlen = 0; + + return 1; +} + +int tls_stop(tls_t *tls) +{ + return -1; +} + +int tls_error(tls_t *tls) +{ + return tls->lasterror; +} + +int tls_is_recoverable(int error) +{ + return (error == SEC_E_OK || error == SEC_E_INCOMPLETE_MESSAGE + || error == WSAEWOULDBLOCK || error == WSAEMSGSIZE + || error == WSAEINPROGRESS); +} + +int tls_pending(tls_t *tls) { + // There are 3 cases: + // - there is data in ready buffer, so it is by default pending + // - there is data in recv buffer. If it is not decrypted yet, means it + // was incomplete. This should be processed again only if there is data + // on the physical connection + // - there is data on the physical connection. This case is treated + // outside the tls (in event.c) + + if (tls->readybufferpos < tls->readybufferlen) { + return tls->readybufferlen - tls->readybufferpos; + } + + return 0; +} + +int tls_read(tls_t *tls, void * const buff, const size_t len) +{ + int bytes; + + /* first, if we've got some ready data, put that in the buffer */ + if (tls->readybufferpos < tls->readybufferlen) + { + if (len < tls->readybufferlen - tls->readybufferpos) { + bytes = len; + } else { + bytes = tls->readybufferlen - tls->readybufferpos; + } + + memcpy(buff, tls->readybuffer + tls->readybufferpos, bytes); + + if (len < tls->readybufferlen - tls->readybufferpos) { + tls->readybufferpos += bytes; + return bytes; + } else { + unsigned char *newbuff = buff; + int read; + tls->readybufferpos += bytes; + newbuff += bytes; + read = tls_read(tls, newbuff, len - bytes); + + if (read == -1) { + if (tls_is_recoverable(tls->lasterror)) { + return bytes; + } + + return -1; + } + + return bytes + read; + } + } + + /* next, top up our recv buffer */ + bytes = sock_read(tls->sock, tls->recvbuffer + tls->recvbufferpos, + tls->recvbuffermaxlen - tls->recvbufferpos); + + if (bytes == 0) { + tls->lasterror = WSAECONNRESET; + return -1; + } + + if (bytes == -1) { + if (!tls_is_recoverable(sock_error())) { + tls->lasterror = sock_error(); + return -1; + } + } + + if (bytes > 0) { + tls->recvbufferpos += bytes; + } + + /* next, try to decrypt the recv buffer */ + if (tls->recvbufferpos > 0) { + SecBufferDesc sbddec; + SecBuffer sbdec[4]; + int ret; + + memset(&sbddec, 0, sizeof(sbddec)); + sbddec.ulVersion = SECBUFFER_VERSION; + sbddec.cBuffers = 4; + sbddec.pBuffers = sbdec; + + memset(&(sbdec[0]), 0, sizeof(sbdec[0])); + sbdec[0].BufferType = SECBUFFER_DATA; + sbdec[0].pvBuffer = tls->recvbuffer; + sbdec[0].cbBuffer = tls->recvbufferpos; + + memset(&(sbdec[1]), 0, sizeof(sbdec[1])); + sbdec[1].BufferType = SECBUFFER_EMPTY; + + memset(&(sbdec[2]), 0, sizeof(sbdec[2])); + sbdec[2].BufferType = SECBUFFER_EMPTY; + + memset(&(sbdec[3]), 0, sizeof(sbdec[3])); + sbdec[3].BufferType = SECBUFFER_EMPTY; + + ret = tls->sft->DecryptMessage(&(tls->hctxt), &sbddec, 0, NULL); + + if (ret == SEC_E_OK) { + memcpy(tls->readybuffer, sbdec[1].pvBuffer, sbdec[1].cbBuffer); + tls->readybufferpos = 0; + tls->readybufferlen = sbdec[1].cbBuffer; + /* have we got some data left over? If so, copy it to the start + * of the recv buffer */ + if (sbdec[3].BufferType == SECBUFFER_EXTRA) { + memcpy(tls->recvbuffer, sbdec[3].pvBuffer, sbdec[3].cbBuffer); + tls->recvbufferpos = sbdec[3].cbBuffer; + } else { + tls->recvbufferpos = 0; + } + + return tls_read(tls, buff, len); + } else if (ret == SEC_E_INCOMPLETE_MESSAGE) { + tls->lasterror = SEC_E_INCOMPLETE_MESSAGE; + return -1; + } else if (ret == SEC_I_RENEGOTIATE) { + ret = tls_start(tls); + if (!ret) + { + return -1; + } + + /* fake an incomplete message so we're called again */ + tls->lasterror = SEC_E_INCOMPLETE_MESSAGE; + return -1; + } + + /* something bad happened, so we bail */ + tls->lasterror = ret; + + return -1; + } + + tls->lasterror = SEC_E_INCOMPLETE_MESSAGE; + + return -1; +} + +int tls_clear_pending_write(tls_t *tls) +{ + if (tls->sendbufferpos < tls->sendbufferlen) + { + int bytes; + + bytes = sock_write(tls->sock, tls->sendbuffer + tls->sendbufferpos, + tls->sendbufferlen - tls->sendbufferpos); + + if (bytes == -1) { + tls->lasterror = sock_error(); + return -1; + } else if (bytes > 0) { + tls->sendbufferpos += bytes; + } + + if (tls->sendbufferpos < tls->sendbufferlen) { + return 0; + } + } + + return 1; +} + +int tls_write(tls_t *tls, const void * const buff, const size_t len) +{ + SecBufferDesc sbdenc; + SecBuffer sbenc[4]; + unsigned char *sendbuffer; + const unsigned char *p = buff; + int sent = 0, ret, remain = len; + + ret = tls_clear_pending_write(tls); + if (ret <= 0) { + return ret; + } + + tls->sendbufferpos = 0; + tls->sendbufferlen = 0; + + memset(&sbdenc, 0, sizeof(sbdenc)); + sbdenc.ulVersion = SECBUFFER_VERSION; + sbdenc.cBuffers = 4; + sbdenc.pBuffers = sbenc; + + memset(&(sbenc[0]), 0, sizeof(sbenc[0])); + sbenc[0].BufferType = SECBUFFER_STREAM_HEADER; + + memset(&(sbenc[1]), 0, sizeof(sbenc[1])); + sbenc[1].BufferType = SECBUFFER_DATA; + + memset(&(sbenc[2]), 0, sizeof(sbenc[2])); + sbenc[2].BufferType = SECBUFFER_STREAM_TRAILER; + + memset(&(sbenc[3]), 0, sizeof(sbenc[3])); + sbenc[3].BufferType = SECBUFFER_EMPTY; + + sbenc[0].pvBuffer = tls->sendbuffer; + sbenc[0].cbBuffer = tls->spcss.cbHeader; + + sbenc[1].pvBuffer = tls->sendbuffer + tls->spcss.cbHeader; + + while (remain > 0) + { + if (remain > tls->spcss.cbMaximumMessage) { + sbenc[1].cbBuffer = tls->spcss.cbMaximumMessage; + } else { + sbenc[1].cbBuffer = remain; + } + + sbenc[2].pvBuffer = (unsigned char *)sbenc[1].pvBuffer + + sbenc[1].cbBuffer; + sbenc[2].cbBuffer = tls->spcss.cbTrailer; + + memcpy(sbenc[1].pvBuffer, p, sbenc[1].cbBuffer); + p += tls->spcss.cbMaximumMessage; + + tls->sendbufferlen = sbenc[0].cbBuffer + sbenc[1].cbBuffer + + sbenc[2].cbBuffer; + + ret = tls->sft->EncryptMessage(&(tls->hctxt), 0, &sbdenc, 0); + + if (ret != SEC_E_OK) { + tls->lasterror = ret; + return -1; + } + + tls->sendbufferpos = 0; + + ret = tls_clear_pending_write(tls); + + if (ret == -1 && !tls_is_recoverable(tls_error(tls))) { + return -1; + } + + if (remain > tls->spcss.cbMaximumMessage) { + sent += tls->spcss.cbMaximumMessage; + remain -= tls->spcss.cbMaximumMessage; + } else { + sent += remain; + remain = 0; + } + + if (ret == 0 || (ret == -1 && tls_is_recoverable(tls_error(tls)))) { + return sent; + } + + } + + return sent; +} diff --git a/source/util.c b/source/util.c new file mode 100644 index 0000000..d1d63a4 --- /dev/null +++ b/source/util.c @@ -0,0 +1,104 @@ +/* util.c +** strophe XMPP client library -- various utility functions +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * Utility functions. + */ + +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +#include "strophe.h" +#include "common.h" +#include "ostypes.h" +#include "util.h" + +/** implement our own strdup that uses the ctx allocator */ +/** Duplicate a string. + * This function replaces the standard strdup library call with a version + * that uses the Strophe context object's allocator. + * + * @param ctx a Strophe context object + * @param s a string + * + * @return a new allocates string with the same data as s or NULL on error + */ +char *xmpp_strdup(const xmpp_ctx_t * const ctx, const char * const s) +{ + size_t len; + char *copy; + + len = strlen(s); + copy = xmpp_alloc(ctx, len + 1); + if (!copy) { + xmpp_error(ctx, "xmpp", "failed to allocate required memory"); + return NULL; + } + + memcpy(copy, s, len + 1); + + return copy; +} + +/** Return an integer based time stamp. + * This function uses gettimeofday or timeGetTime (on Win32 platforms) to + * compute an integer based time stamp. This is used internally by the + * event loop and timed handlers. + * + * @return an integer time stamp + */ +uint64_t time_stamp(void) +{ +#ifdef _WIN32 + return timeGetTime(); +#else + struct timeval tv; + + gettimeofday(&tv, NULL); + + return (uint64_t)tv.tv_sec * 1000 + (uint64_t)tv.tv_usec / 1000; +#endif +} + +/** Get the time elapsed between two time stamps. + * This function returns the time elapsed between t1 and t2 by subtracting + * t1 from t2. If t2 happened before t1, the result will be negative. This + * function is used internally by the event loop and timed handlers. + * + * @param t1 first time stamp + * @param t2 second time stamp + * + * @return number of milliseconds between the stamps + */ +uint64_t time_elapsed(uint64_t t1, uint64_t t2) +{ + return (uint64_t)(t2 - t1); +} + +/** Disconnect the stream with a memory error. + * This is a convenience function used internally by various parts of + * the Strophe library for terminating the connection because of a + * memory error. + * + * @param conn a Strophe connection object + */ +void disconnect_mem_error(xmpp_conn_t * const conn) +{ + xmpp_error(conn->ctx, "xmpp", "Memory allocation error"); + xmpp_disconnect(conn); +} diff --git a/source/util.h b/source/util.h new file mode 100644 index 0000000..4bc00e1 --- /dev/null +++ b/source/util.h @@ -0,0 +1,25 @@ +/* util.h +** strophe XMPP client library -- various utility functions +** +** 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 and GPLv3 licenses. +*/ + +/** @file + * Internally used utility functions. + */ + +#ifndef __LIBSTROPHE_UTIL_H__ +#define __LIBSTROPHE_UTIL_H__ + +#include "ostypes.h" + +/* timing functions */ +uint64_t time_stamp(void); +uint64_t time_elapsed(uint64_t t1, uint64_t t2); + +#endif /* __LIBSTROPHE_UTIL_H__ */ diff --git a/source/uuid.c b/source/uuid.c new file mode 100644 index 0000000..18ed0f3 --- /dev/null +++ b/source/uuid.c @@ -0,0 +1,70 @@ +/* uuid.c + * strophe XMPP client library -- UUID generation + * + * Copyright (C) 2015 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. + */ + +/** @file + * Generation of UUID version 4 according to RFC4122. + */ + +#include "strophe.h" +#include "common.h" +#include "rand.h" + +/** @def XMPP_UUID_LEN + * UUID length in string representation excluding '\0'. + */ +#define XMPP_UUID_LEN 36 + +/** Generate UUID version 4 in pre-allocated buffer. + * + * @param uuid pre-allocated buffer of size (XMPP_UUID_LEN + 1) + */ +static void crypto_uuid_gen(xmpp_ctx_t *ctx, char *uuid) +{ + uint8_t buf[16]; + int i = 0; /* uuid iterator */ + int j = 0; /* buf iterator */ + + static const char hex[] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + xmpp_rand_bytes(ctx, buf, sizeof(buf)); + buf[8] &= 0x3f; + buf[8] |= 0x80; + buf[6] &= 0x0f; + buf[6] |= 0x40; + while (i < XMPP_UUID_LEN) { + if (i == 8 || i == 13 || i == 18 || i == 23) + uuid[i++] = '-'; + else { + uuid[i++] = hex[buf[j] >> 4]; + uuid[i++] = hex[buf[j] & 0x0f]; + ++j; + } + } + uuid[XMPP_UUID_LEN] = '\0'; +} + +/** Generate UUID version 4. + * This function allocates memory for the resulting string and must be freed + * with xmpp_free(). + * + * @return ASCIIZ string + */ +char *xmpp_uuid_gen(xmpp_ctx_t *ctx) +{ + char *uuid; + + uuid = xmpp_alloc(ctx, XMPP_UUID_LEN + 1); + if (uuid != NULL) { + crypto_uuid_gen(ctx, uuid); + } + return uuid; +}