mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-25 22:18:07 +00:00 
			
		
		
		
	Setting channel variables with the AMI Originate action caused a deadlock when you set CDR(amaflags) or CDR(accountcode). This path has the channel locked when the CDR function is called. The CDR function then synchronously passes the job to a stasis thread. The stasis handling function then attempts to lock the channel. Deadlock results. * Avoid deadlock by making the CDR function handle setting amaflags and accountcode directly on the channel rather than passing it off to the CDR processing code under a stasis thread to do it. * Made the CHANNEL function and the CDR function process amaflags the same way. * Fixed referencing the wrong message type in cdr_prop_write(). ASTERISK-27460 Change-Id: I5eacb47586bc0b8f8ff76a19bd92d1dc38b75e8f
		
			
				
	
	
		
			708 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			708 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Asterisk -- An open source telephony toolkit.
 | |
|  *
 | |
|  * Copyright (C) 1999-2006, Digium, Inc.
 | |
|  *
 | |
|  * Portions Copyright (C) 2005, Anthony Minessale II
 | |
|  *
 | |
|  * 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  Call Detail Record related dialplan functions
 | |
|  *
 | |
|  * \author Anthony Minessale II
 | |
|  *
 | |
|  * \ingroup functions
 | |
|  */
 | |
| 
 | |
| /*** MODULEINFO
 | |
| 	<support_level>core</support_level>
 | |
|  ***/
 | |
| 
 | |
| #include "asterisk.h"
 | |
| 
 | |
| #include "asterisk/module.h"
 | |
| #include "asterisk/channel.h"
 | |
| #include "asterisk/pbx.h"
 | |
| #include "asterisk/utils.h"
 | |
| #include "asterisk/app.h"
 | |
| #include "asterisk/cdr.h"
 | |
| #include "asterisk/stasis.h"
 | |
| #include "asterisk/stasis_message_router.h"
 | |
| 
 | |
