#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;
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;
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;
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;
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)))
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 */
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);
}
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)