diff --git a/CHANGES b/CHANGES index 231d1b606..a01077970 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,19 @@ --- 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. diff --git a/channels/chan_sip.c b/channels/chan_sip.c index cbfb8f733..6556b3a07 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -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); diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h index 4b69010b3..0eb8be350 100644 --- a/channels/sip/include/sip.h +++ b/channels/sip/include/sip.h @@ -211,6 +211,8 @@ #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 */ diff --git a/configs/jabber.conf.sample b/configs/jabber.conf.sample index 098122d91..a83856867 100644 --- a/configs/jabber.conf.sample +++ b/configs/jabber.conf.sample @@ -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. diff --git a/configs/sip.conf.sample b/configs/sip.conf.sample index 179678a39..49277d64f 100644 --- a/configs/sip.conf.sample +++ b/configs/sip.conf.sample @@ -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 diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h index a0b171254..37c49d9af 100644 --- a/include/asterisk/_private.h +++ b/include/asterisk/_private.h @@ -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. diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index c1b99db7d..5798e92bb 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -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 */ diff --git a/include/asterisk/jabber.h b/include/asterisk/jabber.h index 85d459cf4..bbf0a2342 100644 --- a/include/asterisk/jabber.h +++ b/include/asterisk/jabber.h @@ -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 index 000000000..f2fe493b1 --- /dev/null +++ b/include/asterisk/message.h @@ -0,0 +1,242 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * Russell Bryant + * + * 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 + * + * 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__ */ diff --git a/main/asterisk.c b/main/asterisk.c index b3bcfe230..d9e3868c6 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -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()); diff --git a/main/channel.c b/main/channel.c index f60635ebe..9c9d6ca5a 100644 --- a/main/channel.c +++ b/main/channel.c @@ -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 index 000000000..f2c5f4ddb --- /dev/null +++ b/main/message.c @@ -0,0 +1,1112 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * Russell Bryant + * + * 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 + */ + +#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 + + + Create a message or read fields from a message. + + + + Field of the message to get or set. + + + 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. + + + Read-only. The source of the message. When processing an + incoming message, this will be set to the source of the message. + + + 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. + + + + + + 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. + + + MessageSend + + + + + Read or write custom data attached to a message. + + + + Field of the message to get or set. + + + + 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. + NOTE: If you want to set an outbound message to carry data in the + current message, do Set(MESSAGE_DATA(key)=${MESSAGE_DATA(key)}). + + + MessageSend + + + + + Send a text message. + + + + A To URI for the message. + + + A From URI for the message if needed for the + message technology being used to send this message. + + + + Send a text message. The body of the message that will be + sent is what is currently set to MESSAGE(body). + + This application sets the following channel variables: + + + This is the time from dialing a channel until when it is disconnected. + + No handler for the technology part of the URI was found. + + + The protocol handler reported that the URI was not valid. + + + Successfully passed on to the protocol handler, but delivery has not necessarily been guaranteed. + + + The protocol handler reported that it was unabled to deliver the message for some reason. + + + + + + ***/ + +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; +} diff --git a/res/res_jabber.c b/res/res_jabber.c index 47fbcadce..ccd5fb2d9 100644 --- a/res/res_jabber.c +++ b/res/res_jabber.c @@ -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 @@ -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);