| /*** DOCUMENTATION
 | |
| 	<function name="CDR" language="en_US">
 | |
| 		<synopsis>
 | |
| 			Gets or sets a CDR variable.
 | |
| 		</synopsis>
 | |
| 		<syntax>
 | |
| 			<parameter name="name" required="true">
 | |
| 				<para>CDR field name:</para>
 | |
| 				<enumlist>
 | |
| 					<enum name="clid">
 | |
| 						<para>Caller ID.</para>
 | |
| 					</enum>
 | |
| 					<enum name="lastdata">
 | |
| 						<para>Last application arguments.</para>
 | |
| 					</enum>
 | |
| 					<enum name="disposition">
 | |
| 						<para>The final state of the CDR.</para>
 | |
| 						<enumlist>
 | |
| 							<enum name="0">
 | |
| 								<para><literal>NO ANSWER</literal></para>
 | |
| 							</enum>
 | |
| 							<enum name="1">
 | |
| 								<para><literal>NO ANSWER</literal> (NULL record)</para>
 | |
| 							</enum>
 | |
| 							<enum name="2">
 | |
| 								<para><literal>FAILED</literal></para>
 | |
| 							</enum>
 | |
| 							<enum name="4">
 | |
| 								<para><literal>BUSY</literal></para>
 | |
| 							</enum>
 | |
| 							<enum name="8">
 | |
| 								<para><literal>ANSWERED</literal></para>
 | |
| 							</enum>
 | |
| 							<enum name="16">
 | |
| 								<para><literal>CONGESTION</literal></para>
 | |
| 							</enum>
 | |
| 						</enumlist>
 | |
| 					</enum>
 | |
| 					<enum name="src">
 | |
| 						<para>Source.</para>
 | |
| 					</enum>
 | |
| 					<enum name="start">
 | |
| 						<para>Time the call started.</para>
 | |
| 					</enum>
 | |
| 					<enum name="amaflags">
 | |
| 						<para>R/W the Automatic Message Accounting (AMA) flags on the channel.
 | |
| 						When read from a channel, the integer value will always be returned.
 | |
| 						When written to a channel, both the string format or integer value
 | |
| 						is accepted.</para>
 | |
| 						<enumlist>
 | |
| 							<enum name="1"><para><literal>OMIT</literal></para></enum>
 | |
| 							<enum name="2"><para><literal>BILLING</literal></para></enum>
 | |
| 							<enum name="3"><para><literal>DOCUMENTATION</literal></para></enum>
 | |
| 						</enumlist>
 | |
| 						<warning><para>Accessing this setting is deprecated in CDR. Please use the CHANNEL function instead.</para></warning>
 | |
| 					</enum>
 | |
| 					<enum name="dst">
 | |
| 						<para>Destination.</para>
 | |
| 					</enum>
 | |
| 					<enum name="answer">
 | |
| 						<para>Time the call was answered.</para>
 | |
| 					</enum>
 | |
| 					<enum name="accountcode">
 | |
| 						<para>The channel's account code.</para>
 | |
| 						<warning><para>Accessing this setting is deprecated in CDR. Please use the CHANNEL function instead.</para></warning>
 | |
| 					</enum>
 | |
| 					<enum name="dcontext">
 | |
| 						<para>Destination context.</para>
 | |
| 					</enum>
 | |
| 					<enum name="end">
 | |
| 						<para>Time the call ended.</para>
 | |
| 					</enum>
 | |
| 					<enum name="uniqueid">
 | |
| 						<para>The channel's unique id.</para>
 | |
| 					</enum>
 | |
| 					<enum name="dstchannel">
 | |
| 						<para>Destination channel.</para>
 | |
| 					</enum>
 | |
| 					<enum name="duration">
 | |
| 						<para>Duration of the call.</para>
 | |
| 					</enum>
 | |
| 					<enum name="userfield">
 | |
| 						<para>The channel's user specified field.</para>
 | |
| 					</enum>
 | |
| 					<enum name="lastapp">
 | |
| 						<para>Last application.</para>
 | |
| 					</enum>
 | |
| 					<enum name="billsec">
 | |
| 						<para>Duration of the call once it was answered.</para>
 | |
| 					</enum>
 | |
| 					<enum name="channel">
 | |
| 						<para>Channel name.</para>
 | |
| 					</enum>
 | |
| 					<enum name="sequence">
 | |
| 						<para>CDR sequence number.</para>
 | |
| 					</enum>
 | |
| 				</enumlist>
 | |
| 			</parameter>
 | |
| 			<parameter name="options" required="false">
 | |
| 				<optionlist>
 | |
| 					<option name="f">
 | |
| 						<para>Returns billsec or duration fields as floating point values.</para>
 | |
| 					</option>
 | |
| 					<option name="u">
 | |
| 						<para>Retrieves the raw, unprocessed value.</para>
 | |
| 						<para>For example, 'start', 'answer', and 'end' will be retrieved as epoch
 | |
| 						values, when the <literal>u</literal> option is passed, but formatted as YYYY-MM-DD HH:MM:SS
 | |
| 						otherwise.  Similarly, disposition and amaflags will return their raw
 | |
| 						integral values.</para>
 | |
| 					</option>
 | |
| 				</optionlist>
 | |
| 			</parameter>
 | |
| 		</syntax>
 | |
| 		<description>
 | |
| 			<para>All of the CDR field names are read-only, except for <literal>accountcode</literal>,
 | |
| 			<literal>userfield</literal>, and <literal>amaflags</literal>. You may, however, supply
 | |
| 			a name not on the above list, and create your own variable, whose value can be changed
 | |
| 			with this function, and this variable will be stored on the CDR.</para>
 | |
| 			<note><para>CDRs can only be modified before the bridge between two channels is
 | |
| 			torn down. For example, CDRs may not be modified after the <literal>Dial</literal>
 | |
| 			application has returned.</para></note>
 | |
| 			<para>Example: exten => 1,1,Set(CDR(userfield)=test)</para>
 | |
| 		</description>
 | |
| 	</function>
 | |
| 	<function name="CDR_PROP" language="en_US">
 | |
| 		<synopsis>
 | |
| 			Set a property on a channel's CDR.
 | |
| 		</synopsis>
 | |
| 		<syntax>
 | |
| 			<parameter name="name" required="true">
 | |
| 				<para>The property to set on the CDR.</para>
 | |
| 				<enumlist>
 | |
| 					<enum name="party_a">
 | |
| 						<para>Set this channel as the preferred Party A when
 | |
| 						channels are associated together.</para>
 | |
| 						<para>Write-Only</para>
 | |
| 					</enum>
 | |
| 					<enum name="disable">
 | |
| 						<para>Setting to 1 will disable CDRs for this channel.
 | |
| 						Setting to 0 will enable CDRs for this channel.</para>
 | |
| 						<para>Write-Only</para>
 | |
| 					</enum>
 | |
| 				</enumlist>
 | |
| 			</parameter>
 | |
| 		</syntax>
 | |
| 		<description>
 | |
| 			<para>This function sets a property on a channel's CDR. Properties
 | |
| 			alter the behavior of how the CDR operates for that channel.</para>
 | |
| 		</description>
 | |
| 	</function>
 | |
|  ***/
 | |
