]> Pileus Git - ~andy/lamechat/blobdiff - xmpp.c
XMPP MUC
[~andy/lamechat] / xmpp.c
diff --git a/xmpp.c b/xmpp.c
index 4dbcd170e2d9c45b66c4097827363259b3ee2ebe..0656b9264f11916b66277cd2fefede52477d997d 100644 (file)
--- a/xmpp.c
+++ b/xmpp.c
 #include <expat.h>
 
 #include "util.h"
+#include "chat.h"
 #include "conf.h"
+#include "net.h"
 
-/* XMPP Constants */
-#define BUF_LEN 1024
+/* Constants */
+#define AUTH_LEN 512
 
 /* XMPP types */
 typedef enum {
        XMPP_DEAD,
-       XMPP_STREAM,
-       XMPP_STARTTLS,
-       XMPP_PROCEED,
+       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_PRESENCE,
+       XMPP_ENCRYPT,
+       XMPP_RESTART,
        XMPP_READY,
 } xmpp_state_t;
 
@@ -47,21 +57,19 @@ typedef struct xmpp_server_t {
        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;
-       poll_t       poll;
+       buf_t        buf;
+       int          indent;
 
-       char        in_buf[BUF_LEN];
-       int         in_len;
-       char        out_buf[BUF_LEN];
-       int         out_pos;
-       int         out_len;
-
-       buf_t       buf;
-       int         indent;
+       int          in_error;
 
        struct xmpp_server_t *next;
 } xmpp_server_t;
@@ -79,6 +87,14 @@ static xmpp_server_t  *servers;
 static xmpp_channel_t *channels;
 
 /* Local functions */
+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;
+}
+
 static xmpp_server_t *find_server(const char *name, int create)
 {
        xmpp_server_t *cur = NULL, *last = NULL;
@@ -113,31 +129,6 @@ static xmpp_channel_t *find_channel(const char *name, int create)
        return cur;
 }
 
-static int send_xml(xmpp_server_t *srv, const char *fmt, ...)
-{
-       int len, sent;
-       va_list ap;
-       if (srv->out_len)
-               return 0;
-
-       va_start(ap, fmt);
-       len = vsnprintf(srv->out_buf, BUF_LEN, fmt, ap);
-       va_end(ap);
-       if (len <= 0)
-               return 0;
-       if (len > BUF_LEN)
-               len = BUF_LEN;
-
-       //debug("xmpp send: [%.*s]", len, srv->out_buf);
-       sent = send(srv->poll.fd, srv->out_buf, len, 0);
-       if (sent == len)
-               return 1;
-
-       srv->out_len = len;
-       srv->out_pos = sent;
-       return 0;
-}
-
 static void on_start(void *_srv, const char *tag, const char **attrs)
 {
        xmpp_server_t *srv = _srv;
@@ -146,136 +137,226 @@ static void on_start(void *_srv, const char *tag, const char **attrs)
        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+6, "",
+               debug("%*s%s=\"%s\"%s",
+                       srv->indent*4+8, "",
                        attrs[i+0], attrs[i+1],
                        attrs[i+2] ? "" : ">");
        }
        srv->indent++;
 
        /* Start TLS */
-       if (srv->state == XMPP_STARTTLS)
-               if (match(tag, "starttls"))
-                       srv->state = XMPP_PROCEED;
+       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 (match(tag, "bind")) {
+                       debug("xmpp: features -> bind");
+                       srv->state = XMPP_SEND_BIND;
+               }
+       }
+       if (srv->state == XMPP_RECV_PROCEED) {
+               if (match(tag, "proceed")) {
+                       debug("xmpp: proceed -> encrypt");
+                       srv->state = XMPP_ENCRYPT;
+               }
+       }
+
+       /* Authentication */
+       if (srv->state == XMPP_RECV_SUCCESS) {
+               if (match(tag, "success")) {
+                       debug("xmpp: success -> restart");
+                       srv->state = XMPP_RESTART;
+               }
+       }
+       if (srv->state == XMPP_RECV_JID) {
+               if (match(tag, "jid")) {
+                       debug("xmpp: jid -> presence");
+                       srv->state = XMPP_PRESENCE;
+               }
+       }
+
+       /* Info queries */
+       if (srv->state == XMPP_READY) {
+               if (match(tag, "item"))
+                       chat_notice(NULL, NULL, "item: [%s] %s",
+                                       find_attr(attrs, "jid"),
+                                       find_attr(attrs, "name"));
+               if (match(tag, "identity"))
+                       chat_notice(NULL, NULL, "identity: %s",
+                                       find_attr(attrs, "name"));
+               if (match(tag, "feature"))
+                       chat_notice(NULL, NULL, "feature: %s",
+                                       find_attr(attrs, "var"));
+       }
+
+       /* Error handling */
+       if (match(tag, "stream:error"))
+               srv->in_error = 1;
 }
 
 static void on_end(void *_srv, const char *tag)
 {
        xmpp_server_t *srv = _srv;
+       const char *data = srv->buf.data;
 
        /* Debug print */
+       if (srv->buf.len) {
+               debug("%*s \"%s\"",
+                       srv->indent*4, "",
+                       (char*)srv->buf.data);
+               srv->buf.len = 0;
+       }
        srv->indent--;
+
+       /* Receive messages */
+       if (srv->state == XMPP_READY) {
+               if (match(tag, "body")) {
+                       debug("xmpp: jid -> ready");
+                       chat_recv("#test", NULL, data);
+               }
+       }
+
+       /* Error handling */
+       if (match(tag, "stream:error"))
+               srv->in_error = 0;
+       if (srv->in_error) {
+               if (match(tag, "text")) {
+                       debug("xmpp: error: %s", data);
+                       chat_notice(NULL, NULL, "error: %s", data);
+               }
+       }
 }
 
 static void on_data(void *_srv, const char *data, int len)
 {
+       xmpp_server_t *srv = _srv;
+
+       /* Debug print */
+       append(&srv->buf, data, len);
 }
 
