Support routing text messages outside of a call.
authorRussell Bryant <russell@russellbryant.com>
Wed, 1 Jun 2011 21:31:40 +0000 (21:31 +0000)
committerRussell Bryant <russell@russellbryant.com>
Wed, 1 Jun 2011 21:31:40 +0000 (21:31 +0000)
Asterisk now has protocol independent support for processing text messages
outside of a call.  Messages are routed through the Asterisk dialplan.
SIP MESSAGE and XMPP are currently supported.  There are options in sip.conf
and jabber.conf that enable these features.

There is a new application, MessageSend().  There are two new functions,
MESSAGE() and MESSAGE_DATA().  Documentation will be available on
the project wiki, wiki.asterisk.org.

Thanks to Terry Wilson for the assistance with development and to David Vossel
for helping with some additional testing.

Review: https://reviewboard.asterisk.org/r/1042/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@321546 65c4cc65-6c06-0410-ace0-fbb531ad65f3

13 files changed:
CHANGES
channels/chan_sip.c
channels/sip/include/sip.h
configs/jabber.conf.sample
configs/sip.conf.sample
include/asterisk/_private.h
include/asterisk/channel.h
include/asterisk/jabber.h
include/asterisk/message.h [new file with mode: 0644]
main/asterisk.c
main/channel.c
main/message.c [new file with mode: 0644]
res/res_jabber.c

diff --git a/CHANGES b/CHANGES
index 231d1b6..a010779 100644 (file)
--- a/CHANGES
+++ b/CHANGES
 --- Functionality changes from Asterisk 1.8 to Asterisk 1.10 -----------------
 ------------------------------------------------------------------------------
 
+Text Messaging
+--------------
+ * Asterisk now has protocol independent support for processing text messages
+   outside of a call.  Messages are routed through the Asterisk dialplan.
+   SIP MESSAGE and XMPP are currently supported.  There are options in
+   jabber.conf and sip.conf to allow enabling these features.
+     -> jabber.conf: see the "sendtodialplan" and "context" options.
+     -> sip.conf: see the "accept_outofcall_message" and "auth_message_requests"
+        options.
+   The MESSAGE() dialplan function and MessageSend() application have been
+   added to go along with this functionality.  More detailed usage information
+   can be found on the Asterisk wiki (http://wiki.asterisk.org/).
+
 Parking
 -------
  * parkedmusicclass can now be set for non-default parking lots.
index cbfb8f7..6556b3a 100644 (file)
@@ -263,6 +263,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/cel.h"
 #include "asterisk/data.h"
 #include "asterisk/aoc.h"
+#include "asterisk/message.h"
 #include "sip/include/sip.h"
 #include "sip/include/globals.h"
 #include "sip/include/config_parser.h"
@@ -1252,7 +1253,8 @@ static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int old
 static int transmit_info_with_aoc(struct sip_pvt *p, struct ast_aoc_decoded *decoded);
 static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration);
 static int transmit_info_with_vidupdate(struct sip_pvt *p);
-static int transmit_message_with_text(struct sip_pvt *p, const char *text);
+static int transmit_message_with_text(struct sip_pvt *p, const char *text, int init, int auth);
+static int transmit_message_with_msg(struct sip_pvt *p, const struct ast_msg *msg);
 static int transmit_refer(struct sip_pvt *p, const char *dest);
 static int transmit_notify_with_mwi(struct sip_pvt *p, int newmsgs, int oldmsgs, const char *vmexten);
 static int transmit_notify_with_sipfrag(struct sip_pvt *p, int cseq, char *message, int terminate);
@@ -1261,7 +1263,7 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char *
 static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno);
 static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno);
 static void copy_request(struct sip_request *dst, const struct sip_request *src);
-static void receive_message(struct sip_pvt *p, struct sip_request *req);
+static void receive_message(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e);
 static void parse_moved_contact(struct sip_pvt *p, struct sip_request *req, char **name, char **number, int set_call_forward);
 static int sip_send_mwi_to_peer(struct sip_peer *peer, const struct ast_event *event, int cache_only);
 
@@ -1532,7 +1534,7 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, int
 static int handle_request_bye(struct sip_pvt *p, struct sip_request *req);
 static int handle_request_register(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *sin, const char *e);
 static int handle_request_cancel(struct sip_pvt *p, struct sip_request *req);
-static int handle_request_message(struct sip_pvt *p, struct sip_request *req);
+static int handle_request_message(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e);
 static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, int seqno, const char *e);
 static void handle_request_info(struct sip_pvt *p, struct sip_request *req);
 static int handle_request_options(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e);