| 
 | |
| enum cdr_option_flags {
 | |
| 	OPT_UNPARSED = (1 << 1),
 | |
| 	OPT_FLOAT = (1 << 2),
 | |
| };
 | |
| 
 | |
| AST_APP_OPTIONS(cdr_func_options, {
 | |
| 	AST_APP_OPTION('f', OPT_FLOAT),
 | |
| 	AST_APP_OPTION('u', OPT_UNPARSED),
 | |
| });
 | |
| 
 | |
| struct cdr_func_payload {
 | |
| 	struct ast_channel *chan;
 | |
| 	const char *cmd;
 | |
| 	const char *arguments;
 | |
| 	const char *value;
 | |
| 	void *data;
 | |
| };
 | |
| 
 | |
| struct cdr_func_data {
 | |
| 	char *buf;
 | |
| 	size_t len;
 | |
| };
 | |
| 
 | |
| STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_read_message_type);
 | |
| STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_write_message_type);
 | |
| STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_prop_write_message_type);
 | |
| 
 | |
| static struct timeval cdr_retrieve_time(struct ast_channel *chan, const char *time_name)
 | |
| {
 | |
| 	struct timeval time = { 0 };
 | |
| 	char *value = NULL;
 | |
| 	char tempbuf[128];
 | |
| 	long int tv_sec;
 | |
| 	long int tv_usec;
 | |
| 
 | |
| 	if (ast_strlen_zero(ast_channel_name(chan))) {
 | |
| 		/* Format request on a dummy channel */
 | |
| 		ast_cdr_format_var(ast_channel_cdr(chan), time_name, &value, tempbuf, sizeof(tempbuf), 1);
 | |
| 	} else {
 | |
| 		ast_cdr_getvar(ast_channel_name(chan), time_name, tempbuf, sizeof(tempbuf));
 | |
| 	}
 | |
| 
 | |
| 	/* time.tv_usec is suseconds_t, which could be int or long */
 | |
| 	if (sscanf(tempbuf, "%ld.%ld", &tv_sec, &tv_usec) == 2) {
 | |
| 		time.tv_sec = tv_sec;
 | |
| 		time.tv_usec = tv_usec;
 | |
| 	} else {
 | |
| 		ast_log(AST_LOG_WARNING, "Failed to fully extract '%s' from CDR\n", time_name);
 | |
| 	}
 | |
| 
 | |
| 	return time;
 | |
| }
 | |
| 
 | |
| static void cdr_read_callback(void *data, struct stasis_subscription *sub, struct stasis_message *message)
 | |
