]> Pileus Git - ~andy/lamechat/blobdiff - xmpp.c
Fixup commands.
[~andy/lamechat] / xmpp.c
diff --git a/xmpp.c b/xmpp.c
index 3a7dae20ba9a25895ae07d0d1ceb90bd7794e771..3bcb846500209b2ecc53a7f44ada0d2f0631b135 100644 (file)
--- a/xmpp.c
+++ b/xmpp.c
 #include <stdarg.h>
 #include <string.h>
 #include <errno.h>
-#include <fcntl.h>
-#include <netdb.h>
 #include <expat.h>
 
 #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_DEAD,
+       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_ENCRYPT,
-       XMPP_RESTART,
+       XMPP_SEND_BIND,
+       XMPP_RECV_JID,
+       XMPP_SEND_PRESENCE,
        XMPP_READY,
+       XMPP_IN_IQ,
+       XMPP_IN_MESSAGE,
+       XMPP_IN_PRESENCE,
 } xmpp_state_t;
 
-typedef struct xmpp_server_t {
-       const char  *name;
-       const char  *protocol;
-       int          connect;
-       const char  *host;
-       int          port;
-       const char  *jid;
-       const char  *user;
-       const char  *pass;
-
-       net_t        net;
-       XML_Parser   expat;
-       xmpp_state_t state;
-       buf_t        buf;
-       int          indent;
-
-       struct xmpp_server_t *next;
-} xmpp_server_t;
+typedef struct {
+       channel_t       channel;
+       char            dest[JID_LEN];
 
-typedef struct xmpp_channel_t {
-       const char  *name;
-       const char  *channel;
-       const char  *server;
-       int          join;
-       struct xmpp_channel_t *next;
+       const char     *room;
+       int             join;
 } xmpp_channel_t;
 
-/* Local data */
-static xmpp_server_t  *servers;
-static xmpp_channel_t *channels;
+typedef struct {
+       server_t        server;
+       xmpp_channel_t  system;
+
+       int             connect;
+       const char     *host;
+       int             port;
+       const char     *muc;
+       const char     *nick;
+       const char     *jid;
+       const char     *user;
+       const char     *pass;
+
+       net_t           net;
+       XML_Parser      expat;
+       xmpp_state_t    state;
+       buf_t           buf;
+       int             indent;
+
+       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;
 
-/* Local functions */
-static xmpp_server_t *find_server(const char *name, int create)
+/* Helper functions */
+static void srv_notice(xmpp_server_t *srv, const char *fmt, ...)
 {
-       xmpp_server_t *cur = NULL, *last = NULL;
-       for (cur = servers; cur; last = cur, cur = cur->next)
-               if (match(cur->name, name))
-                       break;
-       if (!cur && create) {
-               cur = new0(xmpp_server_t);
-               cur->name = get_name(name);
-               if (last)
-                       last->next = cur;
-               else
-                       servers = cur;
-       }
-       return cur;
-}
+       static char buf[1024];
 
-static xmpp_channel_t *find_channel(const char *name, int create)
-{
-       xmpp_channel_t *cur = NULL, *last = NULL;
-       for (cur = channels; cur; last = cur, cur = cur->next)
-               if (match(cur->name, name))
-                       break;
-       if (!cur && create) {
-               cur = new0(xmpp_channel_t);
-               cur->name = get_name(name);
-               if (last)
-                       last->next = cur;
-               else
-                       channels = cur;
-       }
-       return cur;
+       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 on_start(void *_srv, const char *tag, const char **attrs)
+static void chan_notice(xmpp_channel_t *chan, const char *fmt, ...)
 {
-       xmpp_server_t *srv = _srv;
+       static char buf[1024];
 
-       /* Debug print */
-       debug("%*s<%s>",
-               srv->indent*4, "", tag);
-       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] ? "" : ">");
-       }
-       srv->indent++;
+       va_list ap;
+       va_start(ap, fmt);
+       vsnprintf(buf, sizeof(buf), fmt, ap);
+       va_end(ap);
 
-       /* Start TLS */
-       if (srv->state == XMPP_RECV_FEATURES) {
-               if (match(tag, "starttls")) {
-                       debug("xmpp: features -> starttls");
-                       srv->state = XMPP_SEND_STARTTLS;
-               }
-               if (match(tag, "mechanisms")) {
-                       debug("xmpp: features -> auth");
-                       srv->state = XMPP_SEND_AUTH;
-               }
-       }
-       if (srv->state == XMPP_RECV_PROCEED) {
-               if (match(tag, "proceed")) {
-                       debug("xmpp: proceed -> encrypt");
-                       srv->state = XMPP_ENCRYPT;
+       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 (srv->state == XMPP_RECV_SUCCESS) {
-               if (match(tag, "success")) {
-                       debug("xmpp: success -> restart");
-                       srv->state = XMPP_RESTART;
+               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 void on_end(void *_srv, const char *tag)
+static xmpp_channel_t *find_dest(xmpp_server_t *srv,
+               const char *jid, int is_muc)
 {
-       xmpp_server_t *srv = _srv;
+       static char jid_usr[JID_LEN];
+       static char jid_srv[JID_LEN];
+       static char dest[JID_LEN];
+       xmpp_channel_t *chan;
 
-       /* Debug print */
-       if (srv->buf.len) {
-               debug("%*s \"%s\"",
-                       srv->indent*4, "",
-                       (char*)srv->buf.data);
-               srv->buf.len = 0;
+       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->host);
+
+       /* Server channels */
+       if (match(jid, srv->host))
+               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;
        }
-       srv->indent--;
+
+       /* Create a new channel */
+       chan = (xmpp_channel_t *)add_channel(jid_usr, &srv->server);
+       strncpy(chan->dest, dest, JID_LEN);
+       return chan;
 }
 
-static void on_data(void *_srv, const char *data, int len)
+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,
+               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, tag, attrs, NULL, reset(&srv->buf));
+}
 
-       /* Debug print */
+static void on_data(void *_srv, const char *data, int len)
+{
+       xmpp_server_t *srv = _srv;
        append(&srv->buf, data, len);
 }
 
-static void on_recv(void *_srv, char *buf, int len)
+static void on_end(void *_srv, const char *tag)
 {
-       static char plain[AUTH_LEN];
-       static char coded[AUTH_LEN];
+       xmpp_server_t *srv = _srv;
+       xmpp_run(srv, NULL, NULL, tag, reset(&srv->buf));
+}
 
+static void on_send(void *_srv)
+{
        xmpp_server_t *srv = _srv;
+       xmpp_run(srv, NULL, NULL, NULL, NULL);
+}
 
-       /* Parse input */
+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, NULL, NULL, NULL, NULL);
+}
+
+static void on_err(void *_srv, int errno)
+{
+       xmpp_server_t *srv = _srv;
+       xmpp_run(srv, NULL, NULL, NULL, NULL);
+}
 
