]> git.mdlowis.com Git - archive/acc.git/commitdiff
Initial commit of libstrophe code which will be used as the basis for xmpp functionality
authorMike Lowis <mike.lowis@gentex.com>
Wed, 2 Mar 2016 16:05:10 +0000 (11:05 -0500)
committerMike Lowis <mike.lowis@gentex.com>
Wed, 2 Mar 2016 16:05:10 +0000 (11:05 -0500)
46 files changed:
LICENSE.md [moved from LICENSE with 100% similarity]
Makefile [new file with mode: 0644]
source/auth.c [new file with mode: 0644]
source/basic.c [new file with mode: 0644]
source/common.h [new file with mode: 0644]
source/conn.c [new file with mode: 0644]
source/ctx.c [new file with mode: 0644]
source/event.c [new file with mode: 0644]
source/handler.c [new file with mode: 0644]
source/hash.c [new file with mode: 0644]
source/hash.h [new file with mode: 0644]
source/jid.c [new file with mode: 0644]
source/md5.c [new file with mode: 0644]
source/md5.h [new file with mode: 0644]
source/oocontext.cpp [new file with mode: 0644]
source/oostanza.cpp [new file with mode: 0644]
source/ostypes.h [new file with mode: 0644]
source/parser.h [new file with mode: 0644]
source/parser_expat.c [new file with mode: 0644]
source/parser_libxml2.c [new file with mode: 0644]
source/rand.c [new file with mode: 0644]
source/rand.h [new file with mode: 0644]
source/resolver.c [new file with mode: 0644]
source/resolver.h [new file with mode: 0644]
source/sasl.c [new file with mode: 0644]
source/sasl.h [new file with mode: 0644]
source/scram.c [new file with mode: 0644]
source/scram.h [new file with mode: 0644]
source/sha1.c [new file with mode: 0644]
source/sha1.h [new file with mode: 0644]
source/snprintf.c [new file with mode: 0644]
source/snprintf.h [new file with mode: 0644]
source/sock.c [new file with mode: 0644]
source/sock.h [new file with mode: 0644]
source/stanza.c [new file with mode: 0644]
source/strophe.h [new file with mode: 0644]
source/thread.c [new file with mode: 0644]
source/thread.h [new file with mode: 0644]
source/tls.h [new file with mode: 0644]
source/tls_dummy.c [new file with mode: 0644]
source/tls_gnutls.c [new file with mode: 0644]
source/tls_openssl.c [new file with mode: 0644]
source/tls_schannel.c [new file with mode: 0644]
source/util.c [new file with mode: 0644]
source/util.h [new file with mode: 0644]
source/uuid.c [new file with mode: 0644]