| {
 | |
| 	struct cdr_func_payload *payload = stasis_message_data(message);
 | |
| 	struct cdr_func_data *output;
 | |
| 	char *info;
 | |
| 	char *value = NULL;
 | |
| 	struct ast_flags flags = { 0 };
 | |
| 	char tempbuf[512];
 | |
| 	AST_DECLARE_APP_ARGS(args,
 | |
| 		AST_APP_ARG(variable);
 | |
| 		AST_APP_ARG(options);
 | |
| 	);
 | |
| 
 | |
| 	if (cdr_read_message_type() != stasis_message_type(message)) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	ast_assert(payload != NULL);
 | |
| 	output = payload->data;
 | |
| 	ast_assert(output != NULL);
 | |
| 
 | |
| 	if (ast_strlen_zero(payload->arguments)) {
 | |
| 		ast_log(AST_LOG_WARNING, "%s requires a variable (%s(variable[,option]))\n)",
 | |
| 			payload->cmd, payload->cmd);
 | |
| 		return;
 | |
| 	}
 | |
| 	info = ast_strdupa(payload->arguments);
 | |
| 	AST_STANDARD_APP_ARGS(args, info);
 | |
| 
 | |
| 	if (!ast_strlen_zero(args.options)) {
 | |
| 		ast_app_parse_options(cdr_func_options, &flags, NULL, args.options);
 | |
| 	}
 | |
| 
 | |
| 	if (ast_strlen_zero(ast_channel_name(payload->chan))) {
 | |
| 		/* Format request on a dummy channel */
 | |
| 		ast_cdr_format_var(ast_channel_cdr(payload->chan), args.variable, &value, tempbuf, sizeof(tempbuf), ast_test_flag(&flags, OPT_UNPARSED));
 | |
| 		if (ast_strlen_zero(value)) {
 | |
| 			return;
 | |
| 		}
 | |
| 		ast_copy_string(tempbuf, value, sizeof(tempbuf));
 | |
| 		ast_set_flag(&flags, OPT_UNPARSED);
 | |
| 	} else if (ast_cdr_getvar(ast_channel_name(payload->chan), args.variable, tempbuf, sizeof(tempbuf))) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_test_flag(&flags, OPT_FLOAT)
 | |
| 		&& (!strcasecmp("billsec", args.variable) || !strcasecmp("duration", args.variable))) {
 | |
| 		struct timeval start = cdr_retrieve_time(payload->chan, !strcasecmp("billsec", args.variable) ? "answer" : "start");
 | |
| 		struct timeval finish = cdr_retrieve_time(payload->chan, "end");
 | |
| 		double delta;
 | |
| 
 | |
| 		if (ast_tvzero(finish)) {
 | |
| 			finish = ast_tvnow();
 | |
| 		}
 | |
| 
 | |
| 		if (ast_tvzero(start)) {
 | |
| 			delta = 0.0;
 | |
| 		} else {
 | |
| 			delta = (double)(ast_tvdiff_us(finish, start) / 1000000.0);
 | |
| 		}
 | |
| 		snprintf(tempbuf, sizeof(tempbuf), "%lf", delta);
 | |
| 
 | |
| 	} else if (!ast_test_flag(&flags, OPT_UNPARSED)) {
 | |
| 		if (!strcasecmp("start", args.variable)
 | |
| 			|| !strcasecmp("end", args.variable)
 | |
| 			|| !strcasecmp("answer", args.variable)) {
 | |
| 			struct timeval fmt_time;
 | |
| 			struct ast_tm tm;
 | |
| 			/* tv_usec is suseconds_t, which could be int or long */
 | |
| 			long int tv_sec;
 | |
| 			long int tv_usec;
 | |
| 
 | |
| 			if (sscanf(tempbuf, "%ld.%ld", &tv_sec, &tv_usec) != 2) {
 | |
| 				ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
 | |
| 					args.variable, tempbuf, ast_channel_name(payload->chan));
 | |
| 				return;
 | |
| 			}
 | |
| 			if (tv_sec) {
 | |
| 				fmt_time.tv_sec = tv_sec;
 | |
| 				fmt_time.tv_usec = tv_usec;
 | |
| 				ast_localtime(&fmt_time, &tm, NULL);
 | |
| 				ast_strftime(tempbuf, sizeof(tempbuf), "%Y-%m-%d %T", &tm);
 | |
| 			} else {
 | |
| 				tempbuf[0] = '\0';
 | |
| 			}
 | |
| 		} else if (!strcasecmp("disposition", args.variable)) {
 | |
| 			int disposition;
 | |
| 
 | |
| 			if (sscanf(tempbuf, "%8d", &disposition) != 1) {
 | |
| 				ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
 | |
| 					args.variable, tempbuf, ast_channel_name(payload->chan));
 | |
| 				return;
 | |
| 			}
 | |
