X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=xmpp.c;h=374e6615d4c286c4525fdf49503b8ee24a4156a7;hb=HEAD;hp=a06cd5c97701fc1d6d7d1637ff20efa115bd060c;hpb=ffb30e1a5d87f7daf66ba822cc938979ab4878df;p=~andy%2Flamechat diff --git a/xmpp.c b/xmpp.c index a06cd5c..374e661 100644 --- a/xmpp.c +++ b/xmpp.c @@ -20,26 +20,26 @@ #include #include #include -#include #include +#include #include -#include #include #include -#include - #include "util.h" #include "conf.h" #include "chat.h" +#include "view.h" #include "net.h" /* Constants */ +#define ID_LEN 256 #define AUTH_LEN 512 #define JID_LEN 256 /* XMPP types */ typedef enum { + XMPP_DEAD, XMPP_CONNECT, XMPP_ENCRYPT, XMPP_RESTART, @@ -53,29 +53,48 @@ typedef enum { XMPP_RECV_JID, XMPP_SEND_SESSION, XMPP_SEND_PRESENCE, + XMPP_SEND_ROSTER, + XMPP_SEND_ROOMS, XMPP_SEND_JOIN, XMPP_READY, XMPP_IN_IQ, + XMPP_IN_VCARD, + XMPP_IN_ROSTER, + XMPP_IN_ROOMS, XMPP_IN_MESSAGE, XMPP_IN_PRESENCE, } xmpp_state_t; +typedef struct { + user_t user; + char dest[JID_LEN]; + int muc; + char *full_name; + char *mention_name; +} xmpp_user_t; + typedef struct { channel_t channel; char dest[JID_LEN]; + const char *type; + int muc; - const char *room; + char *room; + char *name; int join; } xmpp_channel_t; typedef struct { server_t server; + xmpp_user_t myself; xmpp_channel_t system; int connect; int timeout; + int noverify; const char *host; int port; + const char *srv; const char *muc; const char *nick; const char *jid; @@ -83,26 +102,26 @@ typedef struct { const char *pass; int quiet; - int timer; - poll_t poll; + idle_t idle; net_t net; XML_Parser expat; xmpp_state_t state; buf_t buf; - int indent; + buf_t html; + int level; + int id; + char *bind; - char *body; - stamp_t stamp; + char msg_id[ID_LEN]; + xmpp_channel_t *msg_chan; + xmpp_user_t *msg_usr; + char *msg_body; + char *msg_html; + stamp_t msg_stamp; + int in_html; 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 */ @@ -115,6 +134,7 @@ static void srv_notice(xmpp_server_t *srv, const char *fmt, ...) vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); + debug("xmpp: srv_notice: [%s]", buf); chat_recv(&srv->system.channel, NULL, buf); } @@ -127,6 +147,8 @@ static void chan_notice(xmpp_channel_t *chan, const char *fmt, ...) vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); + debug("xmpp: chan_notice -- %s [%s]", + chan->channel.name, buf); chat_recv(&chan->channel, NULL, buf); } @@ -139,6 +161,8 @@ static void split_jid(const char *jid, char *usr, char *srv, char *res) if (srv) srv[0] = '\0'; if (res) res[0] = '\0'; + while (jid && *jid == ' ') + jid++; for (int i = 0; jid && jid[i]; i++) { switch (jid[i]) { case '@': @@ -155,12 +179,14 @@ static void split_jid(const char *jid, char *usr, char *srv, char *res) ptr[pos] = '\0'; } } + while (ptr && pos >= 1 && ptr[pos-1] == ' ') + 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, +static xmpp_channel_t *find_channel(xmpp_server_t *srv, const char *jid, int is_muc) { static char jid_usr[JID_LEN]; @@ -168,14 +194,19 @@ static xmpp_channel_t *find_dest(xmpp_server_t *srv, static char dest[JID_LEN]; xmpp_channel_t *chan; + /* Server channels */ + if (!jid || match(jid, srv->srv)) + return &srv->system; + + /* Parse JID and check for MUC */ split_jid(jid, jid_usr, jid_srv, NULL); + if (match(jid_srv, srv->muc)) + is_muc = 1; + + /* Find resource-less JID */ snprintf(dest, JID_LEN, "%s@%s", jid_usr, jid_srv[0] ? jid_srv : - is_muc ? srv->muc : srv->host); - - /* Server channels */ - if (match(jid, srv->host)) - return &srv->system; + is_muc ? srv->muc : srv->srv); /* Find existing channels */ for (channel_t *cur = channels; cur; cur = cur->next) { @@ -187,11 +218,181 @@ static xmpp_channel_t *find_dest(xmpp_server_t *srv, } /* Create a new channel */ - chan = (xmpp_channel_t *)add_channel(jid_usr, &srv->server); + chan = new0(xmpp_channel_t); + chan->muc = is_muc; + chan->type = is_muc ? "muc" : "usr"; + chan->channel.server = &srv->server; + chan->channel.name = strcopy(jid_usr); strncpy(chan->dest, dest, JID_LEN); + add_channel(&chan->channel); return chan; } +static xmpp_user_t *find_user(xmpp_server_t *srv, + const char *jid, int is_muc) +{ + static char jid_usr[JID_LEN]; + static char jid_srv[JID_LEN]; + static char jid_res[JID_LEN]; + static char dest[JID_LEN]; + xmpp_user_t *usr; + + /* Server channels */ + if (!jid || match(jid, srv->srv)) + return NULL; + + /* Parse JID and check for MUC */ + split_jid(jid, jid_usr, jid_srv, jid_res); + if (match(jid_srv, srv->muc)) + is_muc = 1; + + /* Channel notices have no resource */ + if (is_muc && !jid_res[0]) + return NULL; + + /* Ignore resources for real users */ + if (is_muc) + snprintf(dest, JID_LEN, "%s@%s/%s", + jid_usr, + jid_srv[0] ? jid_srv : srv->muc, + jid_res); + else + snprintf(dest, JID_LEN, "%s@%s", + jid_usr, + jid_srv[0] ? jid_srv : srv->srv); + + /* Find existing users */ + for (user_t *cur = users; cur; cur = cur->next) { + if (cur->server != &srv->server) + continue; + usr = (xmpp_user_t *)cur; + if (match(usr->dest, dest)) { + debug("xmpp: found user: \"%s\" -> " + "name=[%s] dest=[%s] alias=[%s]", + jid, usr->user.name, usr->dest, + usr->user.alias ? usr->user.alias->name : "(none)"); + return usr; + } + } + + /* Create a new user */ + usr = new0(xmpp_user_t); + usr->user.server = &srv->server; + if (is_muc) { + usr->user.name = strcopy(jid_res); + usr->full_name = strcopy(jid_res); + } else { + usr->user.name = strcopy(jid_usr); + } + usr->muc = is_muc; + strncpy(usr->dest, dest, JID_LEN); + add_user(&usr->user); + + /* Send vcard probe */ + debug("xmpp: added user: \"%s\"%s -> name=[%s] dest=[%s]", + jid, is_muc ? " (muc)" : "", + usr->user.name, usr->dest); + + return usr; +} + +static int match_user(xmpp_user_t *usr, const char *ptrn) +{ + char tmp[32]; + + const char *user = usr->dest; + const char *full = usr->full_name; + const char *nick = usr->mention_name; + + /* Match all users */ + if (!ptrn || !ptrn[0]) + return 1; + + /* Match user id and mention name */ + if (user && strcasestr(user, ptrn) == user) + return 1; + if (nick && strcasestr(nick, ptrn) == nick) + return 1; + + /* Full name matching */ + if (!full || !full[0]) + return 0; + + /* Match first name */ + if (strcasestr(full, ptrn) == full) + return 1; + + /* Match last name */ + snprintf(tmp, sizeof(tmp), " %s", ptrn); + if (strcasestr(full, tmp)) + return 1; + snprintf(tmp, sizeof(tmp), "-%s", ptrn); + if (strcasestr(full, tmp)) + return 1; + + /* Match first initial last name */ + if (tolower(full[0]) == tolower(ptrn[0])) { + snprintf(tmp, sizeof(tmp), " %s", &ptrn[1]); + if (strcasestr(full, tmp)) + return 1; + snprintf(tmp, sizeof(tmp), "-%s", &ptrn[1]); + if (strcasestr(full, tmp)) + return 1; + } + + return 0; +} + +static int match_channel(xmpp_channel_t *chan, const char *ptrn) +{ + const char *dest = chan->dest; + const char *room = chan->room; + + /* Match all users */ + if (!ptrn || !ptrn[0]) + return 1; + + /* Match dest and channel name */ + if (dest && strcasestr(dest, ptrn)) + return 1; + if (room && strcasestr(room, ptrn)) + return 1; + + return 0; +} + +static void complete_xmpp_user(xmpp_server_t *srv, const char *prefix, int mention) +{ + for (user_t *cur = users; cur; cur = cur->next) { + if (cur->server != &srv->server) + continue; + xmpp_user_t *usr = (xmpp_user_t *)cur; + if (usr->muc) + continue; + if (match_user(usr, prefix)) { + if (!mention) + complete_item(prefix, usr->dest, usr->full_name); + else if (usr->mention_name) + complete_item(prefix, usr->mention_name, usr->full_name); + else + complete_item(prefix, usr->full_name, ""); + } + } +} + +static void complete_xmpp_channel(xmpp_server_t *srv, const char *prefix) +{ + 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->muc) + continue; + if (match_channel(chan, prefix)) + complete_item(prefix, chan->dest, chan->name ?: chan->room); + } +} + static const char *find_attr(const char **attrs, const char *name) { for (int i = 0; attrs[i] && attrs[i+1]; i += 2) @@ -200,6 +401,17 @@ static const char *find_attr(const char **attrs, const char *name) return NULL; } +static void lookup_user(xmpp_server_t *srv, xmpp_user_t *usr) +{ + if (usr->full_name) + return; + net_print(&srv->net, + "" + "" + "", + srv->bind, usr->dest); +} + /* Callback functions */ static void xmpp_run(xmpp_server_t *srv, int idle, const char *start, const char **attrs, @@ -237,18 +449,17 @@ static void on_recv(void *_srv, char *buf, int len) xmpp_run(srv, 0, NULL, NULL, NULL, NULL); } -static void on_err(void *_srv, int errno) +static void on_err(void *_srv, int err) { xmpp_server_t *srv = _srv; - xmpp_run(srv, 0, NULL, NULL, NULL, NULL); + srv_notice(srv, "Server disconnected"); + srv->state = XMPP_DEAD; } 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_run(srv, 1, NULL, NULL, NULL, NULL); } /* XMPP State machine */ @@ -258,20 +469,20 @@ static void xmpp_run(xmpp_server_t *srv, int idle, { /* Debug print */ if (data) - debug("%*s \"%s\"", srv->indent*4, "", data); + debug("%*s \"%s\"", srv->level*4, "", data); if (start) { - debug("%*s<%s>", srv->indent*4, "", start); + debug("%*s<%s>", srv->level*4, "", start); for (int i = 0; attrs[i] && attrs[i+1]; i += 2) { debug("%*s%s=\"%s\"%s", - srv->indent*4+8, "", + srv->level*4+8, "", attrs[i+0], attrs[i+1], attrs[i+2] ? "" : ">"); } } if (start) - srv->indent++; + srv->level++; if (end) - srv->indent--; + srv->level--; /* Connection Handling */ if (srv->state == XMPP_CONNECT && !start && !end) { @@ -279,53 +490,58 @@ static void xmpp_run(xmpp_server_t *srv, int idle, 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); + srv->idle.timer = on_timer; + srv->idle.data = srv; + + net_open(&srv->net, srv->host, srv->port); + idle_add(&srv->idle); + idle_set(&srv->idle, srv->timeout, srv->timeout); if (!(srv->expat = XML_ParserCreate(NULL))) - error("Error creating XML parser"); + 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->level = 0; srv->state = XMPP_SEND_STREAM; } if (srv->state == XMPP_ENCRYPT && !start && !end) { - net_encrypt(&srv->net); + net_encrypt(&srv->net, srv->noverify ? NET_NOVERIFY : 0); if (!(XML_ParserReset(srv->expat, NULL))) - error("Error resetting XML parser"); + 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->level = 0; srv->state = XMPP_SEND_STREAM; } if (srv->state == XMPP_RESTART && !start && !end) { if (!(XML_ParserReset(srv->expat, NULL))) - error("Error resetting XML parser"); + 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->level = 0; srv->state = XMPP_SEND_STREAM; } + /* Idle handling */ + if (srv->state > XMPP_CONNECT && idle) { + debug("xmpp: idle"); + net_print(&srv->net, " "); + } + /* Stream Start */ if (srv->state == XMPP_SEND_STREAM) { if (net_print(&srv->net, @@ -337,7 +553,7 @@ static void xmpp_run(xmpp_server_t *srv, int idle, " xml:lang='en'" " xmlns='jabber:client'" " xmlns:stream='http://etherx.jabber.org/streams'>", - srv->jid, srv->host)) { + srv->jid, srv->srv)) { debug("xmpp: stream -> features"); srv->state = XMPP_RECV_FEATURES; } @@ -390,8 +606,14 @@ static void xmpp_run(xmpp_server_t *srv, int idle, } } if (srv->state == XMPP_RECV_SUCCESS) { + if (match(start, "failure")) { + debug("xmpp: success -> dead"); + srv_notice(srv, "Authentication failure"); + srv->state = XMPP_DEAD; + } if (match(start, "success")) { debug("xmpp: success -> restart"); + srv_notice(srv, "Authentication success"); srv->state = XMPP_RESTART; } } @@ -415,8 +637,9 @@ static void xmpp_run(xmpp_server_t *srv, int idle, } } if (srv->state == XMPP_RECV_JID) { - if (match(start, "jid")) { + if (match(end, "jid")) { debug("xmpp: jid -> session"); + strset(&srv->bind, data ?: srv->jid); srv->state = XMPP_SEND_SESSION; } } @@ -425,14 +648,33 @@ static void xmpp_run(xmpp_server_t *srv, int idle, "" "" "", - srv->host)) { + 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"); + debug("xmpp: presence -> roster"); + srv->state = XMPP_SEND_ROSTER; + } + } + if (srv->state == XMPP_SEND_ROSTER) { + if (net_print(&srv->net, + "" + "" + "")) { + debug("xmpp: roster -> rooms"); + srv->state = XMPP_SEND_ROOMS; + } + } + if (srv->state == XMPP_SEND_ROOMS) { + if (net_print(&srv->net, + "" + "" + "", + srv->muc)) { + debug("xmpp: rooms -> join"); srv->state = XMPP_SEND_JOIN; } } @@ -443,68 +685,80 @@ static void xmpp_run(xmpp_server_t *srv, int idle, xmpp_channel_t *chan = (xmpp_channel_t *)cur; if (!chan->join) continue; + if (!srv->quiet) + chan_notice(chan, "XMPP Channel: %s", chan->channel.name); net_print(&srv->net, "" "" "", - srv->jid, chan->dest, srv->nick); + srv->bind, 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 && start) { + const char *id = find_attr(attrs, "id"); + const char *from = find_attr(attrs, "from"); + const char *type = find_attr(attrs, "type"); + int is_muc = match(type, "groupchat"); + + /* Ignore presence errors (federated remote timeout, etc) */ + if (match(type, "error")) + return; + + strncpy(srv->msg_id, id ?: "", ID_LEN); + + srv->msg_chan = find_channel(srv, from, is_muc); + srv->msg_usr = find_user(srv, from, is_muc); + + if (match(start, "iq")) + srv->state = XMPP_IN_IQ; + if (match(start, "message")) + srv->state = XMPP_IN_MESSAGE; + if (match(start, "presence")) + srv->state = XMPP_IN_PRESENCE; + + if (srv->state != XMPP_READY) + debug("xmpp: ready -> in_%s -- " + "from=[%s] -> chan=[%s:%s] user=[%s]", + start, from, + srv->msg_chan->type, + srv->msg_chan->channel.name, + srv->msg_usr ? srv->msg_usr->user.name : "(none)"); } - 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); - } + /* Shorthand Message Data */ + xmpp_channel_t *chan = NULL; + xmpp_user_t *usr = NULL; + xmpp_user_t *alias = NULL; - 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_IN_IQ || - srv->state == XMPP_IN_MESSAGE || - srv->state == XMPP_IN_PRESENCE) { - if (srv->msg_chan && srv->msg_chan != &srv->system) - chan = srv->msg_chan; + if (srv->state > XMPP_READY) { + chan = srv->msg_chan; + usr = srv->msg_usr; + if (usr) + alias = (xmpp_user_t*)usr->user.alias; } /* 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, "item")) { + if (chan == &srv->system) { + srv_notice(srv, "item: [%s] %s", + find_attr(attrs, "jid"), + find_attr(attrs, "name")); + } else if (chan->muc) { + const char *jid = find_attr(attrs, "jid"); + xmpp_user_t *usr = find_user(srv, jid, 1); + if (jid && usr) + chan_notice(chan, "User: %s (%s)", + usr->user.name, jid); + } else { + chan_notice(chan, "item: [%s] %s", + find_attr(attrs, "jid"), + find_attr(attrs, "name")); + } } if (match(start, "identity")) { srv_notice(srv, "identity: %s", @@ -526,17 +780,100 @@ static void xmpp_run(xmpp_server_t *srv, int idle, find_attr(attrs, "type")); } if (match(end, "title")) { - debug("xmpp: title -- jid=[%s]", - end, srv->msg_jid); + debug("xmpp: title -- chan=[%s]", + end, chan->dest); chan_notice(chan, "Title: %s", data); } if (match(end, "instructions")) { - debug("xmpp: instructions -- jid=[%s]", - end, srv->msg_jid); + debug("xmpp: instructions -- chan=[%s]", + end, chan->dest); chan_notice(chan, "%s", data); } } + /* vCards */ + if (srv->state == XMPP_IN_IQ) { + if (match(start, "vCard")) { + if (!match(srv->msg_id, "auto-vcard")) + chan_notice(chan, "Begin vCard (%s)", + usr ? usr->user.name : + chan->channel.name); + srv->state = XMPP_IN_VCARD; + } + } + if (srv->state == XMPP_IN_VCARD) { + if (end && srv->level == 3) { + if (!match(srv->msg_id, "auto-vcard") && + !match(end, "BINVAL")) + chan_notice(chan, " %s -> %s", end, data ?: "..."); + } + if (usr) { + if (match(end, "FN")) { + strset(&chan->channel.name, data); + strset(&usr->user.name, data); + strset(&usr->full_name, data); + chat_update(); + } + } + } + if (srv->state == XMPP_IN_VCARD) { + if (match(end, "vCard")) { + if (!match(srv->msg_id, "auto-vcard")) + chan_notice(chan, "End vCard"); + srv->state = XMPP_IN_IQ; + } + } + + /* Rosters */ + if (srv->state == XMPP_IN_IQ) { + if (match(srv->msg_id, "roster")) + srv->state = XMPP_IN_ROSTER; + } + if (srv->state == XMPP_IN_ROSTER) { + if (match(start, "item")) { + // Todo: cleanup name setting + const char *jid = find_attr(attrs, "jid"); + const char *fname = find_attr(attrs, "name"); + const char *mname = find_attr(attrs, "mention_name"); + xmpp_user_t *usr = find_user(srv, jid, 0); + xmpp_channel_t *chan = find_channel(srv, jid, 0); + if (usr) { + strset(&usr->full_name, fname); + strset(&usr->user.name, fname); + strset(&usr->mention_name, mname); + } + if (chan) { + strset(&chan->channel.name, fname); + } + } + } + if (srv->state == XMPP_IN_ROSTER) { + if (match(end, "iq")) + srv->state = XMPP_IN_IQ; + } + + /* Rooms */ + if (srv->state == XMPP_IN_IQ) { + if (match(srv->msg_id, "rooms")) + srv->state = XMPP_IN_ROOMS; + } + if (srv->state == XMPP_IN_ROOMS) { + if (match(start, "item")) { + const char *jid = find_attr(attrs, "jid"); + xmpp_channel_t *chan = find_channel(srv, jid, 1); + if (jid && chan) { + strset(&chan->room, + find_attr(attrs, "name")); + strset(&chan->name, + find_attr(attrs, "name")); + } + } + } + if (srv->state == XMPP_IN_ROOMS) { + if (match(end, "iq")) + srv->state = XMPP_IN_IQ; + } + /* Messages */ if (srv->state == XMPP_IN_MESSAGE) { if (match(start, "delay")) { @@ -544,66 +881,86 @@ static void xmpp_run(xmpp_server_t *srv, int idle, if (ts) { struct tm tm = {}; strptime(ts, "%Y-%m-%dT%H:%M:%S", &tm); - srv->stamp = timegm(&tm); + srv->msg_stamp = timegm(&tm); } } - if (match(end, "subject") && chan) { + if (match(end, "subject")) { strset(&chan->channel.topic, data); + if (!srv->quiet) + chan_notice(chan, "Topic: %s", data); + } + if (match(end, "body") && data) { + strset(&srv->msg_body, data); + } + if (match(start, "html")) { + srv->in_html = 1; + } + if (srv->in_html && data) { + append(&srv->html, data, strlen(data)); } - if (match(end, "body")) { - strset(&srv->body, data); + if (match(end, "html")) { + strset(&srv->msg_html, reset(&srv->html)); + srv->in_html = 0; } 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) { - chat_recv(&chan->channel, srv->msg_from, srv->body); + debug("xmpp: body (%s) -- chan=[%s] from=[%s]", + chan->type, + chan->channel.name, + usr ? usr->user.name : "(none)"); + char *content = srv->msg_body; + if (srv->msg_html) + content = despace(srv->msg_html); + if (usr && !usr->muc) + lookup_user(srv, usr); + if (content) { + chat_recv(&chan->channel, &usr->user, content); message_t *msg = &messages[history-1]; - msg->when = srv->stamp ?: msg->when; + msg->when = srv->msg_stamp ?: msg->when; } - srv->stamp = 0; - strset(&srv->body, NULL); + srv->msg_stamp = 0; + strset(&srv->msg_body, NULL); + strset(&srv->msg_html, 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(start, "item") && usr) { + const char *jid = find_attr(attrs, "jid"); + xmpp_user_t *alias = find_user(srv, jid, 0); + if (alias) + usr->user.alias = &alias->user; + if (alias && !alias->full_name && usr->full_name) { + strset(&alias->user.name, usr->full_name); + strset(&alias->full_name, usr->full_name); + } + xmpp_channel_t *chan = find_channel(srv, jid, 0); + if (alias && alias->full_name && chan) { + strset(&chan->channel.name, alias->full_name); + } } - if (match(end, "presence") && chan) { - if (alias[0] && !srv->quiet) + if (match(end, "presence") && !srv->quiet) { + if (alias) chan_notice(chan, "%s (%s) entered room.", - srv->msg_from, alias); - else if (!srv->quiet) + usr->user.name, alias->user.name); + else if (usr) chan_notice(chan, "%s entered room.", - srv->msg_from); - alias[0] = '\0'; + usr->user.name); } } /* 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")) { + if (srv->state == XMPP_IN_IQ) + if (match(end, "iq")) + srv->state = XMPP_READY; + if (srv->state == XMPP_IN_MESSAGE) + if (match(end, "message")) + srv->state = XMPP_READY; + if (srv->state == XMPP_IN_PRESENCE) + if (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; @@ -620,23 +977,48 @@ static void xmpp_run(xmpp_server_t *srv, int idle, /* XMPP functions */ void xmpp_init(void) { + static char jid_usr[JID_LEN]; + static char jid_srv[JID_LEN]; + static char jid_res[JID_LEN]; + 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); + split_jid(srv->jid, jid_usr, jid_srv, jid_res); - if (!srv->port) - srv->port = 5222; if (!srv->jid) error("jid is required"); + if (!srv->host) + srv->host = strcopy(jid_srv); + if (!srv->port) + srv->port = 5222; + if (!srv->muc) + srv->muc = strcopy(srv->host); + if (!srv->srv) + srv->srv = strcopy(srv->host); + if (!srv->user) + srv->user = strcopy(jid_usr); + if (!srv->nick) + srv->nick = strcopy(jid_usr); if (!srv->timeout) srv->timeout = 60; - if (srv->connect) + + srv->system.type = "sys"; + srv->system.channel.server = &srv->server; + srv->system.channel.name = strcopy(srv->server.name); + + strncpy(srv->myself.dest, srv->jid, JID_LEN); + srv->myself.user.server = &srv->server; + srv->myself.user.name = strcopy(srv->nick); + + if (srv->connect && !srv->quiet) + srv_notice(srv, "XMPP Server: %s", srv->server.name); + if (srv->connect) { + srv->state = XMPP_CONNECT; xmpp_run(srv, 0, NULL, NULL, NULL, NULL); + } } for (channel_t *cur = channels; cur; cur = cur->next) { if (cur->server->protocol != XMPP) @@ -644,6 +1026,8 @@ void xmpp_init(void) xmpp_channel_t *chan = (xmpp_channel_t*)cur; xmpp_server_t *srv = (xmpp_server_t*)cur->server; + chan->muc = 1; + chan->type = "muc"; if (!chan->room) chan->room = strcopy(cur->name); snprintf(chan->dest, JID_LEN, "%s@%s", @@ -651,16 +1035,6 @@ void xmpp_init(void) } } -server_t *xmpp_server(void) -{ - return new0(xmpp_server_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) @@ -668,15 +1042,25 @@ void xmpp_config(server_t *server, channel_t *channel, xmpp_server_t *srv = (xmpp_server_t*)server; xmpp_channel_t *chan = (xmpp_channel_t*)channel; - if (server) { - if (match(key, "connect")) + if (match(group, "server")) { + if (match(key, "protocol")) { + xmpp_server_t *srv = new0(xmpp_server_t); + srv->server.protocol = XMPP; + srv->server.name = strcopy(get_name(name)); + add_server(&srv->server); + } + else if (match(key, "connect")) srv->connect = get_bool(value); else if (match(key, "timeout")) srv->timeout = get_number(value); + else if (match(key, "noverify")) + srv->noverify = get_bool(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")) @@ -690,60 +1074,111 @@ void xmpp_config(server_t *server, channel_t *channel, else if (match(key, "quiet")) srv->quiet = get_bool(value); } - if (channel) { - if (match(key, "room")) + if (match(group, "channel")) { + if (match(key, "server")) { + xmpp_channel_t *chan = new0(xmpp_channel_t); + chan->channel.server = &srv->server; + chan->channel.name = strcopy(get_name(name)); + add_channel(&chan->channel); + } + else if (match(key, "room")) chan->room = get_string(value); else if (match(key, "join")) chan->join = get_bool(value); } + if (match(group, "autojoin")) { + xmpp_channel_t *chan = new0(xmpp_channel_t); + chan->channel.server = &srv->server; + chan->channel.name = strcopy(key); + chan->room = get_string(value); + chan->join = 1; + add_channel(&chan->channel); + } +} + +void xmpp_complete(channel_t *channel, const char *text) +{ + xmpp_server_t *srv = (xmpp_server_t*)channel->server; + const char *arg; + + if (suffix(text, "@", &arg)) { + complete_xmpp_user(srv, arg, 1); + } + else if (prefix(text, "/join ", &arg)) { + complete_xmpp_channel(srv, arg); + } + else if (prefix(text, "/query ", &arg)) { + complete_xmpp_user(srv, arg, 0); + } + else if (prefix(text, "/vcard ", &arg)) { + complete_xmpp_user(srv, arg, 0); + } + else { + complete_args(text, + "/items ", "Query XMPP server 'items'", + "/info ", "Query XMPP server 'info'", + "/names ", "Query XMPP channel users", + "/join", "Join XMPP (muc) room", + "/config ", "Configure XMPP (muc) room", + "/query ", "Open XMPP user chat", + "/vcard ", "Send XMPP vCard query", + NULL); + } } void xmpp_send(channel_t *channel, const char *text) { + static char buf[4096]; + xmpp_channel_t *chan = (xmpp_channel_t*)channel; xmpp_server_t *srv = (xmpp_server_t*)channel->server; const char *arg; + /* Escape HTML */ + const char *raw = text; + escape(buf, text, sizeof(buf)); + text = buf; + /* Handle commands */ - if (text[0] == '/') { + if (text[0] == '/' && text[1] != '/') { if (prefix(text, "/items", &arg)) { net_print(&srv->net, "" "" "", - srv->jid, arg ?: srv->host); + srv->bind, arg ?: srv->srv); } else if (prefix(text, "/info", &arg)) { net_print(&srv->net, "" "" "", - srv->jid, arg ?: srv->host); + srv->bind, arg ?: srv->srv); } else if (prefix(text, "/names", &arg)) { if (arg) - chan = find_dest(srv, arg, 1); + chan = find_channel(srv, arg, 1); if (chan == &srv->system) { chan_notice(chan, "Cannot get names from server"); return; } net_print(&srv->net, - "" + "" "" "", - srv->jid, chan->dest); + srv->bind, chan->dest); } else if (prefix(text, "/join", &arg)) { if (!arg) { chan_notice(chan, "usage: /join "); return; } + chan = find_channel(srv, arg, 1); net_print(&srv->net, "" "" "", - srv->jid, chan->dest, srv->nick); - chan = find_dest(srv, arg, 1); + srv->bind, chan->dest, srv->nick); chan_notice(chan, "Room: %s", arg); } else if (prefix(text, "/config", &arg)) { @@ -751,44 +1186,57 @@ void xmpp_send(channel_t *channel, const char *text) chan_notice(chan, "Unimplemented: /config "); return; } + if (chan == &srv->system) { + chan_notice(chan, "Cannot get config from server"); + return; + } net_print(&srv->net, "" "" "", - srv->jid, chan->dest); + srv->bind, chan->dest); } else if (prefix(text, "/query", &arg)) { if (!arg) { chan_notice(chan, "usage: /query "); return; } - chan = find_dest(srv, arg, 0); + chan = find_channel(srv, arg, 0); chan_notice(chan, "User: %s", arg); } + else if (prefix(text, "/vcard", &arg)) { + if (arg) + chan = find_channel(srv, arg, 0); + if (chan) + net_print(&srv->net, + "" + "" + "", + srv->bind, chan->dest); + } else { chan_notice(chan, "Unknown command %s", text); } } else { debug("message: [%s]", text); + if (text[0] == '/') + text = &text[1]; 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) { + else if (chan->muc) { net_print(&srv->net, - "" + "" "%s" "", - srv->jid, chan->dest, text); + srv->id++, srv->bind, chan->dest, text); } else { net_print(&srv->net, - "" + "" "%s" "", - srv->jid, chan->dest, text); - chat_recv(channel, srv->nick, text); + srv->id++, srv->bind, chan->dest, text); + chat_recv(channel, &srv->myself.user, raw); } } }