diff --git a/.gitignore b/.gitignore index abceee2..b49b131 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ examples/basic examples/bot examples/component examples/roster +examples/server examples/uuid examples/vcard test_stamp diff --git a/Makefile.am b/Makefile.am index ac9b19b..854b4b1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -36,6 +36,7 @@ libstrophe_la_SOURCES = \ src/resolver.c \ src/sasl.c \ src/scram.c \ + src/server.c \ src/sha1.c \ src/snprintf.c \ src/sock.c \ @@ -99,6 +100,7 @@ noinst_PROGRAMS = \ examples/bot \ examples/component \ examples/roster \ + examples/server \ examples/uuid \ examples/vcard @@ -117,6 +119,9 @@ examples_component_LDADD = $(STROPHE_LIBS) examples_roster_SOURCES = examples/roster.c examples_roster_CFLAGS = $(STROPHE_FLAGS) examples_roster_LDADD = $(STROPHE_LIBS) +examples_server_SOURCES = examples/server.c +examples_server_CFLAGS = $(STROPHE_FLAGS) +examples_server_LDADD = $(STROPHE_LIBS) examples_uuid_SOURCES = examples/uuid.c examples_uuid_CFLAGS = $(STROPHE_FLAGS) examples_uuid_LDADD = $(STROPHE_LIBS) diff --git a/examples/server.c b/examples/server.c new file mode 100644 index 0000000..770b9da --- /dev/null +++ b/examples/server.c @@ -0,0 +1,83 @@ + +#include +#include +#include + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#endif + +int message_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, + void * const userdata) +{ + xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata; + xmpp_stanza_t *success; + + if (strcmp(xmpp_stanza_get_name(stanza), "auth") == 0) { + success = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(success, "success"); + xmpp_stanza_set_ns(success, XMPP_NS_SASL); + xmpp_send(conn, success); + xmpp_stanza_release(success); + } else + xmpp_disconnect(conn); + + return 1; +} + +void server_handler(xmpp_server_t * const srv, xmpp_conn_t * const conn, + const xmpp_server_event_t event, const int error, + void * const userdata) +{ + xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata; + + static char *attrs[] = { + "xmlns", XMPP_NS_CLIENT, "xmlns:stream", XMPP_NS_STREAMS, + "id", "0123456789", "from", "127.0.0.1", "version", "1.0", + "xml:lang", "en", + }; + + switch (event) { + case XMPP_SERVER_ACCEPT: + printf("Event XMPP_SERVER_ACCEPT\n"); + break; + case XMPP_SERVER_OPEN_STREAM: + printf("Event XMPP_SERVER_OPEN_STREAM\n"); + xmpp_handler_add(conn, message_handler, NULL, NULL, NULL, ctx); + xmpp_conn_open_stream(conn, attrs, ARRAY_SIZE(attrs)); + xmpp_send_raw_string(conn, + "" + "PLAIN" + "", XMPP_NS_SASL); + break; + case XMPP_SERVER_DISCONNECT: + printf("Event XMPP_SERVER_DISCONNECT\n"); + xmpp_stop(ctx); + break; + default: + printf("Unknown event\n"); + break; + } +} + +int main() +{ + xmpp_ctx_t *ctx; + xmpp_log_t *log; + xmpp_server_t *srv; + + xmpp_initialize(); + log = xmpp_get_default_logger(XMPP_LEVEL_DEBUG); + ctx = xmpp_ctx_new(NULL, log); + srv = xmpp_server_new(ctx); + xmpp_server_listen(srv, 0, server_handler, ctx); + + xmpp_run(ctx); + + xmpp_server_stop(srv); + xmpp_server_free(srv); + xmpp_ctx_free(ctx); + xmpp_shutdown(); + + return 0; +} diff --git a/jni/Android.mk b/jni/Android.mk index 693ff8f..4a92175 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -44,6 +44,7 @@ LOCAL_SRC_FILES := \ ../src/resolver.c \ ../src/sasl.c \ ../src/scram.c \ + ../src/server.c \ ../src/sha1.c \ ../src/snprintf.c \ ../src/sock.c \ diff --git a/src/common.h b/src/common.h index f1356b0..f027bb8 100644 --- a/src/common.h +++ b/src/common.h @@ -43,6 +43,11 @@ typedef struct _xmpp_connlist_t { struct _xmpp_connlist_t *next; } xmpp_connlist_t; +typedef struct _xmpp_serverlist_t { + xmpp_server_t *server; + struct _xmpp_serverlist_t *next; +} xmpp_serverlist_t; + struct _xmpp_ctx_t { const xmpp_mem_t *mem; const xmpp_log_t *log; @@ -50,6 +55,7 @@ struct _xmpp_ctx_t { xmpp_rand_t *rand; xmpp_loop_status_t loop_status; xmpp_connlist_t *connlist; + xmpp_serverlist_t *serverlist; unsigned long timeout; }; @@ -225,6 +231,24 @@ 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_STATE_STOPPED, + XMPP_STATE_LISTENING +} xmpp_server_state_t; + +struct _xmpp_server_t { + xmpp_ctx_t *ctx; + xmpp_server_state_t state; + + sock_t sock; + unsigned short port; + xmpp_server_handler callback; + void *userdata; + /* incomming connections list */ +}; + +void server_accept(xmpp_server_t * const srv); +void server_handle_open(xmpp_conn_t * const conn); typedef enum { XMPP_STANZA_UNKNOWN, diff --git a/src/conn.c b/src/conn.c index 8ccda34..0cbe4aa 100644 --- a/src/conn.c +++ b/src/conn.c @@ -625,14 +625,15 @@ int xmpp_conn_open_stream(xmpp_conn_t * const conn, char **attributes, { char *tag; - if (!conn->is_raw) + if (!conn->is_raw && conn->type != XMPP_INCOMING) return XMPP_EINVOP; tag = _conn_build_stream_tag(conn, attributes, attributes_len); if (!tag) return XMPP_EMEM; - conn_prepare_reset(conn, auth_handle_open_raw); + if (conn->type != XMPP_INCOMING) + conn_prepare_reset(conn, auth_handle_open_raw); xmpp_send_raw_string(conn, "%s", tag); xmpp_free(conn->ctx, tag); diff --git a/src/ctx.c b/src/ctx.c index cb19b29..38c0183 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -415,6 +415,7 @@ xmpp_ctx_t *xmpp_ctx_new(const xmpp_mem_t * const mem, ctx->log = log; ctx->connlist = NULL; + ctx->serverlist = NULL; ctx->loop_status = XMPP_LOOP_NOTSTARTED; ctx->rand = xmpp_rand_new(ctx); ctx->timeout = EVENT_LOOP_DEFAULT_TIMEOUT; diff --git a/src/event.c b/src/event.c index 068e432..7d582b2 100644 --- a/src/event.c +++ b/src/event.c @@ -67,7 +67,9 @@ void xmpp_run_once(xmpp_ctx_t *ctx, const unsigned long timeout) { xmpp_connlist_t *connitem; + xmpp_serverlist_t *serveritem; xmpp_conn_t *conn; + xmpp_server_t *srv; fd_set rfds, wfds; sock_t max = 0; int ret; @@ -206,6 +208,23 @@ void xmpp_run_once(xmpp_ctx_t *ctx, const unsigned long timeout) connitem = connitem->next; } + serveritem = ctx->serverlist; + while (serveritem) { + srv = serveritem->server; + + switch (srv->state) { + case XMPP_STATE_LISTENING: + FD_SET(srv->sock, &rfds); + if (srv->sock > max) max = srv->sock; + break; + case XMPP_STATE_STOPPED: + /* do nothing */ + default: + break; + } + serveritem = serveritem->next; + } + /* check for events */ if (max > 0) ret = select(max + 1, &rfds, &wfds, NULL, &tv); @@ -293,6 +312,24 @@ void xmpp_run_once(xmpp_ctx_t *ctx, const unsigned long timeout) connitem = connitem->next; } + serveritem = ctx->serverlist; + while (serveritem) { + srv = serveritem->server; + + switch (srv->state) { + case XMPP_STATE_LISTENING: + if (FD_ISSET(srv->sock, &rfds)) { + server_accept(srv); + } + break; + case XMPP_STATE_STOPPED: + /* do nothing */ + default: + break; + } + serveritem = serveritem->next; + } + /* fire any ready handlers */ handler_fire_timed(ctx); } diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000..0d4fc24 --- /dev/null +++ b/src/server.c @@ -0,0 +1,162 @@ +/* server.c + * strophe XMPP client library -- server object functions + * + * Copyright (C) 2016 Dmitry Podgorny + * + * This software is provided AS-IS with no warranty, either express + * or implied. + * + * This program is dual licensed under the MIT and GPLv3 licenses. + */ + +/** @file + * Server management. + */ + +/** @defgroup Server Server management + */ + +#include + +#include "common.h" +#include "strophe.h" + +static xmpp_server_t *conn2srv(xmpp_conn_t *conn); +static void _server_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_server_t *xmpp_server_new(xmpp_ctx_t * const ctx) +{ + xmpp_server_t *srv; + xmpp_serverlist_t *item; + + srv = xmpp_alloc(ctx, sizeof(*srv)); + item = xmpp_alloc(ctx, sizeof(*item)); + if (srv == NULL || item == NULL) { + xmpp_error(ctx, "xmpp", "Failed to allocate memory."); + xmpp_free(ctx, srv); + xmpp_free(ctx, item); + srv = NULL; + item = NULL; + } + + if (srv != NULL) { + memset(srv, 0, sizeof(*srv)); + srv->state = XMPP_STATE_STOPPED; + srv->ctx = ctx; + srv->sock = -1; + + item->server = srv; + item->next = ctx->serverlist; + ctx->serverlist = item; + } + return srv; +} + +void xmpp_server_free(xmpp_server_t * const srv) +{ + xmpp_ctx_t *ctx = srv->ctx; + xmpp_serverlist_t *item, *prev = NULL; + + item = ctx->serverlist; + while (item != NULL) { + if (item->server == srv) { + if (prev == NULL) ctx->serverlist = item->next; + if (prev != NULL) prev->next = item->next; + xmpp_free(ctx, item); + } + prev = item; + item = item->next; + } + + xmpp_free(ctx, srv); +} + +int xmpp_server_listen(xmpp_server_t * const srv, unsigned short port, + xmpp_server_handler callback, void * const userdata) +{ + int rc = 0; + + if (port == 0) port = XMPP_PORT_CLIENT; + srv->callback = callback; + srv->userdata = userdata; + srv->port = port; + srv->sock = sock_listen(port); + if (srv->sock >= 0) { + srv->state = XMPP_STATE_LISTENING; + xmpp_debug(srv->ctx, "xmpp", "Listening on port %u.", port); + } else + rc = XMPP_EINT; + + return rc; +} + +void xmpp_server_stop(xmpp_server_t * const srv) +{ + srv->state = XMPP_STATE_STOPPED; + sock_stop_listen(srv->sock); + xmpp_debug(srv->ctx, "xmpp", "Server stopped on %u.", srv->port); +} + +void server_accept(xmpp_server_t * const srv) +{ + xmpp_conn_t *conn; + sock_t fd; + + fd = sock_accept(srv->sock); + if (fd >= 0) { + xmpp_debug(srv->ctx, "xmpp", "New incoming connection on port %u.", + srv->port); + + conn = xmpp_conn_new(srv->ctx); + conn->type = XMPP_INCOMING; + conn->state = XMPP_STATE_CONNECTED; + conn->sock = fd; + conn->conn_handler = _server_conn_handler; + conn->userdata = (void *)srv; + conn->authenticated = 1; /* don't ignore handlers */ + conn_prepare_reset(conn, server_handle_open); + + srv->callback(srv, conn, XMPP_SERVER_ACCEPT, 0, srv->userdata); + } +} + +void server_handle_open(xmpp_conn_t * const conn) +{ + xmpp_server_t *srv = conn2srv(conn); + + /* XXX need to reset parser and re-open stream after authentication */ + + srv->callback(srv, conn, XMPP_SERVER_OPEN_STREAM, 0, srv->userdata); +} + +static xmpp_server_t *conn2srv(xmpp_conn_t *conn) +{ + xmpp_server_t *srv; + + /* XXX dirty hack */ + if (conn->ctx->serverlist != NULL) + srv = conn->ctx->serverlist->server; + else + srv = NULL; + + return srv; +} + +static void _server_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_server_t *srv = (xmpp_server_t *)userdata; + + if (status == XMPP_CONN_DISCONNECT) { + /* remove conn from the list */ + /* XXX when client sends first, server should close stream too */ + srv->callback(srv, conn, XMPP_SERVER_DISCONNECT, error, srv->userdata); + } +} diff --git a/src/sock.c b/src/sock.c index 78a3d1f..245a234 100644 --- a/src/sock.c +++ b/src/sock.c @@ -109,6 +109,43 @@ sock_t sock_connect(const char * const host, const unsigned short port) return sock; } +sock_t sock_listen(const unsigned short port) +{ + struct sockaddr_in addr; + sock_t sock; + int rc; + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + rc = bind(sock, (struct sockaddr *)&addr, sizeof(addr)); + if (rc == 0) { + rc = listen(sock, 100); + sock_set_nonblocking(sock); + } + if (rc != 0) close(sock); + + return rc == 0 ? sock : -1; +} + +int sock_stop_listen(const sock_t sock) +{ + return sock_close(sock); +} + +sock_t sock_accept(const sock_t sock) +{ + sock_t fd; + + fd = accept(sock, NULL, NULL); + + return fd; +} + int sock_set_keepalive(const sock_t sock, int timeout, int interval) { int ret; diff --git a/src/sock.h b/src/sock.h index e612bea..eadc6b8 100644 --- a/src/sock.h +++ b/src/sock.h @@ -32,6 +32,9 @@ int sock_error(void); sock_t sock_connect(const char * const host, const unsigned short port); int sock_close(const sock_t sock); +sock_t sock_listen(const unsigned short port); +int sock_stop_listen(const sock_t sock); +sock_t sock_accept(const sock_t sock); int sock_set_blocking(const sock_t sock); int sock_set_nonblocking(const sock_t sock); diff --git a/strophe.h b/strophe.h index 8ca53fd..ef41f6d 100644 --- a/strophe.h +++ b/strophe.h @@ -139,7 +139,8 @@ typedef enum { typedef enum { XMPP_UNKNOWN, XMPP_CLIENT, - XMPP_COMPONENT + XMPP_COMPONENT, + XMPP_INCOMING } xmpp_conn_type_t; typedef void (*xmpp_log_handler)(void * const userdata, @@ -211,7 +212,7 @@ typedef struct { xmpp_stanza_t *stanza; } xmpp_stream_error_t; -typedef void (*xmpp_conn_handler)(xmpp_conn_t * const conn, +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, @@ -263,6 +264,30 @@ void xmpp_send_raw_string(xmpp_conn_t * const conn, void xmpp_send_raw(xmpp_conn_t * const conn, const char * const data, const size_t len); +/* server */ + +/* opaque server object */ +typedef struct _xmpp_server_t xmpp_server_t; + +/* server callback */ +typedef enum { + XMPP_SERVER_ACCEPT, + XMPP_SERVER_OPEN_STREAM, + XMPP_SERVER_DISCONNECT, + XMPP_SERVER_FAIL +} xmpp_server_event_t; + +typedef void (*xmpp_server_handler)(xmpp_server_t * const srv, + xmpp_conn_t * const conn, + const xmpp_server_event_t event, + const int error, + void * const userdata); + +xmpp_server_t *xmpp_server_new(xmpp_ctx_t * const ctx); +void xmpp_server_free(xmpp_server_t * const srv); +int xmpp_server_listen(xmpp_server_t * const srv, unsigned short port, + xmpp_server_handler callback, void * const userdata); +void xmpp_server_stop(xmpp_server_t * const srv); /* handlers */