mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 18:55:19 +00:00 
			
		
		
		
	* Document MESSAGE(custom_data) * Update AMI MessageSend documentation * Eliminate a shadowed variable name in msg_func_write() for custom_data. git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/1.8-digiumphones@361502 65c4cc65-6c06-0410-ace0-fbb531ad65f3
		
			
				
	
	
		
			1308 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1308 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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/manager.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="custom_data">
 | |
| 					<para>Write-only.  Mark or unmark all message headers for an outgoing
 | |
| 					message.  The following values can be set:</para>
 | |
| 					<enumlist>
 | |
| 						<enum name="mark_all_outbound">
 | |
| 							<para>Mark all headers for an outgoing message.</para>
 | |
| 						</enum>
 | |
| 						<enum name="clear_all_outbound">
 | |
| 							<para>Unmark all headers for an outgoing message.</para>
 | |
| 						</enum>
 | |
| 					</enumlist>
 | |
| 				</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>
 | |
| 	<manager name="MessageSend" language="en_US">
 | |
| 		<synopsis>
 | |
| 			Send an out of call message to an endpoint.
 | |
| 		</synopsis>
 | |
| 		<syntax>
 | |
| 			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
 | |
| 			<parameter name="To" required="true">
 | |
| 				<para>The URI the message is to be sent to.</para>
 | |
| 			</parameter>
 | |
| 			<parameter name="From">
 | |
| 				<para>A From URI for the message if needed for the
 | |
| 				message technology being used to send this message.</para>
 | |
| 				<note>
 | |
| 					<para>For SIP the from parameter can be a configured peer name
 | |
| 					or in the form of "display-name" <URI>.</para>
 | |
| 				</note>
 | |
| 			</parameter>
 | |
| 			<parameter name="Body">
 | |
| 				<para>The message body text.  This must not contain any newlines as that
 | |
| 				conflicts with the AMI protocol.</para>
 | |
| 			</parameter>
 | |
| 			<parameter name="Base64Body">
 | |
| 				<para>Text bodies requiring the use of newlines have to be base64 encoded
 | |
| 				in this field.  Base64Body will be decoded before being sent out.
 | |
| 				Base64Body takes precedence over Body.</para>
 | |
| 			</parameter>
 | |
| 			<parameter name="Variable">
 | |
| 				<para>Message variable to set, multiple Variable: headers are
 | |
| 				allowed.  The header value is a comma separated list of
 | |
| 				name=value pairs.</para>
 | |
| 			</parameter>
 | |
| 		</syntax>
 | |
| 	</manager>
 | |
|  ***/
 | |
| 
 | |
| 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_ref(struct ast_msg *msg)
 | |
| {
 | |
| 	ao2_ref(msg, 1);
 | |
| 	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;
 | |
| }
 | |
| 
 | |
| int ast_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;
 | |
| 	const char *val = NULL;
 | |
| 
 | |
| 	if (!(data = msg_data_find(msg->vars, name))) {
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	/* Yep, this definitely looks like val would be a dangling pointer
 | |
| 	 * after the ref count is decremented.  As long as the message structure
 | |
| 	 * is used in a thread safe manner, this will not be the case though.
 | |
| 	 * The ast_msg holds a reference to this object in the msg->vars container. */
 | |
| 	val = data->value;
 | |
| 	ao2_ref(data, -1);
 | |
| 
 | |
| 	return val;
 | |
| }
 | |
| 
 | |
| 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 if (!strcasecmp(data, "custom_data")) {
 | |
| 		int outbound = -1;
 | |
| 		if (!strcasecmp(value, "mark_all_outbound")) {
 | |
| 			outbound = 1;
 | |
| 		} else if (!strcasecmp(value, "clear_all_outbound")) {
 | |
| 			outbound = 0;
 | |
| 		} else {
 | |
| 			ast_log(LOG_WARNING, "'%s' is not a valid value for custom_data\n", value);
 | |
| 		}
 | |
| 
 | |
| 		if (outbound != -1) {
 | |
| 			struct msg_data *hdr_data;
 | |
| 			struct ao2_iterator iter = ao2_iterator_init(msg->vars, 0);
 | |
| 
 | |
| 			while ((hdr_data = ao2_iterator_next(&iter))) {
 | |
| 				hdr_data->send = outbound;
 | |
| 				ao2_ref(hdr_data, -1);
 | |
| 			}
 | |
| 			ao2_iterator_destroy(&iter);
 | |
| 		}
 | |
| 	} 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);
 | |