-static void on_poll(void *_srv, int fd)
+static void on_recv(void *_srv, char *buf, int len)
 {
+       static char plain[AUTH_LEN];
+       static char coded[AUTH_LEN];
+       const char *resource;
+
        xmpp_server_t *srv = _srv;
-       static char buf[BUF_LEN];
-       int len;
-
-       /* Handle Input */
-       len = recv(srv->poll.fd, buf, sizeof(buf), 0);
-       if ((len < 0) && (errno != EAGAIN))
-               debug("xmpp recv: error, %s -- %s", srv->name, strerror(errno));
-       if (len == 0) {
-               debug("xmpp recv: close, %s", srv->name);
-               srv->state = XMPP_DEAD;
-               poll_del(&srv->poll);
-               return;
-       }
-       if (len > 0) {
-               //debug("xmpp recv: ready, %s -- [%.*s]", srv->name, len, buf);
+
+       /* Parse input */
+       if (len > 0)
                XML_Parse(srv->expat, buf, len, 0);
-       }
 
-       /* Handle Output */
-       if (srv->out_len > 0) {
-               len = send(fd, &srv->out_buf[srv->out_pos],
-                              srv->out_len-srv->out_pos, 0);
-               if (len > 0)
-                       srv->out_pos += len;
-               if (srv->out_pos == srv->out_len) {
-                       srv->out_pos = 0;
-                       srv->out_len = 0;
+       /* State machine */
+output:
+       if (srv->state == XMPP_SEND_STREAM) {
+               if (net_print(&srv->net,
+                   "<?xml version='1.0'?>"
+                   "<stream:stream"
+                   " from='%s'"
+                   " to='%s'"
+                   " version='1.0'"
+                   " xml:lang='en'"
+                   " xmlns='jabber:client'"
+                   " xmlns:stream='http://etherx.jabber.org/streams'>",
+                   srv->jid, srv->host)) {
+                       debug("xmpp: stream -> features");
+                       srv->state = XMPP_RECV_FEATURES;
                }
        }
-
-       /* Handle Errors */
-       int err = 0;
-       socklen_t elen = sizeof(err);
-       if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &elen))
-               error("Error getting socket opt");
-       if (err) {
-               debug("disconnect: %s -- %s", srv->name, strerror(err));
-               srv->state = XMPP_DEAD;
-               poll_del(&srv->poll);
-               return;
+       if (srv->state == XMPP_SEND_STARTTLS) {
+               if (net_print(&srv->net,
+                   "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")) {
+                       debug("xmpp: startls -> proceed");
+                       srv->state = XMPP_RECV_PROCEED;
+               }
        }
-
-       /* State machine */
-       if (srv->state == XMPP_STREAM) {
-               if (send_xml(srv,
-                    "<?xml version='1.0'?>"
-                    "<stream:stream"
-                    " from='%s'"
-                    " to='%s'"
-                    " version='1.0'"
-                    " xml:lang='en'"
-                    " xmlns='jabber:client'"
-                    " xmlns:stream='http://etherx.jabber.org/streams'>",
-                    srv->jid, srv->host))
-                       srv->state = XMPP_STARTTLS;
+       if (srv->state == XMPP_SEND_AUTH) {
+               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,
+                   "<auth"
+                   " xmlns='urn:ietf:params:xml:ns:xmpp-sasl'"
+                   " mechanism='PLAIN'>%.*s</auth>",
+                       len, coded)) {
+                       debug("xmpp: auth -> success");
+                       srv->state = XMPP_RECV_SUCCESS;
+               }
        }
-       if (srv->state == XMPP_PROCEED) {
-               if (send_xml(srv, "<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")) {
-                       debug("ready");
-                       srv->state = XMPP_READY;
+       if (srv->state == XMPP_SEND_BIND) {
+               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;
                }
        }
-
-       /* Enable output poll */
-       if (srv->out_len) {
-               poll_ctl(&srv->poll, 1, 1, 1);
-       } else {
-               poll_ctl(&srv->poll, 1, 0, 1);
+       if (srv->state == XMPP_PRESENCE) {
+               for (xmpp_channel_t *chan = channels; chan; chan = chan->next) {
+                       if (!chan->join)
+                               continue;
+                       net_print(&srv->net,
+                               "<presence id='join' from='%s' to='%s@%s/%s'>"
+                               "<x xmlns='http://jabber.org/protocol/muc'/>"
+                               "</presence>",
+                               srv->jid, chan->channel, srv->muc, srv->nick);
+               }
+               srv->state = XMPP_READY;
+       }
+       if (srv->state == XMPP_ENCRYPT) {
+               /* Encrypt connection */
+               net_encrypt(&srv->net);
+
+               /* 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: encrypt -> stream");
+               srv->state = XMPP_SEND_STREAM;
+               goto output;
+       }
+       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;
        }
 }
 
 static void xmpp_connect(xmpp_server_t *srv)
 {
-       int sock, flags;
-       struct addrinfo *addrs = NULL;
-       struct addrinfo hints = {};
-       char service[16];
-
-       snprintf(service, sizeof(service), "%d", srv->port);
-       hints.ai_family   = AF_INET;
-       hints.ai_socktype = SOCK_STREAM;
-
-       /* Setup address */
-       if (getaddrinfo(srv->host, service, &hints, &addrs))
-               error("Error getting xmpp address info");
-
-       if ((sock = socket(addrs->ai_family,
-                          addrs->ai_socktype,
-                          addrs->ai_protocol)) < 0)
-               error("Error opening xmpp socket");
-
-       if ((flags = fcntl(sock, F_GETFL, 0)) < 0)
-               error("Error getting xmpp socket flags");
-
-       if (fcntl(sock, F_SETFL, flags|O_NONBLOCK) < 0)
-               error("Error setting xmpp socket non-blocking");
-
-       if (connect(sock, addrs->ai_addr, addrs->ai_addrlen) < 0)
-               if (errno != EINPROGRESS)
-                       error("Error connecting socket");
-
-       freeaddrinfo(addrs);
+       /* 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)))
@@ -286,8 +367,7 @@ static void xmpp_connect(xmpp_server_t *srv)
        XML_SetCharacterDataHandler(srv->expat, on_data);
 
        /* Setup server */
-       srv->state = XMPP_STREAM;
-       poll_add(&srv->poll, sock, on_poll, srv);
+       srv->state = XMPP_SEND_STREAM;
 }
 
 /* XMPP functions */
@@ -322,8 +402,14 @@ void xmpp_config(const char *group, const char *name, const char *key, const cha
                                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);
                }
@@ -343,6 +429,60 @@ void xmpp_config(const char *group, const char *name, const char *key, const cha
 
 void xmpp_send(const char *channel, const char *msg)
 {
+       const char *arg;
+
+       xmpp_channel_t *chan = find_channel(channel, 0);
+       if (!chan)
+               return;
+       xmpp_server_t *srv = find_server(chan->server, 0);
+       if (!srv || !srv->protocol)
+               return;
+
+       /* Handle commands */
+       if (msg[0] == '/') {
+               if (prefix(msg, "/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(msg, "/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(msg, "/list", &arg)) {
+                       net_print(&srv->net,
+                               "<iq id='list' type='get' from='%s' to='%s'>"
+                               "<query xmlns='http://jabber.org/protocol/disco#items' />"
+                               "</iq>",
+                               srv->jid, arg ?: srv->muc);
+               }
+               else if (prefix(msg, "/join", &arg)) {
+                       if (!arg) {
+                               chat_notice(NULL, NULL, "usage: /join <channel>");
+                               return;
+                       }
+                       net_print(&srv->net,
+                               "<presence id='join' from='%s' to='%s@%s/%s'>"
+                               "<x xmlns='http://jabber.org/protocol/muc'/>"
+                               "</presence>",
+                               srv->jid, arg, srv->muc, srv->nick);
+               }
+               else {
+                       debug("unknown: [%s]", msg);
+                       chat_notice(NULL, NULL,
+                               "unknown command %s", msg);
+               }
+       } else {
+               debug("message: [%s]", msg);
+               net_print(&srv->net,
+                       "<message to='%s'><body>%s</body></message>",
+                       "andy@pileus.org", msg);
+       }
 }
 
 void xmpp_exit(void)