#include "net.h"
/* Constants */
+#define ID_LEN 256
#define AUTH_LEN 512
#define JID_LEN 256
typedef struct {
user_t user;
- char jid[JID_LEN];
+ char dest[JID_LEN];
+ int muc;
+ char *full_name;
} xmpp_user_t;
typedef struct {
channel_t channel;
char dest[JID_LEN];
+ const char *type;
+ int muc;
const char *room;
int join;
XML_Parser expat;
xmpp_state_t state;
buf_t buf;
- int indent;
+ int level;
int id;
- char *body;
- stamp_t stamp;
+ char msg_id[ID_LEN];
+ xmpp_channel_t *msg_chan;
+ xmpp_user_t *msg_usr;
+ char *msg_body;
+ stamp_t msg_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 */
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
+ debug("xmpp: srv_notice: [%s]", buf);
chat_recv(&srv->system.channel, NULL, buf);
}
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);
}
// 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];
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->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)
/* Create a new channel */
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);
return chan;
}
-static xmpp_user_t *find_jid(xmpp_server_t *srv, const char *jid, int create)
+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->jid, jid))
+ 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;
- usr->user.name = strcopy(jid);
- strncpy(usr->jid, jid, JID_LEN);
+ 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);
+ if (!usr->muc)
+ net_print(&srv->net,
+ "<iq id='auto-vcard' type='get' from='%s' to='%s'>"
+ "<vCard xmlns='vcard-temp'/>"
+ "</iq>",
+ srv->jid, usr->dest);
+
return usr;
}
{
/* 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) {
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) {
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) {
XML_SetCharacterDataHandler(srv->expat, on_data);
debug("xmpp: restart -> stream");
+ srv->level = 0;
srv->state = XMPP_SEND_STREAM;
}
xmpp_channel_t *chan = (xmpp_channel_t *)cur;
if (!chan->join)
continue;
+ chan_notice(chan, "XMPP Channel: %s", chan->channel.name);
net_print(&srv->net,
"<presence id='join' from='%s' to='%s/%s'>"
"<x xmlns='http://jabber.org/protocol/muc'/>"
}
/* 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);
- }
+ 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");
- debug("xmpp: %s -- jid=[%s] from=[%s] chan=[%s]",
- start,
- srv->msg_jid, srv->msg_from,
- srv->msg_chan->channel.name);
- }
+ /* 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)");
}
+
+ /* Shorthand Message Data */
+ xmpp_channel_t *chan = NULL;
+ xmpp_user_t *usr = NULL;
+ xmpp_user_t *alias = NULL;
+
if (srv->state > XMPP_READY) {
- if (srv->msg_chan && srv->msg_chan != &srv->system)
- chan = srv->msg_chan;
+ 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);
+ 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",
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 (chan)
- chan_notice(chan, "vCard for %s", chan->dest);
+ 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 && 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 (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(&usr->user.name, data);
+ strset(&usr->full_name, data);
+ }
}
+ }
+ 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;
}
}
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 (match(end, "body")) {
- strset(&srv->body, data);
+ if (match(end, "body") && data) {
+ strset(&srv->msg_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);
+ debug("xmpp: body (%s) -- chan=[%s] from=[%s]",
+ chan->type,
+ chan->channel.name,
+ usr ? usr->user.name : "(none)");
+ if (srv->msg_body) {
+ chat_recv(&chan->channel,
+ &usr->user,
+ srv->msg_body);
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);
}
}
/* 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)
+ if (match(start, "item") && usr) {
+ const char *jid = find_attr(attrs, "jid");
+ xmpp_user_t *alias = find_user(srv, jid, 0);
+ if (jid && alias)
+ usr->user.alias = &alias->user;
+ }
+ 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;
/* 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->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->jid)
- error("jid is required");
+ 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_notice(srv, "XMPP Server: %s", srv->server.name);
xmpp_run(srv, 0, NULL, NULL, NULL, NULL);
+ }
}
for (channel_t *cur = channels; cur; cur = cur->next) {
if (cur->server->protocol != XMPP)
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",
}
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;
"<x xmlns='http://jabber.org/protocol/muc'/>"
"</presence>",
srv->jid, chan->dest, srv->nick);
- chan = find_dest(srv, arg, 1);
+ chan = find_channel(srv, arg, 1);
chan_notice(chan, "Room: %s", arg);
}
else if (prefix(text, "/config", &arg)) {
chan_notice(chan, "usage: /query <user>");
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_dest(srv, arg, 0);
+ chan = find_channel(srv, arg, 0);
if (chan)
net_print(&srv->net,
"<iq id='vcard' type='get' from='%s' to='%s'>"
else if (!chan->dest) {
chan_notice(chan, "No destination for message");
}
- else if (chan->room) {
+ else if (chan->muc) {
net_print(&srv->net,
"<message id='chat%d' from='%s' to='%s' type='groupchat'>"
"<body>%s</body>"
srv->id++, srv->jid, chan->dest, text);
} else {
net_print(&srv->net,
- "<message id='chat%d' from='%s' to='%s'>"
+ "<message id='chat%d' from='%s' to='%s' type='chat'>"
"<body>%s</body>"
"</message>",
srv->id++, srv->jid, chan->dest, text);