| 
 | |
| 	ast_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;
 | |
| }
 | |
| 
 | |
| static int action_messagesend(struct mansession *s, const struct message *m)
 | |
| {
 | |
| 	const char *to = ast_strdupa(astman_get_header(m, "To"));
 | |
| 	const char *from = astman_get_header(m, "From");
 | |
| 	const char *body = astman_get_header(m, "Body");
 | |
| 	const char *base64body = astman_get_header(m, "Base64Body");
 | |
| 	char base64decoded[1301] = { 0, };
 | |
| 	char *tech_name = NULL;
 | |
| 	struct ast_variable *vars = NULL;
 | |
| 	struct ast_variable *data = NULL;
 | |
| 	struct ast_msg_tech_holder *tech_holder = NULL;
 | |
| 	struct ast_msg *msg;
 | |
| 	int res = -1;
 | |
| 
 | |
| 	if (ast_strlen_zero(to)) {
 | |
| 		astman_send_error(s, m, "No 'To' address specified.");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if (!ast_strlen_zero(base64body)) {
 | |
| 		ast_base64decode((unsigned char *) base64decoded, base64body, sizeof(base64decoded) - 1);
 | |
| 		body = base64decoded;
 | |
| 	}
 | |
| 
 | |
| 	tech_name = ast_strdupa(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) {
 | |
| 		astman_send_error(s, m, "Message technology not found.");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if (!(msg = ast_msg_alloc())) {
 | |
| 		ao2_ref(tech_holder, -1);
 | |
| 		astman_send_error(s, m, "Internal failure\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	data = astman_get_variables(m);
 | |
| 	for (vars = data; vars; vars = vars->next) {
 | |
| 		ast_msg_set_var_outbound(msg, vars->name, vars->value);
 | |
| 	}
 | |
| 
 | |
| 	ast_msg_set_body(msg, "%s", body);
 | |
| 
 | |
| 	ast_rwlock_rdlock(&tech_holder->tech_lock);
 | |
| 	if (tech_holder->tech) {
 | |
| 		res = tech_holder->tech->msg_send(msg, S_OR(to, ""), S_OR(from, ""));
 | |
| 	}
 | |
| 	ast_rwlock_unlock(&tech_holder->tech_lock);
 | |
| 
 | |
| 	ast_variables_destroy(vars);
 | |
| 	ao2_ref(tech_holder, -1);
 | |
| 	ao2_ref(msg, -1);
 | |
| 
 | |
| 	if (res) {
 | |
| 		astman_send_error(s, m, "Message failed to send.");
 | |
| 	} else {
 | |
| 		astman_send_ack(s, m, "Message successfully sent");
 | |
| 	}
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| int ast_msg_send(struct ast_msg *msg, const char *to, const char *from)
 | |
| {
 | |
| 	char *tech_name = NULL;
 | |
| 	struct ast_msg_tech_holder *tech_holder = NULL;
 | |
| 	int res = -1;
 | |
| 
 | |
| 	if (ast_strlen_zero(to)) {
 | |
| 		ao2_ref(msg, -1);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	tech_name = ast_strdupa(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) {
 | |
| 		ao2_ref(msg, -1);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	ast_rwlock_rdlock(&tech_holder->tech_lock);
 | |
| 	if (tech_holder->tech) {
 | |
| 		res = tech_holder->tech->msg_send(msg, S_OR(to, ""), S_OR(from, ""));
 | |
| 	}
 | |
| 	ast_rwlock_unlock(&tech_holder->tech_lock);
 | |
| 
 | |
| 	ao2_ref(tech_holder, -1);
 | |
| 	ao2_ref(msg, -1);
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| 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);
 | |
| 	res |= ast_manager_register_xml("MessageSend", EVENT_FLAG_MESSAGE, action_messagesend);
 | |
| 
 | |
| 	return res;
 | |
| }
 |