2
0
mirror of https://github.com/msantos/xmppipe synced 2024-11-16 00:12:59 +00:00
xmppipe/src/xmppipe.c

1210 lines
34 KiB
C
Raw Normal View History

/* Copyright (c) 2015-2017, Michael Santos <michael.santos@gmail.com>
2015-05-11 19:19:35 +00:00
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "xmppipe.h"
#include <sys/select.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <resolv.h>
2015-05-11 19:19:35 +00:00
extern char *__progname;
static void usage(xmppipe_state_t *xp);
void handle_connection(xmpp_conn_t * const, const xmpp_conn_event_t, const int,
xmpp_stream_error_t * const, void * const userdata);
int handle_disco_items(xmpp_conn_t * const, xmpp_stanza_t * const,
void * const);
int handle_disco_info(xmpp_conn_t * const, xmpp_stanza_t * const,
void * const);
int handle_version(xmpp_conn_t * const, xmpp_stanza_t * const, void * const);
int handle_message(xmpp_conn_t * const, xmpp_stanza_t * const, void * const);
int handle_presence(xmpp_conn_t * const, xmpp_stanza_t * const, void * const);
int handle_presence_error(xmpp_conn_t * const, xmpp_stanza_t * const,
void * const);
int handle_ping_reply(xmpp_conn_t * const, xmpp_stanza_t * const, void * const);
int handle_sm_request(xmpp_conn_t * const, xmpp_stanza_t * const, void * const);
int handle_sm_enabled(xmpp_conn_t * const, xmpp_stanza_t * const, void * const);
int handle_sm_ack(xmpp_conn_t * const, xmpp_stanza_t * const, void * const);
int handle_null(xmpp_conn_t * const, xmpp_stanza_t * const, void * const);
2015-05-11 19:19:35 +00:00
int xmppipe_connect_init(xmppipe_state_t *);
int xmppipe_stream_init(xmppipe_state_t *);
2015-05-11 19:19:35 +00:00
int xmppipe_muc_init(xmppipe_state_t *);
int xmppipe_presence_init(xmppipe_state_t *);
void event_loop(xmppipe_state_t *);
int handle_stdin(xmppipe_state_t *, int, char *, size_t);
void xmppipe_stream_close(xmppipe_state_t *);
2015-05-11 19:19:35 +00:00
2015-05-12 18:09:14 +00:00
void xmppipe_muc_join(xmppipe_state_t *);
void xmppipe_muc_unlock(xmppipe_state_t *);
void xmppipe_muc_subject(xmppipe_state_t *, char *);
void xmppipe_send_message(xmppipe_state_t *, char *, char *, char *, size_t);
void xmppipe_send(xmppipe_state_t *, xmpp_stanza_t *const);
2015-05-11 19:19:35 +00:00
void xmppipe_ping(xmppipe_state_t *);
int
main(int argc, char **argv)
{
xmppipe_state_t *state = NULL;
xmpp_log_t *log = NULL;
char *jid = NULL;
char *pass = NULL;
2015-05-16 13:54:35 +00:00
char *addr = NULL;
u_int16_t port = 0;
2015-05-11 19:19:35 +00:00
int ch = 0;
state = xmppipe_calloc(1, sizeof(xmppipe_state_t));
state->status = XMPPIPE_S_CONNECTING;
state->bufsz = 2049;
2015-05-11 19:19:35 +00:00
state->poll = 10;
state->keepalive = 60 * 1000;
2015-05-16 15:10:31 +00:00
state->keepalive_limit = 3;
state->sm_request_interval = 1;
state->sm_fc = 15;
state->sm_unacked = 5;
2015-05-11 19:19:35 +00:00
jid = xmppipe_getenv("XMPPIPE_USERNAME");
pass = xmppipe_getenv("XMPPIPE_PASSWORD");
while ( (ch = getopt(argc, argv, "a:b:c:dDehI:k:K:o:P:p:r:sS:u:U:vx")) != -1) {
2015-05-11 19:19:35 +00:00
switch (ch) {
case 'u':
/* username/jid */
free(jid);
2015-05-11 19:19:35 +00:00
jid = xmppipe_strdup(optarg);
break;
case 'p':
/* password */
free(pass);
2015-05-11 19:19:35 +00:00
pass = xmppipe_strdup(optarg);
break;
case 'o':
/* output/muc */
free(state->room);
2015-05-11 19:19:35 +00:00
state->room = xmppipe_strdup(optarg);
break;
2015-05-16 13:54:35 +00:00
case 'a': {
/* address:port */
char *p = NULL;
free(addr);
addr = xmppipe_strdup(optarg);
p = strchr(addr, ':');
if (p) {
*p++ = '\0';
port = (u_int16_t)atoi(p);
}
2015-05-16 13:54:35 +00:00
}
break;
2015-05-11 19:19:35 +00:00
case 'r':
free(state->resource);
2015-05-11 19:19:35 +00:00
state->resource = xmppipe_strdup(optarg);
break;
case 'S':
free(state->subject);
2015-05-11 19:19:35 +00:00
state->subject = xmppipe_strdup(optarg);
break;
case 'v':
state->verbose++;
break;
case 'x':
state->encode = 1;
break;
2015-05-11 19:19:35 +00:00
case 'b':
/* read buffer size */
state->bufsz = (size_t)atoi(optarg);
break;
case 'c':
/* XEP-0198: stream management flow control */
state->sm_fc = (u_int32_t)atoi(optarg);
break;
case 'I':
/* XEP-0198: stream management request interval */
state->sm_request_interval = (u_int32_t)atoi(optarg);
break;
2015-05-11 19:19:35 +00:00
case 'k':
/* XEP-0199: XMPP ping keepalives */
2015-05-11 19:19:35 +00:00
state->keepalive = (u_int32_t)atoi(optarg) * 1000;
break;
case 'K':
/* XEP-0199: number of keepalive without a reply */
state->keepalive_limit = (u_int32_t)atoi(optarg);
break;
2015-05-11 19:19:35 +00:00
case 'P':
/* poll delay */
state->poll = (u_int32_t)atoi(optarg);
break;
case 'U':
/* XEP-0198: stream management unacked requests */
state->sm_unacked = (u_int32_t)atoi(optarg);
break;
2015-05-11 19:19:35 +00:00
case 'd':
state->opt |= XMPPIPE_OPT_DISCARD;
break;
case 'D':
state->opt |= XMPPIPE_OPT_DISCARD;
state->opt |= XMPPIPE_OPT_DISCARD_TO_STDOUT;
break;
case 'e':
state->opt |= XMPPIPE_OPT_EOF;
break;
case 's':
state->opt |= XMPPIPE_OPT_SIGPIPE;
break;
case 'h':
default:
usage(state);
}
}
2015-07-21 13:44:08 +00:00
if (jid == NULL)
2015-05-11 19:19:35 +00:00
usage(state);
if (state->bufsz < 3 || state->bufsz >= 0xffff
|| (state->encode && BASE64_LENGTH(state->bufsz) + 1 > 0xffff))
2015-05-11 19:19:35 +00:00
usage(state);
2015-05-19 12:03:02 +00:00
if (state->keepalive_limit < 1)
2015-05-11 19:19:35 +00:00
usage(state);
state->server = xmppipe_servername(jid);
2015-07-21 13:44:08 +00:00
if (state->room == NULL)
2015-05-11 19:19:35 +00:00
state->room = xmppipe_roomname("stdout");
2015-07-21 13:44:08 +00:00
if (state->resource == NULL)
2015-05-11 19:19:35 +00:00
state->resource = xmppipe_strdup("xmppipe");
if (strchr(state->room, '@')) {
state->out = xmppipe_strdup(state->room);
state->mucjid = xmppipe_mucjid(state->out, state->resource);
}
if (xmppipe_fmt_init() < 0)
errx(EXIT_FAILURE, "xmppipe_fmt_init");
2015-05-11 19:19:35 +00:00
xmpp_initialize();
log = xmpp_get_default_logger(XMPP_LEVEL_DEBUG);
2015-05-12 18:09:14 +00:00
state->ctx = xmpp_ctx_new(NULL, (state->verbose > 1 ? log : NULL));
2015-07-21 13:44:08 +00:00
if (state->ctx == NULL)
errx(EXIT_FAILURE, "could not allocate context");
2015-05-11 19:19:35 +00:00
state->conn = xmpp_conn_new(state->ctx);
2015-07-21 13:44:08 +00:00
if (state->conn == NULL)
errx(EXIT_FAILURE, "could not allocate connection");
2015-05-11 19:19:35 +00:00
xmpp_conn_set_jid(state->conn, jid);
xmpp_conn_set_pass(state->conn, pass);
2015-05-11 19:19:35 +00:00
if (xmpp_connect_client(state->conn, addr, port, handle_connection, state) < 0)
errx(EXIT_FAILURE, "connection failed");
2015-05-11 19:19:35 +00:00
if (xmppipe_connect_init(state) < 0)
errx(EXIT_FAILURE, "XMPP handshake failed");
2015-05-11 19:19:35 +00:00
if (state->verbose)
(void)fprintf(stderr, "sandbox: entering %s\n", XMPPIPE_SANDBOX);
if (xmppipe_sandbox_init(state) < 0)
err(EXIT_FAILURE, "sandbox failed");
if (xmppipe_stream_init(state) < 0)
errx(EXIT_FAILURE, "enabling stream management failed");
2015-05-11 19:19:35 +00:00
if (xmppipe_muc_init(state) < 0)
errx(EXIT_FAILURE, "failed to join MUC");
2015-05-11 19:19:35 +00:00
if (xmppipe_presence_init(state) < 0)
errx(EXIT_FAILURE, "publishing presence failed");
2015-05-11 19:19:35 +00:00
if (state->subject)
2015-05-12 18:09:14 +00:00
xmppipe_muc_subject(state, state->subject);
2015-05-11 19:19:35 +00:00
event_loop(state);
xmppipe_stream_close(state);
(void)xmpp_conn_release(state->conn);
2015-05-11 19:19:35 +00:00
xmpp_ctx_free(state->ctx);
xmpp_shutdown();
return 0;
}
int
xmppipe_connect_init(xmppipe_state_t *state)
{
for ( ; ; ) {
xmpp_run_once(state->ctx, state->poll);
switch (state->status) {
case XMPPIPE_S_CONNECTED:
return 0;
case XMPPIPE_S_CONNECTING:
break;
default:
return -1;
}
}
}
int
xmppipe_stream_init(xmppipe_state_t *state)
{
xmpp_stanza_t *enable = NULL;
if (state->sm_request_interval == 0)
return 0;
/* <enable xmlns='urn:xmpp:sm:3'/> */
enable = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(enable, "enable");
xmppipe_stanza_set_ns(enable, "urn:xmpp:sm:3");
xmpp_send(state->conn, enable);
(void)xmpp_stanza_release(enable);
xmpp_handler_add(state->conn, handle_sm_enabled,
"urn:xmpp:sm:3", "enabled", NULL, state);
xmpp_handler_add(state->conn, handle_sm_request,
"urn:xmpp:sm:3", "r", NULL, state);
xmpp_handler_add(state->conn, handle_sm_ack,
"urn:xmpp:sm:3", "a", NULL, state);
/* XXX multiple handlers can be called for each event
* XXX
* XXX * is the order handlers are called determinisitc?
* XXX * the NULL handler needs to installed as soon as stream management is enabled
* XXX * a handler has to exist for unsupported events
*/
xmpp_handler_add(state->conn, handle_null, NULL, NULL, NULL, state);
return 0;
}
2015-05-11 19:19:35 +00:00
int
xmppipe_muc_init(xmppipe_state_t *state)
{
xmpp_stanza_t *presence = NULL;
xmpp_stanza_t *iq = NULL;
xmpp_stanza_t *query = NULL;
xmpp_handler_add(state->conn, handle_presence_error,
"http://jabber.org/protocol/muc", "presence", "error", state);
2015-05-11 19:19:35 +00:00
xmpp_handler_add(state->conn, handle_presence,
"http://jabber.org/protocol/muc#user", "presence", NULL, state);
xmpp_handler_add(state->conn, handle_version,
"jabber:iq:version", "iq", NULL, state);
xmpp_handler_add(state->conn, handle_message, NULL, "message", NULL, state);
xmpp_id_handler_add(state->conn, handle_ping_reply, "c2s1", state);
2015-05-11 19:19:35 +00:00
/* Discover the MUC service */
2015-07-21 13:44:08 +00:00
if (state->out == NULL) {
2015-05-11 19:19:35 +00:00
xmpp_handler_add(state->conn, handle_disco_items,
"http://jabber.org/protocol/disco#items", "iq", "result",
state);
xmpp_handler_add(state->conn, handle_disco_info,
"http://jabber.org/protocol/disco#info", "iq", "result",
state);
iq = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(iq, "iq");
xmppipe_stanza_set_type(iq, "get");
xmppipe_stanza_set_attribute(iq, "to", state->server);
2015-05-11 19:19:35 +00:00
query = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(query, "query");
xmppipe_stanza_set_ns(query, "http://jabber.org/protocol/disco#items");
2015-05-11 19:19:35 +00:00
xmppipe_stanza_add_child(iq, query);
2015-05-11 19:19:35 +00:00
xmppipe_send(state, iq);
(void)xmpp_stanza_release(iq);
2015-05-11 19:19:35 +00:00
state->status = XMPPIPE_S_MUC_SERVICE_LOOKUP;
}
/* Send initial <presence/> so that we appear online to contacts */
presence = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(presence, "presence");
xmppipe_send(state, presence);
(void)xmpp_stanza_release(presence);
2015-05-11 19:19:35 +00:00
if (state->out) {
2015-05-12 18:09:14 +00:00
xmppipe_muc_join(state);
xmppipe_muc_unlock(state);
2015-05-11 19:19:35 +00:00
state->status = XMPPIPE_S_MUC_WAITJOIN;
}
return 0;
}
int
xmppipe_presence_init(xmppipe_state_t *state)
{
for ( ; ; ) {
xmpp_run_once(state->ctx, state->poll);
switch (state->status) {
case XMPPIPE_S_READY:
case XMPPIPE_S_READY_AVAIL:
case XMPPIPE_S_READY_EMPTY:
return 0;
default:
break;
}
}
}
void
event_loop(xmppipe_state_t *state)
{
int fd = STDIN_FILENO;
int eof = 0;
char *buf = NULL;
if (xmppipe_set_nonblock(fd) < 0)
return;
buf = xmppipe_calloc(state->bufsz, 1);
2015-05-11 19:19:35 +00:00
for ( ; ; ) {
if (state->status == XMPPIPE_S_DISCONNECTED)
goto XMPPIPE_EXIT;
if (state->sm_enabled &&
( (state->sm_request_unack > state->sm_unacked)
|| (state->sm_request - state->sm_ack_sent > state->sm_fc))) {
if (state->verbose)
(void)fprintf(stderr, "WAIT: request=%u ack_sent=%u unack=%u\n",
state->sm_request, state->sm_ack_sent,
state->sm_request_unack);
goto XMPPIPE_POLL;
}
if (eof) {
if (state->opt & XMPPIPE_OPT_EOF)
goto XMPPIPE_POLL;
if (state->sm_enabled && (state->sm_ack_sent < state->sm_request)) {
if (state->verbose)
(void)fprintf(stderr, "POLLING: request: %d ack: %d\n",
state->sm_request, state->sm_ack_sent);
goto XMPPIPE_POLL;
}
else
goto XMPPIPE_EXIT;
}
2015-05-11 19:19:35 +00:00
switch (handle_stdin(state, fd, buf, state->bufsz-1)) {
case -1:
goto XMPPIPE_EXIT;
case 0:
if (!(state->opt & XMPPIPE_OPT_EOF) && !state->sm_enabled)
2015-05-11 19:19:35 +00:00
goto XMPPIPE_EXIT;
eof = 1;
break;
case 1:
break;
default:
(void)memset(buf, '\0', state->bufsz);
break;
}
state->interval += state->poll;
2015-05-12 18:09:14 +00:00
XMPPIPE_POLL:
2015-05-19 12:03:02 +00:00
if (state->keepalive > 0 && state->interval > state->keepalive) {
2015-05-11 19:19:35 +00:00
xmppipe_ping(state);
state->interval = 0;
}
if (state->keepalive_fail > state->keepalive_limit)
errx(EXIT_FAILURE, "no response to keepalives");
2015-05-11 19:19:35 +00:00
xmpp_run_once(state->ctx, state->poll);
state->interval += state->poll;
if ((state->opt & XMPPIPE_OPT_SIGPIPE)
&& state->status == XMPPIPE_S_READY_EMPTY)
goto XMPPIPE_EXIT;
(void)fflush(stdout);
}
XMPPIPE_EXIT:
free(buf);
return;
}
int
handle_stdin(xmppipe_state_t *state, int fd, char *buf, size_t len)
{
fd_set rfds;
struct timeval tv = {0};
ssize_t n = 0;
int rv = 0;
tv.tv_sec = 0;
tv.tv_usec = state->poll * 1000;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
2017-02-10 15:18:01 +00:00
rv = select(fd+1, &rfds, NULL, NULL, &tv);
2015-05-11 19:19:35 +00:00
if (rv < 0) {
warn("select");
return -1;
}
if (FD_ISSET(fd, &rfds)) {
n = read(fd, buf, len);
2016-09-09 10:57:49 +00:00
if (n <= 0)
return n;
2015-05-11 19:19:35 +00:00
if (state->verbose > 2)
2015-05-11 19:19:35 +00:00
(void)fprintf(stderr, "STDIN:%s\n", buf);
/* read and discard the data */
if ((state->opt & XMPPIPE_OPT_DISCARD) && state->occupants == 0) {
if (state->opt & XMPPIPE_OPT_DISCARD_TO_STDOUT) {
char *enc = NULL;
enc = xmppipe_fmt(buf);
2015-05-11 19:19:35 +00:00
(void)printf("!:%s\n", enc);
2016-09-06 12:15:25 +00:00
(void)fflush(stdout);
2015-05-11 19:19:35 +00:00
free(enc);
}
return 2;
}
xmppipe_send_message(state, state->out, "groupchat", buf, n);
2015-05-11 19:19:35 +00:00
state->interval = 0;
return 3;
}
return 1;
}
void
handle_connection(xmpp_conn_t * const conn, const xmpp_conn_event_t status,
2015-05-11 19:19:35 +00:00
const int error, xmpp_stream_error_t * const stream_error,
void * const userdata)
{
xmppipe_state_t *state = userdata;
switch (status) {
case XMPP_CONN_CONNECT:
if (state->verbose)
fprintf(stderr, "DEBUG: connected\n");
state->status = XMPPIPE_S_CONNECTED;
break;
default:
state->status = XMPPIPE_S_DISCONNECTED;
if (state->verbose)
fprintf(stderr, "DEBUG: disconnected\n");
2015-06-28 14:34:41 +00:00
errx(EXIT_FAILURE, "handle_connection: disconnected");
2015-05-11 19:19:35 +00:00
}
}
int
handle_null(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
void * const userdata)
{
xmppipe_state_t *state = userdata;
2017-01-18 11:31:24 +00:00
const char *name = NULL;
name = xmpp_stanza_get_name(stanza);
2015-07-21 13:44:08 +00:00
if (name == NULL)
return 1;
if (XMPPIPE_STREQ(name, "iq")
|| XMPPIPE_STREQ(name, "message")
|| XMPPIPE_STREQ(name, "presence"))
state->sm_ack_recv++;
return 1;
}
int
handle_sm_enabled(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
void * const userdata)
{
xmppipe_state_t *state = userdata;
state->sm_enabled = 1;
return 0;
}
int
handle_sm_request(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
void * const userdata)
{
xmppipe_state_t *state = userdata;
xmpp_stanza_t *a = NULL;
char h[11] = {0};
if (state->sm_request % state->sm_request_interval != 0)
return 1;
(void)snprintf(h, sizeof(h), "%u", state->sm_ack_recv);
/* <a xmlns='urn:xmpp:sm:3' h='1'/> */
a = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(a, "a");
xmppipe_stanza_set_ns(a, "urn:xmpp:sm:3");
xmppipe_stanza_set_attribute(a, "h", h);
xmpp_send(state->conn, a);
(void)xmpp_stanza_release(a);
return 1;
}
int
handle_sm_ack(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
void * const userdata)
{
xmppipe_state_t *state = userdata;
2017-01-18 11:31:24 +00:00
const char *h = NULL;
u_int32_t ack = 0;
h = xmpp_stanza_get_attribute(stanza, "h");
2015-07-21 13:44:08 +00:00
if (h == NULL)
return 1;
ack = (u_int32_t)atoi(h); /* XXX */
if (state->verbose)
(void)fprintf(stderr, "SM: request=%u ack=%u last=%u\n",
state->sm_request, ack, state->sm_ack_sent);
state->sm_request_unack = 0;
/* Number of stanzas received by server exceeds the number sent by
* the client.
*/
if (ack > state->sm_request)
goto XMPPIPE_STREAMERR;
/* Server count not incremented since last request (stanzas may have
* been dropped).
*
* Could resend dropped stanzas.
*
*/
if (ack == state->sm_ack_sent)
goto XMPPIPE_STREAMERR;
state->sm_ack_sent = ack;
return 1;
XMPPIPE_STREAMERR:
xmppipe_stream_close(state);
errx(EXIT_FAILURE, "ack sequence mismatch: request=%u, ack=%u\n",
state->sm_request, state->sm_ack_sent);
}
2015-05-11 19:19:35 +00:00
int
handle_disco_items(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
void * const userdata)
{
xmpp_stanza_t *query, *item;
xmppipe_state_t *state = userdata;
xmpp_ctx_t *ctx = state->ctx;
query = xmpp_stanza_get_child_by_name(stanza, "query");
2015-07-21 13:44:08 +00:00
if (query == NULL)
2015-05-11 19:19:35 +00:00
return 1;
for (item = xmpp_stanza_get_children(query); item != NULL;
item = xmpp_stanza_get_next(item)) {
xmpp_stanza_t *iq, *reply;
2017-01-18 11:31:24 +00:00
const char *jid = NULL;
const char *name = NULL;
name = xmpp_stanza_get_name(item);
2015-07-21 13:44:08 +00:00
if (name == NULL)
continue;
2015-05-11 19:19:35 +00:00
if (XMPPIPE_STRNEQ(name, "item"))
2015-05-11 19:19:35 +00:00
continue;
jid = xmpp_stanza_get_attribute(item, "jid");
2015-07-21 13:44:08 +00:00
if (jid == NULL)
2015-05-11 19:19:35 +00:00
continue;
iq = xmppipe_stanza_new(ctx);
xmppipe_stanza_set_name(iq, "iq");
xmppipe_stanza_set_type(iq, "get");
xmppipe_stanza_set_attribute(iq, "to", jid);
2015-05-11 19:19:35 +00:00
reply = xmppipe_stanza_new(ctx);
xmppipe_stanza_set_name(reply, "query");
xmppipe_stanza_set_ns(reply, "http://jabber.org/protocol/disco#info");
2015-05-11 19:19:35 +00:00
xmppipe_stanza_add_child(iq, reply);
2015-05-11 19:19:35 +00:00
xmppipe_send(state, iq);
(void)xmpp_stanza_release(iq);
2015-05-11 19:19:35 +00:00
}
return 0;
}
int
handle_disco_info(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
void * const userdata)
{
xmpp_stanza_t *query, *child;
2017-01-18 11:31:24 +00:00
const char *from = NULL;
2015-05-11 19:19:35 +00:00
xmppipe_state_t *state = userdata;
from = xmpp_stanza_get_attribute(stanza, "from");
2015-07-21 13:44:08 +00:00
if (from == NULL)
2015-05-11 19:19:35 +00:00
return 1;
query = xmpp_stanza_get_child_by_name(stanza, "query");
2015-07-21 13:44:08 +00:00
if (query == NULL)
2015-05-11 19:19:35 +00:00
return 1;
for (child = xmpp_stanza_get_children(query); child != NULL;
child = xmpp_stanza_get_next(child)) {
2017-01-18 11:31:24 +00:00
const char *feature = NULL;
const char *var = NULL;
feature = xmpp_stanza_get_name(child);
2015-07-21 13:44:08 +00:00
if (feature == NULL)
continue;
if (XMPPIPE_STRNEQ(feature, "feature"))
2015-05-11 19:19:35 +00:00
continue;
var = xmpp_stanza_get_attribute(child, "var");
2015-07-21 13:44:08 +00:00
if (var == NULL)
continue;
if (XMPPIPE_STRNEQ(var, "http://jabber.org/protocol/muc"))
2015-05-11 19:19:35 +00:00
continue;
state->mucservice = xmppipe_strdup(from);
state->out = xmppipe_conference(state->room, state->mucservice);
state->mucjid = xmppipe_mucjid(state->out, state->resource);
2015-05-12 18:09:14 +00:00
xmppipe_muc_join(state);
xmppipe_muc_unlock(state);
2015-05-11 19:19:35 +00:00
return 0;
}
return 1;
}
int
handle_version(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
void * const userdata)
{
xmpp_stanza_t *reply = NULL;
xmpp_stanza_t *query = NULL;
xmpp_stanza_t *name = NULL;
xmpp_stanza_t *version = NULL;
xmpp_stanza_t *text = NULL;
xmpp_stanza_t *child = NULL;
2017-01-18 11:31:24 +00:00
const char *ns = NULL;
const char *id = NULL;
const char *from = NULL;
2015-05-11 19:19:35 +00:00
xmppipe_state_t *state = userdata;
xmpp_ctx_t *ctx = state->ctx;
reply = xmppipe_stanza_new(ctx);
xmppipe_stanza_set_name(reply, "iq");
xmppipe_stanza_set_type(reply, "result");
id = xmpp_stanza_get_attribute(stanza, "from");
2015-07-21 13:44:08 +00:00
if (id == NULL)
return 1;
xmppipe_stanza_set_id(reply, id);
from = xmpp_stanza_get_attribute(stanza, "from");
2015-07-21 13:44:08 +00:00
if (from == NULL)
return 1;
xmppipe_stanza_set_attribute(reply, "to", from);
2015-05-11 19:19:35 +00:00
query = xmppipe_stanza_new(ctx);
xmppipe_stanza_set_name(query, "query");
child = xmpp_stanza_get_children(stanza);
2015-07-21 13:44:08 +00:00
if (child == NULL) {
(void)xmpp_stanza_release(query);
return 1;
2015-05-11 19:19:35 +00:00
}
ns = xmpp_stanza_get_ns(child);
if (ns)
xmppipe_stanza_set_ns(query, ns);
name = xmppipe_stanza_new(ctx);
xmppipe_stanza_set_name(name, "name");
xmppipe_stanza_add_child(query, name);
2015-05-11 19:19:35 +00:00
text = xmppipe_stanza_new(ctx);
xmppipe_stanza_set_text(text, "xmppipe");
xmppipe_stanza_add_child(name, text);
2015-05-11 19:19:35 +00:00
version = xmppipe_stanza_new(ctx);
xmppipe_stanza_set_name(version, "version");
xmppipe_stanza_add_child(query, version);
2015-05-11 19:19:35 +00:00
text = xmppipe_stanza_new(ctx);
xmppipe_stanza_set_text(text, XMPPIPE_VERSION);
xmppipe_stanza_add_child(version, text);
2015-05-11 19:19:35 +00:00
xmppipe_stanza_add_child(reply, query);
2015-05-11 19:19:35 +00:00
xmppipe_send(state, reply);
(void)xmpp_stanza_release(reply);
2015-05-11 19:19:35 +00:00
return 1;
}
int
handle_presence(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
void * const userdata)
{
xmppipe_state_t *state = userdata;
xmpp_stanza_t *x = NULL;
xmpp_stanza_t *item = NULL;
2017-01-18 11:31:24 +00:00
const char *from = NULL;
const char *to = NULL;
const char *type = NULL;
const char *code = NULL;
2015-05-11 19:19:35 +00:00
char *efrom = NULL;
char *eto = NULL;
char *etype = NULL;
int me = 0;
from = xmpp_stanza_get_attribute(stanza, "from");
to = xmpp_stanza_get_attribute(stanza, "to");
2015-07-21 13:44:08 +00:00
if (from == NULL || to == NULL)
2015-05-11 19:19:35 +00:00
return 1;
x = xmpp_stanza_get_child_by_name(stanza, "x");
if (x) {
for (item = xmpp_stanza_get_children(x); item != NULL;
item = xmpp_stanza_get_next(item)) {
2017-01-18 11:31:24 +00:00
const char *name = xmpp_stanza_get_name(item);
2015-05-11 19:19:35 +00:00
2015-06-11 13:55:54 +00:00
if (name && XMPPIPE_STREQ(name, "status")) {
2015-05-11 19:19:35 +00:00
code = xmpp_stanza_get_attribute(item, "code");
if (code && XMPPIPE_STREQ(code, "110")) {
/* Check for nick conflict */
if (XMPPIPE_STRNEQ(from, state->mucjid)) {
free(state->mucjid);
state->mucjid= xmppipe_strdup(from);
}
state->status = XMPPIPE_S_READY;
me = 1;
break;
}
/* code ignored */
}
}
}
type = xmpp_stanza_get_attribute(stanza, "type");
2015-07-21 13:44:08 +00:00
if (type == NULL)
2015-05-11 19:19:35 +00:00
type = "available";
2015-07-21 13:44:08 +00:00
if (me != 0 && XMPPIPE_STREQ(type, "available")) {
2015-05-11 19:19:35 +00:00
state->occupants++;
}
else if (XMPPIPE_STREQ(type, "unavailable") && (state->occupants > 0)) {
state->occupants--;
}
if (state->status == XMPPIPE_S_READY && state->occupants > 0)
state->status = XMPPIPE_S_READY_AVAIL;
if (state->status == XMPPIPE_S_READY_AVAIL && state->occupants == 0)
state->status = XMPPIPE_S_READY_EMPTY;
etype = xmppipe_fmt(type);
efrom = xmppipe_fmt(from);
eto = xmppipe_fmt(to);
2015-05-11 19:19:35 +00:00
(void)printf("p:%s:%s:%s\n", etype, efrom, eto);
2016-09-06 12:15:25 +00:00
(void)fflush(stdout);
2015-05-11 19:19:35 +00:00
state->interval = 0;
free(etype);
free(efrom);
free(eto);
return 1;
}
int
handle_presence_error(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
void * const userdata)
{
xmppipe_state_t *state = userdata;
xmpp_stanza_t *error = NULL;
xmpp_stanza_t *child = NULL;
2017-01-18 11:31:24 +00:00
const char *from = NULL;
const char *to = NULL;
const char *code = NULL;
const char *text = NULL;
from = xmpp_stanza_get_attribute(stanza, "from");
to = xmpp_stanza_get_attribute(stanza, "to");
2015-07-21 13:44:08 +00:00
if (from == NULL || to == NULL)
return 1;
/* Check error is to our JID (user@example.org/binding) */
if (XMPPIPE_STRNEQ(to, xmpp_conn_get_bound_jid(conn)))
return 1;
/* Check error is from our resource in the MUC (room@example.org/nick) */
if (XMPPIPE_STRNEQ(from, state->mucjid))
return 1;
error = xmpp_stanza_get_child_by_name(stanza, "error");
2015-07-21 13:44:08 +00:00
if (error == NULL)
return 1;
code = xmpp_stanza_get_attribute(error, "code");
child = xmpp_stanza_get_child_by_name(error, "text");
if (child)
text = xmpp_stanza_get_text(child);
errx(EXIT_FAILURE, "%s: %s", code ? code : "no error code specified",
text ? text : "no description");
}
2015-05-11 19:19:35 +00:00
int
handle_message(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
void * const userdata)
{
xmpp_stanza_t *child = NULL;
2015-05-11 19:19:35 +00:00
xmppipe_state_t *state = userdata;
char *message = NULL;
2017-01-18 11:31:24 +00:00
const char *type = NULL;
const char *from = NULL;
const char *to = NULL;
2015-05-11 19:19:35 +00:00
char *etype = NULL;
char *efrom = NULL;
char *eto = NULL;
2015-05-11 19:19:35 +00:00
char *emessage = NULL;
if (xmpp_stanza_get_child_by_name(stanza, "delay"))
return 1;
from = xmpp_stanza_get_attribute(stanza, "from");
2015-07-21 13:44:08 +00:00
if (from == NULL)
return 1;
2015-05-11 19:19:35 +00:00
to = xmpp_stanza_get_attribute(stanza, "to");
2015-07-21 13:44:08 +00:00
if (to == NULL)
return 1;
type = xmpp_stanza_get_type(stanza);
2015-07-21 13:44:08 +00:00
if (type == NULL)
2015-05-11 19:19:35 +00:00
return 1;
/* Check if the message is from us */
if (XMPPIPE_STREQ(type, "groupchat") && XMPPIPE_STREQ(from, state->mucjid))
return 1;
child = xmpp_stanza_get_child_by_name(stanza, "body");
2015-07-21 13:44:08 +00:00
if (child == NULL)
return 1;
2015-05-11 19:19:35 +00:00
message = xmpp_stanza_get_text(child);
2015-07-21 13:44:08 +00:00
if (message == NULL)
2015-05-11 19:19:35 +00:00
return 1;
if (state->encode) {
/* Does not need to be NULL terminated, buf is passed with length */
size_t len = strlen(message) * 3 / 4;
char *buf = xmppipe_calloc(len, 1);
size_t n = b64_pton(message, (u_char *)buf, len);
emessage = xmppipe_nfmt(buf,n);
free(buf);
}
else {
emessage = xmppipe_fmt(message);
}
etype = xmppipe_fmt(type);
efrom = xmppipe_fmt(from);
eto = xmppipe_fmt(to);
2015-05-11 19:19:35 +00:00
(void)printf("m:%s:%s:%s:%s\n", etype, efrom, eto, emessage);
2016-09-06 12:15:25 +00:00
(void)fflush(stdout);
2015-05-11 19:19:35 +00:00
state->interval = 0;
free(message);
free(etype);
free(efrom);
free(eto);
2015-05-11 19:19:35 +00:00
free(emessage);
return 1;
}
int
handle_ping_reply(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
void * const userdata)
{
xmppipe_state_t *state = userdata;
state->keepalive_fail = 0;
return 1;
}
2015-05-11 19:19:35 +00:00
void
2015-05-12 18:09:14 +00:00
xmppipe_muc_join(xmppipe_state_t *state)
2015-05-11 19:19:35 +00:00
{
xmpp_stanza_t *presence = NULL;
xmpp_stanza_t *x = NULL;
presence = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(presence, "presence");
xmppipe_stanza_set_attribute(presence, "to", state->mucjid);
2015-05-11 19:19:35 +00:00
x = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(x, "x");
xmppipe_stanza_set_ns(x, "http://jabber.org/protocol/muc");
2015-05-11 19:19:35 +00:00
xmppipe_stanza_add_child(presence, x);
2015-05-11 19:19:35 +00:00
xmppipe_send(state, presence);
(void)xmpp_stanza_release(presence);
2015-05-11 19:19:35 +00:00
}
void
2015-05-12 18:09:14 +00:00
xmppipe_muc_unlock(xmppipe_state_t *state)
2015-05-11 19:19:35 +00:00
{
xmpp_stanza_t *iq = NULL;
xmpp_stanza_t *q= NULL;
xmpp_stanza_t *x = NULL;
iq = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(iq, "iq");
xmppipe_stanza_set_attribute(iq, "to", state->out);
xmppipe_stanza_set_attribute(iq, "id", "create1");
xmppipe_stanza_set_attribute(iq, "type", "set");
2015-05-11 19:19:35 +00:00
q = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(q, "query");
xmppipe_stanza_set_ns(q, "http://jabber.org/protocol/muc#owner");
2015-05-11 19:19:35 +00:00
x = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(x, "x");
xmppipe_stanza_set_ns(x, "jabber:x:data");
xmppipe_stanza_set_attribute(x, "type", "submit");
2015-05-11 19:19:35 +00:00
xmppipe_stanza_add_child(q, x);
xmppipe_stanza_add_child(iq, q);
2015-05-11 19:19:35 +00:00
xmppipe_send(state, iq);
(void)xmpp_stanza_release(iq);
2015-05-11 19:19:35 +00:00
}
void
2015-05-12 18:09:14 +00:00
xmppipe_muc_subject(xmppipe_state_t *state, char *buf)
2015-05-11 19:19:35 +00:00
{
xmpp_stanza_t *message = NULL;
xmpp_stanza_t *subject= NULL;
xmpp_stanza_t *text= NULL;
message = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(message, "message");
xmppipe_stanza_set_attribute(message, "to", state->out);
xmppipe_stanza_set_attribute(message, "type", "groupchat");
2015-05-11 19:19:35 +00:00
subject = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(subject, "subject");
2015-05-11 19:19:35 +00:00
text = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_text(text, buf);
2015-05-11 19:19:35 +00:00
xmppipe_stanza_add_child(subject, text);
xmppipe_stanza_add_child(message, subject);
2015-05-11 19:19:35 +00:00
xmppipe_send(state, message);
(void)xmpp_stanza_release(message);
2015-05-11 19:19:35 +00:00
}
void
xmppipe_send_message(xmppipe_state_t *state, char *to, char *type, char *buf,
size_t len)
2015-05-11 19:19:35 +00:00
{
xmpp_stanza_t *message = NULL;
xmpp_stanza_t *body = NULL;
xmpp_stanza_t *text = NULL;
char *id = NULL;
id = xmppipe_id_alloc();
2015-05-11 19:19:35 +00:00
2016-10-28 14:13:49 +00:00
if (id == NULL) {
errx(EXIT_FAILURE, "unable to allocate message id");
}
message = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(message, "message");
xmppipe_stanza_set_type(message, type);
xmppipe_stanza_set_attribute(message, "to", to);
xmppipe_stanza_set_id(message, id);
2015-05-11 19:19:35 +00:00
body = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(body, "body");
2015-05-11 19:19:35 +00:00
text = xmppipe_stanza_new(state->ctx);
if (state->encode) {
size_t b64len = BASE64_LENGTH(len) + 1; /* Include trailing NULL */
char *b64 = xmppipe_calloc(b64len, 1);
if (b64_ntop((u_char *)buf, len, b64, b64len) < 0)
2015-05-26 11:43:45 +00:00
errx(EXIT_FAILURE, "encode: invalid input: %zu/%zu", len, b64len);
xmppipe_stanza_set_text(text, b64);
free(b64);
}
else {
xmppipe_stanza_set_text(text, buf);
}
2015-05-11 19:19:35 +00:00
xmppipe_stanza_add_child(body, text);
xmppipe_stanza_add_child(message, body);
2015-05-11 19:19:35 +00:00
xmppipe_send(state, message);
(void)xmpp_stanza_release(message);
free(id);
2015-05-11 19:19:35 +00:00
}
void
xmppipe_ping(xmppipe_state_t *state)
{
xmpp_stanza_t *iq = NULL;
xmpp_stanza_t *ping = NULL;
iq = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(iq, "iq");
xmppipe_stanza_set_type(iq, "get");
xmppipe_stanza_set_id(iq, "c2s1");
xmppipe_stanza_set_attribute(iq, "from", xmpp_conn_get_bound_jid(state->conn));
2015-05-11 19:19:35 +00:00
ping = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(ping, "ping");
xmppipe_stanza_set_ns(ping, "urn:xmpp:ping");
2015-05-11 19:19:35 +00:00
xmppipe_stanza_add_child(iq, ping);
2015-05-11 19:19:35 +00:00
xmppipe_send(state, iq);
(void)xmpp_stanza_release(iq);
state->keepalive_fail++;
2015-05-11 19:19:35 +00:00
}
void
xmppipe_send(xmppipe_state_t *state, xmpp_stanza_t *const stanza)
{
xmpp_stanza_t *r = NULL;
state->sm_request++;
xmpp_send(state->conn, stanza);
2015-07-21 13:44:08 +00:00
if (state->sm_enabled == 0)
return;
if (state->sm_request % state->sm_request_interval != 0)
return;
r = xmppipe_stanza_new(state->ctx);
xmppipe_stanza_set_name(r, "r");
xmppipe_stanza_set_ns(r, "urn:xmpp:sm:3");
xmpp_send(state->conn, r);
state->sm_request_unack++;
(void)xmpp_stanza_release(r);
}
void
xmppipe_stream_close(xmppipe_state_t *state)
{
if (state->sm_enabled)
xmpp_send_raw_string(state->conn, "</stream:stream>");
}
2015-05-11 19:19:35 +00:00
static void
usage(xmppipe_state_t *state)
{
2017-02-06 15:03:06 +00:00
(void)fprintf(stderr, "%s %s (%s)\n",
__progname, XMPPIPE_VERSION, XMPPIPE_SANDBOX);
2015-05-11 19:19:35 +00:00
(void)fprintf(stderr,
"usage: %s <options>\n"
" -u <jid> username (aka JID)\n"
" -p <password> password\n"
" -r <resource> resource (aka MUC nick)\n"
" -o <output> MUC room to send stdout\n"
" -S <subject> set MUC subject\n"
2015-05-16 13:54:35 +00:00
" -a <addr:port> set XMPP server address (port is optional)\n"
2015-05-11 19:19:35 +00:00
" -d discard stdin when MUC is empty\n"
" -D discard stdin and print to local stdout\n"
" -e ignore stdin EOF\n"
" -s exit when MUC is empty\n"
" -x base64 encode/decode data\n"
2015-05-11 19:19:35 +00:00
" -b <size> size of read buffer\n"
2016-09-07 14:22:58 +00:00
" -I <interval> request stream management status every interval messages\n"
" -k <seconds> periodically send a keepalive\n"
" -K <count> number of keepalive failures before exiting\n"
2015-05-11 19:19:35 +00:00
" -P <ms> poll delay\n"
" -v verbose\n",
__progname
);
exit (EXIT_FAILURE);
}