/* * 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 ID_LEN 256 #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 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; } 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; 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; buf_t html; int level; int id; char *bind; 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; } 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); debug("xmpp: srv_notice: [%s]", buf); 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); debug("xmpp: chan_notice -- %s [%s]", chan->channel.name, buf); 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_channel(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; /* 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); /* 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 = 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); if (!usr->muc) net_print(&srv->net, "" "" "", srv->bind, usr->dest); 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->level*4, "", data); if (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->level*4+8, "", attrs[i+0], attrs[i+1], attrs[i+2] ? "" : ">"); } } if (start) srv->level++; if (end) srv->level--; /* 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->level = 0; srv->state = XMPP_SEND_STREAM; } if (srv->state == XMPP_ENCRYPT && !start && !end) { net_encrypt(&srv->net, srv->noverify ? NET_NOVERIFY : 0); 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->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"); 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; } /* 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(end, "jid")) { debug("xmpp: jid -> session"); strset(&srv->bind, data ?: srv->jid); 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; if (!srv->quiet) chan_notice(chan, "XMPP Channel: %s", chan->channel.name); net_print(&srv->net, "" "" "", srv->bind, chan->dest, srv->nick); } debug("xmpp: join -> ready"); srv->state = XMPP_READY; } /* Start message */ 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)"); } /* Shorthand Message Data */ xmpp_channel_t *chan = NULL; xmpp_user_t *usr = NULL; xmpp_user_t *alias = NULL; 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")) { 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, "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 -- chan=[%s]", end, chan->dest); chan_notice(chan, "Title: %s", data); } if (match(end, "instructions")) { 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(&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; } } /* 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->msg_stamp = timegm(&tm); } } if (match(end, "subject")) { strset(&chan->channel.topic, 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, "html")) { strset(&srv->msg_html, reset(&srv->html)); srv->in_html = 0; } if (match(end, "message")) { 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 (content) { chat_recv(&chan->channel, &usr->user, content); message_t *msg = &messages[history-1]; msg->when = srv->msg_stamp ?: msg->when; } srv->msg_stamp = 0; strset(&srv->msg_body, NULL); strset(&srv->msg_html, NULL); } } /* Presence */ if (srv->state == XMPP_IN_PRESENCE) { 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.", usr->user.name, alias->user.name); else if (usr) chan_notice(chan, "%s entered room.", usr->user.name); } } /* End messages */ 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; /* 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) { 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; 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->user) srv->user = strcopy(jid_usr); if (!srv->nick) srv->nick = strcopy(jid_usr); if (!srv->timeout) srv->timeout = 60; 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) 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; chan->muc = 1; chan->type = "muc"; if (!chan->room) chan->room = strcopy(cur->name); snprintf(chan->dest, JID_LEN, "%s@%s", chan->room, srv->muc); } } 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 (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")) 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 (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); } } 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 */ escape(buf, text, sizeof(buf)); text = buf; /* Handle commands */ if (text[0] == '/') { if (prefix(text, "/items", &arg)) { net_print(&srv->net, "" "" "", srv->bind, arg ?: srv->srv); } else if (prefix(text, "/info", &arg)) { net_print(&srv->net, "" "" "", srv->bind, arg ?: srv->srv); } else if (prefix(text, "/names", &arg)) { if (arg) chan = find_channel(srv, arg, 1); if (chan == &srv->system) { chan_notice(chan, "Cannot get names from server"); return; } net_print(&srv->net, "" "" "", srv->bind, chan->dest); } else if (prefix(text, "/join", &arg)) { if (!arg) { chan_notice(chan, "usage: /join "); return; } net_print(&srv->net, "" "" "", srv->bind, chan->dest, srv->nick); chan = find_channel(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->bind, chan->dest); } else if (prefix(text, "/query", &arg)) { if (!arg) { chan_notice(chan, "usage: /query "); return; } 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 (chan == &srv->system) { chan_notice(chan, "Cannot send to server"); } else if (!chan->dest) { chan_notice(chan, "No destination for message"); } else if (chan->muc) { net_print(&srv->net, "" "%s" "", srv->id++, srv->bind, chan->dest, text); } else { net_print(&srv->net, "" "%s" "", srv->id++, srv->bind, chan->dest, text); chat_recv(channel, &srv->myself.user, text); } } } void xmpp_exit(void) { }