@@ -1547,6 +1549,7 @@ static void handle_response_notify(struct sip_pvt *p, int resp, const char *rest
 static void handle_response_refer(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
 static void handle_response_subscribe(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
 static int handle_response_register(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
+static void handle_response_message(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
 static void handle_response(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
 
 /*------ SRTP Support -------- */
@@ -4444,7 +4447,7 @@ static int sip_sendtext(struct ast_channel *ast, const char *text)
        }
        if (debug)
                ast_verbose("Sending text %s on %s\n", text, ast->name);
-       transmit_message_with_text(dialog, text);
+       transmit_message_with_text(dialog, text, 0, 0);
        return 0;
 }
 
@@ -13117,16 +13120,49 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char *
        return res;
 }
 
-/*! \brief Transmit text with SIP MESSAGE method */
-static int transmit_message_with_text(struct sip_pvt *p, const char *text)
+/*! \brief Transmit text with SIP MESSAGE method based on an ast_msg */
+static int transmit_message_with_msg(struct sip_pvt *p, const struct ast_msg *msg)
 {
        struct sip_request req;
-       
-       reqprep(&req, p, SIP_MESSAGE, 0, 1);
-       add_text(&req, text);
+       struct ast_msg_var_iterator *i;
+       const char *var, *val;
+
+       initreqprep(&req, p, SIP_MESSAGE, NULL);
+       ast_string_field_set(p, msg_body, ast_msg_get_body(msg));
+       initialize_initreq(p, &req);
+
+       i = ast_msg_var_iterator_init(msg);
+       while (ast_msg_var_iterator_next(msg, i, &var, &val)) {
+               add_header(&req, var, val);
+               ast_msg_var_unref_current(i);
+       }
+       ast_msg_var_iterator_destroy(i);
+
+       add_text(&req, ast_msg_get_body(msg));
+
        return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
 }
 
+/*! \brief Transmit text with SIP MESSAGE method */
+static int transmit_message_with_text(struct sip_pvt *p, const char *text, int init, int auth)
+{
+       struct sip_request req;
+
+       if (init) {
+               initreqprep(&req, p, SIP_MESSAGE, NULL);
+               ast_string_field_set(p, msg_body, text);
+               initialize_initreq(p, &req);
+       } else {
+               reqprep(&req, p, SIP_MESSAGE, 0, 1);
+       }
+       if (auth) {
+               return transmit_request_with_auth(p, SIP_MESSAGE, p->ocseq, XMIT_RELIABLE, 0);
+       } else {
+               add_text(&req, text);
+               return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
+       }
+}
+
 /*! \brief Allocate SIP refer structure */
 static int sip_refer_allocate(struct sip_pvt *p)
 {
@@ -13357,6 +13393,10 @@ static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, int seqn
                add_header(&resp, "X-Asterisk-HangupCauseCode", buf);
        }
 
+       if (sipmethod == SIP_MESSAGE) {
+               add_text(&resp, p->msg_body);
+       }
+
        return send_request(p, &resp, reliable, seqno ? seqno : p->ocseq);      
 }
 
@@ -15912,15 +15952,52 @@ static int get_msg_text(char *buf, int len, struct sip_request *req, int addnewl
        return 0;
 }
 
+static int get_msg_text2(struct ast_str **buf, struct sip_request *req, int addnewline)
+{
+       int i, res = 0;
+
+       ast_str_reset(*buf);
+
+       for (i = 0; res >= 0 && i < req->lines; i++) {
+               const char *line = REQ_OFFSET_TO_STR(req, line[i]);
+
+               res = ast_str_append(buf, 0, "%s%s", line, addnewline ? "\n" : "");
+       }
+
+       return res < 0 ? -1 : 0;
+}
+
+static void set_message_vars_from_req(struct ast_msg *msg, struct sip_request *req)
+{
+       size_t x;
+       char name_buf[1024] = "";
+       char val_buf[1024] = "";
+       char *c;
+
+       for (x = 0; x < req->headers; x++) {
+               const char *header = REQ_OFFSET_TO_STR(req, header[x]);
+               if ((c = strchr(header, ':'))) {
+                       ast_copy_string(name_buf, header, MIN((c - header + 1), sizeof(name_buf)));
+                       ast_copy_string(val_buf, ast_skip_blanks(c + 1), sizeof(val_buf));
+                       ast_trim_blanks(name_buf);
+                       ast_msg_set_var(msg, name_buf, val_buf);
+               }
+       }
+}
+
+AST_THREADSTORAGE(sip_msg_buf);
 
 /*! \brief  Receive SIP MESSAGE method messages
 \note  We only handle messages within current calls currently
        Reference: RFC 3428 */
-static void receive_message(struct sip_pvt *p, struct sip_request *req)
+static void receive_message(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e)
 {
-       char buf[1400]; 
+       struct ast_str *buf;
        struct ast_frame f;
        const char *content_type = get_header(req, "Content-Type");
+       struct ast_msg *msg;
+       int res;
+       char *from, *to;
 
        if (strncmp(content_type, "text/plain", strlen("text/plain"))) { /* No text/plain attachment */
                transmit_response(p, "415 Unsupported Media Type", req); /* Good enough, or? */
@@ -15929,7 +16006,15 @@ static void receive_message(struct sip_pvt *p, struct sip_request *req)
                return;
        }
 
-       if (get_msg_text(buf, sizeof(buf), req, FALSE)) {
+       if (!(buf = ast_str_thread_get(&sip_msg_buf, 128))) {
+               transmit_response(p, "500 Internal Server Error", req);
+               if (!p->owner) {
+                       sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+               }
+               return;
+       }
+
+       if (get_msg_text2(&buf, req, FALSE)) {
                ast_log(LOG_WARNING, "Unable to retrieve text from %s\n", p->callid);
                transmit_response(p, "202 Accepted", req);
                if (!p->owner)
@@ -15939,23 +16024,93 @@ static void receive_message(struct sip_pvt *p, struct sip_request *req)
 
        if (p->owner) {
                if (sip_debug_test_pvt(p))
-                       ast_verbose("SIP Text message received: '%s'\n", buf);
+                       ast_verbose("SIP Text message received: '%s'\n", ast_str_buffer(buf));
                memset(&f, 0, sizeof(f));
                f.frametype = AST_FRAME_TEXT;
                f.subclass.integer = 0;
                f.offset = 0;
-               f.data.ptr = buf;
-               f.datalen = strlen(buf) + 1;
+               f.data.ptr = ast_str_buffer(buf);
+               f.datalen = ast_str_strlen(buf) + 1;
                ast_queue_frame(p->owner, &f);
                transmit_response(p, "202 Accepted", req); /* We respond 202 accepted, since we relay the message */
                return;
        }
 
-       /* Message outside of a call, we do not support that */
-       ast_log(LOG_WARNING, "Received message to %s from %s, dropped it...\n  Content-Type:%s\n  Message: %s\n", get_header(req, "To"), get_header(req, "From"), content_type, buf);
-       transmit_response(p, "405 Method Not Allowed", req);
+       if (!sip_cfg.accept_outofcall_message) {
+               /* Message outside of a call, we do not support that */
+               ast_debug(1, "MESSAGE outside of a call administratively disabled.\n");
+               transmit_response(p, "405 Method Not Allowed", req);
+               sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+               return;
+       }
+
+       if (sip_cfg.auth_message_requests) {
+               int res;
+
+               copy_request(&p->initreq, req);
+               set_pvt_allowed_methods(p, req);
+               res = check_user(p, req, SIP_MESSAGE, e, XMIT_UNRELIABLE, addr);
+               if (res == AUTH_CHALLENGE_SENT) {
+                       sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+                       return;
+               }
+               if (res < 0) { /* Something failed in authentication */
+                       if (res == AUTH_FAKE_AUTH) {
+                               ast_log(LOG_NOTICE, "Sending fake auth rejection for device %s\n", get_header(req, "From"));
+                               transmit_fake_auth_response(p, SIP_OPTIONS, req, XMIT_UNRELIABLE);
+                       } else {
+                               ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", get_header(req, "From"));
+                               transmit_response(p, "403 Forbidden", req);
+                       }
+                       sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+                       return;
+               }
+               /* Auth was successful.  Proceed. */
+       } else {
+               struct sip_peer *peer;
+
+               /*
+                * MESSAGE outside of a call, not authenticating it.
+                * Check to see if we match a peer anyway so that we can direct
+                * it to the right context.
+                */
+
+               peer = find_peer(NULL, &p->recv, TRUE, FINDPEERS, 0, p->socket.type);
+               if (peer) {
+                       /* Only if no auth is required. */
+                       if (ast_strlen_zero(peer->secret) && ast_strlen_zero(peer->md5secret)) {
+                               ast_string_field_set(p, context, peer->context);
+                       }
+                       peer = unref_peer(peer, "from find_peer() in receive_message");
+               }
+       }
+
+       if (!(msg = ast_msg_alloc())) {
+               transmit_response(p, "500 Internal Server Error", req);
+               if (!p->owner) {
+                       sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+               }
+               return;
+       }
+
+       to = ast_strdupa(REQ_OFFSET_TO_STR(req, rlPart2));
+       from = ast_strdupa(get_header(req, "From"));
+
+       res = ast_msg_set_to(msg, "%s", to);
+       res |= ast_msg_set_from(msg, "%s", get_in_brackets(from));
+       res |= ast_msg_set_body(msg, "%s", ast_str_buffer(buf));
+       res |= ast_msg_set_context(msg, "%s", p->context);
+       res |= ast_msg_set_exten(msg, "%s", p->exten);
+
+       if (res) {
+               ast_msg_destroy(msg);
+       } else {
+               set_message_vars_from_req(msg, req);
+               ast_msg_queue(msg);
+       }
+
+       transmit_response(p, "202 Accepted", req);
        sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
-       return;
 }
 
 /*! \brief  CLI Command to show calls within limits set by call_limit */
@@ -20549,6 +20704,8 @@ static void handle_response(struct sip_pvt *p, int resp, const char *rest, struc
                                handle_response_register(p, resp, rest, req, seqno);
                        else if (sipmethod == SIP_UPDATE) {
                                handle_response_update(p, resp, rest, req, seqno);
+                       } else if (sipmethod == SIP_MESSAGE) {
+                               handle_response_message(p, resp, rest, req, seqno);
                        } else if (sipmethod == SIP_BYE) {
                                if (p->options)
                                        p->options->auth_type = resp;
@@ -20894,11 +21051,11 @@ static void *sip_park_thread(void *stuff)
 
 #ifdef WHEN_WE_KNOW_THAT_THE_CLIENT_SUPPORTS_MESSAGE
        if (!res) {
-               transmit_message_with_text(transferer->tech_pvt, "Unable to park call.\n");
+               transmit_message_with_text(transferer->tech_pvt, "Unable to park call.\n", 0, 0);
        } else {
                /* Then tell the transferer what happened */
                sprintf(buf, "Call parked on extension '%d'", ext);
-               transmit_message_with_text(transferer->tech_pvt, buf);
+               transmit_message_with_text(transferer->tech_pvt, buf, 0, 0);
        }
 #endif
 
@@ -23378,18 +23535,129 @@ static int handle_request_bye(struct sip_pvt *p, struct sip_request *req)
        return 1;
 }
 
+/*!
+ * \internal
+ * \brief Handle auth requests to a MESSAGE request
+ */
+static void handle_response_message(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno)
+{
+       char *header, *respheader;
+       char digest[1024];
+
+       if (p->options) {
+               p->options->auth_type = (resp == 401 ? WWW_AUTH : PROXY_AUTH);
+       }
+
+       if ((p->authtries == MAX_AUTHTRIES)) {
+               ast_log(LOG_NOTICE, "Failed to authenticate on MESSAGE to '%s'\n", get_header(&p->initreq, "From"));
+               pvt_set_needdestroy(p, "MESSAGE authentication failed");
+               return;
+       }
+
+       p->authtries++;
+       auth_headers((resp == 401 ? WWW_AUTH : PROXY_AUTH), &header, &respheader);
+       memset(digest, 0, sizeof(digest));
+       if (reply_digest(p, req, header, SIP_MESSAGE, digest, sizeof(digest))) {
+               /* There's nothing to use for authentication */
+               ast_debug(1, "Nothing to use for MESSAGE authentication\n");
+               pvt_set_needdestroy(p, "MESSAGE authentication failed");
+               return;
+       }
+
+       if (p->do_history) {
+               append_history(p, "MessageAuth", "Try: %d", p->authtries);
+       }
+
+       transmit_message_with_text(p, p->msg_body, 0, 1);
+}
+
 /*! \brief Handle incoming MESSAGE request */
-static int handle_request_message(struct sip_pvt *p, struct sip_request *req)
+static int handle_request_message(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e)
 {
        if (!req->ignore) {
                if (req->debug)
                        ast_verbose("Receiving message!\n");
-               receive_message(p, req);
+               receive_message(p, req, addr, e);
        } else
                transmit_response(p, "202 Accepted", req);
        return 1;
 }
 
+static int sip_msg_send(const struct ast_msg *msg, const char *to, const char *from);
+
+static const struct ast_msg_tech sip_msg_tech = {
+       .name = "sip",
+       .msg_send = sip_msg_send,
+};
+
+static int sip_msg_send(const struct ast_msg *msg, const char *to, const char *from)
+{
+       struct sip_pvt *pvt;
+       int res;
+       char *peer;
+       struct sip_peer *peer_ptr;
+
+       if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_MESSAGE, NULL))) {
+               return -1;
+       }
+
+       peer = ast_strdupa(to);
+       if (strchr(peer, '@')) {
+               strsep(&peer, "@");
+       } else {
+               strsep(&peer, ":");
+       }
+       if (ast_strlen_zero(peer)) {
+               ast_log(LOG_WARNING, "MESSAGE(to) is invalid for SIP - '%s'\n", to);
+               return -1;
+       }
+
+       if (!ast_strlen_zero(from)) {
+               if ((peer_ptr = find_peer(from, NULL, 0, 1, 0, 0))) {
+                       ast_string_field_set(pvt, fromname, S_OR(peer_ptr->cid_name, peer_ptr->name));
+                       ast_string_field_set(pvt, fromuser, S_OR(peer_ptr->cid_num, peer_ptr->name));
+                       unref_peer(peer_ptr, "unref_peer, from sip_msg_send, find_peer");
+               } else if (strchr(from, '<')) { /* from is callerid-style */
+                       char *sender;
+                       char *name = NULL, *location = NULL, *user = NULL, *domain = NULL;
+
+                       sender = ast_strdupa(from);
+                       ast_callerid_parse(sender, &name, &location);
+                       ast_string_field_set(pvt, fromname, name);
+                       if (strchr(location, ':')) { /* Must be a URI */
+                               parse_uri(location, "sip:,sips:", &user, NULL, &domain, NULL);
+                               ast_string_field_set(pvt, fromuser, user);
+                               ast_string_field_set(pvt, fromdomain, domain);
+                       } else { /* Treat it as an exten/user */
+                               ast_string_field_set(pvt, fromuser, location);
+                       }
+               } else { /* assume we just have the name, use defaults for the rest */
+                       ast_string_field_set(pvt, fromname, from);
+               }
+       }
+
+       sip_pvt_lock(pvt);
+
+       if (create_addr(pvt, peer, NULL, TRUE, NULL)) {
+               sip_pvt_unlock(pvt);
+               dialog_unlink_all(pvt, TRUE, TRUE);
+               dialog_unref(pvt, "create_addr failed sending a MESSAGE");
+               return -1;
+       }
+       ast_sip_ouraddrfor(&pvt->sa, &pvt->ourip, pvt);
+       ast_set_flag(&pvt->flags[0], SIP_OUTGOING);
+
+       /* XXX Does pvt->expiry need to be set? */
+
+       res = transmit_message_with_msg(pvt, msg);
+
+       sip_pvt_unlock(pvt);
+       sip_scheddestroy(pvt, DEFAULT_TRANS_TIMEOUT);
+       dialog_unref(pvt, "sent a MESSAGE");
+
+       return res;
+}
+
 static enum sip_publish_type determine_sip_publish_type(struct sip_request *req, const char * const event, const char * const etag, const char * const expires, int *expires_int)
 {
        int etag_present = !ast_strlen_zero(etag);
@@ -24589,7 +24857,7 @@ static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct as
                res = handle_request_bye(p, req);
                break;
        case SIP_MESSAGE:
-               res = handle_request_message(p, req);
+               res = handle_request_message(p, req, addr, e);
                break;
        case SIP_PUBLISH:
                res = handle_request_publish(p, req, addr, seqno, e);
@@ -27368,6 +27636,8 @@ static int reload_config(enum channelreloadreason reason)
        sip_cfg.directrtpsetup = FALSE;         /* Experimental feature, disabled by default */
        sip_cfg.alwaysauthreject = DEFAULT_ALWAYSAUTHREJECT;
        sip_cfg.auth_options_requests = DEFAULT_AUTH_OPTIONS;
+       sip_cfg.auth_message_requests = DEFAULT_AUTH_MESSAGE;
+       sip_cfg.accept_outofcall_message = DEFAULT_ACCEPT_OUTOFCALL_MESSAGE;
        sip_cfg.allowsubscribe = FALSE;
        sip_cfg.disallowed_methods = SIP_UNKNOWN;
        sip_cfg.contact_ha = NULL;              /* Reset the contact ACL */
@@ -27616,6 +27886,10 @@ static int reload_config(enum channelreloadreason reason)
                        if (ast_true(v->value)) {
                                sip_cfg.auth_options_requests = 1;
                        }
+               } else if (!strcasecmp(v->name, "auth_message_requests")) {
+                       sip_cfg.auth_message_requests = ast_true(v->value) ? 1 : 0;
+               } else if (!strcasecmp(v->name, "accept_outofcall_message")) {
+                       sip_cfg.accept_outofcall_message = ast_true(v->value) ? 1 : 0;
                } else if (!strcasecmp(v->name, "mohinterpret")) {
                        ast_copy_string(default_mohinterpret, v->value, sizeof(default_mohinterpret));
                } else if (!strcasecmp(v->name, "mohsuggest")) {
@@ -29586,6 +29860,11 @@ static int load_module(void)
        memcpy(&sip_tech_info, &sip_tech, sizeof(sip_tech));
        memset((void *) &sip_tech_info.send_digit_begin, 0, sizeof(sip_tech_info.send_digit_begin));
 
+       if (ast_msg_tech_register(&sip_msg_tech)) {
+               /* LOAD_FAILURE stops Asterisk, so cleanup is a moot point. */
+               return AST_MODULE_LOAD_FAILURE;
+       }
+
        /* Make sure we can register our sip channel type */
        if (ast_channel_register(&sip_tech)) {
                ast_log(LOG_ERROR, "Unable to register channel type 'SIP'\n");
@@ -29694,6 +29973,8 @@ static int unload_module(void)
        /* First, take us out of the channel type list */
        ast_channel_unregister(&sip_tech);
 
+       ast_msg_tech_unregister(&sip_msg_tech);
+
        /* Unregister dial plan functions */
        ast_custom_function_unregister(&sipchaninfo_function);
        ast_custom_function_unregister(&sippeer_function);
index 4b69010..0eb8be3 100644 (file)
 #define DEFAULT_CALLEVENTS     FALSE    /*!< Extra manager SIP call events */
 #define DEFAULT_ALWAYSAUTHREJECT  TRUE  /*!< Don't reject authentication requests always */
 #define DEFAULT_AUTH_OPTIONS  FALSE
+#define DEFAULT_AUTH_MESSAGE  TRUE
+#define DEFAULT_ACCEPT_OUTOFCALL_MESSAGE TRUE
 #define DEFAULT_REGEXTENONQUALIFY FALSE
 #define DEFAULT_LEGACY_USEROPTION_PARSING FALSE
 #define DEFAULT_T1MIN             100   /*!< 100 MS for minimal roundtrip time */
@@ -680,6 +682,8 @@ struct sip_settings {
        int allowguest;             /*!< allow unauthenticated peers to connect? */
        int alwaysauthreject;       /*!< Send 401 Unauthorized for all failing requests */
        int auth_options_requests;  /*!< Authenticate OPTIONS requests */
+       int auth_message_requests;  /*!< Authenticate MESSAGE requests */
+       int accept_outofcall_message; /*!< Accept MESSAGE outside of a call */
        int compactheaders;         /*!< send compact sip headers */
        int allow_external_domains; /*!< Accept calls to external SIP domains? */
        int callevents;             /*!< Whether we send manager events or not */
@@ -966,6 +970,7 @@ struct sip_pvt {
                AST_STRING_FIELD(parkinglot);   /*!< Parkinglot */
                AST_STRING_FIELD(engine);       /*!< RTP engine to use */
                AST_STRING_FIELD(dialstring);   /*!< The dialstring used to call this SIP endpoint */
+               AST_STRING_FIELD(msg_body);     /*!< Text for a MESSAGE body */
        );
        char via[128];                          /*!< Via: header */
        int maxforwards;                        /*!< SIP Loop prevention */
index 098122d..a838568 100644 (file)
@@ -34,3 +34,6 @@
                                        ; Messages stored longer than this value will be deleted by Asterisk.
                                        ; This option applies to incoming messages only, which are intended to
                                        ; be processed by the JABBER_RECEIVE dialplan function.
+;sendtodialplan=yes                    ; Send incoming messages into the dialplan.  Off by default.
+;context=messages                      ; Dialplan context to send incoming messages to.  If not set,
+                                       ; "default" will be used.
index 179678a..49277d6 100644 (file)
@@ -385,6 +385,16 @@ srvlookup=yes                   ; Enable DNS SRV lookups on outbound calls
 ;auth_options_requests = yes    ; Enabling this option will authenticate OPTIONS requests just like
                                 ; INVITE requests are.  By default this option is disabled.
 
+;accept_outofcall_message = no  ; Disable this option to reject all MESSAGE requests outside of a
+                                ; call.  By default, this option is enabled.  When enabled, MESSAGE
+                                ; requests are passed in to the dialplan.
+
+;auth_message_requests = yes    ; Enabling this option will authenticate MESSAGE requests.
+                                ; By default this option is enabled.  However, it can be disabled
+                                ; should an application desire to not load the Asterisk server with
+                                ; doing authentication and implement end to end security in the
+                                ; message body.
+
 ;g726nonstandard = yes          ; If the peer negotiates G726-32 audio, use AAL2 packing
                                 ; order instead of RFC3551 packing order (this is required
                                 ; for Sipura and Grandstream ATAs, among others). This is
index a0b1712..37c49d9 100644 (file)
@@ -47,6 +47,7 @@ int ast_cel_engine_init(void);                /*!< Provided by cel.c */
 int ast_cel_engine_reload(void);       /*!< Provided by cel.c */
 int ast_ssl_init(void);                 /*!< Provided by ssl.c */
 int ast_test_init(void);            /*!< Provided by test.c */
+int ast_msg_init(void);             /*!< Provided by message.c */
 
 /*!
  * \brief Reload asterisk modules.
index c1b99db..5798e92 100644 (file)
@@ -3496,4 +3496,14 @@ int ast_channel_get_cc_agent_type(struct ast_channel *chan, char *agent_type, si
 }
 #endif
 
+/*!
+ * \brief Remove a channel from the global channels container
+ *
+ * \param chan channel to remove
+ *
+ * In a case where it is desired that a channel not be available in any lookups
+ * in the global channels conatiner, use this function.
+ */
+void ast_channel_unlink(struct ast_channel *chan);
+
 #endif /* _ASTERISK_CHANNEL_H */
index 85d459c..bbf0a23 100644 (file)
@@ -157,6 +157,7 @@ struct aji_client {
        char name_space[256];
        char sid[10]; /* Session ID */
        char mid[6]; /* Message ID */
+       char context[AST_MAX_CONTEXT];
        iksid *jid;
        iksparser *p;
        iksfilter *f;
@@ -179,6 +180,7 @@ struct aji_client {
        int message_timeout;
        int authorized;
        int distribute_events;
+       int send_to_dialplan;
        struct ast_flags flags;
        int component; /* 0 client,  1 component */
        struct aji_buddy_container buddies;
diff --git a/include/asterisk/message.h b/include/asterisk/message.h
new file mode 100644 (file)
index 0000000..f2fe493
--- /dev/null
@@ -0,0 +1,242 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * Russell Bryant <russell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ *
+ * \brief Out-of-call text message support
+ *
+ * \author Russell Bryant <russell@digium.com>
+ *
+ * The purpose of this API is to provide support for text messages that
+ * are not session based.  The messages are passed into the Asterisk core
+ * to be routed through the dialplan and potentially sent back out through
+ * a message technology that has been registered through this API.
+ */
+
+#ifndef __AST_MESSAGE_H__
+#define __AST_MESSAGE_H__
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+/*!
+ * \brief A text message.
+ *
+ * This is an opaque type that represents a text message.
+ */
+struct ast_msg;
+
+/*!
+ * \brief A message technology
+ *
+ * A message technology is capable of transmitting text messages.
+ */
+struct ast_msg_tech {
+        /*!
+         * \brief Name of this message technology
+         *
+         * This is the name that comes at the beginning of a URI for messages
+         * that should be sent to this message technology implementation.
+         * For example, messages sent to "xmpp:rbryant@digium.com" would be
+         * passed to the ast_msg_tech with a name of "xmpp".
+         */
+        const char * const name;
+        /*!
+         * \brief Send a message.
+        *
+        * \param msg the message to send
+        * \param to the URI of where the message is being sent
+        * \param from the URI of where the message was sent from
+        *
+        * The fields of the ast_msg are guaranteed not to change during the
+        * duration of this function call.
+        *
+        * \retval 0 success
+        * \retval non-zero failure
+         */
+        int (* const msg_send)(const struct ast_msg *msg, const char *to, const char *from);
+};
+
+/*!
+ * \brief Register a message technology
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_msg_tech_register(const struct ast_msg_tech *tech);
+
+/*!
+ * \brief Unregister a message technology.
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_msg_tech_unregister(const struct ast_msg_tech *tech);
+
+/*!
+ * \brief Allocate a message.
+ *
+ * Allocate a message for the purposes of passing it into the Asterisk core
+ * to be routed through the dialplan.  If ast_msg_queue() is not called, this
+ * message must be destroyed using ast_msg_destroy().  Otherwise, the message
+ * core code will take care of it.
+ *
+ * \return A message object. This function will return NULL if an allocation
+ *         error occurs.
+ */
+struct ast_msg *ast_msg_alloc(void);
+
+/*!
+ * \brief Destroy an ast_msg
+ *
+ * This should only be called on a message if it was not
+ * passed on to ast_msg_queue().
+ *
+ * \return NULL, always.
+ */
+struct ast_msg *ast_msg_destroy(struct ast_msg *msg);
+
+/*!
+ * \brief Set the 'to' URI of a message
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+               ast_msg_set_to(struct ast_msg *msg, const char *fmt, ...);
+
+/*!
+ * \brief Set the 'from' URI of a message
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+               ast_msg_set_from(struct ast_msg *msg, const char *fmt, ...);
+
+/*!
+ * \brief Set the 'body' text of a message (in UTF-8)
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+               ast_msg_set_body(struct ast_msg *msg, const char *fmt, ...);
+
+/*!
+ * \brief Set the dialplan context for this message
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+               ast_msg_set_context(struct ast_msg *msg, const char *fmt, ...);
+
+/*!
+ * \brief Set the dialplan extension for this message
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+               ast_msg_set_exten(struct ast_msg *msg, const char *fmt, ...);
+       
+/*!
+ * \brief Set a variable on the message
+ * \note Setting a variable that already exists overwrites the existing variable value
+ *
+ * \param name Name of variable to set
+ * \param value Value of variable to set
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_msg_set_var(struct ast_msg *msg, const char *name, const char *value);
+
+/*!
+ * \brief Get the specified variable on the message
+ * \note The return value is valid only as long as the ast_message is valid. Hold a reference
+ *       to the message if you plan on storing the return value. 
+ *
+ * \return The value associated with variable "name". NULL if variable not found.
+ */
+const char *ast_msg_get_var(struct ast_msg *msg, const char *name);
+
+/*!
+ * \brief Get the body of a message.
+ * \note The return value is valid only as long as the ast_message is valid. Hold a reference
+ *       to the message if you plan on storing the return value. 
+ *
+ * \return The body of the messsage, encoded in UTF-8.
+ */
+const char *ast_msg_get_body(const struct ast_msg *msg);
+
+/*!
+ * \brief Queue a message for routing through the dialplan.
+ *
+ * Regardless of the return value of this function, this funciton will take
+ * care of ensuring that the message object is properly destroyed when needed.
+ *
+ * \retval 0 message successfully queued
+ * \retval non-zero failure, message not sent to dialplan
+ */
+int ast_msg_queue(struct ast_msg *msg);
+
+/*!
+ * \brief Opaque iterator for msg variables
+ */
+struct ast_msg_var_iterator;
+
+/*!
+ * \brief Create a new message variable iterator
+ * \param msg A message whose variables are to be iterated over
+ *
+ * \return An opaque pointer to the new iterator
+ */
+struct ast_msg_var_iterator *ast_msg_var_iterator_init(const struct ast_msg *msg);
+
+/*!
+ * \brief Get the next variable name and value that is set for sending outbound
+ * \param msg The message with the variables
+ * \param i An iterator created with ast_msg_var_iterator_init
+ * \param name A pointer to the name result pointer
+ * \param value A pointer to the value result pointer
+ *
+ * \retval 0 No more entries
+ * \retval 1 Valid entry
+ */
+int ast_msg_var_iterator_next(const struct ast_msg *msg, struct ast_msg_var_iterator *i, const char **name, const char **value);
+
+/*!
+ * \brief Destroy a message variable iterator
+ * \param i Iterator to be destroyed
+ */
+void ast_msg_var_iterator_destroy(struct ast_msg_var_iterator *i);
+
+/*!
+ * \brief Unref a message var from inside an iterator loop
+ */
+void ast_msg_var_unref_current(struct ast_msg_var_iterator *i);
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* __AST_MESSAGE_H__ */
index b3bcfe2..d9e3868 100644 (file)
@@ -3750,6 +3750,11 @@ int main(int argc, char *argv[])
        ast_xmldoc_load_documentation();
 #endif
 
+       if (ast_msg_init()) {
+               printf("%s", term_quit());
+               exit(1);
+       }
+
        /* initialize the data retrieval API */
        if (ast_data_init()) {
                printf ("%s", term_quit());
index f60635e..9c9d6ca 100644 (file)
@@ -9593,3 +9593,8 @@ struct ast_channel *ast_channel_alloc(int needqueue, int state, const char *cid_
 
        return result;
 }
+
+void ast_channel_unlink(struct ast_channel *chan)
+{
+       ao2_unlink(channels, chan);
+}
diff --git a/main/message.c b/main/message.c
new file mode 100644 (file)
index 0000000..f2c5f4d
--- /dev/null
@@ -0,0 +1,1112 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * Russell Bryant <russell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Out-of-call text message support
+ *
+ * \author Russell Bryant <russell@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+
+#include "asterisk/module.h"
+#include "asterisk/datastore.h"
+#include "asterisk/pbx.h"
+#include "asterisk/strings.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/app.h"
+#include "asterisk/taskprocessor.h"
+#include "asterisk/message.h"
+
+/*** DOCUMENTATION
+       <function name="MESSAGE" language="en_US">
+               <synopsis>
+                       Create a message or read fields from a message.
+               </synopsis>
+               <syntax argsep="/">
+                       <parameter name="argument" required="true">
+                       <para>Field of the message to get or set.</para>
+                       <enumlist>
+                               <enum name="to">
+                                       <para>Read-only.  The destination of the message.  When processing an
+                                       incoming message, this will be set to the destination listed as
+                                       the recipient of the message that was received by Asterisk.</para>
+                               </enum>
+                               <enum name="from">
+                                       <para>Read-only.  The source of the message.  When processing an
+                                       incoming message, this will be set to the source of the message.</para>
+                               </enum>
+                               <enum name="body">
+                                       <para>Read/Write.  The message body.  When processing an incoming
+                                       message, this includes the body of the message that Asterisk
+                                       received.  When MessageSend() is executed, the contents of this
+                                       field are used as the body of the outgoing message.  The body
+                                       will always be UTF-8.</para>
+                               </enum>
+                       </enumlist>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>This function will read from or write a value to a text message.
+                       It is used both to read the data out of an incoming message, as well as
+                       modify or create a message that will be sent outbound.</para>
+               </description>
+               <see-also>
+                       <ref type="application">MessageSend</ref>
+               </see-also>
+       </function>
+       <function name="MESSAGE_DATA" language="en_US">
+               <synopsis>
+                       Read or write custom data attached to a message.
+               </synopsis>
+               <syntax argsep="/">
+                       <parameter name="argument" required="true">
+                       <para>Field of the message to get or set.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>This function will read from or write a value to a text message.
+                       It is used both to read the data out of an incoming message, as well as
+                       modify a message that will be sent outbound.</para>
+                       <para>NOTE: If you want to set an outbound message to carry data in the
+                       current message, do Set(MESSAGE_DATA(key)=${MESSAGE_DATA(key)}).</para>
+               </description>
+               <see-also>
+                       <ref type="application">MessageSend</ref>
+               </see-also>
+       </function>
+       <application name="MessageSend" language="en_US">
+               <synopsis>
+                       Send a text message.
+               </synopsis>
+               <syntax>
+                       <parameter name="to" required="true">
+                               <para>A To URI for the message.</para>
+                       </parameter>
+                       <parameter name="from" required="false">
+                               <para>A From URI for the message if needed for the
+                               message technology being used to send this message.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Send a text message.  The body of the message that will be
+                       sent is what is currently set to <literal>MESSAGE(body)</literal>.</para>
+
+                       <para>This application sets the following channel variables:</para>
+                       <variablelist>
+                               <variable name="MESSAGE_SEND_STATUS">
+                                       <para>This is the time from dialing a channel until when it is disconnected.</para>
+                                       <value name="INVALID_PROTOCOL">
+                                               No handler for the technology part of the URI was found.
+                                       </value>
+                                       <value name="INVALID_URI">
+                                               The protocol handler reported that the URI was not valid.
+                                       </value>
+                                       <value name="SUCCESS">
+                                               Successfully passed on to the protocol handler, but delivery has not necessarily been guaranteed.
+                                       </value>
+                                       <value name="FAILURE">
+                                               The protocol handler reported that it was unabled to deliver the message for some reason.
+                                       </value>
+                               </variable>
+                       </variablelist>
+               </description>
+       </application>
+ ***/
+
+struct msg_data {
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(name);
+               AST_STRING_FIELD(value);
+       );
+       unsigned int send:1; /* Whether to send out on outbound messages */
+};
+
+AST_LIST_HEAD_NOLOCK(outhead, msg_data);
+
+/*!
+ * \brief A message.
+ *
+ * \todo Consider whether stringfields would be an appropriate optimization here.
+ */
+struct ast_msg {
+       struct ast_str *to;
+       struct ast_str *from;
+       struct ast_str *body;
+       struct ast_str *context;
+       struct ast_str *exten;
+       struct ao2_container *vars;
+};
+
+struct ast_msg_tech_holder {
+       const struct ast_msg_tech *tech;
+       /*! 
+        * \brief A rwlock for this object
+        *
+        * a read/write lock must be used to protect the wrapper instead
+        * of the ao2 lock. A rdlock must be held to read tech_holder->tech.
+        */
+       ast_rwlock_t tech_lock;
+};
+
+static struct ao2_container *msg_techs;
+
+static struct ast_taskprocessor *msg_q_tp;
+
+static const char app_msg_send[] = "MessageSend";
+
+static void msg_ds_destroy(void *data);
+
+static const struct ast_datastore_info msg_datastore = {
+       .type = "message",
+       .destroy = msg_ds_destroy,
+};
+
+static int msg_func_read(struct ast_channel *chan, const char *function,
+               char *data, char *buf, size_t len);
+static int msg_func_write(struct ast_channel *chan, const char *function,
+               char *data, const char *value);
+
+static struct ast_custom_function msg_function = {
+       .name = "MESSAGE",
+       .read = msg_func_read,
+       .write = msg_func_write,
+};
+
+static int msg_data_func_read(struct ast_channel *chan, const char *function,
+               char *data, char *buf, size_t len);
+static int msg_data_func_write(struct ast_channel *chan, const char *function,
+               char *data, const char *value);
+
+static struct ast_custom_function msg_data_function = {
+       .name = "MESSAGE_DATA",
+       .read = msg_data_func_read,
+       .write = msg_data_func_write,
+};
+
+static struct ast_frame *chan_msg_read(struct ast_channel *chan);
+static int chan_msg_write(struct ast_channel *chan, struct ast_frame *fr);
+static int chan_msg_indicate(struct ast_channel *chan, int condition,
+               const void *data, size_t datalen);
+static int chan_msg_send_digit_begin(struct ast_channel *chan, char digit);
+static int chan_msg_send_digit_end(struct ast_channel *chan, char digit,
+               unsigned int duration);
+
+/*!
+ * \internal
+ * \brief A bare minimum channel technology
+ *
+ * This will not be registered as we never want anything to try
+ * to create Message channels other than internally in this file.
+ */
+static const struct ast_channel_tech msg_chan_tech_hack = {
+       .type             = "Message",
+       .description      = "Internal Text Message Processing",
+       .read             = chan_msg_read,
+       .write            = chan_msg_write,
+       .indicate         = chan_msg_indicate,
+       .send_digit_begin = chan_msg_send_digit_begin,
+       .send_digit_end   = chan_msg_send_digit_end,
+};
+
+/*!
+ * \internal
+ * \brief ast_channel_tech read callback
+ *
+ * This should never be called.  However, we say that about chan_iax2's
+ * read callback, too, and it seems to randomly get called for some
+ * reason.  If it does, a simple NULL frame will suffice.
+ */
+static struct ast_frame *chan_msg_read(struct ast_channel *chan)
+{
+       return &ast_null_frame;
+}
+
+/*!
+ * \internal
+ * \brief ast_channel_tech write callback
+ *
+ * Throw all frames away.  We don't care about any of them.
+ */
+static int chan_msg_write(struct ast_channel *chan, struct ast_frame *fr)
+{
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief ast_channel_tech indicate callback
+ *
+ * The indicate callback is here just so it can return success.
+ * We don't want any callers of ast_indicate() to think something
+ * has failed.  We also don't want ast_indicate() itself to try
+ * to generate inband tones since we didn't tell it that we took
+ * care of it ourselves.
+ */
+static int chan_msg_indicate(struct ast_channel *chan, int condition,
+               const void *data, size_t datalen)
+{
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief ast_channel_tech send_digit_begin callback
+ *
+ * This is here so that just in case a digit comes at a message channel
+ * that the Asterisk core doesn't waste any time trying to generate
+ * inband DTMF in audio.  It's a waste of resources.
+ */
+static int chan_msg_send_digit_begin(struct ast_channel *chan, char digit)
+{
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief ast_channel_tech send_digit_end callback
+ *
+ * This is here so that just in case a digit comes at a message channel
+ * that the Asterisk core doesn't waste any time trying to generate
+ * inband DTMF in audio.  It's a waste of resources.
+ */
+static int chan_msg_send_digit_end(struct ast_channel *chan, char digit,
+               unsigned int duration)
+{
+       return 0;
+}
+
+static void msg_ds_destroy(void *data)
+{
+       struct ast_msg *msg = data;
+
+       ao2_ref(msg, -1);
+}
+
+static int msg_data_hash_fn(const void *obj, const int flags)
+{
+       const struct msg_data *data = obj;
+       return ast_str_case_hash(data->name);
+}
+
+static int msg_data_cmp_fn(void *obj, void *arg, int flags)
+{
+       const struct msg_data *one = obj, *two = arg;
+       return !strcasecmp(one->name, two->name) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static void msg_data_destructor(void *obj)
+{
+       struct msg_data *data = obj;
+       ast_string_field_free_memory(data);
+}
+
+static void msg_destructor(void *obj)
+{
+       struct ast_msg *msg = obj;
+
+       ast_free(msg->to);
+       msg->to = NULL;
+
+       ast_free(msg->from);
+       msg->from = NULL;
+
+       ast_free(msg->body);
+       msg->body = NULL;
+
+       ast_free(msg->context);
+       msg->context = NULL;
+
+       ast_free(msg->exten);
+       msg->exten = NULL;
+
+       ao2_ref(msg->vars, -1);
+}
+
+struct ast_msg *ast_msg_alloc(void)
+{
+       struct ast_msg *msg;
+
+       if (!(msg = ao2_alloc(sizeof(*msg), msg_destructor))) {
+               return NULL;
+       }
+
+       if (!(msg->to = ast_str_create(32))) {
+               ao2_ref(msg, -1);
+               return NULL;
+       }
+
+       if (!(msg->from = ast_str_create(32))) {
+               ao2_ref(msg, -1);
+               return NULL;
+       }
+
+       if (!(msg->body = ast_str_create(128))) {
+               ao2_ref(msg, -1);
+               return NULL;
+       }
+
+       if (!(msg->context = ast_str_create(16))) {
+               ao2_ref(msg, -1);
+               return NULL;
+       }
+
+       if (!(msg->exten = ast_str_create(16))) {
+               ao2_ref(msg, -1);
+               return NULL;
+       }
+
+       if (!(msg->vars = ao2_container_alloc(1, msg_data_hash_fn, msg_data_cmp_fn))) {
+               ao2_ref(msg, -1);
+               return NULL;
+       }
+
+       ast_str_set(&msg->context, 0, "default");
+
+       return msg;
+}
+
+struct ast_msg *ast_msg_destroy(struct ast_msg *msg)
+{
+       ao2_ref(msg, -1);
+
+       return NULL;
+}
+
+int ast_msg_set_to(struct ast_msg *msg, const char *fmt, ...)
+{
+       va_list ap;
+       int res;
+
+       va_start(ap, fmt);
+       res = ast_str_set_va(&msg->to, 0, fmt, ap);
+       va_end(ap);
+
+       return res < 0 ? -1 : 0;
+}
+
+int ast_msg_set_from(struct ast_msg *msg, const char *fmt, ...)
+{
+       va_list ap;
+       int res;
+
+       va_start(ap, fmt);
+       res = ast_str_set_va(&msg->from, 0, fmt, ap);
+       va_end(ap);
+
+       return res < 0 ? -1 : 0;
+}
+
+int ast_msg_set_body(struct ast_msg *msg, const char *fmt, ...)
+{
+       va_list ap;
+       int res;
+
+       va_start(ap, fmt);
+       res = ast_str_set_va(&msg->body, 0, fmt, ap);
+       va_end(ap);
+
+       return res < 0 ? -1 : 0;
+}
+
+int ast_msg_set_context(struct ast_msg *msg, const char *fmt, ...)
+{
+       va_list ap;
+       int res;
+
+       va_start(ap, fmt);
+       res = ast_str_set_va(&msg->context, 0, fmt, ap);
+       va_end(ap);
+
+       return res < 0 ? -1 : 0;
+}
+
+int ast_msg_set_exten(struct ast_msg *msg, const char *fmt, ...)
+{
+       va_list ap;
+       int res;
+
+       va_start(ap, fmt);
+       res = ast_str_set_va(&msg->exten, 0, fmt, ap);
+       va_end(ap);
+
+       return res < 0 ? -1 : 0;
+}
+
+const char *ast_msg_get_body(const struct ast_msg *msg)
+{
+       return ast_str_buffer(msg->body);
+}
+
+static struct msg_data *msg_data_alloc(void)
+{
+       struct msg_data *data;
+
+       if (!(data = ao2_alloc(sizeof(*data), msg_data_destructor))) {
+               return NULL;
+       }
+
+       if (ast_string_field_init(data, 32)) {
+               ao2_ref(data, -1);
+               return NULL;
+       }
+
+       return data;
+}
+
+static struct msg_data *msg_data_find(struct ao2_container *vars, const char *name)
+{
+       struct msg_data tmp = {
+               .name = name,
+       };
+       return ao2_find(vars, &tmp, OBJ_POINTER);
+}
+
+static int msg_set_var_full(struct ast_msg *msg, const char *name, const char *value, unsigned int outbound)
+{
+       struct msg_data *data;
+
+       if (!(data = msg_data_find(msg->vars, name))) {
+               if (!(data = msg_data_alloc())) {
+                       return -1;
+               };
+
+               ast_string_field_set(data, name, name);
+               ast_string_field_set(data, value, value);
+               data->send = outbound;
+               ao2_link(msg->vars, data);
+       } else {
+               if (ast_strlen_zero(value)) {
+                       ao2_unlink(msg->vars, data);
+               } else {
+                       ast_string_field_set(data, value, value);
+                       data->send = outbound;
+               }
+       }
+
+       ao2_ref(data, -1);
+
+       return 0;
+}
+
+static int msg_set_var_outbound(struct ast_msg *msg, const char *name, const char *value)
+{
+       return msg_set_var_full(msg, name, value, 1);
+}
+
+int ast_msg_set_var(struct ast_msg *msg, const char *name, const char *value)
+{
+       return msg_set_var_full(msg, name, value, 0);
+}
+
+const char *ast_msg_get_var(struct ast_msg *msg, const char *name)
+{
+       struct msg_data *data;
+
+       if (!(data = msg_data_find(msg->vars, name))) {
+               return NULL;
+       }
+
+       return data->value;
+}
+
+struct ast_msg_var_iterator {
+       struct ao2_iterator i;
+       struct msg_data *current_used;
+};
+
+struct ast_msg_var_iterator *ast_msg_var_iterator_init(const struct ast_msg *msg)
+{
+       struct ast_msg_var_iterator *i;
+       if (!(i = ast_calloc(1, sizeof(*i)))) {
+               return NULL;
+       }
+
+       i->i = ao2_iterator_init(msg->vars, 0);
+
+       return i;
+}
+
+int ast_msg_var_iterator_next(const struct ast_msg *msg, struct ast_msg_var_iterator *i, const char **name, const char **value)
+{
+       struct msg_data *data;
+
+       /* Skip any that aren't marked for sending out */
+       while ((data = ao2_iterator_next(&i->i)) && !data->send) {
+               ao2_ref(data, -1);
+       }
+
+       if (!data) {
+               return 0;
+       }
+
+       if (data->send) {
+               *name = data->name;
+               *value = data->value;
+       }
+
+       /* Leave the refcount to be cleaned up by the caller with
+        * ast_msg_var_unref_current after they finish with the pointers to the data */
+       i->current_used = data;
+
+       return 1;
+}
+
+void ast_msg_var_unref_current(struct ast_msg_var_iterator *i) {
+       if (i->current_used) {
+               ao2_ref(i->current_used, -1);
+       }
+       i->current_used = NULL;
+}
+
+void ast_msg_var_iterator_destroy(struct ast_msg_var_iterator *i)
+{
+       ao2_iterator_destroy(&i->i);
+       ast_free(i);
+}
+
+static struct ast_channel *create_msg_q_chan(void)
+{
+       struct ast_channel *chan;
+       struct ast_datastore *ds;
+
+       chan = ast_channel_alloc(1, AST_STATE_UP,
+                       NULL, NULL, NULL,
+                       NULL, NULL, NULL, 0,
+                       "%s", "Message/ast_msg_queue");
+
+       if (!chan) {
+               return NULL;
+       }
+
+       ast_channel_unlink(chan);
+
+       chan->tech = &msg_chan_tech_hack;
+
+       if (!(ds = ast_datastore_alloc(&msg_datastore, NULL))) {
+               ast_hangup(chan);
+               return NULL;
+       }
+
+       ast_channel_lock(chan);
+       ast_channel_datastore_add(chan, ds);
+       ast_channel_unlock(chan);
+
+       return chan;
+}
+
+/*!
+ * \internal
+ * \brief Run the dialplan for message processing
+ *
+ * \pre The message has already been set up on the msg datastore
+ *      on this channel.
+ */
+static void msg_route(struct ast_channel *chan, struct ast_msg *msg)
+{
+       struct ast_pbx_args pbx_args;
+
+       ast_explicit_goto(chan, ast_str_buffer(msg->context), AS_OR(msg->exten, "s"), 1);
+
+       memset(&pbx_args, 0, sizeof(pbx_args));
+       pbx_args.no_hangup_chan = 1,
+       ast_pbx_run_args(chan, &pbx_args);
+}
+
+/*!
+ * \internal
+ * \brief Clean up ast_channel after each message
+ *
+ * Reset various bits of state after routing each message so the same ast_channel
+ * can just be reused.
+ */
+static void chan_cleanup(struct ast_channel *chan)
+{
+       struct ast_datastore *msg_ds, *ds;
+       struct varshead *headp;
+       struct ast_var_t *vardata;
+
+       ast_channel_lock(chan);
+
+       /*
+        * Remove the msg datastore.  Free its data but keep around the datastore
+        * object and just reuse it.
+        */
+       if ((msg_ds = ast_channel_datastore_find(chan, &msg_datastore, NULL)) && msg_ds->data) {
+               ast_channel_datastore_remove(chan, msg_ds);
+               ao2_ref(msg_ds->data, -1);
+               msg_ds->data = NULL;
+       }
+
+       /*
+        * Destroy all other datastores.
+        */
+       while ((ds = AST_LIST_REMOVE_HEAD(&chan->datastores, entry))) {
+               ast_datastore_free(ds);
+       }
+
+       /*
+        * Destroy all channel variables.
+        */
+       headp = &chan->varshead;
+       while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries))) {
+               ast_var_delete(vardata);
+       }
+
+       /*
+        * Restore msg datastore.
+        */
+       if (msg_ds) {
+               ast_channel_datastore_add(chan, msg_ds);
+       }
+
+       ast_channel_unlock(chan);
+}
+
+AST_THREADSTORAGE(msg_q_chan);
+
+/*!
+ * \internal
+ * \brief Message queue task processor callback
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \note Even though this returns a value, the taskprocessor code ignores the value.
+ */
+static int msg_q_cb(void *data)
+{
+       struct ast_msg *msg = data;
+       struct ast_channel **chan_p, *chan;
+       struct ast_datastore *ds;
+
+       if (!(chan_p = ast_threadstorage_get(&msg_q_chan, sizeof(struct ast_channel *)))) {
+               return -1;
+       }
+       if (!*chan_p) {
+               if (!(*chan_p = create_msg_q_chan())) {
+                       return -1;
+               }
+       }
+       chan = *chan_p;
+
+       ast_channel_lock(chan);
+       if (!(ds = ast_channel_datastore_find(chan, &msg_datastore, NULL))) {
+               ast_channel_unlock(chan);
+               return -1;
+       }
+       ao2_ref(msg, +1);
+       ds->data = msg;
+       ast_channel_unlock(chan);
+
+       msg_route(chan, msg);
+       chan_cleanup(chan);
+
+       ao2_ref(msg, -1);
+
+       return 0;
+}
+
+int ast_msg_queue(struct ast_msg *msg)
+{
+       int res;
+
+       res = ast_taskprocessor_push(msg_q_tp, msg_q_cb, msg);
+       if (res == -1) {
+               ao2_ref(msg, -1);
+       }
+
+       return res;
+}
+
+/*!
+ * \internal
+ * \brief Find or create a message datastore on a channel
+ * 
+ * \pre chan is locked
+ *
+ * \param chan the relevant channel
+ *
+ * \return the channel's message datastore, or NULL on error
+ */
+static struct ast_datastore *msg_datastore_find_or_create(struct ast_channel *chan)
+{
+       struct ast_datastore *ds;
+
+       if ((ds = ast_channel_datastore_find(chan, &msg_datastore, NULL))) {
+               return ds;
+       }
+
+       if (!(ds = ast_datastore_alloc(&msg_datastore, NULL))) {
+               return NULL;
+       }
+
+       if (!(ds->data = ast_msg_alloc())) {
+               ast_datastore_free(ds);
+               return NULL;
+       }
+
+       ast_channel_datastore_add(chan, ds);
+
+       return ds;
+}
+
+static int msg_func_read(struct ast_channel *chan, const char *function,
+               char *data, char *buf, size_t len)
+{
+       struct ast_datastore *ds;
+       struct ast_msg *msg;
+
+       ast_channel_lock(chan);
+
+       if (!(ds = ast_channel_datastore_find(chan, &msg_datastore, NULL))) {
+               ast_channel_unlock(chan);
+               ast_log(LOG_ERROR, "No MESSAGE data found on the channel to read.\n");
+               return -1;
+       }
+
+       msg = ds->data;
+       ao2_ref(msg, +1);
+       ast_channel_unlock(chan);
+
+       ao2_lock(msg);
+
+       if (!strcasecmp(data, "to")) {
+               ast_copy_string(buf, ast_str_buffer(msg->to), len);
+       } else if (!strcasecmp(data, "from")) {
+               ast_copy_string(buf, ast_str_buffer(msg->from), len);
+       } else if (!strcasecmp(data, "body")) {
+               ast_copy_string(buf, ast_msg_get_body(msg), len);
+       } else {
+               ast_log(LOG_WARNING, "Invalid argument to MESSAGE(): '%s'\n", data);
+       }
+
+       ao2_unlock(msg);
+       ao2_ref(msg, -1);
+
+       return 0;
+}
+
+static int msg_func_write(struct ast_channel *chan, const char *function,
+               char *data, const char *value)
+{
+       struct ast_datastore *ds;
+       struct ast_msg *msg;
+
+       ast_channel_lock(chan);
+
+       if (!(ds = msg_datastore_find_or_create(chan))) {
+               ast_channel_unlock(chan);
+               return -1;
+       }
+
+       msg = ds->data;
+       ao2_ref(msg, +1);
+       ast_channel_unlock(chan);
+
+       ao2_lock(msg);
+
+       if (!strcasecmp(data, "to")) {
+               ast_msg_set_to(msg, "%s", value);
+       } else if (!strcasecmp(data, "from")) {
+               ast_msg_set_from(msg, "%s", value);
+       } else if (!strcasecmp(data, "body")) {
+               ast_msg_set_body(msg, "%s", value);
+       } else {
+               ast_log(LOG_WARNING, "'%s' is not a valid write argument.\n", data);
+       }
+
+       ao2_unlock(msg);
+       ao2_ref(msg, -1);
+
+       return 0;
+}
+
+static int msg_data_func_read(struct ast_channel *chan, const char *function,
+               char *data, char *buf, size_t len)
+{
+       struct ast_datastore *ds;
+       struct ast_msg *msg;
+       const char *val;
+
+       ast_channel_lock(chan);
+
+       if (!(ds = ast_channel_datastore_find(chan, &msg_datastore, NULL))) {
+               ast_channel_unlock(chan);
+               ast_log(LOG_ERROR, "No MESSAGE data found on the channel to read.\n");
+               return -1;
+       }
+
+       msg = ds->data;
+       ao2_ref(msg, +1);
+       ast_channel_unlock(chan);
+
+       ao2_lock(msg);
+
+       if ((val = ast_msg_get_var(msg, data))) {
+               ast_copy_string(buf, val, len);
+       }
+
+       ao2_unlock(msg);
+       ao2_ref(msg, -1);
+
+       return 0;
+}
+
+static int msg_data_func_write(struct ast_channel *chan, const char *function,
+               char *data, const char *value)
+{
+       struct ast_datastore *ds;
+       struct ast_msg *msg;
+
+       ast_channel_lock(chan);
+
+       if (!(ds = msg_datastore_find_or_create(chan))) {
+               ast_channel_unlock(chan);
+               return -1;
+       }
+
+       msg = ds->data;
+       ao2_ref(msg, +1);
+       ast_channel_unlock(chan);
+
+       ao2_lock(msg);
+
+       msg_set_var_outbound(msg, data, value);
+
+       ao2_unlock(msg);
+       ao2_ref(msg, -1);
+
+       return 0;
+}
+static int msg_tech_hash(const void *obj, const int flags)
+{
+       struct ast_msg_tech_holder *tech_holder = (struct ast_msg_tech_holder *) obj;
+       int res = 0;
+
+       ast_rwlock_rdlock(&tech_holder->tech_lock);
+       if (tech_holder->tech) {
+               res = ast_str_case_hash(tech_holder->tech->name);
+       }
+       ast_rwlock_unlock(&tech_holder->tech_lock);
+
+       return res;
+}
+
+static int msg_tech_cmp(void *obj, void *arg, int flags)
+{
+       struct ast_msg_tech_holder *tech_holder = obj;
+       const struct ast_msg_tech_holder *tech_holder2 = arg;
+       int res = 1;
+
+       ast_rwlock_rdlock(&tech_holder->tech_lock);
+       /*
+        * tech_holder2 is a temporary fake tech_holder.
+        */
+       if (tech_holder->tech) {
+               res = strcasecmp(tech_holder->tech->name, tech_holder2->tech->name) ? 0 : CMP_MATCH | CMP_STOP;
+       }
+       ast_rwlock_unlock(&tech_holder->tech_lock);
+
+       return res;
+}
+
+/*!
+ * \internal
+ * \brief MessageSend() application
+ */
+static int msg_send_exec(struct ast_channel *chan, const char *data)
+{
+       struct ast_datastore *ds;
+       struct ast_msg *msg;
+       char *tech_name;
+       struct ast_msg_tech_holder *tech_holder = NULL;
+       char *parse;
+       int res = -1;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(to);
+               AST_APP_ARG(from);
+       );
+
+       if (ast_strlen_zero(data)) {
+               ast_log(LOG_WARNING, "An argument is required to MessageSend()\n");
+               pbx_builtin_setvar_helper(chan, "MESSAGE_SEND_STATUS", "INVALID_URI");
+               return 0;
+       }
+
+       parse = ast_strdupa(data);
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       if (ast_strlen_zero(args.to)) {
+               ast_log(LOG_WARNING, "A 'to' URI is required for MessageSend()\n");
+               pbx_builtin_setvar_helper(chan, "MESSAGE_SEND_STATUS", "INVALID_URI");
+               return 0;
+       }
+
+       ast_channel_lock(chan);
+
+       if (!(ds = ast_channel_datastore_find(chan, &msg_datastore, NULL))) {
+               ast_channel_unlock(chan);
+               ast_log(LOG_WARNING, "No message data found on channel to send.\n");
+               pbx_builtin_setvar_helper(chan, "MESSAGE_SEND_STATUS", "FAILURE");
+               return 0;
+       }
+
+       msg = ds->data;
+       ao2_ref(msg, +1);
+       ast_channel_unlock(chan);
+
+       tech_name = ast_strdupa(args.to);
+       tech_name = strsep(&tech_name, ":");
+
+       {
+               struct ast_msg_tech tmp_msg_tech = {
+                       .name = tech_name,
+               };
+               struct ast_msg_tech_holder tmp_tech_holder = {
+                       .tech = &tmp_msg_tech,
+               };
+
+               tech_holder = ao2_find(msg_techs, &tmp_tech_holder, OBJ_POINTER);
+       }
+
+       if (!tech_holder) {
+               ast_log(LOG_WARNING, "No message technology '%s' found.\n", tech_name);
+               pbx_builtin_setvar_helper(chan, "MESSAGE_SEND_STATUS", "INVALID_PROTOCOL");
+               goto exit_cleanup;
+       }
+
+       /*
+        * The message lock is held here to safely allow the technology
+        * implementation to access the message fields without worrying
+        * that they could change.
+        */
+       ao2_lock(msg);
+       ast_rwlock_rdlock(&tech_holder->tech_lock);
+       if (tech_holder->tech) {
+               res = tech_holder->tech->msg_send(msg, S_OR(args.to, ""),
+                                                       S_OR(args.from, ""));
+       }
+       ast_rwlock_unlock(&tech_holder->tech_lock);
+       ao2_unlock(msg);
+
+       pbx_builtin_setvar_helper(chan, "MESSAGE_SEND_STATUS", res ? "FAILURE" : "SUCCESS");
+
+exit_cleanup:
+       if (tech_holder) {
+               ao2_ref(tech_holder, -1);
+               tech_holder = NULL;
+       }
+
+       ao2_ref(msg, -1);
+
+       return 0;
+}
+
+int ast_msg_tech_register(const struct ast_msg_tech *tech)
+{
+       struct ast_msg_tech_holder tmp_tech_holder = {
+               .tech = tech,
+       };
+       struct ast_msg_tech_holder *tech_holder;
+
+       if ((tech_holder = ao2_find(msg_techs, &tmp_tech_holder, OBJ_POINTER))) {
+               ao2_ref(tech_holder, -1);
+               ast_log(LOG_ERROR, "Message technology already registered for '%s'\n",
+                               tech->name);
+               return -1;
+       }
+
+       if (!(tech_holder = ao2_alloc(sizeof(*tech_holder), NULL))) {
+               return -1;
+       }
+
+       ast_rwlock_init(&tech_holder->tech_lock);
+       tech_holder->tech = tech;
+
+       ao2_link(msg_techs, tech_holder);
+
+       ao2_ref(tech_holder, -1);
+       tech_holder = NULL;
+
+       ast_verb(3, "Message technology handler '%s' registered.\n", tech->name);
+
+       return 0;
+}
+
+int ast_msg_tech_unregister(const struct ast_msg_tech *tech)
+{
+       struct ast_msg_tech_holder tmp_tech_holder = {
+               .tech = tech,
+       };
+       struct ast_msg_tech_holder *tech_holder;
+
+       tech_holder = ao2_find(msg_techs, &tmp_tech_holder, OBJ_POINTER | OBJ_UNLINK);
+
+       if (!tech_holder) {
+               ast_log(LOG_ERROR, "No '%s' message technology found.\n", tech->name);
+               return -1;
+       }
+
+       ast_rwlock_wrlock(&tech_holder->tech_lock);
+       tech_holder->tech = NULL;
+       ast_rwlock_unlock(&tech_holder->tech_lock);
+
+       ao2_ref(tech_holder, -1);
+       tech_holder = NULL;
+
+       ast_verb(3, "Message technology handler '%s' unregistered.\n", tech->name);
+
+       return 0;
+}
+
+/*
+ * \internal
+ * \brief Initialize stuff during Asterisk startup.
+ *
+ * Cleanup isn't a big deal in this function.  If we return non-zero,
+ * Asterisk is going to exit.
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_msg_init(void)
+{
+       int res;
+
+       msg_q_tp = ast_taskprocessor_get("ast_msg_queue", TPS_REF_DEFAULT);
+       if (!msg_q_tp) {
+               return -1;
+       }
+
+       msg_techs = ao2_container_alloc(17, msg_tech_hash, msg_tech_cmp);
+       if (!msg_techs) {
+               return -1;
+       }
+
+       res = __ast_custom_function_register(&msg_function, NULL);
+       res |= __ast_custom_function_register(&msg_data_function, NULL);
+       res |= ast_register_application2(app_msg_send, msg_send_exec, NULL, NULL, NULL);
+
+       return res;
+}
index 47fbcad..ccd5fb2 100644 (file)
@@ -60,6 +60,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/manager.h"
 #include "asterisk/event.h"
 #include "asterisk/devicestate.h"
+#include "asterisk/message.h"
 
 /*** DOCUMENTATION
        <application name="JabberSend" language="en_US">
@@ -373,6 +374,13 @@ static int aji_register_transport(void *data, ikspak *pak);
 static int aji_register_transport2(void *data, ikspak *pak);
 */
 
+static int msg_send_cb(const struct ast_msg *msg, const char *to, const char *from);
+
+static const struct ast_msg_tech msg_tech = {
+       .name = "xmpp",
+       .msg_send = msg_send_cb,
+};
+
 static struct ast_cli_entry aji_cli[] = {
        AST_CLI_DEFINE(aji_do_set_debug, "Enable/Disable Jabber debug"),
        AST_CLI_DEFINE(aji_do_reload, "Reload Jabber configuration"),
@@ -1136,6 +1144,44 @@ static int aji_send_exec(struct ast_channel *chan, const char *data)
        return 0;
 }
 
+static int msg_send_cb(const struct ast_msg *msg, const char *to, const char *from)
+{
+       struct aji_client *client;
+       char *sender;
+       char *dest;
+       int res;
+
+       sender = ast_strdupa(from);
+       strsep(&sender, ":");
+       dest = ast_strdupa(to);
+       strsep(&dest, ":");
+
+       if (ast_strlen_zero(sender)) {
+               ast_log(LOG_ERROR, "MESSAGE(from) of '%s' invalid for xmpp\n", from);
+               return -1;
+       }
+
+       if (!(client = ast_aji_get_client(sender))) {
+               ast_log(LOG_WARNING, "Could not finder account to send from as '%s'\n", sender);
+               return -1;
+       }
+
+
+       ast_debug(1, "Sending message to '%s' from '%s'\n", dest, client->name);
+
+       res = ast_aji_send_chat(client, dest, ast_msg_get_body(msg));
+       if (res != IKS_OK) {
+               ast_log(LOG_WARNING, "Failed to send xmpp message (%d).\n", res);
+       }
+
+       /* 
+        * XXX Reference leak here.  See note with ast_aji_get_client() about the problems
+        * with that function.
+        */
+
+       return res == IKS_OK ? 0 : -1;
+}
+
 /*!
 * \brief Application to send a message to a groupchat.
 * \param chan ast_channel
@@ -2218,6 +2264,7 @@ static void aji_handle_message(struct aji_client *client, ikspak *pak)
 {
        struct aji_message *insert;
        int deleted = 0;
+       struct ast_msg *msg;
 
        ast_debug(3, "client %s received a message\n", client->name);
 
@@ -2248,6 +2295,23 @@ static void aji_handle_message(struct aji_client *client, ikspak *pak)
                ast_debug(3, "message comes from %s\n", insert->from);
        }
 
+       if ((msg = ast_msg_alloc())) {
+               int res;
+
+               res = ast_msg_set_to(msg, "xmpp:%s", client->user);
+               res |= ast_msg_set_from(msg, "xmpp:%s", insert->from);
+               res |= ast_msg_set_body(msg, "%s", insert->message);
+               res |= ast_msg_set_context(msg, "%s", client->context);
+
+               if (res) {
+                       ast_msg_destroy(msg);
+               } else {
+                       ast_msg_queue(msg);
+               }
+
+               msg = NULL;
+       }
+
        /* remove old messages received from this JID
         * and insert received message */
        deleted = delete_old_messages(client, pak->from->partial);
@@ -4248,6 +4312,7 @@ static int aji_create_client(char *label, struct ast_variable *var, int debug)
        ASTOBJ_CONTAINER_MARKALL(&client->buddies);
        ast_copy_string(client->name, label, sizeof(client->name));
        ast_copy_string(client->mid, "aaaaa", sizeof(client->mid));
+       ast_copy_string(client->context, "default", sizeof(client->context));
 
        /* Set default values for the client object */
        client->debug = debug;
@@ -4265,6 +4330,7 @@ static int aji_create_client(char *label, struct ast_variable *var, int debug)
        ast_copy_string(client->statusmessage, "Online and Available", sizeof(client->statusmessage));
        client->priority = 0;
        client->status = IKS_SHOW_AVAILABLE;
+       client->send_to_dialplan = 0;
 
        if (flag) {
                client->authorized = 0;
@@ -4356,6 +4422,10 @@ static int aji_create_client(char *label, struct ast_variable *var, int debug)
                        } else {
                                ast_log(LOG_WARNING, "Unknown presence status: %s\n", var->value);
                        }
+               } else if (!strcasecmp(var->name, "context")) {
+                       ast_copy_string(client->context, var->value, sizeof(client->context));
+               } else if (!strcasecmp(var->name, "sendtodialplan")) {
+                       client->send_to_dialplan = ast_true(var->value) ? 1 : 0;
                }
        /* no transport support in this version */
        /*      else if (!strcasecmp(var->name, "transport"))
@@ -4553,6 +4623,13 @@ static int aji_load_config(int reload)
  * (without the resource string)
  * \param name label or JID
  * \return aji_client.
+ *
+ * XXX \bug This function leads to reference leaks all over the place.
+ *          ASTOBJ_CONTAINER_FIND() returns a reference, but if the
+ *          client is found via the traversal, no reference is returned.
+ *          None of the calling code releases references.  This code needs
+ *          to be changed to always return a reference, and all of the users
+ *          need to be fixed to release them.
  */
 struct aji_client *ast_aji_get_client(const char *name)
 {
@@ -4668,7 +4745,7 @@ static int aji_reload(int reload)
  */
 static int unload_module(void)
 {
-
+       ast_msg_tech_unregister(&msg_tech);
        ast_cli_unregister_multiple(aji_cli, ARRAY_LEN(aji_cli));
        ast_unregister_application(app_ajisend);
        ast_unregister_application(app_ajisendgroup);
@@ -4721,6 +4798,7 @@ static int load_module(void)
        ast_cli_register_multiple(aji_cli, ARRAY_LEN(aji_cli));
        ast_custom_function_register(&jabberstatus_function);
        ast_custom_function_register(&jabberreceive_function);
+       ast_msg_tech_register(&msg_tech);
 
        ast_mutex_init(&messagelock);
        ast_cond_init(&message_received_condition, NULL);