/* * Copyright (C) 2017 Andy Spencer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "conf.h" #include "chat.h" #include "net.h" /* Constants */ #define AUTH_LEN 512 #define JID_LEN 256 /* XMPP types */ typedef enum { XMPP_CONNECT, XMPP_ENCRYPT, XMPP_RESTART, XMPP_SEND_STREAM, XMPP_RECV_FEATURES, XMPP_SEND_STARTTLS, XMPP_RECV_PROCEED, XMPP_SEND_AUTH, XMPP_RECV_SUCCESS, XMPP_SEND_BIND, XMPP_RECV_JID, XMPP_SEND_SESSION, XMPP_SEND_PRESENCE, XMPP_SEND_JOIN, XMPP_READY, XMPP_IN_IQ, XMPP_IN_VCARD, XMPP_IN_MESSAGE, XMPP_IN_PRESENCE, } xmpp_state_t; typedef struct { user_t user; char jid[JID_LEN]; } xmpp_user_t; typedef struct { channel_t channel; char dest[JID_LEN]; const char *room; int join; } xmpp_channel_t; typedef struct { server_t server; xmpp_user_t myself; xmpp_channel_t system; int connect; int timeout; const char *host; int port; const char *srv; const char *muc; const char *nick; const char *jid; const char *user; const char *pass; int quiet; int timer; poll_t poll; net_t net; XML_Parser expat; xmpp_state_t state; buf_t buf; int indent; int id; char *body; stamp_t stamp; int in_error; char msg_jid[JID_LEN]; char msg_usr[JID_LEN]; char msg_srv[JID_LEN]; char msg_res[JID_LEN]; char *msg_from; xmpp_channel_t *msg_chan; } xmpp_server_t; /* Helper functions */ static void srv_notice(xmpp_server_t *srv, const char *fmt, ...) { static char buf[1024]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); chat_recv(&srv->system.channel, NULL, buf); } static void chan_notice(xmpp_channel_t *chan, const char *fmt, ...) { static char buf[1024]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); chat_recv(&chan->channel, NULL, buf); } static void split_jid(const char *jid, char *usr, char *srv, char *res) { char *ptr = usr; int pos = 0; if (usr) usr[0] = '\0'; if (srv) srv[0] = '\0'; if (res) res[0] = '\0'; for (int i = 0; jid && jid[i]; i++) { switch (jid[i]) { case '@': ptr = srv; pos = 0; continue; case '/': ptr = res; pos = 0; continue; } if (ptr && (pos+1) < JID_LEN) { ptr[pos++] = jid[i]; ptr[pos] = '\0'; } } //debug("JID: '%s' usr=[%s] srv=[%s] res=[%s]", // jid, usr, srv, res); } static xmpp_channel_t *find_dest(xmpp_server_t *srv, const char *jid, int is_muc) { static char jid_usr[JID_LEN]; static char jid_srv[JID_LEN]; static char dest[JID_LEN]; xmpp_channel_t *chan; split_jid(jid, jid_usr, jid_srv, NULL); snprintf(dest, JID_LEN, "%s@%s", jid_usr, jid_srv[0] ? jid_srv : is_muc ? srv->muc : srv->srv); /* Server channels */ if (match(jid, srv->srv)) return &srv->system; /* Find existing channels */ for (channel_t *cur = channels; cur; cur = cur->next) { if (cur->server != &srv->server) continue; chan = (xmpp_channel_t *)cur; if (match(chan->dest, dest)) return chan; } /* Create a new channel */ chan = (xmpp_channel_t *)add_channel(jid_usr, &srv->server); strncpy(chan->dest, dest, JID_LEN); return chan; } static xmpp_user_t *find_jid(xmpp_server_t *srv, const char *jid, int create) { xmpp_user_t *usr; /* Find existing channels */ for (user_t *cur = users; cur; cur = cur->next) { if (cur->server != &srv->server) continue; usr = (xmpp_user_t *)cur; if (match(usr->jid, jid)) return usr; } /* Create a new channel */ usr = (xmpp_user_t *)add_user(jid, &srv->server); strncpy(usr->jid, jid, JID_LEN); return usr; } static const char *find_attr(const char **attrs, const char *name) { for (int i = 0; attrs[i] && attrs[i+1]; i += 2) if (match(attrs[i+0], name)) return attrs[i+1]; return NULL; } /* Callback functions */ static void xmpp_run(xmpp_server_t *srv, int idle, const char *start, const char **attrs, const char *end, const char *data); static void on_start(void *_srv, const char *tag, const char **attrs) { xmpp_server_t *srv = _srv; xmpp_run(srv, 0, tag, attrs, NULL, reset(&srv->buf)); } static void on_data(void *_srv, const char *data, int len) { xmpp_server_t *srv = _srv; append(&srv->buf, data, len); } static void on_end(void *_srv, const char *tag) { xmpp_server_t *srv = _srv; xmpp_run(srv, 0, NULL, NULL, tag, reset(&srv->buf)); } static void on_send(void *_srv) { xmpp_server_t *srv = _srv; xmpp_run(srv, 0, NULL, NULL, NULL, NULL); } static void on_recv(void *_srv, char *buf, int len) { xmpp_server_t *srv = _srv; if (len > 0) XML_Parse(srv->expat, buf, len, 0); xmpp_run(srv, 0, NULL, NULL, NULL, NULL); } static void on_err(void *_srv, int errno) { xmpp_server_t *srv = _srv; xmpp_run(srv, 0, NULL, NULL, NULL, NULL); } static void on_timer(void *_srv) { uint64_t buf; xmpp_server_t *srv = _srv; while (read(srv->timer, &buf, sizeof(buf)) > 0) xmpp_run(srv, 1, NULL, NULL, NULL, NULL); } /* XMPP State machine */ static void xmpp_run(xmpp_server_t *srv, int idle, const char *start, const char **attrs, const char *end, const char *data) { /* Debug print */ if (data) debug("%*s \"%s\"", srv->indent*4, "", data); if (start) { debug("%*s<%s>", srv->indent*4, "", start); for (int i = 0; attrs[i] && attrs[i+1]; i += 2) { debug("%*s%s=\"%s\"%s", srv->indent*4+8, "", attrs[i+0], attrs[i+1], attrs[i+2] ? "" : ">"); } } if (start) srv->indent++; if (end) srv->indent--; /* Connection Handling */ if (srv->state == XMPP_CONNECT && !start && !end) { srv->net.send = on_send; srv->net.recv = on_recv; srv->net.err = on_err; srv->net.data = srv; net_open(&srv->net, srv->host, srv->port); srv->timer = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC); if (srv->timer < 0) error("creating timer fd"); struct timespec tspec = {srv->timeout, 0}; struct itimerspec itspec = {tspec, tspec}; timerfd_settime(srv->timer, 0, &itspec, NULL); poll_add(&srv->poll, srv->timer, on_timer, srv); poll_ctl(&srv->poll, 1, 0, 1); if (!(srv->expat = XML_ParserCreate(NULL))) error("Error creating XML parser"); XML_SetUserData(srv->expat, srv); XML_SetStartElementHandler(srv->expat, on_start); XML_SetEndElementHandler(srv->expat, on_end); XML_SetCharacterDataHandler(srv->expat, on_data); debug("xmpp: connect -> stream"); srv->state = XMPP_SEND_STREAM; } if (srv->state == XMPP_ENCRYPT && !start && !end) { net_encrypt(&srv->net); if (!(XML_ParserReset(srv->expat, NULL))) error("Error resetting XML parser"); XML_SetUserData(srv->expat, srv); XML_SetStartElementHandler(srv->expat, on_start); XML_SetEndElementHandler(srv->expat, on_end); XML_SetCharacterDataHandler(srv->expat, on_data); debug("xmpp: encrypt -> stream"); srv->state = XMPP_SEND_STREAM; } if (srv->state == XMPP_RESTART && !start && !end) { if (!(XML_ParserReset(srv->expat, NULL))) error("Error resetting XML parser"); XML_SetUserData(srv->expat, srv); XML_SetStartElementHandler(srv->expat, on_start); XML_SetEndElementHandler(srv->expat, on_end); XML_SetCharacterDataHandler(srv->expat, on_data); debug("xmpp: restart -> stream"); srv->state = XMPP_SEND_STREAM; } /* Stream Start */ if (srv->state == XMPP_SEND_STREAM) { if (net_print(&srv->net, "" "", srv->jid, srv->srv)) { debug("xmpp: stream -> features"); srv->state = XMPP_RECV_FEATURES; } } if (srv->state == XMPP_RECV_FEATURES) { if (match(start, "starttls")) { debug("xmpp: features -> starttls"); srv->state = XMPP_SEND_STARTTLS; } if (match(start, "mechanisms")) { debug("xmpp: features -> auth"); srv->state = XMPP_SEND_AUTH; } if (match(start, "bind")) { debug("xmpp: features -> bind"); srv->state = XMPP_SEND_BIND; } } /* Start TLS */ if (srv->state == XMPP_SEND_STARTTLS) { if (net_print(&srv->net, "")) { debug("xmpp: startls -> proceed"); srv->state = XMPP_RECV_PROCEED; } } if (srv->state == XMPP_RECV_PROCEED) { if (match(start, "proceed")) { debug("xmpp: proceed -> encrypt"); srv->state = XMPP_ENCRYPT; } } /* Authentication */ if (srv->state == XMPP_SEND_AUTH) { static char plain[AUTH_LEN]; static char coded[AUTH_LEN]; int len; len = snprintf(plain, AUTH_LEN, "%s%c%s%c%s", srv->user, '\0', srv->user, '\0', srv->pass); len = base64(plain, len, coded, AUTH_LEN); if (net_print(&srv->net, "%.*s", len, coded)) { debug("xmpp: auth -> success"); srv->state = XMPP_RECV_SUCCESS; } } if (srv->state == XMPP_RECV_SUCCESS) { if (match(start, "success")) { debug("xmpp: success -> restart"); srv->state = XMPP_RESTART; } } /* Binding */ if (srv->state == XMPP_SEND_BIND) { const char *resource = srv->jid; while (*resource && *resource != '/') resource++; while (*resource && *resource == '/') resource++; if (net_print(&srv->net, "" "" "%s" "" "", resource)) { debug("xmpp: bind -> jid"); srv->state = XMPP_RECV_JID; } } if (srv->state == XMPP_RECV_JID) { if (match(start, "jid")) { debug("xmpp: jid -> session"); srv->state = XMPP_SEND_SESSION; } } if (srv->state == XMPP_SEND_SESSION) { if (net_print(&srv->net, "" "" "", srv->srv)) { debug("xmpp: session -> presence"); srv->state = XMPP_SEND_PRESENCE; } } if (srv->state == XMPP_SEND_PRESENCE) { if (net_print(&srv->net, "")) { debug("xmpp: presence -> join"); srv->state = XMPP_SEND_JOIN; } } if (srv->state == XMPP_SEND_JOIN) { for (channel_t *cur = channels; cur; cur = cur->next) { if (cur->server != &srv->server) continue; xmpp_channel_t *chan = (xmpp_channel_t *)cur; if (!chan->join) continue; net_print(&srv->net, "" "" "", srv->jid, chan->dest, srv->nick); } debug("xmpp: join -> ready"); srv->state = XMPP_READY; } /* Start message */ xmpp_channel_t *chan = NULL; if (srv->state >= XMPP_READY && idle) { debug("xmpp: idle"); net_print(&srv->net, " "); } if (srv->state == XMPP_READY) { srv->state = match(start, "iq") ? XMPP_IN_IQ : match(start, "message") ? XMPP_IN_MESSAGE : match(start, "presence") ? XMPP_IN_PRESENCE : XMPP_READY; if (srv->state != XMPP_READY) { const char *from = find_attr(attrs, "from") ?: ""; strncpy(srv->msg_jid, from, JID_LEN); split_jid(srv->msg_jid, srv->msg_usr, srv->msg_srv, srv->msg_res); if (match(srv->msg_srv, srv->muc)) { srv->msg_from = srv->msg_res[0] ? srv->msg_res : NULL; srv->msg_chan = find_dest(srv, srv->msg_jid, 1); } else { srv->msg_from = srv->msg_usr[0] ? srv->msg_usr : NULL; srv->msg_chan = find_dest(srv, srv->msg_jid, 0); } debug("xmpp: %s -- jid=[%s] from=[%s] chan=[%s]", start, srv->msg_jid, srv->msg_from, srv->msg_chan->channel.name); } } if (srv->state > XMPP_READY) { if (srv->msg_chan && srv->msg_chan != &srv->system) chan = srv->msg_chan; } /* Info/Queries */ if (srv->state == XMPP_IN_IQ) { if (match(start, "item") && chan) { static char res[JID_LEN]; split_jid(find_attr(attrs, "jid"), NULL, NULL, res); chan_notice(chan, "user: %s", res); } if (match(start, "item") && !chan) { srv_notice(srv, "item: [%s] %s", find_attr(attrs, "jid"), find_attr(attrs, "name")); } if (match(start, "identity")) { srv_notice(srv, "identity: %s", find_attr(attrs, "name")); } if (match(start, "feature")) { srv_notice(srv, "feature: %s", find_attr(attrs, "var")); } if (match(start, "field")) { debug("xmpp: %s -- type=[%s] label=[%s]", end, find_attr(attrs, "type"), find_attr(attrs, "label")); if (!find_attr(attrs, "label")) return; chan_notice(chan, "%-36s -- %s (%s)", find_attr(attrs, "var"), find_attr(attrs, "label"), find_attr(attrs, "type")); } if (match(end, "title")) { debug("xmpp: title -- jid=[%s]", end, srv->msg_jid); chan_notice(chan, "Title: %s", data); } if (match(end, "instructions")) { debug("xmpp: instructions -- jid=[%s]", end, srv->msg_jid); chan_notice(chan, "%s", data); } } /* vCards */ if (srv->state == XMPP_IN_IQ) { if (match(start, "vCard")) { if (chan) chan_notice(chan, "vCard for %s", chan->dest); srv->state = XMPP_IN_VCARD; } } if (srv->state == XMPP_IN_VCARD) { if (end && chan && data) { xmpp_user_t *usr = NULL; chan_notice(chan, "%-12s -- %s", end, data); if (srv->msg_from) usr = find_jid(srv, srv->msg_from, 1); if (usr && match(end, "FN")) strset(&usr->user.full, data); } if (match(end, "vCard")) { srv->state = XMPP_IN_IQ; } } /* Messages */ if (srv->state == XMPP_IN_MESSAGE) { if (match(start, "delay")) { const char *ts = find_attr(attrs, "stamp"); if (ts) { struct tm tm = {}; strptime(ts, "%Y-%m-%dT%H:%M:%S", &tm); srv->stamp = timegm(&tm); } } if (match(end, "subject") && chan) { strset(&chan->channel.topic, data); } if (match(end, "body")) { strset(&srv->body, data); } if (match(end, "message")) { debug("xmpp: body (%s) -- chan=[%s] jid=[%s] from=[%s]", srv->msg_from == srv->msg_usr ? "user" : "chat" , srv->msg_chan->channel.name, srv->msg_jid, srv->msg_from); if (srv->body) { xmpp_user_t *usr = NULL; if (srv->msg_from) usr = find_jid(srv, srv->msg_from, 1); chat_recv(&chan->channel, &usr->user, srv->body); message_t *msg = &messages[history-1]; msg->when = srv->stamp ?: msg->when; } srv->stamp = 0; strset(&srv->body, NULL); } } /* Presence */ if (srv->state == XMPP_IN_PRESENCE) { static char alias[JID_LEN]; const char *jid; if (match(start, "item")) { if ((jid = find_attr(attrs, "jid"))) strncpy(alias, jid, JID_LEN); } if (match(end, "presence") && chan) { if (alias[0] && !srv->quiet) chan_notice(chan, "%s (%s) entered room.", srv->msg_from, alias); else if (!srv->quiet) chan_notice(chan, "%s entered room.", srv->msg_from); alias[0] = '\0'; } } /* End messages */ if (srv->state == XMPP_IN_IQ || srv->state == XMPP_IN_MESSAGE || srv->state == XMPP_IN_PRESENCE) { if (match(end, "iq") || match(end, "message") || match(end, "presence")) { srv->state = XMPP_READY; srv->msg_jid[0] = '\0'; srv->msg_usr[0] = '\0'; srv->msg_srv[0] = '\0'; srv->msg_res[0] = '\0'; srv->msg_from = NULL; srv->msg_chan = NULL; } } /* Error handling */ if (match(start, "stream:error")) srv->in_error = 1; if (match(end, "stream:error")) srv->in_error = 0; if (srv->in_error) { if (match(end, "text")) { debug("xmpp: error: %s", data); srv_notice(srv, "error: %s", data); } } } /* XMPP functions */ void xmpp_init(void) { for (server_t *cur = servers; cur; cur = cur->next) { if (cur->protocol != XMPP) continue; xmpp_server_t *srv = (xmpp_server_t*)cur; srv->system.channel.server = &srv->server; srv->system.channel.name = srv->server.name; srv_notice(srv, "XMPP Server: %s", srv->server.name); if (!srv->port) srv->port = 5222; if (!srv->srv) srv->srv = strcopy(srv->host); if (!srv->jid) error("jid is required"); if (!srv->timeout) srv->timeout = 60; if (srv->connect) xmpp_run(srv, 0, NULL, NULL, NULL, NULL); } for (channel_t *cur = channels; cur; cur = cur->next) { if (cur->server->protocol != XMPP) continue; xmpp_channel_t *chan = (xmpp_channel_t*)cur; xmpp_server_t *srv = (xmpp_server_t*)cur->server; if (!chan->room) chan->room = strcopy(cur->name); snprintf(chan->dest, JID_LEN, "%s@%s", chan->room, srv->muc); } } server_t *xmpp_server(void) { return new0(xmpp_server_t); } user_t *xmpp_user(void) { return new0(xmpp_user_t); } channel_t *xmpp_channel(void) { return new0(xmpp_channel_t); } void xmpp_config(server_t *server, channel_t *channel, const char *group, const char *name, const char *key, const char *value) { xmpp_server_t *srv = (xmpp_server_t*)server; xmpp_channel_t *chan = (xmpp_channel_t*)channel; if (server) { if (match(key, "connect")) srv->connect = get_bool(value); else if (match(key, "timeout")) srv->timeout = get_number(value); else if (match(key, "host")) srv->host = get_string(value); else if (match(key, "port")) srv->port = get_number(value); else if (match(key, "srv")) srv->srv = get_string(value); else if (match(key, "muc")) srv->muc = get_string(value); else if (match(key, "nick")) srv->nick = get_string(value); else if (match(key, "jid")) srv->jid = get_string(value); else if (match(key, "user")) srv->user = get_string(value); else if (match(key, "pass")) srv->pass = get_string(value); else if (match(key, "quiet")) srv->quiet = get_bool(value); } if (channel) { if (match(key, "room")) chan->room = get_string(value); else if (match(key, "join")) chan->join = get_bool(value); } } void xmpp_send(channel_t *channel, const char *text) { xmpp_channel_t *chan = (xmpp_channel_t*)channel; xmpp_server_t *srv = (xmpp_server_t*)channel->server; const char *arg; /* Handle commands */ if (text[0] == '/') { if (prefix(text, "/items", &arg)) { net_print(&srv->net, "" "" "", srv->jid, arg ?: srv->srv); } else if (prefix(text, "/info", &arg)) { net_print(&srv->net, "" "" "", srv->jid, arg ?: srv->srv); } else if (prefix(text, "/names", &arg)) { if (arg) chan = find_dest(srv, arg, 1); if (chan == &srv->system) { chan_notice(chan, "Cannot get names from server"); return; } net_print(&srv->net, "" "" "", srv->jid, chan->dest); } else if (prefix(text, "/join", &arg)) { if (!arg) { chan_notice(chan, "usage: /join "); return; } net_print(&srv->net, "" "" "", srv->jid, chan->dest, srv->nick); chan = find_dest(srv, arg, 1); chan_notice(chan, "Room: %s", arg); } else if (prefix(text, "/config", &arg)) { if (arg) { chan_notice(chan, "Unimplemented: /config "); return; } net_print(&srv->net, "" "" "", srv->jid, chan->dest); } else if (prefix(text, "/query", &arg)) { if (!arg) { chan_notice(chan, "usage: /query "); return; } chan = find_dest(srv, arg, 0); chan_notice(chan, "User: %s", arg); } else if (prefix(text, "/vcard", &arg)) { if (arg) chan = find_dest(srv, arg, 0); if (chan) net_print(&srv->net, "" "" "", srv->jid, chan->dest); } else { chan_notice(chan, "Unknown command %s", text); } } else { debug("message: [%s]", text); if (chan == &srv->system) { chan_notice(chan, "Cannot send to server"); } else if (!chan->dest) { chan_notice(chan, "No destination for message"); } else if (chan->room) { net_print(&srv->net, "" "%s" "", srv->id++, srv->jid, chan->dest, text); } else { net_print(&srv->net, "" "%s" "", srv->id++, srv->jid, chan->dest, text); chat_recv(channel, &srv->myself.user, text); } } } void xmpp_exit(void) { }