/* * $Id: xmpp_server.c 1437 2006-12-20 10:42:03Z miconda $ * * XMPP Module * This file is part of openser, a free SIP server. * * Copyright (C) 2006 Voice Sistem S.R.L. * * openser 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 2 of the License, or * (at your option) any later version * * openser 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Author: Andreea Spirea * */ /* * An inbound SIP message: * from sip:user1@domain1 to sip:user2*domain2@gateway_domain * is translated to an XMPP message: * from user1*domain1@xmpp_domain to user2@domain2 * * An inbound XMPP message: * from user1@domain1 to user2*domain2@xmpp_domain * is translated to a SIP message: * from sip:user1*domain1@gateway_domain to sip:user2@domain2 * * Where '*' is the domain_separator, and gateway_domain and * xmpp_domain are defined below. */ /* * 2-way dialback sequence with xmppd2: * * Originating server (us) Receiving server (them) Authoritative server (us) * ----------------------- ----------------------- ------------------------- * | | | * | establish connection | | * |------------------------------>| | * | send stream header | | * |------------------------------>| | * | send stream header | | * |<------------------------------| | * | send db:result request | | * |------------------------------>| | * | establish connection | * |------------------------------>| * | send stream header | * |------------------------------>| * | send stream header | * |<------------------------------| * | send db:result request | * |------------------------------>| * | send db:verify request | * |------------------------------>| * | send db:verify response | * |<------------------------------| * | send db:result response | * |------------------------------>| * | send db:verify request | * |<------------------------------| * | send db:verify response | * |------------------------------>| * | send db:result response | * |<------------------------------| * : : : * : : : * | outgoing | : * |------------------------------>| : * | incoming | * |------------------------------>| */ #include #include #include #include #include "../../sr_module.h" #include "xmpp.h" #include "xmpp_api.h" #include "network.h" #include "xode.h" #include /* XXX hack */ #define DB_KEY "this-be-a-random-key" #define CONN_DEAD 0 #define CONN_INBOUND 1 #define CONN_OUTBOUND 2 struct xmpp_private_data { int fd; /* outgoing stream socket */ int listen_fd; /* listening socket */ int in_fd; /* incoming stream socket */ int running; }; struct xmpp_connection { struct xmpp_connection *next; char *domain; int type; int fd; char *stream_id; xode_pool pool; xode_stream stream; xode todo; /* backlog of outgoing messages, if any */ }; static char local_secret[64] = { 0, }; static void in_stream_node_callback(int type, xode node, void *arg); static void out_stream_node_callback(int type, xode node, void *arg); static struct xmpp_connection *conn_list = NULL; static struct xmpp_connection *conn_new(int type, int fd, char *domain) { struct xmpp_connection *conn = NULL; conn = malloc(sizeof(struct xmpp_connection)); if(conn==NULL) { LOG(L_ERR, "xmpp: conn_new: out of memory\n"); return NULL; } memset(conn, 0, sizeof(struct xmpp_connection)); conn->domain = domain ? strdup(domain) : NULL; conn->type = type; conn->fd = fd; conn->todo = xode_new_tag("todo"); conn->pool = xode_pool_new(); conn->stream = xode_stream_new(conn->pool, (type==CONN_INBOUND)?in_stream_node_callback:out_stream_node_callback, conn); conn->next = conn_list; conn_list = conn; return conn; } static void conn_free(struct xmpp_connection *conn) { struct xmpp_connection **last_p, *link; last_p = &conn_list; for (link = conn_list; link; link = link->next) { if (link == conn) { *last_p = link->next; break; } last_p = &link->next; } if (conn->todo) xode_free(conn->todo); xode_pool_free(conn->pool); if (conn->fd != -1) close(conn->fd); if (conn->stream_id) free(conn->stream_id); if (conn->domain) free(conn->domain); free(conn); } static struct xmpp_connection *conn_find_domain(char *domain, int type) { struct xmpp_connection *conn; for (conn = conn_list; conn; conn = conn->next) if (conn->domain && !strcasecmp(conn->domain, domain) && conn->type == type) return conn; return NULL; } /* static struct xmpp_connection *conn_find_fd(int fd) { struct xmpp_connection *conn; for (conn = conn_list; conn; conn = conn->next) if (conn->fd == fd) return conn; return NULL; } */ /*****************************************************************************/ static int xode_send(int fd, xode x) { char *str = xode_to_str(x); int len = strlen(str); DBG("xmpp: xode_send->%d [%s]\n", fd, str); if (net_send(fd, str, len) != len) { LOG(L_ERR, "xmpp: send() error: %s\n", strerror(errno)); return -1; } return len; } static int xode_send_domain(char *domain, xode x) { struct xmpp_connection *conn; if ((conn = conn_find_domain(domain, CONN_OUTBOUND))) { xode_send(conn->fd, x); xode_free(x); } else { if((conn = conn_new(CONN_OUTBOUND, -1, domain))==0) return -1; xode_insert_node(conn->todo, x); } return 1; } static void out_stream_node_callback(int type, xode node, void *arg) { struct xmpp_connection *conn = (struct xmpp_connection *) arg; struct xmpp_connection *in_conn = NULL; char *tag; xode x; DBG("xmpp: outstream callback: %d: %s\n", type, node?xode_get_name(node):"n/a"); if (conn->domain) in_conn = conn_find_domain(conn->domain, CONN_INBOUND); switch (type) { case XODE_STREAM_ROOT: x = xode_new_tag("db:result"); xode_put_attrib(x, "xmlns:db", "jabber:server:dialback"); xode_put_attrib(x, "from", xmpp_domain); xode_put_attrib(x, "to", conn->domain); //xode_insert_cdata(x, DB_KEY, -1); xode_insert_cdata(x, db_key(local_secret, conn->domain, xode_get_attrib(node, "id")), -1); xode_send(conn->fd, x); xode_free(x); break; case XODE_STREAM_NODE: tag = xode_get_name(node); if (!strcmp(tag, "db:verify")) { char *from = xode_get_attrib(node, "from"); char *to = xode_get_attrib(node, "to"); char *id = xode_get_attrib(node, "id"); char *type = xode_get_attrib(node, "type"); /* char *cdata = xode_get_data(node); */ if (!strcmp(type, "valid") || !strcmp(type, "invalid")) { /* got a reply, report it */ x = xode_new_tag("db:result"); xode_put_attrib(x, "xmlns:db", "jabber:server:dialback"); xode_put_attrib(x, "from", to); xode_put_attrib(x, "to", from); xode_put_attrib(x, "id", id); xode_put_attrib(x, "type", type); if (in_conn) xode_send(in_conn->fd, x); else LOG(L_ERR, "xmpp: need to send reply to domain '%s'," " but no inbound connection found\n", from); xode_free(x); } } else if (!strcmp(tag, "db:result")) { char *type = xode_get_attrib(node, "type"); if (type && !strcmp(type, "valid")) { /* the remote server has successfully authenticated us, * we can now send data */ for (x = xode_get_firstchild(conn->todo); x; x = xode_get_nextsibling(x)) { DBG("xmpp: sending todo tag '%s'\n", xode_get_name(x)); xode_send(conn->fd, x); } xode_free(conn->todo); conn->todo = NULL; } } break; case XODE_STREAM_ERROR: LOG(L_ERR, "xmpp: outstream error\n"); /* fall-through */ case XODE_STREAM_CLOSE: conn->type = CONN_DEAD; break; } xode_free(node); } static void in_stream_node_callback(int type, xode node, void *arg) { struct xmpp_connection *conn = (struct xmpp_connection *) arg; char *tag; xode x; DBG("xmpp: instream callback: %d: %s\n", type, node ? xode_get_name(node) : "n/a"); switch (type) { case XODE_STREAM_ROOT: conn->stream_id = strdup(random_secret()); net_printf(conn->fd, "" "", conn->stream_id, xmpp_domain); net_printf(conn->fd,""); break; case XODE_STREAM_NODE: tag = xode_get_name(node); if (!strcmp(tag, "db:result")) { char *from = xode_get_attrib(node, "from"); char *to = xode_get_attrib(node, "to"); /* char *id = xode_get_attrib(node, "id"); */ char *type = xode_get_attrib(node, "type"); char *cdata = xode_get_data(node); if (!type) { if (conn->domain) { DBG("xmpp: warning: connection %d has old domain '%s'\n", conn->fd, conn->domain); free(conn->domain); } conn->domain = strdup(from); DBG("xmpp: connection %d set domain '%s'\n", conn->fd, conn->domain); /* it's a request; send verification over outgoing connection */ x = xode_new_tag("db:verify"); xode_put_attrib(x, "xmlns:db", "jabber:server:dialback"); xode_put_attrib(x, "from", to); xode_put_attrib(x, "to", from); //xode_put_attrib(x, "id", "someid"); /* XXX fix ID */ xode_put_attrib(x, "id", conn->stream_id); xode_insert_cdata(x, cdata, -1); xode_send_domain(from, x); } } else if (!strcmp(tag, "db:verify")) { char *from = xode_get_attrib(node, "from"); char *to = xode_get_attrib(node, "to"); char *id = xode_get_attrib(node, "id"); char *type = xode_get_attrib(node, "type"); char *cdata = xode_get_data(node); if (!type) { /* it's a request */ x = xode_new_tag("db:verify"); xode_put_attrib(x, "xmlns:db", "jabber:server:dialback"); xode_put_attrib(x, "from", to); xode_put_attrib(x, "to", from); xode_put_attrib(x, "id", id); //if (cdata && !strcmp(cdata, DB_KEY)) { if (cdata && !strcmp(cdata, db_key(local_secret, from, id))) { xode_put_attrib(x, "type", "valid"); } else { xode_put_attrib(x, "type", "invalid"); } xode_send(conn->fd, x); xode_free(x); } } else if (!strcmp(tag, "message")) { char *from = xode_get_attrib(node, "from"); char *to = xode_get_attrib(node, "to"); char *type = xode_get_attrib(node, "type"); xode body = xode_get_tag(node, "body"); char *msg; if (!type) type = "chat"; if (!strcmp(type, "error")) { DBG("xmpp: received message error stanza\n"); goto out; } if (!from || !to || !body) { DBG("xmpp: invalid attributes\n"); goto out; } if (!(msg = xode_get_data(body))) msg = ""; xmpp_send_sip_msg( encode_uri_xmpp_sip(from), decode_uri_xmpp_sip(to), msg); } else if (!strcmp(tag, "presence")) { /* run presence callbacks */ } break; break; case XODE_STREAM_ERROR: LOG(L_ERR, "xmpp: instream error\n"); /* fall-through */ case XODE_STREAM_CLOSE: conn->type = CONN_DEAD; break; } out: xode_free(node); } static void do_send_message_server(struct xmpp_pipe_cmd *cmd) { char *domain; xode x; DBG("xmpp: do_send_message_server from=[%s] to=[%s] body=[%s]\n", cmd->from, cmd->to, cmd->body); x = xode_new_tag("message"); xode_put_attrib(x, "xmlns", "jabber:client"); xode_put_attrib(x, "id", cmd->id); // XXX xode_put_attrib(x, "from", encode_uri_sip_xmpp(cmd->from)); xode_put_attrib(x, "to", decode_uri_sip_xmpp(cmd->to)); xode_put_attrib(x, "type", "chat"); xode_insert_cdata(xode_insert_tag(x, "body"), cmd->body, -1); domain = extract_domain(decode_uri_sip_xmpp(cmd->to)); xode_send_domain(domain, x); } int xmpp_server_child_process(int data_pipe) { int rv; int listen_fd; fd_set fdset; struct xmpp_connection *conn; snprintf(local_secret, sizeof(local_secret), "%s", random_secret()); while ((listen_fd = net_listen(xmpp_domain, xmpp_port)) < 0) { /* ugh. */ sleep(3); } while (1) { FD_ZERO(&fdset); FD_SET(data_pipe, &fdset); FD_SET(listen_fd, &fdset); /* check for dead connections */ for (conn = conn_list; conn; ) { struct xmpp_connection *next = conn->next; if (conn->type == CONN_DEAD) conn_free(conn); conn = next; } for (conn = conn_list; conn; conn = conn->next) { /* check if we need to set up a connection */ if (conn->type == CONN_OUTBOUND && conn->fd == -1) { if ((conn->fd = net_connect(conn->domain, xmpp_port)) >= 0) { net_printf(conn->fd, "" "", conn->domain, xmpp_domain); net_printf(conn->fd, ""); } else { conn->type = CONN_DEAD; } } if (conn->fd != -1) FD_SET(conn->fd, &fdset); } rv = select(FD_SETSIZE, &fdset, NULL, NULL, NULL); if (rv < 0) { LOG(L_ERR, "xmpp: select() error: %s\n", strerror(errno)); } else if (!rv) { /* timeout */ } else { for (conn = conn_list; conn; conn = conn->next) { if (conn->fd != -1 && FD_ISSET(conn->fd, &fdset)) { char *buf = net_read_static(conn->fd); if (!buf) { conn->type = CONN_DEAD; } else { DBG("xmpp: stream (fd %d, domain '%s') read\n[%s]\n", conn->fd, conn->domain, buf); xode_stream_eat(conn->stream, buf, strlen(buf)); } } } if (FD_ISSET(listen_fd, &fdset)) { struct sockaddr_in sin; unsigned int len = sizeof(sin); int fd; if ((fd = accept(listen_fd,(struct sockaddr*)&sin, &len))<0) { LOG(L_ERR, "xmpp: cannot accept(): %s\n", strerror(errno)); } else { DBG("xmpp: accept()ed connection from %s:%d\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); conn_new(CONN_INBOUND, fd, NULL); } } if (FD_ISSET(data_pipe, &fdset)) { struct xmpp_pipe_cmd *cmd; if (read(data_pipe, &cmd, sizeof(cmd)) != sizeof(cmd)) { LOG(L_ERR, "xmpp: unable to read from command pipe: %s\n", strerror(errno)); } else { DBG("xmpp: got pipe cmd %d\n", cmd->type); switch (cmd->type) { case XMPP_PIPE_SEND_MESSAGE: do_send_message_server(cmd); break; case XMPP_PIPE_SEND_PACKET: case XMPP_PIPE_SEND_PSUBSCRIBE: case XMPP_PIPE_SEND_PNOTIFY: break; } xmpp_free_pipe_cmd(cmd); } } } } return 0; }