similarity index 100%
rename from LICENSE
rename to LICENSE.md
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
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 (file)
index 0000000..ebf8973
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 &lt;stream:features/&gt; stanza.
+ */
+#define FEATURES_TIMEOUT 15000 /* 15 seconds */
+#endif
+#ifndef BIND_TIMEOUT
+/** @def BIND_TIMEOUT
+ *  Time to wait for &lt;bind/&gt; stanza reply.
+ */
+#define BIND_TIMEOUT 15000 /* 15 seconds */
+#endif
+#ifndef SESSION_TIMEOUT
+/** @def SESSION_TIMEOUT
+ *  Time to wait for &lt;session/&gt; 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 <success> or <failure> 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 <stream:error/> and
+ *  <stream:features/>.  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 <stream:features> */
+    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 <bind/> and <session/> 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, "<handshake xmlns='%s'>%s</handshake>",
+                             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 <handshake/> 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 (file)
index 0000000..652d860
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+
+#include <strophe.h>
+
+
+/* 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] <jid> <pass> [<host>]\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 (file)
index 0000000..207734d
--- /dev/null
@@ -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 <stdio.h>
+#include <stdarg.h>
+
+
+#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 <bind/> or <session/> 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 (file)
index 0000000..1765a49
--- /dev/null
@@ -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 <errno.h>
+#include <stdarg.h>
+#include <string.h>
+
+#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 </stream:stream>
+ *  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
+ *  </stream:stream> 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, "</stream:stream>");
+
+    /* 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 &lt;stream:stream&gt; 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, 
+                         "<?xml version=\"1.0\"?>"                     \
+                         "<stream:stream to=\"%s\" "                   \
+                         "xml:lang=\"%s\" "                            \
+                         "version=\"1.0\" "                            \
+                         "xmlns=\"%s\" "                               \
+                         "xmlns:stream=\"%s\">",
+                         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, "<stream:stream");
+    if (len < 0) return;
+
+    pos += len;
+    for (i = 0; attrs[i]; i += 2) {
+        attr = parser_attr_name(conn->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: </stream:stream>");
+    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 (file)
index 0000000..ec2b498
--- /dev/null
@@ -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 <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#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 (file)
index 0000000..ee10fd6
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef _WIN32
+#include <sys/select.h>
+#include <errno.h>
+#include <unistd.h>
+#define _sleep(x) usleep(x*1000)
+#else
+#include <winsock2.h>
+#define ETIMEDOUT WSAETIMEDOUT
+#define ECONNRESET WSAECONNRESET
+#define ECONNABORTED WSAECONNABORTED
+#define _sleep(x) Sleep(x)
+#endif
+
+#include <strophe.h>
+#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 (file)
index 0000000..c369435
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 &lt;iq/&gt; stanza of
+ *  type 'result' or 'error' with a specific id attribute.  This can
+ *  be used to handle responses to specific &lt;iq/&gt;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 &lt;iq/&gt; stanzas based on the &lt;query/&gt;
+ *  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 (file)
index 0000000..8318d60
--- /dev/null
@@ -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 <stdlib.h>
+#include <string.h>
+
+#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 (file)
index 0000000..55892d5
--- /dev/null
@@ -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 (file)
index 0000000..a28daab
--- /dev/null
@@ -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 <string.h>
+
+#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 (file)
index 0000000..4e890be
--- /dev/null
@@ -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 <string.h> /* 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<<s | 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<<s | 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 (file)
index 0000000..235d4fa
--- /dev/null
@@ -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 (file)
index 0000000..9274ad6
--- /dev/null
@@ -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 <stdlib.h>
+
+#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<Context *>(userdata)->alloc(size);
+}
+
+void *XMPP::Context::callRealloc(void *p, const size_t size,
+                                void * const userdata)
+{
+    return reinterpret_cast<Context *>(userdata)->realloc(p, size);
+}
+
+void XMPP::Context::callFree(void *p, void * const userdata)
+{
+    reinterpret_cast<Context *>(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<Context *>(userdata)->log(level, area, msg);
+}
+
diff --git a/source/oostanza.cpp b/source/oostanza.cpp
new file mode 100644 (file)
index 0000000..eea2adf
--- /dev/null
@@ -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<Context **>(p) = ctx;
+    p = reinterpret_cast<void *>(reinterpret_cast<char *>(p) + 
+                                sizeof(Context *));
+
+    return p;
+}
+
+void Stanza::operator delete(void *p)
+{
+    Context *ctx;
+
+    ctx = *reinterpret_cast<Context **>(reinterpret_cast<char *>(p) - 4);
+    ctx->free(reinterpret_cast<char *>(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 (file)
index 0000000..c3c8f47
--- /dev/null
@@ -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 <stddef.h>     /* 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 <stdint.h>
+#endif
+
+#endif /* __LIBSTROPHE_OSTYPES_H__ */
diff --git a/source/parser.h b/source/parser.h
new file mode 100644 (file)
index 0000000..063763c
--- /dev/null
@@ -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 (file)
index 0000000..3e1f963
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <expat.h>
+
+#include <strophe.h>
+#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 (file)
index 0000000..15656f9
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+#include <strophe.h>
+#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 (file)
index 0000000..473ac7a
--- /dev/null
@@ -0,0 +1,307 @@
+/* rand.c
+ * strophe XMPP client library -- pseudo-random number generator
+ *
+ * Copyright (C) 2014 Dmitry Podgorny <pasis.ua@gmail.com>
+ *
+ *  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 <assert.h>
+#include <string.h>
+#include <time.h>
+
+#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 (file)
index 0000000..0879ce9
--- /dev/null
@@ -0,0 +1,40 @@
+/* rand.h
+ * strophe XMPP client library -- pseudo-random number generator
+ *
+ * Copyright (C) 2014 Dmitry Podgorny <pasis.ua@gmail.com>
+ *
+ *  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 (file)
index 0000000..5faa171
--- /dev/null
@@ -0,0 +1,641 @@
+/* resolver.h
+ * strophe XMPP client library -- DNS resolver
+ *
+ * Copyright (C) 2015 Dmitry Podgorny <pasis.ua@gmail.com>
+ *
+ *  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 <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>             /* res_query */
+#endif /* _WIN32 */
+
+#include <string.h>             /* 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 <winsock2.h>
+#include <ws2tcpip.h>
+#include <windns.h>
+#include <Iphlpapi.h>
+
+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 (file)
index 0000000..f4f662f
--- /dev/null
@@ -0,0 +1,41 @@
+/* resolver.h
+ * strophe XMPP client library -- DNS resolver
+ *
+ * Copyright (C) 2015 Dmitry Podgorny <pasis.ua@gmail.com>
+ *
+ *  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 (file)
index 0000000..332947d
--- /dev/null
@@ -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 <stdlib.h>
+#include <string.h>
+
+#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 <stdio.h>
+
+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 (file)
index 0000000..bc7511a
--- /dev/null
@@ -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 (file)
index 0000000..97d0784
--- /dev/null
@@ -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 <pasis.ua@gmail.com>
+ *
+ *  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 <assert.h>
+#include <string.h>
+
+#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 (file)
index 0000000..e6b8cc8
--- /dev/null
@@ -0,0 +1,36 @@
+/* scram.h
+ * strophe XMPP client library -- SCRAM-SHA1 helper functions
+ *
+ * Copyright (C) 2013 Dmitry Podgorny <pasis.ua@gmail.com>
+ *
+ *  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 (file)
index 0000000..028d3a7
--- /dev/null
@@ -0,0 +1,253 @@
+/** @file
+ *  SHA-1 hash.
+ */
+
+/*
+SHA-1 in C
+By Steve Reid <sreid@sea-to-sky.net>
+100% Public Domain
+
+-----------------
+Modified 7/98 
+By James H. Brown <jbrown@burgoyne.com>
+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 <process.h> 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 <sreid@sea-to-sky.net>
+Still 100% public domain
+
+1- Removed #include <process.h> 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 <Saul.Kravitz@celera.com>
+Still 100% PD
+Modified to run on Compaq Alpha hardware.  
+
+-----------------
+Modified 07/2002
+By Ralph Giles <giles@artofcode.com>
+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 <string.h>
+
+#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 (file)
index 0000000..8dd7700
--- /dev/null
@@ -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 (file)
index 0000000..e018edb
--- /dev/null
@@ -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 <blong@fiction.net> 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 <blong@fiction.net> 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 <roessler@guug.de> 01/27/98 for mutt 0.89i
+ *    The PGP code was using unsigned hexadecimal formats. 
+ *    Unfortunately, unsigned formats simply didn't work.
+ *
+ *  Michael Elkins <me@cs.hmc.edu> 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 <rra@stanford.edu> 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 <stdarg.h>
+#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 <string.h>
+#include <ctype.h>
+#include <sys/types.h>
+
+#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 = "<NULL>";
+  }
+
+  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 (file)
index 0000000..1f005cc
--- /dev/null
@@ -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 <stddef.h>
+#include <stdarg.h>
+
+#if defined(HAVE_SNPRINTF) || defined(HAVE_VSNPRINTF)
+#include <stdio.h>
+#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 (file)
index 0000000..49ef205
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#ifdef _WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <Iphlpapi.h>
+#else
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <fcntl.h>
+#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 (file)
index 0000000..753d60c
--- /dev/null
@@ -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 <stdio.h>
+
+#ifndef _WIN32
+typedef int sock_t;
+#else
+#include <winsock2.h>
+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 (file)
index 0000000..9c42061
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+
+#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 '<':   /* "&lt;" */
+            case '>':   /* "&gt;" */
+                len += 4;
+                break;
+            case '&':   /* "&amp;" */
+                len += 5;
+                break;
+            case '"':
+                len += 6; /*"&quot;" */
+                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, "&lt;");
+                dst += 4;
+                break;
+            case '>':
+                strcpy(dst, "&gt;");
+                dst += 4;
+                break;
+            case '&':
+                strcpy(dst, "&amp;");
+                dst += 5;
+                break;
+            case '"':
+                strcpy(dst, "&quot;");
+                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, "</%s>", 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 (file)
index 0000000..91b0669
--- /dev/null
@@ -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 <stddef.h>     /* 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 (file)
index 0000000..07fcf4a
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#else
+#include <pthread.h>
+#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 (file)
index 0000000..e16d5aa
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <pthread.h>
+#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 (file)
index 0000000..efe5ff7
--- /dev/null
@@ -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 (file)
index 0000000..a2179e4
--- /dev/null
@@ -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 (file)
index 0000000..e94a983
--- /dev/null
@@ -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 <gnutls/gnutls.h>
+
+#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 (file)
index 0000000..2351275
--- /dev/null
@@ -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 <string.h>
+
+#ifndef _WIN32
+#include <sys/select.h>
+#else
+#include <winsock2.h>
+#endif
+
+#include <openssl/ssl.h>
+
+#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 (file)
index 0000000..a0f7007
--- /dev/null
@@ -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 <security.h>
+#include <schnlsp.h>
+
+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 (file)
index 0000000..d1d63a4
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <sys/time.h>
+#include <time.h>
+#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 (file)
index 0000000..4bc00e1
--- /dev/null
@@ -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 (file)
index 0000000..18ed0f3
--- /dev/null
@@ -0,0 +1,70 @@
+/* uuid.c
+ * strophe XMPP client library -- UUID generation
+ *
+ * Copyright (C) 2015 Dmitry Podgorny <pasis.ua@gmail.com>
+ *
+ *  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;
+}