-       /* State machine */
-output:
+/* XMPP State machine */
+static void xmpp_run(xmpp_server_t *srv,
+               const char *start, const char **attrs,
+               const char *end, const char *data)
+{
+       xmpp_channel_t *chan = NULL;
+
+       /* 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);
+
+               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,
                    "<?xml version='1.0'?>"
@@ -203,6 +311,22 @@ output:
                        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,
                    "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")) {
@@ -210,7 +334,18 @@ output:
                        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);
@@ -223,113 +358,355 @@ output:
                        srv->state = XMPP_RECV_SUCCESS;
                }
        }
-       if (srv->state == XMPP_ENCRYPT) {
-               /* Encrypt connection */
-               net_encrypt(&srv->net);
+       if (srv->state == XMPP_RECV_SUCCESS) {
+               if (match(start, "success")) {
+                       debug("xmpp: success -> restart");
+                       srv->state = XMPP_RESTART;
+               }
+       }
 
-               /* Reset Expat */
-               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);
+       /* 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,
+                   "<iq id='bind' type='set'>"
+                   "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>"
+                   "<resource>%s</resource>"
+                   "</bind>"
+                   "</iq>",
+                   resource)) {
+                       debug("xmpp: bind -> jid");
+                       srv->state = XMPP_RECV_JID;
+               }
+       }
+       if (srv->state == XMPP_RECV_JID) {
+               if (match(start, "jid")) {
+                       debug("xmpp: jid -> presence");
+                       srv->state = XMPP_SEND_PRESENCE;
+               }
+       }
+       if (srv->state == XMPP_SEND_PRESENCE) {
+               for (channel_t *cur = channels; cur; cur = cur->next) {
+                       xmpp_channel_t *chan = (xmpp_channel_t*)cur;
+                       if (cur->server != &srv->server || !chan->join)
+                               continue;
+                       net_print(&srv->net,
+                               "<presence id='join' from='%s' to='%s/%s'>"
+                               "<x xmlns='http://jabber.org/protocol/muc'/>"
+                               "</presence>",
+                               srv->jid, chan->dest, srv->nick);
+               }
+               srv->state = XMPP_READY;
+       }
 
-               /* Reset server */
-               debug("xmpp: encrypt -> stream");
-               srv->state = XMPP_SEND_STREAM;
-               goto output;
+       /* Start message */
+       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) {
+                       strncpy(srv->msg_jid, find_attr(attrs, "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_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_RESTART) {
-               /* Reset Expat */
-               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);
 
-               /* Reset server */
-               debug("xmpp: restart -> stream");
-               srv->state = XMPP_SEND_STREAM;
-               goto output;
+       /* 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);
+               }
        }
-}
 
-static void xmpp_connect(xmpp_server_t *srv)
-{
-       /* Net connect */
-       srv->net.recv = on_recv;
-       srv->net.data = srv;
-       net_open(&srv->net, srv->host, srv->port);
-
-       /* Setup Expat */
-       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);
-
-       /* Setup server */
-       srv->state = XMPP_SEND_STREAM;
+       /* Messages */
+       if (srv->state == XMPP_IN_MESSAGE) {
+               if (match(end, "body") && data) {
+                       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);
+                       chat_recv(&chan->channel, srv->msg_from, data);
+               }
+       }
+
+       /* 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])
+                               chan_notice(chan, "%s (%s) entered room.",
+                                               srv->msg_from, alias);
+                       else
+                               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 (xmpp_server_t *cur = servers; cur; cur = cur->next) {
-               if (!match(cur->protocol, "xmpp"))
+       for (server_t *cur = servers; cur; cur = cur->next) {
+               if (cur->protocol != XMPP)
                        continue;
-               if (!cur->port)
-                       cur->port = 5222;
-               if (!cur->jid)
+
+               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->jid)
                        error("jid is required");
-               if (cur->connect)
-                       xmpp_connect(cur);
+               if (srv->connect)
+                       xmpp_run(srv, 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);
        }
 }
 
-void xmpp_config(const char *group, const char *name, const char *key, const char *value)
+server_t *xmpp_server(void)
 {
-       xmpp_server_t  *srv;
-       xmpp_channel_t *chan;
+       return new0(xmpp_server_t);
+}
 
-       if (match(group, "server")) {
-               srv = find_server(name, 1);
-               if (match(key, "protocol") &&
-                   match(value, "xmpp"))
-                       srv->protocol = get_string(value);
-               if (match(srv->protocol, "xmpp")) {
-                       if (match(key, "connect"))
-                               srv->connect = 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, "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(group, "channel")) {
-               chan = find_channel(name, 1);
-               if (match(key, "server") &&
-                   find_server(value, 0))
-                       chan->server = get_string(value);
-               if (chan->server) {
-                       if (match(key, "channel"))
-                               chan->channel = get_string(value);
-                       else if (match(key, "join"))
-                               chan->join = get_bool(value);
-               }
+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, "host"))
+                       srv->host = get_string(value);
+               else if (match(key, "port"))
+                       srv->port = get_number(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);
+       }
+       if (channel) {
+               if (match(key, "room"))
+                       chan->room = get_string(value);
+               else if (match(key, "join"))
+                       chan->join = get_bool(value);
        }
 }
 
-void xmpp_send(const char *channel, const char *msg)
+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,
+                               "<iq id='items' type='get' from='%s' to='%s'>"
+                               "<query xmlns='http://jabber.org/protocol/disco#items'/>"
+                               "</iq>",
+                               srv->jid, arg ?: srv->host);
+               }
+               else if (prefix(text, "/info", &arg)) {
+                       net_print(&srv->net,
+                               "<iq id='info' type='get' from='%s' to='%s'>"
+                               "<query xmlns='http://jabber.org/protocol/disco#info'/>"
+                               "</iq>",
+                               srv->jid, arg ?: srv->host);
+               }
+               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,
+                               "<iq id='list' type='get' from='%s' to='%s'>"
+                               "<query xmlns='http://jabber.org/protocol/disco#items'/>"
+                               "</iq>",
+                               srv->jid, chan->dest);
+               }
+               else if (prefix(text, "/join", &arg)) {
+                       if (!arg) {
+                               chan_notice(chan, "usage: /join <channel>");
+                               return;
+                       }
+                       net_print(&srv->net,
+                               "<presence id='join' from='%s' to='%s/%s'>"
+                               "<x xmlns='http://jabber.org/protocol/muc'/>"
+                               "</presence>",
+                               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 <arg>");
+                               return;
+                       }
+                       net_print(&srv->net,
+                               "<iq id='config' type='get' from='%s' to='%s'>"
+                               "<query xmlns='http://jabber.org/protocol/muc#owner'/>"
+                               "</iq>",
+                               srv->jid, chan->dest);
+               }
+               else if (prefix(text, "/query", &arg)) {
+                       if (!arg) {
+                               chan_notice(chan, "usage: /query <user>");
+                               return;
+                       }
+                       chan = find_dest(srv, arg, 0);
+                       chan_notice(chan, "User: %s", arg);
+               }
+               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,
+                               "<message id='chat' from='%s' to='%s' type='groupchat'>"
+                               "<body>%s</body>"
+                               "</message>",
+                               srv->jid, chan->dest, text);
+               } else {
+                       net_print(&srv->net,
+                               "<message id='chat' from='%s' to='%s'>"
+                               "<body>%s</body>"
+                               "</message>",
+                               srv->jid, chan->dest, text);
+                       chat_recv(channel, srv->nick, text);
+               }
+       }
 }
 
 void xmpp_exit(void)