| 			snprintf(tempbuf, sizeof(tempbuf), "%s", ast_cdr_disp2str(disposition));
 | |
| 		} else if (!strcasecmp("amaflags", args.variable)) {
 | |
| 			int amaflags;
 | |
| 
 | |
| 			if (sscanf(tempbuf, "%8d", &amaflags) != 1) {
 | |
| 				ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
 | |
| 					args.variable, tempbuf, ast_channel_name(payload->chan));
 | |
| 				return;
 | |
| 			}
 | |
| 			snprintf(tempbuf, sizeof(tempbuf), "%s", ast_channel_amaflags2string(amaflags));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ast_copy_string(output->buf, tempbuf, output->len);
 | |
| }
 | |
| 
 | |
| static void cdr_write_callback(void *data, struct stasis_subscription *sub, struct stasis_message *message)
 | |
| {
 | |
| 	struct cdr_func_payload *payload;
 | |
| 	struct ast_flags flags = { 0 };
 | |
| 	AST_DECLARE_APP_ARGS(args,
 | |
| 		AST_APP_ARG(variable);
 | |
| 		AST_APP_ARG(options);
 | |
| 	);
 | |
| 	char *parse;
 | |
| 
 | |
| 	if (cdr_write_message_type() != stasis_message_type(message)) {
 | |
| 		return;
 | |
| 	}
 | |
| 	payload = stasis_message_data(message);
 | |
| 	if (!payload) {
 | |
| 		return;
 | |
| 	}
 | |
| 	if (ast_strlen_zero(payload->arguments)
 | |
| 		|| !payload->value) {
 | |
| 		/* Sanity check.  cdr_write() could never send these bad messages */
 | |
| 		ast_assert(0);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	parse = ast_strdupa(payload->arguments);
 | |
| 	AST_STANDARD_APP_ARGS(args, parse);
 | |
| 
 | |
| 	if (!ast_strlen_zero(args.options)) {
 | |
| 		ast_app_parse_options(cdr_func_options, &flags, NULL, args.options);
 | |
| 	}
 | |
| 
 | |
| 	/* These are already handled by cdr_write() */
 | |
| 	ast_assert(strcasecmp(args.variable, "accountcode")
 | |
| 		&& strcasecmp(args.variable, "peeraccount")
 | |
| 		&& strcasecmp(args.variable, "amaflags"));
 | |
| 
 | |
| 	if (!strcasecmp(args.variable, "userfield")) {
 | |
| 		ast_cdr_setuserfield(ast_channel_name(payload->chan), payload->value);
 | |
| 	} else {
 | |
| 		ast_cdr_setvar(ast_channel_name(payload->chan), args.variable, payload->value);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void cdr_prop_write_callback(void *data, struct stasis_subscription *sub, struct stasis_message *message)
 | |
| {
 | |
| 	struct cdr_func_payload *payload = stasis_message_data(message);
 | |
| 	enum ast_cdr_options option;
 | |
| 	char *parse;
 | |
| 	AST_DECLARE_APP_ARGS(args,
 | |
| 		AST_APP_ARG(variable);
 | |
| 		AST_APP_ARG(options);
 | |
| 	);
 | |
| 
 | |
| 	if (cdr_prop_write_message_type() != stasis_message_type(message)) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (!payload) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_strlen_zero(payload->arguments)) {
 | |
| 		ast_log(AST_LOG_WARNING, "%s requires a variable (%s(variable)=value)\n)",
 | |
| 			payload->cmd, payload->cmd);
 | |
| 		return;
 | |
| 	}
 | |
| 	if (ast_strlen_zero(payload->value)) {
 | |
| 		ast_log(AST_LOG_WARNING, "%s requires a value (%s(variable)=value)\n)",
 | |
| 			payload->cmd, payload->cmd);
 | |
| 		return;
 | |
| 	}
 | |
| 	parse = ast_strdupa(payload->arguments);
 | |
| 	AST_STANDARD_APP_ARGS(args, parse);
 | |
| 
 | |
| 	if (!strcasecmp("party_a", args.variable)) {
 | |
| 		option = AST_CDR_FLAG_PARTY_A;
 | |
| 	} else if (!strcasecmp("disable", args.variable)) {
 | |
| 		option = AST_CDR_FLAG_DISABLE_ALL;
 | |
| 	} else {
 | |
| 		ast_log(AST_LOG_WARNING, "Unknown option %s used with %s\n", args.variable, payload->cmd);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_true(payload->value)) {
 | |
| 		ast_cdr_set_property(ast_channel_name(payload->chan), option);
 | |
| 	} else {
 | |
| 		ast_cdr_clear_property(ast_channel_name(payload->chan), option);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| static int cdr_read(struct ast_channel *chan, const char *cmd, char *parse,
 | |
| 		    char *buf, size_t len)
 | |
| {
 | |
| 	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
 | |
| 	RAII_VAR(struct cdr_func_payload *, payload, NULL, ao2_cleanup);
 | |
| 	struct cdr_func_data output = { 0, };
 | |
| 
 | |
| 	if (!chan) {
 | |
| 		ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if (!cdr_read_message_type()) {
 | |
| 		ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: message type not available\n",
 | |
| 			ast_channel_name(chan));
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	payload = ao2_alloc(sizeof(*payload), NULL);
 | |
| 	if (!payload) {
 | |
| 		return -1;
 | |
| 	}
 | |
| 	payload->chan = chan;
 | |
| 	payload->cmd = cmd;
 | |
| 	payload->arguments = parse;
 | |
| 	payload->data = &output;
 | |
| 
 | |
| 	buf[0] = '\0';/* Ensure the buffer is initialized. */
 | |
| 	output.buf = buf;
 | |
| 	output.len = len;
 | |
| 
 | |
| 	message = stasis_message_create(cdr_read_message_type(), payload);
 | |
| 	if (!message) {
 | |
| 		ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: unable to create message\n",
 | |
| 			ast_channel_name(chan));
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	/* If this is a request on a dummy channel, we're doing post-processing on an
 | |
| 	 * already dispatched CDR. Simply call the callback to calculate the value and
 | |
| 	 * return, instead of posting to Stasis as we would for a running channel.
 | |
| 	 */
 | |
| 	if (ast_strlen_zero(ast_channel_name(chan))) {
 | |
| 		cdr_read_callback(NULL, NULL, message);
 | |
| 	} else {
 | |
| 		RAII_VAR(struct stasis_message_router *, router, ast_cdr_message_router(), ao2_cleanup);
 | |
| 
 | |
| 		if (!router) {
 | |
| 			ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: no message router\n",
 | |
| 				ast_channel_name(chan));
 | |
| 			return -1;
 | |
| 		}
 | |
| 		stasis_message_router_publish_sync(router, message);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int cdr_write(struct ast_channel *chan, const char *cmd, char *arguments,
 | |
| 	const char *value)
 | |
| {
 | |
| 	struct stasis_message *message;
 | |
| 	struct cdr_func_payload *payload;
 | |
| 	struct stasis_message_router *router;
 | |
| 	AST_DECLARE_APP_ARGS(args,
 | |
| 		AST_APP_ARG(variable);
 | |
| 		AST_APP_ARG(options);
 | |
| 	);
 | |
| 	char *parse;
 | |
| 
 | |
| 	if (!chan) {
 | |
| 		ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
 | |
| 		return -1;
 | |
| 	}
 | |
| 	if (ast_strlen_zero(arguments)) {
 | |
| 		ast_log(LOG_WARNING, "%s requires a variable (%s(variable)=value)\n)",
 | |
| 			cmd, cmd);
 | |
| 		return -1;
 | |
| 	}
 | |
| 	if (!value) {
 | |
| 		ast_log(LOG_WARNING, "%s requires a value (%s(variable)=value)\n)",
 | |
| 			cmd, cmd);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	parse = ast_strdupa(arguments);
 | |
| 	AST_STANDARD_APP_ARGS(args, parse);
 | |
| 
 | |
| 	/* These CDR variables are no longer supported or set directly on the channel */
 | |
| 	if (!strcasecmp(args.variable, "accountcode")) {
 | |
| 		ast_log(LOG_WARNING, "Using the %s function to set 'accountcode' is deprecated. Please use the CHANNEL function instead.\n",
 | |
| 			cmd);
 | |
| 		ast_channel_lock(chan);
 | |
| 		ast_channel_accountcode_set(chan, value);
 | |
| 		ast_channel_unlock(chan);
 | |
| 		return 0;
 | |
| 	}
 | |
| 	if (!strcasecmp(args.variable, "amaflags")) {
 | |
| 		int amaflags;
 | |
| 
 | |
| 		ast_log(LOG_WARNING, "Using the %s function to set 'amaflags' is deprecated. Please use the CHANNEL function instead.\n",
 | |
| 			cmd);
 | |
| 		if (isdigit(*value)) {
 | |
| 			if (sscanf(value, "%30d", &amaflags) != 1) {
 | |
| 				amaflags = AST_AMA_NONE;
 | |
| 			}
 | |
| 		} else {
 | |
| 			amaflags = ast_channel_string2amaflag(value);
 | |
| 		}
 | |
| 		ast_channel_lock(chan);
 | |
| 		ast_channel_amaflags_set(chan, amaflags);
 | |
| 		ast_channel_unlock(chan);
 | |
| 		return 0;
 | |
| 	}
 | |
| 	if (!strcasecmp(args.variable, "peeraccount")) {
 | |
| 		ast_log(LOG_WARNING, "The 'peeraccount' setting is not supported. Please set the 'accountcode' on the appropriate channel using the CHANNEL function.\n");
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	/* The remaining CDR variables are handled by CDR processing code */
 | |
| 	if (!cdr_write_message_type()) {
 | |
| 		ast_log(LOG_WARNING, "Failed to manipulate CDR for channel %s: message type not available\n",
 | |
| 			ast_channel_name(chan));
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	payload = ao2_alloc(sizeof(*payload), NULL);
 | |
| 	if (!payload) {
 | |
| 		return -1;
 | |
| 	}
 | |
| 	payload->chan = chan;
 | |
| 	payload->cmd = cmd;
 | |
| 	payload->arguments = arguments;
 | |
| 	payload->value = value;
 | |
| 
 | |
| 	message = stasis_message_create(cdr_write_message_type(), payload);
 | |
| 	ao2_ref(payload, -1);
 | |
| 	if (!message) {
 | |
| 		ast_log(LOG_WARNING, "Failed to manipulate CDR for channel %s: unable to create message\n",
 | |
| 			ast_channel_name(chan));
 | |
| 		return -1;
 | |
| 	}
 | |
| 	router = ast_cdr_message_router();
 | |
| 	if (!router) {
 | |
| 		ast_log(LOG_WARNING, "Failed to manipulate CDR for channel %s: no message router\n",
 | |
| 			ast_channel_name(chan));
 | |
| 		ao2_ref(message, -1);
 | |
| 		return -1;
 | |
| 	}
 | |
| 	stasis_message_router_publish_sync(router, message);
 | |
| 	ao2_ref(router, -1);
 | |
| 	ao2_ref(message, -1);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int cdr_prop_write(struct ast_channel *chan, const char *cmd, char *parse,
 | |
| 		     const char *value)
 | |
| {
 | |
| 	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
 | |
| 	RAII_VAR(struct cdr_func_payload *, payload, NULL, ao2_cleanup);
 | |
| 	RAII_VAR(struct stasis_message_router *, router, ast_cdr_message_router(), ao2_cleanup);
 | |
| 
 | |
| 	if (!chan) {
 | |
| 		ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if (!router) {
 | |
| 		ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: no message router\n",
 | |
| 			ast_channel_name(chan));
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if (!cdr_prop_write_message_type()) {
 | |
| 		ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: message type not available\n",
 | |
| 			ast_channel_name(chan));
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	payload = ao2_alloc(sizeof(*payload), NULL);
 | |
| 	if (!payload) {
 | |
| 		return -1;
 | |
| 	}
 | |
| 	payload->chan = chan;
 | |
| 	payload->cmd = cmd;
 | |
| 	payload->arguments = parse;
 | |
| 	payload->value = value;
 | |
| 
 | |
| 	message = stasis_message_create(cdr_prop_write_message_type(), payload);
 | |
| 	if (!message) {
 | |
| 		ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: unable to create message\n",
 | |
| 			ast_channel_name(chan));
 | |
| 		return -1;
 | |
| 	}
 | |
| 	stasis_message_router_publish_sync(router, message);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static struct ast_custom_function cdr_function = {
 | |
| 	.name = "CDR",
 | |
| 	.read = cdr_read,
 | |
| 	.write = cdr_write,
 | |
| };
 | |
| 
 | |
| static struct ast_custom_function cdr_prop_function = {
 | |
| 	.name = "CDR_PROP",
 | |
| 	.read = NULL,
 | |
| 	.write = cdr_prop_write,
 | |
| };
 | |
| 
 | |
| static int unload_module(void)
 | |
| {
 | |
| 	RAII_VAR(struct stasis_message_router *, router, ast_cdr_message_router(), ao2_cleanup);
 | |
| 	int res = 0;
 | |
| 
 | |
| 	if (router) {
 | |
| 		stasis_message_router_remove(router, cdr_prop_write_message_type());
 | |
| 		stasis_message_router_remove(router, cdr_write_message_type());
 | |
| 		stasis_message_router_remove(router, cdr_read_message_type());
 | |
| 	}
 | |
| 	STASIS_MESSAGE_TYPE_CLEANUP(cdr_read_message_type);
 | |
| 	STASIS_MESSAGE_TYPE_CLEANUP(cdr_write_message_type);
 | |
| 	STASIS_MESSAGE_TYPE_CLEANUP(cdr_prop_write_message_type);
 | |
| 	res |= ast_custom_function_unregister(&cdr_function);
 | |
| 	res |= ast_custom_function_unregister(&cdr_prop_function);
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static int load_module(void)
 | |
| {
 | |
| 	RAII_VAR(struct stasis_message_router *, router, ast_cdr_message_router(), ao2_cleanup);
 | |
| 	int res = 0;
 | |
| 
 | |
| 	if (!router) {
 | |
| 		return AST_MODULE_LOAD_DECLINE;
 | |
| 	}
 | |
| 
 | |
| 	res |= STASIS_MESSAGE_TYPE_INIT(cdr_read_message_type);
 | |
| 	res |= STASIS_MESSAGE_TYPE_INIT(cdr_write_message_type);
 | |
| 	res |= STASIS_MESSAGE_TYPE_INIT(cdr_prop_write_message_type);
 | |
| 	res |= ast_custom_function_register(&cdr_function);
 | |
| 	res |= ast_custom_function_register(&cdr_prop_function);
 | |
| 	res |= stasis_message_router_add(router, cdr_prop_write_message_type(),
 | |
| 	                                 cdr_prop_write_callback, NULL);
 | |
| 	res |= stasis_message_router_add(router, cdr_write_message_type(),
 | |
| 	                                 cdr_write_callback, NULL);
 | |
| 	res |= stasis_message_router_add(router, cdr_read_message_type(),
 | |
| 	                                 cdr_read_callback, NULL);
 | |
| 
 | |
| 	if (res) {
 | |
| 		unload_module();
 | |
| 		return AST_MODULE_LOAD_DECLINE;
 | |
| 	}
 | |
| 	return AST_MODULE_LOAD_SUCCESS;
 | |
| }
 | |
| 
 | |
| AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call Detail Record (CDR) dialplan functions");
 |